'마이그레이션'에 해당하는 글 2건

제목: What's New in Swift 4

주의: 이 튜토리얼은 Xcode9 beta 1에 들어있는 스위프트 4버전을 사용함.

스위프트4는 애플이 2017년 가을에 낼 가장 최신의 메이저 배포이다. 그 메인 초점은 스위프트3 코드와 소스 호환성 제공이며, ABI 안정성에 맞춰 작업하고 있다. 이 글에서는 스위프트가 바뀌면서 여러분의 코드에 중요하게 영향을 줄 것을 짚어 줄 것이다. 그리고 이제 시작해보자!


Swift 4Swift 4

시작하며
스위프트4는 Xcode9에 포함되있다. 여러분은 애플의 developer portal에서 최신버전의 Xcode9를 다운받을 수 있다(반드시 활성화된 개발자 계정이 있어야한다). 각 Xcode beta는 배포될 시점의 가장 최신의 스위프트4를 탑재할 것이다.

읽어내려가다보면 [SE-xxxx] 양식의 링크를 볼 수 있을 것이다. 이 링크들은 관련된 스위프트 에볼루션 프로포절로 연결해준다. 어떤 주제든 더 배우고 싶으면 이 링크를 확인해보자.

나는 여러분이 스위프트4의 각 기능을 시도해보길 추천하며 플레이그라운드에서 돌려보길 바란다. 그리하여 지식을 여러분 머릿속에 확립시킬 수 있고 각 주제마다 더 깊게 들어가볼 수 있게 해줄 것이다. 각 예제를 고쳐보고 추가해보면서 가지고 놀아보자. 잘 즐겨보길 바란다!

노트: 이 글은 각 Xcode beta에대해 갱신될 것이다. 여러분이 다른 스위프트 스넵샷을 사용하면 여기 코드가 동작할것이라는 보장은 할 수 없다.

스위프트4로 마이그레이션하기
스위프트3에서 4로 마이그레이션하는 것은 2.2에서 3으로 가는 것보다 덜 힘들 것이다. 보통 많은 변화가 추가되고 개별적으로 건드릴 필요가 없다. 그덕에 스위프트 마이그레이션 툴은 주요 변화만 처리해 줄 것이다.

Xcode9는 동시에 스위프트4와 스위프트3.2안에 스위프트3도 가능하게 지원한다. 여러분 프로젝트에서 각 타겟은 스위프트3.2나 스위프트4가 될 수 있고, 필요에따라 조각조각 마이그레이션 할 수 있다. 그래도 스위프트3.2로 변환하는 것은 완전히 자유롭진 않다. 새로운 SDK에 맞추기위해 여러분의 코드 부분을 업데이트 해야할 것이다. 그리고 스위프트는 아직 ABI 안정이 되지 않았기 때문에 Xcode9로 여러분의 의존성들을 다시 컴파일 해야할 것이다.

스위프트4로 마이그레이션할 준비가 되었다면 Xcode는 다시한번 마이그레이션 툴로 여러분을 도와줄 것이다. Xcode에서 Edit/Convert/To Current Swift Syntax...로 가서 변환 툴을 실행시킨다.

변환하고 싶은 타겟을 선택한 후, Xcode는 Objective-C 추론에대한 설정을 물어볼 것이다. 추론을 제한하여 바이너리 크기를 줄이는 추천 옵션을 선택하자(이 토픽에대해 더 알고 싶으면 아래에 Limiting @objc Inference를 확인해보자).

여러분의 코드가 의도한대로 바뀌었는지 잘 이해하기위해 우리는 제일 먼저 스위프트4의 API 변경사항들을 다룰 것이다.

API 변경사항
스위프트4에서 소개된 추가사항에 들어기보기 전에, 기존의 API를 변경하거나 증진시킨것이 무엇이 있는지 먼저 살펴보자.

