'ios'에 해당하는 글 66건

목차

  • libxlsxwriter 소개

  • Swift 프로젝트에서 c라이브러리 pod 세팅하기

  • libxlsxwriter 사용법

  • 마치며


libxlsxwriter 소개

libxlsxwriter는 C라이브러리입니다. (벌써부터 머리가 지끈..) Objective-C를 사용할때는 편하게 썼었는데 요즘은 Swift만 쓰다보니 C를 쓰는게 여간 부담스러운 일이 아닐 수 없네요. 여러가지 라이브러리를 써봤지만 실패를 하곤 결국 C 라이브러리까지 찾게 되었습니다. 그래도 한가지 다행인 소식은 이 라이브러리가 CocoaPods을 지원한다는 것입니다.

아래는 여러가지 엑셀 편집 라이브러리입니다. 저는 결국 libxlsxwriter를 사용하기로 했습니다.

Swift 프로젝트에서 c라이브러리 pod 세팅하기

libxlsxwriter를 사용하려면 pod으로 워크스페이스를 만들어줘야하는데요. 그밖에도 몇가지 세팅이 더 필요합니다. C라이브러리라그런지 프레임워크로 인식을 못하더라구요. 그래서 bridge-header.h를 만들고 #include로 라이브러리의 파일을 불러오면 프로젝트의 Swift에서 사용할 수 있습니다.

Podfile 만들기

Podfile을 만듭니다. 확장자가 따로 없는 텍스트파일입니다. Podfile.xcodeproj 와 같은 경로(프로젝트 경로라 부르겠습니다)에 저장해둡니다.

libxlsx_구조.png

Podfile 의 내용물입니다. CocoaPods은 Objective-C나 Swift 언어의 프로젝트에서 사용하는 의존성 관리 툴인데, 사용하는 방법은 따로 찾아보시길 바랍니다.

source 'https://github.com/CocoaPods/Specs.git'
platform :ios, '9.0'
use_frameworks!

def my_pods
  pod 'libxlsxwriter', '~> 0.7.7'
end

target 'excel-example' do
  my_pods
end
target 'excel-exampleUITests' do
  my_pods
end

여기서 중요한 부분은 pod 'libxlsxwriter', '~> 0.7.7'입니다. 0.7.7버전의 libxlsxwriter 라이브러리를 설치하라는 부분이죠. (참고로 excel-example는 제 프로젝트 이름입니다.)

pod 설치

cd ~/{프로젝트경로}
pod install
excel-example-Bridging-Header.h 만들어서 설정

저는 use_frameworks!를 사용했었는데, libxlsxwriter는 C 라이브러리라 그런지 읽어오질 못했습니다. 그래서 따로 excel-example-Bridging-Header.h를 만들고 프로젝트에 명시해주었습니다(Build Setting에 들어가서 Swift Compiler - Code Generation > Objective-C Bridging headerexcel-example/excel-example-Bridging-Header.h으로 설정해주시면 됩니다).

C 라이브러리 불러올 수 있게 Pod 경로 설정

이번에는 헤더파일을 불러올 수 있게 Pod 경로를 명시해줍니다(마찬가지로 Build Setting에 들어가서 Search Paths > User Header Search PathsPods를 추가하고 recursive를 선택해줍니다).

여기까지 잘 따라오셨다면 이제 excel-example-Bridging-Header.h를 통해 C라이브러리를 불러와서 사용할 수 있습니다!

libxlsxwriter 사용법

excel-example-Bridging-Header.h에서 xlsxwriter.h 불러오기

#include "xlsxwriter.h"
저장할 경로 정하기

let fileManager = FileManager.default
let documentsURL = fileManager.urls(for: .documentDirectory, in: .userDomainMask)[0]
let url = documentsURL.appendingPathComponent("filename").appendingPathExtension("xlsx")
let path = NSString(string: url.path).fileSystemRepresentation
print("path:", url.path)
초기 설정

/* 새 워크북을 만들고 워크시트를 하나 추가하기 */
let workbook = new_workbook(path)
let worksheet = workbook_add_worksheet(workbook, nil);

아래같은 새 엑셀 파일을 하나 만들고 Sheet1 시트를 하나 만든 것입니다. 시트 이름을 바꾸려면 nil대신에 문자열을 넣어줍니다.

new_xlsx.png

초기 설정

/* 0컬럼부터 5컬럼까지 13.7 너비 적용 */
worksheet_set_column(worksheet, 0, 5, 13.7, nil);
셀 생성

/* 0 row, 4 column에 "이름" 문자열 넣기 */
worksheet_write_string(worksheet, 0, 4, "이름", nil);
/* 1 row, 2 column에 1.25123 숫자 넣기 */
worksheet_write_number(worksheet, 1, 2, 1.25123, nil);


/* 포멧 생성 */
let my_format = workbook_add_format(workbook);
format_set_align(my_format, UInt8(LXW_ALIGN_RIGHT.rawValue));
format_set_align(my_format, UInt8(LXW_ALIGN_VERTICAL_CENTER.rawValue));
format_set_bold(my_format);
format_set_font_size(my_format, 20)
format_set_font_name(my_format, "Arial")
/* 포멧이 적용된 셀 생성 */
worksheet_write_string(worksheet, 0, 1, "타이틀", my_format);
셀 합치기

worksheet_merge_range(worksheet, 3, 0, 3, 5, "합쳐진 셀", my_format);
파일 닫기

workbook_close(workbook);

모든 작업이 끝났으면 파일을 닫아주셔야합니다. 그리고 닫은 파일을 사용하려하면 에러가 발생합니다.

전체코드

https://github.com/tucan9389/excel-example-ios

위 코드로 만들어진 엑셀파일 예제코드 실행 ê²°ê³¼ 엑셀파일.png

마치며

코드 분할

C 인터페이스를 사용하다보니 Swift 코드와 안 어울리는것 같습니다. 저는 ExcelMaker.swift 파일로 빼내서 ExcelMaker 클래스를 만든 다음 클래스 메소드 static func createXLSX(filename: String, info: MyInfo) { ... }를 만들어서 사용했습니다.

Excel 포멧의 한계

엑셀파일 만들기가 csv파일 만들기와 비슷할 줄 알고 쉽게봤다가 고생 좀 하면서 느낀점은, MS는 엑셀파일을 자기네 프로그램 안에서만 쓰게 하려고 이렇게 사용하기 어렵게 만들었나 싶습니다. 아마도 엑셀파일 용량 줄이고 최적화시킨다고 이렇게 된게 아닐까 생각이 드는데, 라이브러리를 쓰실때도 오피셜 라이브러리가 따로 없기때문에 문제가 생겼을때 처리하기도 매우 곤란한 상황입니다. 여하튼 제3자 개발자가 엑셀파일을 직접 건드리는 작업은 피하는걸 권장하고싶습니다.

엑셀파일 만들기가 csv파일 만들기와 비슷할거라 생각하고 덤볐다가 고생했습니다. 그리고 고생한 이유를 찾아보았습니다.

  1. 엑셀파일 포멧이 표준을 지키지 않습니다.마이크로소프트 자신만의 포멧을 만들어 엑셀을 지원하고 있는데, 아마도 용량을 줄이거나 최적화시키려고 그랬겠지요..

  2. 공식라이브러리는 당연히 없고, 그러다보니 엑셀파일을 만들 수 있게 지원해주는 비공식(이지만 거의 공식처럼 쓰이는)라이브러리도 약한 편입니다.물론 Swift 라이브러리를 찾아보려다보니 범위가 좁긴 했지만, 엑셀 정도로 자주쓰이는 포멧은 Swift용 라이브러리가 있을법도한데 Star가 100개를 넘는 저장소를 찾기 힘들었습니다.

참고



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

,

원문: https://medium.com/developerinsider/best-ios-development-tips-and-tricks-6c42c1d208c1

이 글은 먼저 DeveloperInsider에 발행됩니다. 여기(원문)서 확인할 수 있습니다.

1. Xcode에서 빌드시간 확인하기

프로젝트의 정확한 빌드 시간을 모른다면 Xcode에서 아래 옵션을 켜자.

defaults write com.apple.dt.Xcode ShowBuildOperationDuration -bool YES

2. Swift 프로젝트 빌드시간 단축시키기

Xcode 9.2 릴리즈 노트에서는 BuildSystemScheduleInherentlyParallelCommandsExclusively 사용자 디폴트를 켜서 스위프트 빌드시간을 단축시켜주는 실험단계의 기능을 언급했습니다.

defaults write com.apple.dt.Xcode BuildSystemScheduleInherentlyParallelCommandsExclusively -bool NO

주의: 릴리즈 노트에 따르면 "빌드시간동안 메모리 사용을 증가시킬 수 있는 실험적 기능"이라고 했습니다.

3. Xcode에서 전체화면 모드로 시뮬레이터 사용하기

나는 Xcode 9 기능중에 전체화면으로 iOS 시뮬레이터와 Xcode를 실행시키는 것을 좋아한다. 이 기능을 사용하려면 그냥 터미널을 켜서 아래 명령을 실행시키면 된다.

defaults write com.apple.iphonesimulator AllowFullscreenMode -bool YES

시뮬레이터에 숨겨진 더 많은 기능을 사용하고싶으면 애플의 숨겨진 Internals 메뉴를 활성시키면 된다. 이렇게 하기 위해서는 루트 폴더에 AppleInternal이라는 빈 폴더를 만들자. 아래 명령을 실행시키고 시뮬레이터를 재시작하면 된다.

sudo mkdir /AppleInternal

4. iOS 시뮬레이터 화면 녹화하기

xcrun 명령어 유틸리티를 이용하면 시뮬레이터 창을 스크린샷 찍거나 비디오로 저장할 수 있습니다. 비디오를 찍기 위해서는 아래 명령을 실행시키세요.

xcrun simctl io booted recordVideo <filename>.<file extension>

예시:

xcrun simctl io booted recordVideo appvideo.mov

녹화를 멈추려면 control + c를 누르십시오. 파일이 만들어지면 현재 폴더에 저장됩니다.

5. 파인더에서 시뮬레이터에 파일 공유하기

Xcode 9부터는 시뮬레이터가 파인더 확장을 가지고 있습니다. 이것은 파이더 창에서 바로 파일을 공유할 수 있게 해줍니다. 그래도 파일을 시뮬레이터로 드레그&드롭해 주는게 더 빠른거 같네요.

아니면 아래 simctl 명령으로 이미지/비디오 파일을 전송할 수 있습니다.

xcrun simctl addmedia booted <PATH TO FILE>

6. sudo 인증시 지문 사용하기

맥북프로의 지문을 sudo 인증의 비밀번호로 사용하고 싶다면 /etc/pam.d/sudo를 수정하고 상단에 아래줄을 넣습니다.

auth sufficient pam_tid.so

이제 sudo로 지문을 사용할 수 있습니다.

7. 소리 알림과 함께 AutoLayout 컨스트레인트 디버깅하기

이번 방법은 AutoLayout 컨스트레인트를 디버깅하는데 훌륭한 벙법입니다. 그냥 실행시 UIConstraintBasedLayoutPlaySoundOnUnsatisfiable 인자를 넘겨주면 런타임시 컨스트레인트가 어긋날을 경우 소리가 납니다.

-_UIConstraintBasedLayoutPlaySoundOnUnsatisfiable YES

8. Xcode에서 사용할 수 없는 시뮬레이터 제거하기

이 조그만 명령어는 Xcode에서 사용할 수 없는 모든 시뮬레이터를 제거해 줍니다. 여기서 "unavailable"은 Xcode의 xcode-select 버전에서 사용불가능한 것을 의미합니다.

xcrun simctl delete unavailable

재밌었나요? 아래에 댓글을 달아주시면 고맙겠습니다.



이 블로그는 공부하고 공유하는 목적으로 운영되고 있습니다. 번역글에대한 피드백은 언제나 환영이며, 좋은글 추천도 함께 받고 있습니다. 피드백은 - 블로그 댓글 - 페이스북 페이지(@나는한다번역-ios) - 트위터(@tucan9389) 으로 보내주시면 됩니다.



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

,

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



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

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

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

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

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

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

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

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

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

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

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

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

Dave Verwer


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


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

,

출처: [애플 공식 문서] Integrating a Core ML Model into Your App(원문)

앱에 간단한 모델을 추가하고, 모델에 입력 데이터를 넣은 뒤, 모델 예측을 실행시키는 예제입니다.

프로젝트 다운로드

SDKs

iOS 11.0+

Xcode 9.2+

Framework

Core ML

On This Page

개요

함께 보기

개요

이번 간단한 앱은 학습된 간단한 모델 (MarsHabitatPricer.mlmodel)을 사용하여 화성거주지 가격을 예측할 것이다.

여러분의 Xcode 프로젝트에 모델 넣기

모델을 Xcode 프로젝트에 넣기 위해서는 모델을 드레그해서 프로젝트 네비게이터에 넣어야한다.

Xcode에서 모델을 열면 모델 타입, 예상되는 입출력등 모델에대한 정보를 확인할 수 있다. 여기서 모델 입력값은 태양열 전지판과 온실 갯수, 그리고 거주지 크기(에이커 단위)이다.

코드에서 모델 생성하기

Xcode는 모델의 입출력 정보를 이용하여 자동으로 모델의 인터페이스를 생성하는데, 이 인터페이스는 여러분의 코드상에서 모델과 상호작용하는데 쓰일 것이다. MarsHabitatPricer.mlmodel의 경우 Xcode는 MarsHabitatPricer모델을 표현하는 인터페이스와 모델의 입력(MarsHabitatPricerInput), 모델의 출력(MarsHabitatPricerOutput)을 생성한다.

모델을 생성하려면 만들어진 MarsHabitatPricer 클래스 생성자를 사용하자.


let model = MarsHabitatPricer()

모델에 넣을 입력값 가져오기

이번의 간단한 앱에서는 사용자로부터 모델의 입력값을 가져오기위해 UIPickerView를 사용한다.


func selectedRow(for feature: Feature) -> Int {
   return pickerView.selectedRow(inComponent: feature.rawValue)
}

let solarPanels = pickerDataSource.value(for: selectedRow(for: .solarPanels), feature: .solarPanels)
let greenhouses = pickerDataSource.value(for: selectedRow(for: .greenhouses), feature: .greenhouses)
let size = pickerDataSource.value(for: selectedRow(for: .size), feature: .size)

예측을위해 모델 사용하기

MarsHabitatPricer 클래스는 자동으로 생성된 prediction(solarPanels:greenhouses:size:) 메소드를 가지는데, 이 메소드는 모델 입력값으로부터 가격을 예측하는데 쓰인다. 이번의경우 태양열 전지판과 온실 갯수, 그리고 거주지 크기(에이커 단위)를 넣는다. 이 메소드는 MarsHabitatPricerOutput 인스턴스를 결과로 내뱉는다.


guard let marsHabitatPricerOutput = try? model.prediction(solarPanels: solarPanels, greenhouses: greenhouses, size: size) else {
   fatalError("Unexpected runtime error.")
}

예측된 가격을 가져오기위해 marsHabitatPricerOutputprice프로퍼티에 접근하여 앱 UI에 표시하자.


let price = marsHabitatPricerOutput.price
priceLabel.text = priceFormatter.string(for: price)
주의

자동으로 생성된 prediction(solarPanels:greenhouses:size:) 메소드에서 에러를 던질수 있다. Core ML으로 작업하면서 만나게될 가장 일반적인 에러 종류는 입력 데이터의 내용과 모델이 기대하는 내용이 서로 일치하지 않는 경우입니다(가령 잘못된 이미지 포멧이라던지).

Core ML 앱을 빌드하고 실행하기

Xcode는 Core ML 모델을 디바이스에 최적화시켜 컴파일한 후 리소스 안에 넣어둘 것입니다. 최적화된 모델은 앱번들안에 들어가며, 이 모델은 앱이 디바이스에서 실행되는동안 예측을 만드는데 쓰입니다.

함께 보기

첫번째 단계



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

,

1. Tensorflow mobile for iOS(Deprecated)

  • pod 'TensorFlow-experimental'

  • C++ 인터페이스

  • pod(의존성 관리 툴)으로 프로젝트 환경을 구축하면 450MB가 넘음. 여러 플랫폼을 묶다보니 용량이 큼. 예제를 앱으로 말면 약 25MB정도로 만들어짐.

  • Tensorflow Lite로 대체됨.

2. Tensorflow Lite

iOS 기기에서 TensorFlowLite 로 .tflite 모델을 사용할 수 있음

  • TensorFlow Lite를 바로 사용할 시 C++ 인터페이스

  • TensorFlow Mobile의 다음 버전

  • 더 작은 바이너리 크기

  • 적은 의존성, 더 나은 퍼포먼스

