원문: https://iosdevweekly.com/issues/351참고: https://pilgwon.github.io/blog/2018/05/14/iOS-Dev-Weekly-351.html
pilgwon님 번역글을 보다가 빠진 부분이 있는것 같아서 제가 번역해봤습니다. 피드백은 댓글로 남겨주세요.



구글 I/O 컨퍼런스가 이번주에 열렸습니다. 여러분은 대부분 Google Duplex의 데모를 봤을 텐데요. 자세히 알아볼 예정은 아니지만, 제 생각을 정리하자면 다음과 같을 것입니다:

  1. 어시스턴트는 아래 몇가지 이유때문에라도 "이 전화는 구글 어시스턴트가 거는 것입니다.."같은 이야기로 시작해서 구별할 수 있어야합니다.

  2. 이런 세부 유스케이스에대한 기술 개념이나 윤리에대해서는 문제가 없습니다. 만약 이런 전화를 받는 사업자가 이런 종류의 전화를 원치 않으면 호환가능한 온라인 체계를 만들고 구글은 전화 대신에 이것을 사용할 수 있을 것입니다. 물론 우리는 미래에 이런 종류의 기술을 어떻게 다룰지 굉장히 조심스럽게 접근할 필요가 있지만, 아직 이게 종말의 시작까지는 아닌거 같습니다.

  3. 네, 수많은 어시스턴트는 단도직입적이고 짧았었습니다만 이것이 어쩌면 사람 반응을 줄이려고 했을지 궁금합니다. 만약 직접 대화를 시작하면 여러 대화가 정해지며 어시스턴트는 더 유연한 대화의 복잡한 대답을 시도하거나 다룰 필요가 없습니다. 이는 1번에서 언급한 것처럼 조수 자신을 확인한 경우에도 일어납니다.

  4. 항상 현실 세계에서는 잘못될 수 있으며, 이런 일이 일어났을때 어떻게 대처할지 저는 매우 궁금합니다. 어떤 부분에서 이게 컴퓨터라는것을 인정할 수 있을까요? 첫번째에서 이야기한것처럼 하면 훨씬 쉬워질 수 있을 것 같습니다.

구글도 모든 것을 생각했을 거라 확신하지만, 이것은 저도 처음에 생각했던 것입니다.

아무튼, 이 모든 것은 iOS(혹은 모바일)로 조금만 작업하면 할 수 있는 것들입니다. WWDC의 키노트는 일반 대중뿐만 아니라 개발자를 위한 키노트이기 때문에 우리를 위한 정말 유익한 뉴스와 눈에 띌만한 무언가가 몇 가지 있을 것입니다:

  1. 구글 어시스턴트는 엄청나게 많은 제3자앱을 지원하며, 이번 키노트에서 어마어마하게 많이 발표되었습니다. SiriKit은 지원된 도매인을 매우 잘 이해했야하므로 굉장히 한정적인 통합 가능성으로 출발했었습니다. 구글은 더 많이 개방된 API로 시작하였으며 이제는 그 도매인을 더 이해하기위해 모인 자료들을 이용하려하고 있습니다. 둘은 굉장히 다른 접근법입니다.

  2. AR과 ML 프레임워크 이름 짓기가 웃긴 상황(원문 트위터)이 되고있습니다만 이런 기술들은 키노트에서 구글이 이야기한 거의 모든 주제였습니다. MLKit은 Firebase의 일부(원문)로써 iOS에서도 사용할 수 있으며 매우 흥미롭습니다.

  3. App slices는 매우 멋지며 나는 사용자가 앱과 어떻게 상효작용하는지가 더 큰 부분이 되어간다는것을 확인할 수 있었습니다. 이것은 iOS 앱 확장같은 느낌이 났지만 다른 접근법을 사용합니다.

  4. 안드로이드 스튜디오(그리고 다른 리눅스 앱들)는 이제 크롬OS에서 돌릴 수 있습니다. 이건 데스크탑 리눅스의 해입니다!! 😂

여튼 구글 I/O에서 나온 것들은 정말 흥미로웠지만 우린 iOS 얘기를 하려고 모였으니 iOS 얘기를 해봅시다.

Dave Verwer


이하, 아티클에대한 번역은 pilgwon 블로그 글을 참고해주세요.


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

,
제목: What's new in Swift 4.0

This article was translated with permission from the English original, What's new in Swift 4.0? on Hacking with Swift .

새로운 스위프트4의 새로운 점을 배우기위한 실제 예제 코드: 새로운 인코딩과 디코딩, 똑똑해진 키패스(keypaths), 다열 문자열(multi-line string) 등이 있습니다!

스위프트4.0이 모두의 인기있는 앱 개발 언어로 새로 배포되었고, 간단하고 안전한 코드를 작성할 수 있는 다양한 기능들을 소개했다. 스위프트 3.0이 발표되었을때 그 극적인 변화에 비하면 즐겁게 받아드릴 수 있을 것인데, 실제로 대부분 변화는 현재 존재하는 스위프트 코드와 완전히 호환이 되게 만든 것이다. 따라서 여러분이 약간의 변경사항을 원한다면, 그렇게 오래 걸리지 않을 것이다.

주의: 스위프트4는 아직 활발히 개발중이다. 나는 여기서 몇가지 유용한 새 기능을 골랐고, 이것들은 현재 모두 구현되었으며 시도해볼 수 있다. 마지막 배포가 있기 전의 그 달에 더 많은 기능이 나올 것임을 기억하라.

이 글이 마음에 들었다면, 아래의 것들도 흥미있을 수 있을 것이다.

스위프트스러운 인코딩과 디코딩
값 타입이 멋지다는 사실은 알고있지만, NSCoding과같은 Objective-C API와 인터렉트하기는 힘들다는 것도 알고있다. 둘을 이어주는 레이어를 만들거나 클래스에 넣어 사용해야한다. 두가지 방법 다 좋지만은 않다. 더욱 나쁜것은, 클래스에 넣어 변경할지라도 직접 인코딩과 디코딩 메소드를 작성해야하며, 이것은 고통스러운 에러를 야기할 수 있다.

스위프트4는 특수한 코드 없이 커스텀 데이터 타입을 시리얼라이즈하고 디-시리얼라이즈하게해주는 Codable 프로토콜을 소개했다. 여러분의 값 타입 손실에대해 더이상 걱정하지 않아도 된다. 더 나은점은 어떻게 데이터를 시리얼라이즈 할지 정할 수 있다는 것이다. 기존의 프로퍼티 리스트 양식을 사용할 수도 있고 JSON도 가능하다.

여러분이 읽고 있는 것이 맞는 말이다: 스위프트4는 특별한 코드 없이 여러분의 커스텀 데이터 타입을 JSON으로 시리얼라이즈 해준다.

이 얼마나 아름다운 광경인지 지켜보자. 먼저, 여기에 커스텀 데이터 타입이 있고, 그 인스턴스가 있다.
struct Language: Codable {
   var name: String
   var version: Int
}

let swift = Language(name: "Swift", version: 4)
let php = Language(name: "PHP", version: 7)
let perl = Language(name: "Perl", version: 6)
Language 구조체에 Codable 프로토콜을 따르게 한 것을 볼 수 있을 것이다. 이 간단한 추가사항만으로 아래처럼 JSON의 Data 표현으로 변환할 수 있다.
let encoder = JSONEncoder()
if let encoded = try? encoder.encode(swift) {
   // save encoded somewhere
}
스위프트는 자동으로 여러분의 데이터 타입 안에있는 모든 프로퍼티들을 인코딩할 것이다. 여러분은 딱히 할 일이 없다.

이제 여러분도 나와같이 오랫동안 NSCoding을 사용해왔다면 뭔가 의심스러울 것이다. 정말로 제대로 동작할까? 그리고 이게 동작한다는 것을 어떻게 확신할 수 있을까? 음, Data 오브젝트를 문자열로 만드는 코드를 추가해서 한번 출력해보자. 그리고 다시 Language 인스턴스로 디코딩 시켜, 읽을 수 있는지 보자.
if let encoded = try? encoder.encode(swift) {
   if let json = String(data: encoded, encoding: .utf8) {
       print(json)
   }

   let decoder = JSONDecoder()
   if let decoded = try? decoder.decode(Language.self, from: encoded) {
       print(decoded.name)
   }
}
디코딩이 타입캐스팅을 필요로 하지 않는다는 점을 인지하자. 첫번째 파라미터로 데이터 타입 이름을 주면, 스위프트는 여기서 리턴 타입을 추론한다.

JSONEncoder와 그 프로퍼티 리스트에 짝인 PropertyListEncoder는 이 부분이 어떻게 동작할지 수많은 커스터마이징 옵션을 가진다. 빽빽한(compact) JSON을 원하는지, 출력하지 좋은(pretty-printed) JSON을 원하는지, ISO8601 데이터를 원하는지, 유닉스 epoch 데이터를 원하는지, 바이너리 프로퍼티 리스트를 사용하고 싶은지 XML을 사용하고 싶은지 정할 수 있다. 다른 옵션들에대한 더 많은 정보를 원한다면  the Swift Evolution proposal for this new feature을 확인해보자.

다열 문자열 리터럴
스위프트에서 다열 문자열로 쓰려면 항상 문자열 안에 \n을 써서 개행을 했었어야 했다. 코드상에서 보기에 좋아보이지 않지만 사용자에게는 올바르게 보여진다. 다행히 스위프트4에서 새로 소개된 다열 문자열 리터럴 문법은 문자열 보간법같은 기능도 쓸 수 있는 채로, 자유롭게 개행을 할 수 있게하고 이스케이핑 없이 따옴표를 사용할 수 있다.

