SlideShare une entreprise Scribd logo
1  sur  94
Télécharger pour lire hors ligne
실전 스위프트 프로그래밍
김영후
Fancy
Fancy
Fancy Seller
Watch App
•http://www.apple.com/watch/apps/
Widget
ApplePay
WWDC 2015 Session 702, “Apple Pay Within Apps”
목차
•최근에 팬시앱에 들어간 메세징 구현을 따라가며 스위프트 기능 살펴보기

•옵셔널

•이뮤터블

•Guard

•Error Handling
세션 소개
• 스위프트를 가볍게 소개해주는 세션은 아니지만 iOS 개발자가 매일 작성하는 뷰/모델/셀/
뷰컨트롤러단의 코드를 예로 들어 좋은 습관과 안 좋았던 습관, 어느정도 옵셔널을 써야하
는 가에 대한 고민, 코드를 좋게 만들기 위해 적용해 나갈 수 있는 기능들에 대한 소개와 모
나드에 대한 이해와 적용 그리고 부분적으로 스위프트 2.0에서 달라지는 예외처리나 패턴
매칭과 같은 기능도 다루려고 합니다

•모나드(flatMap) 제외. 죄송합니다
메세지 기능
•FANCY 앱에 내장된 메세징 (고객과 셀러간의 커뮤니케이션)

•스티커, # 검색 같은거 없는 그냥 채팅
대화목록
ThreadViewController
MessageThread의 목록을 보여줌

class MessageThread {
var threadId: NSNumber
var unreadCount: Int
init(dict: NSDictionary) {
...
}
}
대화창
MessageViewController
메세지뷰는 스레드를 이용하여 메세지를 불러온다

