์ด์ „ ๊ธ€์—์„œ URLSession, URL, URLRequest ๋ฅผ ์ƒ์„ฑํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ์•Œ์•„๋ณด์•˜๋‹ค. ์ด๋ฒˆ์—๋Š” ์–ด๋–ค Task๋“ค์ด ์žˆ๋Š”์ง€ ์•Œ์•„๋ณด์ž.

Task

Apple์—์„œ ๊ธฐ๋ณธ์ ์œผ๋กœ ์ œ์•ˆํ•˜๋Š” task์˜ ์ข…๋ฅ˜๋Š” ์ด 3๊ฐ€์ง€์ด๋‹ค. ํ•˜์ง€๋งŒ ๋ฌธ์„œ๋ฅผ ๋ณธ ๊ฒฐ๊ณผ, URLSession์—์„œ ํ•จ์ˆ˜๋กœ ์ œ์•ˆํ•˜๋Š” task์˜ ์ข…๋ฅ˜๋Š” ์ด 5๊ฐœ ์˜€๋‹ค.

์—ฌ๊ธฐ์„œ ์ƒ์œ„ 3๊ฐœ๊ฐ€ ๋ฌธ์„œ์—์„œ ๊ธฐ๋ณธ์ ์œผ๋กœ ์„ค๋ช…ํ•˜๋Š” Task์ด๋‹ค.

URLSessionTask

class URLSessionTask : NSObject

๊ตฌ์ฒด์ ์œผ๋กœ Task์— ๋Œ€ํ•ด์„œ ์„ค๋ช…ํ•˜๊ธฐ ์ „์—, ๊ทธ ์ƒ์œ„์— ์žˆ๋Š” class๋ฅผ ์•Œ๋ฉด ์ข‹๋‹ค. Task๋Š” ํ•ญ์ƒ URLSession ์˜ ๋ถ€๋ถ„์œผ๋กœ ์žˆ์–ด์•ผ ํ•œ๋‹ค. ์ฆ‰, task๋ฅผ ๋งŒ๋“ค๋ ค๋ฉด URLSession instance์— ์žˆ๋Š” method๋ฅผ callํ•˜๋Š” ๊ฒƒ์œผ๋กœ ์‹œ์ž‘ํ•ด์•ผ ํ•œ๋‹ค๋Š” ๋ง์ด๋‹ค.

์•ž์œผ๋กœ ์„ค๋ช…ํ•  ๋ชจ๋“  task๋ฅผ ๋งŒ๋“  ํ›„์—, resume()๋ฉ”์„œ๋“œ๋ฅผ callํ•˜๋Š” ๊ฒƒ์œผ๋กœ ์‹œ์ž‘ํ•  ์ˆ˜ ์žˆ๋‹ค. Session์€ ์ด task ๊ฐ์ฒด๋ฅผ ์ž‘์—…์ด ์™„๋ฃŒ/์‹คํŒจ ํ•  ๋•Œ๊นŒ์ง€ strongํ•˜๊ฒŒ ๋ถ™๋“ค๊ณ  ์žˆ๋‹ค. ๊ทธ๋ ‡๊ธฐ ๋•Œ๋ฌธ์— ๊ตณ์ด ํ•„์š”ํ•˜์ง€ ์•Š๋‹ค๋ฉด task ๊ฐ์ฒด๋ฅผ reference๋กœ ๋“ค๊ณ  ์žˆ์„ ํ•„์š”๊ฐ€ ์—†๋‹ค.

๋ชจ๋“  Task property๋Š” KVO๋ฅผ ์ง€์›ํ•œ๋‹ค๊ณ  ํ•œ๋‹ค.

Task๋ฅผ ํ†ตํ•ด ์‘๋‹ต์„ ๋ฐ›๋Š” ๋ฐฉ์‹์€ ๋‘๊ฐ€์ง€๊ฐ€ ์žˆ๋‹ค. ํ•˜๋‚˜๋Š” Completion Handler, ๋‘˜์งธ๋Š” Delegate์ด๋‹ค.