문자열 리터럴을 시작하기 위해서는 쌍따옴표를 3개(""") 적고 리턴키를 누른다. 그 다음 여러분이 원하는 만큼 문자열을 쳐내는데, 변수나 개행도 가능하며, 문자열이 끝날때는 리턴키를 누르고 쌍따옴표를 세개 치면 된다.

문자열 리터럴에는 두가지 중요한 규칙이 있기 때문에 리턴키를 누르는 것에대해 이야기하고 싶다. """으로 문자열을 열때, 여러분의 문자열 내용은 반드시 새로운 라인에서 시작해야한다. 그리고 """로 다열 문자열을 끝낼때는 새로운 줄에서 """을 쳐야한다.

여기에 예시가 있다.
let longString = """
When you write a string that spans multiple
lines make sure you start its content on a
line all of its own, and end it with three
quotes also on a line of their own.
Multi-line strings also let you write "quote marks"
freely inside your strings, which is great!
"""
이것은 정의에서 몇몇 개행이 들어있는 새로운 문자열을 만든 것이다. 훨씬 읽고 쓰기 쉽다.

더 많은 정보를 원한다면 the Swift Evolution proposal for this new feature을 확인해보자.

키-밸류 코딩을위한 키패스 증진(Improved keypaths for key-value coding)
Objective-C에서 사랑했던 기능 중 하나는 직접 접근하는 것이 아닌 동적으로 프로퍼티를 참조하는 능력이다. 이것은 "주어진 오브젝트 X에 내가 읽고 싶은 프로퍼티가 있어"라고 말할 수 있다. 이런 참조 방법을 키패스(keypath)라 부르고 이것은 직접 프로퍼티에 접근하는 것과 구별되는데, 여기서는 실제로 값을 읽고 쓰는 것이 아니라 슬며시 감춰놨다가 나중에 사용하는 것이다.

여러분이 아직 키패스를 사용해보지 않았다면 기존의 스위프트 메소드를 이용해서 어떤식으로 동작했는지 보여주겠다. Starship이라는 구조체를 하나 정의하고 Crew라는 구조체를 정의하자. 그리고 각각의 인스턴스를 하나씩 만든다.
// an example struct
struct Crew {
   var name: String
   var rank: String
}

// another example struct, this time with a method
struct Starship {
   var name: String
   var maxWarp: Double
   var captain: Crew

   func goToMaximumWarp() {
       print("\(name) is now travelling at warp \(maxWarp)")
   }
}

// create instances of those two structs
let janeway = Crew(name: "Kathryn Janeway", rank: "Captain")
let voyager = Starship(name: "Voyager", maxWarp: 9.975, captain: janeway)

// grab a reference to the goToMaximumWarp() method
let enterWarp = voyager.goToMaximumWarp

// call that reference
enterWarp()
스위프트에서는 함수가 일급 타입이므로 마지막 두 줄에서 goToMaximumWarp() 메소드를 enterWarp으로 참조하게 만들 수 있다. 그리고 나중에 우리가 원할때 언제든지 사용할 수 있다. 여기서 문제는 이와같은 것을 프로퍼티에는 못한다는 것이다. "불가피한 상황이 생겼을때 확인할 수 있도록 captain의 이름 프로퍼티를 참조하는 것을 만들어라"고 말할 수 없다. 스위프트는 단지 직접 프로퍼티를 읽어서 원래의 값을 얻어내가 때문이다.

이것은 키패스로 고칠 수 있는데, 우리의 enterWarp() 코드같은 프러퍼티를 참조하며 호출되지 않은 것(uninvoked references to properties)이다. 이제 참조를 호출하면 현재 값을 얻어낸다. 그러나 나중에 참조를 호출하면 나중에 값을 얻어낸다. 프로퍼티의 수를 통해 파헤쳐볼 수 있고, 스위프트는 올바른 타입을 받나냈는지 확인하기위해 타입 추론을 이용한다.

스위프트 에볼루션 커뮤니티는 이 키패스 문법이 올바른지에대해 꽤 오랫동안 토론해왔었는데, 이것이 스위프트 코드와 시각적으로 달라 보일 필요가 있었기 때문이다. 결국 백슬래시를 사용한 문법이 되었다. \Starship.name, \Starship.maxWarp, \Starship.captain.name. 이 두가지를 변수에 할당하여 여러분이 원하는 언제나 어떤 Starship 인스턴스에서도 사용할 수 있다. 예제를 보자.
let nameKeyPath = \Starship.name
let maxWarpKeyPath = \Starship.maxWarp
let captainName = \Starship.captain.name

let starshipName = voyager[keyPath: nameKeyPath]
let starshipMaxWarp = voyager[keyPath: maxWarpKeyPath]
let starshipCaptain = voyager[keyPath: captainName]
스위프트는 타입 추론이 가능하기 때문에 starshipName 문자열과 starshipMaxWarp 더블형을 만들 것이다. 이 예제의 세번째처럼 프로퍼티의 프로퍼티라도 스위프트는 역시 올바르게 이해할 것이다.

나중에는 런타임중에도 배열 인덱스를 접근할 수 있거나 문자열로부터 키패스를 만들어낼 수 있는 계획이 있다. 더 많은 정보를 원한다면 the Swift Evolution proposal for this new feature을 확인해보자.

약간의 광고
If you're enjoying this article, you might like my free Natural Swift video. It gives you 75 minutes of hands-on coding that teaches functional programming, protocol-oriented programming, and value types, and you can download it for free with no obligation or catches – just click here.

And now back to your regularly scheduled broadcast…

딕셔너리 기능 증진
스위프트4에서 한가지 더 흥미로운 프로퍼절은 딕셔너리를 더욱 강력하게 만들고 특정 상황에 여러분이 예상한대로 동작하게 만드는 기능이었다.

간단한 예제로 시작해보자. 스위프트3에서 딕셔너리를 필터링 하는 것은 새로운 딕셔너리를 반환하지 았다. 대신 키/값 래이블과 함께 튜플 배열을 반환했었다. 예제이다.
let cities = ["Shanghai": 24_256_800, "Karachi": 23_500_000, "Beijing": 21_516_000, "Seoul": 9_995_000];
let massiveCities = cities.filter { $0.value > 10_000_000 }
이후에는 더이상 딕셔너리가 아니므로 massiveCities["Shanghai"]로 읽을 수 없다. 대신 massiveCities[0].value를 사용해야했는데, 별로 좋아보이지 않는다.

스위프트4부터는 더욱 여러분이 생각한대로 동작한다. 여기서는 새로운 딕셔너리를 반환해준다. 명백하게 이것은 튜플-배열 리턴 타입에 의존하고있는 현재 코드를 변경해야 할 것이다.

비슷하게, 딕셔너리에서 map() 메소드도 많은 사람들이 원하던 방식대로 되지 않았었다. 전달되면 키-값 튜플을 받고, 배열에 추가하기위해 한 값을 반환할 수 있다. 예제를 보자.
let populations = cities.map { $0.value * 2 }
이것은 스위프트4에서 바뀌지 않았지만, mapValues()라 불리는 새 메소드가 추가되었다. 이것은 더욱더 유용할 것으로 보이는데, 값을 변경하고 기존의 키를 이용해 딕셔너리에 다시 값을 넣어둘 수 있게 해준다.

예를들어, 이 코드는 모든 도시 인구를 100만으로 나누고 문자열로 만든다. 그리고 다시 Shanghai, Karachi, Seoul의 같은 키로 새로운 딕셔너리에 넣는다.
let roundedCities = cities.mapValues { "\($0 / 1_000_000) million people" }
(여기서 여러분은 실수로 중복할 수도 있기 때문에 딕셔너리 키를 맵핑시키는게 안전하지 않다고 생각할 수 있다.)

쉽게, 내가 좋아하는 새로운 딕셔너리 추가사항은 grouping 생성자이다. 이것은 시퀀스를 여러분이 원하는대로 그룹화한 시퀀스의 딕셔너리로 만들어준다. 계속해서 cities 예제로가서, 도시 이름의 배열을 얻어내기위해 cities.keys를 사용할 수 있으며, 첫글자로 그룹화한다.
let groupedCities = Dictionary(grouping: cities.keys) { $0.characters.first! }
print(groupedCities)
// ["B": ["Beijing"], "S": ["Shanghai", "Seoul"], "K": ["Karachi"]]
대신 아래처럼 그 이름의 길이에따라 도시들을 그룹화 시킬 수 있다.
let groupedCities = Dictionary(grouping: cities.keys) { $0.count }
print(groupedCities)
// [5: ["Seoul"], 7: ["Karachi", "Beijing"], 8: ["Shanghai"]]
마지막으로, 딕셔너리 키에 접근할 수 있고 키에 해당하는 값이 없다면 디폴트 값을 제공할 수 있다.
let person = ["name": "Taylor", "city": "Nashville"]
let name = person["name", default: "Anonymous"]
이제 경험이 좀 있는 개발자들은 nil-coalescing으로 사용하는게 더 났다고 주장할 것이다. 나도 동의한다. 현재 버전의 스위프트것을 사용하는 것 대신에 아래처럼도 쓸 수 있다.
let name = person["name"] ?? "Anonymous"
그러나 그냥 읽는데는 문제가 없지만, 딕셔너리 값을 수정할때는 동작하지 않는다. 적절하게 딕셔너리 값을 수정할 수 없는데, 그 키로 접근하면 옵셔널을 반환하기 때문이다. 키가 존재하지 않을 수도 있다. 스위프트4의 디폴트 딕셔너리 값으로 여러분은 더욱 적합한 코드를 작성할 수 있다. 아래처럼 말이다.
var favoriteTVShows = ["Red Dwarf", "Blackadder", "Fawlty Towers", "Red Dwarf"]
var favoriteCounts = [String: Int]()

for show in favoriteTVShows {
   favoriteCounts[show, default: 0] += 1
}
이 코드는 favoriteTVShows에 있는 모든 문자열을 돌면서, favoriteCounts라는 딕셔너리를 사용하여 각 항목이 나타날때마다 카운팅을 한다. 코드 한줄에서 딕셔너리를 수정할 수 있는데, 딕셔너리에는 항상 값을 가질거라는 것을 알기 때문이다. 디폴트값의 0이든 이전에 카운팅되었던 것에따라 더 높은 숫자든.

더 많은 정보를 원한다면 the Swift Evolution proposal for these new features을 확인해보자.

문자열은 다시 컬랙션이다!
작은 변화지만 많은 사람들을 행복하게 만든다. 문자열이 다시 컬랙션이 되었다. 이 의미는, 이것을 뒤집거나, 문자를 하나씩 돌거나, map()하거나 flatMap()하는 등이 가능하다. 예제를 보자.
let quote = "It is a truth universally acknowledged that new Swift versions bring new features."
let reversed = quote.reversed()

for letter in quote {
   print(letter)
}
이 변경은 String Manifesto라 불리는 다양한 개정사항의 부분에 소개되어 있다.

한쪽만의 범위(One-sided ranges)
가장 최신은 아니지만 최근에, 스위프트4는 파이썬같은 한쪽만 컬랙션 자르기를 소개했다. 한쪽이 빠지면 자동으로 컬랙션의 시작이나 끝을 추론한다. 이 변경은 현재 코드에 영향을 주지 않는다. 현재 연산자에 새로운 부분이기 때문에 잠재적인 손상을 걱정하지 않아도 된다.

아래에 예제가 있다.
let characters = ["Dr Horrible", "Captain Hammer", "Penny", "Bad Horse", "Moist"]
let bigParts = characters[..<3]
let smallParts = characters[3...]
print(bigParts)
// ["Dr Horrible", "Captain Hammer", "Penny"]
print(smallParts)
// ["Bad Horse", "Moist"]
더 많은 정보를 원한다면 the Swift Evolution proposal for this new feature을 확인해보자.

아직 더 남은게 있다...
스위프트4를 탑재한 Xcode의 첫번째 배포판은 6월에 iOS11, tvOS11, watchOS4, macOS와함께 나올것으로 보인다(역자: 이미 나왔습니다). 지금까지 우리가 본 것은 특히 명확하게 이미 나오기로 약속되었고, 팀은 가능한 추가할 수 있도록 스위프트4를 만들려고 노력하고 있다. 주로 기존에 있는 것을 고치거나 수정하지 않도록 새로운 기능을 추가하는 것이 업그레이드하기 편하기 해줄 수 있고, 고맙게도 이 언어를 위한 새로운 안정성의 시작의 신호가 있다.

스위프트 에볼루션이 때론 혼돈속에 빠지기도 했지만(접근 수준같은 부분), 스위프트4는 애플의 커뮤니티 방법을 다시 검증했다. 나는 위에 몇몇 스위프트 에볼루션을 링크 걸어두었는데, 각각의 것들은 커뮤니티 여론의 도움으로 광범위하게 토론되었다. 애플 엔지니어가 변경을 강행하지 않고 분별력있게 하였으며, 무엇이 이미 스마트하고 엘레강트한 언어인지 다듬기위해 고려했다.

한가지 지연된 기능은 ABI 호환성인데, 이것은 개발자들에게 컴파일된 라이브러리를 배포할 수 있게 해줄 것이다. 오늘 스위프트에 몇가지 핵심 기능이 구현되지 않고 남아있다. 다행히도 스위프트5가 되기 전에 만나볼 수 있을 것이다...


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

으로 보내주시면 됩니다.




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

,
제목: 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

,
제목: What’s New in Swift 3.1?


멋진 소식: Xcode8.3과 스위프트3.1이 베타로 나왔다! 이 릴리즈는 오래 기다린 스위프트 패키지 매니저 기능들과 언어 자체의 증진을 담고있다.

Swift Evolution Process에 가깝게 팔로우하고 있지 않았다면, 이 글을 계속 읽어보자(이 글은 그런 여러분을 위한 글이다!).

이 글에서는, 스위프트3.1에서 여러분 코드에 주요하게 영향을 줄 수 있는 가장 중요한 변화들만 집어주겠다. 안으로 들어가보자! :]