class MessageViewController: UIViewController {
var thread: MessageThread!
override func viewDidLoad() {
//스레드의 메세지들을 멋지게 보여준다
thread를 “Implicitly Unwrapped Optional”로 선언
옵셔널이지만 옵셔널이 아닌것 처럼 사용 (다른 머글 언어의 레퍼런스 타입과 동일)
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를 전달 안하면 어떻게 되지?
크래시!
설마 그런일이 있을리가 ㅋ
그런데 기존의 스레드를 보여주는 것 말고

새로운 대화를 시작하려면 Thread가 없다
그럴땐

서버 개발자를 협박해서 

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

그냥 내가 고친다
MessageViewController
새 대화: 기존 스레드가 없을 경우
그럼 targetUser 변수를 추가한다!

class MessageViewController: UIViewController {
var thread: MessageThread! //스레드가 있을 때 (기존 대화창)
var targetUser: MessageMember! //스레드가 없는데 새 상대랑 대화할 때
override func viewDidLoad() {
//스레드의 메세지들을 멋지게 보여준다
MessageViewController
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)
}
MessageViewController
상호 배타적인 두 변수가 !
targetUser나 thread 둘 중 하나만 값이 있고 나머지는 nil이므로 그냥 옵셔널로 바꾸자

class MessageViewController: UIViewController {
var thread: MessageThread? //스레드가 있을 때 (기존 대화창)
var targetUser: MessageMember? //스레드가 없는데 새 상대랑 대화할 때
MessageViewController는 둘 중 하나만 있는 상황에 적절하게(?) 춤을 춘다
MessageViewController
옵셔널 댄스
춤의 예

override func viewDidLoad() {
// 뷰 타이틀 (스레드의 제목 혹은 사용자 이름)
title = thread?.title ?? (targetUser?.fullname ?? targetUser.username)
이런 코드가 도처에 난무할 것이다.
전송 때도 사용자ID 혹은 스레드ID를 선택적으로 보냄
if let t = thread { } , if let user = targetUser { }
MessageViewController
변수 선언 리뷰
class MessageViewController: UIViewController {
var thread: MessageThread? //스레드가 있을 때 (기존 대화창)
var targetUser: MessageMember? //스레드가 없는데 새 상대랑 대화할 때
이 선언은 맞는 것 같지만 우리가 원하는건 thread가 있거나 targetUser가 있는 양자 택일의
제약. 하지만 이 선언대로라면 둘다 nil이 될 수 있고 둘다 nil이 안될 수도 있음
설마 그런일이 있을리가 ㅋ
이런 코드를 수억수천번 

써왔으므로 견딜 수 있다
메세지 뷰의 진입 지점은 겨우 2곳

(기존 스레드와 새창)이니 견딜 수 있다
는 그럴리가 없음
뷰컨트롤러의 시작 지점은 통제가 어려움
- (void)application:(UIApplication *)application didReceiveRemoteNotification:
(NSDictionary *)userInfo fetchCompletionHandler:(void (^)
(UIBackgroundFetchResult))completionHandler {
//메세지 노티피케이션을 탭하면 메세지를 보여주어야 함
}
- (void)handleDeepLinkURL:(NSURL *)url {
//딥링크에서 메세지 보여주기
}
이 코드를 내가 만든다는 보장도 없음
현실
AppDelegate에만 Message를 여는 곳이 4곳
그리고

아이패드가 출동하면?
iPad
iPad Message
• 빈 메세지 뷰를 보여주어야 함

• UISplitViewController를 처음 열때

• 혹은 메세지를 아카이브 했을 때
MessageViewController
진짜 둘다 nil
class MessageViewController: UIViewController {
var thread: MessageThread? //스레드가 있을 때 (기존 대화창)
var targetUser: MessageMember? //스레드가 없는데 새 상대랑 대화할 때
둘다 nil인 상황이 현실이 됨
이제 둘다 nil일 때도 처리해서 빈 화면을 보여주어야 함
참을 수 없다

리팩토링
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? 타입의 네임드튜플을 가지는 케이스
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)
}
}
MessageViewController
생성시
let vc = MessageViewController(mode: .Thread(thread)) //기존 스레드를 보여주는 모드
let vc = MessageViewController(mode: .User(member)) //새 사용자와 대화를 시작하는 모드
self.splitViewController?.showDetailViewController(

UINavigationController(rootViewController: MessageViewController(mode: .Empty))
//비어있는 모드
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
}
MessageViewController
제약 조건 올킬
•메세지 뷰는 3가지 모드: 스레드 모드, 유저 모드, 비어있는 모드가 있다 (enum)

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

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

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

•비어있는 모드는 스레드 객체와 멤버 객체 둘 다 있을 수 없다
MessageViewController
하나 더: 왜 messageViewMode가 var일까?
class MessageViewController: UIViewController {
var messageViewMode: MessageViewMode //만약 이 뷰컨트롤러의 로직상 메세지 모드가 바뀔 수 있다면 var가 적합
class MessageViewController: UIViewController {
let messageViewMode: MessageViewMode //바뀔 수 없다는 걸 강제하고 싶다면 let
MessageViewController
Objective-C
typedef 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의 모드와 객체 변수와의 상관관계를 표현할 수 없다
MessageViewController
Objective-C
enum을 쓴다 해도 Thread, User와의 연관성은 표현할 수 없음

생성자를 강제 해도 내부적으로 Thread모드에 TargetUser 객체 할당을 막을 방법은 없다
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
정리
개발자(김영후씨)의 의식 변화

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

•아 새 대화도 해야하네

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

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

•아 죽겠네

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

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

스위프트는 좋은 언어입니다
옵셔널
옵셔널
다른 머글 언어의 레퍼런스 타입은 기본이 옵셔널
1. 스위프트는 반대로 기본 타입이 nil이 불가능하며 옵셔널 변수 선언을 ?나 !로 명시한다
2. !로 명시하면 옵셔널을 옵셔널이 아닌거 같이 쓰겠다는 만용
(저도 만용 많이 부립니다...)
옵셔널
실제 코드
class SettingsHeaderView: UIView {
let logoImageView: UIImageView
let storeLabel: UILabel
let nameLabel: UILabel
let logoSize = 50.0
• 옵셔널이 아닌 변수가 의외로 많음
옵셔널
옵셔널을 안쓰는 뷰 코드 템플릿
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
}
}
옵셔널
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를 따라가면 알 수 있음
옵셔널
class MerchantProductDetail: JSONModelProtocol {
var productId: String!
이렇게 암시적 선언을 하면 productId를 옵셔널이 아닌것 처럼 쓰기 때문에 

ProductDetail 라이브러리 코드의 사용자가 그 정보를 놓칠 수 있음

productId가 당연히 있다고 가정한 코드를 쓰다가 크래시!
옵셔널
class ListViewController: UIViewController {
var headerView: UIView!
var tableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
//headerView 초기화
//tableView 초기화
}
func anyFunction() {
tableView.reloadData() //옵셔널이 아닌것 처럼 사용
}
}
사실상 viewDidLoad부터 뷰 컨트롤러가 종료
될 때까지 nil이 아니라고 보증할 수 있기 때문
에 많이 사용되는 패턴
보증할 수 있다고?
옵셔널
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보다 먼저 호출됨
!를 쓸 때 자신을 믿지 말자
옵셔널
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
}
}
이뮤터블 Immutability
무조건 / 생각없이 let부터 쓰는 습관
class ActivityTabView: UIView {
let activityButton: UIButton
let notificationButton: UIButton
let separator: UIView
•스위프트의 let은 객체의 프로퍼티 값이 변하는지에 관심이 없다

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

•즉 밸류 타입이 아닌 레퍼런스 타입은 대부분 let이 가능
이뮤터블 Immutability
var가 보인다면
class MerchantCustomerDetail: MerchantCustomer {
let email: String
let isVip: Bool
let completedOrder: Int
let processingOrder: Int
let note: String
var orders:[MerchantOrder] = []
•var가 보인다면 이 타입이 밸류 타입이면서 값이 변하거나

•레퍼런스 타입이며 레퍼런스가 바뀐다는 아주 강력한 신호
옵셔널과 이뮤터블
•현란한 테크닉 없이 이 둘만 꼼꼼히 챙겨도 상대적으로 좋은 코드 구현이 가능

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

•스위프트 2.0부턴 Xcode가 let을 더 잘 챙겨줌
옵셔널 판단은 베팅
class FCMessageMember {
var memberId: NSNumber
init(dict: NSDictionary) {
memberId = dict.objectForKey("id") as! NSNumber //JSON에 id가 없다면 크래시
•옵셔널을 잘 못 판단하면 크래시가 날 수 있다

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

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

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

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

•그동안 얼마나 자주 문제를 이해하지 못하고 그저 null 체크만 추가했던가?
모든 변수를 옵셔널(?, !)로 선언하면

베팅을 전혀 안한 것

스위프트를 안 쓰는 것과 다름 없다
Guard
옵셔널을 이용하는 방법
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)
}
마지막 패턴은 충분히 강력하고 좋다 하지만...
옵셔널을 이용하는 방법
if tableView.tableFooterView == nil || loggedInUser == nil {
//역으로 실패하는 조건문
return
}
setupFooterInterface(tableView.tableFooterView!, forUser: loggedInUser!)
•예외가 아닌 정상적인 흐름이 if let { 블록에 의해 브랜치 됨
•이렇게 할 경우 정상적인 흐름을 메인 브랜치에 둘 수 있지만 단점은 명백

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

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

•스위프트가 아닌 머글 언어에서 흔히 보는 코드 ...
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 { 실패 경우 처리 }
Pattern Matching
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절로 패턴을 벗어난 모든 조건 검사
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하게 처리함
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 케이스가 있을 경우 컴파일 경고
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
Error Handling
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)
}
NSException
•Objective-C에 있는 다른 언어의 예외 처리와 같은 @try { } @catch{ }

•복원 불가능한 에러, valueForKey:
@try {
[invocation invokeWithTarget:o];
}
@catch (NSException *exception) {
LogError(@"Exception during invocation: %@", exception);
}
•이것을 NSError 대신 못 쓸 이유는 없지만 프레임워크가 그럴 수 있도록 다양한 예외를 만
들어주지 않음

•NSFileSavingException 같은 예외를 만들어 주지 않고 NSError를 사용
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
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 { }로 퉁칠 수 없다!
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을 사용할 것
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
}
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)")
}
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 등)이 마지막에 무조건 호출 됨

•에러 처리를 떠나 보편적인 응용 가능성이 높음
새로 스위프트 프로젝트를 시작한다면...
•Fancy Seller

•AFNetworking 대신 Alamofire

•JSON처리에 NSDictionary대신 SwiftyJSON

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

•Guard와 ErrorType을 이용해서 예외처리를 좀 더 제대로 설계 했을 것
Q&A
Thank You
옵셔널과 flatMap
Optional
map
let 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라고 부름
Optional
Optional
var 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?
...
}
즉 옵셔널을 하나의 값이 있거나 없는

