Network in iOS

Woozoo·2022년 12월 29일
0

깃헙스위프트기초

목록 보기
21/38

URLSession

// configuration -> urlsession -> urlsessionTask

let configuration = URLSessionConfiguration.default
let session = URLSession(configuration: configuration)
let url = URL(string: "https://api.github.com/users/woozoobro")!

let task = session.dataTask(with: url) { data, response, error in
    guard let httpResponse = response as? HTTPURLResponse, (200..<300).contains(httpResponse.statusCode) else {
        print(" --> response \(response)")
        return
    }
    
    guard let data = data else { return }
    
    let result = String(data: data, encoding: .utf8)
    print(result)
}

task.resume()

configuration을 만들고 session을 만든다
url을 만들어주고
session의 dataTask를 만들어준 url로 수행하게되면
completionHandler에 dat, response, error 를 받아서
뭔가를 실행해줄 수 있다!


Decode Data

struct GithubProfile: Codable {
    let login: String
    let avatarURL: String
    let htmlURL: String
    let followers: Int
    let following: Int
    
    enum CodingKeys: String, CodingKey {
        case login
        case avatarURL = "avatar_url"
        case htmlURL = "html_url"
        case followers
        case following
    }
}

// App Model <-> JSON
// 모델을 JSON으로 바꾸는게 인코딩, 그 반대는 디코딩

let configuration = URLSessionConfiguration.default
let session = URLSession(configuration: configuration)
let url = URL(string: "https://api.github.com/users/woozoobro")!

let task = session.dataTask(with: url) { data, response, error in
    guard let httpResponse = response as? HTTPURLResponse, (200..<300).contains(httpResponse.statusCode) else {
        print("--->response: \(response!)")
        return
    }
    
    guard let safeData = data else { return }
    // data -> GithubProfile
    do {
        let decoder = JSONDecoder()
        let profile = try decoder.decode(GithubProfile.self, from: safeData)
        print("profile: \(profile)")
    } catch let error as NSError {
        print("error: \(error)")
    }
}

task.resume()

Fetch Method

enum NetworkError: Error {
    case invalidRequest
    case transportError(Error)
    case responseError(statusCode: Int)
    case noData
    case decodingError(Error)
}

struct GithubProfile: Codable {
    let login: String
    let avatarURL: String
    let htmlURL: String
    let followers: Int
    let following: Int
    
    enum CodingKeys: String, CodingKey {
        case login
        case avatarURL = "avatar_url"
        case htmlURL = "html_url"
        case followers
        case following
    }
}

final class NetworkService {
    let session: URLSession
    
    init(configuration: URLSessionConfiguration) {
        session = URLSession(configuration: configuration)
    }
    
    func fetchProfile(userName: String, completion: @escaping (Result<GithubProfile, Error>) -> Void) {
        let url = URL(string: "https://api.github.com/users/\(userName)")!
        let task = session.dataTask(with: url) { data, response, error in
            if let error = error {
                completion(.failure(NetworkError.transportError(error)))
                return
            }
            if let httpResponse = response as? HTTPURLResponse, !(200..<300).contains(httpResponse.statusCode) {
                completion(.failure(NetworkError.responseError(statusCode: httpResponse.statusCode)))
                return
            }
            guard let safeData = data else {
                completion(.failure(NetworkError.noData))
                return
            }
            
            // data -> GithubProfile
            do {
                let decoder = JSONDecoder()
                let profile = try decoder.decode(GithubProfile.self, from: safeData)
                completion(.success(profile))
            } catch let error as NSError {
                completion(.failure(NetworkError.decodingError(error)))
            }
        }
        task.resume()
    }
}

// network 담당 NetworkService
// NetworkService 이용한 네트워크 작업

let networkService = NetworkService(configuration: .default)

networkService.fetchProfile(userName: "woozoobro") { result in
    switch result {
    case .success(let profile):
        print("Profile: \(profile)")
    case .failure(let error):
        print("Error: \(error)")
    }
}
  • 왜 fetchProfile의 파라미터로 클로져가 들어가는가?
  • @escaping (Result<>) -> Void 의 의미는?

