>100 Views
September 26, 15
スライド概要
2015.09.26 に開催した『カジュアル Swift 勉強会』で、Swift 2.0 での変更点をざっくりと紹介してみました。今回は、前回の続き「後編」として、細々とした仕様変更の話題が中心になってます。とりわけ目立つ変化は「前編」にまとめてあるので、Swift 2 のおおよその雰囲気を知りたい時はそちらから目を通すのがオススメです。
これらで挙げたほかにもいろいろ小さな変更があるんですけど、とりあえずこの「前編」「後編」で Swift 2.0 の変更点のお話はおしまいです。
※ Docswell での公開に移行する直前の Slideshare での閲覧数は 18,337 でした。
正統派趣味人プログラマー。プログラミングとは幼馴染です。
Swift カジュアルプログラミング Swift 2.0 で変わったところ (後編) 2015.09.26 @ カジュアル Swift 勉強会 #2 EZ-‐‑‒NET 熊⾕谷友宏 http://ez-‐‑‒net.jp/
熊谷友宏 EZ-NET http://ez-net.jp/ @es̲kumagai ̶ 勉強会開催 ̶ #yidev 横浜 iPhone 開発者勉強会 カジュアル Swift 勉強会 @ 青葉台 Xcode 5 徹底解説 MOSA IP Phone 音でダイヤル いつもの電卓 with 割勘ウォッチ 音で再配達ゴッド
Swift 2.0 ̶ 2015.09.16 ̶
大幅な仕様変更 ?
そこで
Swift 2.0 で変わったところを ざっくり紹介してみる
前編
前編 SlideShare で公開中 http://www.slideshare.net/ tomohirokumagai54/swift-20-cswift
後編
1/5 NSObject
performSelector
performSelector ▶ Selector を使って機能を実行 ▶ Objective-C と同等の使い心地 ▶ プリミティブ型の引数や戻り値は非対応 func performSelector(sel: Selector) -> Unmanaged<AnyObject>! func performSelector(sel: Selector, withObject obj: AnyObject!) -> Unmanaged<AnyObject>! func performSelector(sel: Selector, withObject obj1: AnyObject!, withObject obj2: AnyObject!) -> Unmanaged<AnyObject>!
Selector ▶ Objective-C と同じ指定方法 ▶ 文字列リテラルから生成できる ▶ nil リテラルから生成できる struct Selector : StringLiteralConvertible, NilLiteralConvertible
performSelector 文字列を返すメソッドを実行 let obj = NSString(string: "TEST.TXT") // ObjC でいう NSString (NSObject) は取得可能 let extname:String = obj.performSelector("pathExtension") .takeRetainedValue()
performSelector 文字列を渡してメソッドを実行 let obj = NSString(string: "TEST.TXT") // ObjC でいう NSString (NSObject) は取得可能 let extname:String = obj.performSelector( "componentsSeparatedByString:", withObject: "T") .takeRetainedValue()
performSelector プリミティブな値は取得できない? let obj = NSString(string: "TEST.TXT") // 戻り値が NSInteger (long) 相当だと nil になる let length:Int = obj.performSelector("length") .takeRetainedValue() Execution was interrupted, reason: EXC̲BAD̲ACCESS.
performSelector プリミティブな引数は受け取れない? let obj = NSString(string: "TEST.TXT") // NSUInteger (unsigned long) 相当を渡せない let length:Int = obj.performSelector("characterAtIndex:", withObject: UInt(1)) .takeRetainedValue() Execution was interrupted, reason: signal SIGABRT. characterAtIndex: Range or index out of bounds.
performSelector プリミティブな引数は受け取れない? let obj = NSString(string: "TEST.TXT") // NSNumber に変換しても渡せない let length:Int = obj.performSelector("characterAtIndex:", withObject: NSNumber(unsignedInteger:1)) .takeRetainedValue() Execution was interrupted, reason: signal SIGABRT. characterAtIndex: Range or index out of bounds.
Swift performSelector 引数
performSelector Swift クラスの機能は呼び出せる? class MyClass : NSObject { func toStringWithStr(v:String) -> String func toStringWithInt(v:Int) -> String }
performSelector 文字列を受け取るメソッドは実行可能 let obj = MyClass() // String を渡すメソッドは正常動作 let result:String = obj.performSelector("toStringWithStr:", withObject: "A") .takeRetainedValue()
performSelector 数値を受け取るメソッドは予期しない動作 let obj = MyClass() // Int だと渡した値とは違う値が渡される様子 let result:String = obj.performSelector("toStringWithInt:", withObject: 1) .takeRetainedValue() エラーにならないが 違う値 が渡される
performSelector 数値を受け取るメソッドは予期しない動作 let obj = MyClass() // NSNumber にラップしても違う値が渡される様子 let result:String = obj.performSelector("toStringWithInt:", withObject: NSNumber(integer:1)) .takeRetainedValue() エラーにならないが 違う値 が渡される
performSelector NSNumber を受け取るメソッドは OK let obj = MyClass() // 呼び出し先が NSNumber を受け取るなら正常動作 let result:String = obj.performSelector("toStringWithNSNumber:", withObject: NSNumber(integer:1)) .takeRetainedValue()
performSelector NSNumber を受け取るメソッドは OK let obj = MyClass() // 呼び出し先の NSNumber にリテラル値も渡せる let result:String = obj.performSelector("toStringWithNSNumber:", withObject: 1) .takeRetainedValue()
Swift performSelector 戻り値
performSelector Swift クラスの機能は呼び出せる? class MyClass : NSObject { func asString() -> String func asInt() -> Int }
performSelector 文字列を返すメソッドは実行可能 let obj = MyClass() // String 型の戻り値は取得可能 let value:String = obj.performSelector("asString") .takeRetainedValue()
performSelector 整数を返すメソッドは実行できない let obj = MyClass() // Int 型の戻り値は取得できない let value:Int = obj.performSelector("asInteger") .takeRetainedValue() Execution was interrupted, reason: EXC̲BAD̲ACCESS.
Swift performSelector ObjC 互換 & Swift ネイティブ
performSelector Swift で定義したクラスは受け取れる? class MyClass : NSObject { func asObjC() -> ObjCClass func asNative() -> NativeClass } class ObjCClass : NSObject { } class NativeClass { }
performSelector ObjC 互換クラスを返すメソッドは実行可能 let obj = MyClass() // 自作 ObjC 互換クラスは取得できるが AnyObject let result:AnyObject = obj.performSelector("asObjC") .takeRetainedValue() // キャストして正常に使用できる let value:Int = (result as! ObjCClass).value
performSelector Swift クラスを返すメソッドは実行できない let obj = MyClass() // Swift ネイティブ型の戻り値は取得できない let value:Int = obj.performSelector("asNative") .takeRetainedValue() Execution was interrupted, reason: signal SIGABRT. asNative: unrecognized selector sent to instance.
performSelector は 独特の癖に悩まされそう
performSelector スレッド
performSelector extension NSObject { func performSelectorOnMainThread( aSelector: Selector, withObject arg: AnyObject?, waitUntilDone wait: Bool) func performSelector( aSelector: Selector, onThread thr: NSThread, withObject arg: AnyObject?, waitUntilDone wait: Bool) func performSelectorInBackground( aSelector: Selector, withObject arg: AnyObject?) }
@objc
@objc これまで ▶ ObjC で使いたい Swift クラスに付与 ▶ NSObject を継承していなくても良い これから ▶ ObjC 互換な Swift クラスに付与 ▶ NSObject を継承している必要がある
@objc class コンパイル時の扱われ方 // NSObject を継承しないクラスには付けられない @objc class SwiftClass { } Only classes that inherit from NSObject can be declared @objc // NSObject を継承するクラスには付けてもいい @objc class SwiftClass : NSObject { }
@objc protocol コンパイル時の扱われ方 // 原則 ObjC 互換クラスにだけ設定できる @objc protocol MyProtocol { } // 全ての型に設定できる protocol MyProtocol { } 実装を要求しない場合は どんな型にも設定できる
@objc protocol コンパイル時の扱われ方 // 明記によりネイティブクラスにも設定可能 @objc protocol MyProtocol { } func method() // @objc プロトコルが要求する機能に @objc を明記 class SwiftClass : MyProtocol { } @objc func method()
@nonobjc
@nonobjc ▶ Objective-C にブリッジしない機能を明示 ▶ プロパティやメソッドに記載する class SwiftClass : NSObject { var value:Int @nonobjc var native:Int @objc var compatible:Int }
@nonobjc ブリッジしない機能を選べる SwiftClass* instance = [[SwiftClass alloc] init]; // @nonobjc を付与した機能は候補に挙がらない instance.value
2/5 制御構文
#available
#available ▶ プラットフォーム毎に条件を指定 ▶ 必ず『全てのプラットフォーム』を含む ▶ 判定は実行時に行われる // 全てのプラットフォームが対象 #available(*) // OSX 10.10 以上と、その他のプラットフォームが対象 #available(OSX 10.10, *) // OSX 10.10 以上と、iOS 8.4 以上と、 // その他のプラットフォームが対象 #available(OSX 10.10, iOS 8.4, *)
#available if 文で使用する if #available(OSX 10.10, *) { // OSX 10.10 以上か、その他のプラットフォーム } else { // OSX 10.10 未満 }
#available else if 文も使用できる if #available(OSX 10.9, *) { // OSX 10.9 以上か、その他のプラットフォーム } else if #available(OSX 10.10, *) { // OSX 10.9 以上か、その他のプラットフォーム } else { // OSX 10.9 未満 }
#available 論理演算はできない if #available(iOS 9.0, *) && value == 1 { } Expected ʻ{ʻ after ʻifʼ condition
#available 条件を必ず満たす場合は警告扱い // Deployment Target が iOS 9.0 のとき if #available(iOS 9.0, *) { Unnecessary check for ʻiOSʼ; minimum deployment target ensures guard will always be true } ▶ ライブラリ内で使用されていると 使うときに警告に悩まされるかもしれない
#available guard 文で使用する guard #available(OSX 10.10, *) else { // 条件を満たさない場合は早期 Exit 必須 } // 以降は OSX 10.10 以上か、 // その他のプラットフォームであることを保証
#available 指定できるプラットフォーム ▶ * ▶ iOS ▶ iOSApplicationExtension ▶ OSX ▶ OSXApplicationExtension ▶ watchOS ▶ watchOSApplicationExtension
3/5 Error Handling Beta 5 以降降の 変更更点
Error Type
Error Type 列挙型以外もエラー型にできる // 構造体でもエラーを定義可能 struct FileOperationError : ErrorType { } ▶ 型を ErrorType に準拠 ▶ 既定のエラードメインは型名 ▶ 既定のエラーコードは 1
Error Type ドメインとエラーコードを指定可能? // 構造体でもエラーを定義可能 struct FileOperationError : ErrorType { } var _code:Int var _domain:String ▶ ̲code でエラーコードを指定 ▶ ̲domain でエラードメインを指定可能 ▶ インスタンス毎に違う値を持てる ▶ ただしプロトコルには明記されていない
Error Type エラーの送出方法 // インスタンスを生成してエラー送信 throw FileOperationError()
rethrows
rethrows 標準メソッドが rethrows に対応 func map<T>( transform: (Generator.Element) throws -> T ) rethrows -> [T] func reduce<T>( initial: T, combine: (T, Generator.Element) throws -> T ) rethrows -> T func filter( element: (Generator.Element) throws -> Bool ) rethrows -> [Generator.Element]
rethrows 演算子も僅かに rethrows 対応 func &&<T : BooleanType, U : BooleanType>( lhs: T, @autoclosure rhs: () throws -> U ) rethrows -> Bool func ||<T : BooleanType, U : BooleanType>( lhs: T, @autoclosure rhs: () throws -> U ) rethrows -> Bool func ??<T>( optional: T?, @autoclosure defaultValue: () throws -> T ) rethrows -> T
try?
try? エラー機構をオプショナルに変換 var handle:Handle? = try? File.open(path) ▶ 戻り値をオプショナルで包む ▶ エラーが発生すると nil が返る ▶ エラー情報は 破棄 される
try? 動作のイメージ // 戻り値をオプショナルで扱う var handle:Handle? do { // メソッドを呼び出して戻り値を取得 handle = try File.open(path) } catch { // エラーが発生した場合は nil を設定 handle = nil }
try? guard で使う guard let handle = try? File.open(path) else { // エラーが発生したときに早期 Exit } return // これ以降はファイルを開けた前提で記載できる return readFrom(handle)
try? rethrows と使う // クロージャー内でエラー発生時に全体を nil に変換 let sum:Int? = try? objects.reduce(0) { } try $0 + $1.toInt() // メソッドチェーンの流れを打ち切ってみたり let sum:Int? = try? objects .map { try $0.toInt() } .reduce(0, combine: +)
4/5 関数
大域関数
大域関数
大域関数の多くがプロトコル拡張に移行
// 主要な大域関数が軒並み削除
count(array)
map(array) { $0 }
// プロトコル拡張に移行された
extension CollectionType {
var count: Index.Distance { get }
func map<T>(f:(Generator.Element)->T)->[T]
}
大域関数 機能の呼び出し方の違い // Swift 1 までの呼び出し方 count(array) map(array) { $0 } // Swift 2 からの呼び出し方 array.count array.map { $0 }
大域関数 toString 関数は変換イニシャライザへ // toString 関数が削除された let string = toString(value) // これからは String の変換イニシャライザを使う let string = String(value) ▶ 変換は型が責任を持つ意志の顕われ?
引数のラベル付けルール
引数のラベル付けルール ▶ Swift 1.x では 3 つのルール ✴ ✴ ✴ 関数 メソッド イニシャライザ ▶ Swift 2.0 では 2 つのルール ✴ ✴ 関数 & メソッド イニシャライザ
引数のラベル付けルール 関数はメソッドと同じルールに変更 // このような “関数” 定義のとき func getPrice(price:Int, tax:Double) -> Int // Swift 1 では両方にラベル名が不要 getPrice(100, 0.05) // Swift 2 では第二引数以降でラベル名が必要 getPrice(100, tax: 0.05)
引数のラベル付けルール 外部ラベル名の省略表記が廃止 ▶ 外部引数名の明記が必須 ▶ 同じ名前でも # で表記できない func getPrice(#price:Int) -> Int '#' has been removed from Swift; double up 'price price' to make the argument label the same as the parameter name func getPrice(price price:Int) -> Int
引数のラベル付けルール まとめ 引数ラベル 第1引数 第2引数 … 関数 メソッド なし(明示で付与) あり(̲ で削除) イニシャライザ あり(̲ で削除)̲ あり(̲ で削除) ▶ 左側に ̲ を付けてラベル名を削除可能 ▶ 左側にラベル名を明記できる
ディフォルト引数の補完
ディフォルト引数の補完 補完候補が複数出るようになった ▶ 全ての引数を省略した時の補完候補 ▶ 全ての引数を指定するための補完候補 func action(a:Int, b:Int = 1, c:Int = 2) { } // 複数の補完候補が表示される value.action
forEach
forEach ▶ 各要素を順番に処理 ▶ 処理にはクロージャーを使う ▶ for … in と似た動作(関数+クロージャー版) extension SequenceType { } func forEach( @noescape body:(Generator.Element) throws -> () ) rethrows
forEach メソッドチェインと併用する items.filter(isValid).forEach { } $0.activate() クロージャーで 処理理を実⾏行行 ▶ break や continue は使えない ▶ return はクロージャーを抜ける ▶ チェーン以外では for … in を推奨らしい
可変長引数
可変長引数 定義と実行方法 // 可変値引数を引数リストの途中に含む関数の定義 func writeTo( path:String, items:String..., permit:Int ) { } // 定義した関数の実行 writeTo(path, items:"A","B","C", permit:0o775) 可変⻑⾧長引数の終わりは ラベルで判断
可変長引数 既定値引数と併せて使う // 可変値引数の後に既定値を持つ引数を定義 func writeTo( path:String, items:String..., permit:Int = 0o755) { } // 定義した関数の実行 writeTo(path, items:"A","B","C") 可変⻑⾧長引数の後の 引数を省省略略可能
可変長引数
末尾クロージャーと併せて使う
// 引数リストの最後でクロージャーを定義
func writeTo( path:String,
items:String...,
preAction:(String) -> String) {
}
// 定義した関数の実行
writeTo(path, items:"A","B","C") {
return parseItem($0)
}
末尾クロージャーの
書式で引数を渡せる
可変長引数 複数の可変長引数は指定できない // 複数の可変長引数があるとビルドエラー func writeTo( path:String, items:String..., permits:Int...) { } Only a single variadic parameter '...' is permitted
イニシャライザ
イニシャライザ 失敗可能イニシャライザに委譲可能 struct MyType { // 失敗可能イニシャライザがあったとき init?(string: String) { } // ある失敗可能イニシャライザで init?(resource:String) { // 別の失敗可能イニシャライザへ委譲可能 } } self.init(string: readFrom(resource)) 失敗時は nil
イニシャライザ 通常のものから失敗可能なものに委譲 struct MyType { // 失敗可能イニシャライザがあったとき init?(string: String) { } // ある通常のイニシャライザで init() { // 別の失敗可能イニシャライザへ委譲可能 } } self.init(string: "DEFAULT")! 失敗時は打ち切切り
イニシャライザ 静的メソッドとして使える ▶ イニシャライザの引数を取る ▶ 戻り値に自身の型を返す ▶ 失敗可能な場合はオプショナルを返す let strings = ["10", "3.5", "8", "20.8"] // イニシャライザを普通の関数として渡せる let values = strings.flatMap(Double.init)
イニシャライザ 静的メソッドは衝突しやすい ▶ 引数ラベル名で選択できない ▶ 関数型の引数に適切なものを渡しづらい struct Selector : StringLiteralConvertible { } init(_ str: String) init(stringLiteral value: String) // 実行したい方を選択できない Ambiguous reference to member ʻSelector.initʼ let sel = optionalString.map(Selector.init)
インスタンスメソッド Swift 2 以前からかもしれない
インスタンスメソッド 静的メソッドとして使える ▶ インスタンスを取り 本来のインスタンスメソッドを返す関数 ▶ プロパティーは静的メソッドにならない let string = "New in Swift 2.0" // インスタンスからメソッドを呼び出す操作を string.containsString("Swift") // 型から静的メソッドを呼ぶ操作に書き換えられる String.containsString(string)("Swift")
インスタンスメソッド 静的メソッドとメソッドチェインの例 // クロージャーを使ったメソッドチェイン let sorted = letters .filter { string.containsString($0) } .sort { $0 > $1 } // 関数だけで作ったメソッドチェイン let sorted = letters .filter(String.containsString(string)) .sort(>)
5/5 列挙型
indirect case
indirect case 再帰的な列挙型を作る // 再帰的な列挙型を定義 enum Cascade { indirect case にすることで 列列挙⼦子の値で⾃自⾝身の型を扱える indirect case Device(String, next:Cascade) case Terminate } // 再帰的に列挙子を組み立てられる let devices:Cascade = .Device("HDD", next: .Device("DVD", next: .Device("MO", next: .Terminate)))
indirect case 深さを計算する機能の実装例 extension Cascade { var depth:Int { switch self { case .Device(_, next: let next): return 1 + next.depth case .Terminate: return 0 } } } // 深さを計算する devices.depth
Raw 値
Raw 値 列挙子と同じ文字列なら省略可能に // 再帰的な列挙型を定義 enum Language : String { } case Swift case ObjectiveC = "Objective-C" case C case Ruby 列列挙⼦子と違う場合だけ 明記すれば OK // 未設定のものは列挙子名と同じ文字列が得られる Language.Swift.rawValue
おしまい 他にも細かい変更はあるけれど 😏
Swift 2.0 で変わったところ 後編 前編 ▶ NSObject ▶ 制御構文 ▶ 制御構文 ▶ Error Handling ▶ Error Handling ▶ 詳細な条件指定 ▶ 関数 ▶ Extension ▶ 列挙型