Tensorflow Lite for iOSiOS 기기에서 .tflite 모델을 실행시킨 결과



3. Core ML

iOS 기기에서 Core ML 로 .mlmodel 모델을 사용할 수 있음

  • Swift/Objective-C 인터페이스(Core ML)

  • 텐서플로우(.pb)를 CoreML(.mlmodel)로 변환하여 사용.

  • 애플이나 구글의 공식 프로젝트는 아니지만 스타가 많은 프로젝트.

4. MLKit with Firebase

Firebase 프레임워크에 들어있는 MLKit 로 .tflite 모델을 Swift 인터페이스로 사용할 수 있음

  • Swift, Objective-C 인터페이스

  • 18.05.09 구글I/O에서 발표

  • 기본 ML 기능 제공

    • Text Recognition (OCR)

    • Face Detection

    • Label Detection

    • Cloud Landmark Detection

    • Cloud Text Recognition

    • Cloud Label Detection

    • Custom Model Object Detection(.tflite 모델 사용)

MLKit for iOSiOS 기기에서 MLKit으로 .tflite 모델을 실행시킨 결과


+. tensorflow + swift

기계학습 모델을 만드는 새로운 방법.18년5월에 처음 공개되었고 개발중..



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

,
제목: iOS — Identifying Memory Leaks using the Xcode Memory Graph Debugger

이 짧은 포스팅에서 내가 설명할 것은 아래와 같다
  • Xcode의 메모리 그래프 디버거란
  • 이것을 어떻게 사용하고, 몇가지 팁들
  • 장/단점들

이것이 무엇인가
짧게말해 메모리 그래프 디버거는 다음 질문의 답변에 도움을 준다. 한 오브젝트가 왜 메모리에 남아있는가?

Xcode의 메모리 그래프 디버거는 레티인 사이클과 메모리 누수를 찾아내고 고치는데 도움을 준다. 그 일이 발생하면, 앱 실행이 일시정지되고 현재 힙에서 오브젝트를 보여주는데, 이 오브젝트가 살아있게 하는 참조들이 무엇인지 그 관계와 함께 나타난다.


어떻게 사용하는가
리테인 사이클과 메모리 누수를 식별하는데 3가지 간략한 단계가 있다.
  • 1. 아래처럼 Xcode scheme editor로 stack logging에 체크한다.

live allocations을 위해 Malloc stack logging을 켜기live allocations을 위해 Malloc stack logging을 켜기


'Live Allocation'의 logging만 켰다. 이것은 디버깅할때 'All Allocations'를 선택하는것보다 오버헤드가 적고, 레테인 사이클과 누수를 식별하는데 필요한 것이다.
  • 2. 분석하고 싶은 앱을 실행시키고(리테인 사이클이나 누수가 의심되는 행동), 그 디버그 바 버튼을 선택하여 메모리 그래프 디버깅 모드로 들어가자.

메모리 그래프 디버깅 버튼메모리 그래프 디버깅 버튼

  • 3 .메모리 그래프 디버거는 앱 실행을 일시정지하고 아래를 보여준다.

Xcode의 메모리 그래프 디버거 모드Xcode의 메모리 그래프 디버거 모드

왼편에 디버그 네비게이터가 앱의 힙 내용을 보여준다.

디버그 네비게이터에 type/instance를 선택하면 가운데 패널에서 인스턴스 참조들을 보여준다.

가운데 참조 패널에 인스턴스를 선택하면 오른편의 인스팩터 페널에서 일반적인 오브젝트 메모리 정보와 allocation backtrace를 보여준다.

누수는 아래처럼 디버그 네비게이터에서 볼 수 있다.

디버그 네비게이터에 나타난 누수디버그 네비게이터에 나타난 누수


  • 1. 메모리 누수를 식별하는데 도움이 되기위해서, 아래처럼 누수만 보이도록 힙 내용을 필터링할 수 있다.

메모리 누수를위한 필터링메모리 누수를위한 필터링

  • 2. 런타임 이슈 네비게이터도 유용한데, 식별된 모든 누수의 숫자를 표시한다.

수많은 메모리 누수들!수많은 메모리 누수들!


좋은점과 나쁜점
  • 좋은점: 운좋게 누수를 쉽게 찾아낼 수 있다(간단한 리테인 사이클). 예를들어 한 오브젝트가 클로저 프로퍼티 안에서 자신을 붙잡고 있을때. 이건 붙잡고 있는 참조를 weak로하여 쉽게 고쳐진다.
  • 나쁜점: 잘못 알려주는 경우. 예를들어 UIButton 오브젝트를 만들고 UIToolBars 아이템 배열에 추가했는데, 메모리 누수로 나왔지만 그 이유는 볼 수가 없었다.


유용한 링크들

이게다다! 📱🚀👍🏽


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

으로 보내주시면 됩니다.



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

,
제목: WWDC 2017 Viewing Guide

다른 년도, 다른 WWDC 그리고 더욱 많은 세션들을 이해하기위해서 준비했다. 이 글에서는 이번년도 가장 흥미있는 세션을 위한 나의 가이드를 공유한다.

기계학습, 드레그 앤 드롭, Xcode9
제일 먼저 Platform state of the union이 좋다.
  • Platforms State of the Union 아직 이 세션을 보지 않았다면, 여기에는 iOS, macOS, tvOS, watchOS의 전반적인 변화들을 발표한다.

이것들은 넘겨보지 말자
개인적으로 내가 가장 좋아하는 것들인데, 여러분은 그냥 넘겨볼 수도 있겠지만 시간을 투자해보는 것도 의미있는 일이 될 것이다.

앱 프레임워크
수많은 좋은 세션들이 여기에 있다. 나는 watchOS, tvOS, macOS 세션까지 넣지는 않았다. 여기에 내가 뽑은 리스트이다.
  • Session 201 What’s New in Cocoa Touch WWDC의 스타같은 팀, Eliza Block와 Josh Shaffer이 돌아와 Cocoa Touch의 바뀐점을 요약해준다.
  • Session 204 Updating Your App for iOS 11 통합된 서치바, 리프레시 컨트롤, 오토레이아웃 지원과함께 새로나온 큰 타이틀 네비게이션 바(large title Navigation bar)를 다룬다. 새로운 레이아웃 마진(layout margin)은 뷰컨트롤러의 루트뷰 세팅에대한 가이드를 하고(이제 시스템 최솟값을 바꿀 수 있다) 세이프 영역(safe area)는 상단, 하단 레이아웃 가이드를 대신한다. 또한 뷰컨트롤러는 더이상 스크롤뷰의 contentInset으로 더러워지지 않는다. 테이블뷰 셀은 이제 디폴트로 스스로 크기변경이 가능하고 더 쉽게 스와이프 액션을 넣을 수 있다.
  • Session 206 Introducing Password AutoFill for Apps 여러분의 웹 서비스에 로그인 기능이 있다면 쉽게 구현 가능해 보인다. 여러분의 사용자는 iOS 키체인에 증명서를 저장하게 된다.
  • Session 210 What’s New in Core Data 이번년도의 커다란 코어데이터 뉴스는 Core Spotlight 통합과 Persistent History Tracking을 지원한다는 점이다. 후자는 영속 저장소를 갱신하고있는 여러 컨텍스트와 여러 익스텐션의 이슈를 대상으로 한다. 포그라운드로 돌아오는 앱은 일부로 다시 불러오게 하지 않고서 변경사항들을 패치할 수 있다.
  • Session 214 What’s New in SiriKit 아마 예상한것처럼 극적인 변화는 아닐 것이나, 리스트와 노트를 위한 새로운 계획을 받았고 비주얼 (QR) 코드를 보여주는 것도 곧 나올 것이다.
  • Session 219 Modern User Interaction on iOS 애플에서는 이것을 지향하지는 않을 수 있지만, 여러분은 이제 컨트롤센터, 노티피케이션, 액션을 넘은 슬라이드가 발생하기 전에 스크린의 모서리에서 일어나는 시스템 스와이프를 가로챌 수 있다.
  • Session 225 What’s New in Safari View Controller 여러분의 앱에 더 매칭시키기위해 바(bar)와 틴트 칼라(tint color)를 커스터마이징 한다. 드레그 앤 드롭에대한 지원을 탑재한다. 이제 각 앱은 사파리 브라우저와 별개로된 고유의 쿠키 저장소(jar)를 얻었다. 취소나 닫기를 위해 완료(done) 버튼을 변경하고, 공유 시트로부터 활성화를 배제한다.
  • Session 230 Advanced Animations with UIKit 프로퍼티 애니메이터를 사용하는 것에대한 좋은 요약이고, CALayer의 cornerRadius 속성은 이제 애니메이션 가능한 점에대한 좋은 팁이다.
  • Session 235 Building Visually Rich User Experiences 세션 230를 이은 코어 애니메이션의 좋은 팁과 트릭이다.
  • Session 237 What’s New in MapKit 지도에서 여러분의 데이터를 표현할 수 있는 새로운 맵 타입. 새로운 MKMarkerAnnotationView는 주석들이 겹칠때 이것을 묶어준다.
  • Session 241 Introducing PDFKit on the iOS CoreGraphics 위에 만들었지만 현대의 스위프트와 Objective-C API를 지원한다. PDF 문서를 더 쉽게 보고 주석을 달기위해 만들어졌다.
  • Session 242 The Keys to a Better Text Input Experience 높이를 변경하거나 커스텀 인풋 뷰를 넣은 키보드를 다루는 여러 방법을 소개한다. 사용자가 선택한 키보드가 어떤것인지 앱이 기억하게 만들 수 있다. iOS는 똑똑하게도 자동으로 따옴표와 대쉬를 바꿔준다.
  • 개발바들을위해 애플은 동적 타입을 지원하여 계속해서 밀고있다. iOS11에서 큰 변화로는, 모든 스타일이 이제 여분의 큰 접근성 크기로 커진다(예전에는 바디 스타일만 가능했다). 또한 애플은 UIFontMetrics를 사용하여 커스텀 폰트도 쉽게 지원할 수 있게 만들었다. 어셋(asset) 카탈로그에서 PDF vector asset은 이제 벡터 데이터를 저장할 수 있게 설정할 수 있다.

드래그 앤 드롭
이번년도의 커다란 UIKit 증진사항은 앱간의 드래그 앤 드롭을 지원한다는 점이다.

여러분이 시간이 있다면 아래 세션은 더 깊은 내용을 담고 있다.
자연어 처리(NLP, Natural Language Processing), 기계 학습(ML, Machine Learning), 증강 현슬(AR, Augmented Reality)
애플이 밀고 있는 이번년도의 또다른 큰 테마는, 우리 앱에서 NLP, ML, AR을 쉽게 사용할 수 있게 만들고 있다는 점이다. 또한 애플은 개인정보, 속도, 낮은 지연율에 대한 이점을 강조했는데, 클라우드 기반 서비스 없이 기기에서 가능하기 때문이다.

스위프트
스위프트 에볼루션 과정이 공개로 됨으로서 새로운 스위프트4를 보기위해 WWDC까지 기다릴 필요가 없어졌다. 그럼에도 잘 정리해 놓았다.

개발 
Xcode9는 스위프트를 크게 돕기위해 리팩토링 지원에대한 업데이트를 보고 있는것 같다.

디자인과 접근성
애플이 계속해서 개선하고 강조하는 부분으로, 모두를 위한 앱 디자인과 제작에서 필요한 점을 볼 수 있다.
  • Session 110 Convenience for You is Independence for me Todd Stabelfeldt (aka the Quadfather)는 8살때부터 사지마비가 왔는데, 접근성 제작으로 다른 앱 디자인을 만들어 그의 발표에서 영감을 얻자. 만약 여러분 앱의 접근가능함에 의심이 된다면 그가 손으로 사용하는 스위치 컨트롤 시연을 보자 (가끔 혀를 쓰기도 한다).
  • Session 215 What’s New in Accessibility iOS11 사용자를 위해 나온 일반적인 접근성 기능 몇개가 크게 개선되었다. 여러분은 이제 텍스트로 시리와 인터렉트할 수 있다. 모든 시스템은 동적 타입을 위해 감사(audit)할수 있게 되는데, 이제 모든 스타일에 최고로 큰 접근성 크기를 사용할 수 있다(여러분의 UI에서 테스트해보라!). 이제 VoiceOver은 이미지 안에 있는 텍스트를 인지해서 말할 수 있고, 컨테이너 타입(container type) 지원이 개선되었다. 마지막으로 당신이 드래그 앤 드롭 지원을 고려하고 있다면 접근성을 고려하는 것도 잊지 말자.
  • Session 803 Designing Sound Highly entertaining talk on using sound including how some of the familiar Apple system sounds are created.

보안과 네트워킹

앱 스토어
여러분이 앱내구매(IAP, In App Purchase)를 사용한다면 앱스토어에서 바뀌게될 부분을 주목하자. 아래 두 세션을 추천하고 싶다.
  • 몇몇 멋진 개선이다: 올해 후반기에 TestFlight 제한이 10,000명 사용자로 올라갈 것이다. 더이상 iTunes Connect에서 앱 아이콘을 관리할 필요가 없다. 앱 부제목과 프로모셔널 텍스트 필드가 있다. 20 API까지 올렸다. 단계별 배포(phased release).

미디어
요점은 MusicKit이 Apple Music API에 접근할 수 있게 된 것과 Photos API가 바뀐 부분이다. 이미지 피커(image picker)는 과정에서 사라졌으며, 따라서 사용자는 허가를 묻지 않고 이미지를 선택할 수 있다.



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

으로 보내주시면 됩니다.


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

,
제목: Protocols and MVVM in Swift to avoid repetition

우리가 Viable을 최신 iOS 앱의 토대를 만들어갈때, 이전 iOS 앱으로부터 배우려 했다. 우리는 2가지 목표를 정했다.
  • Massive View Controller(MVC를 비꼬는 약자) 증후군 피하기
  • 가능한 적은 중복
초기에 디자인팀이 만든 Viable 화면에는 수많은 비슷한 화면이었다. 아래에 간단하게만든 예시를 한번 보자. 두 화면은 모두 상단에 UILabel이 있고 검색 결과를 보여주는 UITableView가 있다. 각각의 결과에대한 UITableViewCell도 매우 비슷했다. 이들은 다소 레이아웃을 공유했고 데이터만 달랐다.


Viable은 화면에 표시되는 6가지 타입의 데이터가 있었으며, 각 타입마다 새로운 뷰 컨트롤러를 만들어서 코드 중복이 많았다. 그리하여 우리는 6개의 데이터 타입을 모두 표시할 수 있는 SearchResultsViewcController를 만들었다.
데이터 타입에따라 다르게 렌더링하기위해 제일 처음 떠오른 방법으로는, tableView:cellForRowAtIndexPath:에 거대한 if/else문이었는데, 코드 규모가 잘 정연되지 못했고 결국 길고 못난 메소드가 되버렸다.

MVVM와 프로토콜을 사용하여 해결하기
테일러 구이던(Taylor Guidon)은 MVVM(Model-View-ViewModel) 패턴에대한 입문의 글을 포스팅했는데, 여기서 확인할 수 있다. 이 글은 그 요약 버전인데, 데모 프로젝트에 적용한 것을 깃헙에서 확인할 수 있다.

모델(Models)
모델 그룹에서의 모델은 데이터를 담고 있는다. 우리는 DomainModelProductModel을 가지는데, 둘 다 구조체이다. DomainModel은 이름(name)과 그 상태 도메인을 가질것이고, ProductModel은 제품이름(product name), 제품평점(product rating), 제품로고(product logo), 제품가격(product price)을 가진다.
struct Product {
    var name: String

    var rating: Double

    var price: Double?
}

뷰모델(View Models)
모든 데이터 모델은 해당되는 뷰모델을 가진다. 그 말은, 우리 예제에서는 DomainViewModelProductViewModel을 가진다는 뜻이다. 뷰모델은 모델로부터 데이터를 받아서 사용자에게 보여주기전에 뷰에 적용시킨다. 예를들어 ProductViewModel4.99 가격의 부동소수점을 받아서 $4.99라 읽히는 문자열로 변형한다.
class ProductViewModel: CellRepresentable {
    var product: Product

    var rowHeight: CGFloat = 80

    var price: String {
        guard let price = product.price else {
            return "free"
        }

        return "$\(price)"
    }

    init(product: Product) {
        self.product = product
    }

    func cellInstance(_ tableView: UITableView, indexPath: IndexPath) -> UITableViewCell {
        // Dequeue a cell

        let cell = tableView.dequeueReusableCell(withIdentifier: "ProductCell", for: indexPath) as! ProductTableViewCell


        // Pass ourselves (the view model) to setup the cell

        cell.setup(vm: self)

        // Return the cell

        return cell
    }
}

