티스토리 뷰

클로저를 매개 변수 중 하나로 사용하는 함수를 선언할 때 매개 변수 유형 앞에 @escaping을 작성하여 클로저가 탈출하도록 나타낼 수 있습니다.

클로저가 탈출하는 방법 중 하나는 변수에 저장하는 것입니다.

예를 들어 비동기 작업을 시작하는 많은 함수는 클로저 인수를 completion handler로 사용합니다.

함수는 작업을 시작하고 나서 return하지만 그 클로저는 작업이 완료되기 전까지 불리지 않습니다.

그 클로저는 탈출해야 하고 나중에 호출돼야 합니다.

 

아래 예시를 많이 봤을 겁니다.

var completionHandlers: [() -> Void] = []
func someFunctionWithEscapingClosure(completionHandler: @escaping () -> Void) {
    completionHandlers.append(completionHandler)
}

func someFunctionWithNonescapingClosure(closure: () -> Void) {
    closure()    // 함수 안에서 끝나는 클로저
}

class SomeClass {
    var x = 10
    func doSomething() {
        someFunctionWithEscapingClosure { self.x = 100 } // 명시적으로 self를 적어줘야 합니다.
        someFunctionWithNonescapingClosure { x = 200 }
    }
}

let instance = SomeClass()
instance.doSomething()
print(instance.x)
// Prints "200"

completionHandlers.first?()
print(instance.x)
// Prints "100"

() -> Void, () -> (), Void -> Void 는 모두 같은 의미입니다.

클로저가 어떤 파라미터도 받지 않으면서 어떤 값도 리턴하지 않는다는 의미입니다.

 

func doSomething() {

        someFunctionWithEscapingClosure { self.x = 100 }

        someFunctionWithNonescapingClosure { x = 200 }

    }

에서 똑같이 x의 값을 바꾼 것으로 보입니다.

 

SomeClass의 인스턴스를 생성하고 함수를 실행하고 print를 하면 x는 200이 나옵니다.

그리고 completionHandlers의 클로저를 실행하니 x가 100이 되었습니다.

클로저가 탈출하고 나중에 호출되면서 x = 100이 됐습니다.

 

저는 솔찍히 이걸 여러번 봤는데 이해가 안됐습니다.

그러다가 비동기처리 공부를 하면서 알게 됐습니다.

 

func getImage(from url: String, completion: @escaping (UIImage?) -> Void) {
    let task = URLSession.shared.dataTask(with: URL(string: url)!) { data, _, _ in
        guard let data = data, let image = UIImage(data: data) else {
            return
        }
        DispatchQueue.main.async {
            completion(image)
        }
    }
    task.resume()
}

이 함수는 이미지URL을 받아서 image를 인수로 받는 클로저를 탈출시키는 함수입니다.

저는 이 함수를 테이블 뷰에서 사용할 예정입니다.

 

아래 식을 보면 이해가 더 쉬울거에요.

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    let cell = tableView.dequeueReusableCell(withIdentifier: "MemberTableViewCell", for: indexPath) as! MemberTableViewCell
    
    getImage(from: imageURL) { image in
        cell.avatarImageView.image = image
    }
    return cell
}

 

getImage(from: imageURL) { image in

    cell.avatarImageView.image = image

}

 

이미지url을 가져오면 image가 생기는 데 나는 그 image를 cell의 imageView에 넣어주겠다.

 

만약 이걸 이렇게 안쓰면 아래와 같이 써야합니다.

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    let cell = tableView.dequeueReusableCell(withIdentifier: "MemberTableViewCell", for: indexPath) as! MemberTableViewCell
    
        let task = URLSession.shared.dataTask(with: URL(string: url)!) { data, _, _ in
        guard let data = data, let image = UIImage(data: data) else {
            return
        }
        DispatchQueue.main.async {
             cell.avatarImageView.image = image
        }
    }
    task.resume()
    
    return cell
}

이렇게쓰면 cellForRowAt함수가 너무 길어지고 가독성도 떨어지고 이미지를 받아오는 함수도 재사용할 수 없습니다.

 

위에서 썼던 패턴을 기억하시고 나중에 필요할 때 사용하시면 좋을 것 같습니다.

저도 @escaping이라는 것을 이제서야 이해를 했네요.

 

 

getImage(from: imageURL) { image in

    cell.avatarImageView.image = image

}

제가 이런 비슷한 함수를 보면서 image 는 뭔가요? 이런 질문을 했었는데 "파라미터다" 라는 대답을 듣고 이해를 못했어요.

근데 이렇게 저 함수를 직접 만들고 보니까 이제 이해가 잘 되네요.ㅎㅎ

늦게라도 알아서 다행이다.

댓글
공지사항