Post

Swift 비동기 처리 방법 - Closure

Closure 클로저

  • Result<Success, Failure> 활용

    Result 는 제너릭 타입을 가진 enum 으로 아래처럼 선택한 case에 맞는 성공데이터 또는 실패 에러코드를 전달 한다.

1
2
3
4
5
@frozen public enum Result<Success, Failure> where Failure : Error, Success : ~Copyable {}

//사용시
.success(BaseListResponse<Todo>)
.fail(ApiError)
  • Todo 목록을 조회하는 API 호출 함수
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
static func fetchTodos(page: Int = 1, completion: @escaping (Result<BaseListResponse<Todo>, ApiError>) -> Void ) {
        
        // 1. urlRequest 를 만든다
        let urlString = baseURL + "/todos" + "?page=\(page)"
        
        guard let url = URL(string: urlString) else {
            return completion(.failure(ApiError.notAllowedUrl))
        }
        
        var urlRequest = URLRequest(url: url)
        urlRequest.httpMethod = "GET"
        urlRequest.addValue("application/json", forHTTPHeaderField: "accept")
        
        // 2. urlSession 으로 API 호출한다.
        // 3. API 호출에 대한 응답을 받는다
        
        URLSession.shared.dataTask(with: urlRequest) { data, response, err in
            if let error = err {
                return completion(.failure(ApiError.unknown(error)))
            }
            
            guard let httpResponse = response as? HTTPURLResponse else {
                print("bad status code")
                return completion(.failure(ApiError.unknown(nil)))
            }
            
            switch httpResponse.statusCode {
            case 401:
                return completion(.failure(ApiError.unauthorized))
            default: print("default")
            }
            
            if !(200...299).contains(httpResponse.statusCode) {
                return completion(.failure(ApiError.badStatus(code: httpResponse.statusCode)))
            }
            
            if let jsonData = data {
                // convert data to our swift model
                do {
                    //JSON -> Struct 로 변경 즉 디코딩 즉 데이터 파싱
                    let listResponse = try JSONDecoder().decode(BaseListResponse<Todo>.self, from: jsonData)
                    
                    // 상태 코드는 200인데 파싱한 데이터에 따라서 에러처리
                    guard let todos = listResponse.data,
                          !todos.isEmpty else {
                        return completion(.failure(ApiError.noContent))
                    }
                    completion(.success(listResponse))
                    
                } catch {
                    // decoding error
                    completion(.failure(.decodingError))
                }
            }
        }.resume()
    }
  • 연쇄 API 호출

    할일을 등록후 성공이면 이어서 전체목록을 조회해 오는 연속 API 호출

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
static func addATodoAndFetchTodos(title: String,
                                      isDone: Bool = false, completion: @escaping (Result<BaseListResponse<Todo>, ApiError>) -> Void) {
        
        self.addATodo(title: title) { result in
            switch result {
                // 1-1
            case .success(_):
                self.fetchTodos {
                    //2
                    switch $0 {
                        //2-1
                    case .success(let data):
                        completion(.success(data))
                        //2-2
                    case .failure(let err):
                        completion(.failure(err))
                    }
                }
                // 1-2
            case .failure(let err):
                completion(.failure(err))
            }
        }
    }
  • 동시 API 호출 [Int] 응답 - Dispatch Group 활용

    선택한 할일들을 동시에 삭제후 삭제가 성공한 녀석들만 todoId 값을 [Int] 리턴하는 함수

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
static func deleteSelectedTodos(seletedTodoIds: [Int],
                                    completion: @escaping ([Int]) -> Void)  {
        
        let group = DispatchGroup()
        
        // 성공적으로 삭제가 이뤄진 녀석들
        var deletedTodoIds: [Int] = [Int]()
        
        seletedTodoIds.forEach { aTodoId in
            group.enter()
            self.deleteATodo(id: aTodoId) { result in
                switch result {
                case .success(let response):
                    // 삭제된 아이디를 삭제된 아이디 배열에 넣는다
                    if let todoId = response.data?.id {
                        deletedTodoIds.append(todoId)
                        print("inner deleteATodo - success: \(todoId)")
                    }
                case .failure(let failure):
                    print("inner deleteATodo - failure: \(failure)")
                }
            }
            group.leave()
        } // 단일 삭제 API 호출
        
        // configure a completion callback
        group.notify(queue: .main) {
            // all requests completed
            print("모든 api 완료 됨")
            completion(deletedTodoIds)
        }
    }
  • 동시 API 호출 Result<> 응답 - Dispatch Group 활용
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
static func fetchSelectedTodos(seletedTodoIds: [Int],
                                   completion: @escaping (Result<[Todo], ApiError>) -> Void)  {
        
        let group = DispatchGroup()
        
        // 가져온 할일들
        var fetchedTodoIds: [Todo] = [Todo]()
        // 에러들
        var apiErros: [ApiError] = []
        
        seletedTodoIds.forEach { aTodoId in
            group.enter()
            
            self.fetchATodo(id: aTodoId) { result in
                switch result {
                case .success(let response):
                    // 삭제된 아이디를 삭제된 아이디 배열에 넣는다
                    if let todo = response.data {
                        fetchedTodoIds.append(todo)
                        print("inner fetchATodo - success: \(todo)")
                    }
                case .failure(let failure):
                    apiErros.append(failure)
                    print("inner fetchATodo - failure: \(failure)")
                }
            }
            group.leave()
        } // 단일 삭제 API 호출
        
        // configure a completion callback
        group.notify(queue: .main) {
            // all requests completed
            print("모든 api 완료 됨")
            
            // 만약 에러가 있다면 에러 올려주기
            if !apiErros.isEmpty {
                if let firstError = apiErros.first {
                    completion(.failure(firstError))
                    return
                }
            }
            
            completion(.success(fetchedTodoIds))
        }
        
    }
This post is licensed under CC BY 4.0 by the author.

Trending Tags