iOS 14강 네트워킹

린다·2021년 3월 23일
1

iOS beginner

목록 보기
14/14
post-thumbnail
  • 각 어플에서 네트워킹을 사용하는 파트
  1. 넷플릭스
    • 컨텐츠 리스트
    • 사용자 정보
    • 동영상 스트리밍
  2. 애플 뮤직
    • 컨텐츠 리스트
    • 사용자 정보
    • 음악 스트리밍
  3. 페이스북
    • 뉴스피드 리스트
    • 사용자 친구 정보
    • 새로운 노티 정보
  • 이때 iOS의 네트워킹은? URLSession을 활용함
  • Concurrency(동시성) : 동시성이 제공이 되지 않으면 사용자가 불편함을 느낌. 네트워킹이 되는 동안에도 사용자 인터랙션은 계속 유지되어야함.
    -> 즉, 동시에 여러 개를 수행하는 능력을 말함
  • 동시성을 제공하는 방법? GCD & Operations
    동시성을 제공하기 위해 필요한 작업들을 어떻게 수행할 수 있는지 알아볼 예정

HTTP 기초

네트워킹이란?

서버와 앱 간의 데이터를 주고 받는 것
-> HTTP라는 방법으로 REST API를 이용해서 Json 데이터를 주고 받음

  • HTTP(HyperText Transfer Protocol)
    : 클라이언트(모바일)과 서버 간의 메세지를 어떻게 주고 받을지, 방법에 대한 규칙
    : 기존에는 html 다큐만 전송 가능했지만 현재는 다양한 파일 포맷을 주고 받을 수 있음

작동 방식

서버 <-> 클라이언트 (메세지를 주고 받음)

클라이언트는 서버에게 요청하는 메세지를 보냄(Request)
(ex: 인기 top100 노래 알려줘)
서버는 요청에 데이터로 응답함(Response)
(ex: 인기 top100 노래를 Json형태로 보내줌)

Request

HTTP Request Method의 대표적인 4가지 방법

  • Post: Create
  • Get: Read
  • Update
  • Delete

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)을 대부분 사용하고 있음


Concurrency(동시성)

  • 앱이 해야하는 일들
  1. 사용자 인터랙션 처리
  2. 네트워킹
  3. 백그라운드에서 파일 다운로드
  4. 파일 저장하고 읽기
  • 이러한 작업들을 앱/컴퓨터에서는 스레드라고 부름, 모두 동시에 처리해줘야하는 일들 (조금씩 하면서 왔다갔다 동시 수행됨)
  • 메인 스레드: 사용자 인터랙션 처리하면서 UI표시하는 작업을 함, 복잡한 네트워킹이나 다른 작업들은 메인 스레드말고 다른 스레드에서 처리해주도록 코드를 작성해야함

GCD(Grand Central Patch)

  • 스레드 위에 만들어졌다고 볼 수 있음
  • 자료구조 큐를 사용해서 GCD가 태스크를 관리함(Dispatch Queue)
  • Dispatch Queue의 종류
    - 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
	}
}
  • 큐에 들어온 태스크를 처리해주는 방식?
    GCD는 태스크를 비동기 혹은 동기식으로 처리해줄 수 있음
    동기: sync(앞의 일이 끝나기 전까지 다음 일을 막아 둠), 비동기: async(앞의 일이 끝나지 않아도 다음 일이 같이 시작할 수 있음)
    -> 앞의 작업이 다 끝난 뒤에 다음 작업을 시작해야할때는 sync 사용함
    -> 평소에는 대부분 async 사용함
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는 잘 사용하지 않음

URLSession

  • URLSession은 URLSessionConfiguration을 이용해서 생성함
  • URLSession은 URLSessionTask를 여러 개 만들 수 있음 ➡️ 이걸 통해서 실제로 서버와 통신함
  • Delegate를 통해서 네트워킹 중간 과정을 확인할 수 있음
  • Default: 기본 통신
  • Ephemeral: 쿠키, 캐쉬 등 저장하지 않게 진행할 때 사용(ex: chrome private mode)
  • Background: 앱이 백그라운드에 있을 때 사용

-> 대부분 디폴트 사용하면 커버 가능함

  • 작업에 따라 종류가 나뉨
  • DataTask: 데이터를 받는 작업, Response 데이터를 메모리상에서 처리하게 됨, 간단한 데이터를 받을 때 사용, 백그라운드 세션 지원하지 않음
  • UploadTask: 파일을 업로드할 때 사용함, RequestBody 제공
  • DownloadTask: 다운받아서 디스크에 쓸 때 사용함
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() //이렇게 하면 네트워킹이 진행됨

0개의 댓글

관련 채용 정보