제목: Naming Things in Swift

최근에 나는 여러 프로그래밍 언어와 환경을 사용해보고 있고, 내 기술을 다양하게 만드려 노력하고 있다. 나는 보통 리액트(React), 스위프트, Objective-C, 스칼라로 작업해왔다. 이것들은 각자 그들의 어풍과 규약을 가지고 있다. 나는 실제 경험을 통해 배우면서 언어를 비교하고 언어의 차이를 발견하여 더 나은 스위프트 개발자가 되는데 적용시켜보기로했다.

내가 스칼라를 배울 수 있게 도와준 내 상사는 네이밍에관한 블로그 포스팅(링크)을 보내주었고 스칼라에서 다른 수준의 장황함을 사용할 때 그것의 포괄적인 설명으로 나에게 깊은 인상을 주었다. 오늘 내 목표는 스위프트에서 언제 간결해야하고 언제 설명을 덧붙여야하는지에대한 위 포스팅과 비슷한 멋진 글을 쓰려한다. 이 스칼라 포스팅에서 몇 예시를 빌려 스위프트와 iOS 앱을 연관시킬 것이다.

여러분이 프로그래밍할때 간결함을 좋아하든 하지않든, 스위프트는 당신이 선호하는 수준의 말수로 코드를 작성할 수 있게 하는 기능을 가지고 있다. 네이밍을 넘어, 트레일링 클로저 문법, 이름없는 파라미터, 익명의 클로저 인자는 어떨때는 간결하게 해주고, 어떨때는 풀어서 설명해준다.

간결해야하는지 아닌지 그 질문이 아니라, 바로 어디에 간결하게(혹은 풀어서 길게) 하면 되는지이다.

스위프트는 꽤 오랫동안 겪어왔는데, 이것은 코드가 제네럴하게 접목될 수 있는 관용구를 개발하면서 시작되었었다. 스위프트 창시자들은 휼륭한 문서인 offical API design guidelines(링크)를 배포하는데 충분히 친절해왔다. 이것들이 좋긴하지만 나는 좀 더 원하는게 따로 있다. 우리가 어떻게 스위프트의 관용적인 직감을 만들 수 있는지 이야기해보고 싶다. 우리는 세부적으로 직관적으로 네이밍하는 것에 대해 다룬 뒤 언어의 특성에대해 토론해볼것이다.

철학
스위프트 API 설계의 원리에서 특히 네이밍이 언급하는 것은 다음과 같다.
  • 사용되는 시점에서 명료함은 가장 중요한 목표이다.
  • 명료함이 간단함보다 더 중요하다.
멋진 가이드라인인데, 좀 더 깊게 들어가보자. Haoyi의 스칼라 블로그 포스팅에는 우리가 뭔가 네이밍을 붙일때 우리의 목표가 무엇인지 말해준다.
프로그래머가 아직 모르지만 알고싶어하는 것을 보여주어라
매우 흥미로운 가이드라인이며, 이것은 우리에게 코드의 맥락을. 생각하게 만들고, 미래에 어디에서 동작할지 생각하게 만든다.코드는 한번만 쓰여지지만, 계속해서 읽혀진다. 따라서 프로그래머들은 읽기 편하게 최적화시키고 작성하는 것에 힘을 들여야함을 잊지말자. 그리고 읽기 최적화될때 고려해야할 가장 중요한 점은 바로 문맥이다. 스칼라 블로그 포스팅에서 이것들을 잘 정리해 놓았다(링크). 그리고 문맥이란 프로그래머가 이미 알고 있는 것과 프로그래머가 알고 싶어 하는 것 둘 다를 모두 포함한다.

프로그래머가 이미 아는 것
  • 당신의 코드베이스에서 예전에 이미 본 것
  • 다른 코드베이스에서 예전에 이미 본 것
  • 이전 작업에서 그들이 골랐던 사실들
프로그래머가 알고 싶어 하는 것
  • 그들이 하는 것에 영향을 주는 것
  • 그들이 이해할 필요가 있는 것
  • 그들이 익숙하지 않은 것
  • 정확함, 보안, 성능등의 이유로 특별히 위험한 것
이것은 포괄적인게 아니다.

