2016년 9월 20일 화요일

Xcode 8 GM 으로 겪어보는 Swift 3 의 변화들 #2

Xcode 8이 드디어 베타 딱지를 떼고 GM 마크를 박고 올라왔다. 이제 기능 면에서 바뀔 일은 없을 것 같으니 마지막으로 마무리 하는 겸으로 무엇이 바뀌었는지에 대한 경험담을 이어서 적어본다.

이 글은 'Xcode 8 Beta 로 겪어보는 Swift 3 의 변화들' 글에서 정리한 내용을 제외하고 GM 버전에서 추가로 발견한 사항들을 정리한다.

이 글을 올리는 오늘 Xcode 8 이 정식 릴리즈 되었다는 것은 참 거시기하다

Access Control 이 뒤엎어졌다

기존에는 public, internal, private의 세 가지 지시어가 있었는데 이게 아래와 같이 늘었고 의미 또한 바뀌었다.
  • public: 기존과 비슷하게 내외부 가리지 않고 접근 가능 이라는 의미이다. 대신 오버라이드를 할 수 없게 바뀌었다. 즉 Swift 2.x 버전의 public final 의 의미와 동일하다.
  • open: 이전의 public 의 기능은 open 이라는 키워드로 옮겨왔다. 제한 없이 공개도 되고 오버라이드 제한도 없는 녀석이다.
  • internal: 이전과 동일한 듯 하다.
  • private: 소속된 네임스페이스 내에서만 접근이 가능한 것으로 바뀌었다. 이제 같은 파일이라도 private 로 선언된 클래스나 메소드 등에는 그 외부에서 접근 할 수 없다.
  • fileprivate: 이건 기존의 private 와 비슷하게 동일 파일 내에서는 액세스가 가능하게, 그 외에는 접근 할 수 없게 만들어준다.
뭔가 세분화가 많이 되었는데 난 마음에 안든다. 기존 방식에 익숙해서 그렇겠지만...

NSDicionary 호환성

기존의 Objective-C 에서 선언된 NSDictionary 타입은 [AnyObject : AnyObject] 타입이었다. 그래서 Swift 에서 이 NSDictionary 오브젝트에는 구조체(struct)타입의 데이터를 사용 할 수 없는 문제가 있었다.
AnyObject 는 이름처럼 Object 즉 클래스 인스턴스만을 가리키는 타입이다.
그런데 이제 NSDictionary 의 Swift 인터페이스는 별 다른 Generics 를 지정하지 않을 경우 기본적으로 [AnyHashable : Any] 타입으로 컨버전 된다.

즉, 이제 NSDictionary 인스턴스에 오브젝트 타입 대신 구조체 값(Value)을 넣는게 가능해졌다.

Swift 친화적인 NSNumber

이전에도 Foundation 의 Swift 친화에 대해 이야기 했었는데, NSNumber 도 Swift 타입에 어울리게 바뀌고 있다.

예를 들어 특정 NSNumber 오브젝트의 64비트 정수형을 얻기 위한 getter 인 longLongValue 대신 Swift 3 에서는 int64Value 형식으로 접근하도록 바뀌었다.

또한 앞서 이야기한 NSDicionary 와의 호환성 향상에서 이야기 했다싶이 Any 타입을 사전형에서 사용할 수 있게 됨으로써 숫자 데이터를 형변환으로 가져올 수 있게 되었다. 무슨 말인지 모르겠으면 아래 예제를 보자. 우선은 Objective-C 코드이다.
// Objective-C Code
NSDictionary *someDict = @{ @"value": [NSNumber numberWithInt: 10] };
Objective-C 에선 일반 수치 데이터의 경우 객체가 아니기 때문에 포인팅이 불가능하기 때문에 NSNumber 오브젝트로 만들어서 사전형 데이터로써 사용했었다.

Swift 에서 위 사전형 데이터에 엑세스 하려는 경우 예전에는 'value' 키에 지정된 값을 가져올 때 NSNumber 형식으로 가져온 뒤 여기서 intValue 를 호출해서 값을 가져와야 했다.
// Swift 2.x (Xcode 7.x)
let value = (someDict["value"] as! NSNumber).intValue
Swift 3 에서는 아래와 같이 가져올 수 있다.
// Swift 3 (Xcode 8)
let value = someDict["value"] as! Int
한마디로 Swift 스러워졌다.

안쓰면 무조건 귀찮게 할거야!

기존에도 변수 등을 선언해 놓고 안쓰면 경고가 나왔는데, 이제는 더 귀찮게 한다. 함수의 반환값을 받아서 처리하지 않으면 무조건 경고를 낸다. @warn_unused_result 가 기본이 된 셈이다.

물론 반환값이 없는 경우는 제외지만 말이다.

만약 반환값이 필요없는 경우라면 아래와 같은 식으로 경고를 무시하는 방법이 있긴 있다.
_ = someFunc()

이제 let 은 각각이 let 이어야... 뭐?

그냥 아래 코드를 보자.
guard let a = someA, b = someB else { return }
someA, someB 라는 옵셔널 변수의 값이 nil이 아니어야 guard 문을 통과할 수 있는 코드다.

Swift 3 에선 위 코드를 아래처럼 고쳐야 한다.
guard let a = someA, let b = someB else { return }
뭐가 바꼈는지 알 수 있을 것이다. let 을 모두 붙여줘야 한다. 개인적으론 이런 스타일이 마음에 든다.

클로져는 기본적으로 도망(?) 칠 수 없어

함수나 메소드 등의 변수로 쓰이는 클로져는 이전과는 반대로 @noescape 속성이 기본적으로 붙게 되었다. 아래 코드를 보자.
func someWork(completed: () -> ()) {
  ...
  DispatchQueue.global().async {

    ...
    DispatchQueue.main.async {
      ...
      completed()
    }
  }
}
평범한(?) GCD Dispatch Queue 를 활용해 비동기로 실행하는 유명한 예이다. 그런데 Swift 3 에서 이런 식의 코드는 Closure use of non-escaping parameter 'closureName' may allow it to escape 라는 형식의 오류가 발생한다. 따라서 아래처럼 해당 클로져의 escape 속성을 명확하게 해 주어야 한다.
func someWork(completed: @escaping () -> ()) { ... }
@escaping 속성은 '이 클로져가 어딘가 저장되거나 나중에 호출 될 수 있게 메소드 등에 전달되는 용도로 사용될 수 있다' 라는 의미로 보자. 위의 경우는 디스패치 큐를 통해서 다른 스레드에서 별도로 클로져가 실행되는 환경이기에 escaping 이 필요하다.

반대로 클로져가 다른 변수로 전달되는게 아니라 해당 메소드 내에서 호출되고 마는 경우라면 별도의 escaping 에 대한 속성은 필요없다. 앞서 이야기 했지만 기본적으로 @noescape 가 기본 속성이기 때문이다.

[이전글] Xcode 8 Beta 로 겪어보는 Swift 3 의 변화들
[관련글] Swift - @noescape 너 정체가 뭐냐
[관련글] 스위프트(Swift) 가이드

댓글 3개 :

jusung :

지식 공유해 주셔서 감사합니다.
매번 볼때마다 지식의 깊이에 감탄합니다.
항상 잘 보고 있습니다. :)

Renn Seo :

따로 공부한 지식이 아니라 실무에서 경험한 것들 위주로 정리한 내용입니다. 지식의 깊이라니 부끄러워지네요. ;-)

Myeong-Hyeon Lee :

소중한 경험 공유 해주셔서 감사합니다~
잘 읽고 갑니다~!