제목: Optimizing your Swift Codebase with Attirbutes

편집자의 노트: 우리 커뮤니티 블로그 포스트 시리즈는 buddybuild 유저와 훌륭한 모바일 개발자들에의해 작성되었습니다. 이 포스트는 Jordan Morgan에의해 작성되었는데, Buffer의 iOS 개발자이다.

스위프트 + 속성들
모든 iOS 개발자들이 모두 여기에 있을거라 생각한다: 우리는 그냥 프로그래밍을 고르거나 어떤것이 이색적인 새로운 언어를 배우기 시작했다. 그리고 몇 코드를 만날 수 있다. 우리가 이해할 수 없을 수도 있지만, 이것이 동작한다고 생각한다. 따라서 그것에대해 좋은 확신을 가지고 계속하면 나아지지 않는다.

이 과정은 정확히 내가 어떻게 내 코드베이스에 스위프트 속성을 넣는것을 시작할수 있는지에 관한 이야기이다. 스위프트는 탄탄하고 다양한 속성들을 지원하는데, 깃헙 저장소를 검색해볼때 우리가 모르는 한두개를 볼 수 있을지도 모르겠다. 나의 경험과 비슷하다면 나중에 구글에 적어두면 당신만의 방법이 된다.

왜 스위프트 속성들을 이용할까?
속성은 우리 코드베이스의 코드 질을 희생시키지 않은채 효율성을 증진시키는데 도움을 준다. 그 코드는 더 읽기 쉬워지고, 컴파일하기 쉬워지고, 궁극적으로 유지보수하기 쉽고 사용하기에 더 안전하게 된다.

개인적인 프로젝트에서나 Buffer에서나 효율성은 항상 나의 iOS 개발의 중심에 있다. 이 포스트는 스위프트 개발자로서 내 효율성을 증진시킬 수 있는 몇가지 스위프트 속성들을 열거한다. 안으로 들어가보자...

기초
수많은 정의를 보기 전에, 속성이 정확하게 무엇인지 빠르게 짚고 넘어가야한다고 생각한다. 그리고 매우, 모든 스위프트의 속성은 타입이나 선언에대한 더맣은 정보를 준다. 이 정보는 어떤것이 메모리에서 어떻게 다뤄질지 컴파일러 경고로부터 모든것을 지시할 수 있다.

어떻게 생겼든, 모두 "@" 표시를 앞에 붙인다. 또한, 속성 선언은 파라미터로 감싸진것 안에 인자를 받을 수 있다.

여기 간단한 것이 있다.
@attributeName

//Or with arguments…
@attributeName(arguments)
아래 몇가지 예시를 보자.

속성들
@available(args) 속성
스위프트 속성중에 맥가이버칼은 @available()이다. 강력한만큼 유연하고, 매번 API를 관리하고 배포하는 입장이라면 반드시 필요한 것임을 발견할 수 있다. 이것으로는 API 네이밍이 바뀌었다는 것을 지시할 수 있고, 가능한 플랫폼을 지시하는 등을 할 수 있다.

블로그 포스트를 올리는 은유적인 API로부터 나온 오브젝트를 생각해보자.
class BasicPost {}
우리 API를 쓰는 사람들은 오랫동안 BasicPost 클래스를 즐겨 사용한다. 그러나 기술 블로그 포스트를 표현하는 오브젝트에서 더 원하는 몇몇 요청을 처리한다고 생각해보자. 여러분이 읽고 있는 것과 대체로 같다. 그러니 버전1.2에서 이것을 소개하였다.
class TechnicalPost {}
이제 우리 문서를 완성하기위해서,  이것이 존재한다는것을 알리기위해 @available() 이점을 취함으로서 API 사용자에게 정보를 제공하고 코드는 분별력있게된다.
@available(*, introduced: 1.2)
class TechnicalPost {}
이 특정 속성은 몇몇 이자를 받을 수 있지만, 첫번째 인자는 항상 의도하는 플랫폼을 나타낸다. 나머지 인자들은 그 순서에따라 공급될수 있게 지원한다.

