์ด์ ๊ธ์์ URLSession
, URL
, URLRequest
๋ฅผ ์์ฑํ๋ ๋ฐฉ๋ฒ์ ์์๋ณด์๋ค. ์ด๋ฒ์๋ ์ด๋ค Task๋ค์ด ์๋์ง ์์๋ณด์.
Task
Apple์์ ๊ธฐ๋ณธ์ ์ผ๋ก ์ ์ํ๋ task์ ์ข
๋ฅ๋ ์ด 3๊ฐ์ง์ด๋ค. ํ์ง๋ง ๋ฌธ์๋ฅผ ๋ณธ ๊ฒฐ๊ณผ, URLSession
์์ ํจ์๋ก ์ ์ํ๋ task์ ์ข
๋ฅ๋ ์ด 5๊ฐ ์๋ค.
- URLSessionDataTask
- URLSessionDownloadTask
- URLSessionUploadTask
- URLSessionStreamTask
- URLSessionWebSocketTask
์ฌ๊ธฐ์ ์์ 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์ด ๋ฌด์์ธ์ง์ ๋ํด ๊ถ๊ธํด์ ธ์ ๋ค์์๋ ํด๋น ๊ธ์ ์์ฑํ๋๋ก ํ๊ฒ ๋ค. ๋!