박스와 같은 자료구조로 볼 수 있다
flatMap
숫자값을 가지고 있는 String을 가격 포매팅을 해보자
근데 ,가 아닌 .로 바꿔보자 

“5000” -> Some(“5.000”)

“ABC” -> nil
flatMap
숫자값을 가지고 있는 String을 가격 포매팅을 해보자
•문자를 숫자로 변환(NSNumberFormatter의 numberFromString) 

= String -> NSNumber?

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

= NSNumber -> String?

•다시 문자를 문자로 변환 

= String -> String?
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?
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이라고 부르는 이유
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?
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
}
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 ...
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
flatMap
•flatMap은 제네릭 패턴 (if let은 옵셔널에 한정): 어레이도 flatMap이 있음

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

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

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

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

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

•더 공부해 봅시다
Q&A (진짜)
Thank You (진짜)

Contenu connexe

Tendances

코드 생성을 사용해 개발 속도 높이기 NDC2011
코드 생성을 사용해 개발 속도 높이기 NDC2011코드 생성을 사용해 개발 속도 높이기 NDC2011
코드 생성을 사용해 개발 속도 높이기 NDC2011
Esun Kim
 
[C5]deview 2012 nodejs
[C5]deview 2012 nodejs[C5]deview 2012 nodejs
[C5]deview 2012 nodejs
NAVER D2
 

Tendances (19)

[NEXT] Android 개발 경험 프로젝트 3일차 (Database)
 [NEXT] Android 개발 경험 프로젝트 3일차 (Database) [NEXT] Android 개발 경험 프로젝트 3일차 (Database)
[NEXT] Android 개발 경험 프로젝트 3일차 (Database)
 
안드로이드 개발자를 위한 스위프트
안드로이드 개발자를 위한 스위프트안드로이드 개발자를 위한 스위프트
안드로이드 개발자를 위한 스위프트
 
ECMAScript 6의 새로운 것들!
ECMAScript 6의 새로운 것들!ECMAScript 6의 새로운 것들!
ECMAScript 6의 새로운 것들!
 
URQA 삼성 컨퍼런스 발표
URQA 삼성 컨퍼런스 발표 URQA 삼성 컨퍼런스 발표
URQA 삼성 컨퍼런스 발표
 
[D2 오픈세미나]5.robolectric 안드로이드 테스팅
[D2 오픈세미나]5.robolectric 안드로이드 테스팅[D2 오픈세미나]5.robolectric 안드로이드 테스팅
[D2 오픈세미나]5.robolectric 안드로이드 테스팅
 
Spring 교육 자료
Spring 교육 자료Spring 교육 자료
Spring 교육 자료
 
ReactJS | 서버와 클라이어트에서 동시에 사용하는
ReactJS | 서버와 클라이어트에서 동시에 사용하는ReactJS | 서버와 클라이어트에서 동시에 사용하는
ReactJS | 서버와 클라이어트에서 동시에 사용하는
 
[NEXT] Andorid에 MVC 패턴 적용하기
[NEXT] Andorid에 MVC 패턴 적용하기[NEXT] Andorid에 MVC 패턴 적용하기
[NEXT] Andorid에 MVC 패턴 적용하기
 
반복적인 코드 작업 자동화, Codebone으로 손쉽게
반복적인 코드 작업 자동화, Codebone으로 손쉽게반복적인 코드 작업 자동화, Codebone으로 손쉽게
반복적인 코드 작업 자동화, Codebone으로 손쉽게
 
[114]angularvs react 김훈민손찬욱
[114]angularvs react 김훈민손찬욱[114]angularvs react 김훈민손찬욱
[114]angularvs react 김훈민손찬욱
 
Spring boot DI
Spring boot DISpring boot DI
Spring boot DI
 
Django를 Django답게, Django로 뉴스 사이트 만들기
Django를 Django답게, Django로 뉴스 사이트 만들기Django를 Django답게, Django로 뉴스 사이트 만들기
Django를 Django답게, Django로 뉴스 사이트 만들기
 
Ryan Dahl의 Node.js 소개 동영상 해설 by doortts
Ryan Dahl의 Node.js 소개 동영상 해설 by doorttsRyan Dahl의 Node.js 소개 동영상 해설 by doortts
Ryan Dahl의 Node.js 소개 동영상 해설 by doortts
 
NDC 2017 하재승 NEXON ZERO (넥슨 제로) 점검없이 실시간으로 코드 수정 및 게임 정보 수집하기
NDC 2017 하재승 NEXON ZERO (넥슨 제로) 점검없이 실시간으로 코드 수정 및 게임 정보 수집하기NDC 2017 하재승 NEXON ZERO (넥슨 제로) 점검없이 실시간으로 코드 수정 및 게임 정보 수집하기
NDC 2017 하재승 NEXON ZERO (넥슨 제로) 점검없이 실시간으로 코드 수정 및 게임 정보 수집하기
 
코드 생성을 사용해 개발 속도 높이기 NDC2011
코드 생성을 사용해 개발 속도 높이기 NDC2011코드 생성을 사용해 개발 속도 높이기 NDC2011
코드 생성을 사용해 개발 속도 높이기 NDC2011
 
[C5]deview 2012 nodejs
[C5]deview 2012 nodejs[C5]deview 2012 nodejs
[C5]deview 2012 nodejs
 
유지보수 가능한 개발 원칙
유지보수 가능한 개발 원칙유지보수 가능한 개발 원칙
유지보수 가능한 개발 원칙
 
아꿈사 발표 Node JS 프로그래밍 8장
아꿈사 발표 Node JS 프로그래밍 8장아꿈사 발표 Node JS 프로그래밍 8장
아꿈사 발표 Node JS 프로그래밍 8장
 
Front end dev 2016 & beyond
Front end dev 2016 & beyondFront end dev 2016 & beyond
Front end dev 2016 & beyond
 

En vedette

En vedette (20)

[132] rust
[132] rust[132] rust
[132] rust
 
[121]네이버 효과툰 구현 이야기
[121]네이버 효과툰 구현 이야기[121]네이버 효과툰 구현 이야기
[121]네이버 효과툰 구현 이야기
 
[111] 네이버효과툰어떻게만들어졌나
[111] 네이버효과툰어떻게만들어졌나[111] 네이버효과툰어떻게만들어졌나
[111] 네이버효과툰어떻게만들어졌나
 
