티스토리 뷰

반응형

Swift Docs > Closures > Autoclosures  를 바탕으로 하고 있습니다. 

 

# Autoclosure 란

autoclosure는 함수에 argument로 전달되는 expression 을 감싸기 위해 자동으로 만들어지는 클로저 입니다. 

(An autoclosure is a closure that’s automatically created to wrap an expression that’s being passed as an argument to a function)

 

1) autoclosure 를 전달받고 싶으면, 파라미터 타입을  @autoclosure attribute 로 marking 하면 됩니다. 

2) autoclosure는 argument 를 가지지 않으며 리턴값이 있어야합니다. 

 

3) 이런 syntactic convenience 는 함수 parameter에 중괄호를 쓰는 것을 생략할 수 있게 해줍니다. 

4) autoclosure는 evaluation 을 delay 할 수 있게 해줍니다. (closure를 콜하기 전 까지 코드가 실행되지 않게 해줌)

    Delaying  evaluation 은 코드가 side effect 가 있거나 계산 비용이 많이 드는 경우 유용합니다. 

 

# Autoclosure 예제 (1)

 

예를들어  assert(condition:message:file:line:)  function은 condition, message 파라미터로 autoclosure를 받습니다. 

 

1) 파라미터 타입이 @autoclosure 로 마킹된 것을 볼 수 있습니다. 

2)  autoclosure는 argument를 가지지 않으며 리턴값이 있는 것을 볼 수 있습니다.

 

3) 

assert문을 쓸 때 이렇게 안쓰고 

assert({ a == b }, { "에러 발생" })

 

괄호를 생략해서 사용하고 있습니다. 

assert(a == b, "에러 발생")

 

autoclosure이기 때문에 저희가 expression을 넘겨주면 

자동으로 expression을 감싼 클로저로 컨버팅 될 것입니다. 

 

 

4)

condition parameter는 오직 debug build 일 때만 평가되며

message parameter는 오직 condition이 false 일때만 평가됩니다. 

(문서에서는 evaluated 라는 단어를 사용하고 있는데, 불리는 지 안불리는 지로 생각하시면 될 것 같아요) 

 

 

 

# Autoclosure 예제 (2)

 

@autoclosure what, why and when 글을 보면 autoclousre를 잘 활용한 예제가 나옵니다!

(아래 예제는 모두 이 글에서 가져왔습니다)

 

 

우선 이런 간단한 함수가 있다고 해볼게요! 

morning 값을 보고 true일 때만 굿모닝을 해주고 있습니다. 

func goodMorning(morning: Bool, whom: String) {
    if morning {
        print("Good morning, \(whom)")
    }
}

goodMorning(morning: true, whom: "Pavel")
goodMorning(morning: false, whom: "John")

// 출력결과
// Good morning, Pavel

 

이제 name 관련 코드를 이런 식으로 확장해볼게요!

giveAname 함수는 간단하게 구현되어있지만 랜덤으로 name을 리턴해준다는 등 복잡한 로직으로 대체될 수 있겠죠?!

 

우선 morning이 true일 때는 기존처럼 잘 동작합니다. 

func goodMorning(morning: Bool, whom: String) {
    if morning {
        print("Good morning, \(whom)")
    }
}

func giveAname() -> String {
    print("giveAname() is called")
    return "Robert"
}

goodMorning(morning: true, whom: giveAname())

// 출력결과
giveAname() is called
Good morning, Robert

 

하지만 false 일 때는 기존과 동작이 달라졌죠..?  (false일 때는 whom을 평가안했는데 하게 바뀜. 즉 delaying evaluation이 안되고 있음)

goodMorning(morning: false, whom: giveAname())

// 출력결과
giveAname() is called

 

그래서 morning이 false일 때, giveAname 이 평가 안되게 하고 싶으니까 whom을 클로저 타입으로 바꾸게 됩니다. 

func goodMorning(morning: Bool, whom: () -> String) {
    if morning {
        print("Good morning, \(whom())")
    }
}

func giveAname() -> String {
    print("giveAname() is called")
    return "Robert"
}

goodMorning(morning: true, whom: giveAname)
// 출력결과
giveAname() is called
Good morning, Robert

goodMorning(morning: false, whom: giveAname)
// 출력결과 없음

 

하지만 이렇게 바꾸면 맨 처음 예제처럼 String 값을 넘겨줄 수가 없게 됩니다... 

 

 

 

이럴 때 autoclosure를 사용하면 모든 경우를 대응할 수 있게 됩니다. 🎉

func goodMorning(morning: Bool, whom: @autoclosure () -> String) {
    if morning {
        print("Good morning, \(whom())")
    }
}

func giveAname() -> String {
    print("giveAname() is called")
    return "Robert"
}

goodMorning(morning: false, whom: giveAname())
goodMorning(morning: true, whom: "Pavel")

// 출력결과
Good morning, Pavel

 

정리해보면

"autoclousre를 사용해서 값을 직접 받거나 함수가 리턴해주는 값을 받을 수 있으며

모든 경우 delaying evaluation이 가능하다. "

 

 

앞서 살펴본 assert문도 위와 동일한 니즈로  autoclosure를 사용했을 것 같네요! 

assert(a == b, "에러 발생")
assert(condition(), message())

 

# 그외 

1)

autoclosure를 과도하게 사용하면 코드를 이해하기 어려울 수 있습니다.

context 또는 function name을 통해 평가가 delay 될 수 있음을 명확하게 해야합니다.

 

2)

autoclosure가  escape 되도록 하고 싶으면 @autoclosure 와 @escaping 를 둘다 사용해주면 됩니다. 

func collectCustomerProviders(_ customerProvider: @autoclosure @escaping () -> String) {
      ... 
}

 

순서는 상관없습니다.  

참고로 Alamofire 는  @escaping @autoclosure 순서로 사용해주고 있더라구요! 

 

 

 

 

 

 

 

 

 

반응형
댓글