뷰(Views)
우리 예제에서 뷰는 두가지 UITableViewCell이다. DomainTableViewCellProductTableViewCell를 가진다. 레이아웃은 앱의 스토리보드에 만들어놓았따. 두 클래스 모두 간단한데, 뷰모델을 인자로 받는 setup 메소드 하나만 가지고 있다. 뷰모델은 셀에 정보를 옮길때 사용되는데, 예를들자면 읽을 수 있는 가격($4.99)을 받아서 UILabel의 테스트 프로퍼티에 할당한다.
class ProductTableViewCell: UITableViewCell {
    func setup(vm: ProductViewModel) {
        self.textLabel?.text = vm.product.name
        self.detailTextLabel?.text = vm.price
    }
}

합쳐보기
3가지 큰 기둥을 만들었으니 합쳐보자. 뷰 컨트롤러와 뷰모델을 합치기위해 프로토콜을 사용할 것이다. 프로토콜은 이것을 따르는 클래스나 구조체가 어떤 변수와 메소드를 가질지 정의한다. 계약서를 생각해보자. 여러분이 X라는 프로토콜을 따르고 싶다면, 여기에 명시된 모든것을 구현해야한다. 간결하게 만들기위해 한 프로퍼티와 한 메소드만 넣어놨다. DomainViewModelProductViewModel 둘 다 이 프로토콜을 따른다.
protocol CellRepresentable {
    var rowHeight: CGFloat { get }
    func cellInstance(_ tableView: UITableView, indexPath: IndexPath) -> UITableViewCell

}
스위프트에서 프로토콜은 일급 객체(first class citizen)이므로 SearchResultsViewController 파일은 화면에 표시할때 필요한 뷰모델 배열을 가진다. [DomainViewModel]()이나 [ProductViewModel]()처럼 배열을 초기화하는것 대신, 프로토콜을 사용하여 뷰모델을 담아둘 수 있다. var data = [CellRepresentable](). DomainViewModelProductViewModelCellRepresentable을 따르기 때문에 배열은 둘 다 담아둘 수 있다.

이제 배열에 있는 모든 요소를 CellRepresentable을 따르게하여 UITableViewCell을 반환하는 cellInstance(_ tableView: UITableView, indexPath: IndexPath) 메소드를 가진다고 확신하게 만들자. 고맙게도 tableView:cellForRowAtIndexPath:cellInstance 메소드만 호출하면 된다.
extension SearchresultsViewController: UITableViewDataSource {
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return data.count
    }
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        return data[indexPath.row].cellInstance(tableView, indexPath: indexPath)
    }
}

extension SearchresultsViewController: UITableViewDelegate {
    func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
        return data[indexPath.row].rowHeight
    }
}
이게 전부다. 우리는 다양한 셀의 다양한 열 높이로 표시해주는 작은 뷰컨트롤러를 가지게 되었다! ISL의 깃헙 페이지에서 데모 프로젝트를 확인해볼 수 있다. 제안이나 질문이 있다면 주저하지말고 @thomasdegry에 트윗해달라.



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

으로 보내주시면 됩니다.


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

,
제목: Picking the right way of failing in Swift

스위프트의 주요 초점중 한가지는 바로 컴파일타임 세이프티이다. 런타임 에러의 경향을 줄여주고 더욱 예상가능한 코드를 작성하는데 개발자들이 집중할 수 있게 해준다. 그러나 때론 여러 이유로 실패한다. 그래서 이번주에는 어떻게 적절하게 실패를 다루는지 보고, 이것을 처분하기위해 어떤 도구를 가져야하는 볼것이다.

몇주전에 우리는 실제 옵셔널이 아닌 옵셔널을 어떻게 다루는지("Handling non-optional optionals in Swift")에대해 보았었다. 지난 포스트에서 나는 강제 언랩핑하는것 대신 guard와함께 preconditionFailure() 사용에대한 경우를 만들었고, 이것을 위해 Require라는 경량의 프레임워크를 소개했었다.

그 포스트 이후로, 많은 사람들이 preconditionFailure()assert()의 차이가 무엇인지, 스위프트의 throwing 기능에 어떻게 연관시킬 수 있는지 물어보았다. 그래서 이번 포스트에서는 각각 그것을 사용할때 그 모든 언어 특징을 좀 더 살펴보자.

한 리스트로 시작해보자.
아래는 스위프트에서 에러를 처리하는 (내가 아는) 모든 것이다.
  • nil을 반환하던지 에러 열거형 케이스를 반환한다. 에러 처리의 가장 간단한 형식은 에러를 만난 함수에서 nil을 반환하는 것이다 (혹은 리턴타입으로 Result 열거형을 사용한다면 .error를 반환한다). 이것은 많은 상황에서 아주 유용할 수 있지만, 모든 에러 처리에대해 과용하면 사용하기 성가셔지고, 로직 결점이 숨어있는 위험을 안게 된다.
  • 에러를 throw한다(throw MyError를 사용하여). 잠재적인 에러 처리를 위해 호출자에서 do, try, catch 패턴을 사용해야한다. 혹은 호출 시점에서 try?를 사용하여 에러를 무시할 수도 있다.
  • assert()assertionFailure()를 사용하여 특정 조건이 참인지 검증한다. 디폴트로, 디버그 빌드에서는 fatal error를 내고, 배포 빌드에서는 무시한다. assert를 유발하면 실행이 멈출것이라는 보장이 없으므로 위험한 런타임 경고처럼 보인다.
  • assert대신 precondition()preconditionFailure()을 사용한다. 핵심적으로 다른점은 배포 빌드일지라도 항상* 판별한다는 점이다. 이 의미는 조건이 성립하지 않으면 절때 계속하지 않음을 보장한다는 뜻이다.
  • fatalError()를 호출한다. 이것은 UIViewController같은 시스템 클래스를 따르는 NSCoding을 상속할때, 아마 Xcode가 생성한 init(coder:) 구현에서 보았을 것이다.
  • exit()를 호출한다. 이것은 코드와함께 여러분의 프로세스를 종료한다. 이것은 전역 범위에서 종료하고 싶을때, 커멘드라인 툴이나 스크립트에서 매우 유용하다(예를들어 main.swift에서)
*Ounchecked 최적화 모드를 사용하여 컴파일을 하지 않는다면

복구가능한 vs 복구불가능한
실패를 올바르게 잡아낼때 생각해야하는 키포인트는 발생한 에러가 복구가능한지 불가능한지 정하는 것이다.

예를들어 우리가 서버에 호출하고있는데 에러를 받았다고 하자. 우리가 얼마나 멋진 프로그래머인지, 서버 기반이 얼마나 탄탄한지 상관없이 종종 이런일이 일아난다. 따라서 fatal과 복구불가능하게 이런 에러 타입을 처리하는것은 종종 실수이다. 대신, 우리가 원하는것은 복구하여 사용자에게 에러 화면의 양식을 보여주는 것이다.

따라서, 이런경우 어떻게 적절한 방법으로 실패를 뽑아낼까? 위의 리스트를 한번 보면, 복구가능한 기술과 복구불가능한 기술로 나눌 수 있다. 아래처럼 말이다.

복구가능한 기술
  • nil을 반환하던지 에러 열거형 케이스를 반환한다.
  • 에러를 throw한다.

복구불가능한 기술
  • assert()를 사용한다.
  • precondition()을 사용한다.
  • fatalError()를 호출한다.
  • exit()를 호출한다.

이 경우 비동기 처리를 다루기 떄문에, 아마 nil을 반환하거나 에러 열거형 케이스를 반환하는게 제일 좋은 방법이다. 아래처럼 말이다.
class DataLoader {
     enum Result {
          case success(Data)
          case failure(Error?)
     }

     func loadData(from url: URL, completionHandler: @escaping (Result) -> Void) {
          let task = urlSession.dataTask(with: url) { data, response, error in
               guard let data = data else {
                    completionHandler(.failure(error))
                    return
               }

               completionHandler(.success(data))
          }

          task.resume()
     }
}
적절한 방법으로 에러를 처리하려고 우리 API를 사용자들에게 강요하는 것은 비동기 API에서, throw는 좋은 선택이다.
class StringFormatter {
     enum Error: Swift.Error {
          case emptyString
     }

     func format(_ string: String) throws -> String {
          guard !string.isEmpty else {
               throw Error.emptyString
          }

          return string.replaceOccurrences(of: "\n", with: " ")
     }
}
그러나 때론 에러가 복구되지 않는다. 예를들어 앱을 실행하는동안 설정파일을 불러와야한다고 하자. 만약 설정파일을 놓힌다면 앱은 정의되지 않은 상태로 갈것이다. 이 경우는 프로그램을 계속 실행하는것 보단 크래쉬를 내는게 낫다. 그러니 더 강한것을 사용하여 실패를 복구하지 않는 방법이 더 적절하다.

이 경우, 설정 파일을 놓혔을 경우에 실행을 멈추기위해 preconditionFailure()을 사용한다.
guard let config = FileLoader().loadFile(named: "Config.json") else {
     preconditionFailture("Failed to load config file")
}

프로그래머 에러 vs 실행 에러
만드는데 중요한 또다른 구별은 결점이있는 로직에의해 에러가 만들어진것인지 잘못된 설정에 의해 에러가 만들어진 것인지이다. 혹은 에러가 앱 플로우의 합법적인 부분으로 고려될 수 있는지로 구별된다. 기본적으로 프로그래머가 만든 것읹 외부 요인이 만든 것인지이다.

프로그래머 에러에대해 대비할 때는 거의 항상 복구하지않는 기술을 사용하고 싶을 것이다. 이런식으로 앱 전체에 걸쳐 특별한 상황을 코딩하지 않아도 된다. 좋은 테스트 수트가 가능한빨리 이런 에러를 잡을 수 있게 해줄 것이다.

예를들어, 한 뷰를 만들것인데 이것을 사용하기전에 바인딩된 ViewModel이 필요하다고 가정하자. 이 ViewModel은 우리 코드에서 옵셔널이지만 사용할때마다 언랩핑하고 싶지 않응ㄹ 것이다. 그러나 제품의 상태에선 ViewModel을 잃어버렸을때 크래쉬를 내고 싶지 않다. 디버그에서 에러를 받는것으로 충분하다. assert를 사용하는 경우가 되겠다.
class DetailDView: UIView {
     struct ViewModel {
          var title: String
          var subtitle: String
          var action: String
     }

     var viewModel: ViewMode?

     override func didMoveToSuperview() {
          super.didMoveToSuperview()

          guard let viewMode = viewModel else {
               assertionFailure("No view model assigned to Detailview.")
               return
          }

          titleLabel.text = viewModel.title
          subtitleLabel.text = viewModel.subtitle
          actionButton.setTitle(viewModel.action, for: .normal)
     }
}
assertionFailure()는 배포빌드에서 묵묵히 실패할것이기 때문에 guard문에서 return해야함을 인지하자.

결론
스위프트에서 가능한 기술을 다루는 여러 에러들 사이에 차이를 명확하게하는데 도움이 되었으면 좋겠다. 내 조언은 한 기술만을 고수하는게 아니라 그 상황에 맞는 가장 적절한 것을 고르는 것이다. 에러가 치명적으로 다룰 수 없어도 사용자 경험을 방해하지 않아야 하기 때문에, 나는 보통 가능한 항상 에러를 복구하려고 노력하는것을 제안하는 편이다.

또한 print(error)는 에러 처리가 아님을 기억하자.

질문이 생기거나 피드백을 주고 싶다면 Twitter로 연락할 수 있다. 또한 나의 다음 주간 플로그 포스트에서 다뤄보고 싶은 주제가 있으면 나에게 알려달라.

읽어주어서 감사하다!



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

으로 보내주시면 됩니다.


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

,
제목: My Development Toolset 2017 for iOS


모두들 반갑다! 내가 현재 맥북에서 사용하는 iOS 개발 툴, 잡다한 것, 서비스, 웹사이트, 프레임워크 툴에 대해 소개하고자 한다.

  • Git 사용을 위한 GitKraken를 추천한다. GitKraken은 자동으로 GitFlow를 추가할 수 있다.
  • 이슈에 대해서는 이 Gitscout를 따로 쓰고 있다.
  • GitBar는 커밋되지 않은 소스코드를 상기시켜준다.
  • Build Time Analyzer는 여러분 프로젝트의 스위프트 빌드타임을 쪼개에 보야주는 macOS용 앱이다.
  • Tomato One를 사용해서 Pomodoro Technique로 효율을 증가시키자.
  • WatchDog는 Xcode를 끄거나 macOS를 재시작하지 않은채 자동으로 DerivedData를 정리해준다.
  • Cakebrew는 GUI로 Homebrew를 관리한다.
  • Liya는 하나의 인터페이스에서 MySQL, PostgreSQL, SQLite3에 접근할 수 있다.
  • Quiver는 코드 조각 메니저이고 메모나 코드, 파일을 저장해놓는 노트이다. 5년동안 snippets를 사용했었는데, 스위프트가 추가되었다는 이유로 이것을 사용했다. 그러나 이제 바꿀때가 된것 같다.
  • Oh My Zsh와함께 터미널을 쓰면 터미널이 눈과 두뇌를 가지게 될 것이다.
  • 나중에 읽고 싶으면 Pocket에 담아두자. 사파리나 크롬 익스텐션은 설치하지 말자. LINER 이것도 있다.
  • 이미지를 작게 만드려면 Squash를 사용하자. 무료의 솔루션으로는 guetzli도 있다.
  • 여러분이 Sketch는 알거라 생각된다. 그러면 Zeplin라는 것도 있는데, 이것도 확인해보자.
  • 인터렉트 레이아웃 텍스트나 모든 화면 크기를 확인하고자할때는 RevealApp를 쓰자.
  • SizeUp는 단축키로 여러분의 창 크기를 조정하고 위치를 조정해준다.
  • BitBar는 모든 스크립트 출력을 macOS 메뉴바에 보여준다. 더 중요한 것은 리뷰의 평균 점수, 앱 상태, 버전, 이런 당신이 필요로하는 어떤것이든 넣을 수 있다. dev 플러그인을 확인하는 것을 잊지말자.
  • LittleIpsum 단어나 문장, 문단을 생성해준다.
  • 지금은 코드 snippets을 사용하고 있다.코드 snippets이 어디에 쓰였는지, 언제 쓰였는지 알고 싶다면 Paste는 클립보드 관리자이다.
  • Gitsome은 터미널에서 쓸 수 있는 멋진 Git/GitHub 커멘드라인 인터페이스이다.
  • Easy APNs Provider는 최고의 푸시 노티피케이션 테스팅 툴이다.
  • Houston은 애플 푸시 노티피케이션을 위한 간단한 gem이다.
  • Bee는 JIRA 클라이언트, JIRA 에자일 클라이언트, 깃헙 이슈 클라이언트, FogBugz 클라이언트, 마크다운 편집기의 모든 기능이 들어있다.

팟케스트
여러분이 팟케스트를 들을 시간이 있다면 PodcastMenu는 내가 좋아하는 맥용 앱이다.
  • Fedrico Viticci, John Voorhees의 AppStories를 확인해보아라.

프레임워크
  • 내 프로젝트에서 가짜 데이터가 필요하다면 Fakery를 추천한다.
  • LocalizationKit는 스위프트로 다이나믹 다국어 번역 배달 시스템이다.
  • Armchair는 스위프트로 작성된 간단하지만 강력한 앱 리뷰 관리자이다. iOS와 macOS용이 있다.
  • Siren는 설치된 iOS 앱의 버전을 확인하고 새 버전이 출시되었을 때 알림을 준다.
  • SwiftGen는 자동 스위프트 코드 생성을 위한 코드 생성기이다.
  • Bohr는 화면을 설정하기위한 초기 세팅을 할 수 있게 해준다.
  • SwiftyJSON는 JSON 파싱계의 최고봉이다. 만약 문제를 겪으면 kitura(링크) 버전을 확인해보자.
  • SwiftyBeaver는 스위프트를 위한 다채롭고, 유연하며, 가벼운 로그 툴이다. 그리고 탐색, 검색, 필터링을 위한 맥용 앱을 제공한다.
  • 스위프트에서 JSON 파싱 라이브러리의 대안으로는 ✨ Gloss가 있다.
  • Hero는 커스텀된 뷰컨트롤러 트렌지션을 제공한다.



