티스토리 뷰
클로저를 매개 변수 중 하나로 사용하는 함수를 선언할 때 매개 변수 유형 앞에 @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 는 뭔가요? 이런 질문을 했었는데 "파라미터다" 라는 대답을 듣고 이해를 못했어요.
근데 이렇게 저 함수를 직접 만들고 보니까 이제 이해가 잘 되네요.ㅎㅎ
늦게라도 알아서 다행이다.
'Swift' 카테고리의 다른 글
[Swift] Swift 언어의 특징 (0) | 2021.05.01 |
---|---|
[Swift] Delegation이란? (0) | 2021.04.09 |
[RxSwift] toArray, map, flatMap, flatMapLatest (0) | 2020.12.23 |
[RxSwift] PublishSubject, BehaviorSubject, ReplaySubject, PublishRelay , BehaviorRelay (0) | 2020.12.23 |
[RxSwift] Observable, just, of, from (0) | 2020.12.23 |