누구나 그리고 언제나, 당신의 코드를 읽고 있다고 생각해라. 코드를 매일 사용하는 사람이 직장동료일까? 아니면 지금으로부터 6개월뒤의 자기자신일까? 당신의 오픈소스 프로젝트에 가볍게 컨트리뷰트를 하려고 하고있는것일까? 이러한 여러 상황들은 어떤 함수의 이름을 어떻게 정할지 영향을 받을 것이다. 설명해보자.

당신의 코드를 매일 사용하는 한 동료의경우 당신의 코드베이스와 그것의 규약에 완전히 친숙하다면 간결한 코드가 최고일 것이다. 만약 6개월동안 그 코드베이스에서 작업할 계획이 없다면 그 규약이 결국 생소하게 되어갈 것이므로 설명하는 말처럼 만드는게 더 도움이 될 것이다. 오픈소스 프로젝트의 가벼운 컨트리뷰터들은 아마 큰 코드베이스가 어떻게 서로 맞춰지는지 이해할 수 없을 것이다. 따라서 지나친 설명은 당신 프로젝트의 컨트리뷰터가 이해하는데 도움을 줄 수 있을 것이다.

어떤 사람이 당신의 코드를 읽고싶어할지, 그리고 그들의 목적이 무엇인지 생각해보아라.

가이드라인
이것은 원칙이 아닌 가이드라인이다. 여러분의 직감이 규칙을 지키기 싫어한다면 그렇게하지 말아라. 이제 중요한 순서로 네이밍에 관한 가이드라인에대해 이야기해보고자한다. 그리고 항상 마음속에 문맥을 기억하자!

(네기 지금 이 글에서 나온(링크) 가이드라인을 스위프트에 적용시키는 점을 기억해달라-우리는 이 포스팅과 그 저자인 Li Haoyi(링크)에게 감사해야한다)

넓은 범위의 이름은 길어야한다
이 예제에서 i라는 이름이 왜 괜찮을까?
for var i in 0..<10 {
  print(i)
}
그러나 여기서는 왜 안될까?
struct MyStruct {
  let i: Int
}
i라는 놈이 코드베이스 어디에서 쓰이는지 생각해보아라. 처음 예제에서 i는 for문 안에서만 접근되었다. 그러나 두번째 예제는 구조체의 맴버이며 저 구조체를 사용하는 어디에서나 i를 접근할 수 있는데, 잠재적으로 모든 코드베이스에서 사용가능하다. 기볍게 본 것으로 i는 매우 널리 쓰일 수도 있다는 이유때문에 i의 문맥을 찾을 수 없다.

우리는 프로그래머들이 아직 모르지만 알고싶어할 코드 어떤것을 알려주어야한다는 점을 잊지말자. 위 구조체를 고쳐보자.
struct MyStruct {
  let numberOfInteractions: Int
}

여기서 말하고자하는 바는, 루프에서 쓰이는 모든 변수가 짧아야함을 의미하는게 아니라, 넓게 쓰일 것의 이름은 길어야한다는 의미이다. 이에 반해 아래 예제를 보자 이 예제는 루프 안에서도 짧은 변수가 나쁜 방법일 수 있음을 보여준다.
for var i in 0..<10 {

  ...

  ...

  let data = Data(repeating: 0, count: i)

  ...

  ...

  writeToDb(transformedData, i) // Tricky C API...

  ...

  ...

  ...

  let temp = i + 1

  ...
}
우리 모드 i가 길어야 좋을 거라는 점에 동의할 것이라 생각된다. 왜냐? 그 사용의 범위가 넓은데다가 더 사용되기 때문이다. 이것이 다음 가이드라인으로 연결시켜준다.

더 사용된 이름들은 짧아야한다.
스위프트에서 처음 배웠던 함수인 print를 한번 보자. 함수의 이름처럼 "print"는 아주 완벽하게 동작한다.
print("Hi there!")
그렇다면 왜 "cache"가 별로 좋지 않은 것일까?
class Downloader {
  func cache() { ... }
}

...

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
  func applicationDidFinishLaunching(_ application: UIApplication) {
    ...
    downloader.cache() // Only called at app startup.
  }
}
print는 아주 많이 사용되고 모든 스위프트 개발자들이 그것에 익숙하다고 생각할 것이다. cache는 한번만 쓰이며 거의 모습을 보지 못하는 커스텀 객체에 정의해 놓는다. 이런것은 긴 이름이 의미가 있다.
class Downloader {
  func initializeCache() { ... }
}
훨씬 낫다.