[113] lessons from realm
[113] lessons from realm[113] lessons from realm
[113] lessons from realm
 
[131] packetbeat과 elasticsearch
[131] packetbeat과 elasticsearch[131] packetbeat과 elasticsearch
[131] packetbeat과 elasticsearch
 
[142] how riot works
[142] how riot works[142] how riot works
[142] how riot works
 
[161] 데이터사이언스팀 빌딩
[161] 데이터사이언스팀 빌딩[161] 데이터사이언스팀 빌딩
[161] 데이터사이언스팀 빌딩
 
[153] apache reef
[153] apache reef[153] apache reef
[153] apache reef
 
[133] 브라우저는 vsync를 어떻게 활용하고 있을까
[133] 브라우저는 vsync를 어떻게 활용하고 있을까[133] 브라우저는 vsync를 어떻게 활용하고 있을까
[133] 브라우저는 vsync를 어떻게 활용하고 있을까
 
[162] jpa와 모던 자바 데이터 저장 기술
[162] jpa와 모던 자바 데이터 저장 기술[162] jpa와 모던 자바 데이터 저장 기술
[162] jpa와 모던 자바 데이터 저장 기술
 
[141] react everywhere
[141] react everywhere[141] react everywhere
[141] react everywhere
 
[152] 웹브라우저 감옥에서 살아남기
[152] 웹브라우저 감옥에서 살아남기[152] 웹브라우저 감옥에서 살아남기
[152] 웹브라우저 감옥에서 살아남기
 
[114] DRC hubo technical review
[114] DRC hubo technical review[114] DRC hubo technical review
[114] DRC hubo technical review
 
[124] mit cheetah 로봇의 탄생
[124] mit cheetah 로봇의 탄생[124] mit cheetah 로봇의 탄생
[124] mit cheetah 로봇의 탄생
 
[164] pinpoint
[164] pinpoint[164] pinpoint
[164] pinpoint
 
[122] line on apple watch
[122] line on apple watch[122] line on apple watch
[122] line on apple watch
 
[154] 데이터 센터의 오픈 소스 open compute project (ocp)
[154] 데이터 센터의 오픈 소스 open compute project (ocp)[154] 데이터 센터의 오픈 소스 open compute project (ocp)
[154] 데이터 센터의 오픈 소스 open compute project (ocp)
 
[143] 모바일 혈액진단기기 개발 삽질기
[143] 모바일 혈액진단기기 개발 삽질기[143] 모바일 혈액진단기기 개발 삽질기
[143] 모바일 혈액진단기기 개발 삽질기
 
[144]mobile앱에서 효율적인 storage 접근 방법
[144]mobile앱에서 효율적인 storage 접근 방법[144]mobile앱에서 효율적인 storage 접근 방법
[144]mobile앱에서 효율적인 storage 접근 방법
 
[134] immersive sound vr
[134] immersive sound vr[134] immersive sound vr
[134] immersive sound vr
 

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

메타웍스3 워크숍 - 개념소개 및 예제, 그리고 간단한 API문서포함
메타웍스3 워크숍 - 개념소개 및 예제, 그리고 간단한 API문서포함 메타웍스3 워크숍 - 개념소개 및 예제, 그리고 간단한 API문서포함
메타웍스3 워크숍 - 개념소개 및 예제, 그리고 간단한 API문서포함
uEngine Solutions
 
Effective c++(chapter 5,6)
Effective c++(chapter 5,6)Effective c++(chapter 5,6)
Effective c++(chapter 5,6)
문익 장
 
20 handler and_async_task
20 handler and_async_task20 handler and_async_task
20 handler and_async_task
운용 최
 
Modern C++ 프로그래머를 위한 CPP11/14 핵심
Modern C++ 프로그래머를 위한 CPP11/14 핵심Modern C++ 프로그래머를 위한 CPP11/14 핵심
Modern C++ 프로그래머를 위한 CPP11/14 핵심
흥배 최
 

Similaire à [112] 실전 스위프트 프로그래밍 (20)

HOONS닷넷 오픈소스 프로젝트 Part1.
HOONS닷넷 오픈소스 프로젝트 Part1.HOONS닷넷 오픈소스 프로젝트 Part1.
HOONS닷넷 오픈소스 프로젝트 Part1.
 
[스프링 스터디 3일차] @MVC
[스프링 스터디 3일차] @MVC[스프링 스터디 3일차] @MVC
[스프링 스터디 3일차] @MVC
 
[1B1]스위프트프로그래밍언어
[1B1]스위프트프로그래밍언어[1B1]스위프트프로그래밍언어
[1B1]스위프트프로그래밍언어
 
Protocol Oriented Programming in Swift
Protocol Oriented Programming in SwiftProtocol Oriented Programming in Swift
Protocol Oriented Programming in Swift
 
Data-binding AngularJS
Data-binding AngularJSData-binding AngularJS
Data-binding AngularJS
 
react-ko.pdf
react-ko.pdfreact-ko.pdf
react-ko.pdf
 
Effective c++chapter4
Effective c++chapter4Effective c++chapter4
Effective c++chapter4
 
막하는 스터디 네 번째 만남 AngularJs (20151108)
막하는 스터디 네 번째 만남 AngularJs (20151108)막하는 스터디 네 번째 만남 AngularJs (20151108)
막하는 스터디 네 번째 만남 AngularJs (20151108)
 
adios 2021 oct 유현식 발표자료
adios 2021 oct 유현식 발표자료adios 2021 oct 유현식 발표자료
adios 2021 oct 유현식 발표자료
 
Angular js 의존관계 주입과 서비스
Angular js 의존관계 주입과 서비스Angular js 의존관계 주입과 서비스
Angular js 의존관계 주입과 서비스
 
Working Effectively With Legacy Code - xp2005
Working Effectively With Legacy Code - xp2005Working Effectively With Legacy Code - xp2005
Working Effectively With Legacy Code - xp2005
 
Vue guide v0.1
Vue guide v0.1Vue guide v0.1
Vue guide v0.1
 
메타웍스3 워크숍 - 개념소개 및 예제, 그리고 간단한 API문서포함
메타웍스3 워크숍 - 개념소개 및 예제, 그리고 간단한 API문서포함 메타웍스3 워크숍 - 개념소개 및 예제, 그리고 간단한 API문서포함
메타웍스3 워크숍 - 개념소개 및 예제, 그리고 간단한 API문서포함
 