URLSessionDataTask

class URLSessionDataTask : URLSessionTask

๋‹ค์šด๋กœ๋“œํ•œ data๋ฅผ memory์— load๋œ app์œผ๋กœ ์ง์ ‘ ๋ฐ˜ํ™˜ํ•˜๋Š” task

URLSessionDataTask์€ ๊ธฐ๋ณธ์ ์œผ๋กœ Request๋ฅผ ๋ณด๋‚ด๊ณ  Response๋กœ ํ•˜๋‚˜์ด์ƒ์˜ NSData ๊ฐ์ฒด๋ฅผ ๋ฉ”๋ชจ๋ฆฌ์— ๋ฐ›๋Š”๋‹ค. URLSession์„ ๋งŒ๋“ค ๋•Œ, ๋“ค์–ด๊ฐ€๋Š” configuration์œผ๋กœ default, ephemeral์—์„œ ์ง€์›ํ•œ๋‹ค. ์ถ”๊ฐ€์ ์œผ๋กœ shared instance์—์„œ๋„ dataTask๋ฅผ ์ง€์›ํ•œ๋‹ค. ํ•˜์ง€๋งŒ background session์—์„œ๋Š” ์ง€์›ํ•˜์ง€ ์•Š๋Š”๋‹ค.

// ์‚ฌ์šฉ ์˜ˆ์‹œ
let url = URL(string: "https://www.example.com/")!
let task = URLSession.shared.dataTask(with: url) { data, response, error in
    if let error = error {
        self.handleClientError(error)
        return
    }
    guard let httpResponse = response as? HTTPURLResponse,
        (200...299).contains(httpResponse.statusCode) else {
        self.handleServerError(response)
        return
    }
    if let mimeType = httpResponse.mimeType, mimeType == "text/html",
        let data = data,
        let string = String(data: data, encoding: .utf8) {
        DispatchQueue.main.async {
            self.webView.loadHTMLString(string, baseURL: url)
        }
    }
}
task.resume()
// ์ฃผ์š” Delegate
urlSession(_:task:didSendBodyData:totalBytesSent:totalBytesExpectedToSend:) // body ์—…๋กœ๋“œ ๊ฒฝ์šฐ
urlSession(_:dataTask:didReceive:completionHandler:) // ์ดˆ๊ธฐ ์‘๋‹ต์„ ๋ฐ›์€ ๊ฒฝ์šฐ
urlSession(_:dataTask:didReceive:) // ๋ฐ์ดํ„ฐ๋ฅผ ๋ฐ›๊ณ  ์žˆ๋Š” ๋™์•ˆ
urlSession(_:dataTask:willCacheResponse:completionHandler:) // ๋ฐ์ดํ„ฐ๊ฐ€ ๋‹ค ๋ฐ›์•„์ง„ ๊ฒฝ์šฐ

์ถ”๊ฐ€ ์ •๋ณด๋Š” Fetching Website Data into Memory๋ฅผ ํ™•์ธํ•˜์ž.

URLSessionUploadTask

class URLSessionUploadTask : URLSessionDataTask

์ด๋…€์„๋งŒ URLSessionDataTask๋ฅผ ์ƒ์†๋ฐ›๋Š”๋‹ค๋Š” ๊ฒƒ์ด ํŠน์ดํ•˜๋‹ค. ์ด์œ ๋Š” ๊ธฐ๋ณธ์ ์œผ๋กœ URLSessionDataTask์™€ ์œ ์‚ฌํ•˜์ง€๋งŒ, request body๋ฅผ ์ œ๊ณตํ•˜๋Š” ๋ฐฉ์‹์ด ์ข€ ๋” ์‰ฝ๋‹ค๊ณ  ํ•œ๋‹ค. ๊ทธ๋ž˜์„œ ์„œ๋ฒ„์˜ ์‘๋‹ต์„ ๋ฐ›๊ธฐ ์ „์— body์— ๋ฐ์ดํ„ฐ๋ฅผ ๋„ฃ์–ด ์—…๋กœ๋“œํ•  ์ˆ˜ ์žˆ๋‹ค. URLSessionDataTask์™€ ๋‹ฌ๋ฆฌ background session์„ ์ง€์›ํ•œ๋‹ค.