위험한 이름은 길어야한다. 
몇 함수들은 그들이 하는 때문에 이름이 길다. 위험한 함수들은 이름이 길어야하는 반면 빈번하게 쓰이는 일상의 함수들은 짧아야한다. 여기 아주 긴 함수이다.
extension Downloader {
  func loadDataFieldsFromOfflineCache() { ... }
}
대신 loadFromCache으로 될 수 있습니다.
extension Downloader {
  func loadFromCache() { ... }
}
그러나 이 함수를 길게하는 것이 의미가 있는지 생각해보자.
extension Downloader {
  func deleteAPICredentialsFromCache() { ... }
}
이 함수를 호출하는 것이 위험하여 긴 이름을 가지게 되었다. 우리는 사고로 사용자의 데이터를 지워버리는 일은 항상 피하고 싶을 것이다. 그렇기 때문에 이렇게 너무 간결하게 호출하고 싶지 않을 것이다.
extension Downloader {
  func delToken() { /* deletes user data omg! */ }
}
우리는 프로그래머들이 아직 모르지만 알고싶어할 코드 어떤것을 알려주어야한다는 점을 잊지말자. 이것이 사용자 데이터를 제거할 때 함수를 호출하는 누군가가 당연히 그 사실을 알고 싶어할 것이라고 상상한다.

소스 문맥의 이름들은 짧아야한다.
내부 타입이 존재하는 타입 이름은 짧아야한다. 그리고 외부 타입이 존재하면 길어야한다. 아래를 한번 생각해보자.
protocol Delegate {
  ...
}
Delegate 프로토콜이 무엇을 위한 것인지 알 수 없으므로 이것은 너무 짧다. 좀 더 긴 이름을 붙여서 더 낫게 해보자.
protocol DownloaderDelegate {
  ...
}
멋지다! 이제 저 프로토콜이 무엇을 위한 것인지 알도록 도와준다.

스위프트 컴파일러가 타임으로 프로토콜을 도와준다면 대안의 방안이 될 수 있다.
class Downloader {
  protocol Delegate {
    ...
  }
}
이것은 충분히 자격이 있는 Downloader.Delegate로 확장할 수 있다. 그러나 슬프게도 스위프트는 아직 이런식으로 감쌓여진 프로토콜의 종류를 지원하지 않는다.

그냥 이름으로 타입 정보 중복을 피해야한다.
class Downloader {
  protocol DownloaderDelegate {
    ...
  }
}
개발자들은 이미 Downloader 클래스 안에 타입들이 이 클래스와 동작해야한다는 것을 알기 때문에 정보의 중복은 무의미하다. 이제 마지막 가이드라인으로 넘어가자.

강타입(Strongly Typed) 이름들은 짧아야한다.
스위프트는 강력하게 표현력있는 타입 시스템을 가지며, 우리는 이를 이용하여 이름을 짧게 만들 수 있다. 예를들어 아래 프로퍼티를 생각해보자.
class Downloader {
  var downloaderDelegate: Delegate
}
우리는 이미 저 델리게이트 프로퍼티가 DownLoader 클래스에 속한다는 것을 알기 때문에 프로퍼티 이름으로서 downloaderDelegate를 부르는 것이 불필요하다.

아래에 또다른 카운터 예제가 있다.
func zipTwoSequences<...>(_ sequence1: Sequence1, _ sequence2: Sequence2) -> ...
대신에 표준 라이브러리는 이것만 포함한다.
func zip<...>(_ sequence1: Sequence1, _ sequence2: Sequence2) -> ...
타입 시그니처에서 인자가 시퀀스라는게 확실하기 때문이다.

여기까지가 네이밍 가이드라인에대한 이야기이고, 이제는 간결하게 만들어주는 스위프트 특징들을 이야기해보자!

전반적으로 이름들을 생략하기
설명이 긴 것부터 간결한 것까지 그 범주중에 굉장히 "간결함"의 끝에는 아예 이름이 없는 것이다. 트레일링 크로저 문법, 이름없는 파라미터, 익명의 클로저 인자들로 이름없이 할 수 있다. 그것들을 사용할 때는 아래 가이드라인을 넘는 문제이다.