와일드카드의 이점을 얻을 수도 있다. 이 경우, 와일드카드는 첫번째 인자로 들어가있는 별표이다. 이것은 사용하는 모든 플랫폼의 API에서 소통한다. 이 클래스는 버전1.2에서 처음 소개되었다(두번째 인자가 설명하는 바).

깔끔하지만, 꽤 길다. 고맙게도 더 단축된 문법으로 초점을 맞출 수 있다.
@available(iOS 10.0, macOS 10.12)
class TechnicalPost {}
더 낫다! 이 속성에대해 잘 알지 못하더라도, 이제 우리가 만들어낸 API는 이 클래스가 사용될때 명확하게 볼 수 있다.

그러나 그대로 두면 컴파일타임 에러가 나올 것이다. 왜일까?

애플은 새로운 플랫폼을 소개하는 경향이 있는데, 우리는 우리 코드에 이것을 설명해야한다. 그러기위해서 이 코드는 제공된 플랫폼과 다른 잠재적인 미래의 플랫폼에 사용가능하다는 마지막 인자로서 와일드카드를 넣는다.
@available(iOS 10.0, macOS 10.12, *)
class TechnicalPost {}
이 방법은 여러분의 코드가 다음 애플의 계획에대해 이미 준비해 놓았다는 뜻이다. For now though, 애플은 오늘날에 존재하는 각 플랫폼을 표현한 것들을 나열하여 제공하고 있따.
  • iOS
  • iOSApplicationExtension
  • macOS
  • macOSApplicationExtension
  • watchOS
  • watchOSApplicationExtension
  • tvOS
  • tvOSApplicationExtension

다음 속성으로 가보기 전에, 다른 공통성을 생각해보자. 최근의 변경에서, 우리 API가 쓰지않게 되었고, 세상에있는 iOS 개발자들은 기술 블로그 포스트를 표현하는 사랑스러운 약간의 JSON으로 제공하도록 책임을 맡게되었다.

이처럼, 원래 클래스는 더이상 필요없게 되었다. 그리고 디프리케이트시킬 시간이 된 것이다.
@available(*, deprecated: 1.3)
class BasicPost {}
한 스위프트에서 모든 플랫폼에서 BasicPost를 디프리케이스 시켜, 와일드카드 인자가 유용해질 수 있게 명확해졌다.

게다가 이것으로 계속하면서 조금씩 리팩토링 하고 싶으면, API 네이밍 변경사항을 알도록 제공해야 할것이다. 내가 애플로부터 발견한 기술의 예의는, ㅈ사용자들이 사용하기 더 쉽게 만들기 위해 사용불가능한 인자와 타입에일리어스로 짝을 지을 수 있다는 것이다.
//From an earlier API version
class BasicPost {}

//From a new API version, where we renamed it for whatever reason
class BaseTechnicalPost {}

@available(*, unavailable, renamed: “BaseTechnicalPost”)
typealias BasicPost = BaseTechnicalPost
개인적으로 이것을 좋아하는데, 효율성 관점에서나 비용적 관점에서, 가장 명확한 코드는 항상 최고의 코드이다.

이 속성은 워닝, 에러 등을 연결하여 보다 트릭적인 메시지를 제공한다(여기서 '트릭적인'은 옛 코드를 인자로 지정하는 것을 의미함).

@discardableResult 속성
성숙하고 물려받은 코드베이스에서 작업하고 있다면, 몇몇 함수가 약간 장황하게 되있는것이 놀랍지 않을 수 있다.

아마 90년대 초반부터 만들어진 기능이 지금까지 계속 추가되고 바뀐 코드를 물려받았으며 기능 가체에 결함이 없었을 것이다. (여기엔 소프트웨어가 켜질때 일어나는 124가지의 중요한 작업들이 있을 수 있다. 데이터베이스 접근, 케스 설정, 프로세스에서 몇몇 사인을 초기화하기 - 시나리오는 끝도 없다)

그 전까지, 당신이 돌아와서 필요한 리팩토링을 할 수 있게 프로덕트 매니저를 설득하고 있다.

그렇다. 그런가?! 그렇다.
let someUnusedVarBecauseIHaveToCallThisOldInsaneFunction = anOldInsaneFunction()
그러나 이상한 커플링 이유를 위해 이 함수를 실행하게 되버려서 clang은 이것들을 더 컴파일할 것이다. 이제 우리는 이것을 위해 보여주기위해 사용하지 않는 변수도 가진다. 아래로 내려가면서 이야기해보자.

