'Swift와 iOS'에 해당하는 글 94건

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

,
제목: Pretty much every way to assign optionals

Non-옵셔널을 저장하거나 옵셔널을 옵셔널에 저장하기

기본적인 것이다. 로켓과학처럼 복잡한 것이 아니다.
optItem = 5 // optItem is now .some(5)
optItem = optValue // optItem is whatever optValue is
요약
사용빈도: 모든 경우
터무니없는 접근법인가: 전혀 그렇지 않다.

옵셔널을 non-옵셔널에 저장하기
많은 함수와 메소드들이 옵셔널 값을 반환한다. throw와 함께 try?를 사용할때도 옵셔널 값을 반환한다. 여러분은 종종 그 결과를 non-옵셔널 변수 혹은 프로퍼티로 담아둬야 할 때가 있을 것이다.

이렇게하기위해 nil을 테스트하고 어떤 non-nil 결과의 언랩핑된 결과를 저장한다. 아래에 몇가지 방법이 있다.

조건부의 바인딩
if let을 사용하여 조건부로 옵셔널을 바인딩하고 대입을 실행할 수 있다.
if let optItem = optItem { 
    item = optItem // if optItem is non-nil
}
if let과 완전히 동일하지만 그래도 여러분이 원한다면 if case도 사용할 수 있다.
// Sugared optional
if case let optItem? = optItem { 
    item = optItem // if optItem is non-nil
}

// External let
if case let .some(optItem) = optItem {
    item = optItem // if optItem is non-nil
}

// Internal let
if case .some(let optItem) = optItem { 
    item = optItem // if optItem is non-nil 
}

Nil coalescing
만일의 대비의 값으로 nil coalescing을 사용할 수 있다.
item = optItem ?? fallbackValue
만일의 대비의 값이 필요없다면 원래 값을 사용해도 된다.
item = optItem ?? item
약간의 주의. 컴파일러가 "자신에 자신을 할당하는" 경우를 최적화하는지 잘 모르겠다. 그것을 확인하고 할당한다면 if let 방법보다 덜 효율적일 것이다.

또한 이 Itemnon-nil 일때만 갱신한다는 의도가 추가적인 if let을 쓰는게 깔끔하다고 생각하므로 효율면이나 가독성면에서 이것을 추천하진 않는다.

요약
사용빈도: 종종 사용한다
터무니없는 접근법인가: 전혀 아니다

optItemnon-nil일때만 옵셔널을 갱신하기
이번에는 현재 옵셔널이 nil일때 갱신을 스킵하는 시나리오에대해 설명하겠다. 이러한 경우 nil은 "이 옵셔널을 건드리지 마시오"라는 의미이다. 나는 이런 시나리오가 일어나지 않게 생각했다.

명확한 해결 방법이다.
if optItem != nil { optItem = newValue }
? 표시를 사용한 완전히 이상한 벙법이다.
optItem? = nonOptionalValue
?의 사용에서 rhs는 반드시 non-옵셔널이어야하고 컴파일 시점에 보장된다. 스위프트 언어의 특징중에 다소 모호한 것이다(이것을 짚어준 Joe Groff에게 감사하다).

혹은 "non-nil 수신자를 위한 테스트" 대입을 위해 이렇게 할 수 있다(바보같지만).
if let optValue = optValue {
    optItem? = optValue
}
이 예제는, rhs? 대입은 non-옵셔널이여야한다. 조건부로 옵셔널을 바인딩하는 것은 ?을 쓸 수 있게 해준다. Madalin Sava가 아래의 간단한 대안을 알려주었다. (이 섹션의 모든 것이 그렇듯) 절약 면에서는 높은 점수지만 명료하지 않은 결과에대해서는 낮은 점수를 받는다.
optItem? = optValue ?? optItem!
요약
사용빈도: 절대 사용하지 말라
터무니없는 접근법인가: 확실히 그렇다

optItemnil일때만 옵셔널을 갱신하기
이번에는 "대부분 사용하는, 한번만 세팅하기"로 설명할 수 있겠다. 옵셔널이 non-nil 값으로 한번 대입하게 되면, 그것은 다시 덮어쓰이지 않게 만든다. 대입을 하기 전에 nil을 확인하여 가장 간단하게 할 수 있다.
if optItem == nil { optItem = newValue }

혹은 오퍼레이터로 불태워도 된다. 아마 이렇게 하고 싶진 않을 것이다.

infix operator =?? : AssignmentPrecedence

// "fill the nil" operator
public func =??<T>(target: inout T?, newValue: T?) {
    if target == nil { target = newValue }
}
optItem =?? newValue

이것도 한번 보자: SE-0024

요약
사용빈도: 나는 이렇게 사용하진 않으나, 다시 대입되는 것을 막고 싶을때 유용할 것이다. 이것이 묵시적으로 언랩핑된 옵셔널의 미친 버전의 종류이나, 그것을 다시는 바꾸지 않는다고 보장함을 테스트하는 곳과, (IVO의) 모든 일련의 변화가 non-nil 값이여야하는 곳이 아닌..
터무니없는 접근법인가: 터무니없지는 않으나 일반적이지도 않다.

새로운 값이 non-nil일때만 옵셔널을 갱신하기
이 시나리오는 기본적으로 묵시적인 언랩핑된 옵셔널을 따라한 것이지만 세이프티가 추가되고 IVO 크레쉬가 없다. 항상 non-nil에대한 테스트를 하기때문에 그 값을 검증하여 한번 세팅하면 옵셔널이 다시는 nil을 반환하지 않을 것이다.

한계는 non-nil의 새 값을 갱신하고 nil 대입을 버린다.
if let newValue = newValue { optItem = newValue }

혹은 이렇게(nil-값을 위해 다시 대입하는 행위를 한다는게 낭비처럼 느껴지지 않는가?)
optItem = newValue ?? optItem
혹은 연산자로 불태워도 된다. 마찬가지로 이렇게 하고 싶지는 않을 것이다.
infix operator =? : AssignmentPrecedence

// "assign non-nil values" operator
public func =?<T>(target: inout T, newValue: T?) {
    if let newValue = newValue {
        target = unwrapped
    }
}
이것도 한번 보자 : Swift Evolution

요약
사용빈도: 내가 사용하진 않는다만 non-IVO 옵셔널을 사용하고 싶은 사람들이 원할 수도 있을 것 같다.
터무니없는 접근법인가: 그렇진 않으나 일반적이지도 않다.


'Swift와 iOS > Swift Basic' 카테고리의 다른 글

[번역]No-contiguous raw value enumeration  (372) 2017.05.13

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

,

최근에 Brennam Stehling는 내가 완전히 모르고 있었던 환상적인 Swift의 기능 하나를 알려주었다. 여러분은 각 case마다 자동으로 값이 증가하는 raw값의 열거형을 만들 수 있을 것이다.
enum MyEnumeration: Int {
   case one = 1, two, three, four
}

MyEnumeration.three.rawValue // 3
그리고 여러분은 손수 값을 지정한 raw 값 열거형을 만들 수도 있다.
enum MyEnumeration: Int {
    case one = 1, three = 3, five = 5
}
그런데 나는 이 두가지를 합쳐서 사용할 수 있다는 것을 몰랐었다!(아마 아래처럼 표준-기반 값들에는 그렇게 하지 않을것 같지만..)
enum HTTPStatusCode: Int {
    // 100 Informational
    case continue = 100
    case switchingProtocols
    case processing
    // 200 Success
    case OK = 200
    case created
    case accepted
    case nonAuthoritativeInformation
}

HTTPStatusCode.accepted.rawValue // 202
그래도 멋지지 않는가?

나는 아마 그 위치(예를들면 "1에서 시작")와함께 값으로 접근할 수 있게 지정하고, 아래에 있는 값들은 확정되지 않은 의미를 가진다. Kristina Thai는 다음과같이 언급했다. 의미있는 값을 생략하는 것은 가독성면이나 열람시에 도움이 되지 않을것이라고.



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

으로 보내주시면 됩니다.

'Swift와 iOS > Swift Basic' 카테고리의 다른 글

[번역]옵셔널을 대입하는 여러가지 방법들  (0) 2017.05.13

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

,
제목: Refactoring singleton usage in Swift

더 명료하고, 더 모듈화시키며, 더 테스트용이한 코드베이스를 위한 팁

소프트웨어 개발에서 싱글톤널리 권장되지 않고 눈쌀을 찌푸리게 만든다. 이것을 테스트하는 것은 어렵거나 불가능하고, 묵시적으로 다른 클래스에서 사용하면 여러분의 코드베이스는 헝클어져버린다. 또한 이것은 코드의 재사용도 어렵게 만든다. 오랫동안 싱글톤은 전역 변수나 가변 상태의 변형에 지나지 않다고 생각해왔다. 적어도 많은 사람들은 이 방법이 나쁜 방법이라는 것 정도는 인지하고 있다. 그러나 때때로 싱글톤은 피할수 없는, 필요한 독이기도 하다. 이것을 어떻게 깔끔하고 모듈화되고 테스트용이하게 우리 코드에 집어넣을 수 있을까?

싱글톤은 어디에나 있다
애플 플랫폼에서 싱글톤은 Cocoa와 Cocoa Touch 프레임워크 어디에나 있다. UIApplication.shared, FileManager.default, NotificationCenter.default, UserDefaults.standard, URLSession.shared 등이 있다. 또한 이 디자인 패턴은 Cocoa Core Competencies 가이드의 한 섹션으로 나와있다.

여러분이 묵시적으로 저 싱글톤(혹은 여러분의 싱글톤)을 참조할때 여러분의 코드를 변경하는데 드는 노력이 증가할 것이다. 싱글톤을 사용하는 클래스에서 싱글톤을 변경하거나 목(mock) 할 수 있는 방법이 없기 때문에 여려분의 코드를 테스트하기 어려워지거나 불가능해진다. 아래는 iOS 앱에서 일반적으로 볼 수 있는 것이다.
class MyViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()

        let currentUser = CurrentUserManager.shared.user
        if currentUser != nil {
            // do something with current user
        }

        let mySetting = UserDefaults.standard.bool(forKey: "mySetting")
        if mySetting {
            // do something with setting
        }

        URLSession.shared.dataTask(with: URL(string: "http://someResource")!) { (data, response, error) in
            // handle response
        }
    }
}
이것이 묵시적인 참조이다(클래스 안에서 직접 싱글톤을 사용한다). 더 나아지게 할 수 있는데, 스위프트로 가볍고 쉬우며 의존성을 줄이는 방법이 있다. 또한 스위프트로 우아하게 만들 수도 있다.

의존성 주입
짧게 말해, 답은 의존성 주입이다. 이 원리는 여러분의 함수와 클래스를 모든 입력이 명시적으로 되게 설계하는 방법이다. 위의 코드를 의존성 주입을 사용하여 리팩토링 한다면 아래처럼 생겼을 것이다.
class MyViewController: UIViewController {

    let userManager: CurrentUserManager
    let defaults: UserDefaults
    let urlSession: URLSession

    init(userManager: CurrentUserManager, defaults: UserDefaults, urlSession: URLSession) {
        self.userManager = userManager
        self.defaults = defaults
        self.urlSession = urlSession
        super.init(nibName: nil, bundle: nil)
    }

    override func viewDidLoad() {
        super.viewDidLoad()

        let currentUser = userManager.user
        if currentUser != nil {
            // do something with current user
        }

        let mySetting = defaults.bool(forKey: "mySetting")
        if mySetting {
            // do something with setting
        }

        urlSession.dataTask(with: URL(string: "http://someResource")!) { (data, response, error) in
            // handle response
        }
    }
}
이 클래스는 이제 더이상 모든 싱글톤에 묵시적으로 의존하지 않는다. 이것은 명시적으로 CurrentUserManager, UserDefault, URLSession에 의존한다. 그러나 이런 의존성들이 이것들이 싱글톤이라는 것을 이 클래스는 전혀 모른다. 이런 세부사항들은 기능이 바뀌지 않은채 문제가 없다. 뷰컨트롤러는 단이 이 오브젝트의 인스턴스가 존재한다는 사실만 알고있고, 호출 시점에서 싱글톤을 담아 보낼 수 있다. 다시 말하지만, 이런 세부사항은 이번 수업의 관점과는 관련이 없다.
let controller = MyViewController(userManager: .shared, defaults: .standard, urlSession: .shared)

present(controller, animated: true, completion: nil)
프로 팁: 여기서 스위프트 타입 추론이 일어난다. URLSession.shared라고 쓰는 것 대신에 .shared라고 쓸 수 있다.

만약 다른 userDefaults를 줘야한다면(예를들어 App Groups과 데이터를 공유해야한다면) 바꾸기 쉽다. 사실 이 클래스에서는 아무것도 바뀌지 않아야한다. UserDefualts.standard를 보내는 것 대신에 UserDefaults(suitName: "com.myApp")을 보내야한다.

게다가 이제 유닛테스트에서 이 클래스의 가짜나 목(mock)을 보낼 수 있다. 실제 스위프트에서 목 하는 것은 불가능하지는 않으나 더 편한 workarounds가 있다. 이것은 여러분의 코드를 구성하고 싶은대로 할 수 있다. CurrentUserManager라는 프로토콜을 쓸 수 있는데, 이것은 테스트에서 "목"할 수 있다. 또한 테스트를 위한 가짜 UserDefault에 제공할 수 있고 URLSession을 옵셔널로 만들어 테스트에선 nil을 넣으면 된다.

리팩토링 지옥
이 방법에 열중하여 이제 기술적 빚을 안고있는 여러분의 코드베이스를 해방시키고 싶을 것이다. 의존성 주입은 이상적이고 더 순수한 객체 모델을 제공하지만, 종종 달성하기 어려울때가 있다. 게다가 처음 코드를 짤 때 이것을 수용하도록 설계하지 않을 것이다.

위에 우리가 리팩토링한 것은 이제 더 모듈화되고 테스트용이하다. 그러나 여기에는 현실적인 문제가 있다. MyViewController의 생성자는 빈(init())것에 익숙한데 이제 3개의 파라미터를 받아야한다. 모든 호출시점이 바뀌게 되는 것이다. 이것을 구성하기위해 깔끔하고 적절한 방법은, 계층 위아래로 혹은 이전 뷰컨트롤러에서 지금 뷰컨트롤러까지 인스턴스를 보내도록 만드는 방법이다. 이것은 객체 그래프의 루트에서부터 모든 자식까지 데이터를 보내야 한다는 뜻이다. iOS에서는 특히 뷰컨트롤러에서 뷰컨트롤러로 데이터를 보내는 것이 머리아픈일이다. 특히 다른 사람에게 넘겨받은 코드베이스는 갑자기 많이 바뀌는 구현에서 애를 먹을 것이다. 그러면 대부분 클래스들(특히 뷰컨트롤러)의 생성자는 바뀌어야한다. 이런 바뀜은 앱 전체를 일괄적으로 리팩토링해야하는데 당신이 인지하고 있기 어려운 범주가 되버린다. 모든 코드를 고칠 수도 있겠지만 아니면 다른 클래스들은 여전히 묵시적으로 싱글톤을 참조하게 두고 몇개만 의존성 주입으로 바꾸는 것이다. 그래도 이런 부조화는 훗날에 문제를 야기할 수도 있다.

따라서 이런 리팩토링은 복잡하고, 크고, 넘겨받은 코드베이스에는 알맞지 않다. 이런 이유로 리팩토링을 하지 말고 이런 기술적 부채와 함께 살아갈것인지도 의논해보아야한다. 그러다가 몇달, 몇년뒤 멀티 사용자 기능을 지원해야할때, 계정을 바꿀때 CurrentUserManager가 동작하지 않을 수 있다. 이것은 어떻게 해결할 것인가?

여러분이 어떤 프로젝트를 시작할때부터는 클래스 설계의 첫 단계부터 이런 종류의 변경을 수용할 수 있게 만드는 방법이 있을 것이다.

디폴트 파라미터 값들
스위프트에서 내가 좋아하는 기능 중 하나는 디폴트 파라미터 값들이다. 이것은 놀랍도록 유용하고 여러분 코드에 엄청난 유연성을 가져다준다. 디폴트 파라미터로, 의존성 주입이라는 토끼구멍으로 들어가지 않고, 또 여러분의 코드베이스에서 엄청난 복잡성도 만들지 않고서, 위에서 말한 문제를 해결할 수 있다. 아마 여러분의 앱은 한명의 유저만을 가질 것이니, 위와같으 의존성 주입의 구현이 불필요하게 과하다.

싱글톤을 디폴트 파라미터로하여 사용할 수 있다.
class MyViewController: UIViewController {

    init(userManager: CurrentUserManager = .shared, defaults: UserDefaults = .standard, urlSession: URLSession = .shared) {
        self.userManager = userManager
        self.defaults = defaults
        self.urlSession = urlSession
        super.init(nibName: nil, bundle: nil)
    }
}
이제 호출 시점을 고칠 필요가 없다. 그러나 클래스 그 자체 안에서는 수많은 다양한 것들이 있다. 이제 의존성 주입을 사용하고 더이상 싱글톤을 참조하지 않는다.
let controller = MyViewController()

present(controller, animated: true, completion: nil)
이러한 변경으로 어떤 이득을 취할 수 있을까? 모든 호출 지점을 바꾸지 않고 이 패턴을 사용하기 위해 모든 클래스를 리팩토링할 수 있다. 의미로나 기능적으로나 바뀐것은 없다. 그러나 여러분의 클래스들은 이제 의존성 주입을 사용한다. 이것들은 단지 내부적으로 인스턴스를 사용하고 있다. 이것을 위에서 설명한 것처럼 테스트할 수 있고 유연하게 모듈화된 API를 유지보수 할 수 있다. (그래도 모든 퍼블릭 인터페이스는 바뀌지 않는다는 점) 본질적으로는 아무것도 바뀌지 않은채 계속 코드베이스에서 작업할 수 있을 것이다.

커스텀을 전달받는 떄가 오면, non-싱글톤 파라미터로 어떤 클래스도 변경하지 않고서 해결할 수 있다. 오직 호출 지점만 바꾸면 된다. 게다가 완전한 의존성 주입을 구현하여 계층의 위에서 애래로 모든 의존성마다 전달해가려면 그냥 드폴트 파라미터를 지우고 그 위에서 의존성으로 전달하면 된다.

필요에따라 어떤 디폴트값의 opt-in 혹은 opt-out도 할 수 있다. 아래 예제에서는 커스텀 UserDefaults를 제공하지만, CurrentUserManagerURLSession을 위해 디폴트 파라미터를 가지고 있는다.
let appGroupDefaults = UserDefaults(suiteName: "com.myApp")!

let controller = MyViewController(defaults: appGroupDefaults)

present(controller, animated: true, completion: nil)

결론
스위프트는 적은 노력으로 "partial" 종류의 의존성 주입을 만든다. 여러분의 클래스에 드폴트값으로 새 프로퍼티와 생성자 파라미터를 추가하여, 코드를 모듈화시키고 테스트하기 좋게 만들 수 있다(리팩토링에 빠지지 않고 완전한 의존성 주입을 만들 필요 없이 가능하다). 만약 프로젝트 시작 시점에 클래스를 이렇게 설계하면 코딩하면서 궁지에 몰리는 일이 더 적어질 것이다(그리고 여러분이 궁지에 몰리더라도 쉽게 빠져나올 수 있을 것이다).

여러분은 이 예제를 넘어 클래스, 구조체, 열거형, 함수 등 코드 전반에 이 개념과 설계를 적용시켜 볼 수 있다. 스위프트의 모든 함수는 디폴트 파라미터 값을 받을 수 있다. 나중에 어떤게 바뀔 수 있을지 생각해봄으로서, 적은 노력으로 변경할 수 있는 타입이나 함수를 만들어낼 수 있을 것이다.

좋은 소프트웨어를 만드는 것과 설계하는 것은 원하는 것을 쉽게 바꿀 수 있지만, 모든것을 바꾸진 않아도 되는 코드를 짠다는 의미이다. 이것이 의존성 주입 뒤에 있는 그 이유이며 스위프트의 디폴트 파라미터가 이것을 빠르고 쉽고 우아하게 해결할 수 있게 도와줄 것이다.


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

으로 보내주시면 됩니다.



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

,

모든 문제는 또다른 프로토콜을 추가하여 해결할 수 있다.

옵셔널은 멋지다. 이제까지 나는 Objective-C의 "messages to nil return nil" 버그를 너무 많이 봐왔었고 다시 그때로 돌아가고 싶지도 않다.

그러나 당신은 옵셔널이나 특정 타입의 옵셔널이 필요할 때가 종종 있다. 아래에는 내가 즐겨쓰는 그 경우들이다.

isNilOrEmpty
가끔씩 nilisEmpty==true의 차이를 신경쓰지 않아도 될 때가 있다. 먼저 _CollectionOrStringish 프로토콜을 만든다. 이 프로토콜은 비어있고, 이 타입이 isEmpty 프로퍼티를 가진다는 것을 표시하여 사용한다.
protocol _CollectionOrStringish {
    var isEmpty: Bool { get }
}

extension String: _CollectionOrStringish { }
extension Array: _CollectionOrStringish { }
extension Dictionary: _CollectionOrStringish { }
extension Set: _CollectionOrStringish { }
다음으로 Optional where Wrapped: _CollectionOrStringish를 확장(extension)하자.

extension Optional where Wrapped: _CollectionOrStringish {
    var isNilOrEmpty: Bool {
        switch self {
        case let .some(value): return value.isEmpty
        default: return true
        }
    }
}

let x: String? = ...
let y: [Int]? = ...

if x.isNilOrEmpty || y.isNilOrEmpty {
    //do stuff
}

value(or:)
이것은 아주 간단하다. 이것은 함수로 표현된 ?? nil-coalescing 연산자이다.
extension Optional {
    func value(or defaultValue: Wrapped) -> Wrapped {
        return self ?? defaultValue
    }
}
이것은 아주 코드에서 연산자의숲(operator-soup)에 들어갈때 사용하는데, 어디서 사용하든 함수형태의 것이 명확하다. 혹은 함수 파라미터로 nil-coalescing을 써야할 때 사용한다.
// operator form
if x ?? 0 > 5 {
    ...
}

// function form
if x.value(or: 0) > 5 {
    ...
}

apply(_:)
이것은 리턴 값이 없는(혹은 ()을 리턴할 수도 있다) 버전의 map이다.
extension Optional {
    /// Applies a function to `Wrapped` if not `nil`
    func apply(_ f: (Wrapped) -> Void) {
        _ = self.map(f)
    }
}

flatten()
Update: VictorPavlychoko가 댓글로 짚어주었듯, ExpressibleByNilLiteral으로 flatten을 더 간단하게 만들 수 있다!
protocol OptionalType: ExpressibleByNilLiteral { }

// Optional already has an ExpressibleByNilLiteral conformance
// so we just adopt the protocol
extension Optional: OptionalType { }

extension Optional where Wrapped: OptionalType {
    func flatten() -> Wrapped {
        switch self {
        case let .some(value):
            return value
        case .none:
            return nil
        }
    }
}
ExpressibleByNilLiteral이 적용되지 않았을 때 사용할 수 있다는 것을 설명하기 위해, 교육의 목적으로 원래의 구현을 남겨두고 있다.