문자열(String)
스위프트4에서 String은 상당히 많은 사랑을 받고 있다. 이 프로포절은 많은 변경사항을 가지며, 큰것부터 하나씩 보자. [SE-0163]

여러분은 이 부분에서 옛날 기분이 들것이다. 스위프트가 2.0버전으로 돌아와서 다시 컬랙션이 되었다. String에서 characters 배열이 필요하다는 점을 없앴다. 이제 String 오브젝트를 직접 이터레이트(iterate) 할 수 있다.
let galaxy = "Milky Way 🐮"
for char in galaxy {
  print(char)
}

YesYes


String으로 논리적인 이터레이트만 가능한게 아니라, SequenceCollection의 모든 기능을 사용할 수 있다.
galaxy.count       // 11

galaxy.isEmpty     // false

galaxy.dropFirst() // "ilky Way 🐮"

String(galaxy.reversed()) // "🐮 yaW ykliM"


// Filter out any none ASCII characters
galaxy.filter { char in
  let isASCII = char.unicodeScalars.reduce(true, { $0 && $1.isASCII })
  return isASCII
} // "Milky Way "
위의 ASCII 예제는 Character의 작은 개선사항을 설명해준다. 이제 Character에서 직접 UnicodeScalarView에 접근할 수 있다. 이전에는 새 String을 만들어야 했었다 [SE-0178].

다른 추가사항은 StringProtocol이다. 이전에 String에 정의되있던 많은 기능들이 이 프로토콜에 정의되있다. 이렇게 변경하게된 이유는 자르기(slice) 작업을 개선하기 위함이다. 스위프트4는 String에서 서브시퀀스를 참조하기위해 Substring 타입을 추가했다.

StringSubstring은 둘 다 StringProtocol에 주어진 기능을 대부분 비슷하게 구현하였다.
// Grab a subsequence of String
let endIndex = galaxy.index(galaxy.startIndex, offsetBy: 3)
var milkSubstring = galaxy[galaxy.startIndex...endIndex]   // "Milk"

type(of: milkSubstring)   // Substring.Type


// Concatenate a String onto a Substring
milkSubstring += "🥛"     // "Milk🥛"


// Create a String from a Substring
let milkString = String(milkSubstring) // "Milk🥛"
또다른 멋진 개선사항은 String이 grapheme cluster를 해석하는 방식이다. 이 해결방안은 유니코드9 각색한 것으로부터 나왔다. 이전에는 유니코드 문자들이 여러 코드 포인트로 만들어져있었는데, 이때문에 count는 1로 합산되었다. 피부톤을 선택하고 이모티콘을 넣으면 이런 일이 발생했다. 아래에 몇가지 예제가 있는데, 예전의 동작방식과 이후의 동작방식을 보여준다.
"👩💻".count // Now: 1, Before: 2

"👍🏽".count // Now: 1, Before: 2

"👨❤️💋👨".count // Now: 1, Before, 4
이것은 String Manifesto에 언급된 변화들중 일부분이다. 나중에는 여러분이 예상했던대로 이것에대한 모든 동기와 제안된 솔루션에대해 읽을 수 있다.

딕서녀리와 셋(Dictionary and Set)
Collection 타입이 가버린다면(As far as Collection types go), SetDictionary은 항상 직관적이지 않았다. 운좋게도 스위프트 팀은 여기에 애정을 가져주었다 [SE-0165].

생성자 기반의 시퀀스
그것들 중 첫번째는 키-값 쌍(튜플)의 시퀀스에서 딕셔너리를 만들 수 있게 해주었다는 점이다.
let nearestStarNames = ["Proxima Centauri", "Alpha Centauri A", "Alpha Centauri B", "Barnard's Star", "Wolf 359"]
let nearestStarDistances = [4.24, 4.37, 4.37, 5.96, 7.78]