클로저 문법 추적은 매우 편하고 함수 호출을 더 간결하게 하도록 도와준다. Ray Wenderlich의 스위프트스위프트 스타일 가이드에서 쿨로저 섹션(링크)의 말을 빌리자면, 클로저의 목적이 모호하다면 트레일링 클로저 문법은 사용하지 마라고 한다. 예를들자면 아래같은 경우가 나쁜 경우이다.
UIView.animate(withDuration: 1.0, animations: {
  ...
}) { finished in
  ...
}
이렇게하는 것이 훨씬 더 명료하다.
UIView.animate(withDuration: 1.0, animations: {
  ...
}, completion: { finished in
  ...
})
이름없는 파라미터(unnamed parameters)의경우는 인자 레이블로 공식 스위프트 API 가이드라인을 참고할것이다.
  • 인자들이 유용하게 구별되지 않을때 모든 레이블들을 생략하라.(ex. union(set1, set2))
  • 함수 이름의 문법이 첫번째 인자가 무엇인지 명확할때 레이블들을 생략하라.(ex. addSubview(y))
  • 타입 규약을 위해서는 레이블들을 생략하라.(ex. Int64(someUInt32))
  • 그렇지 않으면 (일반적으로는) 인자 레이블을 명시하라.

마지막으로 익명의 클로저 인자가 남았다. 대부분 클로저의 길이에따라 이것을 사용하는데, "넓은 범위의 이름은 길어야한다"는 규칙과 일맥상통한다.

만약 여러분의 클로저가 몇가지 안되는 일을 한다면 익명의 클로저 인자를 사용하라.
(0..<10).map({ String($0) })
아래는 과하게 설명이 긴 카운터 예제이다.
(0..<10).map({ number in String(number) })
그리고 아래의 것은 네이밍에관해 처음 두가지 가이드라인을 접목하지 않을 때 어떤식으로 생겼을 수 있는지 보여준다.
(0..<10).map({
  ...

  ...

  let data = Data(repeating: 0, count: $0)

  ...

  ...

  return Model(fromData: data, index: $0)
})
다시한번 Ray Wenderlich 가이드로가서 클로저에대한 정보를 살펴보길 바란다.

오늘 다루었던 가이드라인이라는 것은 절대적인 어떤 것이 아님을 기억하자. 경험하고 다른 사람에게 물어보고 그리고 배우자. 그 과정을 즐길 수 있길 바란다!


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

으로 보내주시면 됩니다.



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

,

기억할것: Swift는 여전히 ABI-안정성이 지원되지 않음!
이 글을 읽기 전에 모두가 알아야 할 사실은 Swift가 여전히 ABI에 안정적이지 않다는 사실이다. 이 말은 이전 버전의 Swift 컴파일러로 만든 바이너리가 현재 버전과 호환이 되지 않는다는 의미이다. 좀 더 알아듣기 쉽게 설명하자면 여러분의 모든 파일과 의존성들은 같은 버전의 Swift로 컴파일해야한다는 의미이다: 다른 버전의 Swift로 작성된 코드들은 합칠 수 없다.

애플은 현재 최신버전의 Swift3.0을 깨부수면서 (약속한것은 아니지만) 이것을 해결하려고 노력중에 있으며 Swift4에서 ABI 안정성을 지원할 계획이다.

2.3으로 가야할까 3.0으로 가야할까?
만약 여러분이 우리 VTS처럼 중요한 Swift 코드베이스를 가지고 있다면 아래에 해답이 있다.

그렇다. 두가지 다이다.

여러분의 Swift 코드베이스가 지저분하지 않다면 2.3을 생략하고 3.0으로 넘어가면 된다.

아래에 염두할 것들이다.
  1. 의존성은 중요하다. 여러분의 의존성이 Swift2.3이나 Swift3.0을 지원하는지 확인해야한다. 대부분 주요 라이브러리/프레임워크들은 둘 다 지원하고 있다(Alamofire, Charts). 그러나 기억해야할 것은 Swift3.0을 업그레이드 하는 방법은 하나밖에 없다. Swift3.0으로 새 개발을 시작하고 나면 2.3버전의 모든 의존성이 업데이트 없이는 작동하지 않을 것이다.
  2. 8.x 버전의 Xcode는 Swift2.3 지원을 멈출 것이다. Swift2.3은 Swift3.0으로 업그레이드 하기 힘든 프로젝트를 위한 매개 단계의 경향이 있다.
  3. 만약 여러 프로젝트에 걸쳐진 개인의 CocoaPods이 있다면 먼저 이것부터 업그레이드 해야한다.