웹사이트
  • AppSight는 iOS 모바일 앱에서 회사가 어떤 SDK와 서비스를 사용했는지 찾아준다.
  • iOSCookies는 스위프트로 작성된 iOS 라이브러리 컬랙션이다.
  • Ole Begemann는 iOS에대한 거장의 블로그이다.
  • littlebitesofcocoa는 iOS와 맥을 위한 팁과 기술이다.
  • 멋진 모바일 엔지니어링 블로그이다. 당신은 Toptal에 있는 모든 글을 읽으면 좋을 것이다.
  • Erica Sadun는 깊은 iOS 블로그이다.

잡다한것

서비스
  • 로컬에 MongoDB, MySQL, Jenkins, Minecraft을 설치하고 싶지 않으면 Docker가 그것에대해 혹은 그 이상으로 도움을 줄것이다. Docker를 사용하면 여러분의 프로젝트나 프로토타입을 위해 백엔드, 데이터베이스, 배포된 앱을 빌드할 수 있다. 스위프트를 위한 Docker를 확인해보자.
  • 여러분이 Docker를 사용하고 있다면, KitematicCaptain를 사용하면 좋다.
  • 클라우드 컴퓨팅 플랫폼이 필요하면 나는 digitalOcean를 즐겨쓴다.
  • 프로젝트를 관리하는데는 Asana를 사용한다.
  • rollout.io는 앱에서 크래쉬를 고치고 파라미터를 재정의한다.
  • GoogleAnalytics는 제품 사용을 분석해준다.
  • Zoommy에서는 한 곳에서 무료 사진을 찾을 수 있다.
  • heNounProject는 무료이며 높은 품질의 아이콘을 가지고 있다.

편집
  • Ulysses는 발행에있어서 최고의 맥용 앱이며, 마크다운 편집기, 글을 쓰는데 좋다.
  • Podfile과 함께 작업할때는 나는 Atom를 더 선호한다.
  • node.js나 express.js로 작업한다면 Brackets 사용을 선호한다. 여러분이 WWDC2016(링크) 테마를 다우받고 싶으면 여기서 다운받을 수 있다.

이게다다. 읽어줘서 고맙다. 이 모든 툴이 여러분의 생산성에 도움이 되길 바란다.



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

으로 보내주시면 됩니다.



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

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

,

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

,





애플은 이번 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

,

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

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

터치 핸들링
터치 이벤트는 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

,

이전 포스팅에서 NSScreencast iOS 앱을 위한 다운로드 시스템을 어떻게 설계하는지에대해 이야기했다.

여기서 사용자에게 강제로 포그라운드(foreground)에 붙잡아두고 다운로드 받게 할 필요가 없었으므로 자연스럽게 백그라운드 다운로드를 지원하게되었다.

겉으로 보았을때는 꽤나 쉬워보였다. 세션을 백그라운드 세션 구셩으로하고, id를 부여해서, 앱과는 분리된 진행으로 다운로드가 일어날 것이다.

백그라운드 세션을 사용할 때, 세션과 델리게이트가 주어진 다운로드에서 나중에 업데이트를 받기 위해 다시 생성될 필요가 있어보였는데, 블락 기반의 작업 API를 쓸 수 없었다. 여러 시나리오를 생각했지만, 먼저 이상적인 방식을 이야기해보자.
  • 사용자가 다운로드를 시작하고 앱을 멈춘다.
  • 몇 초뒤(내 경험으로는 10-30초이다) 앱은 꺼진다.
  • 다운로드는 다른 프로세스에서 계속 진행된다.
  • 다운로드가 끝나면, 앱은 다시 켜지고 당신의 앱 델리게이트가 다운받기 시작할때 사용한 id와 함께 application(handleEventsForBackgroundSessionWithIdentifier:)에서 받는다.
팁: Xcode에서 디버깅할때, 사실상 디버거는 백그라운드에 있는 동안 앱이 꺼지는 것을 막는다. 따라서 나는 Wait for Launch 옵션을 켜고, 수동으로 앱을 키고 다운로드를 시작한 뒤, 디버거를 켜기 전에 앱을 백그라운드에 놔두었다.

이 메소드가 호출될 때, 같은 id로 새로운 세션 구성을 만들어야한다. 그리고 델리게이트 인스턴스를 심는다. 이 시스템은 다운로드 상태를 즉시 여러분의 델리게이트에 알릴 수 있다.

그러나 어떤 다운로드일까?

실제로는 모른다. 당신이 얻은 모든 것은 원래의 요청 URL인데, 충분한 정보일 수도 있고 아닐수도 있다. URL은 가끔 바뀌기도 하므로 유일한 값이 아닐 수도 있으며, 가장 좋은 키 값이 아니다. http와 https 둘 다 가지고 있을때, 이런 경로들은 같은 리소스를 가리킬 수도 있고, 아마 할쪽이 리다이렉트 할것이다. 왜 이런게 불편한지에대한 여러 이유가 있다. 내 경우 일반적인 에피소드 URL들을 연관지어서 가지고 있는데, 이 URL은 아마존 클라으드프론트 URL로 리다이렉트하여, 유일하지도않고 임시적이기까지했다. 따라서 알림을 받은 에피소드 모델로 돌아갈 방법이 없게 되버린다.

이것이 API에서 좀 이상한 부분인데, 문서에 명확하게 명시되있지 않았다. 그러나 내가 찾은 해결책은 각 다운로드마다 유일한 세션 id를 부여하고 모델에 저장해 놓는다. 그러면 어떤 다운로드가 알림이 왔는지 쉽게 찾아낼 수 있게 된다.

좋다. 이상적인 상황에서는 확실하다. 그러나 이상적이지 않은 상황은 어떨까? 만약 다운로드가 실패한다면? 셀룰러 접근을 해제하고 다운로드가 백그라운드에서 일어나는데 Wi-Fi 존을 벗어나면 어떻게 될까?

마지막 경우는 어느정도 방법을 알고 있다. 만약 보통 세션 구성으로 Wi-Fi 에서 다운받기 시작한 뒤, Wi-Fi를 끈다면 여러분은 셀룰러 다운로드가 허용되지 않았다고 즉시 에러를 받을 것이다. 그러나 백그란운드 세션을 사용하고 있다면 시스템은 똑똑하게 Wi-Fi존에 다시 들어올때까지 기다리다가 그때 요청을 다시 시도한다.

다른 에러로 나타날 수도 있다. 실제로 내 로컬 서버가 동작하지 않을때 커넥션 에러가 나는데, 다운로드는 계속 진행되는것 같지만 계속해서 0%를 가리켰다. 로컬 서버를 켜니 마치 아무 문제 없던것처럼 다운로드를 시작했다.

요청을 재시작할때까지 얼마나 기다려야하는지에대한 이야기가 문서에 명확하게 나와있지 않았다. 사실 사용자가 앱을 다시 실행하면 다운로드는 어떤 상태이야야할까? 우리는 어떻게 알 수 있을까? 개발을 하면서 가끔 주인없는 다운로드를 발견했다. 다운로드 정보의 상태는 .downloading인데 완료나 성공이나 다른 콜백을 받지 못했다. 그때 내가 한 것은 실패로 다시 표시하는 것이다. 그런데 사실 '언제' 그것을 해야할까? 다운로드는 시간이 좀 걸릴 수 있고 몇분 뒤에 다시 시작할 수도 있으므로 단지 x분 뒤에 실패로 표시하는것처럼 간단하지는 않ㄴ았다.

그런식으로 처리하긴 했지만 아마 옳은 방법은 아닌것 같다.



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

,



NSScreencast iOS 앱에서 비디오를 다운받아  오프라인에서도 사용할 수 있는 기능을 넣고 싶었다. 보통 비디오는 80에서 200MB 크기인데, 이것에는 실패시 다시 복구하는 다운로드 시스템을 만들기 위해 신경을 조금 쓸 필요가 있었다.



첫번재로 해야 할 일은 에피소드 화면에 진행상태가 보이는 다운로드 버튼을 넣는 것이었다. 여기서는 iTunes에서 음악을 다운 받는 것과 비슷하게 구현했다. 다운로드는 백그라운드에서 일어나며 진행 노티피케이션을 받아서 UI에 퍼센테이지로 나타낸다.




NSOperation 서브클래스를 통해 실제 다운로드가 끝난다. 이런 방식은 동시성을 제어하고, quality of service과 비행기 모드 다운로드에서 취소시키는 메커니즘을 쉽게 구현할 수 있게 해준다. 다운로드 진행은 에피소드 id를 가진 노티피케이션을 통해 보내지는데, 이 부분과 관련된 UI는 이것을 가져와서 갱신할 수 있다.


물론 다운로드 되는 동안 사용자가 화면을 응시하고 있을 필요는 없으므로, 앱을 둘러보든 기다리든 다운로드는 계속 진행될 것이다.

다은으로 어떤것이 다운중인지, 다운받은 비디오를 오프라인에서 보고, 저장공간을 확보하기 위해 다운로드 중인 것과 예전에 다운로드 받은 것들을 한 곳에서 볼 수 있게 하고 싶었다.

이 화면은 각 줄이 에피소드로서 테이블 뷰 안에 데이터를 보여준다. 현재 다운로드 받은 셀들은 진행 노티피케이션을 받아서 빨리 다시 불러와야 한다.

이 모든것들을 하기 위해 상태를 코어데이터 모델로 저장했다. 나의 DownloadInfo 모델은 아래와 같이 생겼다.


이것을 위해 코어데이터를 활용함으로서 그 라이프 사이클 내내 다운로드 상태를 추적할 수 있다. 이전에는 plist를 사용했었는데, 간단한 저장소로 plist보다 코어데이터가 더 간편했다.

모델에 다운로드 진행 상태 퍼센테이지를 저장하는 것을 볼 수 있지만, 이것을 반복적으로 코어데이터에 저장하지는 않았다. 빠른 커넥션에서 다운로드 진행 변화가 빠르게 일어날 것이고, 코어데이터를 매번 저장할 필요는 없었다. 요청이 취소되거나 다운로드가 더이상 일어나지 않음을 UI에 보여주고 싶을 때만 데이터를 저장했다.

코어데이터를 사용해서 얻은 또다른 장점에는 빠르게 DownloadsViewController를 구성하기위해 NSFetchedResultsController의 유용한 점을 사용할 수 있다는 것이다.

실패 처리하기
네트워크는 항상 뭔가 잘못될지도 모른다는 가능성을 가지고 있다. 이러한 가능성은 큰 파일 다운로드시 더 커진다. 사람들이 Wi-Fi 존을 벗어나거나, 터널로 들어간다던지, 비행기 모드를 켠다던지, 다른 다운로드들과 함께 한꺼번에 많이 다운받는다던지 할 수도 있다. 최고로 좋은 사용자 경험을 보장하기 위해서는 이것을 다루고 싶었고 사용자가 빨리 재시도를 할 수 있도록(때때로는 자동으로 재시도 하도록) 하고 싶었다.

실패가 일어나면 Download의 state 프로퍼티를 .failed로 바꾸고 UI를 적절하게 갱신한다. 그 셀을 다시 불러오고 사용자는 다운로드를 재시도 하기위해 탭할 수 있다.

일시정지와 재개
NSURLSession API가 시작되면, 요청을 취소하고 resume data라 불리는 애매한 오브젝트를 만들어내는 기능을 추가한다. 이것을 이용하면 끊긴 곳에서 요청을 시작할 수 있으며, 한가지 의문은 나중에 다시 시작할 수 있도록이 데이터를 유지하는 방법이다. NSScreencast 모델에 추가하는 것이 딱 알맞았다. 사용자가 진행중에 다운로드를 누르면 downloadTask.cancel(byProducing:)을 호출하고 나중에 사용하기위한 재개 데이터를 모델에 저장한다.

다운로드가 시작되고 모델 데이터가 재개 데이터라면 어느 시점에서 끊겼든지 그 곳에서 요청을 재개하는데 사용된다. 이 기능은 추가하기 쉬운데다, 큰 파일 다운로드시 굉장히 유용하다.


셀룰러 다루기
나는 다른 사람들의 데이터 요금을 불태우고 싶지 않았으므로 NSURLSessionConfigurationallowsCelluarAccessfalse로 설정해두었다. 그릭고 사용자가 원할때 셀룰러로 다운받을 수 있게 토글을 추가했다.




Fx Reachability를 사용하여 커넥션 상태를 모니터링했다. 사용자가 셀룰러일때 에피소드를 다운받으려 한다면 토글을 띄워 설정하고 다운로드 할 수 있게 하였다.

모델과 파일시스템의 동기화 유지하기(Keeping the Model and FileSystem in Sync)
파일에대한 메타데이터는 디스크에 저장하기 때문에 이것이 항상 동기화 되어있음을 보장해야한다. 에피소드를 하나 삭제하면 코어데이터 모델 뿐만 아니라 디스크에 파일도 삭제해주어야한다. 이 동기화를 보장해주기위해 나는 CleanupDownloadsOperation을 가지고 있는데, 이것은 앱이 켜질때 실행되며, 각 저장된 DownloadInfo가 디스크에 옳바른 파일을 가지고 있는지(혹은 삭제되었는지) 확인하고, 다운로드 폴더의 각 파일이 코어데이터에 저장되 있는지(혹은 삭제되었는지) 확인한다.

이렇게하여 뭔가 잘못되거나 두가지 상태(데이터페이스/디스크)가 싱크가 맞지 않을때의 대비책을 마련해 둔 것이다.

백그라운드 다운로드
겉으로 보기엔 간단해 보이지만 백그라운드 다운로드는 혼란과 복잡함의 근원으로 표현된다. 이 주제에 대해서는 나의 다음 포스팅에서 다루겠다.

그들이 말하는 그냥 오프라인 다운로드 추가하기
처음에 오프라인 다운로드를 앱에 넣어보기 전까지는 그냥 하루 이틀정도 더 걸리는 작업이라 생각했지만, 어마무시하게 복잡한 작업이 되버렸다.

역시 이런것이 소프트웨어라 생각된다.




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

,

두 프로젝트를 동시에 진행하면서 앱 아키텍처에 관한 중요한 경험을 얻을 수 있었다. 내가 공부했던 것이나 생각했던 것에서 적용해보고 싶은 개념을 두 프로젝트에 적용시켜 진행했다. 그중 하나는 최근에 내가 공부하기도 했고 가장 의미있다고 생각되는, 네트워크 레이어를 어떻게 만드는지에 관한 이야기이다.

요즘 모바일 앱은 클라이언트-서버 지향이고, 앱이 크든 작든 앱 어디에나 네트워크 레이어를 사용한다. 나는 수 많은 네트워크 레이어 구현을 보았지만, 다들 뭔가 단점들이 있었다. 내가 제일 마지막으로 만든것에서 단점이 아예 없을거라 생각하진 않지만 두 프로젝트에서 굉장히 잘 동작하는듯 보였고, 현재까지도 작업을 하고 있다. 또한 테스트 커버리지가 거의 100%에 이른다.

이 글에서는 그렇게 복잡한것 까지는 다루지 않을 것인데, 인코딩된 요청을 JSON으로 보내는 한 백엔드의 네트워크 레이어만 다룰 것이다. 이 레이어는 나중에 AWS와 함께 이야기해보고 거기에 몇몇 파일도 보내볼 것이지만 기능적으로 확장하기 쉬울 것임을 확신한다.

프로세스를 생각해보자
이 레이어를 만들기 전에 던졌던 질문들이다.
  • 백엔드 url에관한 정보는 코드 어디에 놔둘까?
  • url 끝부분은 어느 코드에서 알아야할까?
  • 요청을 어떻게 만드는지는 코드 어디에서 알아야 할까?
  • 요청을 보내기 위해 준비해야하는 파라미터는 코드 어디에 둘까?
  • 인증(authentication) 토큰은 어디에 저장해 둘까?
  • 어떻게 요청(request)을 실행할까?
  • 언제 그리고 어디서 요청을 실행할까?
  • 요청 취소에 대해서도 고려해야할까?
  • 잘못된 백엔드 응답이나 백엔드 버그도 고려해야할까?
  • 써드파티 라이브러리를 사용해야하나? 어떤것을 사용할까?
  • 코어데이터도 처리해야할 것이 있나?
  • 이 솔루션을 어떻게 테스트해볼까?

백엔드 url을 저장하기
먼저, 어디에 백엔드 url을 저장해 두어야할까? 어떻게 우리 시스템에서 어떤 다른 시스템에 요청을 날릴지 알까? 나는 BackendConfiguration 클래스를 만들어 그 정보(url)를 담아둔다.

테스트하기도 쉽고, 구성하기도 쉽다. shared라는 스태틱 변수에 할당해놓고 파라미터로 전달할 필요 없이 네트워크 레이어 어디에서나 접근할 수 있다.