// Dictionary from sequence of keys-values
let starDistanceDict = Dictionary(uniqueKeysWithValues: zip(nearestStarNames, nearestStarDistances))
// ["Wolf 359": 7.78, "Alpha Centauri B": 4.37, "Proxima Centauri": 4.24, "Alpha Centauri A": 4.37, "Barnard's Star": 5.96]

중복 키 해결법
이제 여러분이 원하는 방식으로 중복키로 딕셔너리를 초기화할 수 있게 되었다. 이렇게하여 이 문제에대해 별 말 없이도 키-값 쌍을 덮어쓰는 것을 막을 수 있다.
// Random vote of people's favorite stars
let favoriteStarVotes = ["Alpha Centauri A", "Wolf 359", "Alpha Centauri A", "Barnard's Star"]

// Merging keys with closure for conflicts
let mergedKeysAndValues = Dictionary(zip(favoriteStarVotes, repeatElement(1, count: favoriteStarVotes.count)), uniquingKeysWith: +) // ["Barnard's Star": 1, "Alpha Centauri A": 2, "Wolf 359": 1]
위 코드에서는 충돌하는 두 값을 더하여 중복 키 문제를 해결하기위해 축약된 +와 함께 zip을 사용했다.

노트: zip이 생소하다면 애플의 Swift Documentation를 빠르게 읽어보자.

필터링
이제 DictionarySet 모두 필터하였을때 원래 타입의 새 오브젝트를 결과로 할 수 있게 되었다.
// Filtering results into dictionary rather than array of tuples
let closeStars = starDistanceDict.filter { $0.value < 5.0 }
closeStars // Dictionary: ["Proxima Centauri": 4.24, "Alpha Centauri A": 4.37, "Alpha Centauri B": 4.37]

딕셔너리 맵핑
Dictionary는 그 값을 바로 맵핑할 수 있는 유용한 메소드를 얻어냈다.
// Mapping values directly resulting in a dictionary
let mappedCloseStars = closeStars.mapValues { "\($0)" }
mappedCloseStars // ["Proxima Centauri": "4.24", "Alpha Centauri A": "4.37", "Alpha Centauri B": "4.37"]

딕셔너리의 디폴트 값
일반적으로 Dictionary의 값에 접근할 때, 값이 nil인 경우 디폴트 값을 지정하기위해 nil-coalescing 연산자를 사용한다. 스위프트4에서는 그 줄에서 변경하여 더 깔끔하고 멋진 방법으로 되었다.
// Subscript with a default value
let siriusDistance = mappedCloseStars["Wolf 359", default: "unknown"] // "unknown"


// Subscript with a default value used for mutating
var starWordsCount: [String: Int] = [:]
for starName in nearestStarNames {
  let numWords = starName.split(separator: " ").count
  starWordsCount[starName, default: 0] += numWords // Amazing

}
starWordsCount // ["Wolf 359": 2, "Alpha Centauri B": 3, "Proxima Centauri": 2, "Alpha Centauri A": 3, "Barnard's Star": 2]
이전에 이 타입을 변경하려면 커다란 if let문으로 감싸야 했다. 스위프트4는 한줄에 된다!

딕서녀리 그룹핑
또다른 멋진 추가사항은 Dictionary를 Sequence로부터 초기화할 수 있는 기능과 대괄호로 그룹화할 수 있는 기능이다.
// Grouping sequences by computed key
let starsByFirstLetter = Dictionary(grouping: nearestStarNames) { $0.first! }

// ["B": ["Barnard's Star"], "A": ["Alpha Centauri A", "Alpha Centauri B"], "W": ["Wolf 359"], "P": ["Proxima Centauri"]]
이렇게하면 데이터를 특정 패턴으로 그룹화할때 편하게 다가올 것이다.

용량 잡아놓기(Reserving Capacity)
SequenceDictionary 둘 다 이제 명시적으로 용량을 정해놓을 수 있다.
// Improved Set/Dictionary capacity reservation
starWordsCount.capacity  // 6