2.3으로 업그레이드 할 수 있으나 어느정도 지연이 될지도 모른다. Xcode9가 배포되기 전에 Swift3으로 갈아타야하기 때문이다.

Swift2.2에서 2.3으로 마이그레이션 이슈
마이그레이터는 아마 Range<T>를 CountableRange<T>로 변환할 것이다. Swift3에는 오직 CountableRange<T> 밖에 없기 때문이다.

마이그레이터는 dispatch_queue_set_specific() 사용을 위해 /*Migrator FIXME: Use a variable of type DispatchSpecificKey*/ 주석을 달아 줄 것이다. 이것은 미래의 Swift3으로 마이그레이션 하는 것과 관련이 있다.

Swift2.x에서 Swift3.0으로의 마이그레이션 이슈
Swift의 3.0버전은 "모든걸 깨부순(break everything)" 배포이다. Swift3은 모든 것을 깨부수고 언어적 긴 용어가 필요없는 모든 것을 없앨 것이다. 따라서 많은 변화가 생기고, 프로젝트를 안정화시키기위해 그 많은 것들을 고쳐야할 것이다.

Swift2.3
Swift2.3은 굉장히 마이너한 업데이트이다. 어떤 식으로 마이너할까? 사실 딱 한가지가 바뀌었다. 컴파일타임 nullability가 바뀐 애플 SDK의 Objective-C 코드를 체크하는 것이다.

몇몇 마이너한 이름 재정의과 수많은 옵셔널 생성자와 같은 다른 변화들은 이제 옵셔널 대신에 필요한 오브젝트를 반환한다. 이것은 번들로부터 nib을 인스턴스화하는 것과 같은 것들에 적용됐다.

Swift3.0

(인지할만한)주요 변화들
private의 정의는 fileprivate로 바뀌었다.

공식적으로 private라 불리던 것이 이제 fileprivate로 바뀌었다. fileprivate 변수는 extension으로 접근할 수 있다. private 변수는 클래스에서 extension으로 접근할 수 없다.


public의 정의는 open으로 바뀌었다.
현재 클래스나 파라미터 public 정의는 두가지 기능을 제공한다.
  1. 외부모듈이 클래스나 맴버를 사용할 수 있다.
  2. 외부모듈이 클래스나 맴버를 오버라이드 할 수 있다.

Swift3에서의 public은 외부적으로 사용가능함이지, 오버라이드 가능함은 아니다.
이전의 이 기능은 open으로 되었다.


동사와 명사
함수 이름이 -d(과거형)으로 끝나는 것들은 그 오브젝트의 새 인스턴스를 반환한다.
이러한 변화는 reverse와 reversed, enumerate와 enumerated 등에 적용한다.

Objective-C의 불리언은 이제 is라는 단어로 시작되고 Foundation 타입은 더이상 접두에 NS를 사용하지 않는다(NSString처럼 String과 충돌이 일어나는 경우들은 빼고 말이다.).

Foundation 타입은 Swift의 let과 var 선언에서 아지 잘 동작할 것이다.



맴버로 불러오기
설명이 달린 C 함수들을 메소드로 부를 수 있다. 임포터(importer)는 보통 이러한 맵핑을 자동으로 추론할 수 있아며 Swift에 예전의 C-API를 자연스럽고 네이티브하게 사용할 수 있게 해준다. Case and point, CoreGraphics API. 참고로 CoreGraphics는 이번 배포때 공식적으로 업데이트 되지 않았다.


케멀케이스(CamelCase) 변화들
열거형이나 프로퍼티에서 머리문자(CG, NS등)로 시작하는 것들이 UpperCamelCase에서 LowerCamelCase로 대체되었다. 
// Before
let red = UIColor.redColor().CGColor
// After
let red = UIColor.red.cgColor

(개인적으로 가장 작은 변화라 생각되는)상태절 변화

이제부터는 guard, if, while절에서 where 키워드를 사용할 수 없다. where 키워드는 for-in-where 형태의 for문에서는 사용할 수있다.
case 절에서도 마찬가지로 바뀌었다.