필요 없을것같은 함수의 결과를 컴파일러에게 말하여 @discardableResult 속성이 도움이 될 수 있다. 컴파일타임에 경고도 없애준다.
@discardableResult func anOldInsaneFunction() -> String{
   //Bunch of business logic occurs
   return “”
}
이제 불러진 함수를 호출한 저 코드는 과거의 소프트웨어 공학적 실수처럼만 남아있을 것이다. 그러나 에러 없이 되고 있을 것이다.

명확성을 추가하기위해 간단하게 _를 할당하여 좀 더 명확한 상태로 만들 수 있다.
_ = anOldInsaneFunction()
때론 소프트웨어 개발에선 우리가 직접 제어하거나 고칠 수 없는 함수나 아키텍처가 있으며, 이 속성이 그런 상황을 조금이라도 더 낫게 해준다.

@autoclosure 속성
@autoclosure는 여러분의 코드베이스에 손쉽게 명확성을 추가할 수 있는 또다른 속성이다. 이것은 인자로 공급된 클로저를 자동으로 감싼다. 클로저가 아무 인자도 스스로 받으면 이것으로 감싸인 표현식의 실제 값을 반환할 것이다.

보기에 좀 혼란스러울 수 있는데, 한번 만나보면 쉽게 이해된다. 고수준 관점에서, 자동으로 클로저로 만들기위해 표현식을 얻는 능력에대해 이야기하고 있다. 여러분이 만약 프로젝트에 유닛테스트를 넣으려 해보았다면, 이미 이 속성들을 몇번 만나보았을지도 모르겠다.

아래처럼 클래스에 간단한 테스트를 넣고 싶다고 가정해보자.
class Programmer  {
   var pay:Int
   init(withPay pay:Int)
   {
       self.pay = pay
   }

   func applyRaise(by amount:Int)
   {
       self.pay += amount
   }
}

class ProgrammerTests: XCTestCase
{
   func testPayRaise()
   {
       let devsPay = 50000
       let raiseAmount = 25000
       let expectedSalaryPostRaise = devsPay + raiseAmount

       let aDev = Programmer(withPay: devsPay)
       aDev.applyRaise(by: raiseAmount)

       XCTAssertEqual(expectedSalaryPostRaise, aDev.pay, "Unexpected salary after raise was applied.")
    }
}
XCAssertEqual의 앞에 두 파라미터는 둘다 클로저인데, 이 클로저는 제네릭 표현식으로 받는다. 함수의 시그니처가 약간 위협적으로 보이지만, 처음 두 파라미터는 @autoclosure의 이점을 취하였다는 것을 인지하자.
func XCTAssertEqual<T>(_ expression1: @autoclosure () throws -> T?, _ expression2: @autoclosure () throws -> T?, _ message: @autoclosure () -> String = default, file: StaticString = file, line: UInt = line) where T : Equatable
@autoclosure 속성이 제공되었기 때문에, 함수를 호출하는 것이 꽤 가독성 좋고 우리에게도 쉬워졌다. 클로저를 값처럼 무언가를 간단하게 전달하거나(이전 예제에서 했던 것처럼) 약간의 로직을 더 전달할 수 있고, 각각은 가볍게 사용할 수 있다.
class ProgrammerTests: XCTestCase
{
   func testPayRaise()
   {
       let devsPay = 50000
       let raiseAmount = 25000

       let aDev = Programmer(withPay: devsPay)
       aDev.applyRaise(by: raiseAmount)

       XCTAssertEqual(aDev.pay + raiseAmount, 750000, "Unexpected salary after raise was applied.")
   }
}
첫번째 인자가 제공될때, 클로저라기보단 추가적인 연산처럼 읽힌다.
XCTAssertEqual(aDev.pay + raiseAmount, 750000, "Unexpected salary after raise was applied.")
반대로 @autoclosure 속성이 없을때는 어떻게 생겼을지 보자.
XCTAssertEqual({
     return aDev.pay + raiseAmount
,}, {
   return 75000}
, "Unexpected salary after raise was applied.")
여러분도 볼 수 있듯, (문법대로) 완전한 클로저를 전달한다. 읽고 쓰기에 좀 많은 편이다. 하나가 마지막 인자로 후행 클로저(trailing closure)를 사용할 수 없으면, 복합적인 문제가 될 수 있다.