starWordsCount.reserveCapacity(20) // reserves at _least_ 20 elements of capacity

starWordsCount.capacity // 24
이 타입에서 재할당은 부담되는 작업일 수 있다. 저장되는데 필요한 데이터가 얼마인지 알때 reserveCapacity(_:)을 사용하여 퍼포먼스를 쉽게 개선할 수 있다.

이것은 엄청난 양의 정보이니, 그 타입들에대해 명확하게 확인해보고 추가적으로 여러분의 코드에 사용할 방법이 있는지 찾아보자.

private 접근 수정자
스위프트3애서 그렇게 반갑지 않던 부분은 fileprivate 였을 것이다. 이론적으로는 좋지만, 실제로는 사용하기 혼란스러울 수 있었다. private의 목표는 맴버 자신에서만 사용하는 것이었는데, fileprivate은 같은 파일안에 맴버들사이에서 접근을 공유하려는 경우에는 거의 사용하지 않는다.

이 이슈는 스위프트가 익스텐션을 사용하여 논리적인 그룹으로 코드를 쪼개도록 지향하기 때문에 생긴다. 익스텐션은 원래 맴버가 선언된 범위 밖에서 사용할때가 많다. 그렇기때문에 fileprivate에대한 확장이 필요하게 되었다.

스위프트4는 한 타입과 다른 익스텐션 사이에 같은 접근 제어 범위를 공유하여 원래 의도를 이해한다. 이것은 오직 같은 소스파일 안에서만 잡아둔다 [SE-0169].

struct SpaceCraft {
  private let warpCode: String

  init(warpCode: String) {
   self.warpCode = warpCode
  }
}

extension SpaceCraft {
  func goToWarpSpeed(warpCode: String) {
   if warpCode == self.warpCode { // Error in Swift 3 unless warpCode is fileprivate

     print("Do it Scotty!")
   }
  }
}

let enterprise = SpaceCraft(warpCode: "KirkIsCool")
//enterprise.warpCode  // error: 'warpCode' is inaccessible due to 'private' protection level
enterprise.goToWarpSpeed(warpCode: "KirkIsCool") // "Do it Scotty!"
그리하여 fileprivate는 코드 구조를 위한 임시목적이라기보단 의도한 목적에 맞게 사용할 수 있다.

API 추가사항들
이제 스위프트4의 반짝반짝 빛나는 새 기능을 살펴보자. 이 변경사항들은 현재 코드를 망가뜨리진 않고 간단하게 추가할 수 있다.

아키브와 시리얼라이즈


지금까지 스위프트는, 커스텀타입을 시리얼라이즈와 아키브하기위해서 여러노력을 했었어야 했다. class 타입의 경우 NSObject를 상속하고 NSCoding 프로토콜을 구형해야했다.

structenum같은 값 타입은 꼼수처럼 NSObjectNSCoding을 확장한 보조 오브젝트를 만들어야 했다.

스위프트4는 세가지 타입 모두 시리얼라이즈 할 수 있게하여 문제를 해결해 주었다 [SE-0166].

struct CuriosityLog: Codable {
  enum Discovery: String, Codable {
   case rock, water, martian
  }

  var sol: Int
  var discoveries: [Discovery]
}

// Create a log entry for Mars sol 42
let logSol42 = CuriosityLog(sol: 42, discoveries: [.rock, .rock, .rock, .rock])
이 예제에서 볼 수 있듯, EncodableDecodable을 위해 Codable 프로토콜만 구현하면 된다. 모든 프로퍼티가 Codable이면 그 프로토콜은 컴파일러예의햬 자동으로 생성된다.

실제로 오브젝트를 인코드하기 위해, 이것을 인코더에 보내야 할 것이다. 스위프트 인코더는 스위프트4에서 활발하게 구현되고있다. 각각의 다른 스킴에따라 오브젝트를 인코딩할 수 있다 [SE-0167]. (주의: 이 제안중 일부는 아직 개발중에 있다)