끝부분(Endpoints)
이 토픽은 내가 준비-시작(ready-to-go) 솔루션을 찾기 전에 경험했던 것이다. NSURLSession을 구성하는 동안 끝부분을 하드코딩하려 했고, url의 끝부분을 알고 쉽게 객체로 만들 수 있거나 주입될 수 있는 더미의 Resource스러운 오브젝트를 시도했다. 그러나 내가 찾던 해답은 아니었다. 

결국 *Request라는 객체를 만들기로 했는데, 이 객체는 다음과 같은 것들을 가진다. 어떤 메소드를 사용하는지, GET, POST, PUT 혹은 다른 어떤것인지, 요청의 바디(body)가 어떻게 구성되는지, 헤더에는 무엇을 담아 보내는지 이 객체에 담겨있다.

프로토콜로 구현된 이 클래스는 요청을 만들때 필요한 기본 정보를 제공한다. NetworkService.Method는 GET, POST, PUT, DELETE를 표현하는 열거형이다.

url 뒷부분이 저장된 예제 요청은 아래와같이 생겼다.

헤더 어디에도 딕셔너리를 생성하지 않으려고 BackendAPIRequest를 위한 extension을 정의할 수 있다.

*Request 클래스는 요청에 성공하기위해 필요한 모든 파라미터를 받는다. 적어도 당신이 필요로한 모든 파라미터는 보내야하고, 그렇지 않으면 요청 객체는 생성할 수 없다.

url 끝부분을 선언하는 것은 간단하다. 만약 끝부분에 id를 포함해햐한다면 아주 쉽게 추가할 수 있다. 왜냐하면 프로퍼티로 저장해둔 id를 가지고 있기 때문이다.

요청의 메소드는 절때 바뀌지 않으며 파라미터 바디나 헤더가 쉽게 구성되고 쉽게 수장할 수 있다. 테스트하기에 모든것이 쉬워진다.

요청을 실행하기

백엔드와 소통하려면 써드파티 프레임워크가 필요한가?

사람들이 AFNetworking(Objective-C)나 Alamofire(Swift)를 사용하는 것을 보았다. 나도 이것을 오랫동안 써왔으나 어떨때는 사용하지 않았다. NSURLSession이라는 것이 있는데, 이것이 굉장히 잘 되있기 때문에 나는 여러분이 굳이 써드파티 프레임워크를 사용하지 않아도 된다고 생각한다. 내 생각에는 이런 의존성이 여러분의 앱 구조를 더 복잡하게 만든다.

현재 솔루션은 NetworkService와 BackendService의 두 클래스로 구성된다.
  • NetworkService — 내부적으로 NSURLSession과 구성되어 HTTP 요청을 실행할 수 있게 한다. 모든 네트워크 서비스는 한번에 하나만 요청할 수 있고, 요청을 취소할 수도 있으며(큰 장점이다), 성공하거나 실패한 응답을 위한 콜백을 가지고 있는다.
  • BackendService — (좋은 이름은 아니지만 꽤 잘 들어 맞는다)백엔드와 관련있는 요청(위에서 설명한 *Request 객체)을 받는 클래스이다. NetworkService를 사용한다. 내가 현재 사용하는 버전에서는 응답 데이터를 NSJSONSerializer를 이용하여 json으로 만든다.

위에서 볼 수 있듯 BackendService는 인증 토큰을 헤더에 세팅할 수 있다. BackendAuth 객체는 NSUserDefault에 토큰을 저장하는 간단한 저장소이다. 필요에따라 토큰을 키체인에 저장할 수 있다.

BackendService는 request(_:success:failure:) 메소드의 파라미터로 BackendAPIRequest를 받고 request 객체로부터 필요한 정보를 얻어낸다. 이렇게 하면 캡슐화면에서도 좋고 백엔드 서비스도 꺼네온것만 사용한다.

NetworkService, BackendService, BackendAuth 모두 테스트하거나 유지보수하기 쉽다.

요청을 큐하기
어떤 방식으로 네트워크 요청을 날려야할까? 한번에 많은 네트워크 요청을 날려야 한다면 어떻게 할까? 일반적으로 요청의 성공과 실패를 처리하려면 어떻게 할까? 이 파트에서 위 질문들을 해결할 수 있을 것이다.

네트워크 요청을 실행하는 NSOperation과 NSOperationQueue를 사용하자.

나는 NSOperation으로 서브클래스를 만들고 asynchronous 프로퍼티를 오버라이드하여 true를 반환하게 한다.

다음으로, 네트워크 콜을 실행시키기위해 BackendService를 사용하고 싶으므로 NetworkOperation을 상속받은 ServiceOperation을 하나 만든다.

이 클래스 내부에는 BackendService를 생성하므로 이제 모든 서브클래스마다 이것을 생성할 필요가 없다.

이제 로그인 동작이 어떻게 구현되는지 보자.

서비스는 이 오퍼레이션의 초기화때 만들어놓은 요청을 start 메소드에서 실행시킨다. handleSuccess와 handleFailure 메소드는 서비스의 request(_:success:failure:) 메소드에 콜백으로 전달된다. 내 생각엔 이렇게하면 코드가 더 깔끔해지고 가독성도 좋아진다.

오퍼레이션들은 싱글톤 오브젝트인 NetworkQueue로 전달되며 모든 오퍼레이션이 이 큐에 들어갈 수 있다. 이제 나는 가능한 간단하게 유지한다.

한 곳에 오퍼링션 실행을 모아두면 어떤 이점이 있을까?
  • 모든 네트워크 오퍼레이션을 간편하게 취소할 수 있다.
  • 이미지를 다운받거나 열악한 네트워크 환경에서 많은 데이터를 소모해야하지만 앱 동작에는 크게 상관없는 다른 오퍼레이션들을 취소할 수 있다.
  • 큐를 필요한 순서대로 실행할 수 있고 빨리 답변 올 수 있는 것부터 실행할 수 있다.

코어데이터와 함께 작업하기
사실 이것 때문에 이 글의 발행이 늦어졌다. 이전 버전에서는 네트워크 레이어 오퍼레이션이 코어데이터 객체를 반환했었다. 응답을 받고 파싱한 뒤, 또다시 코어데이터 객체로 변환시켰다. 이 방법은 그다지 이상적이지 않았다.
  • 오퍼레이션에서 어떤 코어데이터인지 알고 잇어야했다. 왜냐하면 프레임워크를 분리하기 위해 떼어놓은 모델을 가지고 있고 네티워크 레이어 역시 프레임워크로부터 분리되 있었다. 네트워크 프레임워크는 모델 프레임워크에대해 알고 있어야 했다.
  • 각 오퍼레이션은 어떤 컨텍스트에서 동작하는지 알아야 했으므로 NSManagedObjectContext 파라미터를 받아야 했다.
  • 응답을 받고 성공(success) 블럭을 호출할 때마다 먼저 컨텍스트에 객체를 찾으려 하거나 디스크에 객체를 패치하기위해 디스크 검색을 해야만 했다. 내 생각엔 이 점이 매우 큰 단점이었고, 아마 당신도 항상 코어데이터 객체를 생성하고 싶지는 않았을 것이다.

그래서 나는 네트워크 레이어를 코어데이터로부터 완전히 떼어냈고, 응답을 파싱하여 오브젝트를 만드는 중간 레이어를 만들었다.
  • 이렇게 파싱하고 오브젝트를 생성하면 디스크에 접근하지 않으므로 빠르다.
  • 오퍼레이션에 NSManagedObjectContext를 전달할 필요가 없다.
  • 성공 블럭에서 파싱된 아이템으로 코어데이터 객체를 갱신할 수도 있고, 오퍼레이션을 생성하는 곳 어디에든 있을 수 있는 코어데이터 객체를 참조할 수 있다. — 오퍼레이션이 큐에 추가될 때의 내 경우이다.

응답을 맵핑하기
유용한 아이템을 위해 JSON을 파싱하는 로직과 매핑하는 로직을 분리해주는 응답 맵퍼(response mapper)가 있다.

우리는 두가지 타입으로 파서를 구별할 수 있다. 첫번재는 특정 타입의 한 객체를 반환한다. 두번째는 어떤 항목의 배열을 파싱하는 파서이다.

먼저 모든 항복에 해당하는 일반적인 프로토콜을 선언하자.

이제 모델과 맵핑하는 몇 객체이다.

그리고 파싱하다가 에러가 발생시 던지는(throw) 에러타입을 선언하자.
  • Invalid — 파싱된 json이 nil이고, nil을 반환하면 안될때나, json이 하나의 객체가 아니라 객체의 배열일때 던지는 에러타입이다.
  • MissingAttribute — 이름이 의미하는 바와 같다. json에서 키를 잃어버리거나 파싱 후에 값이 nil이면 안되는데 nil일때 던지는 에러타입이다.

ResponseMapper는 이렇게 생겼을 것이다.

ResponseMapper는 백엔드로부터 받은 obj(우리의 경우 JSON이다)와 parse라는 메소드를 받는데, parse는 obj을 이용해 ParsedItem을 따르는 A 객체를 반환한다.

이제 우리가 세부적으로 구현한 제네릭 매퍼를 만든다. 아래 매퍼는 로그인 오퍼레이션에대한 응답을 파싱하는데 사용되는 매퍼이다.

ResponseMapperProtocol은 세부적인 매퍼에의해 구현된 프로토콜로서 메소드를 공유하여 응답을 파싱한다.

그러면 이 매퍼를 오퍼레이션의 성공 블락에서 사용하고, 이것을 딕셔너리 대신에 특정 타입의 객체로 사용할 수 있다. 이전보다 훨씬 사용하기 쉽고 테스트하기도 쉽다.

마지막으로 배열을 파싱하기위한 응답 매퍼이다.

모든것이 정상적으로 파싱되면 매핑할 함수를 받아 항목의 배열을 반환한다. 매퍼의 결과가 당신이 예상한 것에 따라 한 아이템이 파싱되지 않을 경우 에러를 던질 수도 있고, 문제가 있을 때는 빈 배열을 반환할 수도 있다. 매퍼는 백엔드로부터 받은 응답인 obj가 JSON 요소의 배열이라 예상한다.

이 도표는 네트워크 레이어의 구조를 보여준다.




예제 프로젝트
여러분은 내 깃헙에서 예제 프로젝트를 확인해 볼 수 있다. 프로젝트에서 사용된 백엔드 url은 모두 가짜이며 모든 요청은 실패할 것이다. 나는 여러분에게 네트워크 레이어의 파운데이션이 어떻게 생겼는지 보여주기만을 위해 이것을 만들었다.

요약
나는 이러한 네트워크 레이어 방식이 굉장히 유용하고 간편하며 작업에 편리하다는 것을 깨닭았다.

  • 가장 큰 이점은 비슷한 설계의 다른 새 오퍼레이션을 쉽게 추가할 수 있고, 이 레이어가 코어데이터에대해 전혀 모른다는 점이다.
  • 큰 노력없이 코드 커버리지를 거의 100%에 근접하도록 만들 수 있다. 완전 어려운 케이스를 어떻게 커버할지 생각할 필요도 없다. 어디에도 케이스가 없기 때문이다(원문: because there is no such cases at all).
  • 이 네트워크 레이어의 핵심은 비슷한 복잡성을 가진 다른 앱에서도 다시 사용할 수 있다는 점이다.



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

,

올해 초에 우리는 속도, 명확한 경험, 그리고 미국 바깥의 우리 앱을 사용하는 피너(pinner)들을 위해 우리 앱의 새 구조를 짜게 되었다. 앱의 새로운 구조 조정 목표중 하나는 우리 앱을 완전히 불변 모델(Immutable model)로 바꾸는 것이었다. 이 포스팅에서는 이런 것에 대한 동기에대해 이야기하고, 우리의 새 시스템이 어떻게 모델 갱신을 다루는지 탐구해보며, API로부터 새로운 정보를 어떻게 불러오는지, 데이터 일관성에대해 이야기해 볼 것이다.

왜 불변 모델인가?
"불변  모델"이라는 말은 많은 앱들이 불변하게 바뀌면서부터 많이 들어본 용어이다. 불변성은 한번 초기화 되고 나면 더이상 수정되지 않음을 의미한다. 왜 우리는 이것을 선택하게 되었을까? 음, 가변성에서의 주된 문제는 공유된 상태에 있다.

이런식으로 생각해보자: 가변 모델 시스템에서 A와 B 둘 다 C를 참조하고 있는다.



만일 A가 C를 수정하면 A와 B 둘 다 바뀐 값을 보게 될 것이다. 여기까지는 괜찮지만 만일 B가 이러한 것을 예상하지 못하고 있다면 의도치 않은 일이 일어날 수 있다.

예를들어 내가 두명의 사용자와 함께  메시지 스레드에 있다. 나는 '사용자' 프로퍼티와 함께 메시지 오브젝트를 가지고 있다.

내가 이 화면에 있을 동안 앱의 다른 부분에서 Devin 대화를 제거하기로 했다(아마 바뀐 서버 응답을 받고 모델을 변경할 것이다). 이제 두번째 사람을 탭할때, 나는 두번째 오브젝트를 찾기 위해 message.users 배열을 확인해 볼것이다. 그러면 Devin 대신 Stephanie를 반환하고 잘못된 사람을 보게 된다.

불변 모델은 태생적으로 스레드 세이프하다. 이전에는 다른 곳에서 그것을 읽고 있는 동안 한 스레드가 그 모델에 쓰기라도 하는 것을 항상 신경써야했다. 우리 새로운 시스템에서는 오브젝트가 한번 초기화되고나면 더이상 수정될 수 없으며, 따라서 언세이프한 값을 읽는 것을 걱정없이 다중 스레드로 동시에 읽을 수 있게 되었다. 이것이 우리 iOS 앱을 더 동시적이고 다중스레드에 강하게 해줌으로서 쉽게 작업할 수 있게 해주었다.

모델 갱신하기
생성된 후에는 우리 모델이 완전히 불변하므로, 모델을 갱신/변경하는 유일한 방법은 새로운 오브젝트를 생성하는 방법 뿐이다. 이것을 위해 두가지 방법이 있다.
  • (보통 JSON 응답으로부터) 딕셔너리를 사용하여 모델을 초기화한다.
  • "builder" 오브젝트를 사용하는데, 이것은 그냥 모든 프로퍼티를 받아다가 필요한것만 변경하는 방식이다. 기존에 만들어놓은 모델로부터 빌더를 만든 뒤, 우리가 필요한 프로퍼티만 변경하고, initwithBuilder를 호출하여 새로운 모델을 받는다. (이것에 대해서는 나중에 새 포스팅에서 설명하겠다)

 API 데이터를 불러오고 캐싱하기
우리 API는 서버로부터 모델 필드의 부분집합과 함께 부분적인 JSON 모델을 요청할 수 있게 해두었다. 예를들어 핀 피드 화면에서 이미지 URL과 설명과 같은 필드가  필요하지만, 모든 정보가 필요하지는 않았다. 요리법 요소에서 사용자가 그 핀 화면에 들어가기 전까지 말이다. 이러한 방식은 우리가 보내는 엄청난 양의 데이터를 자르는데 도움을 주고, 게다가 백엔드에서 처리 시간도 단축된다.

우리는 PINCache로 만든 주요한 모델 캐시를 가지고 있는데, 이 오브젝트 캐시는 iOS를 위해 오픈소스로 만들었다. 이 캐시의 핵심은 유일함인데, 모델의 서버 쪽 ID이다. 우리가 새로운 서버 응답을 받을 때 현재 모델의 캐시를 확인한다. 만일 현재 모델이 발견되면 현재 모델의 프로퍼티들과 서버 응답의 필드들을 합쳐서 새로운 모델을 만든다. 이 새로운 모델은 캐시에 저장되있던 것과 대체된다. 이렇게 하면 캐싱된 모델은 항상 우리가 받은 가장 최신의 필드를 유지할 수 있다.



데이터 일관성
모델이 수정된 후(새로운 모델이 생성되고나면), 그 수정은 모델을 보여주는 그 화면에 반영된다. 이전에는 Key-Value Observing를 사용하였지만 KVO는 오직 한 모델 인스턴스의 변화만을 관찰하기 때문에 불변 오브젝트에는 적용되지 않았다. 이제 우리는 NSNotificationCenter-기반 시스템으로 그 변화를 알리고 있다.

변화를 관찰하기
뷰나 뷰컨트롤러는 모델에 갱신 알림을 등록해 놓을 수 있다. 이 예제에서는 MessageViewController가 그 메시지 모델의 갱신을 등록한다. 새로운 모델에 프로퍼티 갱신이 일어날 수 있기 때문에 새 메시지 모델이 생성되면 알림을 받고 싶다.