@autoclosure를 사용하면 그 안에 랩핑된 실제값을 반환한다. 아마 자동으로 파라미터가 클로저가 된다고까지 말할 수 있으므로... @autoclosure이다!

이 코드는 상속적으로도 지연된다. 실제 클로저가 결국 무거운 작업을 하고 있거나 의도치않은 사이트 이팩트를 낳았다면 추가적인 이점이 있다. 제공된 코드는 안에 감싸인 클로저가 되기 전까지 절때 실행되지 않는다.

더 나아가서, 여러분의 최근 iOS 작업물의 어디에서 보았을까? assert()는 어떨까?
struct Programmer
{
   var isSenior:Bool
   var appsShipped:Int
}

let aSeniorDev = Programmer(isSenior: true, appsShipped: 13)

assert(aSeniorDev.isSenior, “This dev isn’t a senior!”)
제공된 첫번째 인자는 @autoclosure를 사용한다. 그렇지 않았다면 이렇게 호출해야 했을 것이다.
assert({
     return aSeniorDev.isSenior
}, {
    return “This dev isn’t a senior!”
})
@autoclosure를 사용하면, 코드는 작성할때 좀 더 쉬워지고, 즐겁게 코드를 읽게 만드는 경험을 제공한다고도 말할 수 있겠다.

그리고 assert() 시그니처가 어떻게 생겼는지 궁금하다면, 이렇게 생겼을 것이다.
func assert(_ condition: @autoclosure () -> Bool, _ message: @autoclosure () -> String = default, file: StaticString = file, line: UInt = line)
우리는 이 시그니처에서 두가지 파라미터로부터 벗어나면, 여기에 할당된 디폴트 값이 있기 때문에 예제에서 이것들을 생략한다. 여러분도 알다시피 그렇지 않을까?

여러 속성사용하기
각 속성은 그들이 가지고 있는 것으로 도와줄 수 있는 이점을 가지지만, 어떤 시나리오에서는 이것을 같이 사용하는 것도 도움이 된다.

예를들어, @escaping 속성을 보자. 이 @escaping 속성은 클로저에 들어온 것이 보냈던 함수보다 오래 살 수 있게 해준다.
//A property on a view controller
var onFakeCompletions:[()->()] = []

func fakeNetworkOp(_ completion:@escaping ()->())

{
   //Network stuff happens
   //The closure is appended to an external array outside of the function's scope. This implies it could be invoked outside of the function - i.e., it could "escape" it
   onFakeCompletions.append(completion)
}
이것을 고려하여 같은 파라미티터에 @escaping@autoclosure를 둘다 쓸 수 있다. 예제로 인사부(H.R.)를 생각해보자.  예를들어 임금 인상을위해 직급이 "Senio"인 개발자이면서 적어도 세개의 앱을 출시한 개발자가 누구인지 알려주는 인사부(H.R.)를 생각해보자. 이 개발자는 이력적인 이유로 각각의 평가를 계속 추적해야한다(As an example, let's imagine H.R. let us know that any developer who is both a "Senior" in title and has shipped at least three apps is due for a raise, but we also need to keep track of each evaluation for historical purposes).
class Programmer
{
   var previousPayRaiseEvaluations:[()->Bool] = []
   var isSenior:Bool = false
   var appsShipped:Int = 0

   func evaluatePayRaise(withAccolades raiseEvaluation:@escaping @autoclosure ()->Bool)
   {
       if raiseEvaluation()
       {
           //Give them a raise, and then save it to their records
           previousPayRaiseEvaluations.append(raiseEvaluation)
       }
   }
}

let aProgrammer = Programmer()
aProgrammer.isSenior = true
aProgrammer.appsShipped = 4

print("Past pay raise evaluations: \(aProgrammer.previousPayRaiseEvaluations.count)") //0

aProgrammer.evaluatePayRaise(withAccolades: aProgrammer.isSenior && aProgrammer.appsShipped > 3)

print("Past pay raise evaluations: \(aProgrammer.previousPayRaiseEvaluations.count)") //1

   func evaluatePayRaise(withAccolades raiseEvaluation:@escaping @autoclosure ()->Bool)
   {
       if raiseEvaluation()
       {
           //Give them a raise, and then save it to their records
           previousPayRaiseEvaluations.append(raiseEvaluation)
       }
   }
}