let jsonEncoder = JSONEncoder() // One currently available encoder


// Encode the data
let jsonData = try jsonEncoder.encode(logSol42)
// Create a String from the data
let jsonString = String(data: jsonData, encoding: .utf8) // "{"sol":42,"discoveries":["rock","rock","rock","rock"]}"
한 오브젝트를 받아서 자동으로 JSON 오브젝트로 인코딩 시킨다. 속성들을 확인하기위해 JSONEncoder를 커스터마이징된 아웃풋으로 만든다.

이 프로세스의 마지막 과정은 데이터를 구체적인 오브젝트로 디코딩하는 것이다.
let jsonDecoder = JSONDecoder() // Pair decoder to JSONEncoder


// Attempt to decode the data to a CuriosityLog object
let decodedLog = try jsonDecoder.decode(CuriosityLog.self, from: jsonData)
decodedLog.sol         // 42

decodedLog.discoveries // [rock, rock, rock, rock]
스위프트4에서의 인코딩과 디코딩은 @objc 프로토콜의 오버헤드나 한계에 의존하지 않고서 스위프트로부터 기대되는 타입 세이프티를 취할 수 있다.

키-값 코딩
지금까지 스위프트에서 함수는 클로저이기 때문에 함수를 호출하지 않은채 참조하여 붙잡아둘 수 있었다. 그렇지만 프로퍼티가 데이터를 잡아두어 실제로 접근하고 있지 않는한 함수를 프로퍼티에 참조하여 잡아둘 수 없었다.

스위프트4에서 아주 흥미로운 추가사항은 인스턴스의 값에서도 get/set으로 타입의 키패스를 참조할 수 있다는 점이다.
struct Lightsaber {
  enum Color {
   case blue, green, red
  }
  let color: Color
}

class ForceUser {
  var name: String
  var lightsaber: Lightsaber
  var master: ForceUser?

  init(name: String, lightsaber: Lightsaber, master: ForceUser? = nil) {
   self.name = name
   self.lightsaber = lightsaber
   self.master = master
  }
}

let sidious = ForceUser(name: "Darth Sidious", lightsaber: Lightsaber(color: .red))
let obiwan = ForceUser(name: "Obi-Wan Kenobi", lightsaber: Lightsaber(color: .blue))
let anakin = ForceUser(name: "Anakin Skywalker", lightsaber: Lightsaber(color: .blue), master: obiwan)
이것은 몇 ForceUser 인스턴스를 그 name, lightsaber, master로 설정하여 생성하는 코드이다. 키패스를 만들기위해서는 필요한 프로퍼티 앞에 백슬래시(\)만 넣어주면 된다.
// Create reference to the ForceUser.name key path
let nameKeyPath = \ForceUser.name

// Access the value from key path on instance
let obiwanName = obiwan[keyPath: nameKeyPath]  // "Obi-Wan Kenobi"
이 예제에서는 ForceUsername 프로퍼티 키패스를 생성했다. 그리고 keyPath라는 새로운 서브스크립트에 이 키패스를 담아서 사용한다. 이제 디폴트에의해 이 서브스크립트는 모든 타입에서 사용할 수 있다.

아래에는 하위 오브젝트 예제, 프로퍼티 설정, 키패스 참조 만들기에대한 여러가지 키패스 사용법 예시가 있다.
// Use keypath directly inline and to drill down to sub objects
let anakinSaberColor = anakin[keyPath: \ForceUser.lightsaber.color]  // blue


// Access a property on the object returned by key path
let masterKeyPath = \ForceUser.master
let anakinMasterName = anakin[keyPath: masterKeyPath]?.name  // "Obi-Wan Kenobi"


// Change Anakin to the dark side using key path as a setter
anakin[keyPath: masterKeyPath] = sidiousan
akin.master?.name // Darth Sidious


