[112] 실전 스위프트 프로그래밍

94
실전 스위프트 프로그래밍 김영후

Upload: naver-d2

Post on 16-Apr-2017

12.320 views

Category:

Technology


4 download

TRANSCRIPT

Page 1: [112] 실전 스위프트 프로그래밍

실전 스위프트 프로그래밍

김영후

Page 2: [112] 실전 스위프트 프로그래밍

Fancy

Page 3: [112] 실전 스위프트 프로그래밍

Fancy

Page 4: [112] 실전 스위프트 프로그래밍

Fancy Seller

Page 5: [112] 실전 스위프트 프로그래밍

Watch App

•http://www.apple.com/watch/apps/

Page 6: [112] 실전 스위프트 프로그래밍

Widget

Page 7: [112] 실전 스위프트 프로그래밍

ApplePayWWDC 2015 Session 702, “Apple Pay Within Apps”

Page 8: [112] 실전 스위프트 프로그래밍

목차

•최근에 팬시앱에 들어간 메세징 구현을 따라가며 스위프트 기능 살펴보기

•옵셔널

•이뮤터블

•Guard

•Error Handling

Page 9: [112] 실전 스위프트 프로그래밍

세션 소개

• 스위프트를 가볍게 소개해주는 세션은 아니지만 iOS 개발자가 매일 작성하는 뷰/모델/셀/

뷰컨트롤러단의 코드를 예로 들어 좋은 습관과 안 좋았던 습관, 어느정도 옵셔널을 써야하

는 가에 대한 고민, 코드를 좋게 만들기 위해 적용해 나갈 수 있는 기능들에 대한 소개와 모

나드에 대한 이해와 적용 그리고 부분적으로 스위프트 2.0에서 달라지는 예외처리나 패턴

매칭과 같은 기능도 다루려고 합니다

•모나드(flatMap) 제외. 죄송합니다

Page 10: [112] 실전 스위프트 프로그래밍

메세지 기능

•FANCY 앱에 내장된 메세징 (고객과 셀러간의 커뮤니케이션)

•스티커, # 검색 같은거 없는 그냥 채팅

Page 11: [112] 실전 스위프트 프로그래밍

대화목록

ThreadViewController

MessageThread의 목록을 보여줌

class MessageThread { var threadId: NSNumber var unreadCount: Int init(dict: NSDictionary) { ... } }

Page 12: [112] 실전 스위프트 프로그래밍

대화창

MessageViewController

메세지뷰는 스레드를 이용하여 메세지를 불러온다