let aProgrammer = Programmer()
aProgrammer.isSenior = true

aProgrammer.appsShipped = 4

print("Past pay raise evaluations: \(aProgrammer.previousPayRaiseEvaluations.count)") //0

aProgrammer.evaluatePayRaise(withAccolades: aProgrammer.isSenior && aProgrammer.appsShipped > 3)

print("Past pay raise evaluations: \(aProgrammer.previousPayRaiseEvaluations.count)") //1
확실히 속성들을 함께 "채이닝(chaining)"하는 것을 금지하지 않는다. 그리고 그 상황을 위해 호출할때 꽤 매끄럽게 동작한다.

마지막 생각
속성은 항상 내 코드에서 사용에대해 특별히 열정적으로 해왔던 것이다. 나는 명확하고 정확하고 간단한 방법으로 강력하게 무거운것을 들어올리는 아이디어를 좋아한다. 그리고 이것이 정말로 속성이 하는 일이다. 물론 스위프트와 Objective-C 프로젝트 어디에나 있는 @objc 속성같은 알만한 가치가 있는 것들도 있다.

이것을 고려하여, 한 인자는 속성이 nicety 보다는 necessity에 더 가깝게 만들어질 수 있다. 끝으로 개발자로서 이 모든것들은 여러분의 작업 플로우의 최적화에대한 것이다. 여러분의 코드베이스나 다른 곳에 이렇게 만들어라. 속성은 이런 최적화를 달성하기위한 하나의 방법에 불가하다.

앱이 완성되고나면, buddybuild 같은 서비스를 사용하여 여러분의 지속적 통합(CI, Countinuous Integration)과 배포 과정같은 부담되는 일을 자동화할 수 있겠다.

Buffer에서는 이미 우리 개발 작업 플로우를 최적화하기위한 방법을 항상 찾아보고있고, 이것들은 우리를 도와줄 수 있는 방법 중 우리가 찾은 것이다.


이 블로그는 공부하고 공유하는 목적으로 운영되고 있습니다. 번역글에대한 피드백은 언제나 환영이며, 좋은글 추천도 함께 받고 있습니다. 피드백은 

으로 보내주시면 됩니다.



WRITTEN BY
tucan.dev
개인 iOS 개발, tucan9389

,

Swift는 escaping 클로저와 non-escaping 클로저에 차이를 두고 있다. escaping 클로저는 한번 호출되고나면 리턴값으로 클로저를 반환하는 함수이다. 클로저는 인자로 받은 함수 스코프를 escape한다.

클로저를 escape 하는 것은 종종 아래 예시처럼 비동기 컨트롤 플로우와 연관되있다. 
  • 함수가 백그라운드 작업을 시작하고 즉시 리턴하면, 완료 핸들러를 통해 백그라운드 작업의 결과를 알린다.
  • 뷰 클래스가 버튼 탭 이벤트를 다르기위해 프로퍼티에 클로저를 저장해둔다. 이 클래스는 사용자가 버튼을 탭 할때마다 클로저를 호출한다. 클로저 프로퍼티는 세터를 escape한다.
  • 여러분은 DispatchQueue.async를 사용하여 디스패치 큐에서 비동기 실행을 위한 작업을 스케줄링한다. 이 테스크 클로저는 비동기에 호출된 이후에도 소멸되지 않은 채 살아있다.
DispatchQueue.sync와 대조되는데, 이것은 리턴되기 전에 테스크 클로저의 실행이 끝나기 전까지 기다린다. 이 클로저는 절때 escape하지 않는다. 표준 라이브러리에 map, 다린 일반적인 sequence, 그리고 collection 알고리즘에도 동일하다.