Effective c++(chapter 5,6)
Effective c++(chapter 5,6)Effective c++(chapter 5,6)
Effective c++(chapter 5,6)
 
20 handler and_async_task
20 handler and_async_task20 handler and_async_task
20 handler and_async_task
 
01 에러핸들링(함송연)
01 에러핸들링(함송연)01 에러핸들링(함송연)
01 에러핸들링(함송연)
 
8.hooks
8.hooks8.hooks
8.hooks
 
Modern C++ 프로그래머를 위한 CPP11/14 핵심
Modern C++ 프로그래머를 위한 CPP11/14 핵심Modern C++ 프로그래머를 위한 CPP11/14 핵심
Modern C++ 프로그래머를 위한 CPP11/14 핵심
 
Angular 2 rc5 조사
Angular 2 rc5 조사Angular 2 rc5 조사
Angular 2 rc5 조사
 
Sonarqube 20160509
Sonarqube 20160509Sonarqube 20160509
Sonarqube 20160509
 

Plus de NAVER D2

Plus de NAVER D2 (20)

[211] 인공지능이 인공지능 챗봇을 만든다
[211] 인공지능이 인공지능 챗봇을 만든다[211] 인공지능이 인공지능 챗봇을 만든다
[211] 인공지능이 인공지능 챗봇을 만든다
 
[233] 대형 컨테이너 클러스터에서의 고가용성 Network Load Balancing: Maglev Hashing Scheduler i...
[233] 대형 컨테이너 클러스터에서의 고가용성 Network Load Balancing: Maglev Hashing Scheduler i...[233] 대형 컨테이너 클러스터에서의 고가용성 Network Load Balancing: Maglev Hashing Scheduler i...
[233] 대형 컨테이너 클러스터에서의 고가용성 Network Load Balancing: Maglev Hashing Scheduler i...
 
[215] Druid로 쉽고 빠르게 데이터 분석하기
[215] Druid로 쉽고 빠르게 데이터 분석하기[215] Druid로 쉽고 빠르게 데이터 분석하기
[215] Druid로 쉽고 빠르게 데이터 분석하기
 
[245]Papago Internals: 모델분석과 응용기술 개발
[245]Papago Internals: 모델분석과 응용기술 개발[245]Papago Internals: 모델분석과 응용기술 개발
[245]Papago Internals: 모델분석과 응용기술 개발
 
[236] 스트림 저장소 최적화 이야기: 아파치 드루이드로부터 얻은 교훈
[236] 스트림 저장소 최적화 이야기: 아파치 드루이드로부터 얻은 교훈[236] 스트림 저장소 최적화 이야기: 아파치 드루이드로부터 얻은 교훈
[236] 스트림 저장소 최적화 이야기: 아파치 드루이드로부터 얻은 교훈
 
[235]Wikipedia-scale Q&A
[235]Wikipedia-scale Q&A[235]Wikipedia-scale Q&A
[235]Wikipedia-scale Q&A
 
[244]로봇이 현실 세계에 대해 학습하도록 만들기
[244]로봇이 현실 세계에 대해 학습하도록 만들기[244]로봇이 현실 세계에 대해 학습하도록 만들기
[244]로봇이 현실 세계에 대해 학습하도록 만들기
 
[243] Deep Learning to help student’s Deep Learning
[243] Deep Learning to help student’s Deep Learning[243] Deep Learning to help student’s Deep Learning
[243] Deep Learning to help student’s Deep Learning
 
[234]Fast & Accurate Data Annotation Pipeline for AI applications
[234]Fast & Accurate Data Annotation Pipeline for AI applications[234]Fast & Accurate Data Annotation Pipeline for AI applications
[234]Fast & Accurate Data Annotation Pipeline for AI applications
 
Old version: [233]대형 컨테이너 클러스터에서의 고가용성 Network Load Balancing
Old version: [233]대형 컨테이너 클러스터에서의 고가용성 Network Load BalancingOld version: [233]대형 컨테이너 클러스터에서의 고가용성 Network Load Balancing
Old version: [233]대형 컨테이너 클러스터에서의 고가용성 Network Load Balancing
 
[226]NAVER 광고 deep click prediction: 모델링부터 서빙까지
[226]NAVER 광고 deep click prediction: 모델링부터 서빙까지[226]NAVER 광고 deep click prediction: 모델링부터 서빙까지
[226]NAVER 광고 deep click prediction: 모델링부터 서빙까지
 
[225]NSML: 머신러닝 플랫폼 서비스하기 & 모델 튜닝 자동화하기
[225]NSML: 머신러닝 플랫폼 서비스하기 & 모델 튜닝 자동화하기[225]NSML: 머신러닝 플랫폼 서비스하기 & 모델 튜닝 자동화하기
[225]NSML: 머신러닝 플랫폼 서비스하기 & 모델 튜닝 자동화하기
 
[224]네이버 검색과 개인화
[224]네이버 검색과 개인화[224]네이버 검색과 개인화
[224]네이버 검색과 개인화
 
[216]Search Reliability Engineering (부제: 지진에도 흔들리지 않는 네이버 검색시스템)
[216]Search Reliability Engineering (부제: 지진에도 흔들리지 않는 네이버 검색시스템)[216]Search Reliability Engineering (부제: 지진에도 흔들리지 않는 네이버 검색시스템)
[216]Search Reliability Engineering (부제: 지진에도 흔들리지 않는 네이버 검색시스템)
 
[214] Ai Serving Platform: 하루 수 억 건의 인퍼런스를 처리하기 위한 고군분투기
[214] Ai Serving Platform: 하루 수 억 건의 인퍼런스를 처리하기 위한 고군분투기[214] Ai Serving Platform: 하루 수 억 건의 인퍼런스를 처리하기 위한 고군분투기
[214] Ai Serving Platform: 하루 수 억 건의 인퍼런스를 처리하기 위한 고군분투기
 
[213] Fashion Visual Search
[213] Fashion Visual Search[213] Fashion Visual Search
[213] Fashion Visual Search
 
[232] TensorRT를 활용한 딥러닝 Inference 최적화
[232] TensorRT를 활용한 딥러닝 Inference 최적화[232] TensorRT를 활용한 딥러닝 Inference 최적화
[232] TensorRT를 활용한 딥러닝 Inference 최적화
 