첫번째 인자의 문자 일관성
이렇게 이해하면 쉽다: 첫번째 파라미터의 이름은 디폴트로 필요하다.


(내가 좋아하는 변화인)묵시적으로 언랩핑된 옵셔널 다루기
여러분이 여러 언어(shared-language)로 프로젝트를 할 때 이 점 덕분에 마이그레이션 작업이 의미있어진다고 생각이 든다. 그것이 무엇일까?

이전에 ImplicitlyUnwrappedType! 프로퍼티를 가진 Objective-C 타입이 이제 WrappedType?로 되었다. 아래의 경우를 제외하고 모두 적용된다.

더 나은 Objective-C API 변환
네이밍이 더 명확해졌다.

최신식 디스패치

컬랙션 타입이 새로운 모델을 가지게 됨
이전에 컬랙션 타입에서 한 인덱스에서 시작하여 탐색할 때는 index의 successor 메소드를 사용해야 했다. 이제는 이 책임이 컬랙션으로 넘어가게 되었다. c.index(after:index) 이런식으로 작성한다. 컬랙션은 이제 어떤 comparable 타입의 인덱스를 가진다.
(아래 부분은 Range 오브젝트를 손수 만들때 적용된다. 보통 이렇게 할 일은 드물거라 생각된다.)

이런 변화에서 사이드 이팩트로서 Range가 여러 타입으로 쪼개어졌다(Range, ClosedRange, CountableRange, CountableClosedRange) ClosedRanged는 이제 그 타입(0...Int8.max)의 최대값 범위를 포함한다. Range와 ClosedRange는 더이상 반복(iterate)을 할 수 없다. 그 의무로서 오직 Comparable 오브젝트만 필요하다. 따라서 Range<String>을 만들 수 있다.

Objective-C의 id는 Swift의 Any 타입으로 불러와진다.
애플의 말을 인용하자면
이제부터 id는 'AnyObject'가 아닌 'Any'로 불러오기 때문에, 이전에 여러분이 'AnyObject'로 동적인 검색을 수행하는 곳에서 애러가 뜰 수도 있다.

(인식하지 못할지도 모르는)작은 변화들
옵셔널 비교연산자가 제거됨
현재 nil 타입은 비교가능하다.

이 점은 아래와같이 버그를 만들기 쉽다.

이제는 이것들을 비교하기 전에 반드시 언랩핑을 해주어야한다.
이것이 굉장히 좋은 점 중 하나이기도 하지만 유닛테스트를 망가뜨릴지도 모른다.

클러저 파라미터 이름과 레이블
수많은 클로저 파라미터 이름이 재정의되고 선택적으로 바뀌었다.

flatten이 join으로 명칭이 바뀌었다.

UnsafePointer<T> 다루기
오브젝트가 아닌 것들의 포인터 타입의 nullability는 옵셔널을 사용해 표현할 수 있다.

부동 소숫점 값을 반올림하는 기능은 이제 그 값이 가지고 있다.
이전에는 부동 소숫점을 반올림하기 위해 전역의 C함수(float나 ceil)을 사용할 수 있었다. 아직 이 함수들도 사용가능하나 디프리케이트 될지 고려되고 있다.

대신 아래와 같이 사용할 수 있다.
추가적인 반올림 기준이다.
  1. toNearestOrAwayFromZero
  2. toNearestOrEven
  3. towardZero
  4. awayFromZero

제네릭 타입 에일리어스

연산자 정의 문법의 변화

Objective-C의 상수는 이제 Swift 타입이다.
이제 Objective-C inter-op 식의 문자열을 사용할 수 없다.

굳이 걱정하지 않을 정도로 작은 것
NSError의 Bridging이 강화되었다.

nulTerminatedUTF8CString이 utf8CString으로 이름이 바뀜

문자열의 UnicodeScalar 생성자 중복을 제거

실패할 수 있는 UnicodeScalar 생성자는 이제 옵셔널을 반환한다.

더이상 튜플 splatting이 안된다.

더이상 curry한 func 선언 문법이 안된다.

이 모든 변화가 Swift3에서 일나날까?
아니다.

