대망의 Closure다. 처음 공부할 때 정말 어려웠던 경험이 있다. 그 당시 생각을 하면서 찬찬히 적었으니 잘 이해가 되었으면 좋겠다. ARC의 개념은 후반 포스팅에 작성하였는데, 해당 단어가 궁금하다면 먼저 읽고 와도 상관없다. closure에서 다루는 대부분의 개념을 하나의 포스팅에서 다루고 싶어 불가피하게 흐름을 변경했다. 그럼 즐겁게 읽어주시길 바란다!
이 네가지를 배워볼 것이다. 말이 어려울 뿐 코드로 이해하자.
// 클로저를 매개변수로 갖는 함수
func calculate(a: Int, b: Int, method: (Int, Int) -> Int) -> Int {
return method(a, b)
}
var result: Int // calculate의 값을 받을 변수
// 후행 클로저
// 클로저가 함수의 마지막 전달인자일 때,
// 마지막 매개변수의 이름을 생략하고 함수의 소괄호 외부에 클로저를 구현할 수 있다.
result = calculate(a: 10, b: 10, method: { (left: Int, right: Int) -> Int in
return left + right
})
print(result) // 20
result = calculate(a: 10, b : 10) { (left: Int, right: Int) -> Int in
return left + right
}
print(result) // 20
위에 적혀있는 것이 기본적으로 알고 있는 클로저의 사용방법이다. 그런데 클로저가 해당 함수의 마지막 위치에 있을 경우, 조금 변용된 방법으로 사용할 수가 있는데, 소괄호 외부에 중괄호로 연 뒤, 안에 클로저를 구현해서 같은 결과를 얻을 수 있다.
// 반환타입 생략
// calculate라는 함수는 이미 closure 함수가 Int형을 반환한다는 것을 알고 있다.
// 그렇기 때문에 이를 생략할 수 있다. 다만 in 키워드는 생략 불가
result = calculate(a: 10, b: 10, method: {(left: Int, right: Int) -> Int in
return left + right
})
print(result) // 20
// 후행 클로저에도 적용가능
result = calculate(a: 10, b: 10) { (left: Int, right: Int) in
return left + right
}
print(result) // 20
// 단축 인자이름
// 굳이 closure에 들어오는 매개변수이름이 필요없다면 명시하지 않고 $를 사용해 접근할 수 있다.
// 이 때 in 키워드는 사용할 필요가 없다.
result = calculate(a: 10, b: 10, method: {
return $0 + $1
})
print(result) // 20
// 후행 클로저와도 가능!
result = calculate(a: 10, b: 10) {
return $0 + $1
}
print(result) // 20
$0
, $1
, $3
과 같이 사용한다.// 암시적 반환 표현
// 클로저를 파라미터로 받는 함수에서 클로저의 반환형이 있다면
// 클로저의 마지막 줄의 결과값은 암시적으로 반환값으로 취급한다.
result = calculate(a: 10, b: 10) {
$0 + $1
}
print(result) // 20
// 한줄 표현도 가능
result = calculate(a: 10, b: 10) { $0 + $1 }
print(result) // 20
func doSomething() {
var message = "Hi i am sodeul!"
//클로저 범위 시작
var num = 10
let closure = { print(num) }
//클로저 범위 끝
print(message)
}
func doSomething() {
var num: Int = 0
print("num check #1 = \(num)")
let closure = {
print("num check #3 = \(num)")
}
num = 20
print("num check #2 = \(num)")
closure()
}
num
이라는 변수의 주소를 들고 있음Value type으로 값을 캡쳐하자.
클로저의 시작인 {
옆에 []
을 이용해 캡쳐할 멤버를 나열한다.
let closure = { [num1, num2] in
// something..
}
그럼 Reference Type을 Capture List에 작성하면 어떻게 되지?
class Human {
var name: String = "Sodeul"
}
var human1: Human = .init()
let closure = { [human1] in
print(human1.name)
}
human1.name = "Unknown"
closure()
Unknown
NSCopying
프로토콜을 사용한다.copy
라는 메서드를 통해, copy시 어떤 동작(아마 내부 property를 복사한 객체를 리턴하는 동작)을 수행할 것인지 정의할 수 있다.class Human {
var name = ""
// lazy : 사용되기 전까지는 연산이 되지 않음
lazy var getName: () -> String = {
return self.name
}
init(name: String) {
self.name = name
}
deinit {
print("Human Deinit!")
}
}
var wansik: Human? = Human(name: "choiwansik")
print(wansik?.getName)
wansik = nil
wansik
인스턴스가 필요없어서 nil을 할당했다.Human instance | closure |
---|---|
2 wansik 변수, closure내에서 참조 | human instance 변수인 getName이 참조 |
class Human {
lazy var getName: () -> String? = { [weak self] in
return self?.name
}
}
class Human {
lazy var getName: () -> String = { [unowned self] in
return self.name
}
}
함수의 인자로 전달되는 코드를 감싸서 자동으로 클로저로 만들어 줌
조건
() -> SomeType
non-escaping closure
@escaping
추가 필요차이점
func defaultPrint(_ closure: () -> String) {
closure()
}
defaultPrint({ print("Default") }) // Default
func autoClosurePrint(_ closure: @autoclosure () -> String) {
closure()
}
autoClosurePrint(print("AutoClosure"))
{}
가 없어져 보다 간결해졌다.사용 예시
UIView.animate(withDuration: 1) {
self.view.frame.width = 100
}
func animate(_ animation: @autoclosure @escaping () -> Void, duration: TimeInterval) {
UIView.animate(withDuration: duration, animations: animation)
}
animate(self.view.frame.width = 100, duration: 1)
{}
를 없애어 보다 간결하게 표현할 수 있다.let isLiveInSea = True
let legOfAnimals = ["tiger": 4, "chicken": 2, "squid": 10]
let legOfSquid = legOfAnimals["squid"] ?? { isLiveInSea ? 0 : 4 }
extension Dictionary {
func value<T>(forKey key: Key, defaultValue: @autoclosure () -> T) -> T }
guard let value = self[key] as? T else {
return defaultValue()
}
return value
}
let legOfSquid = legOfAnimals.value(forKey: "squid", defaultValue: isLiveInSea ? 0 : 4)
유의 사항