[242]컴퓨터 비전을 이용한 실내 지도 자동 업데이트 방법: 딥러닝을 통한 POI 변화 탐지
[242]컴퓨터 비전을 이용한 실내 지도 자동 업데이트 방법: 딥러닝을 통한 POI 변화 탐지[242]컴퓨터 비전을 이용한 실내 지도 자동 업데이트 방법: 딥러닝을 통한 POI 변화 탐지
[242]컴퓨터 비전을 이용한 실내 지도 자동 업데이트 방법: 딥러닝을 통한 POI 변화 탐지
 
[212]C3, 데이터 처리에서 서빙까지 가능한 하둡 클러스터
[212]C3, 데이터 처리에서 서빙까지 가능한 하둡 클러스터[212]C3, 데이터 처리에서 서빙까지 가능한 하둡 클러스터
[212]C3, 데이터 처리에서 서빙까지 가능한 하둡 클러스터
 
[223]기계독해 QA: 검색인가, NLP인가?
[223]기계독해 QA: 검색인가, NLP인가?[223]기계독해 QA: 검색인가, NLP인가?
[223]기계독해 QA: 검색인가, NLP인가?
 

Dernier

Grid Layout (Kitworks Team Study 장현정 발표자료)
Grid Layout (Kitworks Team Study 장현정 발표자료)Grid Layout (Kitworks Team Study 장현정 발표자료)
Grid Layout (Kitworks Team Study 장현정 발표자료)
Wonjun Hwang
 

Dernier (7)

A future that integrates LLMs and LAMs (Symposium)
A future that integrates LLMs and LAMs (Symposium)A future that integrates LLMs and LAMs (Symposium)
A future that integrates LLMs and LAMs (Symposium)
 
캐드앤그래픽스 2024년 5월호 목차
캐드앤그래픽스 2024년 5월호 목차캐드앤그래픽스 2024년 5월호 목차
캐드앤그래픽스 2024년 5월호 목차
 
Grid Layout (Kitworks Team Study 장현정 발표자료)
Grid Layout (Kitworks Team Study 장현정 발표자료)Grid Layout (Kitworks Team Study 장현정 발표자료)
Grid Layout (Kitworks Team Study 장현정 발표자료)
 
도심 하늘에서 시속 200km로 비행할 수 있는 미래 항공 모빌리티 'S-A2'
도심 하늘에서 시속 200km로 비행할 수 있는 미래 항공 모빌리티 'S-A2'도심 하늘에서 시속 200km로 비행할 수 있는 미래 항공 모빌리티 'S-A2'
도심 하늘에서 시속 200km로 비행할 수 있는 미래 항공 모빌리티 'S-A2'
 
[Terra] Terra Money: Stability and Adoption
[Terra] Terra Money: Stability and Adoption[Terra] Terra Money: Stability and Adoption
[Terra] Terra Money: Stability and Adoption
 
MOODv2 : Masked Image Modeling for Out-of-Distribution Detection
MOODv2 : Masked Image Modeling for Out-of-Distribution DetectionMOODv2 : Masked Image Modeling for Out-of-Distribution Detection
MOODv2 : Masked Image Modeling for Out-of-Distribution Detection
 