아직 이번 배포를 위해 검토하는 계획 안이다.(옮긴이: Swift3은 16.9.13에 정식 배포가 되었는데, 이 글은 16.8.31에 쓰여졌다)  몇 계획은 Swift3.x에서 달라질 것이고, 우리가 일반적으로 사용하지 않는 API 호출의 제거/병합과 같은 아주 마이너한 변화들은 포함시키지 않았다. 

추가로 나는 이 개별적인 변화를 깊게 볼 필요가 없었으며, 대신 각 변화에대한 높은 수준에서 개괄적인 설명을 하였다. 몇 변화는 그 영역에서 파급표과를 가지고 있으며, 여러분이 이러한 변화에 관심이 있으면 Swift EVO project를 한번 방문해 보아라.

이번 배포에서 검토 진행/대기중인 변화들
  1. Sequence-기반 생성자를 추가하고 Dictionary에 메소드를 합친다.
  2. 약 참조(weak reference)에서 강 참조(strong reference)로 self를 업그레이드하여 옵셔널 바인딩을 사용할 수 있게 해준다.
  3. 표준 라이브러리에 AnyHashable을 추가한다.

3.x 배포에서 바뀐것들
  1. 순환 알고리즘
  2. .self 제거
  3. 커스텀 Objective-C 표현을 제공하는 것을 Swift 타입에서 허용
  4. 동적 케스터로부터 연결 변환 동작을 제거
  5. Sequence end-operation 이름을 합리화

보너스
제거되어서 우리 모두가 기쁜 것들

  1. Swift 언어에서 where문 제거
  2. 인스턴스 맴버를 접근하기 위해 self가 필요함
  3. extension에서 접근 변경자 제거



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

,

Swift는 Objective-C와 같은 벤더를 가지고 있기 때문에 Objective-C의 후계자이다. 이것은 비슷하게 생기지도 않고 똑같이 동작하지도 않으며 비슷한 느낌이 들지 않는다. Objective-C에서 (메소드 이름처럼) 잘 동작했던 패러다임은 새로운 Swift 세계로 넘어오기 위해 천천히 바뀌고 있다. 예를들어 아래 메소드는 Objective-C에서 이런식의 긴 형태로 호출되었다.
[string dataUsingEncoding:NSUTF8StringEncoding];

Swift2.2에서는 다소 어색하게 바뀌었고

string.dataUsingEncoding(NSUTF8StringEncoding)

Swift3에서 이 메소드는 비로소 간결해졌다.

string.data(using: .utf8)

Swift3 버전의 메소드가 Swift에게 알맞는 것이고, 같은 의미에서 Objective-C 버전은 Objective-C 버전에 알맞는 것이다. 여러분의 메소드를 어떻게 Swift에 맞춰 사용할 수 있는지 다루는데 이 글이 도움을 줄 것이다.


우리 앱을 만들기 위해 사용하는 것들 중에 Swift하게 바꿔야하는 프레임워크나 언어적인 부분들이 있다. 오늘 나는 델리게이트 네이밍에대해 이야기하려 한다.

Swift에서 델리게이트는 Obejctive-C에서처럼 부드럽게 잘 바뀌지 않는다. Objective-C는 "sender"와 "receiver"에대해 굉장히 친숙하다. 애플의 Objective-C 문서에서는 이 용어를 자주 사용한다. 예를들어 UIResponer에있는 isFirstResponder 메소드 문서를 확인해 보아라.

receiver가 첫 응답자(first responder)인지 나타내는 불리언 값을 반환한다.
- (void)buttonTapped:(UIButton *)sender { }
델리게이트는 비슷한 방식으로 동작한다:: Objective-C에서 델리게이트 메소드의 첫 인자도 항상 sender였다. 이것이 왜 유용할까? 만약 receiver가 같은 타임의 여러 오브젝트 델리게이트라면 그것들을 구분해야할 것이다. 델리게이트는 첫번째 인자를 제공하면서 그것을 구분할 수 있게 해준다.

나는 Backchannel SDK 예제로 몇 클래스 이름을 간단히 해볼 것이다.

