URLSession
을 활용함GCD & Operations
서버와 앱 간의 데이터를 주고 받는 것
-> HTTP라는 방법으로 REST API를 이용해서 Json 데이터를 주고 받음
서버 <-> 클라이언트 (메세지를 주고 받음)
클라이언트는 서버에게 요청하는 메세지를 보냄(Request)
(ex: 인기 top100 노래 알려줘)
서버는 요청에 데이터로 응답함(Response)
(ex: 인기 top100 노래를 Json형태로 보내줌)
HTTP Request Method의 대표적인 4가지 방법
ex) 구글 주소를 주소창에 입력하고 엔터: Get 리퀘스트를 보내는 행위 -> 이때 사용하는 것이 URL(Uniform Resource Locator)
Request, Response 데이터 모두 헤더와 바디로 나뉨
Response data의 경우, 헤더에는 가장 중요한 상태코드(status code, 응답이 제대로 되었는지 확인할 수 있는 코드(HTTP/1.1 2000 Ok)
)와 Content-type(어떤 타입의 데이터가 전달될 것 인지)이 같이 포함돼서 내려옴
: Content Type에는 text/html
, application/json
, image/png
, video/mpeg
등이 잇음
바디에는 해당 페이지를 구성하기 위한 html코드가 포함돼있음.
동영상/음원 등 스트리밍을 위한 프로토콜로는 HLS(HTTP Live Streaming)
을 대부분 사용하고 있음
Main Queue
: 메인 스레드에서 작동하는 큐Global Queue
: 시스템에 의해 관리되는 큐, 여기서 처리될 태스크들의 우선순위를 표현하는 클래스가 있고, 그건 QoS(Quality of Service) 클래스임Custom Queue
: 위 두개는 시스템에서 제공, 만약에 직접 만들어서 관리해야 할 경우에 custom queue 사용함. qos, attribute 설정이 가능함QoS 클래스
1. user Interactive(우선순위 젤 높음)
2. userInitiated(사용자 결과를 기다리는 작업, 이것도 거의 바로 수행돼야함)
3. default(우선순위에 별 의미가 없음)
4. utility(네트워킹, 파일 불러오기 등등)
5. background(영상 다운받기, 사용자 위치 업데이트 하기 등등)
DispatchQueue.main
DispatchQueue.global(qos: .background).
DispatchQueue(label:"", qos: ....)
//global queue로 background에서 이미지 다운로드
DispatchQueue.global(qos: .background).async {
let image = downloadimageFromServer()
//main queue로 화면에 이미지 띄워줌
DispatchQueue.main.async{
self.imageView.image = image
}
}
import UIKit
// Queue - main, global, custom
// main
DispatchQueue.main.async {
// UI update
let view = UIView()
view.backgroundColor = #colorLiteral(red: 1.0, green: 1.0, blue: 1.0, alpha: 1.0)
}
// global
DispatchQueue.global(qos: .userInteractive).async {
// 진짜 핵중요한거, 지금 당장 해야하는 것
}
DispatchQueue.global(qos: .userInitiated).async {
// 거의 바로 해줘야할 것, 사용자가 결과를 기다린다
}
DispatchQueue.global(qos: .default).async {
// 우선순위에 딱히 신경안쓰겠다. 이건 굳이?
}
DispatchQueue.global().async {
// default 쓸거면 이렇게 써도 됨, 우선순위에 큰 의도가 없기 때문에
}
DispatchQueue.global(qos: .utility).async {
// 시간이 좀 걸리는 일을 할 때, 사용자가 당장 기다리지 않는 것, 네트워킹, 큰 파일 불러올때?
}
DispatchQueue.global(qos: .background).async {
// 사용자한테 당장 인식될 필요가 없는 것들, 뉴스데이터 미리 받기, 위치 업데이트(자동으로), 영상 다운
}
// custom queue: 다양한 속성들까지 더해서 만들 수 있음
let concurruntQueue = DispatchQueue(label: "concurrent", qos: .background, attributes: .concurrent)
let serialQueue = DispatchQueue(label: "serial", qos: .background)
// 복합적인 상황
func downloadImageFromServer() -> UIImage{
// Heavy Task
return UIImage()
}
func updateUI(image: UIImage){
//이런건 메인 스레드에서 해줘야함
}
DispatchQueue.global(qos: .background).async {
let image = downloadImageFromServer()
//UIupdate 해줘야함 -> 메인 스레드
DispatchQueue.main.async {
//updateUI
updateUI(image: image)
}
}
// sync, async
// Async
DispatchQueue.global(qos: .background).sync {
for i in 0...5 {
print("hi \(i)")
}
}
DispatchQueue.global(qos: .userInteractive).async {
for i in 0...5 {
print("bye \(i)")
}
}
// 개발자 입장에서는 동시성을 최대한 제공해야하기 때문에 sync는 잘 사용하지 않음
-> 대부분 디폴트 사용하면 커버 가능함
import UIKit
// URL
let urlString = "https://itunes.apple.com/search?media=music&entity=song&term=Gdragon"
let url = URL(string: urlString)
url?.absoluteString
// 내가 지금 어떤 방식으로 네트워킹 하고 있냐
url?.scheme
url?.host
url?.path
// 검색어라던지 쿼리를 묶어서 보낼 때 관련 정보들
url?.query
url?.baseURL
let baseURL = URL(string:"https://itunes.apple.com")
// 한국어가 들어가면 이해 못함. 인코딩이 필요함
let relativeURL = URL(string: "search?media=music&entity=song&term=Gdragon", relativeTo: baseURL)
relativeURL?.absoluteString
// 내가 지금 어떤 방식으로 네트워킹 하고 있냐
relativeURL?.scheme
relativeURL?.host
relativeURL?.path
// 검색어라던지 쿼리를 묶어서 보낼 때 관련 정보들
relativeURL?.query
relativeURL?.baseURL
// URLComponents
// 1. base가 될 URL로 URLComponents 만들어줌
var urlComponents = URLComponents(string: "https://itunes.apple.com/search?")
// 2. QueryITem 작성해줌. 쿼리 안에 조건이 3개임, URLQueryItem은 쿼리 부분으로 들어갈 name, value 쌍
let mediaQuery = URLQueryItem(name: "media", value: "music")
let entityQuery = URLQueryItem(name: "entity", value: "song")
let termQuery = URLQueryItem(name: "term", value: "지드래곤")
// queryItem으로 만들어주면 자동으로 인코딩해줌!!!
// 3. urlComponent의 QueryItem에 만들어준 쿼리 아이템을 넣어줌
urlComponents?.queryItems?.append(mediaQuery)
urlComponents?.queryItems?.append(entityQuery)
urlComponents?.queryItems?.append(termQuery)
urlComponents?.url?.scheme
urlComponents?.string
urlComponents?.queryItems?.last?.value
import UIKit
//URLSession
//1.URLSessionCofiguration
//2.URLSession
//3.URLSessionTask를 이용해서 서버와 네트워킹
//URSLSessionTask
// - dataTask
// - uploadTask
// - downloadTask
// 1. URLSession을 만들기 위한 URLSessionConfiguration 생성
// 종류가 default, ephemeral, background가 있지만 웬만하면 그냥 default~
let config = URLSessionConfiguration.default
// 2. URLSession 생성
let session = URLSession(configuration: config)
// URL
// URL Components
var urlComponents = URLComponents(string: "https://itunes.apple.com/search?")!
let mediaQuery = URLQueryItem(name: "media", value: "music")
let entityQuery = URLQueryItem(name: "entity", value: "song")
let termQuery = URLQueryItem(name: "term", value: "지드래곤")
urlComponents.queryItems?.append(mediaQuery)
urlComponents.queryItems?.append(entityQuery)
urlComponents.queryItems?.append(termQuery)
let requestURL = urlComponents.url!
// 3. Task 만들기
// session(위에서 만든 URLSession의 dataTask 메서드 호출 -> 실제 네트워킹할 url 인자로 받고 네트워킹 요청 후(완료 후) 후행 작업들을 completionHandler에 작성해주면 됨
let dataTask = session.dataTask(with: requestURL) {(data, response, error) in
// 먼저 error 확인. error가 있으면 끝냄 아니면 다음 작업 수행
guard error == nil else { return } //error가 nil일때만 수행해주면 됨
// 상태코드 가져옴
guard let statusCode = (response as? HTTPURLResponse)?.statusCode else { return }
let successRange = 200..<300
// 200대가 성공했다는 코드이기 때문에 포함하고 있으면 성공으로 침
guard successRange.contains(statusCode) else {
//handle response error
return
}
// 성공적인 경우에 data를 봐줌
guard let resultData = data else {return}
let resultString = String(data: resultData, encoding: .utf8)
print("-----> result:\(resultString)")
}
//dataTask를 만들었으면 실행해야함
dataTask.resume() //이렇게 하면 네트워킹이 진행됨
받아온 response를 우리가 원하는 object로 만드는 과정
목표: 트랙리스트 오브젝트로 가져오기
// 만들고 싶은 기능 목록
// - Data -> Track 목록으로 가져오고 싶다 [Track]
// - Track 오브젝트를 만들어야겠다
// - Data에서 struct로 파싱하고 싶다(파싱= 데이터를 가지고 주물럭해서 우리가 만들고 싶은 오브젝트로 만드는 과정) > Codable 이용해서 만들자(todolist때 사용했던...)
// - Json 형태 파일, 데이터 > 오브젝트로 만들 때는 codable쓰면 너무 편함 (Codable 이용하겠다)
// - Response 구조 만들기 ([Track], results를 대표하는 response 오브젝트를 만들어야겠다) -> Response, Track 이렇게 두 개 만들어야겠다
// 해야할 일
// - Response, Track struct (ㅇ)
// - struct의 프로퍼티 이름과 실제 데이터의 키와 맞추기 (Codable 디코딩하게 하기 위해서)(ㅇ)
// - 파싱하기 (Decoding)
// - 트랙리스트 가져오기
import UIKit
//URLSession
//1.URLSessionCoriguration
//2.URLSession
//3.URLSessionTask를 이용해서 서버와 네트워킹
//URSLSessionTask
// - dataTask
// - uploadTask
// - downloadTask
let config = URLSessionConfiguration.default
let session = URLSession(configuration: config)
// URL
// URL Components
var urlComponents = URLComponents(string: "https://itunes.apple.com/search?")!
let mediaQuery = URLQueryItem(name: "media", value: "music")
let entityQuery = URLQueryItem(name: "entity", value: "song")
let termQuery = URLQueryItem(name: "term", value: "지드래곤")
urlComponents.queryItems?.append(mediaQuery)
urlComponents.queryItems?.append(entityQuery)
urlComponents.queryItems?.append(termQuery)
let requestURL = urlComponents.url!
struct Response: Codable {
let resultCount: Int
let tracks: [Track]
enum CodingKeys: String, CodingKey{
case resultCount
case tracks = "results"
}
}
struct Track: Codable {
//이건 우리 마음대로 정한거
let title: String
let artistName: String
let thumbnailPath: String
//실제 데이터와 우리가 정한 프로퍼티의 이름을 연결시켜줘야 파싱이 쉽게 됨
//trackName
//artistName
//artworkUrl30
enum CodingKeys: String, CodingKey{
case title = "trackName"
case artistName
case thumbnailPath = "artworkUrl30"
}
}
let dataTask = session.dataTask(with: requestURL) {(data, response, error) in
guard error == nil else { return } //error가 nil일때만 수행해주면 됨
guard let statusCode = (response as? HTTPURLResponse)?.statusCode else {return }
let successRange = 200..<300
//포함하고 있으면 성공으로 침
guard successRange.contains(statusCode) else {
//handle response error
return
}
// 성공적인 경우에 data를 봐줌
guard let resultData = data else {return}
let resultString = String(data: resultData, encoding: .utf8)
// 파싱 및 트랙 가져오기
do {
let decoder = JSONDecoder()
let response = try decoder.decode(Response.self, from: resultData)
let tracks = response.tracks
print("---> tracks: \(tracks.first?.title), \(tracks.last?.thumbnailPath)")
} catch let error {
print("---> error: \(error.localizedDescription)")
}
// print("-----> result:\(resultString)")
}
//dataTask를 만들었으면 실행해야함
dataTask.resume() //이렇게 하면 네트워킹이 진행됨