์ฆ‰, ์š”์•ฝํ•˜๋ฉด HTTP Method POST, PUT ๊ณผ ๊ฐ™์ด body๊ฐ€ ํ•„์š”ํ•œ ๋…€์„๋“ค์„ ์‚ฌ์šฉํ•  ๋•Œ ์‚ฌ์šฉํ•˜๋ฉด ๋œ๋‹ค. dataTask๋กœ๋„ ํ•  ์ˆ˜ ์žˆ์ง€๋งŒ ์ด๋…€์„์ด ๋ณด๋‹ค ์‰ฌ์šด ๋ฐฉํ–ฅ์ด๋‹ค. Delegate๋กœ upload ์ •๋„๋ฅผ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋‹ค.

// ์‚ฌ์šฉ ์˜ˆ์‹œ
struct Order: Codable {
    let customerId: String
    let items: [String]
}
 
// ...
 
let order = Order(customerId: "12345",
                  items: ["Cheese pizza", "Diet soda"])
guard let uploadData = try? JSONEncoder().encode(order) else {
    return
}
 
let url = URL(string: "https://example.com/post")!
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
 
let task = URLSession.shared.uploadTask(with: request, from: uploadData) { data, response, error in
    if let error = error {
        print ("error: \(error)")
        return
    }
    guard let response = response as? HTTPURLResponse,
        (200...299).contains(response.statusCode) else {
        print ("server error")
        return
    }
    if let mimeType = response.mimeType,
        mimeType == "application/json",
        let data = data,
        let dataString = String(data: data, encoding: .utf8) {
        print ("got data: \(dataString)")
    }
}
task.resume()

delegate์˜ ๊ฒฝ์šฐ URLSessionDataTask์—์„œ ์„ค๋ช…ํ•œ ๋…€์„๋“ค์„ ์ž˜ ์‚ฌ์šฉํ•˜๋ฉด ๋œ๋‹ค. ์ด๋…€์„์€ ์–ด๋–ป๊ฒŒ ๋ณด๋ฉด sugar api์ด๋‹ค. ์ตœ ๋Œ€ํ•œ์˜ ์˜ˆ์‹œ์—์„œ๋Š” upload์‹œ URLSessionUploadTask๋ฅผ ์‚ฌ์šฉํ•˜์ง€ ์•Š๋Š” ์˜ˆ์‹œ๋ฅผ ์ ์–ด๋‘์—ˆ๋‹ค. ์ด ๊ฒฝ์šฐ๋Š” Codable์„ ์‚ฌ์šฉํ•˜์—ฌ body๋ฅผ ๋งŒ๋“ค์—ˆ๊ณ , ํ•˜๋‹จ์—์„œ๋Š” ๊ทธ๋ƒฅ ๋งŒ๋“ค์—ˆ๋‹ค. ํฌ๊ฒŒ ๋‹ค๋ฅด์ง€ ์•Š๊ธฐ ๋•Œ๋ฌธ์— ๋น„๊ตํ•˜๋ฉด์„œ ๋ณด๋ฉด ์ข‹์„ ๋“ฏํ•˜๋‹ค. ์ถ”๊ฐ€์ •๋ณด๋Š” Uploading Data to a Website๋ฅผ ํ™•์ธํ•˜์ž.

URLSessionDownloadTask

class URLSessionDownloadTask : URLSessionTask