// Note: not currently working, but works in some situations
// Append a key path to an existing path
//let masterNameKeyPath = masterKeyPath.appending(path: \ForceUser.name)
//anakin[keyPath: masterKeyPath] // "Darth Sidious"
스위프트의 키패스에서 장점은 강타입이라는 점이다! 더이상 Objective-C의 문자열 스타일로 지저분할 필요가 없다!

다열 문자열 리터럴(Multi-line String Literals)
많은 프로그래밍 언어에서는 일반적인 기능인 다열 문자열 리터럴이다. 스위프트4에서 이것을 추가했으며 세개의 쌍따옴표로 텍스트를 감싸는 형태의 문법이다 [SE-0168].

let star = "⭐️"let introString = """
  A long time ago in a galaxy far,
  far away....

  You could write multi-lined strings
  without "escaping" single quotes.

  The indentation of the closing quotes
      below deside where the text line
  begins.

  You can even dynamically add values
  from properties: \(star)
  """

print(introString) // prints the string exactly as written above with the value of star
XML/JSON을 만들떄나 UI에 보여줄 긴 양식의 텍스트를 만들때 매우 유용하다.

한쪽만의 범위(One-Sided Ranges)
장황한 표현을 줄이고 가독성을 개선하기위해 표준 라이브러리는 한쪽만 표현된 범위로 시작과 끝을 추론할 수 있다 [SE-0172].

한 인덱스에서 시작이나 끝 인덱스까지 범위의 컬랙션을 만들어보면 유용하다는 점을 바로 인지할 수 있다.
// Collection Subscript
var planets = ["Mercury", "Venus", "Earth", "Mars", "Jupiter", "Saturn", "Uranus", "Neptune"]
let outsideAsteroidBelt = planets[4...] // Before: planets[4..<planets.endIndex]

let firstThree = planets[..<4]          // Before: planets[planets.startIndex..<4]
보이는바와 같이, 한쪽만의 범위는 명시적으로 시작이나 끝 인덱스를 지정해줘야하는 필요를 줄여준다.

무한 시퀀스
시작이나 끝 인덱스가 셀 수 없는 타입일때 무한 Sequence를 정의할 도 있다.
// Infinite range: 1...infinity
var numberedPlanets = Array(zip(1..., planets))
print(numberedPlanets) // [(1, "Mercury"), (2, "Venus"), ..., (8, "Neptune")]

planets.append("Pluto")
numberedPlanets = Array(zip(1..., planets))
print(numberedPlanets) // [(1, "Mercury"), (2, "Venus"), ..., (9, "Pluto")]

패턴 매칭
한쪽만의 범위를 사용할 때 또다른 좋은 사용 방법은 패턴 매칭이다.
// Pattern matching

func temperature(planetNumber: Int) {
  switch planetNumber {
  case ...2: // anything less than or equal to 2

   print("Too hot")
  case 4...: // anything greater than or equal to 4

   print("Too cold")
  default:
   print("Justtttt right")
 }
}

temperature(planetNumber: 3) // Earth

제네릭 서브스크립트
서브스크립트는 데이터 타입의 접근성을 직관적이게 만드는데 중요한 역할을 한다. 이런 유용함을 증진하기 위해 서브스크립트는 이제 제네릭으로 만들 수 있다 [SE-0148].

struct GenericDictionary<Key: Hashable, Value> {
  private var data: [Key: Value]

  init(data: [Key: Value]) {
   self.data = data
  }

  subscript<T>(key: Key) -> T? {
   return data[key] as? T
  }
}
이 예제에서는 리턴 타입이 제네릭이다. 그리하여 아래처럼 제네릭 서브스크립트를 사용할 수도 있다.
// Dictionary of type: [String: Any]
var earthData = GenericDictionary(data: ["name": "Earth", "population": 7500000000, "moons": 1])

// Automatically infers return type without "as? String"
let name: String? = earthData["name"]