여기엔 두가지 타입의 델리게이트 메소드가 있는데, 첫번째는 이벤트가 일어났는지 나타낸다.
- (void)messageFormDidTapCancel:(BAKMessageForm *)messageForm;
Swift로 번역하면 다음과 같다.
func messageFormDidTapCancel(_ messageForm: BAKMessageForm)
이것은 더이상 Swift3에 알맞지 않다. Swift3에서는 불필요한 것(두번 나타난 messageForm)을 제거하고, 언더바를 사용해서 없에는게 아니라 자동으로 첫번째 인자 이름을 따른다.

두번째 타입의 델리게이트 메소드는 이벤트가 일어나고 거기에 몇 데이터를 가지고 있는지 나타낸다. 아래 예제를 보자.
- (void)messageForm:(BAKMessageForm *)messageForm didTapPostWithDraft:(BAKDraft *)draft;
- (void)messageForm:(BAKMessageForm *)messageForm didTapAttachment:(BAKAttachment *)attachment;
Swift3으로 번역하면 다음과 같다.
func messageForm(_ messageForm: BAKMessageForm, didTapPostWithDraft draft: BAKDraft)
func messageForm(_ messageForm: BAKMessageForm, didTapAttachment attachment: BAKAttachment)
흠, 좋지 않아 보인다. 왜 이 메소드를 둘다 messageForm이라 부르나? 또한 명사로 시작하는 메소드는 별로 좋지 않다. 보통 그 타입의 오브젝트를 반환할때 사용한다(NSStringdata(using:)를 생각해보면 Data를 반환한다). 여기서는 아무 메시지 형식 오브젝트도 반환하지 않을 것이다. 그 "메시지 형식"은 사실 첫번째 파라미터의 이름이다. 매우 혼란스러운 메소드 이름이 아닐 수 없다!

이 두가지 타입의 델리게이트 메소드는 그 줄 마지막에 "sender"를 보내고 동사를 앞으로 옮겨서 고칠 수 있다. 먼저 sender가 델리게이트를 알려주는 이벤트는 messageFormDidCancel대신 didTapCancel이다.
func didTapCancel(messageForm: BAKMessageForm)
굉장히 좋아졌다. 액션이 앞으로 오면서 메소드 이름이 되었다. 그리하여 메소드가 무슨 일을 하는지 더 명확해졌다. 내 생각엔 읽을때 좀 더 좋게 하기 위해서 파라미터 이름 대신에 전치사를 써도 괜찮을 것 같다.
func didTapCancel(on messageForm: BAKMessageForm)
이렇게 써보면서 아직까지는 전치사를 사용하기에 어색해보이는 상황을 발견하지 못했다. 그리고 다른 여러 상황에서도 "on", "for", "with", "in" 모두 유용하게 쓰인다는 것도 알아냈다. 사용자가 "on"형식을 탭 하면서 나는 "on"이 이곳에 적합하다고 생각한다.

이제 뒤에 데이터를 전달하는 델리게이트 메소드도 한번 보자. 동사를 앞으로 보내는게 도움이 될 것이고, 델리게이트 이름에 전치사로 바꾸는 것도 이런 메소드 타입을 깔끔하게 해준다. 아래 예제 대신
func messageForm(_ messageForm: BAKMessageForm, didTapPostWithDraft draft: BAKDraft)
좀 더 Swift하게 만들고
func didTapPost(with draft: BAKDraft, on messageForm: BAKMessageForm)
좀 더 바꾼다.
func didTap(attachment: BAKAttachment, on messageForm: BAKMessageForm)

이러한 규칙은 나를 빼고 누구도 보증하진 않지만 우리가 그런식으로 쓰면 현재 규칙보다 더 이해하기 쉬워질거라 생각된다. 더 나아가 아마 내 Swift 델리게이트 메소드를 이런 구조로 짜기 시작할 것이다.

UITableView의 델리게이트와 데이터소스 메소드를 보면서 이것이 나중엔 어떻게 생겼을지 생각해보자.
func numberOfSections(in tableView: UITableView) -> Int
numberOfSections는 이 스킴을 따르고 이미 꽤 좋아보인다.

그러나 아래 메소드는 그렇게 좋아 보이진 않는다.
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath)
여기 좀 더 사랑스러운 방법이다.
func numberOfRows(inSection section: Int, in tableView: UITableView) -> Int
func cellForRow(at indexPath: IndexPath, in tableView: UITableView) -> UITableViewCell
func didSelectRow(at indexPath: IndexPath, in tableView: UITableView)

 



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

,