시작하기
스위프트3.1은 스위프트 3.0과 소스-호환이 되므로, 이미 Xcode에서 여러분의 프로젝트를 Edit\Convert\To Current Swift 를 사용해서 스위프트 3.0으로 마이그래이션 했다면, 새 기능이 여러분의 코드를 망가뜨리지 않을 것이다. 그러나 애플은 Xcode 8.3에서 스위프트 2.3 지원을 멈췄다. 따라서 아직 스위프트 2.3에서 마이그레이션을 하지 않았다면 지금이 그 때이다!

아래 섹션에서는, [SE-0001] 같은 링크의 태그를 볼 수 있을 것이다. 이것들은 Swift Evolution 프로포절 번호이다. 각 프로퍼절에 링크를 넣었놨으니 각 특정 변경에대한 세부적인 사항들을 찾을 수 있을 것이다. 나는 우리가 이야기나는 기능들을 플레이그라운드에서 시도해보길 추천하며, 그렇게하여 여러분은 그 모든 변화를 더 잘 이해할 수 있다.

따라서 Xcode를 켜서, File\New\Playground...를 선택하고 플랫폼은 iOS를 선택한다. 여러분이 원하는 아무거나 호출하고, 원하는 아무거나 저장하자. 이 글을 읽는동안, 각 기능을 플레이그라운드에서 시도해보아라.

Note: 만약 스위프트 3.0의 짧고 간단한 하이라이트 리뷰가 필요하다면, What’s New in Swift 3를 확인해보자.

언어 개선
먼저, 숫자 타입의 실패할수있는 생성자, 새로운 시퀀스 함수등을 포함하여, 이번 릴리즈에서 언어 개선에대해 살펴보자.

실패할수있는 숫자 변환 생성자(Failable Numeric Conversion Initializers)
스위프트3.1은 모든 숫자 타입(Int, Int8, Int16, Int32, Int64, UInt8, UInt16, UInt32, UInt64, Float, Float80, Double)을위해 실패할수있는 생성자를 만들었다. 이것은 정보 손실없이 성공적으로 완료하거나 아니면 간단하게 nil을 반환한다 [SE-0080]

이 기능은 유용한데, 예를들어 세이프와 복구가능한 방법에서 외부 소스로부터 느슨한 타입 데이터 변환을 다룰때가 있다. 예를들어서, 한 Student JSON 배열을 어떻게 처리하는지보자.

class Student {
  let name: String
  let grade: Int
  
  init?(json: [String: Any]) {
    guard let name = json["name"] as? String,
          let gradeString = json["grade"] as? String,
          let gradeDouble = Double(gradeString),
          let grade = Int(exactly: gradeDouble)  // <-- 3.1 feature here
    else {
        return nil
    }
    self.name = name
    self.grade = grade
  }
}

func makeStudents(with data: Data) -> [Student] {
  guard let json = try? JSONSerialization.jsonObject(with: data, options: .allowFragments),
        let jsonArray = json as? [[String: Any]] else {
    return []
  }
  return jsonArray.flatMap(Student.init)
}