// Automatically infers return type without "as? Int"
let population: Int? = earthData["population"]
리턴 타입을 제네릭으로 하는것 뿐만 아니라 실제 서브스크립트 타입을 제네릭으로 할 수도 있다.
extension GenericDictionary {
  subscript<Keys: Sequence>(keys: Keys) -> [Value] where Keys.Iterator.Element == Key {
   var values: [Value] = []
   for key in keys {
     if let value = data[key] {
       values.append(value)
     }
   }
   return values
  }
}

// Array subscript value
let nameAndMoons = earthData[["moons", "name"]]        // [1, "Earth"]

// Set subscript value
let nameAndMoons2 = earthData[Set(["moons", "name"])]  // [1, "Earth"]
이 예제에서는 두가지 다른 Sequence 타입(ArraySet)을 넣어 각각 값의 배열을 내뱉는다.

잡다한 것
여기까지는 스위프트4에서 큼직한 변화들을 다뤄봤다. 이제는 비교적 작은 부분들을 빠르게 볼 것이다.

MutableCollection.swapAt(_: _:)
MutableCollection은 이제 변경가능한 swapAt(_:_:) 메소드를 가진다. 이 메소드는 이름대로 동작하는데, 주어진 인덱스에 값을 스왑한다 [SE-0173].

// Very basic bubble sort with an in-place swap
func bubbleSort<T: Comparable>(_ array: [T]) -> [T] {
  var sortedArray = array
  for i in 0..<sortedArray.count - 1 {
   for j in 1..<sortedArray.count {
     if sortedArray[j-1] > sortedArray[j] {
       sortedArray.swapAt(j-1, j) // New MutableCollection method

     }
   }
  }
  return sortedArray
}

bubbleSort([4, 3, 2, 1, 0]) // [0, 1, 2, 3, 4]

연관 타입 제약(Associated Type Constraints)
이제 where을 사용하여 연관타입에 제약을 둘 수 있다 [SE-0142].

protocol MyProtocol {
  associatedtype Element
  associatedtype SubSequence : Sequence where SubSequence.Iterator.Element == Iterator.Element
}
프로토콜 제약을 사용하여 많은 associatedtype 선언이 그 값을 제약할 수 있다.

클래스와 프로토콜의 존재
결국 Objective-C에서 스위프트로 만들어진 이 기능은 한 타입이 클래스를 따를 수 있을 뿐만 아니라 여러 프로토콜도 따르면서 정의할 수 있는 기능이다 [SE-0156].

protocol MyProtocol { }
class View { }
class ViewSubclass: View, MyProtocol { }

class MyClass {
  var delegate: (View & MyProtocol)?
}

let myClass = MyClass()
//myClass.delegate = View() // error: cannot assign value of type 'View' to type '(View & MyProtocol)?'
myClass.delegate = ViewSubclass()

@objc 추론을 제한하기
스위프트 API를 Objective-C에서 쓸 수 있게 만들기 위해서는 @objc라는 컴파일러 속성을 사용한다. 대부분 스위프트 컴파일러는 여러분을 위해 추론해준다. 이 추론에서 커다란 세가지 이슈는 다음과 같다.
  1. 바이너리 크기를 상당하게 증가시킬 가능성을 가진다.
  2. @objc가 추론될 때 알고있는 것이 명확하지 않다.
  3. 무심결에 Objective-C selector 충돌을 만들어낼 가능성을 증가시킨다.
스위프트4는 @objc 추론을 제한하여 이 문제를 해결하였다 [SE-0160]. 이 말은 Objective-C의 모든 동적 디스패치 능력을 원할때는 명시적으로 @objc를 사용해야 함을 의미한다.

이런 변화를 만들어야하는 몇가지 예제는 다음과 같다. private 메소드, dynacmic 선언, NSObject 자식 클래스의 메소드.