์ด๋…€์„์€ ์œ„์˜ ์นœ๊ตฌ๋“ค๊ณผ ๋‹ฌ๋ฆฌ resource๋ฅผ disk์— ์ง์ ‘ ๋‹ค์šด๋กœ๋“œํ•œ๋‹ค. disk์— ์–ด๋–ค ์‹์œผ๋กœ ์ €์žฅํ•˜๋ƒ๋ฉด, ์ผ๋‹จ ์ž„์‹œ ํŒŒ์ผ๋กœ ์ €์žฅํ•œ๋‹ค. completion handler๋‚˜ delegate๋กœ ๋‹ค์šด์ด ์™„๋ฃŒ๋œ ์‹œ์ ์— ์ž„์‹œ ํŒŒ์ผ์˜ URL์„ ์–ป์„ ์ˆ˜ ์žˆ๋‹ค.

๋ชจ๋“  type์˜ ์„ธ์…˜์—์„œ ์‚ฌ์šฉ๊ฐ€๋Šฅํ•˜๋‹ค. ๋งŒ์•ฝ background์—์„œ ์‚ฌ์šฉํ•œ๋‹ค๋ฉด, suspend๊ฑฐ๋‚˜ running ์ƒํƒœ๊ฐ€ ์•„๋‹Œ ์ƒํ™ฉ์—์„œ๋„ ๋‹ค์šด๋กœ๋“œ๊ฐ€ ๊ณ„์†๋œ๋‹ค. (์ด ์ด์œ ๋Š” configuration) ์ถ”๊ฐ€์ ์œผ๋กœ downliad task๋ฅผ pause, cancel, resume ํ•  ์ˆ˜ ์žˆ๋‹ค. network connectivity๊ฐ€ ๋ฌธ์ œ ์ƒ๊ธด ๊ฒฝ์šฐ fail๋˜๋Š”๋ฐ, ์ด ๋•Œ resume์„ ์‚ฌ์šฉํ•˜๋ฉด ๋‹ค์‹œ ์ด์–ด ๋ฐ›์„ ์ˆ˜ ์žˆ๋‹ค.

// ์‚ฌ์šฉ ์˜ˆ์‹œ
let downloadTask = URLSession.shared.downloadTask(with: request) { [weak self] url, response, _ in
    if let data = self?.cache.cachedResponse(for: request)?.data {
        completion(.success(data))
        return
    }
    if let response = response, let localURL = url,
        self?.cache.cachedResponse(for: request) == nil,
        let data = try? Data(contentsOf: localURL, options: [.mappedIfSafe]) {
        self?.cache.storeCachedResponse(CachedURLResponse(response: response, data: data), for: request)
        completion(.success(data))
    }
}
downloadTask.resume()
// ์ฃผ์š” Delegate
urlSession(_:downloadTask:didWriteData:totalBytesWritten:totalBytesExpectedToWrite:) // ๋‹ค์šด๋กœ๋“œ ์ค‘
urlSession(_:downloadTask:didFinishDownloadingTo:) // ์„ฑ๊ณต์ ์ธ ๋‹ค์šด๋กœ๋“œ
urlSession(_:task:didCompleteWithError:) // ์‹คํŒจํ•œ ๋‹ค์šด๋กœ๋“œ

URLSessionStreamTask

class URLSessionStreamTask : URLSessionTask

URLSessionStreamTask์€ host ์ด๋ฆ„๊ณผ port ๋กœ๋ถ€ํ„ฐ TCP/IP Connection์„ ์„ค์ •ํ•œ๋‹ค. ์‚ฌ์šฉํ•ด๋ณด์งˆ ๋ชปํ•ด์„œ ๋‚˜์ค‘์— ์ถ”๊ฐ€๋กœ ๋ฐ˜์˜ํ•˜๋„๋ก ํ•˜๊ฒ ๋‹ค.

URLSessionWebSocketTask

class URLSessionWebSocketTask : URLSessionTask

Socket ํ†ต์‹ ์„ ํŽธํ•˜๊ฒŒ ํ•ด์ฃผ๋Š” ๋…€์„์ธ ๋“ฏ ํ•˜๋‹ค. ์‚ฌ์šฉํ•ด๋ณด์งˆ ๋ชปํ•ด์„œ ๋‚˜์ค‘์— ์ถ”๊ฐ€๋กœ ๋ฐ˜์˜ํ•˜๋„๋ก ํ•˜๊ฒ ๋‹ค.