escaping 클로저와 non-escaping 클로저의 차이가 왜 중요할까?
간단히 말해 메모리관리 때문이다. 클로저가 붙잡고있는 모든 오브젝트는 강참조로 들고 있으며, 클로저 안에서 self의 프로퍼티나 self의 메소드에 접근하려 한다면 이 모든것들이 묵시적으로 self 파라미터를 다루기 때문에 self까지 포함하여 들고 있는다.

이러한 방식은 굉장히 참조 사이클(reference cycle)을 마주치기 쉬운데, 이것이 왜 컴파일러가 클로저 안에서 명시적으로 self를 쓰게 만드는지에대한 이유이다. 명시적으로 쓰게 만듦으로서 당신에게 잠재적인 참조 사이클에대해 생각해볼 수 있게 해주고, 붙잡고 있는 항목들을 이용해 손수 해결할 수 있게 해준다.

그러나 non-escaping 클로저로는 참조 사이클을 만드는게 불가능하다. 클로저는 함수 리턴시점까지 붙잡아둔 모든 오브젝트를 릴리즈(release) 시킬 것이라는 것을 컴파일러가 보장한다. 이러한 이유로 escaping 클로저에서만 명시적으로 self를 사용하여 참조할 것을 요구한다. 이 점이 non-escaping 클로저 사용을 더 확실히 즐겁게 해준다.

non-escaping 클로저의 또다른 장점은 컴파일러가 더 적극적으로 퍼포먼스 최적화를 수행할 수 있다는 점이다. 예를들어 클로저의 라이프타임을 알고 있을때 몇몇 리테인(retain)과 릴리즈(release) 호출은 생략할 수 있다. 또, non-escaping 클로저라면 클로저의 컨텍스트를 위한 메모리가 힙이 아닌 스택에 담아둘 수 있다.(현재 Swift 컴파일러가 이러한 최적화를 시키는지는 잘 모르지만 2016년3월 버그리포팅에서 그렇게 하지말자고 제안이 들어왔었다)

디폴트로 클로저는 non-escaping이다.
Swift3부터 non-escaping 클로저가 디폴트이다. 만약 클로저 파라미터에 escape 시키고 싶으면 그 타입에다가 @escaping 지시자를 써야한다. 예를들어 DispatchQueue.async (escaping)와 DispatchQueue.sync (non-escaping) 선언이 있다.

Swift3 전까지는 좀 다른 방식으로 동작했었는데, escaping이 디폴트이고 오버라이드에 @nonescape를 추가할 수 있었다. 이러한 새로운 동작은 디폴트에의해 더 안전해진다는 면에서 더 좋은데, 이제 함수 인자는 반드시 참조 사이클의 잠재성이 있다는 것을 명시적으로 표시해주어야한다. 따라서 @escaping 지시자는 이 기능을 사용하는 개발자에게 경고를 해주는 역할을 한다.

...그러나 오직 즉석 함수 파라미터(immediate function parameters)로서
디폴트에의한 non-escaping 규칙에 대해 주목할 것이 있다. 이는 직접 함수 매개 변수 위치의 클로저에만 적용된다. 즉, 함수 타입을 가지는 모든 함수 인자에 적용된다. 다른 모든 클로저는 escaping하고 있다.

직접 파라미터 위치가 무슨 뜻일까?
예제를 한번 보자. 가장 간단한 예제로는 map이 있다. 이 함수는 직접 클로저 파라미터를 받는다. 우리가 보았듯 클로저는 non-escaping이다. (여기서 실제 map의 표시를 말하려는게 아니므로, 그것들을 조금 생략하겠다)

함수 타입의 변수들은 항상 escaping이다.
반대로, 변수나 프로퍼티가 함수 타입을 가질 수 있다. 이때는 명시적인 지시 없이 자동으로 escaping이 된다.(사실은 @escaping을 넣으면 에러가 뜬다) 이렇게 이해할 수 있는데, 변수에 값을 할당하면 묵시적으로 값을 변수의 범위로 escape할 수 있기 때문이다. 이것은 non-escaping 클로저로 허가될 수 없다. 그러나 드물게 지시되지 않는 함수는 파라미터에서의 의미가 아닌 다른 곳에서는 다른 의미를 가지기 때문에 혼란스러울 수 있다.

