관리 메뉴

김종권의 iOS 앱 개발 알아가기

[swift] 7. 클로저(closure) 본문

swift 5 문법

[swift] 7. 클로저(closure)

jake-kim 2020. 3. 26. 16:12

1. 클로저표현식 : 익명함수, 주변 환경으로부터 값을 "캡쳐"(휘발성인 지역변수의 값이 저장되는 것)할 수 있는 경량 문법

즉, 클로저라는 것은 리턴된 타입(어떤 함수의 종료)을 참조할 때, 함수가 이미 종료된 시점에서 특정 변수를 참조하는 것

 - 클로저표현식은 익명함수로 생각하면 편함

 - func키워드, 함수이름 제거한 후 나머지만 표현

 

1) 기본 사용법

// 대입 후 실행
let a = { (v1: Int, v2: Int) -> Void in
    print("value1 = \(v1), value2 = \(v2)")
}

a(1,2)

// 바로 실행
({ (v1: Int, v2: Int) -> Void in
    print("value1 = \(v1), value2 = \(v2)")
})​

 

2) 생략

// 정석
let a = { (v1: Int, v2: Int) -> Int in
    return v1 + v2
}
print(a(1,2))

// 반환타입 생략
let b = { (v1: Int, v2: Int) in
    return v1 + v2
}
print(b(2,4))

// 매개변수 타입 생략
let c = { (v1, v2) -> Int in
    return v1 + v2
}
print(c(1,2))

// $사용
let d = { 
	return $0 + $1
}
 

- 생략시, 매개변수타입이나 반환타입 둘 중 하나는 반드시 선언해야함

 

2) 트레일링 클로저(Trailing Closure)

 - 마지막 인수가 함수일 때 클로저 이용

func plus(base: Int, success s: () -> Void) -> Int {
    s()
    return 100 + base
}

// 매개변수 타입, 반환타입 생략된 트레일링 클로저
plus(base: 10) { () in
    print("success!!")
}

 

3) 어노테이션

 - @escaping : 원래 캡쳐 기능을 못하게 해놓았지만, 이 어노테이션을 이용하면 참조가능

 - 인자 타입 앞에다 선언

func callback1(fn: () -> Void) {
    let f = fn // error : Non - escaping prameter 'fn' may only be called
    f()
}

// 어노테이션 적용
func callback2(fn: @escaping () -> Void) {
    let f = fn
    f()
}

callback2{
    print("success!!")
}
 

- 탈출불가의 이점은 컴파일러의 최적화 (해당 클로저가 탈출 할 수 없다는 것은 컴파일러가 더 이상 메모리 관리상의 신경쓸 필요 없다는 것)

- 탈출불가 클로저에서는 self키워드 사용 가능(해당 함수가 끝나서 리턴되기 전에 호출될 것이 명확하기 때문)

- @autoclosure : {} 대신에 ()사용할 수 있게끔 함

func condition(stmt: @autoclosure () -> Bool) {
    if stmt() == true {
        print("true")
    } else {
        print("false")
    }
}


condition(stmt:
    1 > 3
)

// @autoclosure가 없다면 condition(stmt: 
condition{ () in
    1 > 3
}​

 

2. capture list

1) 개념

Strong Reference Cycle을 해결하기 위해서, 클로저에서 weak, unowned를 사용할 수 있는 문법

클로저에서 '{'의 오른쪽에, 'in'의 왼쪽에 '[참조타입의 변수]'형태로 표현

ex) 참조타입인 t는 내부 

class Test {
  var a = 10
}

  func ex(completion: (Int) -> ()) {
    print("ex")
    completion(10)
  }
  
  var t = Test()
  self.ex { [t] (val) in
    print(val)
  }

 

2) caption list를 사용하는 이유

var index = 0
var closureArr: [() -> ()] = []

for _ in 1...5 {
  closureArr.append({print(index)}) /// print(index)라는 함수 삽입
  index += 1
}

for i in 0..<closureArr.count {
    closureArr[i]()
}

위의 결과는?

0, 1, 2 ,3, 4가 아닌 5,5,5,5,5

1에서 5까지 돌면서 최종적으로 변경된 index값은 5이며, 클로저가 참조하고 있는 값은 바로 "index"변수이고 index값은 현재 5이므로

 

아래와 같이 수정하면 결과 : 0, 1, 2, 3, 4

    var index = 0
    var closureArr: [() -> ()] = []

    for _ in 1...5 {
      closureArr.append{[index] in
        print(index)} /// print(index)라는 함수 삽입
      index += 1
    }

    for i in 0..<closureArr.count {
        closureArr[i]()
    }

* 캡쳐 시점 : 값 타입은 클로져가 생성될 때 캡쳐, 참조 타입은 클로져가 호출될 때 캡쳐

 

3) 참조타입의 성질 설정(reference type인 경우만 사용가능)

ARC해결 여기 참고

 

(1) Strong Capture 

참조 카운트 증가

 - 순환참조의 문제점 발생 주의

 

(2) Weak Capture

참조 카운트를 증가시키지 않음

참조 대상이 nil일 수 있다는 Optional형태

 

(3) Unowned Capture

 nil일수 없다는 확신, nil일시 강제종료

Comments