아래는 메시지 모델의 이름+고유id로 갱신된 모델을 듣고 있는(listen) 옵저버를 만드는 코드이다. 메소드는 block-기반 NotificationCenter API를 사용함으로서 옵저버 라이프타임 관리를 더 잘 할 수 있었다.
notificationManager는 등록된 옵저버들을 강타입으로 붙잡아 둔 NSObject이다. 뷰컨트롤러 프로퍼티여서 뷰컨트롤러의 소멸자(dealloc)가 호출되고 나서 그것을 소멸시키는데 모든 옵저버의 등록을 해지시킨다.

변화들을 보내기
메시지 모델이 갱신될 때, 갱신 알림이 보내진다.
postModelUpdatedNotificationWithObject:는 같은 클래스+서버id의 가장 최신 모델을 위해 모델 캐시를 확인하고, 캐시되어있는 모델 인스턴스를 보낸다.

UI 갱신 만들기
알림이 발생하면 NSNotification의 "object" 필드로 새로 모델을 보낸다. 그러면 뷰컨트롤러는 갱신된 모델을 사용해 그것을 갱신시킨다.

마지막으로
규모가 있는 앱의 전반적인 모델을 바꾸는 것이 쉬운 작업은 아니었다. 그리고 우리는 이러한 방법을 도와줄 좋은 도구를 만들어냈다. 다음 포스팅에서 우리가 어떤 방식으로 모델 클래스와 그 이상을 자동 생성했는지 설명할 것이다.

감사: 이 새로운 모델을 사용해주고 피드백을 준 모든 iOS 개발자에게 감사의 말을 전하면서, 특히 내 팀의 Rahul Malik, Chris Danford, Garrett Moon, Ricky Cancro, Scott Goodson, 그리고 Bella You, Rocir Santiago, Andrew Chun에게 고맙다. 




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

,

델리게이트도 괜찮지만, 더 나은 방법이 있다.

당신은 iOS앱을 만들고 있는 개발자라 가정하자. 당신은 당신이 할 수 있는 최선의 견고한(SOLID) 아키텍처로 앱의 구조를 만들어 놓았다. 앱은 모델, 네트워크 레이어, UI 레이어 또는 그것을 돕는 것들로서 구성된다. 이 레이어들 사이에 데이터를 주고 받을때 책에서는 델리게이션을 이용해라고 알려준다. 실제로 iOS 개발에서 일반적으로 사용되는 유용한 패턴이기도 하다.

델리게이션은 간단히 말하자면, 어떤것의 변화로부터 알림받기 원할때 그 어떤것에 알림받기 원하는 대상을 등록해놓는 방식의 패턴이다. 이렇게하면 그 어떤것으로부터 반응(react)할 수 있다. 예를들면 ViewController가 네트워크 서비스에게 말을 걸어서 (ViewController를 네트워크 서비스 델리게이트로 등록해서) 어떤 요청이 완료될때 자신에게 알려달라고 한다. 이때 ViewController를 네트워크 서비스의 델리게이트로 만들어 가능하게 된다. 네트워크 서비스는 요청이 완료되었을 때 델리게이션 메소드를 호출 할 것이다.
델리게이트 참조를 보내는 것은 괜찮은 방법이고 기능적으로도 아무 문제가 없다. 그러나 Swift에서는 더 나은 방법이 있고, 왜 이 방법이 더 나은지 설명해 보겠다.


델리게이션을 위해 콜백을 사용
콜백은 델리게이트 패턴과 비슷한 기능을 가진다. 어떤 일이 발생할때 다른 오브젝트가 알게 해주고, 데이터를 전달하는 기능이다.

델리게이트 패턴과 다른점은, 응답받고 싶은 객체 자체를 넘겨주는 것 대신에 함수만을 넘겨준다. 함수는 Swift에서 클래스의 첫번째 요소이다. 따라서 함수를 프로퍼티로 가지고 있을수도 있다.
MyClass는 이제 myFunction 이라는 프로퍼티를 가지는데, 어딘가에서 호출할 수도 있고, 누구나 값을 바꿀 수도 있다(Swift에서 정의된 규칙에의해 프로퍼티는 디폴트로 internal이 된다). 이것이 델리게이션 대신 콜백을 사용하는 기본 아이디어이다. 아래 예제는 위 예제에서 델리게이트 대신 콜백으로 대체한 것이다:
콜백을 사용하는 다른 상황은 데이터가 바뀔때 알림을 받고 싶을 경우이다. 프로퍼티 옵저버에서 콜백을 호출함으로서 가능하다:
콜백에대한 간단한 노트 : 델리게이트에서는 리테인(retain) 사이클을 멈추기위해 weak 프로퍼티로 만들어야하는 것처럼, 여러분도 클로저 안에서는 self를 weak 변수로 해두어야한다.

그래서 왜 콜백이 더 나은가?
1. 분리됨(Decoupling)
델리게이트는 코드를 분리하는 경향이 있다. 프로토콜로 구현하는 한 NetworkService에게 누가 그것의 델리게이트인지 알 필요가 없다. 그러나 델리게이트가 프로토콜 구현을 가지고 있고, @objc 프로토콜 대신 Swift를 사용한다면, 델리게이트는 프로톸콜에서 모든 메소드 구현을 가진다.(따라서 옵셔널 프로토콜 일치도 필요없다)

다르게 말해보면 콜백을 사용할 때, NetworkService는 메소드를 호출하기위한 델리게이트를 가지고 있을 필요가 없고 누군가가 이 메소드를 구현할 것이라는 것을 알고 있으면 된다. 메소드가 호출되는 시점만 관리하면 되고, 이 메소드가 어떻게 구현되있을지는 알 필요가 없는 것이다.

2. 다중 델리게이션
요청이 끝나고 ViewController에 알림을 주고 싶은데, 그때 로그를 남기는 클래스나 통계를 남기는 클래스를 넣고 싶을 수 있다.

델리게이트로 구현하면, 델리게이트의 배열을 가지고 있어야 할 것이다. 아니면 세개의 서로 다른 프로토콜을 가지는 델리게이트 프로퍼티를 가질 것이다!

그러나 콜백으로 구현한다면 함수의 배열을 선언하고(Swift의 이런 점이 좋다!) 뭔가 처리가 끝날때 각각 호출하면 된다. 따라서 리테인 사이클의 위험을 감수하거나 어마어마한 양의 코드로 작성되거나 하는 수많은 오브젝트와 프로토콜이 필요없어진다.

3. 일을 분리하기에 더 명확하다.
내가 생각하는 델리게이트와 콜백의 차이는 이렇다. 델리게이트는 NetworkService가 델리케이트에게 "이봐, 나 갱신됐어!"라고 말하는 반면 콜백은 델리게이트가 NetworkService를 응시하고 바라보고 있는 느낌이다.

실제로 작은 차이 같지만, 후자의 방법으로 생각하면 NetworkService가 자신의 기능을 하지 못하고 화면 표시의 기능으로 변질되는 그런 것을 방지해주는 패턴으로서는 크게 도움이 될 것이다!

4. 테스트하기 쉬움!
유닛테스트와 함께 구현하면 항상 코드베이스가 두배로 늘어난다고 느낄것이다. 그 이유는 앱의 모든 델리게이트를 포함하여 매 프로토콜마다 목(mock)을 만들어야하기 때문이다.

콜백으로 구현하면 어떠한 델리게이트에 목을 할 필요 없을 뿐만 아니라 각 테스트에서 원하는 어떠한 콜백이든 사용할 수 있게 해준다.

한 테스트에서 콜백이 제대로 호출되는지 호출는지 테스트해보고, 다른 테스트에서 그것이 호출될 때 옳바른 결과를 내는지 테스트 할 수 있다. 그리고 어떠한것도 someFuncDidGetCalled 불리언(boolean)이나 비슷한 프로퍼티를 가진 복잡하게 목(mocked)된 델리게이트를 필요로 하지 않는다.

나는 개인적으로 콜백이 코드와 테스트를 명확하게 만들어주고, 더 Swift스럽게 데이터를 주고 받는 방법이라 생각한다. 여러분이 오늘 뭔가 새로운 것을 배웠기를 바란다! 



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

,

6개월전 우리는 PlanGrid iOS앱에 Flux 아키텍처를 적용시키기 시작했다. 이 포스팅에서는 우리가 왜 전통적인 MVC에서 Flux로 갈아타게 되었는지 이야기해보고, 지금까지 겪은 경험을 공유하고자한다.

실제 제품에 코드와 함께 이야기함으로서 나는 Flux 구현의 큼직한 부분들 위주로 설명해볼 것이다. 만약 당신이 단지 고수준의 결론만 알고 싶다면, 포스팅 중간 부분은 스킵해버려도 좋다.

왜 우리가 MVC로부터 갈아타게 되었을까?
어떤 맥락속에서 우리가 Flux를 결정하게 되었는지 설명하기 위해, PlanGrid 앱이 해결해야할 과제들을 먼저 이야기 해보고 싶다. 그 중 몇몇은 엔터프라이즈 소프트웨어에 의존적이고, 나머지 대부분 iOS 앱에 적용시킬 수 있어야했다.

우리는 모든 상태를 가지고 있어야 한다.
PlanGrid는 꽤 복잡한 iOS 앱이다. 사용자에게 청사진을 보여주고 사용자들이 서로 다른 양식의 주석이나 이슈, 첨부(그리고 특정 산업 지식을 필요로하는 수많은 요소)들을 이용하여 협업할 수 있어야 했다.

또한 이 앱의 중요한 기능은 오프라인이 우선이라는 점이다. 유저들은 인터넷 연결 여부와 상관없이 앱의 모든 기능을 사용할 수 있어야했다. 이 말은 즉, 우리는 그 수많은 데이터와 상태들을 클라이언트에서 관리하고 있어야 했다는 뜻이다. 또한 부분적으로 비즈니스 정책으로서 특정 기능을 따로 실행할 수 있어야 했다(e.g. 특정 유저는 주석을 지울 수 있다던지?).

PlanGrid 앱은 iPad, iPhone 기기 둘 다에서 동작하지만, UI는 테블릿의 큰 화면에 최적화 되어있다. 이 말은 많은 iPhone 앱들과는 다르게 종종 Multiple View Controller를 한 화면에서 보여줘야 했으며, View Controller끼리 상태를 공유해야 했다.

상태 관리의 상태
우리 앱은 상태 관리라는 곳에 상당한 노력을 쏟아붇고 있다. 앱에서의 갱신은 보통 아래의 순서를 따른다.
  1. 로컬 객체에서 상태를 갱신
  2. UI를 갱신
  3. 데이터베이스를 갱신
  4. 네트워크 연결이 가능해지면 서버로 보낼 그 변화를 큐에 넣기
  5. 다른 객체에 상태 변화를 알리기

나중에 또 한번 위의 과정을 담은 우리의 새 아키텍처에대해 포스팅을 할 예정이므로 오늘은 5번째 단계에 대해서만 이야기해보자. 우리는 어떻게 상태를 갱신받아 처리할 수 있을까?

이 질문은 앱 개발시 항상 나오는 질문이다.

PlanGrid를 포함한 대부분의 iOS 엔지니어들은 다음 대답들을 내놓는다:
  • Delegation
  • KVO
  • NSNotificationCenter
  • Callback Blocks
  • 소스의 신뢰로서 DB를 이용하기
위 접근법들은 수많은 시나리오에 걸쳐 검증되었을 것이다. 그러나 수년에 걸쳐 바뀔 수 있는 커다란 코드베이스에서 수많은 옵션들이 있다면 이것은 매우 부적합하다고 할 수 있을 것이다.

자유는 위험하다.
원리의 MVC는 데이터와 데이터 표현을 분리하는 것만을 추구했다. 다른 구조적인 가이드가 부족했으므로, 나머지 모든 것들이 개발자 개인에게 떠넘겨졌다.

오랜 시간동안 (다른 iOS 앱들 처럼) PlanGrid 앱도 상태 관리를 위한 패턴을 정하지 못해왔었다.

델리게이션이나 블럭과 같은 현존하는 수많은 현존하는 상태 관리 도구는 컴포넌트 사이에 강한 의존성을 만드는 경향이 있다 ― 두 View Controller가 서로 상태 갱신을 공유하고자하면 바로 단단히 엮여버린다.

KVO나 Notofication과 같은 다른 도구들은 눈에 보이지 않는 의존성을 만들어낸다. 거대한 코드베이스의경우 그것들을 사용하면 더더욱 예상치 못한 사이드 이팩트가 발생할 수 있고, 많은 코드 수정을 해야할지도 모른다.

이러한 수많은 구조적인 이슈는 작은 모순점에서 시작되어 시간이 점차 흐르면 심각한 문제를 초례한다. 반면 철저한 코드리뷰와 스타일 가이드 만이 이 문제를 잘 해결할 수 있다. 잘 정의된 패턴이 적용된다면 미연에 그 문제를 인지하기 훨씬 쉽다.

상태 관리를 위한 구조적인 패턴
PlanGrid 앱을 리팩토링하면서 우리의 가장 중대한 목표는 깨끗한 패턴들과 최고의 습관을 만들어 놓는 것이었다. 이렇게하면 미래에 훨씬 모순 없는 방식으로 코드를 짤 수 있고, 새로운 엔지니어가 투입 되었을 때도 매우 효율적이다.

이 앱에서 상태 관리는 가장 큰 복잡함을 제공하는 원인 중 하나였고, 우리는 앞을 계속 사용할 수 있게 완전히 새로운 패턴을 정의하기로 마음먹었다.

페이스북에서 처음 Flux 패턴을 소개했을때, 그들이 말한 문제점과 우리가 현재 코드베이스에서 느낀 수많은 고통들이 강하게 매칭되었다:
  • 예측불가능하고, 순차적으로(cascading)처럼 상태가 갱신됨
  • 컴포넌트 사이에 의존성을 이해하기 쉽지 않음
  • 정보의 흐름이 엉켜있음
  • 소스의 신뢰가 불분명함
Flux는 우리가 경험하고 있던 많은 이슈를 해결하기에 적합해 보였다.

Flux로 들어가기전에 가벼운 설명
Flux는 페이스북의 웹 어플리케이션 클라이언트단에서 사용하는 경량의 아키텍처 패턴이다. 비록 참조하여 구현하였지만, 페이스북은 Flux패턴의 아이디어가 특수한 이 구현보다 더 많이 연관되있다고 강조했다.

서로 다른 Flux 컴포넌트를 보여주는 다이어그램과 함께 묘사할 수 있다:


Flux 아키텍처에서의 store는 앱의 특정 부분을 위한 정확한 단일 소스이다. store에서 상태가 업데이트되는 즉시 store를 구독하는 모든 view에 change event를 보낸다. 그 viewstore에의해서만 호출되는 유일한 인터페이스를 통해 갱신되었다는 소식을 받는다.

상태 업데이트는 action을 통해 일어날 수 있다.

action은 상태 변화를 하게 해주는 트리거지만 스스로 상태변화를 구현해놓지는 않는다. 상태 변화를 원하는 모든 컴포넌트들이 글로벌 dispatcheraction을 던진다. 이 storedispatcher와 함께 등록하고 그것들이  어디 action에 필요한지 알아내준다. action이 dispatch되면 바로 관련된 store들이  이것을 받는다.

action에 응답하는 동안 몇몇 store들은 그들의 상태를 갱신하고 새로운 상태를 view에게 알릴 것이다.

Flux 아키텍처는 위 다이어그램에서 보듯 단방향의 데이터 흐름을 행한다. 또한 엄격한 분리가 가능하다:
  • view는 오직 store로부터 데이터를 받는다. store가 갱신되면 view에 있는 메소드를 이용해 불러낸다.
  • view는 오직 action을 dispatch 함으로서 상태를 바꿀 수 있다. action은 단지 의도(intent)를 표현하는 역할이고 비즈니스 로직은 view로부터 숨겨져있기 때문이다.
  • store는 action을 받았을 때만 그 상태를 갱신한다.
이러한 제약들 덕에 새 기능을 설계하고 개발하며 디버깅하기 쉽게 만들어준다.

iOS를 위한 PlanGrid에서의 Flux
PlanGrid iOS 앱에서 우린 Flux를 약간 벗어나 구현했다. 우리는 각 store가 Observable 상태 프로퍼티를 가지고 있다. 기존 Flux 구현과는 다르게, store가 갱신될 때 change event를 보내지 않았다. 대신에 view가 store의 상태 프로퍼티를 Observe하고 있다. view가 상태 변화를 Observe하면 그들 스스로 변화를 감지하며 갱신까지 한다.


이것은 Flux 참조 구현에서 굉장히 미묘한 변경이지만 다음 섹션에서 위해 유용하게 쓰일 것 이다.

