ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • (Swift) closure @escaping
    iOS💖 2024. 10. 16. 17:43

    비동기 HTTP Request CompletionHandler 에서 보았던 @escaping

     

    클로저 ?

    • 이름이 있는 함수를 만들지 않고, 실행되는 그룹 코드
    • 전달 될 수 도, 코드 안에서 사용 될 수 도 있는 코드

     

    클로저 표현식

    // 1
    { (매개변수) -> Void in
        code
    }
    
    // 2
    { () -> () in
        code
    }

     

     

    클로저 @escaping

    • 클로저가 함수의 인수로 전달되지만, 함수가 return된 후에 호출되는 경우
      • 이 경우, 클로저는 함수를 escaping 한다고 한다.
    • 클로저를 매개변수 중 하나로 취하는 함수를 선언할 때, 매개변수 유형 앞에 @escaping 작성
    • 클로저 @escaping 될 수 있는 한 가지 방법
      • 함수 외부에서 정의된 변수에 저장 되는 것
        • 예를 들어, 비동기 작업을 시작하는 함수들은 대부분 handler 로 클로저 인수를 사용함 
        • ➡️ 함수는 작업 완료 후 return 하지만, 클로저는 함수가 return 될 때 까지 호출 되지 X
    • escaping closure에서, self 변수를 capture 하려면, 명시적으로 self 붙여야함!!
      • ➡️ 실수로 strong 참조 cycle을 생성하여, 메모리 누수 될 수 있으니!
      • 일반 closure 에서는, 해당 closure 본문에서 변수를 사용해, 암묵적으로 해당 변수를 캡쳐하기에 self 생략 가능

     

     

    공식 문서 예제

    var completionHandlers: [() -> Void] = []
    func someFunctionWithEscapingClosure(completionHandler: @escaping () -> Void) {
        completionHandlers.append(completionHandler)
    }
    // someFunctionWithEscapingClosure 함수는 클로저를 인수로 받아서 함수 외부에서 선언된 배열에 추가
    // 이 함수의 매개변수를 @escaping으로 표시하지 않으면 컴파일 타임 오류가 발생
    
    func someFunctionWithNonescapingClosure(closure: () -> Void) {
        closure()
    }
    
    
    class SomeClass {
        var x = 10
        func doSomething() {
            someFunctionWithEscapingClosure { self.x = 100 }
            // 실행 순서
            // 1. someFunctionWithEscapingClosure() 함수 호출됨 & 인수로 escaping 클로저 {self.x = 100} 를 받음
            // 2. someFunctionWithEscapingClosure() 함수 코드 실행 (completionHandlers에 {self.x = 100} 가 append 됨
            // 3. someFunctionWithEscapingClosure() 함수 코드 끝났으니 return
            // 4. 이제 escsping closure {self.x = 100} 실행
            // 5. x == 100 이 됨
            // 🔺 escaping closure에서, self 변수를 capture 하려면, 명시적으로 self 붙여야함!!
            
            someFunctionWithNonescapingClosure { x = 200 }
            // x == 200
        }
    }
    
    
    let instance = SomeClass()
    instance.doSomething()
    // completionHandlers는 현재 [{self.x = 100}]
    
    print(instance.x)
    // Prints "200"
    
    
    completionHandlers.first?() // {self.x = 100} 실행해라
    print(instance.x)
    // Prints "100"

     

     

    개인 예제

    struct UserProfile: Codable {
        let loginId: String
        let avatarUrl: String
        let followers: Int
        let following: Int
        
        enum codingKeys: String, CodingKey {
            case loginId = "login"
            case avatarUrl = "avatar_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<UserProfile, Error>) -> Void {
            let urlString = "https://xxxx.xxxxx.com/users/\(userName)"
            let url = URL(string: urlString)!
            
            let task = session.dataTask(with: url) { data, response, error in
                if let error = error { // nil이 아닌경우, 즉 error에 값이 있는 경우
                    completion(.failure(error))
                    return
                }
                
                if let response = response as? HTTPURLResponse,
                    !(200..<300).contains(response.statusCode) {
                    completion(.failure(error))
                    return
                }
                
                guard let data = data else { // data is optional
                    completion(.failure(error))
                    return
                }
                
                do {
                    let decoder = JSONDecoder()
                    let profile = try decoder.decoder(UserProfile.self, from: data)
                    completion(.success(profile))
                } catch let error {
                    completion(.failure(error))
                }
            }
            task.resume()
        }
    }
    
    let networkService = NetworkService(configuration: .default)
    
    // NetworkService 객체 사용 기본형
    // networkService.fetchProfile(userName: "aaa", completion: (Result) -> Void)
    
    networkService.fetchProfile(userName: "hortenssiaa") { result in
        // result 는 Result 타입이므로, success / failure 두가지 case로 나뉜다
        switch result {
        case .success(let profile):
            print("[Profile]: \(profile)")
        case .failure(let error):
            print("[Error]: \(error)")
        }
    }

     

     

     

     

     

     

    https://docs.swift.org/swift-book/documentation/the-swift-programming-language/closures#Escaping-Closures

     

    Documentation

     

    docs.swift.org

     

    댓글

Designed by black7375.