옵셔널 클로저는 항상 escaping이다.
더욱 놀라운 점은, 파라미터로 쓰이지만 다른 타입(튜플이나 enum case, 옵셔널 같은)으로 감쌓여진 클로저들 또한 escaping이라는 것이다. 이 경우에 클로저는 더이상 직접 파라미터가 아니므로 자동으로 escaping된다. 그 결과 Swift3에서는 파라미터가 옵셔널이면서 동시에 non-escaping한 곳에 함수인자로 받는 함수를 만들지 못한다. 아래의 다소 인위적인 예제를 생각해보자. transform 함수는 정수 n과 옵셔널 변환 함수인 f를 받아 f(n)를 반환하거나 f가 nil이면 n을 반환한다.

여기서 ( (Int) -> Int )?가 Optional<(Int) -> Int>의 축약이기 때문에 함수 f는 escaping이며, 따라서 함수 타입은 직접 파라미터 위치에 있지 않다. 이 결과는 우리가 바라는 것이 아닌데, 여기서 f가 non-escaping 될 수 없는 이유가 없기 때문이다.

옵셔널 파라미터를 디폴트 구현으로 대체하자.
Swift 팀은 이 한계를 알고있고, 미래의 배포에서 고치기로 계획했다. 그전까지 우리가 이것을 알고 있어야한다. 현재 강제로 옵셔널 클로저를 non-escaping할 방법은 없지만, 많은 경우에 클로저에다 디폴트 값을 제공하여 옵셔널 인자를 피할 수 있을 것이다. 우리 예제에서는 디폴트 값이 항등 함수(identity function)이고, 이 함수는 간단하게 인자를 바꾸지않고 그대로 반환한다.

옵셔널과 non-escaping 변형을 제공하기 위해 오버로드를 사용하자
디폴트 값을 제공하기 힘든 경우라면, Michael Ilseman이 오버로드를 사용할 것을 제안했다. 여러분은 함수의 두기자 변형을 만드는데, 하나는 옵셔널(escaping) 함수 파라미터를 받고, 하나는 non-옵셔널, non-escaping 파라미터를 받는다.

어떤 함수가 호출되었는지 설명하기 위해 print 상태를 추가했다. 여러 인자로 이 함수를 테스트해보자. 당연하게도 nil을 보내면, 그 인풋과 일치하는 것이 하나밖에 없으므로 타입 체커가 첫번째 오버로드를 선택한다.
동일하게, 옵셔널 함수 타입을 가지고 있는 변수를 보낸다.
그 변수가 non-옵셔널 타입일지라도, Swift는 여전히 첫번째 오버로드를 선택할 것이다. 그 이유는 변수에 저장된 함수는 자동으로 escaping되고, 따라서 non-escaping 인자를 예상한 두번째 오버로드와는 일치하지 않는다.

그러나 클로저 표현식을 보낼때 이것은 바뀐다. 즉 함수 리터럴이 이 자리에 있을때 말이다. 이제 두번째 오버로드 non-escaping이 선택된다.

리터럴 클로저 표현식으로 higher-order 함수를 호출하는 것은 굉장히 일반적이므로, 대부분의 경우 선택적으로 여전히 nil을 보낼 수 있게 해줌으로서 여러분을 즐거운 길(참조 사이클을 생각할 필요 없는 non-escaping)로 안내해줄것이다. 이런 방식으로 한다면 왜 두가지 오버로드가 필요한지 증명할 수 있을 것이다.

타입에일리어스(typealiases)는 항상 escaping이다.

마지막으로 한가지 알고 있어야 하는 것은 Swift3에서는 타입에일리어스에 escaping 혹은 non-escaping 지시자를 넣을 수 없다는 것이다. 함수 선언에서 함수 타입을 위해 타입에일리어스를 사용한다면 그 파라미터는 항상 escaping으로 생각될 것이다. 이 버그 수정은 이미 마스터 브런치에서 이루어졌고, 다음 배포에 적용될 수 있을 것이다. 



WRITTEN BY
tucan.dev
개인 iOS 개발, tucan9389

,