enum

1-1 열거형이란?
같은 주제로 연관된 데이터들을 멤버로 구성해서 나타내는 자료형

enum Position {
	case top
    case mid
    case jug
    case adc
    case sup
}

1-2. 원시값이 있는 열거형

  • Number Type, Character Type, String Type
    위와 같은 원시값을 가질 수 있음
enum Position: Int {
	case top
    case mid
    case jug
    case adc
    case sup
}

요렇게 써주면 가장 먼저 선언된 case부터 0부터 1씩 증가된 값이 들어간다

enum Position: String {
	case top
    case mid
    case jug = "slave"
    case adc
    case sup
}

rawValue를 지정하지 않으면 case 이름과 동일한 Raw Value가 자동으로 만들어짐!!
지금 같은 경우엔 jug에는 slave 말고는 다 case의 이름으로 rawValue가 들어가게 된다

var user1: Position = .top
var user2: Position = .mid
var user3: Position = .jug

user1.rawValue
user2.rawValue
user3.rawValue

Enum - Associated Values(연관값)

enum AppleProduct: String {
	case iPad = "5, 128GB"
    case iPhone = "6, 64GB"
    case macbook = "Pro, 256GB"    
}

rawValue를 사용할 경우,
모든 case가 동일한 형식(위에선 String)으로 Raw Value를 가져야 하고,
case 별 값은 미리 지정된 한 가지 값만 가질 수 있다.
이를 보완해서 사용하는 것이 바로!!
associated Value 즉, 연관값!!

1-1. 연관값을 가지는 열거형 선언 방법

enum TypeName {
	case caseName(Type)
    case caseName(Type, Type, ...)    
}

이렇게 case 옆에 튜플 형태로 원하는 type을 명시하면 됨
아까 rawValue로 한계가 있던 예시를 바꿔보자

enum AppleProduct {
	case iPad(model: String)
    case iPhone(model: String, storage: Int)
    case macbook(model: String, storage: Int, size: Int)
}

Using Combine

import Foundation
import Combine

enum NetworkError: Error {
    case invalidRequest
    case transportError(Error)
    case responseError(statusCode: Int)
    case noData
    case decodingError(Error)
}

struct GithubProfile: Codable {
    let login: String
    let avatarURL: String
    let htmlURL: String
    let followers: Int
    let following: Int
    
    enum CodingKeys: String, CodingKey {
        case login
        case avatarURL = "avatar_url"
        case htmlURL = "html_url"
        case followers
        case following
    }
}

final class NetworkService {
    let session: URLSession
    
    init(configuration: URLSessionConfiguration) {
        session = URLSession(configuration: configuration)
    }
    
    func fetchProfile(userName: String) -> AnyPublisher<GithubProfile, Error> {
        let url = URL(string: "https://api.github.com/users/\(userName)")!
        
        let publisher = session
            .dataTaskPublisher(for: url)
        // 서버에서 받은 response 확인
            .tryMap { result -> Data in
                guard let httpResponse = result.response as? HTTPURLResponse, (200..<300).contains(httpResponse.statusCode) else {
                    let response = result.response as? HTTPURLResponse
                    let statusCode = response?.statusCode ?? -1
                    throw NetworkError.responseError(statusCode: statusCode)
                }
                return result.data
            } // 받은 Data 디코딩 잘하기
            .decode(type: GithubProfile.self, decoder: JSONDecoder())
            .eraseToAnyPublisher()
        return publisher
    }
}

let networkService = NetworkService(configuration: .default)

let subscription = networkService.fetchProfile(userName: "woozoobro")
    .receive(on: RunLoop.main)
    .print()
    .sink { completion in
        print("completion: \(completion)")
    } receiveValue: { profile in
        print("profile: \(profile)")
    }
profile
우주형

0개의 댓글