let rawStudents = "[{\"name\":\"Ray\", \"grade\":\"5.0\"}, {\"name\":\"Matt\", \"grade\":\"6\"},
                    {\"name\":\"Chris\", \"grade\":\"6.33\"}, {\"name\":\"Cosmin\", \"grade\":\"7\"}, 
                    {\"name\":\"Steven\", \"grade\":\"7.5\"}]"
let data = rawStudents.data(using: .utf8)!
let students = makeStudents(with: data)
dump(students) // [(name: "Ray", grade: 5), (name: "Matt", grade: 6), (name: "Cosmin", grade: 7)]

아래처럼 실패할수있는 생성자를 명시하여 Stuent 클래스 안에 Double에서 Intgrade 프로퍼티를 변환하는데 실패할수있는 생성자를 사용한다.

let grade = Int(exactly: gradeDouble)

gradeDouble6.33같은 분수 값이고, 이것은 실패할 것이다. 만약 6.0같은 정확한 Int로 표현할 수 있다면 이것은 성공할 것이다.

Note: 실패할수있는것 대신에 대안의 설계는 throwing 생성자를 사용하는 것이었다. 커뮤니티는 실패할수있는것이 더 낫고 더 인간환경공학적 설계라고 선택했다.

새로운 시퀀스 함수들
스위프트 3.1은 데이터 필터링을위한 표준 라이브러리의 Sequence 프로토콜의 두가지 함수를 추가했다. prefix(while:)drop(while:)이다. [SE-0045]

피보나치 무한 시퀀스를 생각해보자.

let fibonacci = sequence(state: (0, 1)) {
  (state: inout (Int, Int)) -> Int? in
  defer {state = (state.1, state.0 + state.1)}
  return state.0
}

스위프트 3.0에서 우리는 fibonacci sequence를 돌기위해 반복 카운터를 명세했었다.

// Swift 3.0
for number in fibonacci.prefix(10) {
  print(number)  // 0 1 1 2 3 5 8 13 21 34
}

스위프트 3.1은 주어진 두 값 사이에 시퀀스의 모든 엘리먼트를 뽑아내기위해 조건과함께 prefix(while:)drop(while:)을 사용할 수 있게 해준다.

// Swift 3.1
let interval = fibonacci.prefix(while: {$0 < 1000}).drop(while: {$0 < 100})
for element in interval {
  print(element) // 144 233 377 610 987
}

prefix(while:)은 특정 조건을 만족시키는 가장 긴 서브시퀀스를 반환한다. 시퀀스의 첫부분부터 시작해서 주어진 클로저가 false를 반환하는 첫번째 엘리먼트에서 멈춘다.

drop(while:)은 그 반대이다. 주어진 클로저에서 false를 반환하는 첫번째 엘리먼트에서 시작해서 스퀀스의 끝에서 멈춘다.

Note: 이 경우엔 트레일링 클로저 문법(trailing closure syntax)을 사용할 수 있을 것이다.
let interval = fibonacci.prefix{$0 < 1000}.drop{$0 < 100}

구체적인 제한 익스텐션(Concrete Constrained Extensions)
스위프트 3.1은 구체적인 타입 제한으로 제네릭 타입을 익스텐션할 수 있게 해준다. 이전에는, 프로토콜로 제한되어있어서 이런 타입을 익스텐션할 수 없었다. 예제를 살펴보자.

예를들어, 루비온레일즈는 사용자 입력을 확인하기위한 유용한 isBlank 메소드를 제공한다. 여기에 스위프트 3.0에서 String 데이터 타입 익스텐션에서 어떻게 계산된 프로프터(computed property)로 구현할 수 있는지 보자.

// Swift 3.0
extension String {
  var isBlank: Bool {
    return trimmingCharacters(in: .whitespaces).isEmpty
  }
}

let abc = " "
let def = "x"

abc.isBlank // true
def.isBlank // false
isBlank 계산된 프로퍼티가 옵셔널 문자열로 동작하기 원한다면, 스위프트 3.0에서 아래를 할 수있을것이다.
// Swift 3.0
protocol StringProvider {
  var string: String {get}
}

extension String: StringProvider {
  var string: String {
    return self
  }
}

extension Optional where Wrapped: StringProvider {
  var isBlank: Bool {
    return self?.string.isBlank ?? true
  }
}

let foo: String? = nil
let bar: String? = "  "
let baz: String? = "x"

foo.isBlank // true
bar.isBlank // true
baz.isBlank // false

이것은 String에 적용하기위해 StringProvider 프로토콜을 만들었다. isBlank 메소드를 추가하기위해 Wrapped 타입이 StringProvider일때 Optional을 확장하는데 사용한다.

스위프트 3.1은 프로토콜 대신에 아래처럼 구체적인 타입을 익스탠션하게 해준다.

// Swift 3.1
extension Optional where Wrapped == String {
  var isBlank: Bool {
    return self?.isBlank ?? true
  }
}

이것은 이전과 같은 기능을 하나, 아주 많이 코드를 줄일 수 있다!

감싸진 제네릭(Nested Generics)
스위프트 3.1은 제네릭으로 감싸진 타임을 섞을 수 있게 해준다. 예제로, 이것을 보자. raywenderlich.com에서 이끌고있는 어떤 팀이 블로그에 포스팅을 발행하고 싶을 때마다, 웹사이트의 높은 퀄리티 기준을 맞추기위해 이 발행에 참여한 개발자들의 팀을 넣는다.

class Team {
  enum TeamType {
    case swift
    case iOS
    case macOS
  }
  
  class BlogPost {
    enum BlogPostType {
      case tutorial
      case article
    }
    
    let title: T
    let type: BlogPostType
    let category: TeamType
    let publishDate: Date
    
    init(title: T, type: BlogPostType, category: TeamType, publishDate: Date) {
      self.title = title
      self.type = type
      self.category = category
      self.publishDate = publishDate
    }
  }
  
  let type: TeamType
  let author: T
  let teamLead: T
  let blogPost: BlogPost
  
  init(type: TeamType, author: T, teamLead: T, blogPost: BlogPost) {
    self.type = type
    self.author = author
    self.teamLead = teamLead
    self.blogPost = blogPost
  }
}

Team이라는 외부 클래스안에 BlogPost라는 내부 클래스를 감싸고(nest) 두 클래스를 모두 제네릭 클래스로 만든다. 이것은 이 팀이 지금까지 웹사이트에 내가 발행한 튜토리얼과 글을 찾는 방법이다.
Team(type: .swift, author: "Cosmin Pupăză", teamLead: "Ray Fix", 
     blogPost: Team.BlogPost(title: "Pattern Matching", type: .tutorial, 
     category: .swift, publishDate: Date()))

Team(type: .swift, author: "Cosmin Pupăză", teamLead: "Ray Fix", 
     blogPost: Team.BlogPost(title: "What's New in Swift 3.1?", type: .article, 
     category: .swift, publishDate: Date()))
그러나 실제로는, 이 경우 좀 더 심플하게 만들 수 있다. 감싸진 내부 타입은 제네릭 외부 타입을 사용하면 디폴트로 부모 타입을 상속한다. 따라서 이것을 정의할 필요가 없다.
class Team {
  // original code 
  
  class BlogPost {
    // original code
  }  
  
  // original code 
  let blogPost: BlogPost
  
  init(type: TeamType, author: T, teamLead: T, blogPost: BlogPost) {
    // original code   
  }
}
Note: 스위프트에서 제네릭에대해 더 배우고 싶다면, 우리가 최근에 업데이트한 getting started with Swift generics를 읽어보라.

스위프트 버전 사용 가능
여러분은 if swift(>= N) static construct를 사용해서 특정 스위프트 버전을 체크할 수 있다.
// Swift 3.0
#if swift(>=3.1)
  func intVersion(number: Double) -> Int? {
    return Int(exactly: number)
  }
#elseif swift(>=3.0)
  func intVersion(number: Double) -> Int {
    return Int(number)
  }
#endif
그러나 이런 방법은 스위프트 표준 라이브러리같은 곳에서 사용될때 중요한 결점을 가지고 있다. 이것은 각각 예전 언어 버전을 지원하기위해 컴파일하는 표준 라이브러리를 요구한다. 여러분이 스위프트 컴파일러를 예전의 호환성 모드로 실행시킬때(예를들어 스위프트 3.0 동작을 원한다고 말할때), 특정 호환 버전으로 컴파일된 표준 라이브러리 버전을 사용해야할것이다. 만약 3.1버전으로 컴파일한 것을 사용한다면, 간단하게 코드를 작성할 수 없을 것이다.

따라서 스위프트 3.1은 현재 플랫폼 버전 외에도 특정 스위프트 버전 숫자를 지원하는 @available 속성을 확장했다[SE-0141].
// Swift 3.1

@available(swift 3.1)
func intVersion(number: Double) -> Int? {
  return Int(exactly: number)
}

@available(swift, introduced: 3.0, obsoleted: 3.1)
func intVersion(number: Double) -> Int {
  return Int(number)
}
이 새로은 기능은 intVersion 메소드는 어떤 스위프트 버전하에 가능하다는 말과 같은 뜻이다. 그러나 표준 라이브러리같은 라이브러리들은 오직 한번만 컴파일되게 할 수 있다. 그러면 컴파일러는 간단하게 주어진 호환 버전을 선택하여 기능을 고른다.

Note : 스위프트에서 어베일러빌리티 속성(availibility attributes)에대해 너 배우고 싶으면, availability attributes in Swift라는 우리 튜토리얼을 확인해보자.

비-이스케이핑 클로저(Non-Escaping Closure)를 이스케이핑 클로저(Escaping Closures)로 변환하기
함수에 클로저 인자는 스위프트 3.0 디폴트에의해 non-escaping으로 만들어졌다[SE-0103]. 그러나 이 프로퍼절의 일부가 그때 구현되지 못했다. 스위프트 3.1에서는 withoutActuallyEscaping() 핼퍼 함수를 사용하여 임시로 비-이스케이핑 클로저를 이스케이핑 클로저로 변환할 수 있다.

왜 이런게 필요할까? 아마 자주 필요하진 않을것이지만, 제인에서 나온 아래 예제를 생각해보자.
func perform(_ f: () -> Void, simultaneouslyWith g: () -> Void,
             on queue: DispatchQueue) {
  withoutActuallyEscaping(f) { escapableF in     // 1
    withoutActuallyEscaping(g) { escapableG in
      queue.async(execute: escapableF)           // 2
      queue.async(execute: escapableG)     

      queue.sync(flags: .barrier) {}             // 3
    }                                            // 4
  }
}
이 함수는 동시에 두 클로저를 실행하고 두 클로저가 완료되면 반환한다.
  1. fg는 비-이스케이핑으로 들어와서 escapableFescapableG로 변환된다.
  2. async(excute:)은 요구 이스케이핑 클로저를 호출한다. 다행히도 이전 단계 덕에 이것을 가지고 있다.
  3. sync(flags: .barrier)를 실행하여, async(execute:)메소드가 완전히 완료되고 클로저가 다음에는 다시는 호출되지 않을거라 보장한다.
  4. 스코프는 escapableFecapableG 사용을 제한한다.
만약 임시의 이스케이핑 클로저를 어딘가에 넣어주려 한다면(즉, 실제로 이스케이프된 것들) 이것은 버그가 될 수 있다. 표준 라이브러리의 나중의 버전에서는 여러분이 이것을 호출하려할때, 이것을 감지하고 잡아낼 수 있게 될것이다.

스위프트 패키지 매니저 업데이트
아, 긴 시간동안 기다린 스위프트 패키지 매니저 업데이트가 도착했다!

수정가능한 패키지들(Editable Packages)
스위프트 3.1은 스위프트 패키지 매니저에 수정가능한 패키지라는 개념을 넣었다[editable-packages">SE-0082].

swift package edit 명령은 존재하는 패키지를 받아서 수정가능한 패키지로 변환한다. 수정가능한 패키지는 의존성 그래프에서 모든 일반적인 패키지의 발생을 대체한다. 패키지 매니저를 일반적인 해결된 패키지(canonical resolved package)로 돌리려면 --end-edit 명령을 사용하자.

버전 피닝(Version Pinning)
스위프트 3.1은 스위프트 패키지 매니저에 특정 버전으로 버전 피닝 패키지 의존성 개념을 추가했다[package-pinning">SE-0145]. pin이라는 명령은 하나 혹은 모든 의존성을 핀한다.
$ swift package pin --all      // pins all the dependencies
$ swift package pin Foo        // pins Foo at current resolved version
$ swift package pin Foo --version 1.2.3  // pins Foo at 1.2.3
unpin 명령으로 이전 패키지 버전으로 되돌릴 수 있다.
$ swift package unpin —all
$ swift package unpin Foo
이 패키지 매니저는 Package.pins에 각 패키지의 활성된 버전 핀 정보를 저장한다. 이 파일이 없다면, 패키지 매니저는 오토메틱 피닝(automatic pinning) 프로세스의 부분으로서 자동으로 패키지 메니패스트에서 특정 요구에따라 생성한다.

다른 것들
swift package reset 명령은 체크아웃된 의존성이 없거나 빌드 인공의 프레젠트가 없는 깨끗한 상태로 패키지를 되돌린다.

게다가, swift test --parallel 명령은 병렬로 테스트를 실행시킨다.

잡다한 것들
스위프트 3.1에는 어느 카테고리에도 맞지않는 짧은 소식들도 있다.

다중-리턴 함수(Multiple-Return Functions)
vforksetjmp같은 두번 리턴하는 C 함수는 이제 동작하지 않는다. 이것들은 관련된 방법으로 프로그램의 컨트롤 플로우를 바꿔버린다. 그래서 스위프트 커뮤니티는 이것들 사용을 금지시키고 이제 컴파일타임 에러를 내뱉도록 하기로 결정했다.

자동-링킹 끄기(Disable Auto-Linking)
스위프트 패키지 매니저는 C 언어 타겟을 위한 모듈 맵(module maps)의 자동-링킹 기능을 끌 수 있다.
// Swift 3.0
module MyCLib {
    header “foo.h"
    link “MyCLib"
    export *
}

// Swift 3.1
module MyCLib {
    header “foo.h”
    export *
}

여기서 어디로 가야할까?
스위프트 3.1은 스위프트 3.0 기능을 갈고 닦는 역할을 하는데, 올해(2017년 가을쯤) 스위프트 4.0 번들을 더 심사숙고하게 변경하기위한 준비이다. 이런것에는 제네릭, 정규식, 더 인간환경적인 문자열 설계 등에대한 거대한 개선을 포함한다.

여러분이 뭔가 새로운 것을 느끼고 있다면 Swift standard library diffs을 한번 보거나, 스위프트 변화에대한 모든 정보를 얻을 수 있는 공식 Swift CHANGELOG를 보자. 혹은 스위프트 4.0이 나올때까지 지켜보고 있어도 된다.

그리고 스위프트 4.0과 그 넘어에 무엇이 바뀔지 궁금하다면, 바로 지금 프로포절되고있는 것을 볼 수 있는 Swift Evolution proposals를 보면된다. 만일 여러분이 매우 열망하고있다면 현재 리뷰중인 프로포절에 피드백을 날리거나 여러분 스스로 프로포절을 할 수도 있다 ;].

지금까지 스위프트 3.1에서 여러분이 좋아하는점과  싫어하는점이 무엇인가? 이 포럼 아래 토론에서 알려달라!

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

으로 보내주시면 됩니다.



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

,
제목: Thoughts on Swift access control
그리고 스위프트 에볼루션의 기회비용


스위프트에서의 접근제어에관한 엄청난 양의 swift-evolution mailing lists에 있다. 몇일전에 (Private 접근 수준을 고치자는) SE-0159제안이 거절되었다. 이것에 대한 내 생각을 공유하고 싶고, 전반적으로 접근 제어의 커다란 이야기에대한 내 생각도 들려주고 싶다. 그러나 먼저, 스위프트의 접근 제어 이력을 간단하게 알려주겠다.

⚠️주의: 나의 의견이 들어있을 것이다.



접근제어의 간단한 이력
스위프트의 이른 시기(1.0이전)에는 접근제어가 없었다. 이때가 스위프트의 황금기였다. 모든것이 퍼블릭이고 글로벌하게 어디에서나 접근할 수 있었다. 누구도 적절한 캡슐화에대해 생각하지 않았다. 한달동안 토론한 이메일도 없었다(스위프트-에볼루션이 아직 존재하지 않았었다). 접근제어가 없는것은 가장 간단하게 접근하여 제어하게 했고, 에볼루션이 없는것이 최고의 에볼루션이었다.

접근 제어가 나오다
Xcode6 베타4에서 스위프트는 접근제어를 추가하여 지원했다. 이해하기 쉬웠고, 사용하기도 쉬웠으며, 꽤 우아했었다. 즉시 이후에, 접근제어 모델과 함께 스위프트 1.0이 배포되었다. 스위프트 1.0은 Xcode 6과함께 나왔다. 상속과함께 합쳐진 "protected" 접근수준은 없었다. "friend" 접근수준도 없었는데, 이것은 그냥 지겨운 것이기 때문이다. 오직 세개의 접근수준만 있었다.
  • public은 모듈(프레임워크나 라이브러리)을 임포트한 모든 파일로부터 접근할 수 있다
  • internal은 (디폴트로서) 현재 모듈 안에서만 접근할 수 있다(앱이나 프레임워크 타겟도 "현재 디렉토리"라 생각한다 à la Swift Package Manager)
  • private은 이것이 정의된 소스파일 안에서만 접근할 수 있다

라이브러리 저자, 앱 개발자로서 이런 접근수준은 내가 원하는 모든 것을 표현하기위해 나에게 필요한 모든 기능을 제공한다. 명백한 유스케이스를 떠나서, "기존의" "private" 접근의 노테이션은 그 파일안에 타입이나 다른 엔티티를 정의하기만하면 달성할 수 있다. 예를들어 class A를 정의하고 그 모든 (private) 프로퍼티를 다른 클래스로부터 접근하지 못하게 하고 싶으면, 그 파일에 class A를 정의하고 다른것을 할 필요가 없었다. 이것이 바로 내가 좋아했던 스위프트 접근제어이다 - 디스크의 파일과 디렉토리의 "물리적인" 구조를 기본적으로 반영한 접근수준을 제공하여 최고의 실현을 만들어준다(파일이 부푸는것과 모듈이 부푸는것을 줄여준다). 적절한 캡슐화를 설계하려면, 명확하게 정의된 모듈(디렉토리)에 파일을 이동해야하고, 한 파일에 관련된 타입들을 정의하고, 한 파일안에 여러 타입 정의를 완전히 피하면된다. 코드 구성과 접근제어는 좋게 엮여있고 개발자에게 코드를 잘 구성할 수 있게 해준다. (엮이는것 일반적으로 소프트웨어 설계에서 나쁘나, 이 경우 필요한 엮임이다.)

fileprivate가 나오다
다음단계는 스위프트가 오픈소스화되고 스위프트 에볼루션 프로세스가 나왔다. 영역을 지정한 접근수준의 이 SE-0025 제안은 검토되고, 수정되서 마침내 스위프트3에서 수용되었다. 이 제안은 현재 선언된 스코프안에 엔티티에게 엄격한 접근을 하기위해 private의 의미가 바뀌었고, 이전 기능을 보존하기위해 fileprivate라는 키워드가 새로 나왔다. 이때는 새로운 (그리고 다소 의도적으로 못낳게 만든) fileprivate 키워드는 드불게 사용되어서 스위프트의 단계적 공개의 설계 철학에의해 지켜진다. 우리가 이것에대해 조금 알고있는 것은 이것이 실전에는 옳지 않다는 것이다. fileprivateprivate의 기능이 오버랩되고 private용어가 오버로드되면서, 인지적 로드가 증가하는 또다른 사이드이펙트가 있었다.

open에서 바깥
단지 삼개월후에 초기의 토론이 다른 접근제어 변화가 시작되었다. 세번의 논쟁적 검토기간 과 수정 후에 public 접근과 public 오버라이드가능함의 차이를 구별하자는 SE-0117 제안은 스위프트3에서 받아드려졌다. 이 제안은 open이라 부르는 새로운 접근수준을 만들었고 어떤 문맥에서 public의 정의가 바뀌었다. public의 의미가 서브클래스하는것과 오버라이딩하는것에 관련하여 좁아졌다. public 클래스는 더이상 이것이 정의된 곳에 모듈의 바깥에서 서브클래스될 수 없고 클래스의 모든 public 맴버들은 더이상 클래스 모듈의 바깥에서 서브클래스에의해 오버라이드 될 수 없다. 따라서 open 클래스들은 public이며 서브클래스가능하고, open 클래스에있는 모든 open 프로퍼티나 open 함수는 서브클래스에서 오버라이드될 수 있다. 이해할 수 있겠는가? publicopen에 관련된 규칙들은 혼란스럽고, 오버라이드와 서브클래스를 만드는것을 막는 final에의해 더 복잡해진다. 따라서 final publicpublic 클래스는 같은 모듈안이나 그 밖깥에 클라이언트가 있는지 없는지에따라 의미가 달라진다. 다시말해, 접근제어에대해 생각할때 인지적 로드가 증가한다. 이번시간은, openpublic의 기능을 오버랩하는것을 더 판별해야하고 구별하기 어렵다.

open의 복잡함과 그 규칙의 미로와 엣지 케이스에도 불구하고 단계적 공개 철학에의해 너무 잘 지켜졌다. 많은 스위프트 사용자(특히 초보자들)는 open를 사용하거나 알려고 하지 않을 것이다. 라이브러리 저자나 앞서간 사용자들만이 open 사용을 마주할 것으로 보인다. open의 단계적 공개는 스위프트 설계 성질에서 open이 적용되지 않은 값타입에 더 명백하게 되었다. 왜 우리가 SE-0117를 되돌리는 제안이나 open을 더 수정하는 제안을 보지 못했는지 그 이유라고 생각한다.

open은 성공적으로 단계적 공개가 되었을지라도, 적어도 내 경험상 이것은 메리트가 작다는 주장을 여전히 하고싶다. 특별히 퍼블릭 API의 부분일때, 대부분 나는 클래스를 final로 정의한다. 드물게 모듈안의 클래스를 서브클래스로 만들고 싶을때가 있지만, 모듈 바깥에서는 아니다. 특히 스위프트만큼 표현력있고 풍부한 언어에서는 동작을 내부적으로 공유하기위해 클래스와 모듈을 설계하는 다양한 방법이 있으나, 외부적으로 그 동작을 노출시키는것은 피한다. open같은 기능은 명백하게 주로 사용되지 않는 것이다. 이것이 실망스러움을 넘어 public 의미에 영향을 주게 만든다.

게다가, SE-0117SE-0025가 받아드려지고 구현된 후에 의논되고 검토되었더라도 이것은 본래 SE-0025로부터 고립된 제안이 되었다는 점을 짚어야한다. 물론, 그때 커뮤니티는 SE-0025에대해 알았으나 누구도 실제로 스위프트 3과 새로운 privatefileprivate를 사용해보지 않았었다. (스냅샷에서 누군가가 이것을 시험해보았을수 있으나, 매우 적은 개발자들이 그렇게 했을 것이다.) 우리는 그 암시와 SE-0025의 현실성에대해서 완전히 깜깜한 상태였다. 접근제어를 증대하는것에 취해있는동안, 커뮤니티는 변경하기위해 다른 프로포절을 다루고 있었다. 명확하게하면, 누구의 책임도 아니다. 그냥 우리는 인지하지 못했었다.

스위프트 3.0의 접근제어
이것은 스위프트의 현재 접근제어 상태로 나온 것이다. The Swift Programming Language eBook에서 나온 문장이다.
  • open 접근은 정의하는 모듈을 불러온 다른 모듈로부터, 혹은 정의하는 다른 모듈로부터 어떤 소스파일에서도 엔티티를 사용될 수 있다. open은 클래스에만 적용하고, 그것이 접근가능한것에서 서브클래스로 만드는게 가능하다. 또한 모든 open 클래스는 그 맴버를 오버라이드되게 하기위해 open으로 정의할 수 있다.
  • public 접근은 정의하는 모듈 안에서만 서브클래스하고 오버라이드할 수 있는 점만 빼면 open과 비슷하다.
  • internal접근(디폴트이다)은 정의하는 모듈로부터 모든 소스 파일에서 사용할 수 있으나, 모듈 밖에서는 안된다.
  • fileprivate 접근은 소유하는 정의하는 소스파일에만 엔티티를 사용할 수 있다.
  • private 접근은 정의나 스코프로 감싸진 곳에만 엔티티를 사용할 수 있다.

굉장히 짧은 시간에 스위프트는 접근수준의 갯수가 세개에서 다섯개로 두배가까이 뛰었고, 이전의 두 키워드의 의미를 고쳤다. 나는 경험한 프로그래머들이 드것들의 차이를 설명하고, 적절한 사용을 글에 담으려고 노력하는 모습을 보아왔다. 초보자에게 모나드(monads)를 설명하는게 접근제어수준을 설명하는것보다 쉬울땨 뭔가 잘못되었다는것을 알 수 있을 것이다.

단계적 공개의 철학으로 돌아와서, 이 접근수준의 어떤것이 계속 우리에게 필요한것으로 고려될까? 위에서 말한 이유료 open은 없앨 수 있다. internal도 디폴트고 명시적으로 할 필요가 없으므로 없앨 수 있다. 매일 사용하고 일반적인 것으로 public, fileprivate, private이 남았다. 이전보다 더 복잡하게 동작하게하는, 키워드 하나가 늘었다.

fileprivate을 살리거나, 혹은 커다란 타협(compromise)
최근에 private 접근수준을 고치자는 SE-0159프로포절은 SE-0025 변경을 되돌리자는 것으로 되었다. 이것은 fileprivate를 없애고 (스위프트1, 스위프트2의) private 원래 의미로 복구하자는 것이다. 왜이렇게 됐을까? 쉽게 말하면, fileprivate가 꽤 자주 사용되어서 단계적 공개를 부셨다. 새로운 private의 맴버 타입은 같은 파일안에 익스텐션으로 정의되있더라도 더이상 접근할 수 없게되므로, 근본적인 스위프트의 확장-지향 스타일을 망가뜨린다. 이 프로포절은 SE-0025만큼 논쟁적이고 열혈하게 검토되었다. 아이러니하게(혹은 우연히?) 거의 일년만에 SE-0025에대한 마지막 결정 후, 접근제어가 바뀌지 않는 상태로 놔두기로하여 거절되었다고 발표했다. 이 프로포절은 스위프트 4의 소스 안정성에서 너무 많은 영향을 주는게 중요했기때문에 받아드려질 수 없었다. 나도 이런 해결하기 어려운점에는 동의한다.

그러나 SE-0025가 예상의 결과로 되지 않은 접근제어 이휴가 있음은 명확하고, 이것을 어떻게 해결할지에대한 스위프트 커뮤니티에서의 불일치는 존재한다. 코어 팀도 잘 알고있다. Doug Gregor은 다행히도 절충안을 찾고 당장을위해, 아마도 좋은 이유료 스위프트의 접근제어 이야기를 진정시키려고 새로운 토론을 시작했다.

특별히 이 설계는 타입 "X"나 익스탠션으로 정의된 "private" 맴버가 어디서부터 접근할 수 있는지의 것이다.
  • 같은 파일안에 "X"라는 익스텐션
  • 같은 파일에 나타난 것이라면, "X"의 정의
  • 같은 파일안에 나타난 위의 것중 하나의 감싸진 타입(혹은 틱스텐션된 것)
이 설계는 여러 명확한 이점을 가진다.
  • private는 보기에 "전체 모듈보다 작다"는 올바른 디폴트가 되고, 여러 익스텐션에서 타입 정의를 나누는 스위프트 코딩스타일로서 잘 정리된다.
  • fileprivate는 현재 유스케이스로 남지만, 이제 더 작게 사용되는데, 이것은 몇몇 이점을 가진다.
    • 스위프트를 받치는 "단계적 공개" 철학에 잘 맞는다. 여러분은 fileprivate에대해 매우기전까지 당분간은 public, internal, private를 사용할 수 있다(주의: 이것이 SE-0025의 진실이 될것이라 생각하지만 명백하게 틀렸었다)
    • fileprivate가 나타나면, 같은 파일안에 다른 타입들 사이에 재미있는 엮임이 있다는 뜻이다. 이것은 잠재적으로 우리가 대강 사용하고 훑어보기 보다는 fileprivate가 유용한 경고를 해주어서 익스텐션안에 구현을 분리할 수 있게되나.
  • private는 다른 프로그래밍 언어와 비슷하게 정리되었는데, 이로하여금 스위프트로 넘어오는 프로그래머들에게 도움이 될것이다. 스위프트의 높은 익스텐션 사용때문에 약간의 스위프트 트위스트로, private를 만나게되면 그들이 생각하는 것과 비슷하게 생각할 것이다. (When they reach for private, they’re likely to get something similar to what they expect—with a little Swift twist due to Swift’s heavy use of extensions.)
  • private에서 접근제약을 느슨하게 하는것은 현재 코드를 고치지 않아도 될것 같다.

몇몇 단점도 있어보인다.
  • 현재 사전적-스코프의 private 접근재어에 의존하여 패턴을 사용하고있는 개발자들은 새로운 private 해석이 부적절한 제약이될것으로 보인다.
  • 스위프트의 접근제어는 "전체저으로 사전적인(entirely lexical)"것에서 "부분적으로 사전적이고(partly lexical) 부분적으로 타입기반인(partly type-based)" 것으로 갈것인데, 이것이 더 복잡하게 보일수 있다.

궁극적으로 나는 스위프트에 fileprivateopen 접근수준이 나타나는 변경에는 유감스럽다. 이런 변화들 모두 되돌릴수 있기를 바라고, 대신 Swift theme의 부분으로서 접착력있게 접근제어를 수정하는 방법을 고려하면 좋겠다. Doug이 말한것처럼, fileprivate가 드물게 사용될거라는 가정은 완전히 틀렸었다. 이것은 private의 사전적 스코핑을 부순 익스텐션의 주요 결과이다.

SE-0025요청된 수정은 익스텐션과 새로운 동작의 private의 잠재적 스코핑 이슈에 가볍게 알려놓았으나, 이런 암시는 메일링리스트에 넓게 의논되지 않고, 실제로 스위프트 3이 사용될때까지 개발자들에게 완전히 알리지도 못했다. 되돌아가보면, 이것은 메이저한 착오였다. 이것은 내 타입과 익스텐션을 어떻게 설계하는지 때문에 모든곳에서 fileprivate를 사용해야한다는 것을 알게되었을때, 틀림없이 나를 기습하려는 것이었다. 내 경험상, 새로운 privatefileprivate는 실제 문제에대한 솔루션이라기보다는 부담이다. 타입에서 익스텐션이 기능을 구성하고있는 곳에서는, private의 사전적 스코핑 제약이 이상적이고 규약을 잘 지키는 스위프트가 되려는 마음을 무너뜨리게 만든다. 프로토콜 익스텐션은 이런 망가짐의 징후를 증폭시킨다.

Doug이 잡아놓은 아웃라인인 코어팀의 프로퍼절은 좋은 절충안이다. 이 글을 발행하기전에, David Hart는 이것을위한 Type-based Private Access Level이라는 이름의 프로퍼절 초안으로 풀리퀘스트를 열었다. 이것이 받아들여지고 구현되면 좋겠다. 이것이 스위프트 접근제어 시스템에을 더 복잡하게 할지라도, 많은 복잡성은 세부 구현 뒷편의 이야기이다. 사용자 관점에서 privatefileprivate 수정자는 이것에대해 설명하고 설득하는데에 더 쉽게들린다. SE-0025에 정의된 실제 private 사용 이전에, 나는 많은 스위프트 사용자가 private 맴버를 익스텐션에서 접근가능한 것으로 기대했을것이라 생각한다. Doug이 설명한 "부분적으로 사전적이고 부분적으로 타입기반인" 접근제어의 이점은 명료함이고, 내 생각엔 이것들이 그 결점보다 중요하다.

우리는 분명 최적의 위치에 있지 않다. 이것은 이상에서 멀어졌으나, 실제 문제를 해결한다. 위의 제안이 구현되면, 비록 우리 뒤에 깊이 남긴 발자국이 남겠지만, 우리스스로 그려놓은 막다른길로부터 탈출할 수 있다. 많은 사용자와 많은 시간동안 publicprivate만 필요료하게될 그런 상태로 되돌아가게 될것이다. (internal은 여전히 디폴트인데, 타이핑할 필요 없다.) fileprivate를 사용하는것은 드물고 눈에띄게 될것인데, 아마도 결국 나쁜 방법으로 고려될것이다.

기회비용
이번 스위프트 3.0 릴리즈에서 스위프트 커뮤니티가 많은것을 배웠을거라고 생각한다. 접근제어대해 단지 토론하고 휘젓는것 뿐만 아니라, 전반적인 스위프트 에볼루션 프로세스에대해서도 토론해야한다. 오늘날 우리가 있는 이곳에 어떻에 도달했는지 그들의 결과물로서, 우리는 앞으로 나아가는데 마음속에 담아두고, 프로퍼절을 계속 눈여겨봐얀다. 이 프로그래밍 언어로 붕엇을 하고 싶은가? 언어의 개선사항에서 무엇이 우선시되고 무엇이 연기될까? 현재 스위프트 배포 테마에 여러분의 프로퍼절이 적절한가?

스위프트 에볼루션은 무엇이든 되지만 값싸다. 몇몇은 능동적 위험(actively harmful) 이라 생각한다. 모든 연기처럼 모든 변화에는 비용이 있다. 몇몇 변화는 명백하게 비싼반면 다른것들은 더 미묘하다. 스위프트3은 대단한 비용과함께 도착했다(완전히 다른 목표(이 차이를 보자)과 어마어마하게 고통스러운 마이그레이션) 그러나 이것들은 단지 실제 비용들이었고, 그 변화의 결과물이 만들어졌다.

아마도 고려해야할 더 중요한 것은 만들어지지 않은 변화들이다. 각 스위프트 릴리즈에대한 기회비용은 우리가 버리기로 결정한 변화의 가치이다. (이것을 구현되지 않은 모든것들의 가치라 한다) 여기에는 버그를 고친다던지, 컴파일타임 이슈를 해결한다던지, 런타임 성능을 개선한다던지, 전반적인 안정성을 증가한다던지와같은 작업까지, 주요 기능을 포함한다. 이런 일이 일어나지 않았었다고 말하지 않았다, 그들은 틀림없이 그렇게 했었다. 그러나 많은 시간이 스위프트 에볼루션 프로포절에 쓰였고, 몇몇은 지나고보니 명백하게 연기되어왔을 것이다.

어떤것도 스위프트 커뮤니티가 잘못했다고 뜻하지 않는다. 단지 어떻게 하냐의 문제이다. 우리는 코어팀까지 포함해서 모두 배우는 중이다. 다행히도 코어팀은 스위프트 4 프로포절에대해 더 엄격하고 더 생각을 많이하는것은 틀림없으므로, 이런 상황이 다시 찾아올것이라 염려한다. 그러나 이것은 소프트웨어 개발이다. 여기에는 항상 트레이드오프(등가교환)가 있다.




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

,

제목 : Swift4 Release Process

이 포스팅은 Swift4의 목표, 배포과정, 산정된 스케줄에대해 이야기한다.

Swift4는 2017년 가을에 메이저 배포를 완료할 것으로 보인다. 이 언어에서 바이너리 안정성을 필요로하는 필수의 기능 작업들을 구현하면서, Swift3 코드에대한 소스 안정성을 제공하는 것을 중심으로 돌아가고있다. 이것은 핵심 언어와 표준 라이브러리를 증진시키는 중요한 것을 포함할 것이다. 특히 제네릭 체계와 String 타입의 큰 변화에 대해 말이다. 더 자세한 내용은 Swift Evolution 페이지에서 확인할 수 있다.

소스 호환성
Swift4 컴파일러는 -swift-version 3 모드와 -swift-version 4 모드 두가지를 제공한다.

Swift Version 3 모드
-swift-version 3 모드는 현재 코드의 디폴트이다. 이 모드로 할때 강력한 목표는 다음과 같다. Swift3.1로 만들어진 막대한 소스들을 계속해서 Swift4로 만들게 하는 것이다. 한편, 예외가 있는데, 한번에 받아드리지 못한 코드를 거절하기위해 발생한 버그를 고치는 경우이다. 실제로는 이런 경우가 상대적으로 드물 것으로 보인다.

여러분의 코드가 Swift3.1 컴파일러로는 컴파일 되었는데 Swift4 컴파일러로는 의도치않은 거절을 당한다면 버그리포트를 보자.

Swift Version 4 모드
-swift-version 4 모드는 이 배포에서 새로운 것들을 가능하게 해주고 파괴적인 변화를 가능하게하는 모드이다. 주목할만한 중요한점은 String API의 철저한 정비이다. 핵심은 API의 인간공학의 증진과 그 퍼포먼스 증진에 있다.

이 변화들은 기존의 소스를 바꾸게 만들고 새 API를 사용하기위해 현재 코드를 마이그레이션 해야할 것이다.

코드 마이그레이션 관점에서 보면 그 부담의 크기가 2.3에서 3.0으로 넘길때보다 3.0에서 4로 갈때가 훨씬 작을 것이다.

다른 언어로 코드 합치기 모드
의도했던 설계는, 다중 Swift 타겟으로 한 Xcode 프로젝트처럼, 다중 Swift 모듈을 담은 프로젝트가 모듈의 (타겟) 레벨마다 특정 Swift 언어 모드로 적용할 수 있게 하고, 컴파일된 같은 바이너리 안에서 자유롭게 상호 소통할 수 있게 하는 것이다. 타겟이 같은 컴파일러로 컴파일 되었을 때만 바이너리 레벨에서 이런 상호 소통이 가능하다는 점을 잊지말자.

상호소통이 가능해지면 어떤것이 가능한지 예시들을 보자.
  • Xcode에서 앱 타겟은 Swfit4로 작성하는데(-swift-version 4) 여러 프레임워크는 개별의 Swift3으로 작성된 것을 사용할 수 있다(-swift-version 3).
  • Swift4로 작성된 Swift 패키지(-swift-version 4)는 현재 페키지 소스를 Swift4로 업데이트하지 않고서도 Swift3으로 작성된 패키지인채로 사용할 수 있다.

전체적으로는 이런 규약이 점차 Swift3코드를 Swift4로 마이그레이션 시키도록 해줄 것이다(한번에 한 타겟이나 한 패키지씩).

Swift 배포에서 소스 호환성의 더 자세한 계획을 보고싶다면 Swift-evolution 메일링 리스트의 이 스레드에서 확인할 수 있다.

Swift4의 스넵샷
Swift3.1의 경우처럼, Swift4도 매일 다운받을 수 있는 배포 브랜치의 스넵샷이 있을 것이다. 스넵샷은 연속적인 통합 테스트의 부분으로서 만들어지 질 것이다. 다운로드가능한 스넵샷의 주기는 더 빈번해질 것이다. 테스트가 통과되었다면 스넵샷은 매일 찍힐 것이다.

Swift4로 바꾸기
마지막 브랜치 날짜 전까지 현재 메인으로 개발되는 모든 변경사항들(master 브랜치)은 배포 관리자가 발표한다. 아마 2017년 초여름에 될 것으로 보인다. 그 시점 이후, 정해진 사항만 그 기간에 "만들" 것이고, 중요한 수정사항은 swift-4.0-branch로 갈 것이며, master를 개발하여 다음 배포를 준비할 것이다.

브랜치들
  • master : swift-llvm, swift-clang, swift-lldb를 제외하고 Swift4 개발은 master에서 일어난다. master에서 일어나는 모든 변경사항은 마지막 브랜치 날짜까지 마지막 Swift4 배포의 부분으로 들어갈 것이다. 그 시점에서 master는 다음 배포에대한 개발을 따라간다.
  • swift-4.0-branch : Swift4를 위한 배포 관리는 swift-4.0-branch에서 일어난다. 모든 Swift4의 스넵샷은 이 브랜치로부터 만들어지고, 또한 Swift4는 이 브렌치로부터 GM이 된다.
계획상, master는 마지막 브랜치 날짜 전까지 약 2주에 한번꼴로 swift-4.0-branch에 머지(merge)된다. 2주라는 시기에 master 브랜치와 그 보조의 배포 브랜치 개발 사이의 버퍼 역할을 한다. 변경사항들은 (풀 리퀘스트를 통해)신중하게 골라져서 master의 머지들 사이에 swift-4.0-brach로 들어간다.

이 계획의 주목할만한 예외는 swift-package-manager이다. 이것은 매일 master 브랜치에서 swift-4.0-brach로 머지될 것이다.

Swift4로 바꾸는 철학
  • Swift3.1의 소스 호환성은 -swift-version 3모드에서 가장 높은 우선순위이다.
  • Swift4의 보장으로서는 배포의 핵심 목표를 맞추는 변경사항만이 고려될 것이다.
  • 언어와 API에대한 모든 Swift4의 변화는 Swift Evolution 절차에따라 진행될 것이고, 여기에는 배포에대해 어느 범위안에 무엇이 바뀔지 그 기준들을 적어놓았다.
  • 배포 보장처럼 4회로 변경사항을 풀(pull)하는 기준들이 점점더 엄격해질것이다.

영향을 받는 저장소들
아래 저장소들은 Swift4 배포의 일부분으로서 소스를 따라가기위해 swift-4.0-branch 브랜치를 가질 것이다.
swift-llvmswift-clangswift-lldb 저장소는 이미 master에서 swift-4.0-brach로 나왔으며, 다시 브랜치가 나오진 않을 것임을 기억하자.

배포 관리자
배포의 전반적인 관리는 아래 사람들이 개별적으로 감독한다. 배포의 의견으 모아지고, 제한하는 사람(strictoer)이 변경사항을 조정하는게 Swift4 배포에 영향을 줄 때, 이 사람들이 발표할것이다.

배포 관리 과정에 대해 궁금한점이 있다면 마음편하게 swift-dev로 메일을 보내거나 Ted Kremenek로 직접 메일을 보내달라.

릴리즈 브랜치를 위한 풀리퀘스트
릴리즈 브랜치에서 적용시킬 변경사항을 담은 모든 풀리퀘스트는 아래 정보를 담아야한다.
  • 설명(Explanation) : 고쳐진 이슈나 개선된 것에대한 설명. 간단해도 되지만 명확해야한다.
  • 범위(Scope) : 이 변경사항이 주는 영향/중요성의 판단. 예를들어 이 변경사항은 소스를 고쳐야하는 언어 변경사항이다 등..
  • SR 이슈 : SR은 bugs.swift.org에서 이슈/개선점을 고치는/구별하는 변경사항일때이다.
  • 위험성(Risk) : 이 변경사항이 배포에 줄 수 있는 특정 위험성은 무엇인가?
  • 테스트 : 이 변경사항의 영향을 검증하기위해 어떤 특정 테스트를 하였고 나중에 어떤 것이 더 필요한가?
영향을 줄 수 있는 컴포넌트를위해 한명 혹은 그 이상의 코드 소유자들이 이 변경사항을 검토해야한다. 기술적 검토는 코드 소유자로부터 위임받거나, 아니면 적절하거나 유용하다고 간주되게 요청될 수 있다.

swift-4.0-brach로 가는 모든 변경사항(master에서 자동으로 머지된 바깥의 변경사항들)은 반드시 해당 배포 관리자가 승인한 풀리퀘스트를 거처야한다.



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

    ,

    작년에 나는 Swift String Cheat Sheet라는 글을 썼는데, 이 글은 Swift 표준 라이브러리에서 더 복잡한 API중 하나를 어떻게 사용하는지 기억나게 해주었다. 이번 Swift3에서는 중요한 변화를 겪으면서 코드 마이그레이션을 힘들게 만들었따. 이것은 부분적으로 API 네이밍 가이드라인이 새로 바뀌면서, 컬렉션, 인덱스, 범위(Range)의 새로운 모델이 적용되었기 때문이기도 했다.

    이 글은 Swift3을 위해 업데이트한 Swift Playground에 필요한 것들을 메모한 것이다.

    좋은 리네이밍
    표준 라이브러이에서 새로운 API 가이드라인을 적용시키면 사용하고 있던 String에서 많은 프로퍼티와 메소드를 바꿔야한다. Xcode에서 이 작업을 어느정도 대신 해주기 때문에 그 모든 바뀐점에대해 언급하진 않겠다. 아래에는 일반적으로 바뀐것에 대한 방법을 알려준다.

    문자열 초기화
    표준 라이브러리는 String 생성자를 init(count: repeatedValue)에서 init(count: repeatedValue)로 바뀌었다. repeatedValue는 Charater 대신에 String으로 바뀌었는데, 더 유연하게 되었다.

    upper/lower case로 변환하기
    uppercaseString과 lowercaseString 프로퍼티가 이제 uppercased()와 lowercased() 함수로 바뀌었다.

    조금 더 있다가 다른 바뀐 이름에대해 다룰것이다.

    인덱스를 사용하여 컬렉션을 탐색하기
    Swift3에 들어오면서 String에 가장 큰 영향을 준 변화 중 하나는 컬렉션과 인덱스의 새로운 모델이다. 고쳐 쓰기위해 String의 요소에 직접 접근할 수 없고 대신에 컬렉션에 있는 인덱스를 사용해야한다.

    Swift3에서 각 컬렉션의 startIndex와 endIndex 프로퍼티는 바뀌지 않았다.

    character에 있는 것을 원할때 character 프로퍼티를 생략할 수도 있따.

    인덱스로 문자열을 탐색할 수 있도록 바뀌었다. 이제 successor(), predecessor(), advancedBy(n) 함수들은 없어졌다.

    Swift3에서는 이제 같은 결과를 얻기 위해 index(after:), index(before:), index(_: offsetBy:)를 사용한다.

    또한 end 인덱스를 넘어가버릴때 에러를 피하기위해 offset 한계를 정할 수도 있다. index(_: offsetBy: limitedBy:) 함수는 너무 멀리까지 가버리면 nil을 반환하는 옵셔널 반환 함수이다.

    첫번째로 일치하는 요소(아래는 character이다)의 인덱스를 찾는다.

    마지막으로는, 두 인덱스 사이의 거리를 계산해주는 메소드 이름이 바뀌었다.

    범위(Range) 사용하기
    범위는 Swift3에서 바뀌었는데, character에 시작 인덱스(lower bound)와 끝 인덱스(upper bound)를 가지고 있다고 가정하자.

    upper와 lower 바운드로부터 범위를 생성하기 위한 생성자 전체

    ..<와 ... 연산자를 사용하면 더 쉽게 생성할 수 있다.

    하위 문자열에 일치하는 문자가 있는지 확인하고 범위를 반환한다.

    Playground
    여러분은 전체적으로 업데이트된 playground 내 예제 코드를 저장소에서 확인할 수 있다. 또한 이전에 작성한 포스팅(영문)도 업데이트 되었다.

    더 읽을거리



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

    ,

    Swift로 깨끗한 코드를 짜려 할 때, Swift API Design Guidelines은 매우 중요한 것 중 하나이다. 이것을 본 적이 있는가? 사실 나는 애플의 WWCD 비디오를 보기 전까지 이것을 본 적이 없었다. WWDC 영상을 기다리면서 내 직장 동료와 이것저것 이야기 했는데, 영상이 눈에 잘 안들어올 여지가 있었다. 내가 직접 작업하는 것 말고는 다른사람이 사용하기 위해 "프레임워크" 형태의 코드를 작성하진 않는 사람 중 한명으로서, 나에게 이 영상의 설명과 직접 연관되있지 않을것이라 생각했다.

    Swift3은 명확하고 정교한 Swift 코드를 위해 고유의 특징을 만들어 담은 새 API 설계 가이드라인을 소개한다. 이 이야기는 Swift API 가이드라인 너머에 있는 철학을 탐구하고 Swift 표준 라이브러리, Cocoa, Cocoa Touch API를 통한 그 응용프로그림을 탐험할 것이다. 어떻게 이 API 변형이 여러분 Swift 코드에 영향을 줄 수 있는지 확인해보고, 어떻게 Swift3으로 부드럽게 변형할 수 있는지 배워보자. Swift3이 어떻게 Objective-C API를 불러오는지, 어떻게 기존의 Objective-C 라이브러리를 Swift 인터페이스로 만드는지 배워보자.

    비디오를 보고 나서 Swift.org에서 실제 설계 가이드라인을 읽어 보았는데, 의미있었고 여러분도 한번 읽어보기를 추천한다.

    Swift API 설계 가이드라인은 여러분의 코드에서 변수, 파라미터, 메소드의 네이밍의 "단어"를 어떻게 만들어낼 것인지에대한 설명의 메뉴얼이다. 이것은 여러분의 코드에서 단어들을 어떻게 잘 조합할 수 있는지 연관된 문서를 제공하면서 이야기한다. Swift API 설계 가이드라인과 친해질수록 커뮤니티의 코드와 일관성을 가지게 될 것이며, 애플이나 Swift팀의 프레임워크에서 만들어진 API이다(원문: the APIs that are being created within the frameworks right out of Apple and the Swift team). 이것은 윈/윈의 방법이다.

    아래는 내가 뽑은 Swift API 설계 가이드라인의 중요 포인트이다.

    "ed/ing" 규칙
    WWDC 영상의 발표자는 이 규칙은 "ed/ing" 규칙이라고 말했다. Swift.org 문서에서는 strive-for-fluent-usage">String for Fluent Usage 섹션에서 그 사이드 이펙트에의한 함수와 메소드 이름으로서 소개되어있었다. 가이드라인에서는 "가변의 메소드가 종종 유사한 의미에서 불변 변형을 가지는데 이때는 그 자리에서 인스턴스를 갱신하는 것 보다는 새로운 값을 반환하는게 낫다"고 한다. ed/ing 규칙에 따라 불변 버전 뒤에 붙인인다. 아래 예시이다.

    가변(Mutating)
    x.sort()
    x.append(y)
    불변(Nonmutating)
    z = x.sorted() 
    z = x.appending(y)

    필요없는 단어는 생략하기
    Objective-C로부터 남아있는 나쁜 습관이 하나 있다면, 장황한 메소드 이름인데, 특히 반복적으로 사용되는 단어들이다. 예를들어 Objective-C의 NSMutableArray 클래스 문서를 열어 "Object"가 몇번 나오는지 한번 세어보자. 이런 메소드는 Objective-C Foundation API 전반에 걸쳐 나타난다.
    - (void)addObject:(ObjectType)anObject; 
    - (void)removeObject:(ObjectType)anObject;
    NSMutableArray의 클래스 문서에 "Object"라는 단어가 몇번이나 들어갔는지 세어보아라. 결과적으로 내가 쓴 코드에도 그런식으로 하게 만든다.

    반면 Swift에는 이런것이 없다. Swift 창시자들은 간결함과 명확성의 조화를 위해 힘쓰고 있는 중이다.

    Swift 코드가 너무 생략되버릴 수 있는데, 작은 단어 갯수로 코드를 최소화시키는 것이 그 목표는 아니다. 너무 짧은 Swift 코드가 있는 곳에는 강타입 시스템의 부작용이다...

    이 섹션의 마지막 부분과 연관이 있다. 강타입 시스템으로서 NSMutableArray API를 아래처럼 다시 설계해볼 수 있다.


    func add(_ anObject: AnyObject) 
    func remove(_ anObject: AnyObject)

    메소드 이름에서 얼마나 "Object"라는 단어가 없어졌는지 보라. 이것은 가이드라인에서 말하는 "불필요한 단어 생략"의 결과물이다. 이렇게 하는 이유는 이 API 사용자들이 파라미터에 정의된 타입으로만 오브젝트를 메소드로 보내기 위해 Swift의 강타입 시스템에 의존할 수 있기 때문이다. 이것은 Swift API 디자인 가이드라인 안에서 얘기하는, 불필요한 단어 생략을 통해 더 명료한 코드를 만드는 하나의 예시일 뿐이다. 이제 이것을 알았다면 가이드라인을 읽으면서 그것들을 여러분의 API 설계에까지 적용시켜볼 수 있다.

    또한 Swift API 설계 가이드라인에서 변수, 파라미터, 연관타입, 네이밍에 해당하는 규칙들과 약타입 정보에 대한 보장 부분을 확인해보아라. 여기에 자세한 예제가 있지는 않지만, 이 가이드라인에서는 어떻게 필요없는 단어를 생략하여 명료함을 달성할 수 있는지에대한 좋은 안내서를 제공한다. 단 명료함을 넘어 축약이 되지 않도록 조심하자. WWDC 발표자가 언급한 것처럼 API는 꽤 간결하기 때문에 API 문서가 자꾸 바뀌길 원하지는 않을 것이다.

    문법적인 영어 구(Grammatical English Phrases)
    메소드와 함수의 이름은 문법적인 영어의 구처럼 읽을 수 있어야 한다. 나는 이 부분을 굉장히 좋아하는데, 자바 프로그래머로서 암흑기로부터 나를 끄집어내준 기분이 든다. 이것은 Swift API 설계 가이드라인의 strive-for-fluent-usage">String for Fluent Usage 섹션에서 이야기하고있다. 메소드와 파라미터 이름이 옳바른지 확인해보려면 코드를 소리내서 읽어보면 된다. "이것이 영문법적으로 옳바른가? 회화식으로 말할 수 있는가?" 이 질문에 대답이 그렇다면 아마 좋은 API 일것이다. 그렇지 않으면 다시 한번 생각해보아라. 여기 문법적으로 이해하기 쉬운 Swift.org에서 말하는 좋은 API 설계의 예시이다.
    x.insert(y, at: z) // “x, insert y at z” 
    x.subViews(havingColor: y) // “x's subviews having color y” 
    x.capitalizingNouns() // “x, capitalizing nouns”
    나쁜 설계는 소리내서 읽기 힘들다.
    x.insert(y, position: z) 
    x.subViews(color: y)
    x.nounCapitalize()

    다른 멋진 것들
    문서
    내가 전문적으로 사용해본 프로그래밍 언어 중에 가이드라인이 이렇게나 잘 정의된 코드 문서는 본적이 없다. Swift API  설계 가이드라인의 fundamentals">Fundamentals 섹션 상단에 있다. Swift 코드를 위한 문서를 작성할 때 고려해야하는 짧고 친절한 요점을 제공한다. 또한 만들어 놓은 문서를 Xcode가 어떻게 보여줄지와 함께 어떻게 그것과 일관된 주석을 달 수 있는지도 알려준다.

    컨벤션(약속)
    컨벤션 섹션은 매우 마음에 든다. 빅오(Big-Oh) 문서를 만들때나, 변수, 메소드 이름을 정하는 것들로부터 모든 것을 분명하게 설명해주는 1회성 코드들이 여기에 있다.

    요약
    나는 여러분에게 Swift.org에있는 Swift API 설계 가이드라인을 확인하라고 강요할 순 없다. 그래도 오늘 한 것이 여러분이 Swift 코드를 더 좋게 만드는데에 직접 행동으로 옮겨볼 수 있는 방법중 하나라는 생각이 든다. 가이드라인은 간단하고 깔끔하며 이해하기 쉽도록 배려해 놓았다. 여러분의 코드에 일관성이 생기는 것 뿐만 아니라 커뮤니티 어디에서나 쓰일 수 있는 일관성도 갖추게 될것이다. 이것을 누가 바라지 않겠는가?

    즐거운 클리닝하기 바란다. 




    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

    ,