Code

๋งˆ์ง€๋ง‰์œผ๋กœ ์ „์ฒด ์ฝ”๋“œ๋ฅผ ํ•œ๋ฒˆ ํ›‘์–ด๋ณด์ž.

// Swift 5.1, iOS 13 ํ™˜๊ฒฝ์—์„œ ์ •์ƒ์ ์œผ๋กœ ๋™์ž‘ํ•ฉ๋‹ˆ๋‹ค.
class NetworkHandler {
    static func postData(urlString: String, body: String) {
        // ์„ธ์…˜ ์ƒ์„ฑ, ํ™˜๊ฒฝ์„ค์ •
        let configuration = URLSessionConfiguration.default
        let defaultSession = URLSession(configuration: configuration) // URLSession(configuration: .default)
 
        guard let url = URL(string: urlString) else {
            print("URL is nil")
            return
        }
 
        // Request
        let request = URLRequest(url: url)
        request.httpMethod = "POST"
        request.addValue("application/json", forHTTPHeaderField: "Content-Type")
        request.addValue("application/json", forHTTPHeaderField: "Accept")
        request.httpBody = try JSONSerialization.data(withJSONObject: body, options: JSONSerialization.WritingOptions.prettyPrinted)
 
        // dataTask
        let dataTask = defaultSession.dataTask(with: request) { (data: Data?, response: URLResponse?, error: Error?) in
            // getting Data Error
            guard error == nil else {
                print("Error occur: \(String(describing: error))")
                return
            }
 
            guard let data = data, 
                  let response = response as? HTTPURLResponse, 
                  response.statusCode == 200 else {
                return
            }
 
            // ํ†ต์‹ ์— ์„ฑ๊ณตํ•œ ๊ฒฝ์šฐ data์— Data ๊ฐ์ฒด๊ฐ€ ์ „๋‹ฌ๋ฉ๋‹ˆ๋‹ค.
 
            // ๋ฐ›์•„์˜ค๋Š” ๋ฐ์ดํ„ฐ๊ฐ€ json ํ˜•ํƒœ์ผ ๊ฒฝ์šฐ,
            // json์„ serializeํ•˜์—ฌ json ๋ฐ์ดํ„ฐ๋ฅผ swift ๋ฐ์ดํ„ฐ ํƒ€์ž…์œผ๋กœ ๋ณ€ํ™˜
            // json serialize๋ž€ json ๋ฐ์ดํ„ฐ๋ฅผ String ํ˜•ํƒœ๋กœ ๋ณ€ํ™˜ํ•˜์—ฌ Swift์—์„œ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋„๋ก ํ•˜๋Š” ๊ฒƒ์„ ๋งํ•ฉ๋‹ˆ๋‹ค.
            guard let jsonToArray = try? JSONSerialization.jsonObject(with: data, options: []) else {
                print("json to Any Error")
                return
            }
            // ์›ํ•˜๋Š” ์ž‘์—…
            print(jsonToArray)
        }
        dataTask.resume()
    }
}
 
NetworkHandler.postData(resource: "http://www.example.com")

๋งˆ๋ฌด๋ฆฌ

์•„์ง URLSessionStreamTask, URLSessionWebSocketTask๋Š” ์จ๋ณด์ง€ ๋ชปํ•ด์„œ ์ •๋ฆฌํ•ด๋ณด์ง€ ๋ชปํ–ˆ๋‹ค. ํ•˜๋‹ค๋ณด๋‹ˆ Serialization์ด ๋ฌด์—‡์ธ์ง€์— ๋Œ€ํ•ด ๊ถ๊ธˆํ•ด์ ธ์„œ ๋‹ค์Œ์—๋Š” ํ•ด๋‹น ๊ธ€์„ ์ž‘์„ฑํ•˜๋„๋ก ํ•˜๊ฒ ๋‹ค. ๋!

Reference