Flux 아키텍처 기반을 이해하면서, 이제 구체적인 구현이나 PlanGrid 앱에 Flux를 적용시키는 동안 필요했던 질문의 답변들을 한번 살펴보자.

store의 번주는 어디까지인가?
각 개별 store의 범주(scope)는 Flux 패턴을 처음 사용할 때 가장 먼저 떠오르는 질문이다.

페이스북이 Flux 패턴을 발표하고부터, 커뮤니티에의해 다른 변화들이 개발되어왔다. Redux는 그 중 하나인데, 각 어플리케이션당 오직 하나의 store만 가지도록 함으로서 Flux 패턴에서 번갈아가며 한 store를 사용한다. 이 store는 앱의 모든 상태를 가지고 있는다(수많은 다른, 사소한, 이 포스트 영역을 벗어난 그런것들).

Redux는 단일 store 아이디어로 수많은 앱의 아키텍처를 단순하게 해줌으로서 많은 인기를 얻고 있다. 그러나 다중 store를 사용하는 기존의 Flux에서는 조금 다른데, 특정 view를 그려야(reder)하기 때문에 다른 store에서 저장되 있는 상태를 합칠 필요가 있고, 이렇게 해야 앱이 돌아갈 수 있다. 이런 접근법은 곧바로 Flux패턴이 풀어야할 문제로 다시 떠오를 수 있다(다른 컴포넌트들 사이에 복잡한 의존성 같은).

PlanGrid 앱에서는 여전히 Redux 대신 기존의 Flux를 사용하기로 결정했다. 우리는 우리 앱이 얼마나 큰 앱이 될지 예측하지 못했기에, 앱의 모든 상태를 담은 단일 store보다는 다중 store를 선택하였다. 게다가 우리는 가장 작은 inter-store 의존성을 가지는 것을 인지했는데, 이것이 Redux를 대안에서 제외시키게 된 이유가 되었다.

우리는 아직 각 개별 store의 범주를 견고하게 만들어가고 있다.

지금까지도 나는 우리 코드베이스에서 두가지 패턴을 알아냈다:
  • 기능/view 특정 store : 각 View Controller(혹은 View Controller와 가깝게 연관된 각 그룹들)는 그것의 store를 받는다. 이 store는 view에 특화된 상태를 만든다.
  • 상태를 공유하는 store : 우리는 수많은 view들 사이에서 상태가 공유되는데, 이 상태들을 저장하고 관리하는 store를 가진다. 우리는 이 어마어마한 양의 store들을 최소화시키기위해 노력중이다. IssueStore가 그 예시인데, 이것은 현재 선택된 청사진을 볼 수 있는지 없는지에 관한 모든 이슈 상태를 관리한다. 이 이슈들을 화면에 보여주거나 소통하는 수많은 view들은 이 store로부터 정보가 나온다. 이 store의 타입은 필수로 실시간 갱신되는 데이터베이스 쿼리처럼 동작한다.
우리는 현재 상태 store에 공유된 처음 것을 구현하는 과정이고 아직 이 store 타입에서 서로 다른 view의 다중 의존성을 만드는 최고의 방법을 모색중이다.

Flux 패턴을 사용하여 기능을 구현하기
이제 Flux 패턴으로 만드는 세부적인 구현 기능들 안으로 파고 들어가보자.

다음 두 섹션에 걸쳐 예제를 보여주는데, PlanGrid 앱 제품에서의 기능들을 예시로 들 것이다. 그 기능은 사용자가 한 청사진에서 주석들을 필터링할 수 있게 해주는 것이다.


우리가 토론할 이 기능은 스크린샷의 왼편에 나타나있는 popover안에 만들어져있다.

1단계 : 상태를 정의하기
보통 나는 그것의 적절한 상태를 정함으로서 새 기능의 구현을 시작한다. 그 상태는 특정 기능의 표현응ㄹ 그리기위해 UI가 알아야하는 모든것을 나타낸다.

아래 보이는 것처럼 어서 주석 필터 기능을 위한 상태를 둘러보면서 우리 예제 속으로 들어가보자:

이 상태는 여러 필터의 리스트, 현재 선택된 필터 그룹, 어떤 필터가 활성화됬는지 지시하는 boolean 플래그로 구성된다.

이 상태는 정확히 UI에서 요구한 것이다. 필터 리스트는 Table View에 나타난다. 선택된 필터 그룹은 각 개별로 선택된 필터 그룹의 세부사항을 표시/숨김 하기위해 사용된다. 그리고 isFiltering 플래그는 UI에 버튼을 보이게할지 말지 정하는데 필터가 enabled인지 disabled인지에 따라 정해진다.

2단계 : Action을 정의하기
특정 기능을 위한 상태를 정의하고나면, 나는 보통 다음 단계에서 다른 상태 변화를 생각해본다. Flux 아키텍처에서 상태 변화는 action의 모양에 의해 만들어지는데, action은 상태 변화가 의도하는 것을 담고있다. 주석 필터 기능을 위한 action 코드들은 꽤 짧다:

그 기능의 깊은 이해 없이도 이 action이 초기화하는 상태 이동이 어떤 것인지 이해할 수 있을 것이다. Flux 아키텍처의 장점중 하나는 action 리스트는 각 기능들에의해 트리거될 수 있는 모든 상태변화를 한번에 담아낸다는 것이다.


3단계 : store에서 action으로 그 응답을 구현하기
이 단계는 기능의 핵심적인 비즈니스 ㄹ직을 구현하는 단계이다. 나는 개인적으로 이 단계를 TDD를 이용하여 구현하려하고, 나중에 TDD에대해 다시 이야기할 것이다. store의 구현은 아래처럼 요약될 수 있다:
  1. 연관된 모든 action을 dispatcher와 함께 store를 등록한다. 이 예제에선 모든 AnnotationFilteringActions이 될 것이다.
  2. 각 action들별로 호출할 수 있는 핸들러를 만든다.
  3. 핸들러와 함께 필요한 비즈니스 로직을 동작하고 완성에 상태를 갱신한다.

구체적인 예제로서 AnnotationFilterStoretoggleFilterAction을 어떻게 다루는지 확인할 수 있다:
self.annotationFilterService.applyFilter()를 호출 함으로서 시트위에 표시되는 주석들의 필터링을 실제 동작시킨다. 필터링 로직 그 자체는 다소 복잡하나, 일부를 떼어내서 옮겨놓았다.

각 store의 역할은 UI와 관련된 상태 정보를 제공하고 현재 상태를 동일하게 만들어 놓는 것이다. 그러나 이 작업을 위해 모든 비즈니스 로직을 store 안에 다 구현해라는 것은 아니다.

각 action 핸들러의 마지막 작업은 상태를 갱신하는 것이다. _applyFilter() 메소드와 함께, 어떤 필터가 활성화되어있는지 체크하여, 우리는 isFiltering 상태값을 갱신한다.

여기서 특정 store에 대해 인지해야할 중요한 사실이 하나 있다: 추가적인 상태 업데이트를 예상할 수 있다는 점인데, 이 업데이트는 AnnotationFilter에 저장되있는 필터들의 값을 갱신한다. 일반적으로 이것은 store를 어떻게 구현할 것이야는 것지만, 이번 구현은 약간 특별하다.

AnnotationFilterState에 저장된 필터들은 이전에 존재했던 Objective-C 코드와 연결되야 하므로 그들을 새 클래스로 만들기로 했다. 이 클래스는 타입과 store를 참조하고, 주석 필터링 UI는 같은 인스턴스 참조를 공유한다. 즉 store 안에서 필터에 일어나는 모든 변화는 UI의 시각적인 부분과 관계돼있다. 상태 구조체에서 값 타입을 독립적으로 사용함으로서 원래는 이러한 상황을 피하려고 해야한다. ― 그러나 이 포스팅은 실제 세계에서의 Flux 이야기이고 이 특수한 상황에서 좀 더 쉽게 Objective-C를 연결하기 위해 어느정도 타협점을 찾을 수 밖에 없었다.

만약 필터가 값 타입이면, 변화를 관찰한 UI 순서에 따라 우리 상태 프로퍼티에 갱신된 필터 값을 할당할 필요가 있다. 우리는 참조 타입을 사용하기 때문에, 대신 실체가 없는(phantom) 상태 갱신을 실행한다:

_state 프로퍼티에 할당하는 것은 UI를  갱신하는 매커니즘을 필요 없게 만든다. ― 잠시 후에 이 프로세스에 관한 세부적인 이야기를 해볼 것이다.

우리는 세부적인 구현에서 꽤 깊게 쪼개었고, 그래서 나는 이 섹션을 마치면서 store의 역할을 고수준에서 다시 한번 상기시켜보고자 한다:
  1. 필요로 하는 모든 action을 위해 dispatcher와 함께 store를 등록한다. 현재 예제에서는 모두 annotationFilteringActions이 되어야한다.
  2. 각 개별 action들을 위해 불릴 수 있는 핸들러를 구현한다.
  3. 핸들러 안에서 해당 비즈니스 로직을 실행하고 그 결과의 상태를 갱신한다.
다음으로 어떻게 UI가 store로부터 상태 갱신을 받는지 이야기 해보자.

4단계 : store로 UI를 바인딩하기
Flux 개념의 핵심 중 하나는, 상태 갱신이 나타나면 자동으로 UI를 갱신한다는 점이다. 이로인해 UI가 항상 최신 상태를 보여줄 수 있고, 수동으로 이 갱신을 유지하기 위해 필요한 어떤 코드도 만들 수 있어야한다. 이 단계에서는 MVVM 아키텍처에서 View가 ViewModel에 바인딩하는 것과 굉장히 유사하다.

이걸 구현하는데에는 사실 많은 방법들이 존재한다. ― PlanGrid에서는 ReactiveCocoa를 사용하기로 했는데, 이것을 store가 Observable한 상태 프로퍼티를 제공한다. 아래 코드는 AnnotationFilterStore가 어떻게 이 패턴을 구현했는지 보여준다.

_state 프로퍼티는 store 안에서 상태를 바꾸기 위해 사용되었다. state 프로퍼티는 store에 구독하기 원하는 클라이언트를 위해 사용된다. 이것은 store 구독자들이 상태 갱신을 받을 수 있게 해주나 이것은 직접적으로 그 상태를 바꾸게 하지는 못하게 해놓았다(상태 변경은 action을 통해서만 일어난다!).

초기화 시점에서 내부의  Observable한 프로퍼티는 간단하게 외부 시그널 producer로 간다:

이제 _state로 가는 모든 갱신에서는 자동으로 state에 저장된 시그널 producer을 통해 최신 상태 값을 보낼 것이다.

남은 것은 새 state 값을 보낼때 UI가 갱신되는지 확인하는 코드이다. 이 부분은 iOS에서 Flux 패턴을 처음  사용할 때 만든 꼼수의 부분이다. 웹에서 Flux는 페이스북의 React 프레임워크와 굉장히 잘 동작한다. React는 상태가 갱신되면 추가적인 코드가 필요없이 UI를 다시 렌더링 한다는 특정 시나리오를 전제로 설계되었다.

UIKit과 함께 작업하는 상황에서는 이 부분을 깔끔하게 해결하지 못하고 손수 UI 갱신을 구현해야한다. 이 부분에 대한 이야기는 너무 길어질 수 있기 때문에, 이번 포스트에서는 더 깊게 설명할 순 없다. 대신 최하단에 우리는 UITableView와 UICollectionView를 위해 API 형태로 제공하는 React 컴포넌트들을 만들어 놓았다. 나중에 그것에 대해 가볍게 보여주겠다.

만약 이 컴포넌트에대해 더 배워보고 싶으면 최근에 내가 말한 것을 한번 확인해보거나, 두 Github 저장소(AutoTable, UILib)를 보아도 된다.