원래의 flatten
이중 옵셔널로 작업해본적이 있다면 이 익스텐션의 진가를 인정할 수 있을 것이다. 여기서 몇 프로토콜과 익스텐션을 필요로 하는데, 어떤 임의의 Wrappednone 케이스를 구성하는 방법을 찾기위한 꼼수이다. 이 이야기가 와닫지 않는다면 축하한다. 당신에게 평범하고 생산적인 삶을 살 수 있는 희맘ㅇ이 아직 있다. 아래에다가 설명을 갈게 쪼게어 해놓았으니 보자.
  1. 보통 컴파일러 마법은 모든 Optional<Wrapped>들에(감쌓인것 까지도) nil을 대입하게 해주고, 그냥 모든것이 잘 동작한다.
  2. flatten()으로부터 리턴을 표현하기 위해 추상 타입 맴버(연관타입)을 제공할 수 있다.
    * 익스텐션에서 self를 참조하고 아래처럼 제네릭 파라미터를 생략할 수 있다면
    extension Optional where Wrapped: Optional
    flatten() -> Wrapped.Wrapped 이렇게도 할 수 있을 것이나, 불행히도 지금 이렇게 할 수 없다.
  3. 일반적인 옵셔널 마법은 동작하지 않아야한다. 왜냐하면 프로토콜에 익스텐션이 연관타입 WrappedType을 반환할 것이라 약속했기 때문이다. 컴파일러 마법은 nil을 .none으로 만들 수 없다.
    * 만약 WrappedType: Optional<?>으로 만든다면: 동작은 할것이나 그렇게 할 수 없을 것이다.
    * 만약 WrappedType: Self로 만든다면: 스스로 동작은 할 것이나 그렇게 할 수 없을 것이다.
    (If we could constrain WrappedType: Optional<?> it would work but we can't.
    If we could constrain WrappedType: Self it would work but we can't.)
  4. 우리 프로토콜에서 init()를 요구조건으로 추가한다. 이것으로 WrappedType의 인스턴스를 구성하여 반환하는데 사용할 수 있다.
  5. OptionalType 익스텐션에서 self=nil을 사용할 수 있다. 그 이유는, 컴파일러가 self는 옵셔널이라는 것을 알고 있기 때문에 마법이 일어난다.
protocol OptionalType {
    associatedtype WrappedType
    init()
}

extension Optional: OptionalType {
    public typealias WrappedType = Wrapped
    public init() {
        self = nil
    }
}

extension Optional where Wrapped: OptionalType {
    func flatten() -> WrappedType {
        switch self {
        case .some(let value):
            return value
        case .none:
            return WrappedType()
        }
    }
}
언급된 몇 제약들은 결국 타입 시스템에대한 여러 증진으로 드러날 수 있다.

valueOrEmpty()
한 타입이 빈 것으로 표현될때의 작은 규약이며 이것으로 nil-coalesce하여 성가시지 않게 만들 수 있다.

/// A type that has an empty value representation, as opposed to `nil`.
public protocol EmptyValueRepresentable {
    /// Provide the empty value representation of the conforming type.
    static var emptyValue: Self { get }

    /// - returns: `true` if `self` is the empty value.
    var isEmpty: Bool { get }

    /// `nil` if `self` is the empty value, `self` otherwise.
    /// An appropriate default implementation is provided automatically.
    func nilIfEmpty() -> Self?
}

extension EmptyValueRepresentable {
    public func nilIfEmpty() -> Self? {
        return self.isEmpty ? nil : self
    }
}

extension Array: EmptyValueRepresentable {
    public static var emptyValue: [Element] { return [] }
}

extension Set: EmptyValueRepresentable {
    public static var emptyValue: Set { return Set() }
}

extension Dictionary: EmptyValueRepresentable {
    public static var emptyValue: Dictionary { return [:] }
}

extension String: EmptyValueRepresentable {
    public static var emptyValue: String { return "" }
}

public extension Optional where Wrapped: EmptyValueRepresentable {
    /// If `self == nil` returns the empty value, otherwise returns the value.
    public func valueOrEmpty() -> Wrapped {
        switch self {
        case .some(let value):
            return value
        case .none:
            return Wrapped.emptyValue
        }
    }

    /// If `self == nil` returns the empty value, otherwise returns the result of
    /// mapping `transform` over the value.
    public func mapOrEmpty(_ transform: (Wrapped) -> Wrapped) -> Wrapped {
        switch self {
        case .some(let value):
            return transform(value)
        case .none:
            return Wrapped.emptyValue
        }
    }
}

descriptionOrEmpty
Swift3에서 보간법(interpolated) 문자열 옵셔널을 포함한 새로운 경고는 유용하다; 대부분 여러분은 문자열이 "(nil)"으로 표사되길 원하진 않을 것이다. 그러나 그런 동작을 원하든 아니면 그냥 빈 문자열을 원할때든 간편한 프로퍼티들이 있다.
eextension Optional { 
     var descriptionOrEmpty: String { 
         return self.flatMap(String.init(describing:)) ?? ""
     } 

     var descriptionOrNil: String { 
         return self.flatMap(String.init(describing:)) ?? "(nil)" 
     } 
} 

결론
이게 유용하고 재미있었다면 이런 형식으로 임의의 익스텐션으로 몇몇 포스팅을 해왔다.

또한 이런 동작들에대한 아주 커다란 포스팅을 준비하고 있는데, 시간이 많이 걸리는 중이다. 글을 써내려가는 중이니 기다려주길 바란다.


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

으로 보내주시면 됩니다.



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

,
제목: System Level Breakpoints in Swift


모든 훌륭한 소프트웨어 개발자들은 결국 훌륭한 소프트웨어 디버거가 되야한다. 디버깅은 크게는 브레이크포인트의 설정으로 이루어지고, 실행시간동안 앱의 임의의 상태 지점을 관찰하기위해 브레이크 포인트를 걸어 놓은 곳에 가본다. 큼직하게는 두가지 종류의 브레이크 포인트가 있다. 하나는 당신의 코드에 설정한 것이고, 다른 하나는 다른사람의 코드에 설정한 것이다.

여러분의 코드에 브레이크 포인트를 설정하는것은 간단하다. 그냥 Xcode 프로젝트의 소스코드 라인을 찾아서, 관련된 라인 옆에 홈 안의 공간을 탭하면 된다.



그러나 시스템 API에대해 브레이크 포인트를 설정하고 싶거나 소스코드를 가지고 있지 않은 라이브러리 안에 구현된 메소드에 브레이크 포인트를 설정하고 싶은 경우는 어떨까? 예를들어, 레이아웃 버그를 잡아야한다고 할때, UIView에서 애플의 고유의 내부 layoutSubviews 메소드 호출을 관찰하는데 도움이 될 수 있다. 역사적으로 Objective-C 개발자들에게는 이것이 큰 문제가 아니다. 어떤 메소드를 심볼릭하게 표현하기위해 그리고 그것을 브레이크하기위해, Xcode의 lldb 콘솔(View -> Debug Area -> Activate Console)로 들어가서 이 이름을 지정해가면서 브레이크 포인트를 지정하면 된다는 것을 안다. lldb에서 "b"라는 단축 명령어는 우리가 입력한 것으로부터 전체 이름과 매칭되는 것을 찾는 정규식이다.

(lldb) b -[UIView layoutSubviews]
Breakpoint 3: where = UIKit`-[UIView(Hierarchy) layoutSubviews], address = 0x000000010c02f642(lldb)

lldb 콘솔에서 겁을 주거나, 현재 디버그 세션보다 더 길게 붙이는 브레이크 포인트를 원하면, Xcode의 내장된 심볼릭 브레이크 포인트 인터페이스(Debug -> Breakpoints -> Create Symbolic Breakpoint)를 사용하여 같은 결과를 달성할 수 있다.


사실, 여러분의 iOS 앱에 브레이크 포인트를 걸고 앱을 돌리면 애플의 layoutSubviews 메소드에서 브레이크 포인트 안으로 실행시켜볼것이라고 생각한다 lldb 콘솔로 돌아와서 메시지를 받은 오브젝트를 확인한다.

(lldb) po $arg1
<UIClassicWindow: 0x7f8e7dd06660; frame = (0 0; 414 736); userInteractionEnabled = NO; gestureRecognizers = <NSArray: 0x60000004b7c0>; layer = <UIWindowLayer: 0x600000024260>>

이제 계속해서 심블에 브레이크를 걸어보자. 그리고 또다시 그렇게 하자. lldb 콘솔에 "po $arg1"이라고 입력하여 매번 타겟을 확인하라. 유별난 버그를 추적하는동안 이런 종류의 분석을 시행하는게 얼마나 유용한지 상상할 수 있을 것이다.

그러나 우리 플랫폼을 이제 시작한 가여운 스위프트 프로그래머들이나 스위프트 문법에만 열관하는 사람들은 어떻게할까? 애플의 문서를 읽어보거나, "-[UIView layoutSubviews]"를 해석하는게 불가능한 사람들은 "UIView.layoutSubviews"이 완전히 눈에 거슬릴 뿐만 아니라, 스위프트에게는 옳은 것일까?

불행히도 "UIView.layoutSubviews"라고 브레이크포인트를 설정하면 동작하지 않는다.

(lldb) b UIView.layoutSubviews
Breakpoint 3: no locations (pending).WARNING:  Unable to resolve breakpoint to any actual locations.
(lldb)

이것이 실패하는 이유는, UIView에 스위프트로 구현된 layoutSubviews라는 메소드가 없기 때문이다. 이것은 모두 Objective-C로 구현되있다. 사실 아주 많은 스위프트에서 쓸 수 있는 Objective-C 메소드들이 Objective-C 메시지 전송으로 바로 전달하도록 컴파일된다. 스위프트 파일에 "UIView().layoutIfNeeded()"같은 것을 입력하면, 컴파일은 될것이며, layoutIfNeeded라는 스위프트 메소드가 없기때문에 호출해도 아무일도 일어나지 않을 것이다.

이것이 스위프트로 맵핑된 모든 Cocoa 타입들에게 해당되지는 않는다. 예를들어, 모든 "Data.write(to:options:)" 호출에 브레이크를 걸고 싶다고 생각하자. 아마 "Data.write"에 브레이크 포인트를 걸어서 동작하길 원할것이다.

(lldb) b Data.write
Breakpoint 11: where = libswiftFoundation.dylib`Foundation.Data.write (to : Foundation.URL, options : __ObjC.NSData.WritingOptions) throws -> (), address = 0x00000001044edf10

그리고 된다! 이건 어떨까? 실제로 이것만 아니다. 이것은 -[NSData writeToURL:options:error:]의 길에서 libswiftFoundation을 통해 전달하는 모든 호출에 브레이크가 걸릴 것인데, Objective-C 구현을 호출하는것에 직접 잡히지는 않을 것이다. 메소드에서 모든 호출을 잡기 위해서는, 저수준(Objective-C 메소드)에서 브레이크포인트를 설정할 필요가 있다.

따라서 규칙으로, iOS나 Mac 플랫폼에서 더 좋은 디버거를 원하는 스위프트 프로그래머들은 Objective-C의것과 동일하게 스위프트 메소드를 매핑할 수 있는 능력이 필요하다. UIView.layoutSubviews같은 메소드의경우, 바로 "-[UIView layoutSubviews]"로 맵핑하지만, 많은 메소드의경우 지금처럼 간단하게 될것이다.

스위프트로 맵핑된 메소드 이름을 Objective-C로 다시 매핑하기위해서는, 많은 Foundation 클래스들이 NS 접두를 뺐다는 것을 인식하고, 스위프트 API 가이드라인을 적용하기위해 메소드 시그니처를 다시 작성한 영향이다. 예를들어 순수한 스위프트 프로그래머들은 "Data.write(to:options)"의 저수준 구현으로 브레이크 포인트를 설정하기 때문에 쉽게 유추하지 못할것이다. "NS" 접두를 붙이고, URL 파라미터를 명시적으로 넣고, 이상한 에러 파라미터를 추가한다. 이것은 보기에 옛날의 나쁜 시기에 까탈스러운 백발의 노인이 실패를 전달하는데 사용하는 것 같아 보인다.

(lldb) b -[NSData writeToURL:options:error:]
Breakpoint 13: where = Foundation`-[NSData(NSData) writeToURL:options:error:], address = 0x00000001018328c3

성공했다!

이런 Obejctive-C 메시지 시그니처와 API 규약들에대한 추가적인 지식을 개발하게 만드는 생각은 여러분을 힘겹게 만든다는 점에서, 나는 여러분의 다음 과제를 통해 얻을 약간의 수정된것을 제공한다(I offer a little hack that will likely get you through your next challenge). API가 이런 원리중 하나를 사용햐여 작성되었으면, 함수의 스위프트 이름은 거의 Objective-C 메소드 이름의 부분집합이다. 아마도 lldb의 정규식 일치 기능을 활용하여 브레이크포인트를 설정하고싶은 메소드를 0으로 만들 수 있다.

(lldb) break set -s Foundation -r Data.*write
Breakpoint 17: 8 locations.

이제 "break list"를 입력하고 lldb가 표시한 매칭 수를 보자. 그것들 중에는 스위프트로 된 (libswiftFoundation으로 이루어진) 여러 메소드가 있으나, 질문에서 타겟 메소드를 찾아야할 것이다. 사실, 여러분이 브레이크를 걸고싶은 다른 저수준의 Objective-C 메소드도 찾아봐야할 것이다.

리스트를 더 잘 관리하기위해, 주어진 Objective-C 프레임워크안에 타겟 메소드가 있다는 지식을 주고, 이름으로 특정 공유된 라이브러리에 제한된 매칭을위한 "-s" 플래그를 넣는다.

(lldb) break set -s Foundation -r Data.*write
Breakpoint 17: 8 locations.

이 브레이크포인트 사이에서 NSPageData에서 몇 거짓이 있지만, 그 목록은 모두 더 관리할 수 있다. 하나의 브레이크포인트 "17"은 하위-숫자로 식별된 매치의 모든것을 가지고 있는다. 여러분의 방법으로 브레이크포인트의 목록을 걸러내도 좋다.

(lldb) break disable 17.6 17.7 17.8
3 breakpoints disabled.
(lldb) c

Objective-C를 스위프트에 맵핑하는 애플의 방식은 스위프트 개발자를위해 더 즐거운 프로그래밍 경험을 함께 만들게 해주지만, 그 세부적인 구현을 이해하지 못하고 있거나 어떻게 동작하는지에대한 이해가 부족하게되면 엄청난 혼란으로 빠질 수 있다. 나는 이 글이 여러분의 스위프트 앱을 디버깅하는데 필요한 도구가 되었으면 좋겠고, 불가피하게 사용하는 Objective-C 코드를 더 효율적이게 썼으면 좋겠다.

Update: 나는 두가지 관련된 버그를 넣었다: Radar #31115822 스위프트 메소드 포맷에서 자동으로 매핑한 것을 Obejctive-C 메소드로 돌아오도록 해야한다, 그리고 Radar #31115942 간결한 스위프트 메소드 시그니처를 만드는것에대해 lldb를 더 직관적으로 만들도록 해야한다.



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

으로 보내주시면 됩니다.



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

,
제목: Safety In Swift


스위프트는 일반적으로 "세이프한" 언어로 불린다. 실제로 swift.orgAbout 페이지에서 이렇게 말한다.
스위프트는 세이프티, 퍼포먼스, 소프트웨어 설계 패턴의 현대적인 방법 사용을 내장한 일반 목적 프로그래밍 언어이다.
그리고
  • 세이프. 코드를 작성하는 가장 명백한 방법은 안전한 방법으로 동작할 수 있다. 정의되지않은 동작은 세이프티의 적이고, 개발자 실수들은 스프트웨어가 제품으로 되기 전에 잡힌다. 가끔 세이프티를 선택하는것이 스의프트가 엄격하게 느껴질 것이나, 장기적으로 봤을땐 명쾌하게 시간을 절약해줄 것이라 믿는다.
  • 빠름. 스위프트는 C 기반 언어(C, C++, Objective-C)를 대체하려는 목적이 있다. 이것처럼! 스위프트는 많은 작업의 퍼포먼스에서 이런 언어들과 반드시 비교된다. 또한 퍼포먼스는 단지 나중에 깨끗하게 만들어야하는 짧은 폭발적인 빠름이 아니라, 예상가능하고 일관되어야한다. 진귀한 기능을 가진 많은 언어가 있다. 빠른것은 희귀하다.
  • 표현력. 스위프트는 개발자들이 기대하는 현재의 기능과 함께, 즐겁게 사용할 수 있는 문법을 제공하기위해 십여년에서 나온 컴퓨터 사이언스의 증진의 이점이 있다. 그러나 스위프트는 아직 끝난게 아니다. 우리는 언어 증진을 탐색하고 계속해서 어떤 일이 스위프트가 더 나아지게 만드는지 포용할 것이다.

예를들어 우리가 Optional 타입과같은 것과 작업할때, 스위프트가 세이프티를 끌어올리는 것은 명확하다. 이전에는 어떤 변수들이 null이 될 수 있는지 없는지 몰랐다. 이런 새로운 널러빌리티(nullability) 정보로, 우리는 명시적으로 null 경우를 다루게 되었다. 이런 "널러빌리티" 타입으로 작업할때, 우리는 크레쉬를 선택할 수 있고, 보통 느낌표(!)를 포함한 연산자를 사용한다. 여기서 세이프티에의한 의미는 명확하다. 여러분의 위험에대해, 여러분이 잠글지 말지 정할 수 있는 안전띠 역할을 하는 것이다.

그러나 다른 경우에, 세이프티가 부족해 보인다. 한 예제를 보자. 한 딕셔너리를 가지고 있다면, 주어진 키(key)로 값을 쥐는것은 옵셔널을 반환한다.
let person: [String: String] = //...
type(of: person["name"]) // => Optional<String>
그러나 비슷하게 배열에서 하면, 옵셔널을 받지 않는다.
let users: [User] = //...
type(of: users[0]) // => User

왜 그러지 않을까? 배열은 비어있을수도 있다. 만약 users 배열이 비어있다면 프로그램은 다른 실제 선택없이 크레쉬될 수 있다. 이것은 세이프하다고 보기 힘들다. 다시 환불받고싶다!

흠, 좋다. 스위프트는 오픈 개발 프로세스니, 아마도 스위프트 에볼루션 메일링리스트에 이 변경사항을 제안할 수 있다.

안된다. 어느쪽도 하지 못할것이다. 깃헙 저장소의 스위프트-레볼루션 페이지에있는 "일반적으로 거절된" 프로포절들는이런 변경을 받아드리지 않을것이라고 말했다.
  • Array<T> 서브스크립 접근을 T?T! 대신에 T를 반환하는 것으로 만든다. 범위를 넘는 배열 접은은 로직 에러라는 사실을 정확하게 반영하기 때문에, 현재 배열의 동작은 의도적이다. 현재 동작을 바꾸는 것은 수용될 수 없는 정도로 배열 접근을 느리게할 수 있다. 이 주제는 이전에도 여러번 나왔지만 매우 받아드려지지 않을것으로 보인다.

무엇을 주는가? 진술된 이유는 이 특정 상황은 속도가 매우 중요하기 때문이라 했다. 그러나 위의 About 패이지 링크로 돌아가보면, "세이프"는 "빠름" 이전에 언어의 표현으로 목록에 나와있다. 세이프티가 속도보다 중요하기라도 한걸까?

여기엔 근본적인 논쟁이 있고, 그 해결책은 "세이프"라는 단어의 정의를 잡아야한다. 일반적인 "세이프"의 이해는 정도의 차이가 있어도 "크레쉬가 나지 않음"인 반면, 스위프트 코어 맴버들은 종종 "의도치않게 틀린 메모리에 절때 접근하지 않는것"이라는 의미로 쓴다.

이 경우, 스위프트의 배열 서브스크립션은 "세이프"이다. 배열이 절때로 할당된 범위 넘어서 메모리에있는 데이터에 접근하지 않을것이다. 메모리에 무엇이있든, 포함하지 않는 메모리에 접근하려고 하기 전에 크레쉬를 낼것이다. 같은 방법에서, 옵셔널 타입은 현존하는것으로부터 모든 클래스와 버그들(null에 접근하려는것)을 막으며, 이런 동작은 현존하는것으로부터 다른 클래스와 버그들(버퍼 오버플로우)을 막는다.

Chris Lattner가 ATP와했던 그 인터뷰의 24:39에서이런 구별을 만든것을 들어볼 수 있다.
 커뮤니티에 혼란에서 비용이라는 관점에서 보면 이해할 수 있는 유일한 방법은 우리가 세이프한 프로그래밍 언어를 만들면이다. "버그가 없다"는 것의 "세이프"가 아니라, 높은 퍼포먼스를 제공하고 프로그래밍 모델 앞으로 가는동안 메모리 세이프티 관점에서의 "세이프"이다.

아마 "메모리-세이프"는 그냥 "세이프"라는 용어보다 더 낫다. 방법은 이렇다. 어던 어플리케이션 프로그래머가 옵셔널로 돌아가는걸 좋아하는것 대신에 범위밖의 배열 접근에 트랩을 거는것을 좋아하는 반면, 모두가 유요하지 않은 데이터를 담은 변수로 계속 하는것보다 그 프로그래을 크래쉬 내는 것을 더 좋아할 수 있다는 것에 동의할 수 있다. 한 변수는 잠재적으로 버퍼 오버플로우 공격에 이용될 수 있다.

이 두번째 등가교환(버퍼 오버플로우를 허용하는것 대신 크래쉬 나는것)은 당연해 보이지만, 몇 언어들은 이 보장을 하지 않는다. C에서는 배열의 범위밖을 접근하는것이 여러분에게 정의되지 않은 동작을 할 수 있게 해주고, 어떤일이든 일어날 수 있다는 뜻이며, 우리가 사용했던 컴파일러 구현에 의존한다. 특히 프로그래머가 실수를 만들었다고 빠르게 말할 수 있을때(배열의 범위 밖 접근 같은), 그들이 옵셔널을 반환하는것 대신에 결정적으로 정크 메모리를 반환하는것 대신에 수용되는 곳에 크래쉬를 내는것을 좋게 느낀다는 것을 스위프트팀은 봐왔다.

"세이프" 정의를 사용하는 것도 "언세이프"한 API가 무엇을위해 설계되었는지 분명하게 한다. 왜냐면 그들은 직접적으로 메모리를 더럽히고, 프로그래머가 절때 유효하지않은 메모리에 접근하지 않을거라는 보장의 특별한 신경을 쓰게 만든다. 이것은 극도로 힘들고, 전문가들도 틀릴 수 있다. 이 주제에대한 글에 흥미가 있다면, 세이프한 방법으로 C를 스위프트에 연결시키는 Matt Gallagher’의 글 확인해보자.

스위프트와 그 코어팀의 "세이프"의 정의는 여러분의 생각에 100% 맞춰지지 않을 것이나, 그들은 클래스의 버그를 막아주어서 여러분같은 프로그래머들이 매일매일 그것에대해 생각하지 않아도 된다. 그 의미를 이해할때, "세이프"를 "메모리 세이프"로 대체하여 사용하면 종종 도움이 뒬 수 있다.



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

으로 보내주시면 됩니다.




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

,
제목: What are MVP and MVC and what is the difference?- StackOverflow


Q. RAD(드레그-드롭과 구성)을 넘어 볼때, 사용자 인터페이스를 만드는 방법에서 많은 툴이 지향하는 방법은 Model-View-ControllerModel-View-Presenter, Model-View-ViewModel 이 세가지 디자인 패턴으로 이해되었다. 내 질문은 세가지이다.
  1. 이 패턴이 해결할 이슈들은 무엇인가?
  2. 이것들은 어떻게 비슷한가?
  3. 이것들은 어떻게 다른가?


A.
Model-View-Presenter
MVP에서는 프레젠터가 뷰를 위한 UI 비즈니스 로직을 담고 있다. 뷰에서 나온 모든 호출은 프레젠터로 직접 델리게이트한다. 프레젠터는 뷰와 바로 분리되있고 인터페이스를통해 이야기한다. 이것은 유닛테스트에서 뷰를 목(mock) 할 수 있게 해준다. MVP의 한가지 공통된 특징은 양방향 디스패치가 되야한다는 것이다. 예를들어 누군가 "저장" 버튼을 누를때 이벤트 핸들러는 프레젠터의 "OnSave" 메소드에 델리게이트한다. 저장이 완료되면 프레젠터는 인터페이스를 통해 뷰에게 콜백하여, 뷰는 저장이 완료되었다고 표시할 수 있다.

MVP는 웹 폼(Web Form)에서 분리된 표현을 달성하기에 매우 자연스러운 패턴이되는 경향이 있다. 그 이유는 뷰가 항상 ASP.NET 런타임에의해 가장 먼저 만들어지기 때문이다. 여기서 더 다양한 종류에대해 확인할 수 있다.

두가지 주요 종류
수동적인 뷰(Passive View): 이 뷰는 가능한 멍청하고 거의 로직을 가지고 있지 않는 뷰이다. 프레젠터는 뷰와 모델에게 말을 하는 중간자 역할을 한다. 뷰와 모델은 서로 완전히 막혀있다. 모델이 이벤트를 만들어내지만, 프레젠터는 뷰를 생신하기위해 그것을 구독(subscribe)한다. 수동적인 뷰에서는 직접적인 데이터 바인딩은 없고, 프레젠터가 데이터를 셋(set) 하는데 사용되는 뷰의 세터(setter) 프로퍼티로 노출시킨다. 모든 상태는 뷰가 아닌 프레젠터에서 관리된다.
  • 장점: 최대의 테스트성; 뷰와 모델의 분리가 명확하다.
  • 단점: 모든 데이터 바인딩을 여러분 스스로 해야하는, 더 많은 일거리(예를들어 모든 세터 프로퍼티들).

감독 컨트롤러(Supervising Controller): 프레젠터가 사용자 제스처를 다룬다. 뷰는 데이터 바인딩으로 모델을 직접 바인딩한다. 이 경우, 모델을 뷰에 보내주는게 프레젠터의 일이라서 바인딩할 수 있다. 이 프레젠터는 버튼 누르기, 화면 이동 등 제스쳐를 위한 로직을 담고 있을 것이다.
  • 장점: 데이터 바인딩을 이용하여 코드의 양을 줄인다.
  • 단점: 더 낮은 테스트성(데이터 바인딩 때문에), 모델에 직접 말하기 때문에 뷰는 더 낮은 캡슐화가 된다.

Model-View-Controller
MVC에서 컨트롤러는 앱 로딩같은 어떤 액션에 반응하여, 어떤 뷰가 표시될지 결정하는 책임을 가진다. 이것은 액션이 뷰를 통해 프레젠터로 라우트한다는 부분이 MVP와 다른 점이다. MVC에서는, 뷰에서의 모든 액션이 컨트롤러에 호출하여 상호 관련이 있다. 웹에서는 각 액션이 응답할 컨트롤러가있는 다른편에서 URL 호출을 포함한다. 컨트롤러가 그 처리를 완성하면, 올바른 뷰를 돌려줄 것이다. 이런 순서는 어플리케이션의 라이프 내내 그 방법으로 계속된다.


Action in the View
     -> Call to Controller
     -> Controller Logic
      -> Controller returns the View



MVC에대해 한가지 크게 다른점은 뷰가 모델을 직접 바인딩하지 않는다는 것이다. 뷰는 간단하게 랜더링만하고 완전한 상태없는(stateless)것이된다. MVC의 구현에서 뷰는 보통 코드 뒤에서 로직이 하나도 없다. 이것은 절대적으로 필요한 MVP와 상반되는데, 그 뷰가 프레젠터에게 델리게이트하지 않으면 절때 호출되지 않을 것이기 때문이다.

Presentation Model
우리가 볼 또다른 패턴은 프레젠테이션 모델 패턴이다. 이 패턴에는 프레젠터가 없다. 대신에 뷰가 직접 프레젠테이션 모델을 바인딩한다. 그 프레젠테이션 모델은 뷰를 위해 면밀하게 만들어진 모델이다. 이 의미는 이것이 seperation-of-concern의 위배일 수 있으므로, 모델은 절때 도메인 모델일 수 없는 프로퍼티들을 호출시킨다. 이 경우, 프레젠테이션 모델은 도메인 모델을 바인딩하고, 모델에서 나오는 이벤트를 구독할 것이다. 그럼 뷰는 프레젠테이션 모델에서 나오는 이벤트를 구독하고 적절히 스스로 갱신한다. 프레젠테이션 모델은 뷰가 액션을 호출하는데 사용하는 명령을 노출시켜 놓을 수 있다. 이런 방법의 이점은, 프레젠테이션 모델이 완전히 뷰를 위한 모든 동작을 캡슐화하기 때문에, 본질적으로 코드 뒤에서 함께있는 것을 제거할 수 있다. 이 패턴은 WPF 어플리케이션에서 사용하기에 강한 후보이고, 또한 Model-View-ViewModel이라 부르기도 한다.



A. 얼마전에 이것에대해 글을 썼는데, 이 두가지 차이점을 훌륭하게 포스팅한 Todd Snyder 글 을 인용한다.

여기에는 패턴간의 핵심적인 차이가 있다.
MVP 패턴
    • 뷰가 모델에 더 느슨하게 연결되있다. 프레젠터는 모델을 뷰에 바인딩할 책임을 가진다.
    • 뷰와의 인터렉션이 인터페이스를 통하기 때문에 유닛테스트하기 더 쉽다.
    • 보통 뷰:프레젠터는 1:1로 맵핑된다. 복잡한 뷰는 여러 프레젠터를 가질 것이다.
MVC 패턴
    • 컨트롤러는 행동 기반이고, 뷰를 통해 공유될 수 있다.
    • 표시를 위해 어떤 뷰를 선택할지 결정하는 책임일 수 있다.

내가 찾은 것중에 웹에서는 최고의 설명이다.


A. 이건 디자인 패턴의 여러 종류를 과하게 간단하게 만든 그림이긴 하나, 두가지 차이를 생각하기에는 좋아보인다.

MVC
MVC

MVP
enter image description here



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

으로 보내주시면 됩니다.


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

,
제목: Service-oriented AppDelegate


보통 앱델리게이트(AppDelegate)는 거대한 클래스이다. 이것은 여러분의 앱에 대해 너무 많이 알고 있고 점점 더 커지게 된다. 이 글에서는 깔끔한 플러그인 기반 아키텍처를 만들어서 그 세부 기능으로부터 어떻게 분리해낼 수 있는지 보여줄 것이다.

우리는 올바르지 않은 방법으로 문제를 직면하고 있다. 당신의 앱델리게이트를 어떻게 관리하고 있는지 생각해보자. 내 생각엔 굉장히 거대하거나, 아니면 적어도 거기서 코딩한 것들에 대해 자신감이 있지는 않을 것 같아 보인다.

앱델리게이트의 문제는 바로 앱델리게이트가 여러분의 앱에대해 너무 많이 알고 있다는 점이다. 이것은 여러분의 의존성들을 알고있는데, 어떻게 초기화하는지, 어떻게 푸시 노티피케이션을 파싱하는지 등 많은 것을 알고 있다.

요약을 해보자면
ApplicationSercvices를 만들어낼 수 있다. 이것들은 AppDelegate 라이프사이클을 공유하고 해당 단계에 작업을 실행하는 오브젝트이다. 여러분의 앱델리게이트는 피관찰자(Observable)이고 여러분의 서비스들은 관찰자(Observer)이다. 이런 접근법은 그 서비스들로부터 앱델리게이트를 분리시켜주고 단일 책임 오브젝트를 생성해준다.

물론 많은 양의 코드를 작성하게 되므로 이 문제를 다루기위해 CocoaPod을 만들었다. PluggableAppDelegate라 부르며 여기에서 확인할 수 있다: http://github.com/fm091/PluggableApplicationDelegate

서비스를 생성해서 AppDelegate에 등록만 하면 된다. 그 결과는 여러분이 봐왔던 것 중에 가장 작고 깔끔한 AppDelegate가 될것이다.

일반적인 앱델리게이트
일반적으로, 앱델리게이트는 많은 역할을 가지고 있다.
  1. 여러분 앱의 모든 의존성을 초기화한다.
  2. 전역의 UIAppearance 값들을 초기설정한다.
  3. 푸시 노티피케이션을 다룬다.
  4. 푸시 노티피케이션을 등록한다.

그밖에 더 있을 것이다.

여기서 문제는 여러분의 앱델리게이트가 정확하게 모든 컴포넌트가 어떻게 동작하는지, 그리고 어떻게 인스턴스화하는지 알고 있기 때문에 생긴 것이다. 또한 이것은 여러분의 앱이 어떻게 생겼는지도 알 수도 있다. 그리고 푸시 노티피케이션 포멧도 알고있다. 결과적으로 생각했던것보다 너무 커져버린다.

다른 접근법
ApplicationService라 부르는 그 고유의 컴포넌트에 여러분의 의존성들을 캡슐화하여 들고 있다고 생각해보자.

모든 ApplicationService는 하위-앱델리게이트(sub-AppDelegate)이다. 이것은 앱델리게이트의 라이프사이클을 공유하고, 요청된 이벤트에서 행동을 취한다.

여러분 AppDelegate는 이벤트의 발생를 관찰하는 관찰자(observable)이 되어, 어떤 일이 일어나면 서비스에게 알려주기만 하면 된다.



어플리케이션 서비스들
어플리케이션 서비스들은 AppDelegate 라이프사이클을 공유하는 오브젝트들이다. ApplicationService는 그냥 태그한 프로토콜이다. 만약 PluggableApplicationDelegate를 사용한다면 이렇게 생겼을 것이다.
public protocol ApplicationService: UIApplicationDelegate {}
위에서 본 것처럼, UIApplicationDelegate만이지만, 더 전달력있는 이름이다.

이것을 구현할때, AppDelegate를 구현하듯이 하면 된다. 그러나 여기서 다른점은 단일 책임 요소를 코딩한다는 점이다.

아래에 구현에대한 예시가 있다.
import Foundation
import PluggableApplicationDelegate

final class LoggerApplicationService: NSObject, ApplicationService {
    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey : Any]? = nil) -> Bool {
        print("It has started!") return true
    }
    func applicationDidEnterBackground(_ application: UIApplication) {
        print("It has entered background”)
    }
}

새로운 앱델리게이트
PluggableApplicationDelegate를 상속받고 이것을 서비스에 등록함으로서, 이제 여러분의 앱델리게이트는 더 작아졌다.

아래를 확인해보자
import UIKit 
import PluggableApplicationDelegate 

@UIApplicationMain 
class AppDelegate: PluggableApplicationDelegate { 

    override var services: [ApplicationService] { 
        return [ LoggerApplicationService() ] 
    } 
}

PluggableApplicationDelegate는 너무 크다. 뭔가 일어났을때만 그 서비스에 알려준다. 그것에대한 코드를 여기서 확인할 수 있을 것이다.

의존성과 기능을 점점 더 앱에 추가하더래도 앱델리게이트는 아래처럼 깨끗하게 유지될 것이다.
import UIKit 
import PluggableApplicationDelegate 

@UIApplicationMain 
class AppDelegate: PluggableApplicationDelegate { 

    override var services: [ApplicationService] { 
        return [ 
            PushNotificationsService(), 
            AnalyticsService(), 
            CustomAppearanceService(),
            FirebaseService(), 
            FacebookSDKService() 
        ] 
    } 
}

결론
이게 다다. 여러분의 앱델리게이트를 분리시키는 간단한 방법이며, 그것이 커다란 덩어리로 되지 않게 막아준다.

내 깃헙 저장소를 꼭 확인해보기 바란다:

내가 도움이 됐던 만큼 여러분에게도 도움이 되길 바라며, 이것에대한 피드백을 꼭 받고싶다. 아래 댓글로 남겨달라

고맙다!


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

으로 보내주시면 됩니다.



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

,
제목 : Building iOS App with VIPER Architecture



이 글은 완전히 iOS VIPER 아키텍처에 관한 내용이다. 우리는 3가지 포인트를 통해 이야기해 나갈 예정이다.
  • VIPER 아키텍처란?
  • VIPER 아키텍처로 iOS 앱 만들기
  • VIPER 아키텍처의 이점들
이 표준 아키텍처는 재사용성과 테스트용이함에대해 코드를 분리시켜주는 중요한 역할을 한다. 이 아키텍처는 그 역할에 맞춰 앱 컴포넌트를 분리시키며, 이것은 seperation of concern이라 부른다.

이제 iOS를 위한 VIPER 아키텍처에대해 탐험해보자.

VIPER 아키텍처란?
VIPERView, Interactor, Presenter, Entity, Router로 구성되있다.

이 아키텍처는 단일책임원칙(링크)를 기반으로 하는데, 이것이 명확한 아키텍처로 만들어준다.
  • View : View의 책임은 사용자의 동작을 Presenter로 보내주고 Presenter가 요청하는 모든 것을 보여준다.
  • Intereactor : 이것은 비지니스 로직을 가지고있는 앱의 뼈대이다.
  • Presenter : 이것의 책임은 사용자 동작에 Interactor에서 데이터를 뽑아온 뒤, 그것을 보여주기 위해 View에 보낸다. 또한 네비게이션(화면이동)을 위해 router(혹은 wireframe)에게 물어보기도한다.
  • Entity : Interactor에서 사용하는 기본 오브젝트 모델이다.
  • Router : 이것은 어느 화면이 언제 나타날지에대한 정보를 담고 있는 네비게이션 로직이다. 보통 wireframe으로 쓰인다.

VIPER 아키텍처의 청사진


보통 VIPER 아키텍처는 큰 프로젝트에서 쓰인다. 그러나 이해를 돕기위해 이것을 위한 작은 앱을 하나 만들었다.

VIPER 아키텍처로 iOS 앱 만들기
나는 VIPER 아키텍처를 사용한 샘플 iOS 앱을 하나 만들었다.


이해를 돕기위해 프로젝트의 구조를 보자


샘플 앱의 스크린샷이다.



이 앱은 3가지 화면으로 구성된다.
  • 시작화면 : 일반적인 시작화면이다 따라서 더 설명할건 없다.
  • PostListView :화면  이 PostListView는 포스팅 목록을 가져와라고 Presenter에게 말한다. 그런 다음 Presenter는 관련 데이터를 위해 Interactor에게 접근한다. Intereactor는 로컬 데이터베이스에서 그 데이터의 사용 가능 여부를 확인해보고, 만약 데이터가 있으면 Presenter로 반환하고, Presenter는 View로 반환한다. 데이터가 로컬 데이터베이스에 없을경우 네트워크를 호출하여 데이터를 가져온 다음 Presenter에게 반환한다. 그리고 역시 이 데이터를 로컬 데이터베이스(CoreData)에 저장한다.
  • PostDetailView 화면 : 사용자가 PostViewList에 표시된 포스트를 클릭하면, PostListPresenter는 PostDetailView를 열어도 되는지 Router(PostListWireFrame)에게 물어본다. 선택된 포스팅의 세부사항이 이 화면에 나타난다.

그리고 이 프로젝트에서 대부분 클래스간의 의사소통은 정의된 프로토콜을 통해 일어난다.

이것을 완전히 이해하는데 가장 좋은 방법은 소스코드를 확인하고 구현해보는 방법일 것이다. 프로젝트를 깃으로 클론해서 빌드&런 해보자.

VIPER 아키텍처의 이점들
  • 재사용성과 테스트용이함을 위해 코드를 분리할 수 있다.
  • 그 역할에 맞춰 앱 컴포넌트를 분리할 수 있으며, 이것을 seperation of concern이라 부른다.
  • 새 기능을 추가하기 쉽다.
  • UI 로직이 비지니스 로직으로부터 떨어져있기 때문에 자연스럽게 테스트를 만들기 쉬워진다.

이게 다다. 즐거운 코딩하길 바란다 :)


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

으로 보내주시면 됩니다.



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

,
제목: Looking at Model-View-Controller in Cocoa

This article is copyright 2017 Matt Gallagher, https://cocoawithlove.com. The original English version is available here: Looking at Model-View-Controller in Cocoa. This translation is produced and hosted with permission of the original author.

애플의 문서에의하면, 코코아 어플리케이션에서 표준 패턴을 Model-View-Controller라 부른다. 그 이름에도 불구하고 이 패턴은 기존의 smalltalk-80의 Movel-View-Controller 정의와 완전히 다르다. 코코아의 앱 디자인 패턴은 실제로 원래의 Smalltalk의 용어보다 Taligent(1990년대부터 애플과 공동으로 개발해온 프로젝트)에서 만들어진 방법에 더 일반적으로 겹친다.

이 글에서는 코코아에서 주로 사용하는 앱 디자인 패턴의 뒷 배경과 약간의 이론을 보려한다. 나는 코코아의 Model-View-Controller 접근법의 주요 결점에대해 이야기해볼 것이다. 이런 결점을 해결하는데 실패한 애플의 노력과, 다음 메이저 개션으로부터 생기는 의문을 보게 될 것이다.

컨텐츠
  1. Smalltalk-80
  2. 코코아(AppKit/UIKit)
  3. Taligent
  4. 컨트롤러 문제
  5. 바인딩
  6. 새로운 무언가?
  7. 결론

Smalltalk-80
아마 UI 개발에서 널리 인용된 패턴은 Model-View-Controller(MVC)이다. 또한 많이들 잘못 인용되고 있다. 나는 MVC가 이것처럼 아무것도 아니게 되버렸다고 설명한 것을 보는데 시간가는줄 몰랐다 - Martin Fowler, GUI Architectures

나는 Martin Fowler가 사용한 정의로 위의 인용에서 Fowler가 말하고자하는바를 빠르게 알려주려한다. 코코아 앱 개발에 보통 사용되는 이 접근법은 Model-View-Controller가 아니다.

Smalltalk-80에서는 상호 소통하는 뷰가 완전히 분리된 두 오브젝트로 쪼개진다. 바로 뷰 오브젝트와 컨트롤러 오브젝트이다. 뷰 오브젝트는 화면 출력을 실행하나, 모든 클릭이나 인터렉션은 뷰 오브젝트에서 하지 않고, 대신 그 파트너인 컨트롤러 오브젝트에의해 디스패치된다. 중요하게 이해하고 넘어가야할것은 컨트롤러는 뷰를 불러오거나 셋업을 관리하지 않으며, 한 컨트롤러는 여러 뷰를 위한 동작을 다루지 않는다; 원래 Model-View-Controller 정의에서는 뷰와 컨트롤러는 간단하게 동작하고, 화면상의 하나의 조작면에서만 출력한다.

Smalltalk's version of Model-View-Controller
Smalltalk-80의 Model-View-Controller

Smalltalk-80의 Model-View-Controller의 다이어그램은 오브젝트 그래프의 중앙에 모델이 있고, 모델이 뷰나 컨트롤러와 직접 우선적으로 소통한다는 것을 보여준다.

이 명확한 패턴은 Smalltalk-80이 어떻게 사용자 입력을 처리했는지 반영하며, 현대의 프로그램에는 이 명확한 패턴을 사용하는데 조금만 필요로 한다. 이런 의미에서, 어떠한 현대의 프레임워크도 Model-View-Controller가 아니거나, 혹은 용어의 정의가 다른 의미로 바뀌어가고 있는 것이다.

코코아(AppKit/UIKit)
코코아가 Model-View-Controller를 논할때, 대부분 어플리케이션 설계에서 분리된 표시와 컨텐트의 개념을 일깨우려고 노력하고 있다(이 방법은 모델과 뷰가 분리되게 설계하고, 구성에서 느슨하게 연결되있는 것이다). 사실 코코아만 Model-View-Controller를 이런식으로 사용하는게 아니다. 현대의 많은 이 용어의 사용이 원래의 Smalltalk-80 정의보다는 분리된 표시를 전달하기 위함이다.

코코아가 실제로 쓰고있는 정확한 패턴을 보면서 애플의 코코아 참조 가이드가 사용하는 Model-View-Controller가 어떻게 생겼는지 보자.

Cocoa's version of Model-View-Controller
코코아의 Model-View-Controller

주의해야할 중요한 점은 컨트롤러가 오브젝트 그래프의 중앙에서 대부분 소통을 컨트롤러를통해 한다는 것이다. 모델이 그래프의 중앙에 왓던 Smalltalk-80와는 다르다.

코코아는 앱에서 이 패턴을 강요하진 않지만, 모든 어플리케이션 템플릿으로 강력히 내포하고 있다. NIB 파일로부터 불러오는 것은 NSWindowController/UIViewController 사용을 강력하게 지향한다. NSTableView/UITableView나 그 관련 클래스의 델리게이트 필요 조건은 전체 표시의 책임을 이해하는 조정자 클래스를 강하게 의미한다. UITabBarController와 UINavigationController 같은 클래스들은 뷰를 조정하기위해 명시적으로 UIViewController인스턴스를 필요로 한다.

Taligent
학술적 토론에서, 코코아가 Model-View-Controller로 부르는 그 패턴은 모통 Model-View-Presenter라 불린다. 이 두가지는 코코아가 컨트롤러라 부르는 것을 프레젠터라 부르는 것만 빼면 동일하다. "프레젠터"라는 이름은 화면을 셋업하고 동작을 중재하는 역할을 맡는다. 몇몇 케이스에서는 프레젠터 오브젝트를 "감독 컨트롤러(Supervising Controller)"라 부르기도 한다. (왜 Model-View-Supervising Controller가 Model-View-Controller로 다시 돌아오게 되었는지 이해할 수 있을 것이다)

Model-View-Presenter라는 용어는 Taligent에서 기원된다. 일반적으로 많이 인용된 논문은 1997년에나온 “MVP: Model-View-Presenter, The Taligent Programming Model for C++ and Java”인데, 이 모델을 구현하기위한 Taligent의 클래스들은 적어도 1995년만큼 이르게 문서화 되었다.

Taligent는 원래 코드명 "Pink"(그 방법에 사용된 색깔 색인 카드 이후)라는 프로젝트로 System 7(이것은 "Blue" 색인 카드에 해당함)를 대체하는 OS를 지원하기위해 애플 안에서 시작된 회사였다. 애플이 동등하게 운이다한 Copland 프로젝트에 집중하고 시선을 돌리는 동안, 이 프로젝트는 일련의 인기있는 개발과 관리 문제를 가졌었다. Taligent는 1998년에 막을 내리기 전까지, OS 대신에 일련의 어플리케이션 프레임워크로 CommonPoint라는 이름으로 IBM이 배포해왔다.

This Wired article from 1993 gives an interesting insight into Taligent and the apparent bloat and infighting that doomed it.

NeXTStep이 Taligent보다 앞서 나왔지만, AppKit(지금은 AppKit의 Model-View-Controller 디자인 패턴의 양상을 정의하고 있지만)에는 컨트롤러 클래스가 1996년에 NeXTStep 4 전까지 없었다(NeXTStep의 메이저 재설계와 NS가 접두에 붙는 첫번째 NeXTStep 버전이 오늘날까지도 macOS에 남아있다). NeXTStep이 Taligent의 것을 직접 빌렸는지는 모르겠다(이것이 한 점으로 수렴해버린 진화일 수도 있고, 여러 회사가 같은 인재 풀에서 고용을 했기 때문일 수도 있다).

The Taligent documentation, from 1995, is fascinating to read. The Guide to Designing Programs discusses many ideas relevant to application design, 22 years later. However, the Programming with the Presentation Framework tutorial is horrifically bad: baffling, over-technical and unapproachable.

컨트롤러 문제
코코아의 Mode-View-Controller를 Model-View-Presenter 패턴으로 이해하는것이 매우 중요한데, 이 패턴에서는 "컨트롤러 문제"라는 커다란 문제를 유발한다.

"메시브(Massive)/거대한(Huge)/자이언트(Giant) 컨트롤러"라고도 불리는 컨트롤러 문제는 코코아에 있는 컨트롤러가 여러 분리된 역할(특히 뷰가 들어있어 생기는데 기능적으로나 데이터 의존성에도 이럴 필요없다)을 가지면서 끔찍하게 커져버리는 경향의 문제이다. 많은 보통 프로젝트들은 2000줄 혹은 그 이상의 컨트롤러 하나를 가진다.

명확하게 해달라. 이 문제가 단순히 그 크기에만 있는것이 아니라, 컨트롤러가 다루는것이 커지게 되는 점이다. 코코아에서 컨트롤러는 관련된것 혹은 관련되지 않은 역할들의 한 집합체이다. 하나의 뷰 컨트롤러는 대여섯개 혹은 그 이상의 뷰의 역할이 있을테고, 각각은 구조, 구성, 데이터표시, 데이터갱신, 레이아웃, 애니메이션과 동작들을 가질것이며, 끝내 부모 뷰 컨트롤러가 된 상태 보존 역할이 있을 것이다.

상당한 규모로 이 독립적인 역할들과 상호으존적인 역할들의 집합체는 악몽의 유지보수이다. 많은 양의 코드 덩어리들은 실제 의존성을 만들고, 찾기 힘든 상호 의존적인 기능을 만든다. 컨트롤러는 항상 테스트하기 힘들지만 (커다란 앱과 고립시키기는 어렵게 만드는 묶인(bundle) 상태의 의존성 때문에) 규모와 반의존성(semi-depenency)의 문제는 모든것을 더 나쁘게 만든다.

컨트롤러 문제를 해결하는 유일한 방법은 커다란 뷰컨트롤러를 계속적으로 더 작고 간편한 컨트롤러로 리팩토링하는 것이다. 이것에는 뷰컨트롤러에서 빼내와, 여러 뷰컨트롤러를 가지도록하는 분별력있는 접근법을 설계하여 의존성을 빼내고 제거하기위해 데이터 구조를 다시 설계하고 다시 생각해야한다. 완료할 수 있을지라도 수많은 일이 있을 것이고, 각 변경마다 버그가 나오는 일반적인 위험을 가지며, 여전히 테스트하기 힘들고(모든 뷰컨트롤러가 연관되있기 때문에), 이 모든 것에도 불구하고 끝단 사용자에게는 기능을 추가해 주지도 못한다.

바인딩
애플은 그들의 Mac OS X 10.3의 코코아 바인딩을 소개함으로서 얼마동안은 컨트롤러 문제에대해 알고 있었다.

바인딩은 두 컴포넌트 사이에 경로가 만들어진 런타임이다. 이 컴포넌트들은 보통 데이터의 소스이거나 데이터의 옵저버이다. 바인딩은 명시적인 코드 경로가 필요없이 이 컴포넌트들이 변경사항을 소통할 수 있게 해준다. 대신에, 두 컴포넌트 사이에 경로는 데이터안에 정의되있다(코코아 바인딩의경우 "key-path"라 부른다). 컨트롤러를통해 뷰 상태를 조절하는 모델 프로퍼티까지 key-path를 명시함으로서, 바인딩은 컨트롤러를 통하는 코드 경로를 과감히 줄여주어서(제거하기도한다) 컨트롤러 문제를 개선시킬 수 있다.

Cocoa's Model-View-Controller with Bindings
컨트롤러를 통한 코드경로는 바인딩에의해 대체된다

그 소개 이후, 수십가지의 코코아 바인딩이 모두 잊혀진채 남아있다. AppKit에서 아직 바인딩을 사용할 수는 있지만(절때 디프리케이트 시키진 않는다) 절때 UIKit에 넣진 않았고, 뷰를 더 쉽게 프로그래밍하고자하는 그 목적은 완전히 이룰 수 없는 결과를 초래한다. 내 생각에는 직면해야할 문제를 마주하고 몇몇의 경우는 매우 잘 동작할 수 있지만(특히 NSTableView에 있는 NSArrayController로 결합되어있을때) 왜 Mac 프로그래밍을 대체하지 않았는지 이해할 수 있다.

(여러분의 뷰컨트롤러에서 더 적은 코드의) 코코아 바인딩의 핵심 이점은 인터페이스 빌더 인스팩터 패널에서 수많은 설정(configuration)을 할 수 있다는 것이다. 이것은 여러분 코드에서 기능을 찾아볼 때 혼란스러울 수 있고, (Xcode의 프로젝트 범위 검색이 XIB 파일을 검색할 수 있게 되었지만) 그래도 검색하기 어렵고, 디버깅에 힘들며(모델 데이터가 여러분의 코드를 따라가는 스택 추적 없이 변한다), 인스팩터 패널로 검색하고 싶지 않은 새로운 누군가에게 가르치는 것은 매우 힘들며, 시작할때 코드보다 더 보기 힘들어지고(XIB 파일에 주석을 달거나 재구성할 수 없다), 로컬라이제이션이 섞인(localization maxup)것과같은 인터페이스 빌더 이슈나 버전 컨트롤 머지 이슈의 늪에 빠져버릴 수 있다.

내 개인적인 견해로는 코코아 바인딩의 중대한 실패는 커스텀 변형과 커스텀 프로퍼티를 추가하는데 있어 어려움으로 남아있다. 이것들은 모두 완료될 수 있지만 변형자를 등록하고 바인딩 딕셔너리를 노출시키는 일은 지루한 일이다. 항상 바인딩 없이 뷰컨트롤러를 통해 데이터를 보내는것은 쉬워보인다. 이 의미는 바인딩이 가장 간단한 문제를 도울 수 있는 경향은 있지만 (크게 도움이 되진 않는다) 힘든 문제에는 큰 효과를 얻지 못한다.

새로운 무언가?
Mac OS X 10.3의 코코아 바인딩 이래로, 애플은 코코아 앱에 사용될 대안의 디자인 패턴을 찾는데 어떠한 명확한 시도도 해보지 않고 있다.

iOS5와 Mac OS X 10.10에서 스토리보드를 내놓았지만, 스토리보드는 현재 디자인 패턴을 용이하게 만드는 시도만큼 디자인 패턴을 바꾸기위한 시도가 아니다. 스토리보드는 NS/UIViewController 사용을 지향하면서 Model-View-Presenter 디자인 패턴을 보강한다. 스토리보드는 더 작고 더 초점이 가는 뷰 컨트롤러를 지향하고, 많은 셋업과 트랜지션의 "표현(Presentation)"을 아주 약간 줄여주는 역할이다. 그러나 인터페이스 빌더에서 구성될 수 있으므로 코코아 바인딩에 영향을 준 여러 이슈들을 보여준다.

어플리케이션 디자인 패턴에서 더 야심찬 무언가를 기대한 우리로써는, 스토리보드가 그 새로운것을 많이 제공하진 않았다.

어플리케이션 디자인에서 새로운 방법이 존재한다. 애플 바깥에는, 리엑티 프로그래밍(이것을 선택하면 많은 바인딩의 역할로 채울 수 있다), Model-View-ViewModel(변형된 섹션의 모델을 뷰에 가깝게하여 연결함으로서 컨트롤러의 작업을 줄인다), 상호 방향적 데이터흐름(unidirectional dataflow)(이것은 모든 데이터 변경을 앱 전체에 방송하고 리듀서(reducer)를 통해 데이터 변경을 함으로서 바인딩의 필요를 줄인다); 이 모든것들을 다른 사이클로 인기가 있다(원문: all of which are popular within different circles).

리엑트 네이티브(React Native)나 Swift-Elm같이 근본적으로 다른 프레임워크도 있다. 비록 스위프트나 코코아가 희생되는것이 전적으로 중요한 결점을 동반할지라도 말이다.

이런 어떤 것들이 공식적인 코코아 앱 개발에 어떤 영향을 줄지는 불명확하다. 애플은 이따금 과감하게 바꾸고 싶어 한다는 것을 스위프트가 증명했고, 스위프트는 언어면의 이점을 수용한 디자인 패턴이나 뷰 프레임워크 갈망이 점점 더 커진다는 견해가 있다. 그러나 애플은 스위프트만으로된 메이저 프레임워크를 소개하려하기 전까지는 시간이 좀 걸릴것 같다.

결론
코코아의 현재 Model-View-Controller 패턴의 원래 데이터로서 NeXTStep 4를 받아드린다면 올해(2017년)로 20년째이다. 망가지진 않았지만 결점을 가지고 있고, 한번 그렇게 했었던만큼 흥분되거나 능률적인것으로 보이진 않는다.

애플은 디자인패턴 개선을 위해 비교적 이르게 코코아 바인딩을 내놓았었다. 이런 수용은 섞였고 애플의 새 플랫폼에까지 도달하지 않아왔다(원문: Its reception was mixed and it has not been carried forward onto Apple’s newer platforms.).

AppKit 혹은 UIKit 팀에게서 내부적인 노력에대한 다른 정보는 없지만, 애플이 가까운 미래에 과감한 변화를 할 것 같지는 않아보인다. 코코아에서 어플리케이션 디자인 패턴 전반을 개선하는 목적의 써드파티 프레임워크를 쓸 수 있는 여러 디자인 패턴들이 있지만, 반드시 이중 하나가 앞으로의 방향이라는 합의는 없어보인다. 나는 이런 노력들이 어떤 종류의 개선에 관심을 반영한다고 생각한다.



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

으로 보내주시면 됩니다.



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

,
제목: 50 iOS Interview Questions And Answers - Part3



내가 받았던 iOS 인터뷰 질문의 마지막 부분으로 올리기로 결정했다. 아직 보지 못했다면 Part1(링크)와 Part2(링크)를 확인해보아라.

1-  좋아하는 앱은 무엇이고 왜 좋아하는가?
아래 답변은 나의 실제 답변이다. 이것들은 내가 좋아하는 앱들과 그 이유이다.

  • Sleep Cycle : 애플의 헬스를 넣었고, 일어나기 모드, 유용한 원형 차트 리프토 도구와 함께 세계의 통계와 필립스 휴를 넣었다.
  • WaterMinder : 하루 물 섭취 이력을 따라갈 수 있다. 코딩하는동안 물 마시는 것을 밎을 수 있는데, 이 앱이 물을 마시라고 알려주며, 다른 종류의 액체류 섭취량(커피 휫수!)도 넣을 수 있다.
  • DayOne : 2년이상 쓰고 있는 앱이다.
  • Facebook : 모든것이 한곳에 있는 앱이다. 스포츠나 메시지, voip, 날씨나 뉴스앱을 따로 설치할 필요가 없다.
  • Mdeium : 순수하고 네이티브하고 오가닉하며 최신의 그리고 point shot 기술관련 글을 쉽게 찾을 있고 그 회사와 개발자들의 비전을 배울 수 있다.

2- JSONSerialization이 어떤 ReadingOption들을 가지는가?
  • mutableContainers는 배열과 딕셔너리를 변수 오브젝트(상수가 아닌)로 만들어준다.
  • mutableLeaves는 JSON 오브젝트 그래프에서 리프(leaf)의 문자열을 변수 문자열 인스턴스로 만들어준다.
  • allowFragments는 배열이나 딕셔너리 인스턴스가 아닌 최고수준 오브젝트로 파싱할 수 있게 해준다.

3- 서브스크립트(subscript)를 설명하시오
클래스, 구조체, 열거형에서 서브스크립트를 정의할 수 있으며, 이것은 컬랙션, 리스트, 시퀸스의 맴버 요소에 접근하기위한 단축방식(shortcut)이다.

4- 디스패치 그룹(DispatchGroup)이란 무엇인가?
디스패치 그룹은 어떤 작업의 동기화를 모을 수 있게 한다. 여러 다른 작업 항목을 보내서, 각각이 다른 큐에서 동작할지라도 모든것이 완료된 것을 확인하는데 사용한다. 이 동작은 명시된 작업이 모두 완료되기 전까지 처리하지 않도록 만들때 유용하다— 애플의 문서

가장 기본적인 답변은, 처리하기 전에 여러 비동기나 동기적인 작업을 기다릴 필요가 있을때, 디스패치 그룹을 사용할 수 있다고 하면 된다.

5- RGR(Red-Green-Refactor)가 무엇인가?


Red, Green, Refactor는 TDD(테스트 주도 개발)의 단계이다.
  1. Red : 보통 7줄이 넘지 않는 짧은 테스트 코드를 작성하고 실패를 확인한다
  2. Green : 짧은 제품 코드를 작성한다. 다시 7줄이 넘지않는 짧은 테스트 코드를 작성하고 테스트가 통과하게 만든다.
  3. Refactor : 테스트가 통과하면, 걱정없이 변경할 수 있다. 코드를 정리(clean up)한다. 여기에 훌륭한 워크샵 노트가 있다.

6- 의존성 주입(Dependency Injection)이란 무엇인가?
iOS 앱에서 스토리보드나 xib을 사용하면 IBOutlet을 만들게된다. IBOutlet은 뷰에 관련된 프로퍼티이다. 이것들은 그 뷰가 인스턴스화될때 뷰컨트롤러에 주입(inject)되며, 이것이 의존성 주입의 본질적인 형태이다.

의존성 주입의 형식에는 생성자 주입, 프로퍼티 주입, 메소드 주입이 있다.

7- 노티피케이션의 타입을 설명하라
노티피케이션 타입에는 리모트(remote)와 로컬(local), 두가지 타입이 존재한다. 리모트 노티피케이션은 서버 접속을 필요로 한다. 로컬 노티피케이션은 서버 접속을 필요로 하지 않는다. 로컬 노티피케이션은 기기 내부적으로 일어나는 것이다.

8- 프로젝트에서 언제 의존성 주입을 사용하면 좋을까?
여러분이 해볼 수 있는 몇몇 가이드라인이 존재한다.
규칙1. 테스트 가능함이 중요한가? 그렇다면 테스트하고 싶은 클래스에 외부 의존성을 식별하는 것이 필수이다. 의존성을 주입할 수 있으면, 테스트하기 쉽게 만들기위해 실제 서비스를 가짜의 서비스(mock)으로 쉽게 대체할 수 있다.
규칙2. 복잡한 클래스는 복잡한 의존성을 가진다. 앱-수준 로직을 가지고있거나 디스크나 네트워크같은 외부 리소스에 접근한다. 앱의 대부분 클래스는 복잡할 것이며, 많은 컨트롤러 오브젝트와 많은 모델 오브젝트를 가지고 있을 것이다. 이것을 시작하는 가장 쉬운 방법은 여러분의 앱에서 복잡한 클래스를 뻬내고 그 클래스안에 다른 복잡한 오브젝트를 초기화할 장소를 찾는 것이다.
규칙3. 한 오브젝트가 다른 오브젝트와 의존성을 공유하는 오브젝트 인스턴스를 만들고 있다면 의존성 주입을 고려해야할 대상이다.

9- 컬랙션 타입(collection type)에서 우리가 사용할 수 있는 오더 함수(order function)은 어떤 종류가 있는가?
map, filter, flatMap에대한 자세한 내용을 확인해보자!

10- 커밋들을 합치게 해주는 깃 명령은 무엇인가?
git squash

11- AnyAnyObject의 차이는 무엇인가?
애플의 스위프트 문서에 따르면 아래와 같다.
  • Any는 모든 타입에대해 인스턴스를 나타낼 수 있으며, 함수 타입이나 옵셔널 타입까지도 포함된다.
  • AnyObject는 모든 클래스 타입의 인스턴스를 나타낸다.

더 세부적인 내용를 확인해보자.

12- 기본적으로 SOAP과 REST의 차이는 무엇인가?
둘 다 웹 서비스의 접근을 도와준다. SOAP는 서비스에 메시지를 날리기위해 독점적으로 XML과 친하다. SOAP은 웹 서비스 접근에대해 절대적으로 무겁다. 원래는 마이크로소프트에서 만들어진 것이다.

REST (Representational State Transfer)은 경량의 대안이다. 요청시 XML을 사용하는 것 대신에 REST는 대부분 간단한 URL을 사용한다. REST는 작업을 수행하기위해 4가지의 HTTP 1.1 용어(GET, POST, PUT, DELETE)를 사용한다.

13- 당신이 좋아하는 시각화 차트 라이브러리는 무엇인가?
Charts는 iOS, tvOS, OSX를 지원하며 애플 버전의 MPAndroidChart이다.
Core Plot는 macOS, iOS, tvOS를 위한 2D 점에대한 곡선을 그리는(plotting) 프레임워크이다.
TEAChart는 iOS를 지원한다.


14- 나쁜 커밋을 찾아주는 깃 명령은 무엇인가?
git bisect

15- 코어데이터(CoreData)란 무엇인가?
코어데이터는 디스크에 영속 저장소에 오브젝트 그래프를 영속할 수 있게 만드는 오브젝트 그래프 매니저이다. 한 오브젝트 그래프는 일반적인 Model View Controller iOS 앱에서 다른 모든 Model의 지도같은 역할을 한다.

16- 연관타입(Associatedtype)을 설명하시오
제네릭 프로토콜을 만들고 싶다면 연관타입을 사용할 수 있다. 더 자세한 내용을 확인해보자.

17- 깃에서 커밋을 만들지 않고서 코드를 저장하는 명령은 무엇인가?
git stash

18- Priority InversionPriority Inheritance 설명하시오
높은 우선순위 스레드가 낮은 우선순위 스레드를 기다리는 것을 Priority Inversion이라 부른다. 낮은 우선순위 스레드가 일시적으로 제일 높은 우선순위의 스레드의 우선순위를 물려받으면(inherit) Priority Ingeritance라 부른다.

19- Hashable이 무엇인가?
Hashable는 딕셔너리에서 키로서 오브젝트를 사용할 수 있게 해주는 것을 말한다. 따라서 우리만의 커스텀 타입을 만들 수 있다.

20- 옵셔널 체이닝(optional chaining)과 if let, guard는 언제 사용할까?
옵셔널 체이닝은 작업이 실패하는것을 별로 신경쓰지 않을때 사용한다. 그렇지 않으면 if let이나 guard를 사용한다. 이 대답은 Parsing JSON in Swift의 확장판에서 가져온 대답이다.

물음표 연산자를 사용하는 것을 옵셔널 체이닝이라한다. 애플의 문서에는 이렇게 설명하고 있다.
옵셔널 체이닝은 현재 nil일 수 있는 옵셔널에대해 프로토콜, 메소드, 서브스크립트를 쿼리하고 호출하기위해 진행한다. 옵셔널이 값을 가지고 있다면 프로퍼티, 메소드, 서브스크립트 호출은 성공할 것이고; 옵셔널이 nil이라면 프로퍼티, 메소드, 서브스크립트는 nil을 반환할 것이다. 다중 쿼리가 체인(chained)될 수 있으며, 체인에서 어떤 연결이라도 nil이면 모든 체인이 우아하게 실패한다.

21- 스위프트에서 데이터를 전달하는 방법에는 몇가지가 있는가?
델리게이트, KVO, Segue, NSNotification, Target-Action, Callback등 여러 방법들이 존재한다.

22- 어떻게 프로젝트 코드를 깨끗하게 만들 수 있는가?
GithubSwiftLint의 스위프트 프로젝트를 위한 코딩 규약, 스타일 가이드를 따른다.

23- iOS10에서 새로 발표된 내용은 무엇인가?
  • SiriKit
  • Proactive Suggestions
  • 메시지 앱과 동합하기
  • 사용자 노티피케이션
  • 앱 익스텐션
  • CallKit
  • 음성 인식
  • 앱 검색 증진

더 자세한 내용을 확인하자.

24- 옵셔널에서 nil.None의 차이점은 무엇인가?
차이는 없다. Optional.None( .None은 단축형이다)은 값을 잃어버린 옵셔널 변수의 올바른 초기화 방법이다. 반면  nil.None에대한 간편한 문법형태(sugar)이다. 확인해보자.

25- iOS 프로젝트를 위한 다른 IDE를 사용하고 있는가?

그렇다 혹은 아니다. 만약 그렇다고 하고싶다면 Appcode를 확인해보자.

26- VIPER 아키텍처란 무엇인가?
여기에 VIPER에대한 완벽한 설명이 있다.

27- Continuous Integration이 무엇인가?
Continuous Integration은 앱 개발시 뭔가 잘못 되었을때 빠른 피드백을 줄 수 있는 어떤것이다. 수많은 Continuous Integration 툴을 사용할 수 있다.

스스로 호스팅되는 서버

클라우드 솔루션

28- 델리게이트와 콜백의 차이점은 무엇인가?
델리게이트는 NetworkService가 델리게이트에게 "뭔가 바뀌었어"라고 말하는 것이고, 콜백은 델리게이트가 NetworkService를 주시하면서 지켜보고 있는 것이다.


29- 이전에 디자인 툴이나 프로토타입 툴을 사용해 본 적이 있는가?
디자인 툴을 아무것도 모른다면 오늘 바로 시작해보길 바란다.

Sketch + Marvel는 좋은 궁합이다. 코딩을 하면서 프로토타입을 만들어보고 싶으면 Framer도 좋은 선택일 것이다.

30- 백엔드 개발에대해 알고있는가?
사람마다 다를것이다. 나는 PARSE에 있어보았고 FBStart를 받았었다. 나는 순수 백엔드를 배우기로 결정했었다. 여러분은 두가지 선택지가 있을것이다. node.js + express.js 그리고 mongodb를 배우거나, Vapor 나 Kitura를 배울 수도 있을 것이다.

Firebase를 좋아하거나 사용하지 못해보았는가?

Firebase는 macOS 개발자들을 위한 길이 없다.

여러분이 Firebase를 배우고 싶으면, 한달코스의 Firebase Google Group를 따라가보아라.

31- 오토레이아웃을 설명하라
오토레이아웃은 유연하고 강력한 레이아웃 시스템을 제공하며, 이것은 뷰와 UI 조작이 그 계층안에서 크기와 위치를 어떻게 계산할지 표현한다.

32- 하드코딩 로그 절의 단점은 무엇인가?
첫째로, 로그를 시작할때이다. 모으기위해서 이것을 시작한다.많아 보이지는 않을 것이지만 매 분마다 추가된다. 프로젝트 끝쯤에는 잃어버린 분들이 시간 단위가 되있을 것이다.
둘째로, 코드베이스에 추가할 때마다 우리 코드에 새로운 버그를 주입하는 위험을 겪는다.

33- 포인터란 무엇인가?
포인터는 메모리 주소에 직접 참조하는 것이다. 한 변수는 값을위한 투명한 용기로서 동작하는 반면, 포인터들은 추상화층을 없애고 값이 어떻게 저장되있는지 볼 수 있게 해준다.

34- 계약직으로 일해본 적이 있는가?
그렇게 해보면 좋다. 그것은 그것의 장점이 있다. 내 생각에는 클라이언트를 관리해보고 소통하고 작업 관리 능력을 증진시키는데 좋은 경험인 것 같다.

35- 쌍 프로그래밍(pair programming)이란 무엇인가?
쌍 프로그래밍은 하급 개발자와함께 정보를 공유하는 도구이다. 하급 개발자와 상급 개발자가 나란히 앉아서, 상급 개발자에게 배울 수 있는 방법이다.

Martin Fowler의 “Pair Programming Misconceptions” WikiHow의 "Pair Programming" 를 확인해보아라

36- 블럭(block)에대해 설명하라
블럭은 단일 작업이나 동작의 유닛을 전체적인 Objective-C 클래스를 쓰지 않고 정의할 수 있는 하나의 방법이다. 익명 함수이기도 하다.

37- 키체인(Keychain)이란 무엇인가?
키체인은 iOS 앱에서 데이터를 보안상 저장하기위한 API이다. 좋은 라이브러리가 있다(Locksmith).

38- UserNotification에서 가장 크게 바뀐 점은?
  • 오디오, 비디오, 이미지를 넣을 수 있게 되었다.
  • 노티피케이션에 커스텀 인터페이스를 넣을 수 있게 되었다.
  • 노티피케이션 센터에서 인터페이스를 관리할 수 있게 되었다.
  • 새로운 노티피케이션 익스텐션은 우리가 전달하기 전에 리모트 노티피케이션 적재량을 관리할 수 있게 되었다.

39- atomicnonatomic synthesized 프로퍼티의 차이에대해 설명하라
atomic : 디폴트 동작이다. 한 오브젝트가 atomic으로 선언되있으면 스레드 세이프하게된다. 스레드 세이프의 의미는, 클래스의 특정 인스턴스의 한 스레드만 그 오브젝트에대해 조작할 수 있다.
nonatomic : 스레드 세이프 하지 않다. nonatomic 프로퍼티 속성을 사용하면 synthesized 접근자가 간단하게 직접 값을 세팅하거나 리턴하도록 지정할 수 있다. 서로 다른 스레드에서 동시에 같은 값에 접근하려 할때 어떤 일이 일어날지는 보장하지 못한다. 이런 이유로, atomic보다 nonatomic이 접근 속도가 더 빠르다.

40- availability 속성은 왜 사용하는가?
애플은 한 시스템 버전 back을 지원하고 싶어하는데, 이 의미는 우리가 iOS9와 iOS8을 지원해야한다는 것이다. Availability 속성은 이전 버전의 iOS를 지원하게 해준다.

41- 디바이스 토큰을 얻는 방법에는 무엇이 있는가?
두가지 단계가 있다. 첫째로 사용자 허가 화면을 반드시 보여야한다. 그 뒤에 리모트 노티피케이션을 등록할 수 있다. 이 단계가 잘 넘어가면 시스템에서 디바이스 토큰을 줄 것이다. 만약 앱을 지우거나 재설치하면 디바이스 토큰은 변경될 것이다.
 
42- 캡슐화란 무엇인가?
캡슐화는 객체지향 설계 원리이며 내부 상태와 오브젝트의 기능을 숨긴다. 이 의미는, 오브젝트가 자신의 상태 정보를 사적으로(private) 들고 있는다.

43- 빅오(Big-O) 노테이션이란 무엇인가?
알고리즘은 N 크기의 입력에대해 결정되는 작업 횟수를 표현하는 방법이다. 빅오 노테이션 등급은 가장 높은 값으로 표현된다. 그리고 빅오 노테이션은 질문의 O(n)으로 답을 찾는다. 여기 cheat sheet swift algorithm club가 있다. 아래는 예제이다.
for 루프의 빅오 노테이션은 O(n)이다. for 루프가 n번 동작하기 때문이다.
변수(var number: Int = 4) 빅오 노테이션은 O(1)이다.

44- 의존성 관리란 무엇인가?
오픈소스 프로젝트를 통합하고 싶을때나 써트파티 프로젝트로부터 프레임워크를 추가하거나 어떤 고유의 프로젝트의 코드를 재사용할때, 의존성 관리는 이 관계를 관리할 수 있도록 해준다. 여기를 왁인해보자

45- UML 클래스 다이어그램란 무엇인가?
UML 클래스 다이어그램은 규칙의 집합이고 소프트웨어의 명세에대한 노테이션인데, 오브젝트 관리 그룹(Object Management Group)에의해 관리되고 생성된다.

46- throw를 설명하라
throws 키워드를 이용하면 컴파일러에게 에러를 던저라고 할 수 있다. 에러를 던지기 전에, 여러분이 받고 싶은 에러 목록을 만들어야한다.

47- 프로토콜 익스텐션이란 무엇인가?
익스텐션을 이용하여 프로토콜을 적용할 수 있는데, 기존의 타입 정의에도 가능하다. 가지고 있지 않는 타입에 프로토콜을 추가할 수 있게 해준다.

48- 2017년 iOS 앱 개발에서 트랜드는 무엇이었나?
  • 스위프트 코딩
  • 인자화된 현실성
  • IoT
  • 보안성 증진
  • 엔터프라이즈 앱에서의 성장
  • 하이브리드 기술(리엑트 네이티브)에서의 성장

49- ObjC에서 Selectors를 설명하시오
Selectors는 Objective-C의 내부적인 메소드이름 표현법이다.

50- 리보트 노티피케이션 첨부물의 한계는 어떻게 되는가?
푸시 노티피케이션으로 비니오나 이미지를 보낼 수 있다. 그러나 최대는 4kb이다. 만약 높은 퀄리티의 첨부물을 보내고 싶으면, 노티피케이션 서비스 익스텐션을 사용할 수 있다.


나의 추천들




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

,
제목 :



반갑다 , Part2가 준비되었다. Part1을 확인해보고 싶으면 여기(링크)를 확인해보자.

1- 스위프트에서 메소드 스위즐링(Method Swizzling)을 설명하라
메소드 스위즐링은 Objective-C나 동적 메소드 디스패치를 지원하는 다른 언어에서는 잘 알려진 방법이다.

스위즐링은 통해, 특정 #selector(method)와 그 구현을 담은 함수 사이의 맵핑을 변경하여, 메소드의 구현이 런타임시 다른 것으로 대체될 수 있다.

스위프트 클래스와함께 메소드 스위즐링을 사용하기 위해서는 반드시 따라야할 두가지 필수조건이 있다.
  • 스위즐될 메소드를 가진 클래스는 NSObject를 확장해야한다.
  • 스우즐하고싶은 메소드는 동적 속성을 가지고 있어야한다.

2- Non-Escaping 클로저와 Escaping 클로저의 차이는 무엇인가?
non-escaping 클로저의 라이프사이클은 단순하다.
  1. 함수에 클로저를 전달한다.
  2. 함수가 클로저를 실행시킨다.
  3. 함수는 반환한다.

escaping 클로저의 의미는, 함수 안에서, 여전히 클로저를 실행시킬 수 있다; 클로저의 추가적인 부분은 함수 바깥에서 죽지않는 장소에 저장되있다. 여기에는 클로저가 그 담겨진 함수를 escape하는 여러 방법들이 잇다.
  • 비동기 실행 : 디스패치 큐에서 비동기적으로 클로저를 실행시킨다면 큐는 클로저를 잡고 있을 것이다. 클로저가 언제 실행될지는 알지 못하고 함수가 리턴되기 전에 이것이 완료될지는 보장하지 못한다.
  • 스토리지 : 전역 변수, 프로퍼티, 아니면 다른 스토리지(이전의 함수호출에 살고있는)에 클로저를 저장하면 클로저는 escape된다.


3- [weak self][unownded self]를 설명하라
unowned는 한가지만 빼고서 weak와 같다. 바로, 이 변수는 nil이 될 수 없다는 것이고 그러므로 이 변수는 절때 옵셔널이 될 수 없다.

그러나 그 인스턴스가 메모리 해제된 후에 변수에 접근하려하려한다면, 인스턴스가 메모리 해제된 후에 절때 그 변수를 사용하지 않을것이라고 확신할때만 unowned를 사용해야한다.

그러나 변수를 weak로 만들고 싶지 않고 해당 인스턴스가 메모르 해제된 후에 그 변수에 접근하지 않을 확신이 있을때 unowned를 사용할 수 있다.

[weak self]로 정의하면, 클로저 내의 어떤 부분이 nil일 수도 있는 경우를 다룰 수 있게 해주므로 변수는 반드시 옵셔널이여야한다. 비동기 네트워크 요청에서 [weak self]를 쓰는 경우는 그 요청이 빈번하게 사용되는 뷰의 View Controller에서 이다.

4- ARC란 무엇인가?
ARC는 애플에서만든 컴파일타임의 자동 메모리 관리 기능이다. Automatic Reference Counting의 약자이다. 이 말은 오브젝트의 강참조(strong reference)가 0이될때만 그 오브젝트를 메모리에서 해제한다는 뜻이다.

5- #keyPath()를 설명하시오
#keyPath()를 사용하면, StaticString이나 StringLiteralConvertible로 사용된 key-path 리터럴 문자열의 이점에의해 스테틱 타입 체크가 시행될 것이다. 이 시점에서 아래의 것을 보장하는 체크가 일어난다.
  • 실제로 존재하는가
  • Objective-C로 적절하게 노출되었는가

6- IGListKit가 개발자에게 제공하는 것이 무엇인가?
IGListKit은 자동으로 다른 오브젝트들을 비교하여, 변경된 것을 UICollectionView에 배치(batch) 업데이트 애니메이션을 시행한다. 우리가 절때로 배치를 짜지 않는 방법으로 갱신한다.

7- 리엑트 네이티브가 iOS에게 특별하게 만드는 것은 무엇인가?
  1. (PhoneGap과 다르게) 앱을 리엑트 네이티브로 짜면 자바스크립트로 작성하고 실행시키지만, 앱 UI는 전적으로 네이티브이다. 따라서 HTML5 UI와 소통하는 요소는 없다.
  2. 추가적으로(Titanium과 다르게), 리엑트는 novel, radical, 매우 함수적인 접근법을 UI 구성에 사용한다.요약하면, 앱 UI는 간단히 현재 앱 상태의 함수로 표현된다.

8- NSFetchRequest란 무엇인가?
NSFetchRequest는 Core Data에서 패치하는 역할의 클래스이다. 패치 요청은 강력하기도하고 유연하기도하다. 어떤 기준에따른 오브젝트 집합을 패치하라고 요청할 수도 있고 개별의 값들을 패치하라고 요청할 수도 있고 등등이 있다.

9- NSPersistentContainer란 무엇인가?
퍼시스턴트 컨테이너는 앱을 위해 로드한 것을 가지고 있는 컨테이너를 만들어서 반환한다. 저장된 것을 생성하는데 실패한 정규의 에러 상태가 있으므로, 이 프로퍼티는 옵셔널이다.

10- NSFetchedResultsController를 설명하시오
NSFetchedResultsController는 컨트롤러이지만 뷰컨트롤러는 아니다. 사용자 인터페이스를 가지고 있지 않는다. 이것은 Core Data에서 나온 데이터 소스와 테이블뷰의 동기화 코드를 추상화시켜 개발자들을 좀 더 편하게 만들어주는 목적이 있다.

옳바르게 NSFetchedResultsController를 초기설정하면 여러분의 테이블은 몇줄 코드 없이 데이터 소스를 표현해줄것이다.

11- Xcode8에서 주된 3가지 디버깅 개선사항이 무엇인가?
  • View Debugger는 런타임시 우리의 레이아웃을 시각화해주고 constraint 정의를 보여준다. 이것이 Xcode6부터 있었지만 Xcode8에서 constraint 충돌에대한 경고가 새로  나왔는데 매우 유용하며, 다른 편리한 기능들도 소개되었다.
  • Tread Sanitizer는 Xcode8에서의 완전히 새로운 런타임 도구인데, 스레드 이슈를 경고해준다-가장 눈에띄게는 잠재적인 경쟁상태가 있다.
  • Memory Graph Debugger도 Xcode8에서 완전히 새로 나온 것이다. 이것은 시간상 한 지점의 앱 메모리 그래프를 시각화해주고 이슈 네비게이터에서 누수를 표시한다.

12- 테스트 주도 개발의 세가지 규칙은 무엇인가?
  1. 실패한 유닛테스트를 통과시키지 않은 것을 제품의 코드로 만들지 않아야한다.
  2. 충분히 실패한 것보다 더 많은 코드를 작성하지 않으면 안된다; complication 실패는 실패이다.
  3. 한가지 실패한 유닛테스트를 통과시킨것보다 더 제품의 코드를 쓰지 않아야 한다.
  1. You are not allowed to write any production code unless it is to make a failing unit test pass. 
  2. You are not allowed to write any more of a unit test than is sufficient to fail; and compilation failures are failures. 
  3. You are not allowed to write any more production code than is sufficient to pass the one failing unit test.

13- 클래스 안에서 final 키워드가 무엇인지 설명하시오
메소드 이름 앞에 final이라는 키워드를 넣으면, 그 메소드가 오버라이드 되는 것을 막아준다. 만약 final class 키워드를 static이라는 한 단어로 바꿔도 같은 동작을 한다.

14- Yak Shaving이 의미하는 것은 무엇인가?
Yak Shaving은 한 프로젝트가 다음 단계로 진행하기 전에 실행해야할 일련의 작업들을 말하는 프로그래밍 용어이다.

// 역자: 접근자 수준은 스위프트4에서 또 크게 바뀌었습니다. 이 번역글(링크)를 참조해 주세요.
15- openpublic 접근 수준의 차이는 무엇인가?
open은 다른 모듈이 클래스를 사용하거나 상속받는게 가능한 것이다; 맴버의경우 다른 모듈이 그 맴버를 사용하거나 오버라이드할 수 있다.
public은 다른 모듈이나 public 클래스나 public 맴버로 사용할 수 있다. public 클래스는 더이상 하위클래스를 만들 수 없고 public 맴버도 오버라이드할 수 없다.

16- fileprivateprivate 접근 수준의 차이는 무엇인가?
fileprivate는 현재 파일 안에서 접근할 수 있음을 의미하고, private는 현재 선언 안에서 접근할 수 있음을 의미한다. 

17- 내부 접근(Internal access)이 무엇인가?
내부 접근(internal)은 모든 소스 파일 안에서 그들이 정의한 모든 모듈 안에서 엔티티를 사용할 수 있게 하는 것이지만 모듈 바깥의 소스파일은 안된다.

내부 접근은 접근 수준의 디폴트이다. 코드에 어떤 접근 제어자도 쓰지 않았다면 디폴트로 내부접근 수준으로 되있을 것이다.

18- BDD와 TDD의 차이는 무엇인가?
BDD와 TDD의 가장 큰 차이는 공학도가 아닌 사람도 BDD 테스트 케이스를 읽을 수 있다는 점인데, 팀에게 매우 유리하다.

나는 iOS에서 Quick라는 BDD 프레임워크를 선호하고 그것과 궁합이 잘 맞는 Nimble가 있다.

19- Arrange-Act-Assert를 설명하라
AAA는 유닛테스트에서 코드를 정렬(arrange)하고 포맷화(format)시키는 패턴이다. 만약 XCTest를 쓰기로 했으면 각 테스트는 빈 줄을 기준으로 기능적인 섹션별로 그룹화할 것이다.
  • Arrange, 필요한 모든 예측과 입력
  • Act, 테스트중에 오브젝트나 메소드에서 일어나는 것
  • Assert, 예상한 결과가 나오는 것

20- iOS앱에서 테스트를 만드는 것에는 어떤 이점이 있는가?
  • 좋은 테스트는 예상되는 동작의 훌륭한 문서가 된다.
  • 우리가 뭔가 바꾸면 테스트가 실패할거라는 것을 알고 있기 때문에 계속해서 우리 코드를 리팩토링할것이라는 확신을 준다.
  • 테스트를 짜기 어려우면 그 아키텍처를 개선시킬 수 있다. 다음 RGR(Red-Green-Refactor)는 초기에 개선할 수 있게 도와준다.

21- 모바일 제품 설계의 타이포그래픽 퀄리티를 개선시키기위한 필수의 다섯가지 가이드라인은 무엇인가?
  1. 텍스트의 서체를 정하는 것부터 시작하라
  2. 서체를 섞어 쓰는 것을 피하라
  3. 줄 길이를 확인해라
  4. 줄의 높이와 포인트 크기의 균형을 맞춰라
  5. 적절한 아포스트로피(Apostrophe)와 데쉬(Dash)를 사용하라

22- if let의 구조를 설명하라
if let은 스위프트에서 옵셔널 rhs가 값을 가지고 있는지 확인하게 해주는 특별한 구조이다. - 만약 값이 있다면 언랩시켜서 lhs에 할당한다.

23- 어떻게 문맥으로 앱을 학습시킬 것인가?
사용자를 돕는 문맥 기술상의 학습은 요소와 상호소통하고 아주 이전에 완성하지 못하는 방법에 직면한다. 이 기술은 종종 약간의 시각적 단서나 미묘한 애니메이션을 포함한다.



24- bitcode란 무엇인가?
XML타입 목적으로 사용할 수 있는 매우 일반적인 인코딩 포맷이다. 스스로 설명하는 파일 포맷이며 여러 다른 것들이 Bitcode로 인코딩 될 수 있다. Apple Watch 앱은 Bitcode로 되어야하고 Apple TV도 필요하다. iOS앱은 아직 선택적으로 가능하다. 이것에대한 이점은 컴파일러가 keep getting better 하는 것이다.

25- 스위프트 표준 라이브러리 프로토콜을 설명하라
세가지 프로토콜이 있다. equatable 프로토콜은 같은 타입의 두 인스턴스가 어떻게 구별되는지를 결정한다. 우리 앱에서 특정 값을 가지고 있다면. comparable 프로토콜은 같은 타입의 두 인스턴스를 비교하며 sequence 프로토콜은 prefix(while:)drop(while:)이다. [SE-0045]

26- SVN과 Git의 차이점은 무엇인가?
SVN은 버전 관리를 위한 중앙집중식 체계를 따른다. 중앙 저장소는 작업물의 복사본을 생성하고 접근시 네트워크 접속을 필요로한다.

Git은 버전 관리를 위한 분산형 체계를 따른다. 작업하고있는 로컬 저장소를 가질 것이고, 동기화가 필요할 때만 네트워크 접속을 한다.

27- CollectionViewTableView의 차이점은 무엇인가?
TableView는 한 컬럼으로 여러 항목들의 리스트를 보여주고, 수직적 방법이며, 수직 스크롤로 제한된다.
CollectionView도 여러 항복의 리스트를 보여주지만 여러 컬럼과 로우를 가질 수 있다.

28- Alamofire가 하는일은 무엇인가?
Alamofire는 요청/응답 메소드, JSON 파라미터, 응답 시리얼라이즈, 인증등 여러 기능을 연결할 수 있게(chainable) 해준다.

29- REST, HTTP, JSON가 무엇인가?
HTTP는 어플리케이션 프로토콜이며 규칙들의 집합이다. 웹사이트는 웹서버에서부터 클라이언트까지 데이터를 전송하는데 사용한다. 클라이언트(웹브라우저나 앱)는 필요한 행동을 나타내는데 사용한다.
  • GET : 웹페이지같은 것이 데이터를 검색하는데 사용한다. 그러나 서버의 데이터를 변경할 순 없다.
  • HEAD : GET의 식별이지만 헤더에 되돌려받으며 실제 데이터는 들어있지 않다.
  • POST : 서버에 데이터를 보내는데 사용되며, 보통 양식(form)을 채워넣고 보내기(submit)버튼을 누를때 사용된다.
  • PUT : 특정 제공된 위치에 데이터를 보낼때 사용한다.
  • DELETE : 특정 제공된 위치의 데이터를 지울 때 사용한다.

REST(혹은 REpresentational State Teansfer)는 일관성을 설계하기위한 규칙들의 집합이며, 웹-API를 사용하기 쉽고 유지보수하기 용이하게 만든다.

JSON은 JavaScript Object Notation의 약자로서, 직관적이고 사람이 읽을 수 있도록 해주며, 두 시스템간의 데이터 전송을 위한 포터블 메커니즘이다. 애플은 오브젝트를 변환하거나 그 반대로 할때 JSONSerailization 클래스를 사용할 수 있게 해놓았다.

30- 델리게이션이 해결할 수 있는 문제는 무엇인가?
  • 오브젝트들이 엮이는것을 막는다
  • 자식클래스 오브젝트 필요 없이 동작이나 모습을 수정한다
  • 임의의 오브젝트에게 작업을 넘겨줄 수 있다

31- 프레임워크의 주 목적이 무엇인가?
프레임워크는 세가지 목적을 가진다
  • 코드의 캡슐화
  • 코드의 모듈화
  • 코드의 재사용성

당신은 다른 앱이나 팀원, 아니면 iOS 커뮤니티에서 프레임워크를 공유할 수 있다. 스위프트의 접근 제어와 함께 쓰면 프레임워크는 강력하게 정의하고 코드 모듈간에 테스트할 수 있는 인터페이스를 선언할 수 있게 해준다.

32- 스위프트 패키지 매니저를 설명하시오
스위프트 패키지 매니저는 스위프트 생태계를 광활하게 증대시켜주며, 리눅스같은 Xcode가 없는 플랫폼에서도 스위프트를 배포하고 사용하기 쉽게 만들어준다. 스위프트 패키지 매니저는 의존성 지옥이라는 문제를 안고있는데, 이것은 서로 의존하는 라이브러리를 사용할때 나타날 수 있다.

스위프트 패키지 매니저는 마스터 브런치 사용만 지원한다.

33- 일대다 패턴으로 엮임을 막기 위한 소통 방법은 무엇인가?
노티피케이션

34- 일대일 패턴으로 엮임을 막기 위한 소통 방법은 무엇인가?
한 오브젝트에서 메소드를 호출하는 것

35- 텍스트 필드의 이벤트를 받기위해 왜 델리게이트 패턴을 사용하는가?
그 이벤트에 대해서는 대부분 한 오브젝트에서만 필요로 하기때문이다

36- inout을 명시한 파라미터가 보통 파라미터와 다른점은 무엇인가?
보통 파라미터가 pass by value여도 inout은 pass by reference로 해준다.

37- 뷰컨트롤러의 라이프사이클 이벤트 순서를 설명하라
몇몇 다른 라이프사이클 이벤트가 있다.

- loadView
컨트롤러가 관리하는 뷰를 만든다. 이것은 뷰컨트롤러가 생성되고 순차적으로 완성되었을때만 호출된다.

- viewDidLoad
컨트롤러의 뷰가 메모리에 올라간 뒤에 호출된다. 뷰가 생성될때만 호출된다.

- viewWillApear
화면에 뷰가 표시될때마다 호출된다. 이 단계는 뷰는 정의된 바운드를 가지고 있지만 화면회전은 적용되지 않는다.

- viewWillLayoutSubviews
뷰컨트롤러에게 그 자식뷰의 레이아웃을 조정하는 것에 대한 것을 알려주기위해 호출된다. 이 메소드는 frame이 바뀔때마다 호출될 것이다.

- viewDidLayoutSubviews
뷰가 그 자식 뷰의 레이아웃에 영향을 준 것을 뷰컨트롤러에 알려주기 위해 호출된다. 뷰가 그 자식뷰의 레이아웃을 바꾸고난 뒤에 추가적인 변경을 하고싶으면 여기서 하면된다.

- viewDidAppear
뷰가 뷰 계층에 추가되었다고 뷰컨트롤러에 알려준다.

- viewWillDisappear
다음 뷰컨트롤러로 트랜지션하기 전에 일어나며, 기존의 뷰컨트롤러가 화면으로부터 사라지기 전에 이 메소드가 호출된다.

- viewDidDisappear
뷰컨트롤러가 화면에서 사라지고나면 이 함수가 호출된다. 뷰컨트롤러가 화면에 없을때, 실헹시키지 않을 작업들을 중지시키는 용도로 이 메소드를 오버라이드한다.

38- LLVM과 Clang의 차이점은 무엇인가?
Clang는 LLVM 툴 체인의 프론트엔드이다(“clang” C Language Family Frontend for LLVM). 대부분 컴파일는 세가지 파트로 나뉜다.
  1. 앞단-front end(lexical analysis, 파싱)
  2. 최적화기-optimizer(추상 문법 트리를 최적화)
  3. 뒷단-back end(기계어를 생성)

앞단(clang)은 코드를 받아서 추상 문법 트리(LLVM IR)을 생성한다.

39- 클래스란 무엇인가?
클래스는 오브젝트의 선언과 그것이 어떻게 동작하는지를 담고있다. 이 경우, 클래스는 오브젝트의 청사진과 같다.

40- 오브젝트란 무엇인가?
오브젝트는 클래스의 인스턴스이다.

41- 인터페이스란 무엇인가?
Objective-C에서 @interface는 Java의 interface처럼 아무것도 하지 않는다. 간단하게 클래스의 공개된 인터페이스를 선언하며, 그것의 공개 API이다.

42- 언제, 왜 구조체대신에 오브젝트를 사용해야하나?
구조체는 값타입이다. 클래스(오브젝트)는 참조타입이다.

43- UIStackView란 무엇인가?
UIStackView는 수평 혹은 수직으로 일련의 뷰 레이아웃을 잡아주는 방법이다. 사용가능한 공간에 담겨진 뷰들을 어떻게 조정할지 정의할 수 있다. 이 글을 꼭 읽어보길 바란다.

44- iOS 앱 상태는 무엇이 있나?
  1. Non-running : 앱이 실행되지 않고 있음
  2. Inactive : 앱이 포그라운드에서 실행되고 있으나 이벤트는 받지 않음. iOS앱은 inactive 상태로 들어갈 수 있는데, 전화나 SMS 메시지를 받을때가 그 예이다.
  3. Active : 앱이 포그라운드에서 실행되며 이벤트도 받고 있음.
  4. Background : 앱이 백그라운드에서 실행되고 코드를 실행하고 있음.
  5. Suspended : 앱이 백그라운드에 있으나 코드가 실행되지 않음.

45- 개잘자가 다뤄야할 가장 중요한 어플리케이션 델리게이트 메소드는 무엇인가?
운영체제는 다양한 상태로/로부터 변경된것을 편리하게 하기 위해 앱 델리게이트를 이용하여 특정 메소드를 호출한다.아래 일곱가지는 개발자가 다룰 가장 중요한 어플리케이션 델리게이트 메소드이다.

application:willFinishLaunchingWithOptions
시작 프로세스가 초기화되면 호출되는 메소드이다. 이 메소드는 앱에서 어떤 코드보다 가장 먼저 실행되는 기회를 갖는다.

application:didFinishLauchingWithOptions
시작 프로세스가 거의 완료되면 호출되는 메소드이다. 앱 창에 표시되기 직전에 호출되는 메소드이기때문에 인터페이스를 준비하고 최종 조정을 할 수 있는 마지막 기회이다.

applicationDidBecomeActive
앱이 활성상태가 되면 앱 델리게이트는 applicationDidBecomeActive 메소드를 통해 콜백 노티피케이션 메시지를 받을 것이다.

또한 이 메소드는 이전의 비활성(inactive) 상태에서 활성상태로 돌아올때마다(전화나 SMS에서 돌아올때마다) 호출된다.

applicationWithResignActive
applicationWillResignActive 메소드가 호출되는데는 여러 조건들이 있다. 임시적인 이벤트(핸트폰 전화같은)가 일어날때마다 메소드가 호출된다. 또한 iOS앱을 "끈다(quitting)"는 말은 프로세스를 죽인다는 뜻이 아니라 앱을 백그라운드(background)로 보낸다는 뜻임에 주의하자.

applicationDidEnterBackgroud
이 메소드는 iOS 앱이 동작하고 있지만 더이상 포그라운드(foreground)에 있지 않을때 호출된다. 다른말로는 사용자 인터페이스가 현재 보여지고 있지 않다는 뜻이다. 애플의 UIApplicationDelegate Protocol Reference에따르면 앱은 작업을 실행하고 리턴하기위해 대략 5초정도 가지고 있는다. 이 메소드가 5초안에 나타나지 않았다면 앱은 죽은 것이다.

applicationWillEnterForeground
이 메소드는 앱이 백그라운드에서 포그라운드로 넘어갈 준비가 되었을때 호출된다. 그러나 앱이 applicationDidBecomeActive 메소드가 호출되지 않은채로 활성 상태로 넘어가지 않는다. 이 메소드는 앱이 활성상태로 오기 전에 이전 실행 상태로 다시 세팅할 수 있는 기회를 준다.

applicationWillTerminate
이 메소드는 종료 이벤트가 발생했을때 알려주는 어플리케이션 델리게이트이다. 홈버튼을 눌러서 앱일 끈다. iOS를 강제로 종료하거나 기기를 끄게되면 applicationWillTerminate 메소드가 호출된다. 이것은 앱 구성, 설정, 사용자 선택을 저장할 수 있는 기회를 제공해준다.

45- 코드 사이닝(code signing)이 하는 일은 무엇인가?
앱을 사이닝하면 iOS가 우리 앱에 사인한 사람이 누군지 식별하고 사인한 이휴로 앱이 변경되지 않았다는 것을 보장한다. 사이닝 식별(Signing Identity)는 애플이 만든 public키-private키 쌍으로 이루어져있다.

46- 프로퍼티와 인스턴스 변수의 차이는 무엇인가?
프로퍼티는 추상적인 개념이다. 인스턴스 변수는 그냥 저장 슬롯이다(구조체의 슬롯처럼). 일반적으로 다른 오브젝트는 그것에 직접 접근할 수 없다고 가정한다. 보통 프로퍼티는 인스턴스 변수를 겟(get)하거나 셋(set) 하지만, 어떨때는 여러곳으로부터 데이터를 불러와 사용할 수도 있고 불러오지 않고서 바로 사용할 수도 있다.

47- 스위프트 패키지 매니저
스위프트 패키지 매니저는 UIKit을 지원하지 않는다. 스위프트 패키지 매니저를 이용하면 여러분은 파일 템플릿이나 다른 프로젝트를 위한 프레임워크를 만들 수 있다.

48- SDK와 프레임워크의 차이를 설명하라
SDK는 소프트웨어 개발 툴의 집합이다. 이 집합은 앱을 만드는데 사용한다. 프레임워크는 기본적으로 소프트웨어 어플리케이션을 개발하는데 사용되는 플랫폼이다. 이것은 특정 플랫폼에서 개발될 수 있는 플랫폼에 필요한 파운데이션을 제공한다. SDK와 프레임워크는 각자의 완성본이며 SDK는 프레임워크로 이용가능하다.

49- 다운캐스팅(Downcasting)은 어떻게 사용할까?
Objective-C에서 다른 타임으로 케스팅할때는 그렇게 하는 방법이 하나밖에 없었으므로 아주 간단했다. 스위프트에서는 두가지 방법의 캐스팅이 존재하는데, 한가지는 세이프하고 한가지는 그렇지 않다.
  • as는 업캐스팅(Upcasting)에 사용되고 타입을 연결된 타입에 타입캐스팅에 사용된다.
  • as?는 실패하면 nil을 반환하는 안전한 캐스팅으로 사용된다.
  • as!는 실패하면 크래쉬가나는 강제 캐스팅에 사용된다. 다운캐스팅이 실패하지 않을 것이라는 것을 알때만 사용한다.

50- do-catch 블락은 왜 있는가?
스위프트에서 에러는 do-catch 블락 안에서 에러를 받아(thrown)서 다룬다(handled),



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

,
제목: 50 iOS Interview Questions And Answers - Part1


 



1- 실시간 랜더링(Live Rendering)은 어떻게 세팅하는가?
@IBDesignable 속성은 특정 뷰에서 인터페이스 빌더를 실시간 갱신시키게 해준다.

2- 동기 & 비동기 작업의 차이가 무엇인가?
동기: 작업이 완료되기 전까지 기다린다.
비동기: 백그라운드에서 작업을 완료하고나면 알림을 받을 수 있다.

3- B-Tree란?
B-Tree는 정렬된 키-값을 제공하는 서치 트리이며 훌륭한 퍼포먼스의 특징을 가진다. 각 노드는 정렬된 그 고유의 요소 배열을 가지고 있고, 그 자식 노드를 위한 또다른 배열을 가지고 있다.

4- NSError 오브젝트는 무엇으로 구성되있는가?
NSError 객체는 도메인, 에러코드, 사용자 정보 딕셔너리의 세가지 부분으로 나뉜다. 도메인은 어떤 카테고리의 에러인지, 어디서부터 온 에러인지 식별하는 문자열이다.

5- 좋아하는 WWDC 영상은 무엇인가?
2015년도의 Protocol-Oriented Programming in Swift이다.
2016년도의 Visual Debuggin with Xcode이다.

6- 바운딩 박스(bounding box)란?
바운딩 박스는 기하학에서 쓰이는 용어인데, 주어진 점의 집합으로 나타내는 가장 작은 단위(넓이나 부피)이다.

7- Objective-C에서는 왜 열거형을 속성에서 strong을 쓰지 않을까?
열거형은 객체가 아니므로 특정 strong이나 weak를 지정할 수 없다.

8- Objective-C에서 synthesize는 무엇인가?
synthesize는 여러분의 프로퍼티를 위해 gettersetter 메소드를 생성해준다.

9- Objective-C에서 dynamic이란 무엇인가?
우리는 NSManagedObject의 자식 클래스를 위한 dynamic을 사용한다. 또한 @dynamic는 델리게이트를 접근자 구현의 역할에 익숙하다.

10- 왜 synchronized를 사용할까?
synchronized는 주어진 시점에서 한 스레드가 블럭안의 코드를 실행시킬 수 있음을 보장한다.

11- strong, weaks, read only, copy의 차이는 무엇인가?
strong는 참조 카운트가 증가될 것이고 그것에 참조는 그 객체의 생명주기를 통해 유지될 수 있음을 의미한다.
weak는 객체에 포인터를 가리키고 있지만 잠조 카운팅을 올리진 않는다. 부모 자식 관계가 만들어질때 종종 사용된다. 부모는 자식에게 strong 참조를 가지지만 자식은 부모에게 weak 참조를 가지게 만든다.
read only, 초기에 속성을 세팅할 수 있지만 변경할 수 없다.
copy는 객체가 생성될때 객체의 값을 복사한다는 의미이다. 또한 변경으로부터 값을 보호한다.

12- 다이나믹 디스패치란(Dynamic Dispatch)?
다이나믹 디스패치는 여러 형태의 오퍼레이션 구현 선택의 처리인데, 이 오퍼레이션은 런타임시 호출하기위한 메소드나 함수이다. 이 의미는 오브젝트 메소드처럼 우리 메소드를 불러내고 싶을 때이다. 그러나 스위프트는 다이나믹 디스패치에 디폴트가 아니다.

13- 코드 커버리지란 무엇인가?
코드 커버리지는 유닛테스트의 측정 값을 측정하기 위한 도구이다.

14- 컴플리션 핸들러란 무엇인가?
컴플리션 핸들러는 우리 앱이 그 작업이 끝났을때 뭔가 API 호출 하게 만들고 싶을때 최고의 편의를 만들어준다. 애플의 dataTaskWithRequest같은 API에서 컴플리션 핸들러를 볼 수 있고 당신 코드에서 꽤 간편하게 사용할 수 있게 해준다.

컴플리션 핸들러는 세가지 인자(NSDate?, NSURLResponse?, NSError?)로 코드를 받는데, 아무것도 반환하지 않는다(Void). 이것은 클로저이다.

15- 디자인에서 유용성 우선순위를 어떻게 세우는가?
유용성을 우선순위 매기기 위해서는 4단계로 디자인 과정을 쪼갠다.
  • 사용자처럼 생각하고 UX를 설계하기
  • 사용자가 밀집된 인구가 아닌 사람들이라는 점을 기억하기
  • 앱을 알릴때 모든 상황에서 유용한지 고려하기
  • 앱을 출시한 이후에서 계속 앱 사용성을 고민하기

16- framebounds의 차이는 무엇인가?
UIViewbounds는 그 고유 좌표 시스템 (0, 0)에 연관된 position (x, y)size (width, height)로 표현되는 사각형이고,
UIViewframe은 그 뷰를 담고있는 부모뷰에 연관된 position (x, y)size (width, height)로 표현되는 사각형이다.

17- Responder Chain이란 무엇인가?
ResponderChain은 받은 이벤트에 응답하기위한 기회를 가진 객체들의 계층을 의미한다.

18- 정규식(Regular expressions)이란 무엇인가?
정규식은 문자열을 통해 어떻게 검색할지 표현하는 특수한 문자열 패턴이다.

19- 연산자 오버로딩(Operator Overloading)이란 무엇인가?
연산자 오버로딩은 이미 있던 연산자의 동작을 바꿀 수 있게 해준다.

20- TVMLKit이란 무엇인가?
TVMLKit은 TVML과 JavaScript, 네이티브 tvOS 앱을 붙여주는 역할을 한다.

21- tvOS의 플랫폼 한계는 무엇인가?
첫째, tvOS는 어떤 종류로더 브라우저를 제공하지 않으며 여러분이 프로그래밍할 수 있는 어떠한 WebKit이나 웹기반 렌더링 엔진도 없다. 이 말은, 여러분의 앱이 웹 링크나 OAuth, 소셜 미디어 사이트같은 어떤것도 링크를 타고 웹브라우저로 갈 수 없다는 의미이다.

두번째, tvOS 앱은 명시적으로 로컬 스토리지를 사용할 수 없다. 제품 출시 시점에 기기들은 32GB나 64GB의 하드 용량을 탑재한다. 그러나 앱은 그 탑재된 스토리지에 직접 쓰기를 할 권한이 없다.

tvOS 앱 번들은 4GB를 초과하지 못한다.

22- 함수란 무엇인가?
함수는 어떤 작업을 수행하기위해 일련의 명령을 함께 모을 수 있게 해준다. 함수를 만들어 놓으면, 코드상에서 계속 사용할 수 있다. 만약 여러분의 코드에서 중복되는 명령을 찾는다면, 함수가 그 반복을 피하는데 답이 되어 줄 것이다.

23- ABI란 무엇인가?
ABI는 외부 라이브러리를 사용하게 되는 앱이라면 중요하다. 프로그램이 특정 라이브러리를 사용하도록 만들어져있고, 나중에 그 라이브러리가 업데이트될때마다 앱을 다시 컴파일 하고 싶지 않을 것이다(그리고 끝 사용자 관점에서, 그 소스를 가지고 있지 않을 것이다). 업데이트된 라이브러리가 같은 ABI를 사용한다면 여러분의 프로그램은 변경할 필요가 없다.



24- 디자인 패턴이 왜 중요한가?
디자인 패턴은 소프트웨어 설계에서 흔히 만나는 문제들에대한 재사용가능한 솔루션이다. 이것은 이해하기 쉽고 재사용하기 쉬운 코드를 짜는데 도움을 주기위한 설계의 템플릿이다. 아래는 아주 일반적인 Cocoa 디자인 패턴들이다.
  • 생성 : 싱글톤
  • 구조 : MVC, Decorator, Adapter, Facade
  • 동작 : 옵져버, Memento

25- 싱글톤 패턴이란 무엇인가?
싱글톤 디자인 패턴은 주어진 클래스에대해 오직 하나의 인스턴스만 존재하도록 보장하고 그 인스턴스를 가리키는 전역 접근을 가진다. 보통 그 싱글톤이 필요한 첫번째 호출에서 싱글톤 인스턴스를 lazy하게 불러와서 생성한다. 

26- Facade 디자인 패턴이란 무엇인가?
Facade 디자인 패턴은 복잡한 서브세스템에 하나의 인터페이스를 제공해준다. 클래스들이나 API들의 집합을 사용자에게 보여주는 것 대신에, 하나로 모인 간단한 API를 보여준다.

27- Decorator 디자인 패턴이란 무엇인가?
데코레이터 디자인 패턴은 그 코드를 수정하지 않고 한 오브젝트에대한 행동자와 책임들을 동적으로 추가한다. 다른 오브젝트로 그 클래스의 동작을 수정하려할때 서브클래스로 만들지 않고 서 가능하게 해준다.

28- 어답터(Adapter) 패턴이란 무엇인가?
어답터는 클래스들의 호환되지 않는 인터페이스 간에 함께 작업할 수 있게 해준다. 오브젝트를 감싸고 그 오브젝트와 소통하기위해 표준 인터페이스를 보여준다.

29- 옵저버 패턴이란 무엇인가?
옵저버 패턴에서 어떤 오브젝트의 상태변화를 다른 오브젝트에게 알려준다.

30- Memento 패턴이란 무엇인가?
Memento 패턴에서는 어딘가 작업을 저장한다. 그뒤에 이 외부화된 상태는 캡슐화 파괴를 하지 않고서 다시 저장될 수 있다; private 데이터는 private로 남는다. 애플에서 Memento 패턴의 구현 중 하나는 Archiving이다.

31- MVC를 설명하시오
  • Models : 도메인 데이터의 역할이나 데이터를 조작하는 레이어를 접근하는 데이터. 'Person' 이나 'PersonDataProvider'를 생각하면 된다.
  • Views : 표현하는 레이어(GUI)의 역할, iOS 환경에서는 접두에 UI로 시작하는 모든 것들이라고 생각하면 된다.
  • Controller/Presenter/ViewModel : Model과 View의 중개인, 보통은 View에서 실행된 사용자 행동에 반응하여 Model을 바꾸고 바뀐 Model로부터 View에 갱신하는 역할을 한다.

32- MVVM을 설명하시오
View와 그 상태의 표현이 독립적인 UIKit. ViewModel은 Model에서 바뀐 것을 부르고, 갱신된 Model로 그 자신을 갱신한다. 그리고 View와 View Model 사이에 바인딩되있으므로 첫번째가 적절하게 갱신된다.

View Model은 실제로 여러분의 Model안에 들어있을 것이고 View에 나타날 정보의 틀을 잡을 수 있다.

33- Objective-C에서 어떤 주석(annotation)을 사용할 수 있을까?
  • _Null_unspecified, 이것은 스위프트스위프트 묵시적 언랩핑된 옵셔널에 연결해준다. 디폴트로 되어있다.
  • _Nonnull, 이 값은 nil이 되면 안될때 정규 참조에 연결해준다.
  • _Nullable, 이 값은 nil이 될 수 있고, 옵셔널로 연결해준다.
  • _Null_resettable, 읽을때, 그러나 재설정한다는 것을 알기위해 이것을 설정할 수 있을때, 이 값은 절때 nil이 될 수 없다. 이것은 프로퍼티에만 적용가능하다.

34- JSON/PLIST의 한계은 무엇인가?
  • 당신의 오브젝트들를 생성하고 디스크에 시리얼라이즈한다(serialize)
  • 이것은 훌륭하나 매우 제한된 유스케이스이다
  • 여러분의 결과물을 필터링하기위한 복잡한 쿼리를 명확하게 사용할 수 없다.
  • 매우 느리다
  • 뭔가 필요할 때마다, 시리얼라이즈나 디시리얼라이즈를 해야한다.
  • 스레드-세이프 하지 않다

35- SQLite의 한계는 무엇인가?
  • 테이블간의 관계를 정의해야한다. 모든 테이블마다 스킴을 정의한다
  • 데이터를 패치하기위해 손수 쿼리를 작성해야한다
  • 데이터를 쿼라한 다음 모델에 매핑 시켜야한다
  • 쿼리는 매우 빠르다

36- Realm의 장점은 무엇인가?
  • 오픈소스 데이터베이스 프레임워크이다
  • 스크래치에서 구현되었다
  • Zero copy object store
  • 빠르다

37- 베터리-효율 위치 추적을 위한 API가 몇개나 있는가?
3개의 api가 있다.
  • 중요한 위치 변화(Significant location changes) : 약 500미터마다 위치가 전송된다.(때로는 1km 이상일 때도 있다)
  • 지역 모니터링(Region monitoring) : 반경이 100m나 그 이상으로의 원형 지역에서 들어가고 나가는 것을 추적한다. 지역 모니터링은 GPS 이후 가장 정확한 API이다.
  • 방문 이벤트(Visit events) : 한 장소(집/사무실)로 들어가고 나오는 방문 이벤트 장소를 모니터링한다.

38- 스위프트스위프트의 주 이점은 무엇인가?
  • 옵셔널타입, 앱의 크레쉬를 방지해준다
  • 내장된 에러 핸들링
  • 클로저
  • 다른 언어에비해 빠르다
  • 타입-세이프 언어
  • 패턴 매칭을 지원한다

39- 스위프트에서 제네릭을 설명하라
제네릭은 특정 데이터타입에 의존하지 않는 코드를 만드는 것이다.

40- 스위프트에서 lazy를 설명하라
프로퍼티에 저장된 lazy로된 초기 값은 이 프로퍼티가 제일 처음 호출되었을때 계산된다. 개발자에게 lazy 프로퍼티가 아주 유용할때가 있다.

41- defer이 무엇인지 설명하라
defer 키워드는 실행이 현재 스코프를 벗어날때 호출될 코드 블럭을 제공해준다.

42- 변수를 어떻게 참조하여 전달할 수 있는가?
변수에는 두가지 타입이 있다는 점부터 언급해야한다. 참조타입과 값타입이다. 이 두 타입의 차이점은, 값타입을 전달할때는 그 데이터의 복사본을 만들어낼것이고, 참조타입의 변수는 그냥 메모리에서 원래 데이터의 포인터를 가리키고 있을 것이다.

43- 왜 higher order 함수를 사용하는 것이 낫나?
파라미터로 함수를 받을 수 있거나 결과로 함수를 반환할 수 있는 함수들을 higher-order 함수라 부른다. 스위프트는 이런 함수들을 CollectionType으로 정의한다.

아주 기본적인 higher order 함수로는 filter가 있다.

44- 동시성(Concurrency)이란 무엇인가?
동시성은 여러분의 프로그램에서 실행 경로를 쪼개는 것이므로 동일한 시간에 실행될 것이다. 일반적인 용어에는 프로세스, 스레드, 멀티스레드, 등이 있다.
  • 프로세스, 실행되고있는 한 앱의 한 인스턴스
  • 스레드. 코드상의 실행 경로
  • 멀티스레드, 동시간에 실행되는 다중 스레드 혹은 다중 경로
  • 동시성, 스케일 조절이 가능한 방법으로 한번에 여러 작업을 실행시키는 것
  • 큐, 큐는 먼저들어간 것이 먼저나온다는 FIFO(First-in, First-out)방식으로 오브젝트를 관리하는 경량의 자료구조
  • 동기 vs 비동기 작업

45- Grand Central Dispatch(GCD)
GCD는 뒤편에서 스레드를 관리하면서 동시적으로 작업을 실행시키는 저수준 API를 제공하는 라이브러리이다. 용어에는 다음이 있다.
  • Dispatch Queues, 디스패치 큐는 FIFO 순서로 작업을 실행시키는 역할을 담당한다.
  • Serial Dispatch Queue, 시리얼 디스패치 큐는 한번에 한 작업만 실행시킨다.
  • Concurrent Dispatch Queue, 컨커런트 디스패치 큐는 시작한 작업이 끝나는것을 기다리지 않고 가능한 많은 작업을 실행시킨다.
  • Main Dispatch Queue, 앱의 메인 스레드에서 작업을 실행할 수있는 전역에서 사용가능한 시리얼 큐

46- Readers-Writers
읽기는 동시에 여러 스레드에서 할 수 있지만 쓰기는 한 스레드에서만 할 수 있다. 이 문제에 대한 해결책은 readers-writers인데, 이것은 이 방법은 동시에 read-only 접근을 허용하고 독점적인 write 접근을 하게 해준다.
아래는 용어들이다.
  • Race Condition(경쟁 상태), 경쟁상태는 공유된 데이터를 2개 이상의 스레드가 접근하거나 동시에 서로 변경하려고 할 때 나타난다
  • Deadlock(데드락), 데드락은 2개 혹은 그 이상의 작업이 다른 작업이 끝나기를 서로 기다리고 있을때 나타난다
  • Readers-Writers problem, 읽기는 동시에 여러 스레드에서 할 수 있지만 쓰기는 한 스레드에서만 할 수 있다
  • Readers-Writer lock, 이런 락은 동시에 공유된 데이터에 read-only 접근은 허용하되 write 작업은 독점적인 접근을 요구한다
  • Dispatch Barrier Block, 디스패치 베리어 블럭은 동시적인 큐와 작업할때 시리얼-스타일(serial-style)의 버틀넥을 만든다

47- NSOperation-NSOperationQueue-NSBlockOperation
NSOperation은 GCD와 비교했을땐 추가적인 오버해드가 있으나, 다양한 작업들 가운데 의존성을 추가할 수 있고, 재사용, 취소, 중지시킬 수 있다.
NSOperationQueue, 이것은 NSOperation들을 만들어서 병렬로 실행시키는 스레드 풀을 제공한다. Operation queue가 GCD의 일부는 아니다.
NSBlockOperation은 하나 혹은 그 이상의 클로저에서 NSOperation을 생성할 수 있게 해준다. NSBlockOperation들은 동시적으로 실행하는 다중 블락을 가질 수 있다.

48- KVC-KVO
KVC는 Key-Value Coding을 의미한다. 이 메커니즘은 개발시점에 프로퍼티 이름을 알아야하는게 아니라, 런타임중에 문자열을 사용하여 한 오브젝트의 프로퍼티들에 접근할 수 있게 해준다. 
KVO는 Key-Value Observind을 의미하고 컨트롤러나 클래스가 프로퍼티 값이 변경되는지 관찰(observe)하게 해준다. KVO에서는 한 오브젝트가 특정 프로퍼티가 변경되는 모든 알림을 달라고 요구할 수 있으며, 그 프로퍼티는 자기자신것도 되고 다른 오브젝트의 프로퍼티도 된다.

49- 스위프트스위프트의 패턴 매칭 기술들을 설명하라
  • 튜플 패턴(Tuple patterns)은 튜플타입에 해당하는 값을 매칭시키는데 사용된다.
  • 타입케스팅 패턴(Type-casting patterns)은 타입을 캐스팅하거나 매칭할 수 있게 해준다.
  • 와일드카드 패턴(Wildcard patterns)은 어떤 종류나 타입의 값을 매칭 혹은 무시한다.
  • 옵셔널 패턴(Optional patterns)은 옵셔널 값을 매칭하는데 사용된다.
  • 열거형 케이스 패턴(Enumeration case patterns)은 존재하는 열거형 타입의 케이스를 메칭한다.
  • 표현식 패턴(Expression patterns)은 주어진 식에대한 주어진 값을 비교할 수 있게 해준다.

50- guard의 장점은 무엇인가?
guard에는 두가지 큰 장점이 있다. 한가지는 다른사람들이 언급해왔던 피라미드 코드를 피한다는 것이다-엄청나게 많은 수의 if let 절은 각 안으로 점점 오른쪽으로 가면서 감싸이게 된다. 다른 이점은 breakreturn을 사용하여 초기에 함수를 빠져나갈 수 있게 한다는 것이다.



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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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



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

    ,

    NSCodingNSObjectProtocol이라는 클래스 프로토콜이 필요하다. 그리고 그 프로토콜은 구제체가 따를 순 없다. NSCoding을 사용해서 인코딩하고 싶다면 가장 쉬운 방법이 클래스로 만들어 NSObject를 상속받는 것이다.

    나는 구조체를 NSCoding으로 감쌀 수 있는 말끔한 방법을 찾았고 거추장스러운 작업 없이 저장할 수 있다. 예제로서 Coordinate를 사용할 것이다.

    두개의 스칼라 프로퍼티를 가지는 간단한 한 타입이다. 이제 NSCoding을 따르고 Coordinate를 감싸는 클래스를 만들어보자.

    이 로직을 다른 유형으로 사용하는 것이 좋으며 단일 책임 원칙에 더 잘 지킨다. 눈치 빠른 독자는 위 클래스에 Coordinate 프로퍼티의 EncodableCorrdinate가 옵셔널이라는 것을 눈치 챘을 것이지만, 꼭 그럴 필요는 없음을 알 수 있을 것이다. 우리는 옵셔널이 아닌 Coordinate를 받는(혹은 실패하게 만드는) 생성자를 만들 수 있는데, 그렇다면 이미 init(coder:) 메소드는 불가능하다. 그리하여 EnabledCoordinate 클래스 인스턴스를 들고 있을때 항상 coordinate를 가지고 있음을 보장해주어야한다.

    그러나 더블(Double)타입(혹은 어떤 원시 타입)을 인코딩 할 때 NSCoder 동작 방법의 특징으로 인해 Any?를 반환하는 decodeObejct(forKey:)로 추출해낼 수 없게 되었다. DoubledecodeDouble(forKey:)로 하여 특정 윈시타입에 특정 메소드가 있다. 불행히도 이 특정 메소드는 옵셔널을 반환하지 않으며, 키가 맞지 않거나 오류가 나면 0.0을 반환해 버린다. 이러한 이유 때문에 coordinate 프로퍼티를 옵셔널로 두기로 했고 옵셔널로 인코딩한다. 그리하여 나는 decodeObject(forKey:)를 사용해 Double?을 얻어냄으로써 추가적인 안정성을 보장했다.

    이제 Coordinate 오브젝트를 인코딩/디코딩 하기 위해 EncodableCoordinate를 하나 만들고 NSKeyedArchiver로 디스크에 저장할 수 있다.

    이러한 추가적인 오브젝트를 만드는게 이상적이진 않으며, 나는 "가능하면 나를 캐싱해줘" 글에서 사용한 SKCache와같은 오브젝트로 작업하기를 좋아한다. 그리하여 내가 인코더와 인코딩 된 사이의 관계를 형식화 할 수 있다면, 아마 매번 NSCoding 컨테이너를 만들지 않아도 될 것이다.

    그 끝으로 두가지 프로토콜을 추가하자.
    그리고 우리의 두가지 타입에 대해 적용시킨다.

    이렇게하여 이제 타입 시스템은 오브젝트 쌍에서 타입과 값 사이에서 어떻게 변환하여 넣고 빼는지 안다.

    블로그 포스팅에서 나온 SKCache 오브젝트는 Encoded 타입을 넘어 제네릭으로 업그레이드 되어왔다. 인코더 값 타입이 그 자신이라는 조건으로, 이것은 이 두 타입 사이에 쌍방향으로 변환할 수 있게 해준다.

    이 타입에서 마지막 퍼즐조각인 savefetch 메소드가 남아있다. saveencoder를 잡아두고있고(사실 이것은 NSCoding을 따르는 오브젝트이다) path로 저장해둔다.

    패칭은 약간의 컴파일러 댄스(compiler dance)를 포함한다. 우리는 언아카이브(unarchive)된 오브젝트를 T.Encoable로 캐스팅 해야하는데, 이것은 encoder 타입이다. 그리고 그 값을 잡아두고, 동적으로 그것을 캐스팅하여 T로 돌려준다.

    이제 캐시를 사용하기위해 한 인스턴스를 만들고 Coordinate 제네릭으로 만든다.

    이렇게 하면 좌표 구조체를 쉽게 저장하고 검색할 수 있다.
    이것을 가지면 NSCoding을 이용해 구조체를 인코딩할 수 있고, 단일 책임 원칙에 따렴, 타입 세이프티하게 만들어준다. 



    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

    ,





    애플은 이번 2016년 WWDC에서 Xcode Source Editor Extensions를 소개했다. 내가 좋아하는 툴을 쉽고 간편한 방법으로 확장할 수 있게 해준다. 이것이 왜 생산성에 영향을 미칠지 의아해 한다면 아래 이야기를 계속 읽어보아라.

    소프트웨어 개발 업계의 수많은 거장들은 이렇게 생각할 것이다. 전문성은 장인정신 자체에서 나오는 것이다. 이 비유는 특히 그렇다. — 도구. 좋은 장인은 좋은 도구가 필요다. 그러나 "못난 일꾼이 늘 연장 탓한다"이라는 속담이 있다.

    크리스토프 고켈은 우리에게 이렇게 상기시켜주었다. "일을 할때 옳바른 연장을 손에 쥐어라" 그러나 적당한 연장이 없다면 우리 스스로 연장을 만들 수 있으며 이 이야기가 바로 우리가 해야할 일이다.

    Xcode는 수많은 써드파티 플러그인을 사용했었다. "사용했었다"라고 과거시제를 쓴 이유는 Xcode8부터 더이상 플러그인을 지원하지 않기 때문이다. 이것은 굉장히 슬픈 소식이지만, 좋은 이유(보안상의 이유나 신뢰성과 관련되있는)에서 이렇게 하게 되었다.

    애플은 무책임하게 IDE를 플러그인으로부터 벗어내어 개발자를 힘들게 하려는게 아니라, 새로운 방식으로 고유의 툴을 만들 수 있게 제공한다. WWDC에서 새 기능에 대해 멋지게 설명해주었지만 우리 App'n'roll'에서는 우리 손으로 직접 코드를 짜보는게 최고의 방법이라 생각하여 그렇게 해보았다.

    JSON Models
    많은 개발자들이 앱에서 네트워킹 작업을 할 것이다. 그리고 JSON을 파싱하여 모델로 만드는 작업을 하는데 시간을 소비한다. 이러한 일은 IDE에서 대신 해줄 수 있는 일이므로 우리는 extension으로 이것을 만들어 보기로 했다. 몇 가정을 하고 작업에 갔는데, 첫째로 현재 수정되는 파일은 JSON 타입이여야한다. 둘째로 감쌓여진(nested) 오브젝트에 연관된 가장자리 케이스는 무시한다.

    중요 note : 우리 예제는 Xcode8.0 베타2에서 만들어졌고, 아마 항상 모든 버전에서 잘 동작하지는 않을지도 모른다. 만약 여전히 엘케피탄에서 작업하고 있다면 Xcode8.0 beta Release Notes에 들어가서 IDE와 Source Editor Extension에 관한 Xcode8.0 beta 이슈를 보아라. 또한 당신의 extension을 실행시킬때 약간의 딜레이가 있다. 만약 프로젝트가 너무 빨리 열린다면 extension이 불러와지기 전에 켜진것이며, 테스트 Xcode의 인스턴스 메뉴에서 사용할 수 없을 것이다.

    이제 새 macOS 프로젝트를 생성하고(UnitTests 박스에 체크했는지 확인해보라) 기본 앱에서 Xcode Source Editor Extension이라는 새 타깃을 추가하여 시작해보자.



    이렇게하면 하나의 Info.plist와 두개의 클래스를 자동으로 만들어 줄 것이다. 코드를 자세히 살펴보기 전에 먼저 plist를 보자. Xcode의 메뉴에 보이는 이름을 바꾸기 위해 Bundle Name과 XCSourceEditorCommandName을 고친다.

    자동으로 만들어진 첫번째 클래스는 XCSourceEditorExtension이고 이것은 extension이 불러와질때 우리에게 알려주는 역할을 한다. 이번 프로젝트에서는 굳이 손 델 필요가 없다. 두번째 클래스는 XCSourceEditorCommand이다. 명령을 내릴때 실행되는 perform(with invocation:, completionHandler:)  메소드가 하나 프로토콜로서 정의되있을 것이다. 이 extension은 현재 파일의 내용물과 그것을 수정하는 방법을 제공한다. 우리는 간단하게 추상화된 층을 사용하여 유닛테스트하기 쉬운 방향으로 만들어갈 것이다. 파일과 함께 인터렉션 하는 것은 SourceFile 프로토콜을 따른다.

    다음 순서는 테스트하기 쉽게 도와주는 오브젝트이다. 이것을 JSONConverter라 부르자.

    SourceFile을 받고 뭔가 문제가 생기면 예외로 넘겨주는 메소드 하나만 가지고 있다. XCSourceEditorCommand와 합치려면 아래처럼 간단한 연결점이 필요하다.

    아직까지는 extension을 실행하고 명령을 해도 아무일도 일어나지 않을 것이다. 이제 TDD 방식을 조금 사용해보자. 먼저 테스트에 기반한 시스템을 만들고 소스파일을 위한 테스트 쌍을 작성한다.

    첫번째 테스트는 유효한 JSON으로 파싱되었는지 체크한다.

    다음으로는 한 JSON 양식으로된 문자열을 JSON으로 파싱한다. 여기 테스트가 있다.

    ...그리고 여기 그 구현이 있다

    이제 약간 꼼수를 써서 NSNumber의 서로다른 타입들을 위해 3개의 테스트를 만든다.
    ...그리고 이것은 한번에 테스트를 통과할 것이다.
    다음 이 코드는 런타임동안 오브젝트의 타입을 체크하고 Swift 타입과 일치하게 만들어준다.

    다음 우리의 리스트가 한 배열로 만들어지는지의 테스트이다.

    The next step is parsing a simple JSON with one String property. Here is the test:

    ...그리고 구현이다.

    마지막으로 감싸진 타입의 파싱 기능은 특별히 우리에게 필요한 것이었다. 이것이 가능한지 알아보는 테스트는 아래 테스트로 충분히 확인할 수 있다.

    감싸진 타입은 다른 타입보다 더 많은 구현이 필요하다. 여기 JSONConverter의 완성된 구현이다.

    끝이다. 우리는 앞으로 모델 네이밍 시스템을 약간 바꿀 예정이다. JSONConverter는 이미 XCSourceEditorCommand와 합쳐졌고 이제 어떻게 Xcode에서 동작하는지 체크하는 것만 남았다.

    첫 Xcode Source Editor Extension을 완성한 것을 축하한다.

    요약
    툴을 사용하는 것은 모든 소프트웨어 개발자에게 매우 중요한 영역이다. 우리 IDE 프로바이더는 항상 우리가 원하는 것을 제공해주는 것이 아니므로 때론 우리 손으로 직접 그 문제를 해결해야한다. 이런 맥락에서 Xcode Extension은 활용하기 좋은 기능이다.

    애플이 이번에 처음으로 발표한 시점이긴 하지만, 우리 개발자에게 AST와 파일 시스템에 접근할 수 있게 해주는 순간 어마어마한 가능성을 가지게 될 것이다. 여기 Github에서 예제코드를 확인해 볼 수 있다.

    사용된 모든 이미지는 CC0 1.0 Universal (CC0 1.0)로 쓰였다. 



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

    ,

    요즘 많은 사람들이 그들의 다음 그들의 다음 모바일 앱 개발에 사용할 플랫폼으로 리액트 네이티브에 다가가고 있다. 이것은 사소한 일이 아니다. 당신의 소프트웨어 개발 플랫폼을 바꾸는 것은 높은 초기 설정 비용을 수반하여 매일 프로그래밍 작업 플로우에 깊숙히 영향을 줄 것이다. 또한 지금까지 만들어놨던 모든 요소들을 전환하는 것도 가장 비싼 비용의 결정 중 하나이다.

    아마 더 중요한 것은, 당신의 소프트웨어 개발 플랫폼 또한 소프트웨어 엔지니어로서 당신을 이루는 것 중 하나이다. 소프트웨어 개발 플랫폼은 한 언어만 사용하게하고, 어느 특정 아키텍처를 우선시하며, 특정 툴을 요구하고, 당신을 그 모든 생태계와 플랫폼의 개발자 커뮤니티와 결혼시켜버리도록 지향(혹은 강요)한다.

    페이스북은 당신이 전환해오기를 바란다.

    그리고 리액트 네이티브 팀의 꾸준하며, 공들인 노력은 그 야망과 같다. 그들은 우리의 전통적인 Xcode/Swift/Objective-C 스택을 대체할 수 있는지 고려해야하는, 너무 근본의 소프트웨어 개발 플랫폼을 만들어왔다.

    이것이 실용적인 변화일 수 있을까? 내가 읽어온 리액트 네이티브에 관련된 블로그 포스팅들은 오히려 피상적인 접근을 알려주었다. 그것의 장점과 단점에대한 깊이있는 접근은 없고, 여러분이 누군가로부터 소프트웨어 개발 플랫폼을 바꿔라고 설득당한 사람이라고 가정하고 이야기를 시작한다.

    지난 몇달동안 리액트 네이티브를 사용하면서, 나는 이것으로 개발할 수도 없고 이것을 사용하기를 추천하지도 않을 플랫폼인 것으로 결론이 나왔다. 이 글의 목표는 Swift 개발에서 리액트 네이티브로 전환하는 것에 대한 빈틈없는 평가의 찬반을 알려주는 것이다. 그리고 그 전환에대해 반대하는 이야기도 할 것이다.

    찬성
    선언 스타일(Declarative style)
    리액트 네이티브와함께 작업할때 기뻤던 한가지는 UI 프로그래밍의 선언 스타일이었다. 리액트 방법으로 하면 UI는 함수의 상태와 프로퍼티들이다. 반면 Cocoa Touch에서는 UI를 명령적으로 짜야한다.

    아래 예제가 내가 말하는 바를 설명해 줄 것이다. 화면 왼쪽 상단에 작은 사각형을 넣고 싶다고 가정하자. 이 작은 사각형은 사용자가 연결되있을 때는 빨강, 연결되지 않을때는 초록을 표시한다.

    아래는 우리가 일반적으로 iOS에서 어떻게 했는지 보여준다.

    명령형 스타일에서는 UI를 갱신하기 위해 이 모든 단계를 명시해주어야한다. isConnected가 바뀌었는지 듣고 있어야 하고, 뷰에 일치하게 갱신해야한다. 우리는 iOS에게 어떻게 상태를 컴퓨트할지 말해줘야한다.

    이제 리액트의 선언 방법과 비교해보아라.

    간단하게 어떻게 여러분의 앱이 주어진 점을 보여주는지 표현했고, 리액트는 데이터 갱신때마다 자동으로 모든 UI 갱신을 관리해줄 것이다.

    리액트의 선언 스타일은 뷰의 render() 메소드로 작용하여 여러분의 UI를 묘사하개 해준다. 리액트 프레임워크는 상태의 모든 갱신을 다시 랜더링하도록 해준다. 여러분의 데이터 갱신(backgroundColor의 갱신이 일어날때)은 자동으로 UI 갱신으로 이어진다.

    이렇게하면 모델의 갱신에대한 응답에서 손수 뷰를 갱신하지 않앋 되게 해준다. 여러분이 이 책임으로부터 벗어나고 싶든 아니든, 리액트는 당신의 설명에따라 갱신 관리를 보장해주는 점은 매우 좋은 점이며, 여러분은 더이상 isConnected 변수에대한 프로퍼티를 관리할 필요가 없다. 당신이 갱신한 모든 것은 상태이다.

    또한 UI 요소들이 결국 클래스의 객체가 아니라 함수인것처럼 생각하게 될 것이다. 그것들은 상태를 받아서 UIKit 오브젝트로 랜더링한다. Component의 역할은, 요청이 들어왔을때 예상되는 상태에서의 그 자신을 반환하는 것이다.

    UI에 대해서는 유용한 방법이라 생각한다. 그리고 MVC로부터 좋은 발전이다. 뷰는 단지 그 자체를 보여주는 역할만 하고 데이터를 관리하지는 않으며, UIViewController에서 시작하기를 좋아하고, 뷰와 컨트롤러 사이 관계에 밀접하게 붙어있는 장님으로 바뀐다.

    빠른 반복
    리액트 네이티브의 매력은 단지 지적임이 아니다. 프레임워크는 실용적이기까지 할 것이다.

    여러분이 리액트 네이티브에서 프로그래밍을 하면, 프레임워크는 당신이 작업하고 있는 자바스크립트 코드를 서브하는 로컬 서버를 만들어 줄 것이다. 앱을 만들면 iOS 시뮬레이터나 기기에서 돌려보고, 리택트 네이티브는 자바스크립트로 만든 모든 변화를 앱에 반영하게 해준다.

    여러분에게 두가지 옵션이 있다.
    • 실시간 리로딩(Live reloading)은 당신이 그 파일에 수정하고 저장할 때마다 앱을 리로드할 것이다. 기본적으로 시뮬레이터로 전환하는 것을 막고 ⌘ + R을 누르지 않아도 된다.
    • 핫 리로딩(Hot reloading)은 앱 전체를 리로드하지 않고 당신이 수정한 부분만 리로드한다. 당신이 네비게이션 스택 깊숙히 테이블 뷰 셀의 UI를 작업하고 있었다면, 그 바뀐점을 보기위해 시작화면에서 셀까지 찾아가지 않아도 된다. 그리고 컴포넌트는 이미 그 안에 있던 상태로 그대로 있을 것이다. 이것은 WYSIWYG 프로그래밍 경험(WYSIWYG programming experience)이다. 앱을 위해 Xcode가 결코 제공하지 않았던 호화로운 기능이다.

    나는 앱을 시작에서부터 보여주고 싶은 ViewController마다마다 AppDelegate에 디버그 메소드를 넣었던 것을 기억한다. 그리하여 앱을 켤때마다 손수 그 페이지를 찾아가고 싶지 않아서 코드로 그 페이지를 찾아가게 했었다. 핫 리로딩은 나를 동굴에서 나온 사람의 기분으로 만들어 버렸다.

    리액트 네이티브의 피드백 루프 주기는 넋이 나갈 정도로 낮다. 앱에서 파일을 저장하고 그것을 눈으로 보기까지 1~2초정도 걸린다. 이것은 Xcode에서 일반적으로 우리가 사용한 빌드&런보다 10배 작게 든다.

    이것은 방송처럼 코드 갱신을 가능하게 해준다. 자바스크립트 코드에서의 어떤 변화라도 앱이 제품으로 출시되어 있는동안 사용자에게 즉시 적용될 수 있다.

    크로스 플랫폼
    품위없고 서튼 이야기를 기억한다.

    "훌륭한 앱이군요! 안드로이드에서도 동작하나요? [...]오, 안드로이드 버전은 언제 배포할 계획이시죠?[...], 오."

    이제 코드베이스가 수백만 개의 추가 장치에서 실행될 수있는 앱을 만들 수 있으며 아웃 리치를 몇 배 증가시킬 수 있다.

    그리고 여러 플랫폼에서 동일한 코드가 더욱 확실하게 같은 모습과 같은 동작의 앱을 만들 것이라 확신한다.

    같은 코드베이스로 여러분 앱의 안드로이드 것을 만들 수 있고, 그래픽적으로든 기능적으로든 iOS쪽과 동등해지게 한다.

    크로스 플랫폼 프레임워크의 명백한 장점은 언제나 시장 개발, 통합 코드베이스 및 앱의 코드베이스를 유지하는데 필요한 통합 기술 세트이다.

    반대
    불확실한 로드맵
    리액트 네이티브를 사용할 때 생기는 중요한 걱정은 그 프로젝트에서의 장기적인 보장이 없다는 것이다. 

    만약 이 프로젝트가 네트워크 라이브러리나 CGPath를 그리는 SVG같은 부가적으로 합쳐서 쓸 수 있는 요소라면, 장기적인 지원이 두번째 걱정으로 밀려날것이다. 리액트 네이티브 개발자들이 그만둬버리거나 리액트 네이티브 개발 속도가 마음에 들지 않으면, 대충 비슷한 라이브러리로 대체하거나 아니면 내 스스로 고쳐 쓸수도 있었을 것이다. 이것은 큰 작업이 될지도 모르겠지만, 이러나저러나 거대한 일은 아니다. 한 라이브러리를 쓸 수 없게 되었다해서 프로젝트 전체가 붕괴되지 않음을 보장하기 위해선 모든 나의 써드파티 라이브러리/CocoaPods을 내 프로젝트에서 충분히 떼어놓으면 된다.

    그러나 리액트 네이티브는 CocoaPod처럼 부가적으로 쓰는 요소가 아니며, 단지 SDK도 아니고, 단지 라이브러리도 아니다. 이것은 완전히 소프트웨어 개발 플랫폼이다. 내 앱이 이것에 "아주 붙어있다"고 말하는 것은 너무 조심스러운 표현이고, 애 앱이 그것에 완전히 의존적이라고 말해야한다. 만약 페이스북이 리액트 네이티브 유지보수를 중단하면, 그것을 쓰고 있던 내 앱은 썩어갈 것이고, 내가 할 수 있는 "리액트 네이티브 대체물"은 없을 것이다. 만약 그것을 내 스스로 개발하려 한다면 리액트 네이티브 소스에도 친숙해야할 뿐 아니라, React.js 코드베이스, 네이티브 CLI 툴, JavaScriptCore에도 익숙해져야 할것이다. 커뮤니티가 이 프로젝트의 생존을 보장해줄까? 아마-만약 그렇게 되버리면 우리가 사용할 수 있을 정도의 개발 속도가 나지 않을 것이다.

    깃헙 저장소에서는 2주에 한번 정도 건강한 속도로 리액트 네이티브 릴리즈가 되고있다. 두개의 분리된 것과 복잡한 소프트웨어 개발 플랫폼을 타겟으로 하는 소프트웨어 개발 플랫폼에는 나쁘지 않다. 페이스북이 오늘 약속할지도 모르지만, 지속적인 네이티브를 유지하기위한 장기간의 약속을하지 않았다. 회사에서는 이 프로젝트에서 손을 떼지 않을 거라는 어떠한 보장도 하지 않고 있다. 가까운 미래뿐만 아니라 여러분의 앱이 살아있는 동안에 말이다. 다른 말로는, 현재 시점에서 리액트 네이티브가 iOS11이나 iOS12에 호환될거라는 보장은 어디에도 없다는 것이다.
    여기서 다음년도로!
    2016년 4월 이후 공식적인 리액트 네이티브 블로그 포스팅은 종료되었다. 그 이후엔 어떻게 되었을까? 그것에대해 페이스북은 걱정없이 침묵만 지키고 있다.
    우리는 곧 계획을 발표할 것이다-Konstantin
    2015년 12월에 리액티브 팀이 말한 것이다. 이 계획이란것은 아직도 발표되지 않고 있다. 확실히 리액트 네이티브의 페이스북 장기간 비용 분석 결과는 당신이 생각한 만큼 멋지게 보이지 않는다.

    다시 말하면, 우리는 개별의 CocoaPod에대해 이야기하는게 아니라, 여러분의 코드가 실행될 수 있는 유일한 플랫폼에대해 이야기하고 있는 중이다. 굉장히 장기적인 시각으로 다뤄야할 중요한 문제이다.

    명백히 위압하는(Patently daunting)
    리액트 네이티브는 복제를 허용하는 BSD-스타일 라이센스와함께 페이스북의 추가적인 특허권 문서(Additional Grant of Potent Right)를 만들었다.(버전2) 이 파일을 넣은 페이스북의 입장은 분명하지 않고, 이 파일 자체도 불분명하다.

    양극의 문서이다. 문서가 끝나자마자 리액트 네이티브를 사용하기 위해서는 "perpetual(영속의), worldwide(널리), royalty-free(무료저작권), non-exclusive(독점불가능한), irrevocable(변경불가능한)" 라이센스를 따라야한다고 한다. 아래 조항들을 제공받으면서 말이다.

    The license granted hereunder will terminate, automatically and without notice, if you (or any of your subsidiaries, corporate affiliates or agents) initiate directly or indirectly, or take a direct financial interest in, any Patent Assertion: (i) against Facebook or any of its subsidiaries or corporate affiliates, (ii) against any party if such Patent Assertion arises in whole or in part from any software, technology, product or service of Facebook or any of its subsidiaries or corporate affiliates, or (iii) against any party relating to the Software.

    나는 변호사에게 이것을 명확하게 해달라고 직접 물어보았다(여러분이라도 그렇게 할 수 있었을 것이다). 그 변호사 말에 따르면 이 조항은 다음으로 요약된다. 내가 만약 페이스북에게 특허권 위배를 주장하며 소송을 걸면 리액트 네이티브를 사용하는 라이센스가 즉시 사라질 수 있다.(아래 Appendix에서 그의 더 세부적인 설명을 인용했다)

    실제로는 특허권으로 페이스북으로 고소하지 못하게 하는 것처럼 보였다. 이런 의미에서, 이것은 페이스북이 내 특허권을 침해할 수 있는 기회를 주는 것처럼 보였다.("당신이 여기에있는 훌륭한 양자 물리학 기술은 당신의 앱에 나쁜 일이 생길 경우 부끄러운 일이 될 것입니다.")

    위 설명이 비판적이라고 생각할지도 모르겠다. 그러나 소프트웨어 라이센스와 특허권을 평가할 때는 비판적으로 해야한다. 알고리즘이 최악의 시나리오에서 얼마나 잘 동작하는지 그 효율을 평가한다. 똑같은 이유로서 소프트웨어 라이센스와 특허권도 그렇게 평가해야한다고 생각한다.

    리니어 서치(linear search) 알고리즘은 타겟값이 리스트의 첫번째로 일어나는 시나리오에서는 최적화된 동작을 할 것이다. 그 이유 하나로, 모든 시나리오에 리니어 서치 알고리즘을 사용하라고 나를 설득하기엔 만족스러운 이유가 아니다. 만약 타겟값이 리스트 마지막에 있을 수 있다면, 내 리니어 서치 알고리즘의 평균 퍼포먼스는 굉장히 나빠질 것이다.

    비슷한 맥락에서, 페이스 북이 내일 일련의 IP를 침해하는 것이 가능하고 React Native를 사용하기위한 라이센스를 취소함으로써 내측으로부터의 보복을 처벌 할 수 있다면, 페이스북이 오늘 일련의 IP 침해를 하지 않았다는 사실은 나를 조금 안심시킨다.(원문: Likewise, the fact that Facebook happens not to be a serial IP infringer today reassures me little if it is possible for Facebook to become a serial IP infringer tomorrow and punish any retaliation from my side by revoking my license to use React Native.) 만약 내일 페이스북이 내 IP를 침해하고(그러나 그것이 내 소프트웨어와 관련은 없다) 그들에게 소송을 걸면,  페이스북에게 내 앱에서 리액트 네이티브를 빼내게 만들 기회를 주는 셈이 되버린다.

    내 앱이 그 플랫폼에 의존적인 것 뿐만 아니라 영리한 내 프로퍼티들까지도 위험하게된다.

    iOS 앱들은 완전히 애플의 재량으로 앱스토어에 들어간다. 나는 쉽지 않은 느낌을 두배로 느끼고 싶지 않다.

    명백히 침묵하는(Patently silent)
    위의 해석이 정확한가? 최악의 시나리오 가능성인가? 이런것들을 걱정할 만큼 좋은 이유가 있는가? 걱정하지 않아도 될 좋은 이유가 있는가?

    수많은 깃헙 이슈와 포럼 포스팅에서, 그들의 법률 부서가 이런 조항 때문에 리액트나 리액트 네이티브를 쓰지 마라고 조언한다고 이야기해왔다. 볍률 부서가 없는 개발자들에게는 유감스럽게도 이 문제들이 모호하게 남아있다.

    좀 더 걱정스럽게도, 페이스북은 이 문제를 더 명확하게 하기 위한 근본적인 노력을 해오지 않고 있다. 2015년 페이스북 오픈소스 블로그 포스팅에서는 이러한 "혼란"에대해 인정했고, 기꺼히 추가적인 특허권 문제에대해 명확하게 할것이라 알렸었다. 깃헙에서 그것과 연관된 5개의 이슈 이후에도 그 불분명함은 사라지지 않고있따.

    여러 페이스북 개발자들이 이 이슈에대해 응답해오고 있는데, 그들이 적어놓은 것에는 안심될만한게 하나도 없으면서 그것을 명확하게 하는 동안에 개발자들을 안심시키려고 노력하고 있다. 그들 중 한명은 Reddit과 HackerNews에있는 이 이슈에대한 토론 링크를 주기도 했었다(도움이 되지 않을 뿐더러 오해시키기까지 했다).

    이것은 해석학과 추측의 작업으로 바뀌어버렸다. 페이스북은 리액트 네이티브 라이센스를 취소 할 수 있을까? 그렇다면 어떤 조건에서?

    자바스크립트
    Swift에서 리액트 네이티브로 전환할때 생기는 심각한 부정적인 면은 기술적 역행이다. 여러분은 자바스크립트에 적응하고 사용해야하는데 이 언어는 다음과 같은 특징을 담고있다.
    • 기술적으로 불완전함
    • 언세이프
    • 늦은 발전
    왜 그렇게 말하는지 보자.

    여기 이후에 예제에 나오는 모든 자바스크립트 코드는 ES2016에 유효하다.

    자바스크립트의 결점
    내가 좋아하는 법퍼 스티커에서 하는 말이다.
    안전은 사고가 없는 것이다(Safety is no accident)
    처음 봤을땐, 이 말은 중의적이었다. 우리는 안전의 정의를 사고가 나지 않는 것과 여러 안전장치 제품의 제안으로 알고 있다.

    자동차에 안전벨트나 에어백이 탑재된 여러 안전장치의 제품은 안전하다고 할 수 있는가?

    물론 대답은 같은 조건에서 둘 다이다.

    운전자는 수많은 안정장치가 탑재된 차를 더 선호할 수 있다. 운전하기 어렵게 만들지라도 막을 수 있는 사고는 최대한 줄일 것이다.

    비슷한 의미에서, 프로그래밍 언어는 프로그래머 에러에 대비한 안전장치를 제공할 수 있다.

    수많은 운전자가 생산성을 위해 안전벨트를 착용하지 않고 운전한다는 사실은 옳바른 주장이 아니다. 비슷하게, 수많은 자바스크립트 개발자들이 생산성을 위해 태생의 언세이프 언어를 쓴다는 것도 옳바른 주장이 아니다.

    프로그래밍 언어에서 안전의 중요성은 iOS 개발 툴이 진화하고 있는것의 진가를 인정하는 것이기도 하다.

    오토 레퍼런스 카운팅이 Objective-C에 처음 나타났을때, 여러분의 iOS 프로젝트에서 그 옵션을 끄고 작업했을 수도 있다. 왜 ARC를 끄는게 나쁜 생각이었을까? 그 이유는, 이제 컴파일러가 당신의 오브젝트 라이프타임 계산을 할 수 있게 되었는데, 당신이 계산하는 것보다 더 빠르게 해준다. "컴파일러가 당신보다 더 똑똑해졌다"는 진언이고, 레퍼런스 카운팅에 관해서는 확실해졌다. 결과적으로 EXC_BAD_ACCESS 런타임 크레쉬를 줄였다는것을 알게되었을때 얼마나 만족했었는지 기억한다.

    Obejctive-C는 한 변수 타입을 id로 설정할 수 있는데, 이것은 "어떤 것이든 모든 타입"의 의미이다. 그러나 컴파일러가 막을 수 있는 크레쉬임에도, 이러한 습관때문에 런타임 크레쉬가 일어날 수 있다. 컴파일러가 해결할 수 있는 문제라면, 컴파일러가 해결하게 놔두고 당신은 다른 문제를 해결하러 가면 된다.

    여러분은 unrecognized selector sent to instance 크레쉬를 기억할 것이다. 응답하지 않는 오브젝트에 메소드를 호출할 때 생기는 크레쉬이다. 타입에러. 내 버그에서 3번째이다.

    당연하게도 Swift를 사용하고나서 나의 첫 반응은 "Objective-C에서 런타임 크레쉬를 막고 싶었던 누군가가 만들었구만"이었다.

    Swift는 안전하다. String을 받길 예상한 함수에 Int값을 넣도록 컴파일러가 허용하지 않는다. 사실 컴파일러가 타입 추론을 할 수 없었다면, 명시적으로 그렇게 할 수 있었을 것이다. 

    그러나 자바스크립트는 프로그래머 에러에 대비한 안전장치가 부족하며, 여러분의 루틴에서 런타임 크레쉬를 막아야하고, 프로그래머 에러를 막아야한다.

    타입 에러
    자바스크립트는 변수나 함수에서 파라미터의 타입을 정해주지 않는다.

    어떤 변수라도 언제든 무엇이든 될 수 있다.

    자바스크립트는 class, typeof, instanceof와같은 키워드로 타입과 클래스 개념이 있다고 믿게 만든다.

    여기서 우리는 다음을 보자.
    • 자바스크립트에서의 "class", "type", "instance" 개념은 주요 프로그래밍 세계에서의 개념과 완전히 다르다.
    • 자바스크립트에서는 타입을 너무 신뢰할 수 없게 정의해서, 이것들이 유용한 용도를 제공하지 못한다.

    이  unrecognized selector sent to instance 크레쉬가 기억나는가? 이것이 이제 여러분 곁을 떠날것이라 생각하는가? 아래에는 리액트 네이티브의 것들이 있다.


    옵셔널 결핍
    Objective-C 코드(혹은 다른 수많은 오래된 언어)에서 엄청나게 많은 양의 버그들은 프로그래머가 부주의하게 nil에다가 메소드를 호출해서 생긴다.

    리액트 네이티브와 자바스크립트 세계에서는 아래 에러를 종종 볼 수 있다.

    그리고 이론적으로나 실무적으로나 막을 수 있다.

    Swift는 옵셔널을 만들어냄으로서 이 문제를 해결했는데(옵셔널이란 nil일 수도 있고 값이 들어있을 수도 있는 타입이다), 이것을 사용할 때는 여러분에게 강제로 nil 체크를 거쳐서 사용하게 만든다.

    함수 기호(function signature)의 결핍
    자바스크립트에서는 함수가 리턴타입을 가지고 있지 않으며, 이 함수가 어떤 타입을 반환할지 모르거나 함수가 어떤것이든 반환할 수 있다.

    좀 더 재미있게 만들어보자. 자바스크립트에서는 어떤 식이든 어떤 함수에의해 어떤 시간에든 계산될 수 있다. 자바스크립트 표준 라이브러리에 있는 map과 parseInt 함수를 사용한 예제를 생각해보자. map은 아마 Swift의 map과 동일한 것이고, parseInt는 문자열을 숫자로 파싱해주는 함수이다.
    parseInt 함수는 2개의 파라미터(val, radix)를 받는데 반해, map은 3개의 파라미터(currentValue, index, array)를 보내기 때문에 이런 엉망의 결과가 생긴다. 이런것들이 여전히 합법적인 자바스크립트이다(일부 사람들이 함수형 프로그래밍 언어에 유용할것이라고 생각하는 언어).

    불변성
    자바스크립트가 지원하는 불변성은 매우 안 좋다.

    const 연산자가 있는데, 이것은 기본타입(primitive)이 바뀌지 않는 것을 보장하는데 도움을 준다. 그러나 기본타입이 아닌 나머지 모든 것은 젤리처럼 유연하다.

    유일한 복사? 아니다. 어떤 유일한 객체라도 언제든지 여러분의 앱에의해 수정될 수 있다. 멀티스레딩에 행운을 빈다. 정말로.

    앱 개발을 힘들게하는 많은 것들은 가변을 쫓고 상태를 유지하는 것이다.

    페이스북의 Immutable.js 문서에서 말한 내용이다(자바스크립트에서 불변 데이터 구조를 만들기위해 설계된 프레임워크이다).

    그러나 아래는 리액트에서 어떻게 불변성을 만드는지이다.

    이 컴포넌트에 넣는 인풋을 props라 부르는데, "properties"의 준말이다. 그들은 JSX 문법으로 속성들을 보낸다. 여러분은 이것을 컴포넌트에 불변할 것이라 생각할 수 있는데, 이것은 절때 this.props에 덮어 씌울 수 없다.

    여러분은 상태를 바꾸지 말자고 공손히 물어본다. 그렇다 위 글은 리액트 네이티브 문서에서 가져온 것이다.

    배열을 믿을 수 없다.
    여러분은 배열이 "행과 열로 된, 비슷한 객체들의 질서정연한 배치"라 생각하는가? 아래를 보고 다시한번 생각해보아라.
    자바스크립트에서 배열은 우리가 원래 배열이라 부르는 그것보다는, 보통의 자바스크립트 객체에 더 가깝다. 정확한 수서성의 결여와 가변성은 잘 동작하기 힘들게 만든다.

    에러 핸들링하기 힘들다
    자바스크립트에서는 경고 없이 런타임이나 예외를 던지는 함수를 만들 수 있다.
    예외로 당신이 원하는 무엇(문자열, Date, 함수 등)이든 던질 수 있다. 팀원의 코드를 잠제적으로 크레쉬할 수 있다는 표시를 함수에 한다던가, 예외의 과정이 어떻게 되는지 명시할 메커니즘이 따로 없다. 문서에서는 대신에 if문을 사용하라고 권장한다.

    당신의 예상하지 못한 예외 처리를 다루기 위해서는 마지막 라인의 방어로 예외를 남겨놓는게 최고이다. 그리고 예상되는 에러를 컨트롤 플로우 문으로 관리하기에도 최고이다.

    디시멀(Decimal)을 지원하지 않는다.
    하드웨어에서 대부분의 소수 자릿수들은 이진수로 정확하게 표현되지 않으며, 많은 프로그래밍 언어(자바스크립트와 Swift를 포함한)는 종종 수학적으로 잘못된 소수 계산을 내놓을 것이다.
    이것이 왜 다른 언어의 표준 라이브러리가 소수 자릿수를 지원하는지 이유이다(예를들어 Swift에서는 Decimal을 사용할 수 있다). 자바스크립트에서는 서드파티 라이브러리나 여러분이 직접 만든 코드에 의지해야한다.

    믿지 못하는 수학
    초등학교 산수에도 주의를 기울여야함을 기억해라. 자바스크립트는 0과의 관계가 복잡하며 이것은 숫자가 아니다.

    언세이프한 초기화
    자바스크립트는 속성을 초기화 할 필요가 없으므로 객체를 만든 후 모순된 상태로 둘 수 있다.

    if 다음에 선택적으로 가능한 커리 중괄호(curly brace)
    if문 뒤에 커리 중괄호는 선택적이다.
    여러분의 컨트롤 플로우 문에 모험의 취향을 추가한다.

    모호한 커리 중괄호
    프로그래머의 의도가 정확하게 추론된 것이 아니라면, 이 언어는 커리 중괄호를 선택적으로 할 수 있게 놔두지 않는다.

    fallthrough 전환
    switch  절에서 break를 깜빡했다면, 아래로 쭉쭉 떨어질 것이다. 또한 Swift에서는 케이스 철저성을 확인하지 않아도 된다.

    어떤것이 '무(없음)'일까? (What's nothing)
     모든 의도나 목적에서 null이 아닌 것이 무가 될 수 있다. 도움이 되지 않는 구별이다.

    그리고 변수에 어떤 데이터가 담겨있는지 아닌지 알고 싶다면 어떨까? 음, 확인이 너무 거추장스럽게 null과 undefined 둘 다 체크해야한다.


    빈약한 표현력
    • 열거형이 없다. 연관타입을 가진 열거형은 말할것도 없다. 신뢰성 높은 상태 표현에 행운을 빌 수 있을까.
    • guard문이 없다.
    • 제네릭이 없다.
    • 컨트롤 플로우문의 표현력을 늘리기위한 where이 없다.

    지극히 느린 진화
    ES2016 때 자바스크립트에 새로 추가된 기능을 보라.
    1. 배열을 위한 includes 메소드
    배열이 특정 값을 가지고 있는지 확인한다. 아래에 어떻게 사용하는지 나와있다.
    이건 사용하지 말자.
    2. **연산자
    지수 연산에 사용하기위한, a**b는 Math.pow(a, b)의 축약이다.

    한번 생각해보면, 파이썬의 **연산자는 파이선1에 있었고, 루비의 **연산자는 20년도 전에 있었던 연산자이다.

    따라서 자바스크립트는 이 기본적인 산수 연산자와 배열을 위한 꽤 제한된 맴버쉽 체크 메소드를 추가하는데 20년이나 걸렸고, 이 특징이 한 해중에 가장 의미있는 것이었다.

    Flow로 구조받자!(Flow to the rescue!)
    Flow는 위의 수많은 불평으로부터 내놓은 페이스북의 대답이다. 이것은 자바스크립트를 위한 정적 타입 체커이고, 여러분 코드에서 변수의 타입을 추론하고 추적할 수 있는 능력이 있으며, 여러분에게 일어날 운명(옮긴이: 실행되었을 때 일어날 오류)을 경고한다.

    위에서 보았던 함수 기호의 결핍으로부터 나온 문제의 예제를 다시 보자.(number가 들어오길 예상한 divideByFour 함수는 문자열을 받는다) 여기엔 Flow가 이것을 어떻게 다루는지 보여준다.

    함수 기호 부재 때문에 생기는 많은 문제를 고쳐준다.

    제네릭 배열에도 동일하게 할 수 있다.

    Flow는 nullability를 잘 다룰 수 있게해주고, 값이 null이되면 안되는데 null일 수 있을때 여러분에게 경고를 띄워 줄 것이다.

    만약 j가 string으로 가정하지만, 언제든 null이 될 수 있으면, Flow는 그 사실을 우리에게 적절하게 알려주려 할 것이다. 따라서 인자로 string을 받되 null이면 안되는 곳에서 인자를 넘겨주기 전에 굳이 null 체크를 할 필요가 없다면 불만을 가질 수 있을 것이다.

    Flow의 기능들은 타입체킹과 주석을 달아주는 것을 넘어, 새로운 구성체(new construct)를 지원한다. 그 중 하나는 리터럴 타입인데, 우리가 만드는데 사용할 수 있으며, 그 예로 열거형이 있다.

    그리고 Flow는 direction이 "North"도 아니고 "South"도 아닌 것을 반환하려하면 뭔가 불평하고 있을 것이다.

    내가 찾은 또다른 유용한 구성체는 유니온(Union) 타입이다. 이것은 딱 하나의 값으로 제한하고, 미리 정의해놓은 타입들 중에서 하나로 제한한다. 아래 예제는 Flow 문서에서 온 것이다.

    Flow은 도움이되는한 꽤 도움이 되는 리액트 네이티브와 훌륭한 동반자이다. Flow 문서에서는 구성 요소의 유형에 올바르게 주석을 추가 할 때 어떤식으로 동작하는지 보여주는 좋은 예시를 제공한다.

    저 주석은 Flow가 어디서 경고를 내는지 알려주며, 여러분 계약에 만족스럽지 않다고 충분히 꾸짖는다.

    Flow는 전적으로 강력한 도구이다. 여기에는 여러분이 해볼 수 있는 유용한 커멘드라인 인터페이스 예제가 있다.
    • suggest는 주어진 파일에대해 타입 주석 제안을 보여준다(suggest Shows type annotation suggestions for given files)
    • type-at-pos는 주어진 파일과 위치에 타입을 보여준다(type-at-pos Shows the type at a given file and position)
    • get-def는 변수나 프로퍼티의 선언 위치를 받는다(get-def Gets the definition location of a variable or property)

    Flow는 flossing와 비슷하다
    이제 자바스크립트가 고쳐졌을까? 아니다.

    Flow가 했을 공학의 노력에 감명받았고, 이것은 자바스크립트의 부모집합(superset)으로 남았으며, 따라서 당신을 태생적으로 약한 파운데이션에서 만들게 한다. 자바스크립트의 결점을 치료하는 어느 종류로서 Flow가 빗발치는 것은 (Monty Python and the Holy Grail으로부터) Swamp Castle and its King을 떠올리게 했다.

    내가 여기 처음 왔을때, 여기는 전부 늪이였어. 여기 늪에 성을 세울거라는 말에 모두가 나를 얼간이라 했지만, 똑같이 만들어서 그냥 보여주었지. 그것이 늪으로 가라앉았고, 나는 두번째 것을 지었어. 그것도 늪으로 가라앉았고 세번째것을 또 지었어. 그것은 불에타고 쓰러져서 늪에 가라앉았어. 그러나 네번째는 버티고 있어. 그리고 이게 너가 얻어야 할 것이고, 영국에서 가장 강한 성을 얻어냈지.

    안전하지 않은 파운데이션을 만들수 있을거라는 말이 파운데이션을 더 안전하게 만들진 않고, 더 효율적인 과정이지도 않을 것이다. 그리고 이 행동을 저지하려는 주장은 그 불합리함을 놓치게 만든다.

    아마 더 의미있게, 자바스크립트 부모집합, 린터(linters), 정적 분석기는 당신이 더 안전한 언어를 고를 수 없는 플랫폼을 다룰때 완화시키는 방법으로서 일시적으로 억제해줄 것이다. 그게 된다면, 그것들을 사용하면서 도움을 구하지 않아도 될 것이다.

    이런 일시적인 조치에는 또다른 근본적인 문제가 있다. 이것이 실제로 그 권위자가 쓰지도 않고 커뮤니티에서도 존경받지 못하면 법같은 이 안전장치는 아주 조금만 의미있게 된다.

    Flow는 당신이 코드바운드로 리액트 네이티브 앱을 만들고 실행시키는 것부터 런타임 크레쉬를 생성하는 것까지 당신의 작업을 멈추게 하지는 않는다. 그리고 그것은 프로그래밍 언어에대한 기본적인 안전 요구사항이다. 만약 에러를 막을 수 있으면, 그 언어는 능동적으로 막으려고 할 수 있고, 안전하지 않은 코드를 짜고 실행시키는 것을 디폴트에의해 방해할 것이다(그 후에 하는게 아닌).

    리턴값이 모호한 함수를 짜라고 팀에게 말할 수는 없을 것이고, 그것에 응답하지 않는 객체에 메소드를 호출하라고 나에게 말할 수도 없으며, 컴포넌트에 proptypes을 정의하지 않고 proptypes이 정의되있다는 코드 리뷰를 하는 동안 나에게 손수 끄집어내라고 할 수 없을 것이다.

    유닛 테스트와 flossing과같은 Flow는 유익하고 선택적이고 지루한것의 저주를 낳는다. It’s in your next year’s resolutions.

    그리고 여기에 현실이 있다. 깃헙에 공개된 .js 파일에 Flow를 사용하는 파일(즉, Flow가 체크할 수 있는 @flow가 포함되있는 파일)이 몇개나 될까? 어림잡아 80,000,000개의 .js파일에서 1,400,000개정도이다. 안전한 자바스크립트를 짜기 위해 2%보다 작은 곳에서 이 툴을 사용하고 있었다.

    여기에 관련된 노트인, awesome-react-native에서 100개가 넘는 리액트 네이티브 저장소들이 있는데, 여기서 Flow를 제대로 사용하고 타입 주석을 사용한 저장소는 한개도 발견할 수 없었다. 오직 리액트 네이티브 튜토리얼에만 Flow를 사용하고 있었다.

    자바스크립트 생태계: 속박과 굴레
    자바스크립트 개발자를 제외한 나머지 모두는 자바스크립트의 결핍에대한 깊은 인상을 받은것 같다. 위에서 내가 설명한 자바스크립트는 흉측한 사마귀가 아니라는 사람들에게는, they’re “quirks” or “gotchas” that you, not your language, have to be on the lookout for.

    왜냐하면 자바스크립트 개발자들은 자바스크립트가 불충분하다고 믿지 않기 때문이다.

    언어에서 불변성을 지원하지 않는다? 그럼 만들면 된다. 언어에서 typing을 지원하지 않는다? 그럼 만들면 된다. 언어에서 decimal을 지원하지 않는다? 그럼 만들면 된다. 언어에서 안전한 함수형 프로그래밍 언어를 허용하지 않는다? 그럼 만들면 된다. 언어에서 nullability를 지원하지 않는다? 그럼 만들면 된다.

    혹은... 당신이 이 기능들이 근본적으로 중요하다고 인정하면, 여러분은.. 바깥세상에서의 그것을 지원하는 언어로 전환해버리면 될까?(잘 모르겠다. 그냥 생각난것을 말한거다)

    내 생각엔, 자바스크립트가 태생적으로 결핍하다고하여 그것을 계속 갈고 닦는게 아니라 다른것으로 대체하려 한다면 그것은 일반화된 고집의 거부(generalized obstinate refusal)이다. 갑자기 증가한 개선이나 버팀목의 결과는 살아있는 생태계의 신호로 보이나, 실제로 이것이 의미하는 것은 이 언어가 근본적으로 중요한 기능의 결핍이 있다는 것이다.

    아래 그림은 이러한 상황을 만화로 잘 표현했다.

    삽질으로부터의 자유는 당신이 필요한 것이 내장된 언어를 선택하는 것에 달렸다. 이러한 삽질은 에너지 낭비이다. 자바스크립트는 다음의 이유로 좋은 소프트웨어를 제작하는데 도움이 되지 않게 하고 있다: 자바스크립트는 우선 당신이 개발하게 만들고, 다른 언어가 제공하는 것에 의존적이게 만든다.

    사슬(Chains)
    자바스크립트는 튀긴 커다란 생선을 가지고 있다. 이 언어는 다양한 버전의 인터넷 브라우저의 몇십억 사용자 입맛에 맞춰주어야한다. 이것이 언어의 발전을 더디게 만든다.

    typeof(null)==='object'를 기억하는가? 음, 예전에 type of null을 null로 바꾸자고 제안했었다. 그러나

    이렇게하면 현존하는 수많은 사이트가 망가지게 될 것이다. 자바스크립트의 스피릿에 적합하지 않다.

    그리고는 이 제안이 거절되었다. ES06에의해 null은 여전히 object이다.

    자바스크립트의 발전 과정은 필요에의해 입맛에만 맞추고 있다.
    • 수많은 구식 버전의 브라우저 사용자들
    • 각색의 브라우저 벤더 집합
    • 수십억의 사이트와 그 사이트의 각 개발자들
    이것들은 훌륭한 민주주의이긴하나 한편으로는 다른 언어에비해 굉장히 더디게 발전한다. 그리고 이것은 이식성이 좋은 만큼 개발자의 인간환경공학은 좋지 않을 것이다.

    넓은 시각(Wider angles)
    역사적인 관점에서, 한 언어가 인기를 얻고나서, 그것을 넘겨받고싶은 점유자들이 거부하게되는 패턴은 겉으로보기에 불충분한 언어에게 나타나는 익숙한 패턴이다.

    여기 따뜻한 모닥불 옆에 앉아서 내 얘기를 좀 들어봐라.

    1994년으로 돌아가보자. Richard Stallman은 "왜 Tcl을 쓰면 안되는가"라는 다소 모호한 타이틀로 comp.lang.tcl 뉴스그룹(news group)에 글을 하나 썼다. Stallman은 프로그래밍 언어로서 Tcl의 단점을 심술궂게 꾸짖었고, 목적에 맞지 않는 Tcl을 고발했다. 자바스크립트에대해 내 불평과 유사하게 매우 장관이었다.

    Emacs의 주요 수업에선 확장을 위한 언어는 단지 "확장 언어"가 될 수 없다고 한다. 실제 프로그래밍언어가 되야하고, 상당한 양의 프로그램을 짤 수 있어야하며, 그것을 유지보수할 수 있게 설계되야한다. 왜냐하면 사람들이 그것을 바라니까! [...] Tcl은 진지한 프로그래밍 언어로 설계되지 않았다. 이것은 "스크립트 언어"가 되기위해 설계되었으며, "스크립트 언어"라 가정하는 것은 실제 프로그래밍 언어가 되려고 할 필요가 없었음을 의미한다. 따라서 Tcl은 하나의 능력을 빼먹었는데, 배열이 부족하다; 링크드리스트를 만들 수 있는 구조체를 빼먹었다. 그것은 가짜로 넘버를 가지고 있는데, 매우 느리지만 동작은 한다. Tcl은 작은 프로그램을 만드는데 적합하나, 그 넘어를 하기엔 적합하지 않을 것이다.

    이 글은 1994년에 Tcl 전쟁을 촉발시킨 글이다. 그가 들었던 대답들중에 가장 인상적이었던 John Ousterhout(Tcl을 만든 사람)의 대답이다.

    언어 설계자로서 이 언어가 왜 선천적으로 더 낫고 더 나쁜지 우열을 가리는 토론을 사랑한다. 그러나 이런 건방진 주장에는 많은 문제가 있다. 궁극적으로 모든 언어의 이슈는 그 사용자가 원하는 것을 투표할 때 합의가 생긴다. 만약 Tcl이 그들이 사용하는 것보다 더 생산적이게 만들었다면(혹은 이미 그렇게 되었다), 다른 언어 사용자들이 이것이 더 낫다고 하면서 온다면, 그때 사람들은 언어를 바꾼다. 이것이 법칙이고 이게 맞다.

    Outsterhout의 반격은 내가 활동하고 있던 많은 사람들 앞에서 이루어졌고, 그의 일격은 더 가까이서 볼 수 있었다.

    (몇몇 독자들은 Ousterhout가 순회적으로 말했다는 것을 알아차릴 것이다. "왜 사람들이 언어를 바꿀까? 그게 더 낫기 때문이다. 왜 그것이 더 나을까? 사람들이 많이 쓰기 때문이다." 그의 주장은 더 많은 설명이 필요함에도 불구하고 조금도 하지 않았는데, 우리는 종종 특정 기술 스택(예를들어 여러분이 생각하고 있는 리액트 네이티브)에 여러 주장에서 이런 추런 라인을 보았었기 때문이다)

    Ousterhout에 따르면, 프로그래밍 언어는 그 언어가 더 낫다고 말할 수 있는 두가지 특징을 가지고 있는데, 바로 선천적인 특징들(프로그래밍 패러다임, 문법, 표준 라이브러리. 이식성과같은 그 고유의 특성)과 후천적인 특징들(수많은 개발자들이 폭넓게 채택한것)이다. 만약 언어A가 언어B보다 후천적으로 낫다면, 선천적으로 언어B가 언어A보다 더 낫다고해도 별로 소용없어질것이다.

    다른말로 하자면, 채택되는 것이야말로 기술 우선순위에서 최고봉이다.(언어 설계자가 어떤것을 우위로 정의했는지 상관없이 말이다)

    Ousterhout 주장에 딱 맞아보이는 앨범 커버이다.

    더 많은 팬을 가진 가수가 그를 더 낫게 만든다. 요점은 팬이나 히트 기록의 양에의한 인기이며, 이것이 가수를 평가하는 좋은 기준이다.

    그러나 언어가 널리 채택되고 그것으로 만들어진 멋진 앱들을 자랑할 수 있다는 사실로는 그 메리트로 당신의 판단에 설득시킬 수 없을지도 모른다.

    복잡해보이거나 보기에 멋진 앱이 기술X로만 만들었다는 의미는 기술X가 복잡해보이거나 보기에 멋진 앱을 만들기에만 최적화되있고, 보통 일에는 최적화되있지 않다는 뜻일 수도 있다.

    전세계에서 열리는 모래성 대회는 당신도 만드는데 쓸 수 있는 모래와 물로 아름다운 구조물을 만들어서 보여준다. 이것은 모래가 좋은 장난감임을 주장할 수 있는 근거가 될 수는 있으나, 모래성 대회를 근거로하여 모래로 여러분의 집을 지을 수 있다고 납득하기는 어려울 것이다. 그러나 많은 이들이 납득되어 버렸다.

    The King of Swamp Castle은 수많은 해커의 왕이다.

    비슷하게, 인기는 프로그래밍 언어를 평가하기엔 신뢰할 수 없는 기준이다. 아주 기본적인 이유로, 인기는 벤더를 속박시키거나(브라우저에서만 돌아가는 프로그래밍 언어가 된다던지), 벤드웨이건 효과나, 레거시(legacy) 코드베이스의 결과를 매우 잘 만들지도 모른다.

    프로그래밍 언어의 고유 특징들(프로그래머 에러에 대비한 안전장치, 표준 라이브러리, 이식성 등)은 여러분의 기준에 기반해야한다. 이것은 생산성 향상의 기본요소이다.

    사실은 많은 것들이 부정적인 생산성을 이끌기도 한다. 그 예이다.
    • 피할 수 있는 에러를 만드는 당신을 막는 안전장치의 부족
    • 많은 목적에 맞지 않는 작은 표준 라이브러리
    • 당신의 의도를 명확하게 표현하지 않는 문법과 의미론(syntax and sementics)
    • 더딘 발전
    • 위의 모든 것들(자바스크립트)

    의존성
    리액트 네이티브는 총 648개의 의존성을 가지고 있다.

    특별히 놀랄 것 없게도, 의존성 체인은 세상의 npm 중에 길수 있으므로, 리액트 네이티브의 패키지 매니저이다.(원문: Not particularly surprising, as dependency chains can be long in the world of npm, React Native’s package manager.)

    이 광경은 오픈소스의 동료관계이다. 여러분의 앱은 600명 이상의 사람들의 지속된 노력으로 만들어졌다.

    이게 함정이기도한데, 여러분은 648명의 자원 봉사자가 그 라이브러리를 유지해주어야 앱이 잘 돌아가며, 그 자원봉사자들로부터 어떤 약속 따윈 없다.

    그들의 라이센스가 당신의 소프트웨어에 맞게 유지될까? 희망적으로?

    그리고 그 구현이 모두 보안면에서 최고의 실천을 하고 있을까? 혹은 648개의 별개의 잠재적인 보안 리스크를 묵인할 수 있겠는가?

    더 나은 제안들
    크로스-플랫폼 개발 때문에 리액트 네이티브를 선택하게 되었다면, 다른 옵션도 생각해볼 필요가 있다.

    리액트 네이티브는 지금 Xamarin과 Appcelerator라는 두개의 크로스 플랫폼 개발과 경쟁하고 있다.

    Xamarin과 Appcelerator 둘 다 iOS, 안드로이드, 윈도우 폰을 지원한다. 그리고 둘 다 아래의 것들도 지원한다.
    • 더 종합적인 API
    • 더 성숙한 IDE
    • 더 나은 문서
    • 명확하고 친숙한 라이센싱
    • 동등한 퍼포먼스(더 낮진 않더라도)

    Xamarin 개발은 C#으로 하는데, 이것은 자바스크립트에 비해서 버그를 줄여주는 경향이 있고, 더 표현력이 좋다(more expressive). 만약 자바스크립트를 쓰는 개발자라면 Appcelerator 개발이 자바스크립트로 할 수 있다.

    리액트 네이티브와 비교하여 Xamarin과 Appcelerator 둘 다 멀리 보았을때 더 나은 가능성을 가지고 있다. Appcelerator(Titanium의 아버지)(3억  5천만 기기에 앱으로 실행 되었었다)는 2016년 1월에 인수되었고 Xamarin(포춘지 선정 500 대 기업에서 100 기업 이상 사용했다)는 마이크로소프트에의해 2016년 2월에 인수되었다. 둘 다 이전보다 8천만달러 이상의 주식형펀드가 올랐다.

    나에게 지원해주기 위한 비즈니스의 회사가 빽으로 있다면, 프로젝트들은 이 크로스 플랫폼이라는 레드오션에서 신뢰할 수 있는 소프트웨어 개발 플랫폼으로 살아남고 성장해야한다(최근에 세어보니 10개 이상의 활발한 개발 프로젝트들이 있었다).

    (의미있는 언급으로 Flutter(이하 플루터)에 대해 이야기 하겠다. 이것은 구글이 만든 크로스-플랫폼 개발 플랫폼이다(iOS, 안드로이드 둘다 지원한다). Dart라는 언어를 사용하는데, 이 언어는 자바스크립트보다 안전하고 표현력도 더 좋다. 또한 메트리얼 디자인 원칙을 따르게 도와주고, 네이티브하게 컴파일 된 코드를 만들어준다. Xamarin과 Appcelerator와는 다르게 오픈소스이며, 아직 제품으로 준비되지는 않았지만 그럼에도 불구하고 약속해놓았다.)

    결론
    좋은 소프트웨어 개발 플랫폼은 4가지 필수 특징을 가진다.
    • 이식성 - 한가지 이상의 플랫폼을 타겟으로 한다.
    • 생산성 - 성숙한 IDE와 다른 개발 툴, 문서, 그리고 표현력 있는 그 개발 언어
    • 안전 - 당신이 만들 수 있는 실수를 그 플랫폼이 얼마나 막을 수 있는지의 정도
    • 지속성(longevity) - 당신의 앱이 살아있는 동안 플랫폼이 얼마나 오래 가는지
    비록 우리 산업이 아직까지는 이것을 가늠하기위한 확립된 오픈 표준이 없지만, 나는 내 경험과 조사를 바탕으로 내 평가를 공유하고 싶다.


    리액트 네이티브의 강점을 이식성과 생산정의 면에서 보자면 너무 과하고, 안전, 장기간 프로젝트에는 불확실성, 위압적인 특허 라이센스에는 부족함이 있다.


    Xamarin과 Appcelerator와같은 성숙한 플랫폼은 최고의 이식성(윈도우 폰을 지원한다), 성숙된 개발 툴, 프로그래밍 언어를 선택할 수 있게 해주는 것을 제공하는데, 언어 선택으로 생산성과 안전성을 얻어낼 수도 있다. 그리고 이것들이 잘 투자받은 회사의 핵심 제품이라는 사실이 장기간의 면에서 안심시켜준다.


    Swift 개발은 안전성, 지속성, 생산성 면에서는 좋다. 이식성의 면은 상대적으로 낮으나 무시해도 되는 수준이다. 안드로이드 버전을 개발 할 수 있게 해주는건 아니지만 macOS 버전이나 백엔드 서버 앱을 만들 수 있게 한다.





    여기까지가 내가 왜 리액트 네이티브 개발자가 되지 않는지의 그 이유였다.

    감사
    이 글의 초안에 도움을 준 Rami Chowdhury와 Alkis Papadakis에게 감사를 표한다. 그리고 난해한 IP 텍스트들을 해독하는데 도움을 준 Greg McMullen에게도 감사하다.

    바뀐 이력
    이 글이 바뀐 이력은 여기에서 확인할 수 있다.

    커멘트
    이 글에 대한 토론 에 팔로우하여 참여하고 싶으면 Hacker News, Hacker News(Again)/r/programming로 와라.

    참조
     



    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

    ,


    이 튜토리얼은 비도오 시리즈로 나와있다.
    라이브 코딩과 함께 비디오 튜토리얼을 보고 싶다면 이 글의 내용으로 녹화한 비디오 시리즈를 보아라: Egghead - Introduction to Reactive Programming

    여러분은 Rx, Bacon.js, RAC등의 다양한 것들을 포함해서 리액티브 프로그래밍이라 불리는 새 것을 배우는데 관심이 있을 것이다.

    이것을 배우기는 쉽지 않은데, 좋은 도구가 없다는 점이 한 몫을 더한다. 내가 처음 배우려할 때도 튜토리얼을 찾아보고자 했다. 나는 소량의 가이드를 찾아냈지만 그것도 겉핥기 식이었지 전체 구조를 리액티브로 만드는 가이드는 없었다. 또한 라이브러리는 당신이 몇몇의 기능만 이해하려 할 때 크게 도움이 되지 않을 때가 종종 있다. 아래를 보면 무슨 의미인지 공감할 수 있을 것이다.

    Rx.Observable.prototype.flatMapLatest(selector, [thisArg])
    Projects each element of an observable sequence into a new sequence of observable sequences by incorporating the element's index and then transforms an observable sequence of observable sequences into an observable sequence producing values only from the most recent observable sequence.

    아이고..

    나는 두가지 책을 읽었는데, 하나는 그냥 큰 그림을 설명하는 것이었고, 다른 하나는 리액티브 라이브러리를 어떻게 사용하는지 깊이 이야기한 책이었다. 나는 마침내 실제 만들어 보면서 리액티브 프로그래밍 배우는 것을 힘겹게 끝냈다. 나의 Futurice라는 일에서 리액티브 프로그래밍을 실제 프로젝트에 사용하였고 내가 문제가 생겼을 때 몇몇 동료로부터 도움을 얻을 수 있었다.

    이것을 배우면서 가장 어려웠던 점은 리액티브하게 생각해야한다는 것이었다. 자꾸 이전의 명령형이나 상태를 가지는 전형적인 프로그래밍으로 가려는 것을 강제로 새로운 패러다임으로 작업하게 만들었다. 이 부분에 대해서는 인터넷에서 어떤 가이드도 찾지 못했다. 나는 사람들이 새로 이것을 시작할 수 있게 어떻게 리액티브하게 생각하는지에 대한 실질적인 튜토리얼이 필요하다고 생각했다. 이 생각이 조금 바뀌고 나면 라이브러리의 문서가 좀 이해가 될 것이며, 이 글이 당신에게 도움이 되길 바란다.

    "리액티브 프로그래밍이 뭔가?"
    인터넷 상에는 좋지 않은 설명과 정의가 있었다. 위키피티아는 너무 일반적이고 이론적이게 설명해 놓았다. 스택오버플로우의 인기있는 답변은 새내기들에게 적합하지 않아 보인다. 리액티브 매니페스트는 당신 회사에 프로젝트 매니저나 영업자에게나 보여줄 법 하다. 마이크로소프트의 Rx용어 "Rx = observables + LINQ + Schedulers"는 마이크로소프트한것이 우리에게 혼동만 남겨준다. "리액티브"나 "변화의 전파(propagation of change)"와 같은 용어는 일반적인 MV * 나 즐겨쓰는 언어가 이미 한 것과 다른 내용이 아니다. 물론 내 프레임워크의 뷰는 모델에게 리액트한다. 물론 변화는 전파된다. 그렇게 하지 않으면 아무것도 렌더링 되지 않을 것이다.

    그럼 이제 잡소리는 집어치우자.

    리액티브 프로그래밍은 비동기 데이터 스트림으로 프로그래밍 하는 것이다.

    뭔가 새로운 것이 있는게 아니다. 이벤트 버스나 여러분의 일반적인 클릭  이벤트는 굉장히 비동기적인 이벤트 스트림인데, 이것을 옵져브 할수도 있고, 다른 추가적인 효과를 줄 수도 있다. 리액티브 스테로이드(옮긴이: 화학에 관련된 용어)로부터 떠오른 아이디어이다. 여러분은 클릭이나 마우스 호버(hover) 이벤트 뿐만 아니라 어떠한 데이터 스트림도 만들 수 있다. 변수, 유저입력, 프로퍼티, 캐시, 데이터 구조체등 어떤것도 스트림이 될 수 있으며, 스트림은 싸고 범용적이다. 예를들어 당신의 트윗 피드가 클릭  이벤트와 같은 방식으로 데이터 스트림이라고 상상해보아라. 당신은 그것을 듣고(listen)있다가 적절하게 반응을 하면 된다.

    최상부에는 어떤 스트림에서도 합치고, 생성하며, 필터링할 수 있는 툴박스를 제공한다. 이것이 "함수형"의 마법 효과이다. 한 스트림은 다른 것의 입력으로 사용될 수 있다. 여러개의 스트림까지도 다른 스트림의 입력으로 사용될 수 있다. 두 스트림을 합칠 수 있다(merge). 원하는 이벤트만 골라내어 새 스트림으로 필터링할 수 있다(filter). 한 스트림을 새 스트림으로 각 값들을 매핑할 수 있다(map).

    리액티브에서 스트림이 중심이 되면, 스트림을 잘 살펴보자. 우리는 친숙한 "버튼 클릭" 이벤트로 이 이야기를 시작하려한다.


    한 스트림은 시간 순서로 정렬된 이벤트 순서이다. 이 스트림 값(어떤 타입), 에러, '완료'신호 3개의 상태를 내뱉을 수 있다. "완료" 지점이 되었다고 하자. 예를들어 버튼이 있는 현재 윈도우나 뷰가 닫혔을 때 이다.


    우리는 발생된 이벤트를 비동기적으로 잡아내야하는데, 값을 내뱉을 때 실행되는 함수, 에러를 내뱉을 때 실행되는 함수, '완료'를 내뱉을 때 실해오디는 함수를 선언해놓고 잡아낼 수 있다. 때론 뒤에 두가지 함수는 생략하고 값을 위한 함수만 사용할 수도 있다. 스트림을 "듣고(listen)"있는 것은 '구독하고 있다'고 부른다. 우리가 정의한 함수는 옵저버이다. 스트림은 옵저베이블(observable)이다. 이것이 바로 옵저버 디자인 패턴이다.

    이제는 위 그림 대신에 아스키(ASCII)로 설명을 대신하겠다.
    --a---b-c---d---X---|->
    
    a, b, c, d are emitted values
    X is an error
    | is the 'completed' signal
    ---> is the timeline
    여기까지는 꽤 친숙할 것이고 여러분을 지루하게 하고 싶지 않다. 이번에는 원래의 클릭 이벤트 스트림에서 변형하여 만든 새 클릭 이벤트 스트림을 만들어보자.

    먼저 숫자를 세는 스트림을 만들자. 이 스트림은 버튼이 얼마나 클릭됐는지 나타낸다. 일반적인 리액티브 라이브러리에서는 map, filter, scan과 같이 제공되는 많은 함수를 가지고 있다. 만약 당신이 clickStream.map(f)처럼 함수를 호출하면 clickStream으로부터 새 스트림을 반환받는다. 원래의 clickStream은 손대지 않고 말이다. 이러한 특징을 불변성이라 부르며, 마치 팬케잌에 시럽이 좋듯 리액티브에 좋게 해준다. 이것은 clickStream.map(f).scan(g)와 같이 함수 체이닝을 가능하게도 해준다.
      clickStream: ---c----c--c----c------c-->
                   vvvvv map(c becomes 1) vvvv
                   ---1----1--1----1------1-->
                   vvvvvvvvv scan(+) vvvvvvvvv
    counterStream: ---1----2--3----4------5-->
    
    map(f) 함수는 당신이 만든 f함수에 따라 만들어진 각 값으로 대체한다. 우리의 경우 각 클릭마다 숫자 1로 매핑시켰고 scan(g) 함수가 값 x=9(acculated, current)를 실행하여 스트림의 모든 이전 값을 합한다. 여기서 g함수는 단지 더하는 함수이다. 그리고 counterStream은 클릭이 일어난 수를 내뱉는다.

    리액티브의 실제 힘을 보여주기 위해 여러분이 갑자기 '더블 클릭' 이벤트의 스트림이 필요하다고 해보자. 좀 더 흥미롭게 만들기 위해 더블클릭처럼 트리플클릭도 되는지 혹은 다중클릭(더블클릭 이상)까지도 되는지 보자. 깊게 숨을 들이마쉬고 생각해보자. 전통적인 명령형의 상태와 함께 사용한 방식에서는 어떻게 구현했는지 상상해보라. 아마 꽤 지저분하게 몇몇 변수는 상태를 가지고 있었을 것이고 몇몇 변수는 시간차를 세고 있을 것이다.

    흠 리액티브에서는 꽤 간단하게 해결된다. 사실 로직은 4줄의 코드 밖에 안된다. 그러나 지금 당장은 코드를 보지 말자. 당신이 초보자이든 숙련자이든 스트림을 이해하게 만드는 데는 그림으로 설명하는 것이 최고이다.


    회색 상자들은 한 스트림에서 다른 스트림으로 변경시키는 것이다. 먼저 250ms동안 이벤트 발생이 없으면 클릭했던 이벤트들을 리스트로 모은다. (이게 buffer(stream.throttle(250ms))가 무슨 일을 하는지의 이야기이다. 이 시점에서 굳이 깊게 이해하려고 하지 말자. 지금은 단지 리액티브로 데모를 만들고 있다.) 그리고 map()을 적용시켜 각 리스트의 크기를 정수로 매칭시키고, 매핑한 리스트의 스트림으로 결과가 나온다. 마지막으로 filter(x>=2)함수를 이용해 정수 1은 무시한다. 우리가 의도한 스트림으로 만들기 위해 사용한 것은 3개의 오퍼레이션이 다다. 이제 우리가 원하는 곳에 반응해주기위해 이것을 구독할 수 있다.

    여러분이 이 아름다운 방법을 잘 음미했길 바란다. 이 예제는 사실 빙산의 일각이다. API 응답 스트림처럼 다른 스트림을 이 오퍼레이션으로 적용할 수 있고 또 수많은 다른 함수들도 사용할 수 있다.

    "왜 RP 적용시키기를 고려해야할까?"
    리액티브 프로그래밍은 당신 코드의 추상화 수준까지 올라왔으므로, 수많은 세부구현을 끊임없이 할게 아니라 비즈니스 로직을 정의하는 이벤트의 상호 의존에 초점을 맞출 수 있었다. RP의 코드는 더 간결해 질것이다.

    데이터 이벤트에 관련한 수많은 UI 이벤트와 높게 소통하는 현대 웹앱과 모바일앱에서는 그 이점이 더욱 분명하다. 10년전에는 백엔드에 긴 형식으로 보내면 간단하게 프론트엔드에 렌더링 시키는 것이 웹페이지의 인터렉션이었다. 앱은 더 실시간으로 진화되었다. 몇 컨텐츠는 연결된 다른 사용자에게 실시간으로 반영되는 것처럼 가볍게 한 필드를 수정하는 것이 바로 백엔드에 저장하는 트리거가 될 수 있다.

    오늘날의 앱은 사용자에게 높은 소통을 경험하게 해주는 수많은 종류의 실시간 이벤트가 들어가있다. 우리는 이것을 다루기위해 적절한 툴을 골라야하는데, 리액티브 프로그래밍이 그 해답이다.

    예제와함께 RP처럼 생각하기
    실제 예제로 들어가보자. 어떻게 RP처럼 생각하는지 단계별로 설명하는 실세계의 예제이다. 가짜 예제도 아니고 개념의 일부만 설명하기 위한 예제도 아니다. 이 튜토리얼이 끝날때쯤 우리가 왜 이것들을 했는지 아는체로 실제 함수형 코드를 만들 것이다.

    나는 다음의 이유로 자바스크립트RxJS를 사용할 것이다. 자바스크립트가 현재 가장 인기있는 언어이며 Rx* 라이브러리 패밀리가 많은 언어나 플랫폼에서 가장 폭넓게 쓰이고 있다.(.NET, Java, Scala, Clojure, JavaScript, Ruby, Python, C++, Objective-C/Cocoa, Groovy 등등) 따라서 당신이 어떤 툴을 쓰든, 이 튜토리얼을 따라오면서 구체적인 이점을 얻을 수 있을 것이다.

    "Who to follow" 제안 박스 구현하기
    트위터에서는 당신이 팔로우할 수 있는 다른 계정들을 제안하는 UI 요소가 있다.

    우리는 이 핵심 기능을 모방해 볼것이다.
    • 시작하면 API로부터 계정 데이터를 불러오고 3가지 제안을 띄운다.
    • "Refresh"를 클릭하면 3개의 또다른 계정을 띄운다.
    • 계정중에 'x' 버튼을 누르면 그 계정은 사라지고 다른 계정을 띄운다.
    • 각 줄은 계정의 아바타를 띄우고, 누르면 그들 페이지로 이동한다.
    다른 기능이나 버튼은 부수적이기 때문에 남겨 둘 것이다. 그리고 트위터 API는 최근들어 승인이 필요하게 되었으므로 대신에 깃헙의 팔로잉 사람들을 위한 UI를 만들어보자. 여기에 사용자를 얻어내기 위한 Github API(링크)가 있다.

    완성된 코드는 http://jsfiddle.net/staltz/8jFJH/48/ 여기에 준비되있다.

    요청과 응답
    어덯게 Rx로 이 문제를 접근할까? 흠, 시작하기 앞서, (대부분) 모든 것은 스트림이다. 이것이 Rx의 주문이다. 가장 쉬운 "시작하면 API로부터 3가지 제안을 띄운다" 기능부터 시작해보자. 특별한 것은 없어 보이며 간단하게 (1)요청을 날리고 (2)응답을 받고 (3)그 응답을 표시하면 된다. 그럼 이제 우리의 요청을 스트림으로 표현해보자. 처음 봤을때는 좀 힘들어 보이지만 우리는 기초부터 시작해야 한다.

    시작하면 한 요청만 날리면 되고, 데이터 스트림으로 모델링하면 하나의 값만 스트림이 될 것이다. 그 후에는 우리도 알듯 많은 요청을 보내게 될테지만, 지금은 하나만 해보자.
    --a------|->
    
    Where a is the string 'https://api.github.com/users'
    
    이것은 우리가 요청할 URL의 스트림이다. 요청 이벤트가 발생하면 언제, 무엇이 발생했는지 알려준다. 요청이 "언제" 호출될지는 이벤트가 언제 발생되는지와 같은 시점이다. 그리고 "무엇이" 요청됐는지는 발생된 값이다.(URL을 담고 있는 문자열)

    Rx* 한 값으로 이런 스트림을 만드는 것은 굉장히 간단하다. 스트림에서 공식적인 용어는 "Observable"이다. 이것을 Observe 할 수 있다고 하지만 이렇게 바보처럼 말하지 말고, 이것을 스트림이라 부르겠다.

    var requestStream = Rx.Observable.just('https://api.github.com/users');

    이제부터는 이것이 문자열의 스트림이고 다른 동작을 하지 않으며, 값이 나올때 우리가 필요한대로 어떻게 처리할 수 있다. 이것을 subscribing으로 스트림에 할 수 있다.

    requestStream.subscribe(function(requestUrl) {
      // execute the request
      jQuery.getJSON(requestUrl, function(responseData) {
        // ...
      });
    }

    우리는 요청 오퍼레이션의 비동기 처리를 위해 jQuery Ajax 콜백(여러분은 이미 알고 있다고 가정한다)을 사용한다. 그런데 가만보자. Rx는 비동기 데이터 스트림을 다루기위해 있다. 그 요청에 대한 응답이 곧 있다가 받을 데이터를 담은 스트림일 수는 없을까? 음, 개념상으로는 가능해보이니 한번 시도해보자.

    requestStream.subscribe(function(requestUrl) {
      // execute the request
      var responseStream = Rx.Observable.create(function (observer) {
        jQuery.getJSON(requestUrl)
        .done(function(response) { observer.onNext(response); })
        .fail(function(jqXHR, status, error) { observer.onError(error); })
        .always(function() { observer.onCompleted(); });
      });
    
      responseStream.subscribe(function(response) {
        // do something with the response
      });
    }

    Rx.Observable.create()가 하는 일은 각 Observer(혹은 다른말로 '구독자')에게 데이터 이벤트(onNext())나 에러(onError())를 명시적으로 알리는 커스텀 스트림을 만든다. 우리가 한 일은 그냥 jQuery Ajax Promise을 감싼 것이다. 잠시만요, 이게 Promise이란게 Observable을 의미하는 건가요?




    그렇다.

    Observable은 보장++이다. Rx에서 당신은 var stream = Rx.Observable.fromPromise(promise)하여 쉽게 Promise를 Observable로 변환할 수 있다. 그러니 이것을 사용해보자. 다지 다른 점은 Observable이 Promise/A++를 따르지 않지만 개념적으로 충돌은 없다. 간단하게 한 보장은 하나의 발생된 값과 함께 한 Observable이다. 꽤 좋아보인다. 적어도 Observable이 Promise만큼 강력한지 보여준다.

    여러분이 Promise의 속임수라 믿는다면 Rx Observable이 어떤 것이 유능한지 눈으로 지켜보자.

    이제 예제로 다시 돌아와서, 새 subscribe()를 또 하나더 그 안에서 호출하면 콜백지옥 같은 형태가 되버린다. 또한 responseStream 생성은 requestStream에의해 결정된다. 이전에도 들었듯 Rx에서는 새 스트림을 변형, 생성해내는 간단한 매커니즘이 있으므로 이것을 시행해보자.

    이제부터는 알아야할 기본함수인 map(f)가 있다. 이것은 스트림A의 각 값을 받아서 f()를 적용시키고 스트림B로 만든다. 이것을 우리 요청과 응답 스트림에 하려면 요청 URL을 응답 Promise(스트림처럼 만든)에 매핑할 수 있다.
    var responseMetastream = requestStream
      .map(function(requestUrl) {
        return Rx.Observable.fromPromise(jQuery.getJSON(requestUrl));
      });

    그다음 우리는 스트림의 스트림인 "metastream"을 하나 만들 것이다. 아직 당황하지 마라. metastream은 각각 발생했던 값이 있는 또다른 스트림이다. 여러분은 이것을 '포인터'라 생각할 수도 있겠다. 각 발생한 값은 다른 스트림을 가리키는 포인터이다. 우리 예제에서는 각 요청 URL이 Promise 스트림을 가리키는 포인터로 매핑되는데, 이 promise 스트림은 해당되는 응답을 가지고 있다.


    응답을 위한 한 metastream은 혼란스러워 보이며 우리에게 크게 도움이 되지 않는 것 같다. 우리는 각 발생된 값이 'promise'의 JSON 객체가 아닌 그냥 간단한 응답 스트림이 필요할 뿐이다. Flatmap씨(링크)에게 인사하자. "branch" 스트림에서 나오는 모든 "trunk" 스트림을 내보냄으로서 동작하는, flatmap은 한 metastream을 "flattens"하게 만드는 버전의 map()이다. Flatmap은 "고정"된 것이 아니고 metastream은 일종의 버그가 아니다. 이것들은 Rx에서 비동기로 응답을 다루기위한 실제 툴이다.
    var responseStream = requestStream
      .flatMap(function(requestUrl) {
        return Rx.Observable.fromPromise(jQuery.getJSON(requestUrl));
      });


    좋다 응답 스트림이 요청 스트림에 맞춰 정의되었으므로, 후에 요청 스트림에서 이벤트가 발생하면 예상한대로 응답 스트림에서 발생한 응답 이벤트들을 가질 수 있을 것이다.

    requestStream:  --a-----b--c------------|->
    responseStream: -----A--------B-----C---|->
    
    (lowercase is a request, uppercase is its response)
    

    이제 마침내 응답 스트림을 가지게 되었다. 우리가 받은 데이터를 화면에 띄우면 된다.

    responseStream.subscribe(function(response) {
      // render `response` to the DOM however you wish
    });

    지금까지의 코드를 모두 합쳐보자.

    var requestStream = Rx.Observable.just('https://api.github.com/users');
    
    var responseStream = requestStream
      .flatMap(function(requestUrl) {
        return Rx.Observable.fromPromise(jQuery.getJSON(requestUrl));
      });
    
    responseStream.subscribe(function(response) {
      // render `response` to the DOM however you wish
    });
    새로고침 버튼
    나는 아직 응답의 JSON이 100명의 유저라는 것에 대해 말하지 않았다. 이 API는 한 페이지 크기를 설정할 수 있는게 아니라 페이지 번호를 설정할 수 있게 해놓았다. 따라서 우리는 오직 3개의 데이터만 쓰고 나머지 97개의 데이터는 버려진다. 우선은 이 문제를 무시할 것이다. 그리고 나중에 이 응답을 어떻게 캐신할 수 있는지 살펴볼 것이다.

    새로고침 버튼을 누를때마다 요청 스트림은 새 URL을 발생시킬 수 있으므로 새 응답을 얻어낼 수 있다. 우리는 다음 두가지가 필요하다. 새로고침 버튼의 클릭 이벤트 스트림(주문: 모든것이 스트림이 될 수 있다). 그리고 새로고침 클릭 스트림에 따라 요청 스트림을 바꿀 수 있는 것도 필요하다. 기꺼히 RxJS는 이벤트 리스너로부터 Observable을 만들 수 있는 툴을 제공한다.
    var refreshButton = document.querySelector('.refresh');
    var refreshClickStream = Rx.Observable.fromEvent(refreshButton, 'click');

    새로고침 클릭 이벤트가 그 자신의 API URL을 관리하지 않으므로 우리는 각 클릭을 실제 URL로 매핑해주어야한다. 이제 요청 스트림을 새로고침 클릭 스트림으로 바꾸었다. 새로고침 클릭 스트림은 매번 랜덤의 페이지 값으로 API endpoint를 바꾸어 매핑한다.

    var requestStream = refreshClickStream
      .map(function() {
        var randomOffset = Math.floor(Math.random()*500);
        return 'https://api.github.com/users?since=' + randomOffset;
      });

    내가 좀 어리석고 자동 테스트를 못해서 이전에 만든 기능 중 하나를 가져왔다. 한 요청은 더이상 시작할 때 일어나지 않고 새로고침을 눌렀을때만 일어난다. 아아. 나는 이 요청이 새로고침을 누를때나 사이트를 켰을때나 둘 다 동작하게 하고 싶다. 


    우리는 이 상황별 스트림을 어떻게 분리하는지 알고있다.
    var requestOnRefreshStream = refreshClickStream
      .map(function() {
        var randomOffset = Math.floor(Math.random()*500);
        return 'https://api.github.com/users?since=' + randomOffset;
      });
    
    var startupRequestStream = Rx.Observable.just('https://api.github.com/users');

    그러나 이제 '쪼개진' 두 스트림을 어떻게 하나로 합칠까? merge()가 있다. 아래 다이어그램에서 어떻게 되는건지 설명했다.

    stream A: ---a--------e-----o----->
    stream B: -----B---C-----D-------->
              vvvvvvvvv merge vvvvvvvvv
              ---a-B---C--e--D--o----->
    

    이제 쉬워졌다.

    var requestOnRefreshStream = refreshClickStream
      .map(function() {
        var randomOffset = Math.floor(Math.random()*500);
        return 'https://api.github.com/users?since=' + randomOffset;
      });
    
    var startupRequestStream = Rx.Observable.just('https://api.github.com/users');
    
    var requestStream = Rx.Observable.merge(
      requestOnRefreshStream, startupRequestStream
    );

    매개 스트림 없이 만들 수 있는 깔끔한 방법의 대안이다.

    var requestStream = refreshClickStream
      .map(function() {
        var randomOffset = Math.floor(Math.random()*500);
        return 'https://api.github.com/users?since=' + randomOffset;
      })
      .merge(Rx.Observable.just('https://api.github.com/users'));

    더 짧아지고 가독성도 더 좋아졌다.

    var requestStream = refreshClickStream
      .map(function() {
        var randomOffset = Math.floor(Math.random()*500);
        return 'https://api.github.com/users?since=' + randomOffset;
      })
      .startWith('https://api.github.com/users');

    startWith() 함수는 당신이 생각하는데로 정확히 그렇게 동작할 것이다. 입력 스트림이 어덯게 생겼든 상관없이 startWith(x)로부터 나온 결과 스트림은 시작부분에서 x를 가질 것이다. 그러나 나는 아직 DRY 하지 못하다. 나는 API endpoint 문자열을 반복에서 쓰고 있다. 이것을 고치기 위한 한가지 방법은 startWith()refresgClickStream에 붙이는 것이다. 이것은 시작 시점에 새로고침 클릭을 강제로 시행하기 위함이다.

    var requestStream = refreshClickStream.startWith('startup click')
      .map(function() {
        var randomOffset = Math.floor(Math.random()*500);
        return 'https://api.github.com/users?since=' + randomOffset;
      });

    좋다. 만약 내가 자동 테스트를 쪼개었다는 곳으로 돌아가보면 이전 것과 다른 점이 startWith()만 추가한 것 밖에 없다.


    스트림으로 3가지 제안을 모델링하기
    이제부터 터치된 제안 UI 요소만 가지고 있는데, responseStream의 subscribe에서 일어난 렌더링 단계에서 일어났다. 이제 새로고침 버튼에 문제가 있다. '새로고침'을 누르자마자 현재 3개의 제인이 명확하지 않다. 새로운 제안이 응답이 도착한 후에 화면에 나타나는데, UI적으로 좀더 좋게 만들기 위해 새로고침을 누를 때 현재 있던 제안들을 지울 필요가 있다.
    refreshClickStream.subscribe(function() {
      // clear the 3 suggestion DOM elements 
    });

    아니다.. 빠르지 않다. 우리는 제안의 DOM 요소에 영향을 주는 두 구독자가 있기때문에 그렇다. 그리고 이것은 일을 쪼개는 것(sepration of concerns)처럼 보이지도 않는다. 리액티브의 주문이 기억나는가?


    이제 제안을 스트림으로 모델링할것인데, 각각 발생한 값은 제안 데이터를 가지고 있는 JSON 객체이다. 우리는 3가지 각 제안마다 이 일을 할것이다. 이것은 1번 제안을 위한 스트림이 어떻게 생겼는지 보여준다.

    var suggestion1Stream = responseStream
      .map(function(listUsers) {
        // get one random user from the list
        return listUsers[Math.floor(Math.random()*listUsers.length)];
      });

    그리고 suggestion2Streamsuggestion3Stream은 그냥 suggestion1Stream을 복사하여 붙여 넣은 것이다. 이것은 DRY는 아니나 튜토리얼의 예제를 간단하게 만들기위해 이렇게 하였고, 추가로 이 경우에는 어떻게 중복을 피할수 있을지 생각해볼수 있는 좋은 기회라 생각된다.

    responseStreamsubscribe()에서 렌더링이 일어나게 하는 것 대신에 이렇게 했다.
    suggestion1Stream.subscribe(function(suggestion) {
      // render the 1st suggestion to the DOM
    });

    "새로고침을 누르면 제안들을 지운다"로 돌아가서, 우리는 간단하게 새로고침 클릭을 제안 데이터에 null로 매핑하여 suggestion1Stream에 넣는다.

    var suggestion1Stream = responseStream
      .map(function(listUsers) {
        // get one random user from the list
        return listUsers[Math.floor(Math.random()*listUsers.length)];
      })
      .merge(
        refreshClickStream.map(function(){ return null; })
      );

    그리고 화면에 띄울때는 null은 따로 분기처리하여 "데이터가 없음"이라하고 그 UI 요소를 숨긴다.

    suggestion1Stream.subscribe(function(suggestion) {
      if (suggestion === null) {
        // hide the first suggestion DOM element
      }
      else {
        // show the first suggestion DOM element
        // and render the data
      }
    });

    여기 큰 그림이다.

    refreshClickStream: ----------o--------o---->
         requestStream: -r--------r--------r---->
        responseStream: ----R---------R------R-->   
     suggestion1Stream: ----s-----N---s----N-s-->
     suggestion2Stream: ----q-----N---q----N-q-->
     suggestion3Stream: ----t-----N---t----N-t-->
    

    Nnull을 의미한다.


    보너스로, 시작할때 '빈' 제안들이 화면에 나타날 것이다. 이제 제안 스트림에 startWith(null)을 추가하면 된다.
    var suggestion1Stream = responseStream
      .map(function(listUsers) {
        // get one random user from the list
        return listUsers[Math.floor(Math.random()*listUsers.length)];
      })
      .merge(
        refreshClickStream.map(function(){ return null; })
      )
      .startWith(null);

    그 결과이다.

    refreshClickStream: ----------o---------o---->
         requestStream: -r--------r---------r---->
        responseStream: ----R----------R------R-->   
     suggestion1Stream: -N--s-----N----s----N-s-->
     suggestion2Stream: -N--q-----N----q----N-q-->
     suggestion3Stream: -N--t-----N----t----N-t-->
    제안을 닫고 캐싱된 응답을 사용하기
    이 기능이 마지막 남은 구현이다. 각 제안은 그것을 닫을 수 있는 'x'버튼을 하나씩 가지고 있고, 그것을 누르면 그 자리에 다른 제안이 들어온다. 닫기 버튼이 눌러지면 새 요청을 만들어야 한다고 생각해볼 수 있다.
    var close1Button = document.querySelector('.close1');
    var close1ClickStream = Rx.Observable.fromEvent(close1Button, 'click');
    // and the same for close2Button and close3Button
    
    var requestStream = refreshClickStream.startWith('startup click')
      .merge(close1ClickStream) // we added this
      .map(function() {
        var randomOffset = Math.floor(Math.random()*500);
        return 'https://api.github.com/users?since=' + randomOffset;
      });

    위의 것은 제대로 동작하지 않는다. 이것은 하나만 눌렀는데 모두 닫으면서 모든 제안을 다시 갱신할 것이다. 이 문제를 해결하기 위한 방법에는 여러개가 있을 것이다. 우리는 이전 응답을 재사용 함으로서 이 문제를 해결할 것이다. API 응답의 페이지 크기는 100명의 사용하지만 우리는 딱 3명만 사용하고 있으므로 남은 데이터를 사용할 수 있을 것이다. 새로운 요청 없이 말이다.


    다시 말하자면, 스트림으로 생각해보자. 'close1' 클릭 이벤트가 발생하면 우리는 가장 최근에 responseStream에서 가져올 랜덤 유저 한명을 사용하고 싶다.
        requestStream: --r--------------->
       responseStream: ------R----------->
    close1ClickStream: ------------c----->
    suggestion1Stream: ------s-----s----->
    

    Rx*에서는 우리에게 필요한 것 처럼 보이는 결합함수, combineLatest가 있다. 이 함수는 인풋으로 A, B 스트림을 받고, 어떤 스트림이 값을 발생시키든 그때마다 combineLatest는 가장 최근에 두 스트림으로부터 a, b 값을 합친다. 아웃풋 값은 c=f(x,y)인데, f는 여러분이 정의한 함수이다. 다이어그램으로 이해하는게 더 좋을 것이다.

    stream A: --a-----------e--------i-------->
    stream B: -----b----c--------d-------q---->
              vvvvvvvv combineLatest(f) vvvvvvv
              ----AB---AC--EC---ED--ID--IQ---->
    
    where f is the uppercase function
    

    우리는 close1ClickStreamresponseStreamcombineLatest()를 적용시킬 수 있으므로, 닫기버튼1이 클릭될 때 마다 마지막에 발생된 응답을 얻어와서 suggestion1Stream에 새 값으로 만든다. 한편 combineLatest()는 대칭적인데, responseStream에서 새 응답이 나올 때 마다 새 제안을 만들어내기위해 마지막 '닫기1' 클릭을 합친다. 이 부분이 재미있는데, 이전에 suggestion1Stream 코드를 간단하게 만들어준다.

    var suggestion1Stream = close1ClickStream
      .combineLatest(responseStream,             
        function(click, listUsers) {
          return listUsers[Math.floor(Math.random()*listUsers.length)];
        }
      )
      .merge(
        refreshClickStream.map(function(){ return null; })
      )
      .startWith(null);

    아직 한 부분이 빠졌다. combineLatest()는 두 소스 중 가장 최신의 것만 사용한다. 그러나 이 소스 중 하나가 아직 아무것도 발생시키지 않았다면, combineLatest()는 아웃풋 스트림에 데이터 이벤트를 만들 수 없을 것이다. 위의 아스키 다이어그램을 보면 첫번째 스트림이 a를 만들었을때 아웃풋에 아무것도 없음을 확인할 수 있을 것이다. 두번째 스트림에서 b 값을 만들었을때 비로소 아웃풋 값이 만들어질 수 있다.


    다른 해결 방법이 있는데, 시작할 때 '닫기버튼1'을 모의로 눌러보는 것이다.

    var suggestion1Stream = close1ClickStream.startWith('startup click') // we added this
      .combineLatest(responseStream,             
        function(click, listUsers) {l
          return listUsers[Math.floor(Math.random()*listUsers.length)];
        }
      )
      .merge(
        refreshClickStream.map(function(){ return null; })
      )
      .startWith(null);
    합쳐보기
    이제 끝났다. 우리가 만든 코드의 완성본이다.
    var refreshButton = document.querySelector('.refresh');
    var refreshClickStream = Rx.Observable.fromEvent(refreshButton, 'click');
    
    var closeButton1 = document.querySelector('.close1');
    var close1ClickStream = Rx.Observable.fromEvent(closeButton1, 'click');
    // and the same logic for close2 and close3
    
    var requestStream = refreshClickStream.startWith('startup click')
      .map(function() {
        var randomOffset = Math.floor(Math.random()*500);
        return 'https://api.github.com/users?since=' + randomOffset;
      });
    
    var responseStream = requestStream
      .flatMap(function (requestUrl) {
        return Rx.Observable.fromPromise($.ajax({url: requestUrl}));
      });
    
    var suggestion1Stream = close1ClickStream.startWith('startup click')
      .combineLatest(responseStream,             
        function(click, listUsers) {
          return listUsers[Math.floor(Math.random()*listUsers.length)];
        }
      )
      .merge(
        refreshClickStream.map(function(){ return null; })
      )
      .startWith(null);
    // and the same logic for suggestion2Stream and suggestion3Stream
    
    suggestion1Stream.subscribe(function(suggestion) {
      if (suggestion === null) {
        // hide the first suggestion DOM element
      }
      else {
        // show the first suggestion DOM element
        // and render the data
      }
    });

    http://jsfiddle.net/staltz/8jFJH/48/ 여기서 예제가 돌아가는 것을 확인할 수 있을 것이다.

    이 코드 조각은 작지만 빽빽하다. 여기에는 적당히 일을 분배하여 다중 이벤트를 관리하고 응답을 캐싱하는 것까지 기능이 들어있다. 함수형  스타일은 명령형 스타일보다 더 서술적인 코드를 만든다. 우리는 실행되야할 인스트럭션의 순서를 만든게 아니라, 스트림 사이의 관계를 정의함으로서 그냥 어떤것인지 말했을 뿐이다. 예를 들어 if, for, while과같은 컨트롤 플로우 요소들이 없고 자바스크립트에서 주로 쓰는 일반적인 콜백기반 컨트롤 플로우가 없다는 것을 기억에 남기자. 위의 subscribe()에서 당신이 원하면 filter()를 이용하여 ifelse를 없앨수도 있다. (구체적인 구현은 여러분의 연습 과제로 남기겠다) Rx에서는 map, filter, scan, merge, combineLatest, startWith 등과 같이 이벤트 주도 프로그램의 흐름을 컨트롤하는 많은 스트림 함수를 가지고 있다. 이 함수 툴셋은 작은 코드로 더 강한 영향력을 제공할 것이다.

    다음으로 해야할 것은 무엇인가
    만약 Rx*가 당신의 리액티브 프로그래밍의 주 라이브러리가 된다면, Observable을 변형, 결합, 생성하기위한 많은 함수들을 익힐 필요가 있다. 이 함수들을 스트림 다이어그램으로 이해하고 싶으면 RxJava's very useful documentation with marble diagrams에 들어가 보아라. 당신이 뭔가 시도하다가 문제가 생기면 다이어그램을 그려 생각해보고 그 많은 함수들을 본 다음 다시 생각해 보아라. 내 경험에는 이 방법이 가장 효율적이었다.

    한번 Rx와의 프로그래밍을 파악하기 시작하면 Cold vs Hot Observable 개념을 반드시 이해해야한다. 그렇지 않으면 당신을 계속 괴롭힐 것이다. 여러분은 경고받아왔다. 함수형 프로그래밍을 배우고 Rx에 영향을 미치는 사이드 이팩트같은 이슈를 익히면서 당신의 기술을 더 갈고 닦아라.

    하지만 리액티브 프로그래밍이 단지 Rx만 있는게 아니다. Bacon.js라는 것이 있는데, 이것은 가끔 Rx에서 마주칠 수 있는 관습(quirks)없이 직관적으로 작업한다. Elm언어는 그 자신만의 카테고리 안에 있다. 이 언어는 자바스크립트+css+HTML로 컴파일하고 타임 트레블링 디버거(time travelling debugger)기능이 있는 함수형 리액티브 프로그래밍 언어이다. 꽤 멋지다.

    Rx는 이벤트가 중심인 프론트엔드와 앱에서 잘 동작한다. 그러나 이게 클라이언트단에서만 그런 것이 아니라 백엔드와 데이터베이스 접근에도 잘 동작한다. 사실 RxJava는 Netflix API에서 서버단 동시성을 가능하게 해주는 핵심 요소이다. Rx는 특정 타입의 앱이나 언어에 종속된 프레임워크가 아니다. Rx는 이벤트 주도 소프트웨어를 사용할 수 있게 해주는 패러다임이다.

    이 튜토리얼이 마음에 들었다면 트윗해 달라. 



    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

    ,

    여러분 앱이 탭, 스와이프, 팬, 다른 인터렉션을 사용한다면 화면 뒤에서 이벤트를 사용하고 있는 것이다. 이러한 이벤트들은 잘 만들어진 과정을 거쳐 이동하며, 우리는 이것들이 어떻게 동작하는지 한번 볼 것이다. 텍스트 인풋이나 원격 제어 이벤트를 중심으로 트리키한 문제를 디버깅하면, 이것이 어떻게 동작하는지 쉽게 이해할 수 있다. 또한 이 지식을 이용하여 여러분 앱을 통해 커스텀 데이터 흐름을 만들 수도 있을 것이다.

    이 글은 이벤트 전달에 관한 시리즈의 첫번째 글이다. 터치 핸들링에대해 다룰 것인데, 그냥 터치가 어덯게 여러분의 앱을 통해 받은 이벤트가 될 수 있는지, 그 이벤트를 다루기 위해 어떻게 요소들에게 기회를 줄 수 있는지 다룬다.

    터치 핸들링
    터치 이벤트는 iOS 앱에서 가장 주된 형태의 이벤트이다. 어떤식으로 터치를 처리하는지에대한 세부적인 내용은 우리가 매일 사용하는 API들에의해 가려져있다. 앱에서 이 이벤트들을 어떻게 분배하는지 이해하면 여러분의 앱을 어떻게 구성할지 정하는데 도움이 될 것이다. 또한 커스텀 이벤트를 전달하는데 이런 하부조직을 사용할 수도 있다.

    Hit Testing
    이벤트 전달에대해 이야기할 때 가장 먼저 다룰 것은 시스템이 어떻게 터치 이벤트를 다루고 어떻게 앱을 통해 이 이벤트를 받아드리는지이다. 이벤트 사용자가 화면을 탭하면 발생한다. 적절한 요소가 터치를 다루기 전에, 시스템은 어디에서 터치가 발생했는지, 누가 먼저 터치에 응답을 받을 것인지 정해야한다.

    이것이 hit testing이 필요한 곳이다.

    hit testing 과정과 관련이 있는 메소드에는 hitTest:withEvent:pointInside:withEvent:가 있다. hitTest:withEvent:는 hit test한 점이 그 바운드 안에 들어오는지 알아보기 위해 pointInside:withEvent:를 사용한다. 만약 바운드 안에 들어가지 않으면 nil을 반환하고 뷰 계층의 모든 브런치를 스킵한다.

    테스트한 점이 바운드 안에 들어간다면 각 서브뷰마다 pointInside:withEvent:를 호출한다.  pointInside:withEvent:에서 YES를 받은 서브뷰는 hitTest:withEvent:를 호출한다. 최초의 hitTest:withEvent: 호출로부터 최종 결과는 서브뷰 중 하나이거나 자기 자신이다. 자기 자신을 반환하는 경우는 그 서브뷰가 모두 nil을 반환할 때이다.

    아래의 뷰 계층을 보자.

    사용자가 View E를 탭했다고 생각해보자. View A에서부터 hitTest:withEvent:로 시작한다. View A에서 pointInside:withEvent:YES면 View B나 View C의  pointInside:withEvent:를 호출한다. View B가 NO를 반환한다. View C가  YES를 반환하고 거기의 hitTest:withEvent:를 호출한다. View C도 View D와 View E로 똑같은 과정을 따른다. View D의 pointInside:withEvent:NO를 반환한다. View E는 YES를 반환하고 더이상 다른 서브뷰가 없으므로 hitTest:withEvent:에서 자기자신을 반환한다.

    View D가 View C의 서브뷰라 가정하고, 사용자가 View C 바운드의 View D를 탭했다면 무슨일이 일어날까? (이것은 clipToBoundsNO로 설정되있을 때 가능하다) View A가 위 과정을 시작한다. View B와 View C 둘 다 pointInside:withEvent:NO를 반환하므로 결국 View A가 터치를 받는다.

    이제 우리는 hitTest:withEvent:로부터 받은 뷰를 가지고 있다. 이 뷰는 hit-test 뷰이다. 이제 이것은 터치와 연관되있고 터치가 활성화되는 동안 터치 이벤트를 응답하는 어떤 제스처 이후에 첫번째 기회를 줄 것이다.

    주어진 터치에 커스텀 구현이 따로 없으면 무슨일이 일어날까? 상황에 따라 다르다. 만약 뷰가 뷰컨트롤러에의해 관리되고 있으면 뷰컨트롤러에게 응답할 기회를 준다. 뷰컨트롤러가 응답하지 않으면 hit-test 뷰의 부모뷰가 그 응답 기회를 가져간다. 이러한 과정은 "Responder Chain"이라 불리는 방법으로서 반복에서 진행된다.

    Responder Chain
    Responder Chain은 chain-of-responsibility 디자인 패턴으로 구현되있다. Responder Chain의 각 요소는 UIResponder를 상속받는다. UIResponder는 여러 종류의 이벤트를 다루기 위한 메소드들을 가지고 있다. 터치 이벤트를 넘어서 UIResponder는 인풋 뷰, 모션 이벤트, 누름 이벤트, 원격 제어 이벤트를 다루기 위한 메소드들을 정의해 두었다.

    여기에서 많은 이벤트 중 firstResponder가 중요하다. firstResponder는 이벤트를 다룰 수 있는 첫번째 기회가 주어진 오브젝트이다. 첫번째 응답자가 이벤트를 다루지 않으면 nextResponder에게 그 이벤트 핸들링 기회를 준다. 그리고 현재 오브젝트가 nil 일때까지 nextResponder를 반복한다. 보통 체인에서 마지막 오브젝트는 어플리케이션 델리게이트이다.

    제스처
    iOS에서 현재 있는 곳에 제스처 recognizer가 오기 전에, 터치 핸들링 프로세스는 어떻게 앱을 감지하고 제스처를 다룰지의 방법이다. UIGestureRecognizer가 나오면 그들이 감자한 제스처에 따라 터치를 다루고 행동을 취하도록 프로세스를 조금 바꾼다. 우리는 제스처 recognizer를 어떻게 사용하는지 살펴보지는 않겠지만 대신에 제스처를 어떻게 터치 이벤트 전달 시스템에 맞추는지 볼 것이다.

    제스처는 항상 한 터치 이벤트를 다루기 위해 첫번째 기회를 얻는다. 디폴트로는, 제스처가 터치를 얻고 그 다음 뷰를 얻는다. 제스처 상태가 "recognized" 일때 차이점은 뷰에 있는 터치가 취소된다는 것이다. 여러분은 UIGestureRecognizer:, cancelsTouchesInView, delaysTouchesBegandelaysTouchesEnded에 프로퍼티로 뷰에 이 터치 이벤트를 어떻게 전달할지 정할 수 있다. 이 프로퍼티를 잘 써서 응답하지 않는 뷰에도 응답하는 것처럼 만들 수 있다.

    요약
    첫번째 파트는, 터치 이벤트가 어떻게 윈도우에서 뷰까지 탐색하는지 보았는데, 뷰는 responder chain을 통해 비슷한 경로로 돌아와 터치가 일어난다. 또한 제스처 recognizer가 어떻게 이 터치 시스템에 맞춰지는지 다루었다. 두번째 파트에서는 지원하는 다른 이벤트와 responder chain이 어디에 들어맞는지 이야기해볼 것이다.

    설계와 개발에 대해 더 알고 싶으면 BPXL Craft를 구독하거나 Blick Pixel 트위터를 팔로우 해달라. 


    관련 링크



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

    ,

    매년 우리의 Buffer iOS 앱은 애플의 World Wide Developer Conference(WWDC) 이후에 새로운 iOS 기능과 변화를 지원하기 위해 크게 업데이트 해왔었다.

    올해에 큰 변화는 없었지만, 이번 포스팅에서는 퍼포먼스와 미래에대한 대비에대해 이야기 할 것이다.

    새로운 버전의 Buffer 앱의 새로운 변화
    WWDC가 끝나고나면 우리는 보통 앱을 실행해보고 이전 iOS 릴리즈 버전을 최소 타켓으로 조정한다. 이번의 경우 iOS9를 최소로 조정할 예정이었으나 iOS8 기기까지 지원하게 되었다. 그리하여 앱을 정리하고 이전 호환성에 더이상 필요없는 오래된 iOS8 코드를 제거할 수 있었으며, 몇년간 기술적인 빚을 갚을 수 있었다.

    Buffer iOS의 6.0버전에서 증진시키고 싶은 부분중 하나는 바로 테이블이다. 특히 나중에는 레이아웃 부분을 더 유연하게 하고 싶었다. 우리는 2011년부터 UITableView를 사용하고 있었다. 이것은 컨텐트의 목록에는 좋지만, 나중에 v6에는 컨텐트를 그리드(grid)형식으로 보여주고 싶었다.

    목표 : 부드러운 스크롤링을 위해 초당 60프레임을 지원
    우리는 항상 부드러운 스크롤을 위해 마법처럼 초당 60프레임을 달성하려고 노력해가며 테이블을 업데이트 했다. 몇년간 아이폰이 더욱 강력해졌음에도 이것은 매우 어려운 목표치였고, 스마트폰이 싱글 코어 디바이스일때 설계된것같은 UIKit은 아직또 메인스레드에 묶여있다.

    사용자가 프레임이 떨어지는 것을 알아차리지 않으려면 모든 레이아웃과 코드 랜더링이 16ms 안에 실행되야하는데, UIKit은 이것을 개발자에게 떠넘겼다. 시스템이 오버헤드하면 가끔 10ms 정도 휘청거린다.

    우리의 UI는 매우 튼튼하고 다채로우므로 이 과제는 매년 포퍼먼스 유지보수를하게 만들었다. 이 기술적인 과제는 사용자가 "로딩" 화면을 보지 않으면서 스크롤 하는동안 어떻게 부드럽게 업데이트를 서버에서 검색하는지와 연관이 있다.

    우리의 셀(cell)들은 사용자가 만든 소셜미디어 업데이트의 다양한 높이의 UI를 지원하는데, 이것이 꽤 복잡하다. 그래서 올해 애플이 UICollectionView의 퍼포먼스를 올렸다는 발표를 들었을 때 우리는 매우 흥미로웠다. 테이블 스크롤링을 하면서 처리할게 많을때 우리의 기대치는 60프레임을 유지하는 것이었고, 적어도 55프레임 이상 유지하길 바랬다.

    우리는 먼저 앱에서 UICollectionView를 어떻게 이용할지부터 찾아보았지만, 현재의 UITableViews와 비슷한 레이아웃을 만들어내는데 몇 가지 장애물이 있었다. 컬랙션 셀의 오토사이징에대한 새로운 추가 사항과 함께 몇 가지 문제가있었다. 우리는 iOS가 높이를 계산하고나면 스크롤이 점프해서 왔다갔다하는 모습을 보고 있었다. (v5.0에서 가능하면 미리 높이를 계산해두는 방법을 찾았었다) 더 나아가 오토 레이아웃의 편리함은 무거운 처리의 비용으로 돌아오고, 이것은 메인스레드에서 일어났다.

    전략: 페이스북의 AsyncDisplayKit을 사용하기
    얼마전에 아직 보진 못했지만 페이스북이 AsyncDisplayKit(ASDK)를 만들었다는 소식을 접했다. 페이스북은 그들의 제스처와 애니메이션 특징의 앱(Paper)를 위해 2014년 10월부터 ASDK를 커스텀하여 만들고 있었다. 그들은 UIView의  스레드-세이프 추상화를 통해 메인스레드에서 뷰를 랜더링하는데, 초당 60프레임이 유지되도록하여 앱이 부드럽게 동작하게 만들었다. 추가로 ASDK는 "Intelligent Preloading"이라 불리는 강력한 API를 가지는데, 이것은 서버로부터 응답이 필요한 컨텐트 "목록"을 사용자가 스크롤하는 곳에 네트워크 호출과 불러오기를 효율적인 방법으로 다룬다.


    우리는 이번 기회에 이것을 확인하여 사용해보자고 마음을 먹었고, 몇 시간 안에 텍스트만 갱신하면서 스크롤이 점프해서 왔다갔다 하지 않는 v6.0을 만들었다.

    AsyncDisplayKit이 셀 랜더링을 처리해줌으로서 heightForRowAtIndexPath:나 estimatedHeightForRowAtIndexPath: 구현 없이 정확한 높이 값을 받아냈다. 이 컨셉 증명을 통해 아이폰5C에서 부드러운 60프레임을 확인할 수 있었다(아이폰6+에서는 이미 더 좋은 퍼포먼스를 냈다)

    AsyncDisplayKit은 UI를 만들기위해 "Nodes"를 사용하는데, 우리의 바뀐 셀이 ASCellNode가 되도록 다시 만들어야했다. 또한 다른 요소를 새로운 클래스로 추상화하여 셀의 구조를 정리하는데 시간을 썼으며, 2800줄이나 되던것을 945줄까지 줄이게 되었다. ASCellNode는 또한 컬랙션 뷰나 테이블 뷰 둘다와 함께 양립해서 사용할 수 있다는 이점도 얻을 수 있었는데, 이것이 미래의 기능을위해 리팩토링 한 후에 얻은 레이아웃 유연성이다.

    ASDK의 레이아웃은 CSS Flex Box 모델을 기반으로 하는데, 이것은 현재 오토 레이아웃 코드의 시작점중 일부이기도 하다. 그러나 Inset, Overlay, Center, Stack등을 사용하여 레이아웃 기능을 꽤 쉽게 사용할 수 있다. 우리는 빠르게 바뀐 셀을 레이아웃에 구성할 수 있었으며, 아이패드에서 읽을 수 있는 레이아웃 가이드로 방향 전환과 고정된 폭 지원까지 해줌으로서 오토 레이아웃보다 더 깔끔하게 보이게 되었다.

    AsyncDisplayKit의 소개때부터 핀터레스트는 페이스북과 협력하여 AsyncDisplayKit v2.0 작업을 함께 하고 있었다. 우리는 항상 페이스북과 핀터레스트 iOS 앱 둘다의 퍼포먼스에 놀랐었다. 핀터레스트 앱에서 순식간에 핀을 통해 스와이핑할때는 정말로 인상적이고, 좀처럼 로딩화면에 도달하지 않는다. 우리가 보았던 그들의 포퍼먼스 증진을 큰 인상을 받아왔는데, Buffer iOS앱의 다른 영역에서 ASDK를 사용하여 탐험할 수 있음에 흥분을 가라앉힐 수 없었다.

    아래 비디오는 개발하는 중에 찍었는데, 새로 코딩한 버전의 v5.6 큐를 보여준다. 여러분은 셀 높이를 계산하는 iOS가 스크롤이 점프해서 왔다갔다하는것이 적어지면서, 건더뛰는 프레임도 줄었다는 것을 알 수 있을 것이다.

    다음으로 할것 + 여러분의 생각
    다른 오픈 소스 애플리케이션을 ASDK 예제와 곧 공유하여 다른 사람들이 자체 애플리케이션에서 ASDK를 사용할 수 있게 도움이 되면 좋겠다. 첫 번째 버전은 Buffer iOS 앱에서 업데이트를 재정렬하는 데 사용되는 코드이다.

    프로젝트에서 ASDK를 사용해볼 기회가 있다면, 우리는 여러분의 경험을 들어보고 싶다. 부드러운 스크롤링에대한 다른 팁이나 질문이 있다면 코멘트를 달아달라. 



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

    ,

    당신은 시뮬레이터 빌드 속도를 올리기 위해 CrashlyticsHockeyApp와같은 써드파티 크레쉬 리포팅  서비스를 사용하는가? 오늘 나는 이것이 그냥 이상한 속임수가 아님을 알려주겠다(원문: I've got just the right not-so-weird trick for you today).

    이론
    지난 몇년동안 빌드 시간을 단축시키는 것에 대한 많은 조언들이 있었다. 많은 글쓴이들은 Debug 구성에서 Debug Information FormatDWARF로 바꾸어라고 제안했다. 오늘날의 Xcode는 이것이 디폴트로 한다. 이 설정으로 디버그 심볼(생각: 클래스와 메소드 이름)은 바이너리에 직접 들어간다.

    그 대안으로는, dSYM 파일로 DWARF하는 선택지가 있다. DWARF에 비해 두가지 장점이다.
    • 바이너리를 난독화 시킨다
    • 바이너리 크기가 작아진다

    그리고 커다란 단점에는
    • 따로 분리된 dSYM 파일은 각 빌드마다 생성되기 때문에 긴 빌드시간이 걸린다.

    이것은 release 구성에서 디폴트이며, 이 경우는 이해해줄 수 있다.

    문제점
    위에서 말한 설정은 많은 경우에 완벽하게 괜찮다. 그러나 디버거에 붙이지 않고 DWARF 구성으로 앱을 빌드하면 실패한다. 시도해보면 아래처럼 심볼화되지않은 크래쉬 리포트를 보게 될 것이다.
    0   YourApp                         0x00000001001e1594 0x100058000 + 1611156
    1   YourApp                         0x00000001000ed74c 0x100058000 + 612172
    2   UIKit                           0x000000018f2b67b0 -[UIApplication sendAction:to:from:forEvent:] + 96
    3   UIKit                           0x000000018f42a5ec -[UIBarButtonItem(UIInternal) _sendAction:withEvent:] + 168
    

    우리는 이것은 symbolicatecrash 툴을 이용해서 손수 심볼화시켜주어야한다.

    symbolicatecrash YourApp\ \ 17-10-16\ 23-15.crash YourApp.app/YourApp > symbolicated.crash
    

    크러쉬 난 빌드를 위해서는 .ipa(혹은 .app) 파일에 접근해야할 때가 있음을 인지해라. .ipa 파일은 아카이브 하지 않고는 자동으로 생성되지 않는다.이것은 이 크래쉬 상황의 심볼화 프로세스를 만든다. 그래서 왜 Crashlytics가 각 빌드마다 dSYM 파일을 생성하도록 지향하는지의 이유이다.


    (우리가 코드를  수정했다면) 디폴트 설정은 매 빌드마다 앱의 dSYM 파일을 생성하는 것을 보장하는 프로세스이다. 이것이 더 손호하는 구성이므로 개발중이나 내부 테스팅 중에 크레쉬가 나면 그 크레쉬가 난 정확한 라인을 짚어줄 것이다.

    내 생각엔 기기에 직접 크레쉬를 심볼화시킬 수 있을지도 모르지만, 더이상 그렇게 할 수 없을 것 같다. 그 이유는 다음과 같다.
    왜 써드파티 크레쉬 리포터들이 매 케이스마다 dSYM 파일을 필요로하는지에대한 이유일 것이다. 따라서 이제 왜 dSYM 파일이 필요한지 알았으므로 귀중한 빌드 시간을 어떻게 절약할 수 있는지 보자.

    비결
    dSYM 파일을 생성하든 말든 그 선택은 확실해 보인다. 모든 희망을 잃었다...

    타겟 SDK에따라 Debug Information Format 값을 다르게 설정할 수 있게 되었다!


    이 설정이 우리에게 주는 것은:
    • 시뮬레이터 빌드 속도가 더 빨라진다(우리는 거의 항상 연결되있는 디버거를 쓰므로 이제 크레쉬 리포트는 필요하지 않다)
    • 기기 빌드에대해 모든 크레쉬 리포트(디버거 외부에서 동작할 확율이 높은)

    요약

    우리는 매우 쉬운 방법으로 시뮬레이터 빌드 시간을 단축시켰다. 내 경우는 점점 증가하는 빌드(incremental build)에서 1.5초정도 떨어졌다(전체 빌드 시간의 15%). 큰 차이가 아닌것 같아 보여도, 몰입의 순간에는 매 초가 중요할 것이다. 



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

    ,

    때때로 앱은 크레쉬가 난다. 크레쉬는 사용자의 워크플로우를 방해하는데, 데이터가 손실되거나 백그라운드에서 앱 오퍼레이션이 어떠한 손상을 주기도 한다. 개발자에게는 해결하기 어려운 크레쉬는 다시 재현하기 힘들거나 찾아내기 힘든 것들일 것이다.

    나는 최근에 Castro에서 발견하기 힘든 많은 크레쉬들 때문에 생긴 버그를 찾아다가 고쳤는데, 이 이야기를 공유하면서, 여러분이 경험하는 비슷한 이슈에대해 도움이 되길 바란다.

    Oisin과 나는 9월에 Castro2.1을 배포했다. 그리고 곧 아이튠즈 커넥트를 통해 Castro 크레쉬가 높게 치솟는다는 보고를 받았다.

    2.1버전 이후 Crastro2 크레쉬 리포트를 보여주는 차트2.1버전 이후 Crastro2 크레쉬 리포트를 보여주는 차트


    아이튠즈 커넥트 크레쉬 리포트
    흥미롭게도 이 크레쉬들은 우리가 주로 쓰는 크레쉬 리포팅 서비스(HockeyApp)에 나타나지 않았다. 따라서 실제로 그 문제를 발견하는데까지 시간이 좀 걸렸다. 또한 모든 크레쉬를 알아내기 위해 개발자들은 아이튜즈 커넥트나 Xcode를 통해 크레쉬 리포트를 확인했다. (업데이트: "써트파티 크레쉬 리포터는 기록을 남기기위해 in-process 핸들러를 사용한다. 그러나 OS가 밖에서 여러분 앱을 죽이면 핸들러는 절대 동작하지 않을 것이다"고 Greg Parker가 알려주었다. 추가적으로 HockeyApp의 공동 창시자인 Andreas Linde가 한 아티클을 소개해주었는데, 글은 HockeyApp이 어떤 종류의 크레쉬를 찾아내고, 찾아내지 못하는지 설명해주는 글이었다.)

    여러분이 만약 앱 개발자이고 여러분의 계정으로 로그인 했다면 Xcode가 애플에의해 사용자로부터 모은 크레쉬 리포트를 검토할 수 있게 해준다. 이 기능은 Organizer 윈도우의 크레쉬 탭에서 찾을 수 있다. 앱 버전을 고를 수 있고, 개발자와 정보를 교환하고자 동의했던 사용자로부터 애플이 모은 크레쉬 리포트를 다운받을 것이다.

    Xcode에서 이 부분을 사용할 때 크레쉬가 나는 경향이 있다는 것을 발견했다. 특히 크레쉬 리포트에서 쓰레드 방향의 디스클로저를 열고 닫는 토글링시 크레쉬가 나타났다. 쉽게 하는 방법은 리스트 한편에 크레쉬를 오른쪽 마우스로 클릭하고, "Show in Finder"를 선택한다. 나타난 번들을 더 자세히 보면 일반 텍스트 파일로 크레쉬 리포트를 볼 수 있다.

    크레쉬 조사하기
    크레쉬는 원래 코드 경로에의해 발생되지만, 항상 데이터베이스 쿼리 실행 메소드에서 끝난다.

    먼저 나는 스레드 이슈가 아닐까 의심했었다. 지난 몇년동안 스레드 버그러부터 나온 고통을 많이 겪었기 때문에, 나는 항상 그 고통을 먼저 떠올린다. 텍스트 파일로 된 크레쉬 리포트를 열어보니 Xcode가 보여준 것보다 훨씬 세부적인 내용을 볼 수 있었다. 예외타입은 EXC_CRASH (SIGKILL)이고, 노트(note)는 EXC_CORPSE_NOTIFY이며, 종료 원인은 Code 0xdead10cc였다. 나는 0xdead10cc가 무엇을 의미하는지 찾아보았다. 구글과 애플 개발자 포럼에는 이것에대한 이야기가 그리 많지는 않았지만 Technical Note 2151에서 이야기 해주었다.

    0xdead10cc 예외코드는 백그라운드에서 돌고있는 앱을 시스템 자원의 이유로 iOS에의해 종료시킨 경우를 말한다.

    여기서 내가 알아낸 점은 iOS가 앱을 종료시킬때 우리 코드상에서 뭔가 실수때문만은 아니며 정책 침해 때문일 수도 있다는 것이다. 그러나 Castro는 Adreess Book database를 사용하지도 않고 시스템 리소스와 비교가능한 어떤것을 사용하지 않는다고 생각했다. 나는 앱이 백그라운드에서 너무 오래 동작해버리면 어떻게할지 고민했는데, 우리가 받는 크레쉬 리포트의 몇 경우는 고작 2초정도 동작하고 말았다.

    나는 데이터베이스에서 일어나는 크레쉬를 쫒아 수많은 stack trace pointing으로부터 우리의 데이터베이스 SQLite 파일에 문제가 있음을 결국 찾아냈다. 그러나 왜 이러한 크레쉬가 2.1버전이 되서야 나타났을까?

    공유된 앱 컨테이너
    Castro의 2.1 릴리즈에서 최근 재생한 에피소드를 쉽게 공유하기 위한 기능인 iMessage 앱을 지원했다. 메시지 앱이 데이터베이스에 접근하는 과정에 우리는 공유된 앱 컨테이너로 데이터베이스를 옮겼다.

    파일이 공유된 공간에 있으면 그 팔일이 락이 걸리는 정책을 가진다면 어떨지 고민해보았다. 아마 iOS가 앱을 멈출때 다른 프로세스가 그 파일을 사용할 수 있는지 체크하고, 만약 그렇다면 iOS는 그 앱을 종료시킨다. 이것은 무리없이 있을법한 이야기이다.

    어떻게 크레쉬를 낼까
    크레쉬를 고치기위해 크레쉬를 재현해보는 것은 프로그래머에게 좋은 습관이다. 그 크레쉬를 흉내내기 위해 어느 부분을 임시로 다시 짤 수 있다. 만약 크레쉬가 확실히 일어나게 해보았으면, 이런 의심을 확인해보는것과 어느정도 거리가 있으며 우리에게 이 잼제적인 문제를 테스트 할 수 있는 기회를 제공한다. 다른 대안으로는, 무턱대고 일단 고친다음 배포하여 크레쉬 리포트를 받아보는 것이다. 이떨때는 정말 이 방법밖에 없을 수도 있는데, 너무 장황한 프로세스이며 여전히 사용자에게 크레쉬를 만들고 있다.

    특정 어떤 크레쉬는 다시 만들어보기 매우 어려운 경우가 있다. iOS 개발에 대한 적당한 평가가 여기에 있다고 생각한다. OS는 적극적으로 이 정책을 시행한다. 대부분의 경우 보안상이나 베터리 사용량 등에서 이 정책을 시행하면 좋은점이 많다. 그러나 이런 상황에서 테스팅이나 디버깅을 하면 더 힘들다. 정책을 살짝 바꾸고 손수 그 가능한 라이프 사이클 상태를 테스트 하는 것은 매우 번거로울 뿐더러 때론 불가능한 경우도 있다.

    이번의 경우 디버거를 연결한 상태에서 앱 정지를 시키는 방법이 없음을 알아냈다. 사실 디버거는 앱 정지를 막으며 시뮬레이터는 정확하게 시뮬레이팅하지 않는다. 나는 디버거 없이 앱을 실행하고 기기의 로그를 살펴보는 방법밖에 없었다.

    macOS 시에라의 새로운 콘솔 앱은 연결된 아이폰의 시스템 로그를 볼 수 있게 해준다. 기에라 이전에는 이 기능을 위해 Lemon Jar의 iOS 콘솔에 의존했으나, 애플이 로그에 접근할 수 있게 하면서 이 기술이 애플에게 받아드려지고 지원하게 되었다는 것을 알게되어 매우 기쁘다. 새로운 콘솔 앱을 어떻게 쓰는지 배우는 것도 의미있는 시간이 될 것이다. Xcode 디버거가 혼자서는 할 수 없는, 많은 iOS 동작을 보여준다. 이 로그는 시스템 전체의 로그이기때문에 디버깅에 무의미한 로그들도 많지만, 필터를 적용해서 간편하게 당신의 앱에 관련된 메시지만 뽑아낼 수 있다.

    0xdead10cc 크레쉬를 찾기 위해
    • 몇백개의 데이터베이스 쿼리에 applicationDidEnterBackground 메소드를 설정한다.
    • 내 맥에서 콘솔앱을 열고 Castro를 언급한 메시지로 걸러낸다.
    • Xcode를 통해 앱을 설치하되 앱 아이콘을 눌러 앱을 실행한다.
    • 앱을 백그라운드로 보내기위해 홈버튼을 누르고 바로 포켓몬고를 열어 메모리 압박으로 인한 Castro의 정지를 기대한다.
    이 단계를 몇번 거치고나면 크레쉬를 다시 만들어내어 콘솔에서 다뤄볼 수 있다. 이 흔적은 실제 크레쉬와 비슷하며 이제는 크레쉬의 원인을 찾아보기 훨씬 수훨해졌다.

    그러고나서 데이터베이스에 접근할 수 있는 백그라운드 동작이 있는 앱에서 한번에 찾고 고칠 수 있게 되었다(네트워크 reachability 변화에서 앱은 백그라운드 작업 없이 리프레쉬 하였다). 리프레쉬하는 과정에서 데이터베이스에 접근할때 앱이 일시정지해 있었다면, iOS가 앱을 죽일 것이다.

    백그라운드 패치를 해냄
    한가지 더 놀라운 사실을 공유하고 싶다. Castro2에서는 우리의 서버가 새로운 에피소드 배포를 알려주는데, 이것이 사용자 피드의 리프레쉬를 발생시킨다. iOS가 이 메시지를 앱에 보내면 완료 블락을 가지고있는 didReceiveRemoteNotification 메소드를 호출한다. 아래는 문서에서 발췌했다.

    여러분의 앱은 알림을 처리하고 특정 완료 핸들러 블락을 호출하기 위해 최대 30초까지 시간이 있다. 실제로는 알림 처리가 끝나자마자 핸들러 블락을 호출할 수 있을 것이다. 시스템은 백그라운드 경과시간, 베터리 소모량, 데이터비용을 여러분 앱에서 추적하고 있을 것이다.

    이 부분에 이상한 것이 있었다. 앞에서 언급했듯 Castro는 가끔씩 2초만에 죽어버렸다. 스택 추적에서 보면 아직 완료 블락이 호출되기도 전이었다. 따라서 문서에서는 30초까지 안전하다 했어도 어쨌든 정지되어버렸다.

    무슨일인지 알아보기 위해 개발자 지원 사건(developer technical support incidents)을 사용하여 꽤 놀라운 점을 발견했다. 굉장히 도움이 된 Kevin Elliott 대답을 찾았는데, 그 엔지니어의 경우가 내 경우와 비슷했다.

    dead10cc 이슈가 파일 락 때문에 일어난 것이라하여
    무엇이 실제로 이것을 발생시켰냐하면, 당신의 앱이 정지했을때 iOS가 당신의 앱 컨테이너에서 락이 걸린 파일(이 경우에는 SQLite 락)을 발견했기 때문이다. 그렇게 체크하는 이유는 앱 내에서 데이터가 오염되는 것을 줄이고, 관리하기 위한 방법으로 추가된 것이다. 여기서의 문재는, 락이 걸린 파일은 아직 수정중에 있고 합칠 수 없는 상태로 있을 것이다. 다른 말로는, 앱이 주어진 파일에 락을 거는 유일한 이유는 그 파일에대해 추가적으로 일련의 읽기/쓰기를 할 수도 있으며, 그 사이에 다른 쓰기가 끼어들어오지 않게 쓰기를 완료하기 위한 보장을 해주기 위함이다. 좀 더 확장하여, 파일이 아직 락이 걸려있다면 아직 쓰기를 완료하지 못했다는 의미이다. 이러한 상태의 한 파일은 몇몇 잠재적인 문제를 가진다.
    • 앱이 정지해있을때 앱을 죽이면 데이터 쓰기가 이루어질 수 없고 데이터를 오염시킨다.
    • 파일이 두 앱 간에 공유되고 두번째 앱/extension이 실행되면, 두번째 앱은 강제로 락을 풀고 일관된 상태에 파일을 복구하려 할 것이다. 첫번째 앱은 일관되지 않은 상태를 빠져나오거나, 공유된 파일을 완전히 무시한다.

    백그라운드 시간의 30초에 관해서는 
    ... 여기서 올바른 대답은 문제를 완전히 해결하는 것이다 - 만약 델리게이션으로 여러분의 작업을 다 못끝내면 백그라운드 작업을 시작하고 앱을 정지하기 전에 iOS가 (완료 블락에서) 알려줄 것이다 ...

    백그라운드에서 앱이 돌아가는 동안 공유된 컨테이너를 통해 파일을 접근할 수 있는 앱은 백그라운드 작업을 만들 수 있으며, 완료 블락이 30초동안 커버할 수 있다고 가정한다. 이런식으로 작업하기 위해 개발자들은 UIApplication에서 beginBackgroundTaskWithName:expirationHandler 메소드를 사용해 백그라운드 작업을 만들고, 백그라운드 작업이 끝나면 endBackgroundTask를 호출한다.

    추가로 kevin은 한가지 더 제안을 해주었는데, 치솟는 데이터 처리가 끝났음을 보장하고 좀 더 확실하게 이상한 버그를 해결하기 위한 방법으로, 백그라운드로 넘어갈 때 앱은 데이터베이스를 닫을 것을 제안했다.

    일반적으로 오퍼레이션으로 파일을 닫는 것은 불가사의한 버그(백그라운드에서 뭔가 동작하지 않아요)를 좀 더 명확한 버그(내 앱이 백그라운드에서 동작하지 않아요)로 바꿀 수 있다. 이 점이 문제를 직접 해결할 수 있게 도와줄 것이다.

    이것은 꽤 스마트해보인다. 백그라운드로 갈 때 앱의 일부를 종료하는 생각이 들지 않았지만, 확실히 의미가 있다. 나는 백그라운드에서 데이터베이스를 닫는 방법을 Castro의 다음 업데이트에서 찾아볼 것이다.

    결론
    백그라운드에서 계속해서 동작하는 백그라운드 작업을 추가하여 우리의 베타버전에 이 이슈를 고쳤다. 우리는 곧 고친 것을 포함한 업데이트를 배포할 것이다.

    아래에는 내가 배운 것들의 요약본이다.

    • 애플은 다른 서비스들이 발견하지 못한 크레쉬를 보고해준다. 아이튠즈 커넥트나 Xcode, 외부 서비스들까지도 크레쉬 리포트를 확인해보자.
    • 파일 락의 정책은 당신의 데이터베이스가 공유된 공간에 있을 때 더욱 엄격해진다.
    • 백그라운드 패치 완료 블락에 의존하는 것으로는 충분하지 않다. 활성 백그라운드 작업없이는 백그라운드에서 어떤 작업도 하지마라.
    • 앱 라이프 사이클의 특정 부분에서만 나타나는 이슈는 디버깅하기 힘들다. 아직 재현해보지 못했다면 새 시에라의 Console.app을 배워서 사용해보아라.
    • 기술 지원 사건들(Technical Support Incidents)을 기억하며, 이것도 우리가 매년 지불하는 개발자 맴버쉽에 포함된다(원문: Don’t forget about Technical Support Incidents, you get 2 included in your developer account payment every year).



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

    ,