NSNumber 연결하기
NSNumber와 스위프트의 넘버 사이에는 여러 답답한 동작들이 존재해왔는데, 이 언어에 너무 오랫동안 몰해왔다. 다행히 스위프트4는 이 버그를 잡았다 [SE-0170].

아래에는 그 동작들의 예제를 설명해준다.
let n = NSNumber(value: 999)
let v = n as? UInt8 // Swift 4: nil, Swift 3: 231
스위프트3에서 일어나는 이상한 이 동작은 넘버가 오버플로우되면 0에서 시작하게된다. 이 예제에선 999 % 2^8 = 231으로 나온다.

스위프트4는 숫자가 담겨진 타입안에 세이프하게 표현될 수 있을 때만 값을 반환하는 옵셔널으로 만들었다.

스위프트 패키지 매니저
지난 몇달동안 스위프트 패키지 매니저에 여러 업데이트가 있었는데, 가장 큰 변화는 다음과 같다.
  • 브랜치나 커밋 해시에서 의존성을 제공한다(Sourcing dependencies from a branch or commit hash)
  • 더 많은 수용할 수 있는 패키지 버전의 조작(More control of acceptable package versions)
  • 더 일반화하여 해결한 패턴으로 직관적이지 않은 핀닝 명령을 교체()
  • 컴펄레이션에 사용될 스위프트 버전 정의 기능(Ability to define the Swift version used for compilation)
  • 각 타겟마다 소스파일의 위치를 지정함(Specify the location of source files for each target)
이것들은 모두 SPM이 되어야할 모습의 큰 발자국이다.SPM은 아직 긴 여정이 남았지만 프로포절에 활발하게 남아서 윤곽을 잡는 일을 도와줄 수 있다.

최근에 다뤄지는 프로포절의 개괄적인 내용을 보려면 Swift 4 Package Manager Update을 확인해보자.

아직 진행중인 것
이 글을 쓰고 있는 시점에서 15가지 수용된 프로포절이 큐에 있다. 무엇이 어떻게 되가고 있는지 슬쩍 보고 싶으면 Swift Evolution Proposals에 들어가서 Accepted로 필터링해보자.

이제 모두 하나하나 따라가보는것 보단, Xcode9의 베타버전에서 업데이트 된 것을 포스팅 할 것이다.

여기서 어디로 까?
스위프트 언어는 지난 몇년간 매우 커지고 성숙해왔다. 프로포절 과정과 커뮤니티 관계는 이 파이프라인을 따라가면 쉽게 변경사항을 확인할 수 있게 만들어왔다. 또한 누구나 쉽게 직접 에볼루션에 영향을 줄 수 있게 해놓았다.쉽게

스위프트4에서 이런 변화들로, 우리는 마침내 ABI 안정성의 모퉁이에 위치하고 있다. 스위프트 버전을 업그레이드하는데 좀 덜 고통스러워지고 있다. 빌드 퍼포먼스와 툴의 기능은 광대하게 개선되고 있다. 애플의 생태계 바깥에서 사용하는 스위프트는 더욱 실용적이게 되어가고 있다. 그리고 생각하건데, 우리는 아마 오직 몇개만 직관적인 구현에서 벗어나는 String의 완전한 재작성일 것이다(And to think, we're probably only a few full rewrites of String away from an intuitive implementation). ;]

스위프트에 더 많은 것들이 들어왔다. 모든 변경사항들이 어떻게 되가는지 최신 정보를 유지하기 위해서, 아래 자료를 확인하면 된다.
스위프트4에대한 여러분의 생각은 어떤가? 여러분이 제일 좋아하는 변경사항은 무엇인가? 아직도 이 언어 바깥에서 미련이 있는 기능은 무엇인가? 여기서 다루지 않은 새롭고 멋진 무언가를 발견하였는가? 아래 주석에 알려주기 바란다!


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

으로 보내주시면 됩니다.



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

,