이제 주석 필터링 기능은 다시 접어두고 실제 세상의 코드를 보자(이번에는 약간 생략되었다. 이 코드는 AnnotationFilterViewController에 있는 코드이다:

우리의 코드베이스에서 우리는 각 View Controller가 viewWillAppear: 메소드에서 부르게 될 _bind라는 메소드를 들고 있는 규칙을 가졌다. 이 _bind 메소드는 store의 상태를 구독하고 상태 변화가 일어날 때 UI를 갱신하는 역할을 한다.
 
우리는 부분적으로 UI 갱신을 우리 스스로 구현해야 했고, React스러운 프레임워크에만 의존할 수 없었으므로 이 메소드는 어떻게 특정 상태 갱신이 UI 갱신과 맵핑되는지에 대한 코드를 담고있다. 여기 ReactiveCocoa는 이 관계를 설정하기 쉽게 만들어주는 여러 오퍼레이터(skipUtil, take, map 등)을 제공함으로서, 사용하기 쉽게 해준다. 만약 이전에 Reactive 라이브러리를 사용해본 적이 없다면 이 코드가 약간 생소할 수 있다. ― 그러나 우리가 사용하는 ReactiveCocoa는 작은 부분인데다, 배우려고하면 꽤 빨리 배울 수 있다.

예제에서 첫째줄의 _bind 메소드는 상태 변화가 일어날때 Table View를 갱신하게 만든다. 빈 상태일때 갱신이 먹히지 않도록 ReactiveCocoa의 ignoreNil() 오퍼레이터를 사용한다. 우리는 Table View가 어떻게 보여질지 표현에서 store로부터 최신상태를 매핑하기위해 map 오퍼레이터를 사용한다.

이 맵핑은 annotationFilterViewProvider.tableViewModelForState 메소드를 통해 발생한다. 이것은 실행에서, UIKit을 감싸는 우리 커스텀 React가 발생되는 곳이다.

더 깊게 구현에대해 볼 순 없지만, 여기 tableViewModelForState 메소드가 있다.

tableViewModelForState는 인풋으로 최신 상태를 받고, FluxTableViewModel의 양식으로 Table View의 표현을 반환하는 순수 함수이다. 이 메소드의 아이디어는 React의 render 함수와 유사하다. FluxTableViewModel은 전적으로 UIKit과 독립적이고 테이블의 컨텐츠를 담은 구조가 간단하다. 당신은 오픈소스로 구현된 예제를 AutoTable 저장소에서 확인해볼 수 있다.

이 메소드의 결과는 ViewController의 TableViewDataSource 프로퍼티로 넘겨준다. 그 프로퍼티 안에 저장되있는 컴포넌트는 FluxTableViewModel에서 제공하는 정보를 기반으로 UITableView를 갱신하는 역할을 한다.

다른 바인딩 코드는 많이 간단하다. 예를들어 isFiltering 상태에따라 "Clear Filter" 버튼을 enable/disable 하는 코드가 아래에 있다:

UI 바인딩이 UIKit 프로그래밍 모델과 완벽하게 들어맞지 않아서 이것을 구현하는데 꼼수를 조금 사용하였다. 그러나 좀 더 쉽게 커스텀 컴포넌트를 만드려고 아주 약간만 노력을 기울였을 뿐이다. 전통적인 MVC 방식은 수많은 장황한 구현과 수많은 양의 View Controller 구현으로 UI를 갱신하는데, 우리 경험에서는 MVC를 쓰는것 대신 이 컴포넌트를 구현함으로서 구현 시간을 절약할 수 있었다.

이 UI 바인딩이 잘 구현되있다면, 우리는 Flux 기능 구현의 마지막 파트를 이야기할 차례이다. 내가 너무 많은 것을 이야기 했었던 것 같으니 Flux에서의 테스트를 설명하기 이전에 앞에 것들을 빠르게 한번 요약하겠다.

구현의 요약
Flux를 구현할 때 나는 일반적으로 아래 순서에 따라 작업을 쪼개어 한다:
  1. 상태 타입의 모양을 정의한다.
  2. action을 정의한다.
  3. 각 action들의 비즈니스 로직과 상태 변화를 구현한다. ― 이것은 store 안에 구현되있다.
  4. view를 표현하기 위해 상태를 맵핑하는 UI 바인딩을 구현한다.
이것은 우리가 얘기했던 세부적인 구현의 모든것들을 포괄한다.

이제 드디어 Flux에서 어떻게 테스트 할 지에대해 이야기해보자.

테스트 작성하기
Flux 아키텍처의 큰 장점중 하나는 일들을 엄격하게 분리한다는 점이다. 이것은 비즈니스 로직이나 UI 코드의 커다란 부분을 테스트하기 쉽게 해준다.

Flux에서는 테스트 해야하는 두가지 부분이 있다:
  1. store에서 비즈니스 로직
  2. view 모델 프로바이더(이것은 우리 React이다 ― 입력 상태에 따라 UI 표현을 처리하는 함수 형태이다)

store를 테스트하기
store들을 테스트하는 것은 보통 아주 쉽다. 우리 테스트는 action에서 호출하여 store와 함께 상호소통하게 할 수 있고, store에 구독하는 내부 _state 프로퍼티를 Observe하든 하여 상태 변화를 지켜볼 수 있다.

추가적으로 우리는 특정 피처를 구현해보거나 store의 초기화에서 이것을 심어보기위해, store가 소통하는데 필요한 어떤 외부 타입을 모의 객체(Mock Object)로 만들어 볼 수 있다.(특정 피처: API 클라이언트도 될 수 있고 데이터 접근 오브젝트가 될 수도 있다.) 이런 방식은 그 타입들이 우리가 예상한데로 호출되는지 집중할 수 있게 해준다.

PlanGrid에서는 Quick와 Nimble을 사용하여 작업에 관한 스타일의 테스트를 작성하였다. 여기 이 예제는 우리의 주식 필터링 store 부분에서의 테스트이다:

다시한번 말하자면, store를 테스트하는 것은 많은 메리트를 가지고 있다. 이 특정 테스트를 당장에 깊게 다루지는 않을 것이나 테스팅 철학은 명확하다. 가짜로 만든 모의 객체에서 store로 action을 보내고 나서 상태변화된 형태의 응답을 확인한다.

(여러분은 dispatcher를 이용하여 action을 dispatch 하지 않고, 왜 store에서 _handleActions 메소드를 호출하는지 의아해할 것이다. 원래 우리의 dispatcher는 action을 전달할 때, 비동기적 dispatcher를 사용했다. 그렇기에 비동기 테스트가 필요했고, dispatcher의 구현이 바뀌어왔기 때문에 테스트를 진행하면서 dispatcher를 사용할 수 있었다.

store에 비즈니스 로직을 구현할 때 나는 내 첫번째 테스트 코드를 작성하였다. Quick 행동 스펙(spec)과 함께 store 코드의 구조는 테스트 기반 개발 프로세스와 아주 잘 맞게 되어있었다.

view를 테스트하기
선언된 UI 레이어와 Flux 아키텍처는 view를 테스트하기 간단하게 짜여져있다. 팀 내부적으로 우리는 view 레이어에 목표로 하는 커버리지의 양을 아직 의논중이다.

실제로 우리 view에있는 모든 코드는 꽤 직관적으로 짜져있다. view는 store 안에서 우리 UI 레이어의 서로 다른 프로퍼티에 상태를 묶는다. 우리 앱의 경우 UI 자동 테스트를 통해 대부분의 코드를 커버하기로 결정했다.

그러나 여기엔 많은 대안들이 존재한다. view 레이어는 주입된 상태를 렌더링하기위해 초기화함으로 스넵샷 테스트도 매우 잘 동작할 수 있다. Artsy는 다양한 말과 블로그 포스트를 통해 스넵샷 테스팅 아이디어를 소개했다. 이 objc.io 글까지 포함해서 말이다.

우리 앱에선 UI 자동 커버리지가 충분하다고 판단했고, 이 이상 추가적인 스넵샷 테스트는 필요없었다.

또한 나는 view 프로바이더 함수를 유닛 테스트하는 경험도 했다.(e.g. 이전에 보았던 tableViewModelForState 함수) 이 view 프로바이더는 UI 표현을 위해 상태를 맵핑하는 순수 함수들이다. 따라서 입력과 출력 값에 기반한 테스트를 매우 쉽게 할 수 있었다. 그러나 이 테스트들은 실제 구현한 양과 비슷한 양으로 작성되기 때문에 많은 값을 넣어 볼 순 없었다.(However, I found that these tests don’t add too much value as they mirror the declarative description of the implementation very closely.)

우리가 앞에서 본 것처럼 UI 테스팅에는 많은 대안의 솔루션들이 있고, 나는 우리가 긴 기간동안 사용할 솔루션을 모색하는 중이다.

결론
많은 세부적인 구현을 본 뒤에 고수준의 관점에서 우리의 경험을 말해주고 싶었다.

우리는 오직 6개월동안 Flux 아키텍처를 사용해왔지만, 우리 코드를 보면서 이미 여러 장점을 발견할 수 있었다:
  • 새로운 기능을 조화롭게 구현한다. store, view 프로바이더, view controller의 기능의 구조는 거의 동일하다.
  • 상태와 action을 잘 정렬함으로서 그 기능이 어떻게 동작하는지 이해하기 쉽고, BDD 스타일로 테스트 할 수 있다.
  • store와 view를 강력하게 분리해준다. 특정 코드가 모호하게 있는 것이 드물다.
  • 코드 읽기가 굉장히 간단해진다. view가 의존하는 것이 명확하게 보인다. 이게 디버깅하기 매우 수훨하게 해주기까지 한다.
  • 위의 모든 것들이 새 개발자가 투입될때 쉽게 적응하게 만들어준다.

명백하게도 여긴엔 단점들도 있다:
  • UIKit 컴포넌트와 통합하는 첫 걸음이 약간 고통스러울 수 있다. React 컴포넌트와 다르게 UIKit view들은 새 상태에 의해 그들 스스로 간단하게 업데이트 되는 API 지원이 미흡하다. 이것이 조금 힘든 점이고, 우리는 view 바인딩에서 손수 구현하던지 UIKit 컴포넌트를 감싸는 커스텀 컴포넌트를 만들어야 할 필요가 있었다.
  • 아직 우리 모든 새 코드가 Flux 패턴을 정확히 따르지 못했다. 예를들어 Flux에서 동작하는 네비게이션/라우팅 시스템이 아직 자리잡지 못했다. 그래서 Flux 아키텍처에 동등한 패턴을 통합시키던지 ReSwift Router를 사용하여 비슷한 실제 라우터를 사용할 필요가 있었다.
  • 앱의 큰 요소들을 거쳐서쳐 공유되는 상태를 위해 좋은 패턴으로 만들어야한다.(이 포스팅의 초반부에서 "store의 영역은 어디까지인가?"라는 주제로 이야기하였다.) 기존 Flux 패턴으로의 store 사이에 의존성을 만들어야할까? 다른 대안은 무엇이 있을까?

더 많은 실제 구체적인 구현에서 더 많은 이점 혹은 단점이 존재한다. 나는 여기에 좀 더 깊게 파볼 것이고 나중에 블로그 포스트에서 더 세부적인 양상을 확인할 수 있기를 바란다.

지금까지는 이런한 선택으로인해 굉장히 기쁘고, 이 블로그 포스트를 통해 여러분께 Flux 아키텍처가 적절한지 알아볼 수 있는 기회를 제공했기를 바란다.

이제 마지막으로, 여러분이 Swift로 Flux와 함께 작업하고 싶거나 큰 산업을 위해 중요한 제품을 만드는데 도움을 주고 싶으면, 우리는 지금 고용중이다.

이 글의 초안을 검토해준 @zats, @kubanekl, @pixelpartner에게 감사하다.

참고:

  • Flux - 페이스북의 공식적인 Flux 사이트. 원래의 소개가 들어있다.
  • Unidirectional Data Flow in Swift - Swift에서의 Redux 개념과 원래의 ReSwift 구현에대해 이야기한다.
  • ReSwift - Swift에서 Redux를 구현한 것.
  • ReSwift Router - ReSwift 앱을 위한 정의된 라우터



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

,
원문 :  https://medium.cobeisfresh.com/implementing-mvvm-in-ios-with-rxswift-updated-for-swift-2-51cc3ef7edb3#.jzmyljsky

iOS에서 MVVM를 적용시키는 수많은 글들이 존재하지만, 실제로 사용되는 MVVM는 어떻게 생겼는지, 사실상 어떻게 하는지에 초점이 맞춰져있는 글은 거의 없다. 이 글은 RxSwift를 사용하여 좀 더 실질적인 관점에서 MVVM를 살펴 볼 것이다.

ReactiveX는 시퀸스를 observable 함으로서 비동기와 이벤트 기반 프로그램으로 구성된 라이브러리이다.— reactivex.io

RxSwift는 ReactiveX의 Swift 버전이다. 이것은 리엑티브하게 프로그래밍 할 수 있게 도와주는 프레임워크다. 만약 이게 무슨 말인지 모르겠어도(아마 그럴것이다. 함수형 리엑티브 프로그래밍(FRP)은 최근에 각광받기 시작했다.) 멈추지 말고 한번 읽어보길 추천한다. 리엑티브는 여러분의 프로젝트를 더 간결하고, 유지보수 하기 쉽고, 다루기 쉽게 만들어 줄 것이다.

어떻게 iOS 컴포넌트들이 서로 소통할까?
RxSwift의 가장 큰 부분은 앱에서 서로 다른 컴포넌트 사이에서 간단하게 소통할 수 있다는 점이다. 예를들어 Model과 ViewController가 있다. MVC에서는 이들을 연결하기 매우 난잡함을 느낄 수 있었을 것이다.

ViewController에서 모든 outlet을 리셋시키기위해, 아마 model이 갱신될때 항상 Controller에서 updateUI() 함수를 호출해주어야 할 것이다. 이러한 흐름은 불필요한 갱신이나 이상한 버그들이 생기면서 Model과 ViewController 사이에 부조화가 일어나기 쉽다.

우리는 매 순간마다 Model의 옳바른 상태를 표시하는 View Controller가 필요하다. Model이 어떻든 Model 갱신되는 즉시 일치한 데이터를 보여주는 View Controller가 필요하다.

물론 바로 Model만 표시하는 대부분의 앱에서는 의미없는 고민이겠지만, 우리는 Model로부터 데이터를 뽑아와서 화면에 표시할 준비를 하는 과정이 필요하다. 이것이 왜 ViewModel 클래스를 소개하게 되었는지에 대한 이유이다. ViewModel은 화면에 표시할 모든 데이터를 준비한다.

그러나 조금 재미있는 부분이 있다: ViewModel은 ViewController에대해 아무것도 모른다는 사실이다. 절때 그 안에서 직접적으로 참조하거나 프로퍼티를 가지고 있지 않는다. 대신에 ViewController는 ViewModel의 모든 변화를 항상 Observe하고 있으며, ViewModel에서 변화가 일어나면 그것을 화면에 표시한다.

한 프로퍼티당 기반임을 기억하고 있자. 이 의미는 ViewModel 안에서 ViewController가 화면에 개별적으로 각 프로퍼티를 표시한다. 예를들어, 문자열과 이미지를 불러올때, 그 두가지를 다 불러올 때까지 기다리고 있는 것이 아니라, 불러와지는데로 바로바로 각 이미지를 화면에 표시할 수 있다.

ViewController는 화면에 표시하는 역할 뿐 아니라 유저의 입력을 받는 역할도 한다. 우리 ViewController는 단지 프록시(proxy)이고, 그 입력을 ViewController에서 따로 사용하지 않으므로 모든것을 ViewModel로 보내버리고 이것을 ViewModel이 알아서 처리할 것이다.

위 그림은 ViewController와 ViewModel 사이에 단방향 통신을 하는 방법이다. ViewController는 ViewModel을 보고 그것에게 말할 수 있지만, ViewModel은 ViewController가 무엇인지 전혀 모른다. 이 말은 앱에서 ViewController를 완전히 제거해도 모든 로직이 제대로 동작할 것이라는 뜻이다!

좋아보이지 않는가?! 그러나 어떻게 이게 가능할까?

RxSwift와 함께 MVVM
유저의 도시 입력에 따른 기상 예측을 표시해주는 간단한 날씨 앱을 만들어보자.

이 글은 RxSwift의 기본 지식을 가정하고 쓰였다. 만일 ReactiveX에 대해 전혀 모른다면, 마음가는대로 읽어도 상관없지만, ReactiveX 글을 읽어보길 추천한다.


우리는 도시 이름을 입력받기 위해 UITextFeild를 준비하고 현재 온도를 보여주기위해 UILabel을 준비했다.

Note: 이 앱에서는 OpenWeatherMap의 날씨 데이터를 사용했다.

도시의 이름과 날씨로 구성된 Weather 구조체가 우리의 Model이 될 것이다. 이 구조체는 받아온 값을 파싱한 뒤, 속성에 맞춰 만들어진 JSON 오브젝트로부터 만들어진다.


이제 public의 searchText 프로퍼티가 변경될 때, ViewModel이 새 Model을 요청해야한다. ViewController는 유저 입력을 보내기 위해 이 프로퍼티에 접근하게 된다.

searchText는 변수이다. 변수는 필수적으로 BehaviorSubject를 감싼다. 이것은 Observer할수도, Observable할수도 있다. 다시말해 그들이 다시 호출할 수 있는 항목을 그들에게 보낼 수 있다.

BehaviorSubject는 한번만 구독되야하기 때문에 유일한 존재이다. BehaviorSubject는 받았던 마지막 항목을 보낸다. MVVM에서는 이러한 방식이 필요하다. 앱의 라이프 사이클에 의존하며, 다른 클래스에서 Observable은 종종 그것들을 구독하기 전에 엘리먼트를 받기도 한다. ViewController가 ViewModel의 프로퍼티에 구독하면, 화면에 표시하기위해 마지막 항목이 무엇인지 보아야한다. 반대의 경우도 마찬가지이다.

이제 우리는 프로그래밍적으로 변하는 모든 UI 부분에 대해 ViewModel 안에 한 프로퍼티를 정의할 것이다.

ViewModel은 데이터를 출력할 수 있는 형태로 변환하는 역할을 맡고 있다. 이 경우 우리의 Model은 다른 Weather 객체의 Observe되어지는 한 순서이다. 위 프로퍼티(cityName, degrees)는 Weather Observable에 다른 맵핑이 일어날 것이다.

이 프로퍼티가 private로 선언된 이유를  기억하자. ViewController에는 비즈니스 로직에 대해 전혀 몰라야 하기 때문이다. ViewController는 화면에 표시하기 위한 데이터 밖에 모른다.

검색
이제 우리가 위에서 선언한 searchText 프로퍼티에 우리의 Model을 연결해보자.

우리는 searchText가 바뀔때마다 네트워크 요청을 만들 것이다. 그리고 우리의 Model은 그 요청을 구독하고 있을 것이다.

이 경우 searchText가 바뀔때마다 jsonRequest는 NSURLRequest와 통신하기 위해 스스로 갱신된다. 갱신마다 우리의 Model은 NSURLRequest로부터 어떤것을 받던지간에 세팅된다.

만약 JSON 요청중 에러가 나오면 그것을 출력하고 빈 값을 반환한다.

Note: rx_JSON() 메소드는 실제로 그 스스로 Observable 순서이다. 그러므로 jsonRequest는 Observable의 Observable이다. jsonRequest가 가장 최신의 것을 리턴하기 위함이 마지막에 .switchLatest()를 사용하는지에대한 이유이다. 또한 요청을 당신이 그것에 구독하기 전까지 패치되지 않을 것이라는 것을 기억해두자.

.shareReplayWeather에 구독하는 모든 것들이 정확하게 같은 결과를 받았는지 확신하기 위함이다. 그렇지 않을 경우 각 구독은 날씨의 개별 객체를 호출할 것이고 요청이 중복으로 일어날 수 있기 때문이다.

이제 남은 것은 ViewController를 ViewModel에 연결하는 것이다. ViewModel의 Observable을 Controller의 outlet에 바인딩하여 연결할 수 있다.(We’ll do this by binding the PublishSubjects in the ViewModel to outlets in the Controller.)

사용자가 텍스트 필드에 친 값을 ViewModel이 알고 있어야함을 기억하자! ViewModel의 searchText 프로퍼티에 ViewController의 textField 값을 바인딩하여 위 일을 할 수 있다. 따라서 viewDidLoad()에 아래의 코드만 추가하면 된다:


이제 됐다! 우리 앱은 유저가 타이핑하는 동안 날씨 데이터를 갱신한다. 그리고 유저가 어떤 것을 볼지라도 화면 뒤의 앱 상태를 보게된다.

이 앱에서 좀 더 확장되고 주석이 달린 코드의 버전에 관심이 있다면 내 Github의 Weather 앱을 확인해보아라.


여기 당신이 흥미있어할 법한 더 많은 글들이 있다.


용어 정리

  1. Observable, Observer, Subscribe
    : 옵저버 디자인 패턴에서 사용하는 용어로, Observer는 구독(Subscribe)하는 오브젝트, Observable은 구독당하는 오브젝트를 말한다. Observer는 
    Observable에게 자기 자신을 넘겨줘서 Observable에서 이벤트가 발생할때 Observer에 있는 메소드를 호출해줌으로서 구독할 수 있다. 이 글에선 Observer나 Observable에 적합한 한글번역을 찾지 못해, 그대로 표기하였다.



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

,