class MessageViewController: UIViewController { var thread: MessageThread! override func viewDidLoad() { //스레드의 메세지들을 멋지게 보여준다

thread를 “Implicitly Unwrapped Optional”로 선언

옵셔널이지만 옵셔널이 아닌것 처럼 사용 (다른 머글 언어의 레퍼런스 타입과 동일)

Page 13: [112] 실전 스위프트 프로그래밍

Thread -> Message

ThreadViewController는 MessageViewController를 만들때 thread를 전달

func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) { ... let messageViewController = MessageViewController() messageViewController.thread = self.threads[indexPath.row] navigationController?.pushViewController(messageViewController, animated: true) }

혹시 thread를 전달 안하면 어떻게 되지?

크래시!

Page 14: [112] 실전 스위프트 프로그래밍

설마 그런일이 있을리가 ㅋ

Page 15: [112] 실전 스위프트 프로그래밍

그런데 기존의 스레드를 보여주는 것 말고새로운 대화를 시작하려면 Thread가 없다

Page 16: [112] 실전 스위프트 프로그래밍

그럴땐서버 개발자를 협박해서

대화를 시작할 때 Thread를 얻어온다

Page 17: [112] 실전 스위프트 프로그래밍

…그냥 내가 고친다

Page 18: [112] 실전 스위프트 프로그래밍

MessageViewController

새 대화: 기존 스레드가 없을 경우

그럼 targetUser 변수를 추가한다!

class MessageViewController: UIViewController { var thread: MessageThread! //스레드가 있을 때 (기존 대화창) var targetUser: MessageMember! //스레드가 없는데 새 상대랑 대화할 때 override func viewDidLoad() { //스레드의 메세지들을 멋지게 보여준다

Page 19: [112] 실전 스위프트 프로그래밍

MessageViewController

Page 20: [112] 실전 스위프트 프로그래밍

MessageViewController

targetUser는 다른 뷰(사용자 선택 뷰)를 통해 할당된다

func newMessage(sender: AnyObject) { let selectUserViewController = SelectUserViewController() //사용자 선택 뷰 selectUserViewController.didSelect = { user in //선택하고 닫힐 때의 콜백 let messageViewController = MessageViewController() messageViewController.targetUser = user //선택된 유저 navigationController?.pushViewController(messageViewController, animated: true) } presentViewController(selectUserViewController, animated: true, completion: nil) }

Page 21: [112] 실전 스위프트 프로그래밍

MessageViewController

상호 배타적인 두 변수가 !

targetUser나 thread 둘 중 하나만 값이 있고 나머지는 nil이므로 그냥 옵셔널로 바꾸자

class MessageViewController: UIViewController { var thread: MessageThread? //스레드가 있을 때 (기존 대화창) var targetUser: MessageMember? //스레드가 없는데 새 상대랑 대화할 때

MessageViewController는 둘 중 하나만 있는 상황에 적절하게(?) 춤을 춘다

Page 22: [112] 실전 스위프트 프로그래밍

MessageViewController

옵셔널 댄스

춤의 예

override func viewDidLoad() { // 뷰 타이틀 (스레드의 제목 혹은 사용자 이름) title = thread?.title ?? (targetUser?.fullname ?? targetUser.username)

이런 코드가 도처에 난무할 것이다.

전송 때도 사용자ID 혹은 스레드ID를 선택적으로 보냄

if let t = thread { } , if let user = targetUser { }

Page 23: [112] 실전 스위프트 프로그래밍

MessageViewController

변수 선언 리뷰class MessageViewController: UIViewController { var thread: MessageThread? //스레드가 있을 때 (기존 대화창) var targetUser: MessageMember? //스레드가 없는데 새 상대랑 대화할 때

이 선언은 맞는 것 같지만 우리가 원하는건 thread가 있거나 targetUser가 있는 양자 택일의

제약. 하지만 이 선언대로라면 둘다 nil이 될 수 있고 둘다 nil이 안될 수도 있음

Page 24: [112] 실전 스위프트 프로그래밍

설마 그런일이 있을리가 ㅋ

Page 25: [112] 실전 스위프트 프로그래밍

이런 코드를 수억수천번 써왔으므로 견딜 수 있다

Page 26: [112] 실전 스위프트 프로그래밍

메세지 뷰의 진입 지점은 겨우 2곳(기존 스레드와 새창)이니 견딜 수 있다

Page 27: [112] 실전 스위프트 프로그래밍

는 그럴리가 없음

뷰컨트롤러의 시작 지점은 통제가 어려움

- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler {

//메세지 노티피케이션을 탭하면 메세지를 보여주어야 함 }

- (void)handleDeepLinkURL:(NSURL *)url { //딥링크에서 메세지 보여주기 }

이 코드를 내가 만든다는 보장도 없음

Page 28: [112] 실전 스위프트 프로그래밍

현실

AppDelegate에만 Message를 여는 곳이 4곳

Page 29: [112] 실전 스위프트 프로그래밍

그리고아이패드가 출동하면?

Page 30: [112] 실전 스위프트 프로그래밍

iPad

iPad Message

• 빈 메세지 뷰를 보여주어야 함

• UISplitViewController를 처음 열때

• 혹은 메세지를 아카이브 했을 때

Page 31: [112] 실전 스위프트 프로그래밍

MessageViewController

진짜 둘다 nilclass MessageViewController: UIViewController { var thread: MessageThread? //스레드가 있을 때 (기존 대화창) var targetUser: MessageMember? //스레드가 없는데 새 상대랑 대화할 때

둘다 nil인 상황이 현실이 됨

이제 둘다 nil일 때도 처리해서 빈 화면을 보여주어야 함

Page 32: [112] 실전 스위프트 프로그래밍

참을 수 없다리팩토링

Page 33: [112] 실전 스위프트 프로그래밍

MessageViewController

리팩토링enum MessageViewMode { case Thread(MessageThread) case User(MessageMember) case Empty }

Enumeration (Associated Values): 연관된 값을 가질 수 있는 enum

//케이스 예 case MyCase(Int, String, Float?) //숫자, 문자열, 옵셔널 Float을 연관 타입으로 가지는 케이스 case MyAnotherCase(v1: Int, v2: NSError?) //Int, NSError? 타입의 네임드튜플을 가지는 케이스

Page 34: [112] 실전 스위프트 프로그래밍

MessageViewController

리팩토링: 생성자 오버라이딩, Non-Optional 변수class MessageViewController: UIViewController { var messageViewMode: MessageViewMode //옵셔널이 아님. nil이 될 수 없다 required init(coder aDecoder: NSCoder) { fatalError("Not available") } init(mode: MessageViewMode) { messageViewMode = mode super.init(nibName: nil, bundle: nil) } }

Page 35: [112] 실전 스위프트 프로그래밍

MessageViewController

생성시let vc = MessageViewController(mode: .Thread(thread)) //기존 스레드를 보여주는 모드 let vc = MessageViewController(mode: .User(member)) //새 사용자와 대화를 시작하는 모드

self.splitViewController?.showDetailViewController( UINavigationController(rootViewController: MessageViewController(mode: .Empty)) //비어있는 모드

Page 36: [112] 실전 스위프트 프로그래밍

MessageViewController

제약 조건이 명확히 표현되었는가?class MessageViewController: UIViewController { var messageViewMode: MessageViewMode //옵셔널이 아니므로 모드가 꼭 있다 required init(coder aDecoder: NSCoder) { fatalError("Not available") } init(mode: MessageViewMode) { messageViewMode = mode //생성시 모드를 꼭 넘겨 받는다 super.init(nibName: nil, bundle: nil) } }

enum MessageViewMode { case Thread(MessageThread) //옵셔널이 아니므로 nil일 수 없다 case User(MessageMember) //역시 옵셔널이 아니므로 nil일 수 없다 case Empty }

Page 37: [112] 실전 스위프트 프로그래밍

MessageViewController

제약 조건 올킬

•메세지 뷰는 3가지 모드: 스레드 모드, 유저 모드, 비어있는 모드가 있다 (enum)

•이 3가지 모드 중 한 가지는 꼭 있어야 한다 (Non-optional과 init 오버라이딩)

•스레드 모드는 꼭 스레드 객체가 있어야 하며 다른 객체는 있을 수 없다

•유저 모드는 꼭 멤버 객체가 있어야 하며 다른 객체는 있을 수 없다

•비어있는 모드는 스레드 객체와 멤버 객체 둘 다 있을 수 없다

Page 38: [112] 실전 스위프트 프로그래밍

MessageViewController

하나 더: 왜 messageViewMode가 var일까?

class MessageViewController: UIViewController { var messageViewMode: MessageViewMode //만약 이 뷰컨트롤러의 로직상 메세지 모드가 바뀔 수 있다면 var가 적합

class MessageViewController: UIViewController { let messageViewMode: MessageViewMode //바뀔 수 없다는 걸 강제하고 싶다면 let

Page 39: [112] 실전 스위프트 프로그래밍

MessageViewController

Objective-Ctypedef NS_ENUM(NSUInteger, MessageViewMode) { MessageViewModeThread, MessageViewModeTargetUser, MessageViewModeEmpty, }; //모드를 위한 enum을 만든다

@interface MessageViewController : UIViewController

@property (nonatomic, assign) MessageViewMode messageViewMode; //모드가 있지만 @property (nonatomic, strong) MessageThread *thread; //이 레퍼런스는 모드와 상충될 수 있다 @property (nonatomic, strong) MessageMember *targetUser; //마찬가지로 상충될 수 있다

@end

•NS_ENUM의 모드와 객체 변수와의 상관관계를 표현할 수 없다

Page 40: [112] 실전 스위프트 프로그래밍

MessageViewController

Objective-C

enum을 쓴다 해도 Thread, User와의 연관성은 표현할 수 없음

생성자를 강제 해도 내부적으로 Thread모드에 TargetUser 객체 할당을 막을 방법은 없다

Page 41: [112] 실전 스위프트 프로그래밍

MessageViewController

물론 더 구조화를 하면 어떻게든 할 수는 있겠지만.. 오버엔지니어링의 시작

@interface MessageViewMode : NSObject @end

@interface ThreadMode : MessageViewMode @property (nonatomic, strong) MessageThread *thread; @end

@interface TargetUserMode : MessageViewMode @property (nonatomic, strong) MessageMember *targetUser; @end

@interface MessageViewController : UIViewController @property (nonatomic, strong) MessageViewMode *messageViewMode; @end

Page 42: [112] 실전 스위프트 프로그래밍

정리

개발자(김영후씨)의 의식 변화

•아 스레드 빨리 보여줘야지

•아 새 대화도 해야하네

•아 아이패드는 빈 화면도 있네

•아 막 여기저기서 메세지를 열어야 하네

•아 죽겠네

개발자의 머릿속에 있는 제약조건을 코드에 얼마나 표현 할 수 있는가?

그 표현을 위해 복잡도가 얼마나 증가하는가?

스위프트는 좋은 언어입니다

Page 43: [112] 실전 스위프트 프로그래밍

옵셔널

Page 44: [112] 실전 스위프트 프로그래밍

옵셔널

다른 머글 언어의 레퍼런스 타입은 기본이 옵셔널

1. 스위프트는 반대로 기본 타입이 nil이 불가능하며 옵셔널 변수 선언을 ?나 !로 명시한다

2. !로 명시하면 옵셔널을 옵셔널이 아닌거 같이 쓰겠다는 만용

(저도 만용 많이 부립니다...)

Page 45: [112] 실전 스위프트 프로그래밍

옵셔널

실제 코드class SettingsHeaderView: UIView { let logoImageView: UIImageView let storeLabel: UILabel let nameLabel: UILabel let logoSize = 50.0

• 옵셔널이 아닌 변수가 의외로 많음

Page 46: [112] 실전 스위프트 프로그래밍

옵셔널

옵셔널을 안쓰는 뷰 코드 템플릿class MyView: UIView { let myLabel: UILabel required init(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } override init(frame: CGRect) { myLabel = UILabel() ... //기타 UILabel 설정 (self에 접근하지 않는 설정들, 폰트, 색깔 등)

super.init(frame: frame) //self가 필요한 설정 (이벤트 등) addSubview(myLabel) //UITableViewCell이면 contentView.addSubview } }

Page 47: [112] 실전 스위프트 프로그래밍

옵셔널

class ProductDetail: JSONModelProtocol { //Fancy Seller의 모델 코드 var productId: String? var quantity: Int var title: String var description: String var price: String var numAvailable: Int

•이 경우 왜 ProductDetail의 productId는 옵셔널일까?

•앱에서 생성될 경우 서버와 싱크 되기 전까진 id 값이 없다

•즉 앱에서 생성될 수 있다는걸 productId를 따라가면 알 수 있음

Page 48: [112] 실전 스위프트 프로그래밍

옵셔널

class MerchantProductDetail: JSONModelProtocol { var productId: String!

이렇게 암시적 선언을 하면 productId를 옵셔널이 아닌것 처럼 쓰기 때문에 ProductDetail 라이브러리 코드의 사용자가 그 정보를 놓칠 수 있음

productId가 당연히 있다고 가정한 코드를 쓰다가 크래시!

Page 49: [112] 실전 스위프트 프로그래밍

옵셔널

class ListViewController: UIViewController { var headerView: UIView! var tableView: UITableView! override func viewDidLoad() { super.viewDidLoad()

//headerView 초기화 //tableView 초기화 } func anyFunction() { tableView.reloadData() //옵셔널이 아닌것 처럼 사용 } }

사실상 viewDidLoad부터 뷰 컨트롤러가 종료

될 때까지 nil이 아니라고 보증할 수 있기 때문

에 많이 사용되는 패턴

Page 50: [112] 실전 스위프트 프로그래밍

보증할 수 있다고?

Page 51: [112] 실전 스위프트 프로그래밍

옵셔널

class ListViewController: UIViewController { var headerView: UIView! var tableView: UITableView! override func viewDidLoad() { super.viewDidLoad()

//headerView 초기화 //tableView 초기화 } override func viewWillTransitionToSize(size: CGSize, withTransitionCoordinator coordinator: UIViewControllerTransitionCoordinator) { tableView.reloadData() //크래시 } }

iPad와 UITabBarController를 쓸 경우 로테이션이 일어나면 viewWillTransitionToSize가 viewDidLoad보다 먼저 호출됨

Page 52: [112] 실전 스위프트 프로그래밍

!를 쓸 때 자신을 믿지 말자

Page 53: [112] 실전 스위프트 프로그래밍

옵셔널

class ListViewController: UIViewController { var headerView: UIView? var tableView: UITableView? override func viewDidLoad() { super.viewDidLoad()

//headerView 초기화 //tableView 초기화 } override func viewWillTransitionToSize(size: CGSize, withTransitionCoordinator coordinator: UIViewControllerTransitionCoordinator) { tableView?.reloadData() tableView?.tableViewHeader = headerView } }

Page 54: [112] 실전 스위프트 프로그래밍

이뮤터블 Immutability

무조건 / 생각없이 let부터 쓰는 습관class ActivityTabView: UIView { let activityButton: UIButton let notificationButton: UIButton let separator: UIView

•스위프트의 let은 객체의 프로퍼티 값이 변하는지에 관심이 없다

•오직 레퍼런스만 바뀌지 않으면 let

•즉 밸류 타입이 아닌 레퍼런스 타입은 대부분 let이 가능

Page 55: [112] 실전 스위프트 프로그래밍

이뮤터블 Immutability

var가 보인다면class MerchantCustomerDetail: MerchantCustomer { let email: String let isVip: Bool let completedOrder: Int let processingOrder: Int let note: String var orders:[MerchantOrder] = []

•var가 보인다면 이 타입이 밸류 타입이면서 값이 변하거나

•레퍼런스 타입이며 레퍼런스가 바뀐다는 아주 강력한 신호

Page 56: [112] 실전 스위프트 프로그래밍

옵셔널과 이뮤터블

•현란한 테크닉 없이 이 둘만 꼼꼼히 챙겨도 상대적으로 좋은 코드 구현이 가능

•코드리딩시 옵셔널과 뮤터블 정보만 챙겨도 구현자의 주의점을 이해할 수 있음

•스위프트 2.0부턴 Xcode가 let을 더 잘 챙겨줌

Page 57: [112] 실전 스위프트 프로그래밍

옵셔널 판단은 베팅

class FCMessageMember { var memberId: NSNumber init(dict: NSDictionary) { memberId = dict.objectForKey("id") as! NSNumber //JSON에 id가 없다면 크래시

•옵셔널을 잘 못 판단하면 크래시가 날 수 있다

•코딩을 할 때 머릿속에서 어떤 가정을 해야하며 그 가정을 표현한다

•하지만 크래시는 피할 수 없다 Software is Hard

•그럼에도 옵셔널은 내가 어떤 가정을 했는지 알게 해주고

•잘못된 판단을 내렸을 경우 해당 문제를 확실히 이해하게 해준다

•그동안 얼마나 자주 문제를 이해하지 못하고 그저 null 체크만 추가했던가?

Page 58: [112] 실전 스위프트 프로그래밍

모든 변수를 옵셔널(?, !)로 선언하면베팅을 전혀 안한 것

스위프트를 안 쓰는 것과 다름 없다

Page 59: [112] 실전 스위프트 프로그래밍

Guard

Page 60: [112] 실전 스위프트 프로그래밍

옵셔널을 이용하는 방법

tableView.tableFooterView?.hidden = true //nil이면 실행되지 않는다 tableView.tableFooterView?.addSubview(myView) //메소드 호출도 같은 방식

if let footerView = tableView.tableFooterView { //가장 일반화된 if-let setupFooterInterface(footerView) }

if let footerView = tableView.tableFooterView, let user = loggedInUser where indexPath.section == 0 { //where에는 옵셔널과 상관없이 어떠한 조건문이든 올 수 있음 setupFooterInterface(footerView, forUser: user) }

마지막 패턴은 충분히 강력하고 좋다 하지만...

Page 61: [112] 실전 스위프트 프로그래밍

옵셔널을 이용하는 방법

if tableView.tableFooterView == nil || loggedInUser == nil { //역으로 실패하는 조건문 return }

setupFooterInterface(tableView.tableFooterView!, forUser: loggedInUser!)

•예외가 아닌 정상적인 흐름이 if let { 블록에 의해 브랜치 됨

•이렇게 할 경우 정상적인 흐름을 메인 브랜치에 둘 수 있지만 단점은 명백

•실패하는 조건문을 만들어야 하며

•옵셔널을 검사 하긴 했지만 바인딩 되지 않았으므로 !를 써야함

•스위프트가 아닌 머글 언어에서 흔히 보는 코드 ...

Page 62: [112] 실전 스위프트 프로그래밍

Guard (Swift 2.0)

guard let footerView = tableView.tableFooterView, let user = loggedInUser else { //guard문이 실패할 경우 return }

//여기서부터 이 guard문이 있는 블록에 footerView와 user 값이 바인딩되어 있음 setupFooterInterface(footerView, forUser: user)

•guard는 정상 구문과 옵셔널이 바인딩된 값을 메인 브랜치에서 사용 가능하게 해줌

•guard let <여기 오는 조건은 if let과 동일 (역이 아님)> else { 실패 경우 처리 }

Page 63: [112] 실전 스위프트 프로그래밍

Pattern Matching

Page 64: [112] 실전 스위프트 프로그래밍

Pattern Matching

switch (5, "Hello", 3.14) { case (_, _ as String, let pi) where pi > 3.0: print("\(pi)") default: print("default") }

•스위프트의 switch는 강력한 패턴 매칭을 지원함

•와일드카드 _ 패턴

•와일드카드 + 타입 체크 패턴 as String

•튜플의 3번째 요소를 let pi 바인딩

•where절로 패턴을 벗어난 모든 조건 검사

Page 65: [112] 실전 스위프트 프로그래밍

Pattern Matching

enum MerchantRouter: URLRequestConvertible {

case Order(orderId: String) case OrderMarkAsProcessing(orderId: String) case OrderFulfill(orderId: String, carrierCode: Int, tracking: String, items: String) case OrderCancel(orderId: String) case Products(filter: String, sort: String, ascending: String, search: String?, cursor: APIPageCursor) case Product(productId: String)

Enum, Associated Value로 더 강력하게 쓸 수 있다 (Alamofire)

클라이언트에서 URL 라우팅을 Enum으로 Type-safe하게 처리함

Page 66: [112] 실전 스위프트 프로그래밍

Pattern Matching

switch self { case .Orders(let filter, let sort, let ascending, let search, _): return ("/orders", ["filter": filter.0, "search": search, "sort[sort]": sort, "sort[ascending]": ascending]) case .Order(let orderId): return ("/orders/\(orderId)", nil) case .OrderMarkAsProcessing(let orderId): return ("/orders/\(orderId)", ["action": "mark_as_processing"])

URL 라우팅과 파라메터를 설정

빠진 Enum 케이스가 있을 경우 컴파일 경고

Page 67: [112] 실전 스위프트 프로그래밍

Pattern Matching

enum DownloadResult { case Success(size: Int) case Failure(description: String, code: Int) }

if case .Failure(let message, let code) = result where code >= 400 { print("Download Failure:\(message)") }

Swift 2.0부턴 switch가 아니어도 패턴 매칭 가능 if case

Page 68: [112] 실전 스위프트 프로그래밍

Error Handling

Page 69: [112] 실전 스위프트 프로그래밍

NSError

•Swift1.2까지의 에러 처리는 Objective-C의 패턴을 계승

•NSError ** 를 NSErrorPointer

•복원가능한(Recoverable) 에러에 한해 사용

var error: NSError? //꼭 옵셔널이어야 함 myString.writeToURL(url, atomically: true, encoding: NSUTF8StringEncoding, error: &error) if let e = error { println(e.localizedDescription) }

Page 70: [112] 실전 스위프트 프로그래밍

NSException

•Objective-C에 있는 다른 언어의 예외 처리와 같은 @try { } @catch{ }

•복원 불가능한 에러, valueForKey:

@try { [invocation invokeWithTarget:o]; } @catch (NSException *exception) { LogError(@"Exception during invocation: %@", exception); }

•이것을 NSError 대신 못 쓸 이유는 없지만 프레임워크가 그럴 수 있도록 다양한 예외를 만

들어주지 않음

•NSFileSavingException 같은 예외를 만들어 주지 않고 NSError를 사용

Page 71: [112] 실전 스위프트 프로그래밍

Swift 2.0 Error Handling

•스위프트 1.2의 에러 시그니쳐

func writeToURL(url: NSURL, atomically useAuxiliaryFile: Bool, encoding enc: NSStringEncoding, error: NSErrorPointer = default) -> Bool

•스위프트 2.0의 에러 시그니쳐: NSErrorPointer가 없어지고 throw가 붙음

func writeToURL(url: NSURL, atomically useAuxiliaryFile: Bool, encoding enc: NSStringEncoding) throws

Page 72: [112] 실전 스위프트 프로그래밍

Swift 2.0 Error Handling

•에러 핸들링

do { try myString.writeToURL(url, atomically: true, encoding: NSUTF8StringEncoding) } catch let error as NSError { print(error.localizedDescription) }

•do, catch 블록

•throw 마크가 된 함수를 호출 할 때 try를 붙여야 함

•do 내에서 throw가 있는 호출은 다 try를 붙여야 함

•Java와 달리 어떤 호출이 예외를 발생시키는지를 명확히 함. try { }로 퉁칠 수 없다!

Page 73: [112] 실전 스위프트 프로그래밍

ErrorType•ErrorType 프로토콜을 사용해서 Enum으로 에러를 정의 가능

enum MyGameError: ErrorType { case Bad(String) case Worse(Int) case Terrible }

func doDangerousStuff() throws { //위험한 작업을 한다 throw MyGameError.Bad("OMG") //예외 발생 }

do { try doDangerousStuff() } catch MyGameError.Bad(let message) { print(message) }

•스위프트 2.0이 보편화 되면 많은 API가

NSError대신 Enum을 사용할 것

Page 74: [112] 실전 스위프트 프로그래밍

ErrorType + Guard•Guard는 에러 핸들링에 아주 적합함

enum ProductError: ErrorType { case InputMissing case Quantity(Int) case Other(String) }

func createProduct() throws { guard let title = titleField.text, quantity = quantityField.text where !title.isEmpty && !quantity.isEmpty else { throw ProductError.InputMissing } guard let quantityNumber = Int(quantity) where quantityNumber > 10 else { throw ProductError.Quantity(quantityNumber) } //create product }

Page 75: [112] 실전 스위프트 프로그래밍

ErrorType + Pattern Matching

•Catch는 switch나 if case처럼 패턴 매칭이 가능

do { try createProduct() } catch ProductError.InputMissing { print("Input Missing") } catch ProductError.Quantity(let minimum) { print("Quantity Minimum:\(minimum)") }

Page 76: [112] 실전 스위프트 프로그래밍

Defer

•try-catch-finally?

do { defer { //finally print("release resource") }

try myString.writeToURL(url, atomically: true, encoding: NSUTF8StringEncoding) } catch let error as NSError { print(error.localizedDescription) }

•defer는 해당 블록(func, do 등)이 마지막에 무조건 호출 됨

•에러 처리를 떠나 보편적인 응용 가능성이 높음

Page 77: [112] 실전 스위프트 프로그래밍

새로 스위프트 프로젝트를 시작한다면...

•Fancy Seller

•AFNetworking 대신 Alamofire

•JSON처리에 NSDictionary대신 SwiftyJSON

•2.0에서 강화된 프로토콜과 제네릭을 이용해서 MVVM 구조를 가지도록 노력해 봤을 것

•Guard와 ErrorType을 이용해서 예외처리를 좀 더 제대로 설계 했을 것

Page 78: [112] 실전 스위프트 프로그래밍

Q&A

Page 79: [112] 실전 스위프트 프로그래밍

Thank You

Page 80: [112] 실전 스위프트 프로그래밍

옵셔널과 flatMap

Page 81: [112] 실전 스위프트 프로그래밍

Optional

maplet numbers = [1, 2, 3] let n = numbers.map { $0 * 2 }

•Array에 대한 map은 이제 너무나 당연한 이디엄이 됨

•하지만 옵셔널도 map이 됨

var number: Int? = 5 var newNumber: Int? = number.map { $0 * 5 }

•이렇게 map 연산을 지원하는 타입(어레이, 옵셔널 등)을 Functor라고 부름

Page 82: [112] 실전 스위프트 프로그래밍

Optional

Optionalvar numberFive: Optional<Int> = 5 //Int?와 동일함

사실은 enum임: None이냐 Some(T)이냐 (Haskell은 Option 타입)

enum Optional<T> : Reflectable, NilLiteralConvertible { case None case Some(T) init() init(_ some: T) func map<U>(f: @noescape (T) -> U) -> U? func flatMap<U>(f: @noescape (T) -> U?) -> U? ... }

Page 83: [112] 실전 스위프트 프로그래밍

즉 옵셔널을 하나의 값이 있거나 없는박스와 같은 자료구조로 볼 수 있다

Page 84: [112] 실전 스위프트 프로그래밍

flatMap

숫자값을 가지고 있는 String을 가격 포매팅을 해보자

근데 ,가 아닌 .로 바꿔보자

“5000” -> Some(“5.000”)

“ABC” -> nil

Page 85: [112] 실전 스위프트 프로그래밍

flatMap

숫자값을 가지고 있는 String을 가격 포매팅을 해보자

•문자를 숫자로 변환(NSNumberFormatter의 numberFromString)

= String -> NSNumber?

•숫자를 가격 문자로 변환 (NSNumberFormatter의 stringFromNumber)

= NSNumber -> String?

•다시 문자를 문자로 변환

= String -> String?

Page 86: [112] 실전 스위프트 프로그래밍

flatMap

숫자값을 가지고 있는 String을 가격 포매팅을 해보자let f1: NSNumberFormatter = … //문자->숫자? 포매터 let f2: NSNumberFormatter = … //숫자->문자? 포매터, NSNumberFormatterStyle.CurrencyStyle 사용

extension String { public func priceAsDecimal() -> String? { return f1.numberFromString(self).flatMap { f2.stringFromNumber($0) } } }

•값을 받아 박스(옵셔널)를 돌려주는 함수의 무한한 결합을1차원으로 만들 수 있음

•String -> NSNumber?, NSNumber -> String?

Page 87: [112] 실전 스위프트 프로그래밍

flatMap

왜 map은 안되나extension String { public func priceAsDecimal() -> String?? { return f1.numberFromString(self).map { f2.stringFromNumber($0) } } }

•stringFromNumber가 String?을 돌려준다

•map은 계산된 값을 다시 박스(옵셔널)에 감싼다 (어레이의 map도 동일)

•즉 map을 쓰면 String?? = Optional<Optional<String>>이 되어 불가능

•flatMap이라고 부르는 이유

Page 88: [112] 실전 스위프트 프로그래밍

flatMap

숫자값을 가지고 있는 String을 가격 포매팅을 해보자func replaceComma(s: String) -> String? { ... }

public func priceAsDecimal() -> String? { return f1.numberFromString(self).flatMap { f2.stringFromNumber($0) }.flatMap(replaceComma) }

•값을 받아 박스(옵셔널)를 돌려주는 함수의 무한한 결합을1차원으로 만들 수 있음

•String -> NSNumber?, NSNumber -> String?, String -> String?

Page 89: [112] 실전 스위프트 프로그래밍

flatMap

if let을 쓴다면public func priceAsDecimal2() -> String? { //스위프트 1.0 if let n = f1.numberFromString(self) { if let p = f2.stringFromNumber(n) { return replaceComma(p) } } return nil }

Page 90: [112] 실전 스위프트 프로그래밍

flatMap

if let을 쓴다면public func priceAsDecimal2() -> String? { //스위프트 1.2 if let n = f1.numberFromString(self), p = f2.stringFromNumber(n) { return replaceComma(p) } return nil }

•1.2의 if let은 flatMap처럼 의존성 있는 옵셔널 바인딩들을 1차원으로 만들 수 있음

•if let이 옵셔널에 대한 flatMap을 문법으로 만들었다고 볼 수 있음

•그럼 왜 쓰나 flatMap ...

Page 91: [112] 실전 스위프트 프로그래밍

flatMap

let title = NSURL(string:”http://“).flatMap { NSData(contentsOfURL: $0)}?.flatMap { NSJSONSerialization.JSONObjectWithData($0, options: NSJSONReadingOptions(0)) as? NSDictionary}.flatMap { $0["title"] as? String}

if let url = NSURL(string: "http://"), data = NSData(contentsOfURL: url), json = NSJSONSerialization.JSONObjectWithData(data, options: NSJSONReadingOptions(0), error: nil) as? NSDictionary { title = $0["title"] as? String }

flatMap vs if let

Page 92: [112] 실전 스위프트 프로그래밍

flatMap

•flatMap은 제네릭 패턴 (if let은 옵셔널에 한정): 어레이도 flatMap이 있음

•타입A를 받고 무언가로 감싼A를 돌려주는 함수 패턴을 결합 가능

•함수를 A->B? 식으로 설계하면 flatMap으로 연계 가능

•flatMap은 모나드 연산자 >>= 이며 flatMap을 지원하는 타입을 모나드 라고 부름

•즉 스위프트의 옵셔널과 어레이는 모나드

•다른 추상 타입도 flatMap 연산을 만들면 모나드

•더 공부해 봅시다

Page 93: [112] 실전 스위프트 프로그래밍

Q&A (진짜)

Page 94: [112] 실전 스위프트 프로그래밍

Thank You (진짜)