Upload
yuichi-adachi
View
4.014
Download
0
Embed Size (px)
Citation preview
安達 勇一• 2013/12 入社
• TwitterID: @UsrNameu1
• Github: https://github.com/UsrNameu1
• Blog: http://dev.classmethod.jp/author/yad
• で連載やってます
UIKitでのイベントハンドリング
• UIControl, UIBarButtonItem の target, action
navigationItem.leftBarButtonItem = UIBarButtonItem(title: NSLocalizedString("cancel", comment: ""), style: .Bordered, target: self, action: Selector(“cancelButtonDidTapped:”));
UIKitでのイベントハンドリング
• UIControl, UIBarButtonItem の target, action
navigationItem.leftBarButtonItem = UIBarButtonItem(title: NSLocalizedString("cancel", comment: ""), style: .Bordered, target: self, action: Selector(“cancelButtonDidTapped:”));
UIKitでのイベントハンドリング
• UIControl, UIBarButtonItem の target, action
navigationItem.leftBarButtonItem = UIBarButtonItem(title: NSLocalizedString("cancel", comment: ""), style: .Bordered, target: self, action: Selector(“cancelButtonDidTapped:”));
UIKitのAPIのイベントハンドリングでは Selectorが要を握る
SwiftでのSelector
• Selectorの定義
struct Selector : StringLiteralConvertible, NilLiteralConvertible { ...
SwiftでのSelector
• Selectorの定義
struct Selector : StringLiteralConvertible, NilLiteralConvertible { ...
"buttonDidTouchUpInside:"リテラルで宣言可能→
SwiftでのSelector
• Selectorの定義
struct Selector : StringLiteralConvertible, NilLiteralConvertible { ...
"buttonDidTouchUpInside:"
Selectorを指定しない時はnilを入れられる→ nil
リテラルで宣言可能→
• リテラルで宣言可能
navigationItem.leftBarButtonItem = UIBarButtonItem(title: NSLocalizedString("cancel", comment: ""), style: .Bordered, target: self, action: “cancelButtonDidTapped:");
SwiftでのSelector
• Selectorを指定しない時はnilを入れられる
navigationItem.leftBarButtonItem = UIBarButtonItem(title: NSLocalizedString("cancel", comment: ""), style: .Bordered, target: self, action: nil);
SwiftでのSelector
Objective-Cとの比較
• Objective-C
• Swift
[removeButton addTarget:self action:@selector(removeButtonDidTouchUpInside:) forControlEvents:UIControlEventTouchUpInside];
removeButton.addTarget(self, action: Selector("removeButtonDidTouchUpInside:"), forControlEvents: .TouchUpInside)
Objective-Cとの比較
• Objective-C
• Swift
[removeButton addTarget:self action:@selector(removeButtonDidTouchUpInside:) forControlEvents:UIControlEventTouchUpInside];
removeButton.addTarget(self, action: Selector("removeButtonDidTouchUpInside:"), forControlEvents: .TouchUpInside)
⇧Selector名が間違っていたら コンパイラが警告を出してくれる
Objective-Cとの比較
• Objective-C
• Swift
[removeButton addTarget:self action:@selector(removeButtonDidTouchUpInside:) forControlEvents:UIControlEventTouchUpInside];
removeButton.addTarget(self, action: Selector("removeButtonDidTouchUpInside:"), forControlEvents: .TouchUpInside)
⇧Selector名が間違っていたら コンパイラが警告を出してくれる
⇧Selector名が間違っていても コンパイラは警告を出してくれない
Objective-Cとの比較
• Objective-C
• Swift
[removeButton addTarget:self action:@selector(removeButtonDidTouchUpInside:) forControlEvents:UIControlEventTouchUpInside];
removeButton.addTarget(self, action: Selector("removeButtonDidTouchUpInside:"), forControlEvents: .TouchUpInside)
⇧Selector名が間違っていたら コンパイラが警告を出してくれる
⇧Selector名が間違っていても コンパイラは警告を出してくれない
BlocksKit
• BeforeremoveButton.addTarget(self, action: Selector("removeButtonDidTouchUpInside:"), forControlEvents: .TouchUpInside)
⇧Selector名が間違っていても コンパイラは警告を出してくれない
BlocksKit
• Before
• After
removeButton.addTarget(self, action: Selector("removeButtonDidTouchUpInside:"), forControlEvents: .TouchUpInside)
⇧Selector名が間違っていても コンパイラは警告を出してくれない
removeButton.bk_addEventHandler({[weak self] sender in self?.removeButtonDidTouchUpInside(sender) return Void() }, forControlEvents: .TouchUpInside)
⇧ハンドラの中でメソッドを直に呼び出すため、 メソッド名が間違っていたらコンパイラはエラーになる
• weak, unownedで解決
BlocksKit
removeButton.bk_addEventHandler({[weak self] sender in self?.removeButtonDidTouchUpInside(sender) return Void() }, forControlEvents: .TouchUpInside)
• weak, unownedで解決
BlocksKit
removeButton.bk_addEventHandler({[weak self] sender in self?.removeButtonDidTouchUpInside(sender) return Void() }, forControlEvents: .TouchUpInside)
・ weak : selfがなくなった後もアクセスされる場合 ・ unowned : selfがなくなった後にアクセスされない場合
2つのFramework : Quick, Nimble
• QuickはテストのためのBDD流DSLを用意
• Nimbleは実際の値と期待する値を比較するマッチャー
• テストはプロダクトコードに含まれない為、 iOS 8 向けにインストール
2つのFramework : Quick, Nimble
• 病院にかかわる一連のモデル
create
confirmuse
public struct Diagnosis { public let name: String public init(name: String) { self.name = name } }
public protocol Examinable { func examine() -> Diagnosis }
public class Doctor: Examinable { public init() {} public func examine() -> Diagnosis { return Diagnosis(name: "cold") } }
public class Hospital { private let examinable: Examinable public init(examinable: Examinable) { self.examinable = examinable } public func serve() -> Diagnosis { return examinable.examine() } }
2つのFramework : Quick, Nimble
• 診断データ型
public struct Diagnosis { public let name: String public init(name: String) { self.name = name } }
2つのFramework : Quick, Nimble
• 医者クラス
public class Doctor: Examinable { public init() {} public func examine() -> Diagnosis { return Diagnosis(name: "cold") } }
2つのFramework : Quick, Nimble
• 病院クラス
public class Hospital { private let examinable: Examinable public init(examinable: Examinable) { self.examinable = examinable } public func serve() -> Diagnosis { return examinable.examine() } }
2つのFramework : Quick, Nimble
• BDD Example
class HospitalSpecs: QuickSpec { override func spec() { describe("病院の診断について") { context("診察できる人が診断を行うとき") { let doctor = Doctor() let hospital = Hospital(examinable: doctor) it("診察できる人の診断を返すこと。") { expect(hospital.serve().name) == "cold" } } } } }
SwiftでのMock実装例class HospitalSpecs: QuickSpec { // protocol準拠でMockを使う override func spec() { describe("病院の診断について") { context("診察できる人が診断を行うとき") { class MockExaminable: Examinable { override func examine() -> Diagnosis { return Diagnosis(name: "cold") } } let mock = MockExaminable() let hospital = Hospital(examinable: mock) it("診察できる人の診断を返すこと。") { expect(hospital.serve().name) == "cold" } } } } }
SwiftでのMock実装例class HospitalSpecs: QuickSpec { // protocol準拠でMockを使う override func spec() { describe("病院の診断について") { context("診察できる人が診断を行うとき") { class MockExaminable: Examinable { override func examine() -> Diagnosis { return Diagnosis(name: "cold") } } let mock = MockExaminable() let hospital = Hospital(examinable: mock) it("診察できる人の診断を返すこと。") { expect(hospital.serve().name) == "cold" } } } } }
SwiftでのMock実装例class HospitalSpecs: QuickSpec { // 継承でMockを使う override func spec() { describe("病院の診断について") { context("診察できる人が診断を行うとき") { class MockDoctor: Doctor { override func examine() -> Diagnosis { return Diagnosis(name: "headache") } } // HospitalがDoctorを直接用いている時も使える let mock = MockDoctor() let hospital = Hospital(examinable: mock) it("診察できる人の診断を返すこと。") { expect(hospital.serve().name) == "headache" } } } } }
SwiftでのMock実装例class HospitalSpecs: QuickSpec { // 継承でMockを使う override func spec() { describe("病院の診断について") { context("診察できる人が診断を行うとき") { class MockDoctor: Doctor { override func examine() -> Diagnosis { return Diagnosis(name: "headache") } } // HospitalがDoctorを直接用いている時も使える let mock = MockDoctor() let hospital = Hospital(examinable: mock) it("診察できる人の診断を返すこと。") { expect(hospital.serve().name) == "headache" } } } } }
SwiftでのMock実装例
• protocol, subclassは一長一短
• protocol準拠のMockの場合、@objc とoptionalをつけない限り必須実装をモックにもれなく実装する必要がある
• subclassのMockの場合、ネストの深い箇所ではSegmentation fault 11が起こるケースがあった
OSSのインストール
• git submodule でのOSS導入例(Alamofire)
$ cd (Project dir)$ git submodule add https://github.com/
Alamofire/Alamofire.git
詳しくはWebで! http://dev.classmethod.jp/references/swift-oss-
alamofire-1/
iOS7でOSSを使う
• iOS8 では Dynamic Library の形式では使えないので直にソースファイルをプロジェクトに入れる必要がある
• 極力 git submodule の更新のみでファイル追従を行うためにCopyせずにソースファイルへの参照をリンク
使用中のOSS
• Alamofire : HTTP通信のためのOSS
• SwiftyJSON : JSONハンドリングのためのOSS
• BrightFutures : 非同期処理のためのOSS
• Quick : テストのDSLを提供するOSS
• Nimble : テストのマッチャ−を提供するOSS
• Alamofire
• SwiftyJson
• BrightFutures
• Quick
• Nimble
テストターゲットのみに含まれる為、 テストをiOS8向けとして Dynamic frameworkでプロジェクトに入れた
使用中のOSS
• Alamofire
• SwiftyJson
• BrightFutures
• Quick
• Nimble
テストターゲットのみに含まれる為、 テストをiOS8向けとして Dynamic frameworkでプロジェクトに入れた
アプリターゲットの対象に iOS7が含まれるため frameworkとしてではなく、 ソースの参照を保持するようにした
使用中のOSS
使用中のOSS : SwiftyJSON
https://github.com/SwiftyJSON/SwiftyJSON
使用中のOSS : SwiftyJSON
https://github.com/SwiftyJSON/SwiftyJSON
使用中のOSS : BrightFutures
https://github.com/Thomvis/BrightFutures
使用中のOSS : BrightFutures
https://github.com/Thomvis/BrightFutures
• Before
User.logIn(username, password) { user, error in if !error { Posts.fetchPosts(user, success: { posts in // do something with the user's posts }, failure: handleError) } else { // handeError is a custom function to handle errors handleError(error) }}
使用中のOSS : BrightFutures
https://github.com/Thomvis/BrightFutures
• After
User.logIn(username,password).flatMap { user in Posts.fetchPosts(user)}.onSuccess { posts in // do something with the user's posts}.onFailure { error in // either logging in or fetching posts failed}
クラス名だけを文字列で取得する
// モジュール名.Sample が得られる let nameWithModule = NSStringFromClass(Sample.self) // Sampleのみを取得したい let nameWithoutModule = Sample.nameForClass
クラス名だけを文字列で取得する
extension NSObject { /// クラス名をモジュール名を取り除いて取得します。 public class var nameForClass: String { return NSStringFromClass(self) .componentsSeparatedByString(".").last! } }
NSManagedObject in Test
• NSManagedObjectサブクラスを用いたクラスのテストで実行時に落ちる
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'executeFetchRequest:error: A fetch request must have an entity.' *** First throw call stack: ( 0 CoreFoundation 0x000000010387ef35 __exceptionPreprocess + 165 1 libobjc.A.dylib 0x0000000103517bb7 objc_exception_throw + 45 2 CoreData 0x00000001025e137d -[NSManagedObjectContext executeFetchRequest:error:] + 4541
NSManagedObject in Test
• NSManagedObjectサブクラスを用いたクラスのテストで実行時に落ちる
• Objective-Cから見た時のNSObjectサブクラス名もSwiftのクラスに対してはModule名.クラス名
• @objc()キーワードを使ってObjective-Cから見た時のNSObjectサブクラス名を変更する
NSManagedObject in Test
• Before
• After
class Product: NSManagedObject { @NSManaged var name: String}
@objc(Product)class Product: NSManagedObject { @NSManaged var name: String}
NSManagedObject in Test
• Before
• After
class Product: NSManagedObject { @NSManaged var name: String}
@objc(Product)class Product: NSManagedObject { @NSManaged var name: String}
@objc()キーワードを使ってObjective-Cから見た時のNSObjectサブクラス名を変更
Storyboard & InterfaceBuilder
• Storyboardで定義したInitialViewControllerに接続したUINavigationControllerのrootViewControllerがうまく初期化されない
Storyboard & InterfaceBuilder
• Xcode 6 より追加されたModule入力欄にターゲットモジュールを入力
• またはViewController宣言の前に @objcでObjective-Cから見た クラス名を記述
http://stackoverflow.com/questions/24924966/xcode-6-strange-bug-
unknown-class-in-interface-builder-file
CIとマイナーアップデート
• Travisがすぐに対応してくれた
• Twitterのアカウントで最新情報にキャッチアップ https://twitter.com/travisci
osx_image: xcode611
CIとマイナーアップデート
• 0.0.1単位のマイナーアップデートでも XcodeでOptionalの変更がよくある
例
var textLabel: UILabel? { get }
var textLabel: UILabel! { get } Xcode 6.1.0
Xcode 6.1.1
Swift 1.2
• Incremental Buildが実現!
• より便利になった if let !
• 集合データ構造Set<T>!
• Objective-Cの諸クラスからの暗黙的型変換禁止!
Swift 1.2
• Objective-Cの諸クラスからの暗黙的型変換禁止!
func mangleString(input: String) { // do something with input} let someString: NSString = "hello" mangleString(someString) // compile error!
Swift 1.2
• Objective-Cの諸クラスからの暗黙的型変換禁止!
func mangleString(input: String) { // do something with input} let someString: NSString = "hello" mangleString(someString as! String)
Chris Lattnerの哲学
• The Architecture of Open Source Applications : Chapter 11 LLVM
• 邦語訳へのリンクhttp://m-takagi.github.io/aosa-ja/
Chris Lattnerの哲学
もうひとつ、LLVMを軽量なままに保ち続けている大きな特徴がある (ライブラリを使うクライアント側から見ると賛否両論がある)。 それは、過去の決断も積極的に見直して、過去との互換性を気にせずにAPIを大きく変更していくということだ。 たとえばLLVM IR自体に大幅な変更を加えるには、すべての最適化パスの変更が必要になる。 そしてそれは、C++のAPIにも大きな影響を及ぼす。 LLVMでは過去に何度かそういうことがあった。 クライアント側にとっては辛かっただろうが、今後の開発を順調に進めていくためにはそうすべきだった。
Chris Lattnerの哲学
もうひとつ、LLVMを軽量なままに保ち続けている大きな特徴がある (ライブラリを使うクライアント側から見ると賛否両論がある)。 それは、過去の決断も積極的に見直して、過去との互換性を気にせずにAPIを大きく変更していくということだ。 たとえばLLVM IR自体に大幅な変更を加えるには、すべての最適化パスの変更が必要になる。 そしてそれは、C++のAPIにも大きな影響を及ぼす。 LLVMでは過去に何度かそういうことがあった。 クライアント側にとっては辛かっただろうが、今後の開発を順調に進めていくためにはそうすべきだった。
Chris Lattnerの哲学
ここまではなんとかうまくやってきたが、まだやり残したことは多い。 さらに、今後LLVMが年を重ねるにつれて、軽快さが失われて硬直化してしまうというリスクもある。 この問題には魔法のような解決策があるわけではない。 でも、新しい問題領域を公開し続けていることや 過去の決断を躊躇せず再考していること、 さらに再設計で過去のコードを捨てられるようにしていることなどが、 すこしでもその対策になって欲しいものだ。 結局のところ、パーフェクトな存在を目指すのではなく、常に向上し続けることが大切なのだ。
Chris Lattnerの哲学
ここまではなんとかうまくやってきたが、まだやり残したことは多い。 さらに、今後LLVMが年を重ねるにつれて、軽快さが失われて硬直化してしまうというリスクもある。 この問題には魔法のような解決策があるわけではない。 でも、新しい問題領域を公開し続けていることや 過去の決断を躊躇せず再考していること、 さらに再設計で過去のコードを捨てられるようにしていることなどが、 すこしでもその対策になって欲しいものだ。 結局のところ、パーフェクトな存在を目指すのではなく、常に向上し続けることが大切なのだ。