Continual Active Learning for Efficient Adaptation of Machine LearningModels ...
Continual Active Learning for Efficient Adaptation of Machine LearningModels ...Continual Active Learning for Efficient Adaptation of Machine LearningModels ...
Continual Active Learning for Efficient Adaptation of Machine LearningModels ...
 

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

  • 7. ApplePay WWDC 2015 Session 702, “Apple Pay Within Apps”
  • 8. 목차 •최근에 팬시앱에 들어간 메세징 구현을 따라가며 스위프트 기능 살펴보기 •옵셔널 •이뮤터블 •Guard •Error Handling
  • 9. 세션 소개 • 스위프트를 가볍게 소개해주는 세션은 아니지만 iOS 개발자가 매일 작성하는 뷰/모델/셀/ 뷰컨트롤러단의 코드를 예로 들어 좋은 습관과 안 좋았던 습관, 어느정도 옵셔널을 써야하 는 가에 대한 고민, 코드를 좋게 만들기 위해 적용해 나갈 수 있는 기능들에 대한 소개와 모 나드에 대한 이해와 적용 그리고 부분적으로 스위프트 2.0에서 달라지는 예외처리나 패턴 매칭과 같은 기능도 다루려고 합니다 •모나드(flatMap) 제외. 죄송합니다
  • 10. 메세지 기능 •FANCY 앱에 내장된 메세징 (고객과 셀러간의 커뮤니케이션) •스티커, # 검색 같은거 없는 그냥 채팅
  • 11. 대화목록 ThreadViewController MessageThread의 목록을 보여줌 class MessageThread { var threadId: NSNumber var unreadCount: Int init(dict: NSDictionary) { ... } }
  • 12. 대화창 MessageViewController 메세지뷰는 스레드를 이용하여 메세지를 불러온다 class MessageViewController: UIViewController { var thread: MessageThread! override func viewDidLoad() { //스레드의 메세지들을 멋지게 보여준다 thread를 “Implicitly Unwrapped Optional”로 선언 옵셔널이지만 옵셔널이 아닌것 처럼 사용 (다른 머글 언어의 레퍼런스 타입과 동일)
  • 13. 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를 전달 안하면 어떻게 되지? 크래시!
  • 15. 그런데 기존의 스레드를 보여주는 것 말고
 새로운 대화를 시작하려면 Thread가 없다
  • 16. 그럴땐 서버 개발자를 협박해서 
 대화를 시작할 때 Thread를 얻어온다
  • 18. MessageViewController 새 대화: 기존 스레드가 없을 경우 그럼 targetUser 변수를 추가한다! class MessageViewController: UIViewController { var thread: MessageThread! //스레드가 있을 때 (기존 대화창) var targetUser: MessageMember! //스레드가 없는데 새 상대랑 대화할 때 override func viewDidLoad() { //스레드의 메세지들을 멋지게 보여준다
  • 20. 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) }
  • 21. MessageViewController 상호 배타적인 두 변수가 ! targetUser나 thread 둘 중 하나만 값이 있고 나머지는 nil이므로 그냥 옵셔널로 바꾸자 class MessageViewController: UIViewController { var thread: MessageThread? //스레드가 있을 때 (기존 대화창) var targetUser: MessageMember? //스레드가 없는데 새 상대랑 대화할 때 MessageViewController는 둘 중 하나만 있는 상황에 적절하게(?) 춤을 춘다
  • 22. MessageViewController 옵셔널 댄스 춤의 예 override func viewDidLoad() { // 뷰 타이틀 (스레드의 제목 혹은 사용자 이름) title = thread?.title ?? (targetUser?.fullname ?? targetUser.username) 이런 코드가 도처에 난무할 것이다. 전송 때도 사용자ID 혹은 스레드ID를 선택적으로 보냄 if let t = thread { } , if let user = targetUser { }
  • 23. MessageViewController 변수 선언 리뷰 class MessageViewController: UIViewController { var thread: MessageThread? //스레드가 있을 때 (기존 대화창) var targetUser: MessageMember? //스레드가 없는데 새 상대랑 대화할 때 이 선언은 맞는 것 같지만 우리가 원하는건 thread가 있거나 targetUser가 있는 양자 택일의 제약. 하지만 이 선언대로라면 둘다 nil이 될 수 있고 둘다 nil이 안될 수도 있음
  • 25. 이런 코드를 수억수천번 
 써왔으므로 견딜 수 있다
  • 26. 메세지 뷰의 진입 지점은 겨우 2곳
 (기존 스레드와 새창)이니 견딜 수 있다
  • 27. 는 그럴리가 없음 뷰컨트롤러의 시작 지점은 통제가 어려움 - (void)application:(UIApplication *)application didReceiveRemoteNotification: (NSDictionary *)userInfo fetchCompletionHandler:(void (^) (UIBackgroundFetchResult))completionHandler { //메세지 노티피케이션을 탭하면 메세지를 보여주어야 함 } - (void)handleDeepLinkURL:(NSURL *)url { //딥링크에서 메세지 보여주기 } 이 코드를 내가 만든다는 보장도 없음
  • 30. iPad iPad Message • 빈 메세지 뷰를 보여주어야 함 • UISplitViewController를 처음 열때 • 혹은 메세지를 아카이브 했을 때
  • 31. MessageViewController 진짜 둘다 nil class MessageViewController: UIViewController { var thread: MessageThread? //스레드가 있을 때 (기존 대화창) var targetUser: MessageMember? //스레드가 없는데 새 상대랑 대화할 때 둘다 nil인 상황이 현실이 됨 이제 둘다 nil일 때도 처리해서 빈 화면을 보여주어야 함
  • 33. 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? 타입의 네임드튜플을 가지는 케이스
  • 34. 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) } }
  • 35. MessageViewController 생성시 let vc = MessageViewController(mode: .Thread(thread)) //기존 스레드를 보여주는 모드 let vc = MessageViewController(mode: .User(member)) //새 사용자와 대화를 시작하는 모드 self.splitViewController?.showDetailViewController(
 UINavigationController(rootViewController: MessageViewController(mode: .Empty)) //비어있는 모드
  • 36. 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 }
  • 37. MessageViewController 제약 조건 올킬 •메세지 뷰는 3가지 모드: 스레드 모드, 유저 모드, 비어있는 모드가 있다 (enum) •이 3가지 모드 중 한 가지는 꼭 있어야 한다 (Non-optional과 init 오버라이딩) •스레드 모드는 꼭 스레드 객체가 있어야 하며 다른 객체는 있을 수 없다 •유저 모드는 꼭 멤버 객체가 있어야 하며 다른 객체는 있을 수 없다 •비어있는 모드는 스레드 객체와 멤버 객체 둘 다 있을 수 없다
  • 38. MessageViewController 하나 더: 왜 messageViewMode가 var일까? class MessageViewController: UIViewController { var messageViewMode: MessageViewMode //만약 이 뷰컨트롤러의 로직상 메세지 모드가 바뀔 수 있다면 var가 적합 class MessageViewController: UIViewController { let messageViewMode: MessageViewMode //바뀔 수 없다는 걸 강제하고 싶다면 let
  • 39. MessageViewController Objective-C typedef 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의 모드와 객체 변수와의 상관관계를 표현할 수 없다
  • 40. MessageViewController Objective-C enum을 쓴다 해도 Thread, User와의 연관성은 표현할 수 없음 생성자를 강제 해도 내부적으로 Thread모드에 TargetUser 객체 할당을 막을 방법은 없다
  • 41. 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
  • 42. 정리 개발자(김영후씨)의 의식 변화 •아 스레드 빨리 보여줘야지 •아 새 대화도 해야하네 •아 아이패드는 빈 화면도 있네 •아 막 여기저기서 메세지를 열어야 하네 •아 죽겠네 개발자의 머릿속에 있는 제약조건을 코드에 얼마나 표현 할 수 있는가? 그 표현을 위해 복잡도가 얼마나 증가하는가? 스위프트는 좋은 언어입니다
  • 44. 옵셔널 다른 머글 언어의 레퍼런스 타입은 기본이 옵셔널 1. 스위프트는 반대로 기본 타입이 nil이 불가능하며 옵셔널 변수 선언을 ?나 !로 명시한다 2. !로 명시하면 옵셔널을 옵셔널이 아닌거 같이 쓰겠다는 만용 (저도 만용 많이 부립니다...)
  • 45. 옵셔널 실제 코드 class SettingsHeaderView: UIView { let logoImageView: UIImageView let storeLabel: UILabel let nameLabel: UILabel let logoSize = 50.0 • 옵셔널이 아닌 변수가 의외로 많음
  • 46. 옵셔널 옵셔널을 안쓰는 뷰 코드 템플릿 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 } }
  • 47. 옵셔널 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를 따라가면 알 수 있음
  • 48. 옵셔널 class MerchantProductDetail: JSONModelProtocol { var productId: String! 이렇게 암시적 선언을 하면 productId를 옵셔널이 아닌것 처럼 쓰기 때문에 
 ProductDetail 라이브러리 코드의 사용자가 그 정보를 놓칠 수 있음 productId가 당연히 있다고 가정한 코드를 쓰다가 크래시!
  • 49. 옵셔널 class ListViewController: UIViewController { var headerView: UIView! var tableView: UITableView! override func viewDidLoad() { super.viewDidLoad() //headerView 초기화 //tableView 초기화 } func anyFunction() { tableView.reloadData() //옵셔널이 아닌것 처럼 사용 } } 사실상 viewDidLoad부터 뷰 컨트롤러가 종료 될 때까지 nil이 아니라고 보증할 수 있기 때문 에 많이 사용되는 패턴
  • 51. 옵셔널 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보다 먼저 호출됨
  • 52. !를 쓸 때 자신을 믿지 말자
  • 53. 옵셔널 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 } }
  • 54. 이뮤터블 Immutability 무조건 / 생각없이 let부터 쓰는 습관 class ActivityTabView: UIView { let activityButton: UIButton let notificationButton: UIButton let separator: UIView •스위프트의 let은 객체의 프로퍼티 값이 변하는지에 관심이 없다 •오직 레퍼런스만 바뀌지 않으면 let •즉 밸류 타입이 아닌 레퍼런스 타입은 대부분 let이 가능
  • 55. 이뮤터블 Immutability var가 보인다면 class MerchantCustomerDetail: MerchantCustomer { let email: String let isVip: Bool let completedOrder: Int let processingOrder: Int let note: String var orders:[MerchantOrder] = [] •var가 보인다면 이 타입이 밸류 타입이면서 값이 변하거나 •레퍼런스 타입이며 레퍼런스가 바뀐다는 아주 강력한 신호
  • 56. 옵셔널과 이뮤터블 •현란한 테크닉 없이 이 둘만 꼼꼼히 챙겨도 상대적으로 좋은 코드 구현이 가능 •코드리딩시 옵셔널과 뮤터블 정보만 챙겨도 구현자의 주의점을 이해할 수 있음 •스위프트 2.0부턴 Xcode가 let을 더 잘 챙겨줌
  • 57. 옵셔널 판단은 베팅 class FCMessageMember { var memberId: NSNumber init(dict: NSDictionary) { memberId = dict.objectForKey("id") as! NSNumber //JSON에 id가 없다면 크래시 •옵셔널을 잘 못 판단하면 크래시가 날 수 있다 •코딩을 할 때 머릿속에서 어떤 가정을 해야하며 그 가정을 표현한다 •하지만 크래시는 피할 수 없다 Software is Hard •그럼에도 옵셔널은 내가 어떤 가정을 했는지 알게 해주고 •잘못된 판단을 내렸을 경우 해당 문제를 확실히 이해하게 해준다 •그동안 얼마나 자주 문제를 이해하지 못하고 그저 null 체크만 추가했던가?
  • 58. 모든 변수를 옵셔널(?, !)로 선언하면 베팅을 전혀 안한 것 스위프트를 안 쓰는 것과 다름 없다
  • 59. Guard
  • 60. 옵셔널을 이용하는 방법 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) } 마지막 패턴은 충분히 강력하고 좋다 하지만...
  • 61. 옵셔널을 이용하는 방법 if tableView.tableFooterView == nil || loggedInUser == nil { //역으로 실패하는 조건문 return } setupFooterInterface(tableView.tableFooterView!, forUser: loggedInUser!) •예외가 아닌 정상적인 흐름이 if let { 블록에 의해 브랜치 됨 •이렇게 할 경우 정상적인 흐름을 메인 브랜치에 둘 수 있지만 단점은 명백 •실패하는 조건문을 만들어야 하며 •옵셔널을 검사 하긴 했지만 바인딩 되지 않았으므로 !를 써야함 •스위프트가 아닌 머글 언어에서 흔히 보는 코드 ...
  • 62. 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 { 실패 경우 처리 }
  • 64. 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절로 패턴을 벗어난 모든 조건 검사
  • 65. 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하게 처리함
  • 66. 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 케이스가 있을 경우 컴파일 경고
  • 67. 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
  • 69. 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) }
  • 70. NSException •Objective-C에 있는 다른 언어의 예외 처리와 같은 @try { } @catch{ } •복원 불가능한 에러, valueForKey: @try { [invocation invokeWithTarget:o]; } @catch (NSException *exception) { LogError(@"Exception during invocation: %@", exception); } •이것을 NSError 대신 못 쓸 이유는 없지만 프레임워크가 그럴 수 있도록 다양한 예외를 만 들어주지 않음 •NSFileSavingException 같은 예외를 만들어 주지 않고 NSError를 사용
  • 71. 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
  • 72. 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 { }로 퉁칠 수 없다!
  • 73. 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을 사용할 것
  • 74. 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 }
  • 75. 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)") }
  • 76. 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 등)이 마지막에 무조건 호출 됨 •에러 처리를 떠나 보편적인 응용 가능성이 높음
  • 77. 새로 스위프트 프로젝트를 시작한다면... •Fancy Seller •AFNetworking 대신 Alamofire •JSON처리에 NSDictionary대신 SwiftyJSON •2.0에서 강화된 프로토콜과 제네릭을 이용해서 MVVM 구조를 가지도록 노력해 봤을 것 •Guard와 ErrorType을 이용해서 예외처리를 좀 더 제대로 설계 했을 것
  • 78. Q&A
  • 81. Optional map let 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라고 부름
  • 82. Optional Optional var 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? ... }
  • 83. 즉 옵셔널을 하나의 값이 있거나 없는
 박스와 같은 자료구조로 볼 수 있다
  • 84. flatMap 숫자값을 가지고 있는 String을 가격 포매팅을 해보자 근데 ,가 아닌 .로 바꿔보자 “5000” -> Some(“5.000”) “ABC” -> nil
  • 85. flatMap 숫자값을 가지고 있는 String을 가격 포매팅을 해보자 •문자를 숫자로 변환(NSNumberFormatter의 numberFromString) = String -> NSNumber? •숫자를 가격 문자로 변환 (NSNumberFormatter의 stringFromNumber) = NSNumber -> String? •다시 문자를 문자로 변환 = String -> String?
  • 86. 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?
  • 87. 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이라고 부르는 이유
  • 88. 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?
  • 89. 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 }
  • 90. 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 ...
  • 91. 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
  • 92. flatMap •flatMap은 제네릭 패턴 (if let은 옵셔널에 한정): 어레이도 flatMap이 있음 •타입A를 받고 무언가로 감싼A를 돌려주는 함수 패턴을 결합 가능 •함수를 A->B? 식으로 설계하면 flatMap으로 연계 가능 •flatMap은 모나드 연산자 >>= 이며 flatMap을 지원하는 타입을 모나드 라고 부름 •즉 스위프트의 옵셔널과 어레이는 모나드 •다른 추상 타입도 flatMap 연산을 만들면 모나드 •더 공부해 봅시다