-- Views
April 04, 16
スライド概要
きっかけは try! Swift 2016 の HIPSTER SWIFT でした。そこで Hector さんの lazy var について興味深い(当時はどんなに頑張っても意味を汲み取れなかった)話を受け、もう一度 lazy var の特徴を眺めそこから『Hector さんの発していた意味』と『lazy var をどんな風に使ったらいいのかな』みたいなことを考察してみた資料です。
※ Docswell での公開に移行する直前の Slideshare での閲覧数は 7,211 でした。
正統派趣味人プログラマー。プログラミングとは幼馴染です。
Swift カジュアルプログラミング lazy var の特徴を知る 2016.03.12 第66回 Cocoa 勉強会勉強会 2016.04.02 第6回 カジュアル Swift 勉強会 EZ-NET 熊⾕友宏 Swift 2.1.1 http://ez-net.jp/
熊谷友宏 @es_kumagai EZ-NET http://ez-net.jp/ 書籍 / 登壇 Xcode 5 徹底解説 MOSA Xcode 5 の全機能を 徹底的に解説した本 OSX/iOS 系の歴史深い 有料会員制の勉強会 Xcode 7 でも役立つはず 法人会員も多数 紙版は絶版、電子書籍は販売中
熊谷友宏 @es_kumagai EZ-NET http://ez-net.jp/ 勉強会 横浜 iPhone 開発者勉強会 カジュアル Swift 勉強会 【 横浜・馬車道 】 #yidev 【 横浜・青葉台 】 #cswift わいわい・ゆるく、iPhone 開発者の みんなで楽しく過ごすのが目的の会 ゆるくみんなで Swift を語らえる場を 作りたくて始めた会 第6回を 2016-04-02 に開催予定
熊谷友宏 @es_kumagai EZ-NET http://ez-net.jp/ iOS, OS X, Apple Watch アプリ CodePiece いつもの電卓 ソースコードを Twitter と Gist に同時投稿できる。 計算式も見える電卓アプリ。 watchOS 1 対応 音で再配達ゴッド EZ-NET IP Phone 簡単操作で 再配達の申し込み。 iPhone でひかり電話を使う。 自宅 LAN からの利用専用
CodePiece for OS X 勉強会を楽しむアプリ ソースコードを Twitter と Gist に同時投稿できる 勉強会で知見をみんなと共有したい時とかに便利! できること #cswift
try! Swift 2016.03.02 - 2016.03.04
try! Swift 2016 初日
try! Swift 2016 二日目
try! Swift 2016 最終日
try! Swift The most impressive speaker for me ! 😆 HIPSTER SWIFT ★ @noescape ★ @autoclosure ★ inline lazy vars ★ variadic parameters ★ labeled loops ★ type omitting
HIPSTER SWIFT inline lazy vars
Lazy Variables 初期化を参照時まで遅延できる変数
HIPSTER SWIFT Lazy Variables ▶ 値型プロパティを lazy 付きで定義 ▶ 右辺で参照時に設定する値を記載 class MyClass { lazy var value: Int = { return self.calculate() }() }
HIPSTER SWIFT 評価式の書式 これが … lazy var value: Int = { return self.calculate() }() こう書ける lazy var value: Int = self.calculate()
HOW BEAUTIFUL ... BUT … SAFE ?
HIPSTER SWIFT 評価式の書式 初期化コードで self を使っていても … class MyClass { lazy var value: Int = self.calculate() } 循環参照しない var obj: MyClass? = MyClass() obj.value DEINIT! obj = nil
AWESOME !! 🎉
HIPSTER SWIFT 気になったところ Super Lazy Vars class MyClass { lazy var value: Int = self.calculate() } 循環 しない ! 強く参照 ! 強く参照 ! オート クロージャー どういうことだろう…
Lazy Variables もっと理解して使いたい
Lazy Variables 概要 ▶ 初期値の設定を参照直前まで延期できる ▶ 主に保存型プロパティで使用 // 定義の仕方 class File { lazy var content: Content = Content(path: somePath) }
Lazy Variables 初期化タイミング File lazy var Contents = Content(path: somePath) 参照 instance Contents 値
Lazy Variables 自身の機能で初期化できる File lazy var Contents = Content(path: self.path) 参照のときは インスタンス生成済みなので 初期化処理で self も使える 参照 instance Contents
Lazy Variables 使いどころ? プロパティを自分自身の機能で 初期化したいときに便利 … なもの? そう単純なものでもなさそう
挙動
Lazy Variables 挙動 1/7 … 初期化のタイミング ▶ 参照のときに初期化が行われる ▶ 初期化して保存してすぐ参照する class File { } lazy var content: Content = Content(path: somePath) // ここでは content は初期化されない let file = File() // ここで初めて content が初期化され、値がプロパティに保持される let content = file.content
Lazy Variables 挙動 2/7 … 初期化後は既存の値を取得 ▶ 初回参照時に値が設定される ▶ 以降参照時は初回初期化時の値を取得 class File { } lazy var content: Content = Content(path: self.path) let file = File() let content1 = file.content file.path = newPath let content2 = file.content // いったん初期化が行われると // 関係するものを更新しても // 得られる値は連動しない
Lazy Variables 挙動 3/7 … 自由に代入可能 ▶ 値は自由に代入できる ▶ 初期化コードとは無関係に設定可能 class File { } lazy var content: Content = Content(path: somePath) // ここでは content は初期化されない let file = File() // 自分で値を自由に設定できる file.content = Content(path: anotherPath)
Lazy Variables 挙動 4/7 … 初期化の省略 ▶ 参照しなければ初期化されない ▶ 使わなければ初期化処理を省略できる class File { } lazy var content: Content = Content(path: somePath) // ここでは content は初期化されない let file = File() // その後 content を参照しなければ、初期化は行われない
Lazy Variables 挙動 5/7 … 初期化コードを使わない初期化 ▶ 未初期化のときにだけ初期化される ▶ 参照前の代入も初期化とみなす class File { } lazy var content: Content = Content(path: somePath) let file = File() // 参照前に content に値を代入すると … file.content = Content(path: anotherPath) // 初めての参照時でも初期化コードは実行されない let content = file.content
Lazy Variables 挙動 6/7 … 初期化は同期的に実施 ▶ 初期化コードは参照直前に実行 ▶ 意識的に初期化完了を待つ必要なし class File { } lazy var content: Content = Content(path: somePath) let file = File() // 初めての content 参照 let content = file.content // 初期化して self.content = Content(path: somePath) // 取得する return self.content
Lazy Variables 挙動 7/7 … プロトコルに対する実装で使用可能 ▶ プロトコルが求めるプロパティに対して使える ▶ インスタンス生成後まで初期化を延期できる protocol MyProtocol { // 非オプショナルな型を求められても } var property: Int { get } class MyObject : MyProtocol { // 初期化をインスタンス生成後まで遅延可能 lazy var property: Int = self.updateProperty() }
特徴と利用場面
Lazy Variables 特徴 参照直前まで 既定値の代入を遅延できるプロパティ
Lazy Variables 利用場面 ▶ 計算に時間のかかるプロパティで ▶ 必ず使うとは限らないが ▶ 使うなら各所で何度も参照する場面 ▶ 自身の値で初期化したいプロパティで、 ▶ 明確な初期化タイミングを定められず、 ▶ 自身の値が変化しても影響を受けず、 ▶ 別の値に書き換える道も提供したい場面
注意
Lazy Variables 注意 1/4 ▶ ゲッターも mutating 扱い ▶ 構造体だと var に格納しないと参照できない struct File { } lazy var content: Content = Content(path: somePath) let file = File() let content = file.content cannot use mutating getter on immutable value: 'file' is a 'let' constant
Lazy Variables 注意 2/4 ▶ 初期化以降の状態変化は連動しない ▶ 初期化後の状態と矛盾する可能性 class File { } lazy var content: Content = Content(path: self.path) let file = File() let content1 = file.content file.path = newPath let content2 = file.content // 現在のパスから初期化されたら // その後にパスが変更されても // 古いパスのコンテンツが得られる
Lazy Variables 注意 3/4 ▶ 遅延初期化を除き、普通のプロパティと同等 ▶ 初期化式と無関係な独自の値を自由に設定可能 class File { } lazy var content: Content = Content(path: somePath) let file = File() // 初期化式とは無関係な値をいつでも設定できる file.content = Content(text: "TEXT 1") // 何度でも設定できる file.content = Content(text: "TEXT 2")
Lazy Variables 注意 4/4 ▶ 予測困難な初期化タイミング ▶ マルチスレッドで扱うときに悲劇を生むかも class File { lazy var content: Content = Content(path: somePath) } // どちらで content が初期化される? func pushButton(sender: AnyObject) { contentView.content = file.content } func updateNotification(notification: NSNotification) { contentView.content = file.content }
HIPSTER SWIFT strong reference 循環 しない ! 強く参照 ! 強く参照 ! オート クロージャー
HIPSTER SWIFT 循環参照を起こす例 class MyClass { lazy var calculate1: () -> Int = self.calculate3 lazy var calculate2: () -> Int = { () -> Int in return self.calculate3() } } func calculate3() -> Int { return 10 } let obj = MyClass() obj.calculate1() obj.calculate2() // 循環参照する // 循環参照する obj.calculate3() // 循環参照しない
関連する話題 lazy var から広がる話
大域変数
大域変数も lazy
大域変数 遅延初期化 ▶ 初めて使うタイミングで初期化される ▶ 内部的に lazy var として扱われる var bundle = NSBundle.mainBundle() class File { ここで初めて 初期化される init() { self.content = Content(path: bundle.bundlePath) } }
大域変数 既定値を保証 ▶ 大域変数は let で宣言できる ▶ 既定値にしかならないことを保証可能 let bundle = NSBundle.mainBundle() class File { init() { bundle = NSBundle(forClass: Object.self) } } Cannot assign to value: 'bundle' is a 'let' constant
大域変数 名前空間の活用 ▶ 大域変数はモジュールの名前空間に所属 ▶ 他のモジュールと衝突しない MyModule で定義 public let defaultManager = ... アプリで使用 func method() { let manager = MyModule.defaultManager }
lazy var と似ている機能
ImplicitlyUnwrappedOptional ( Type! )
ImplicitlyUnwrappedOptional 概要 ▶ 値が無いことを表現できる型 ▶ 値がある前提で扱える // 値が無いまま存在させて、 var content: Content! = nil // 値を後から設定できる content = Content(path: path) 値がなければ 強制終了 // 普通の変数のように操作する doSomethingWithData(content.data)
ImplicitlyUnwrappedOptional 意図 使う時には 値が入っていることを約束する プログラマーが!
ImplicitlyUnwrappedOptional 特徴 lazy var と似ているところ ▶ 初期化を遅らせることができる ▶ 初期化されているものとして使用できる lazy var と決定的に異なるところ ▶ 適切なタイミングで初期化が必要 初期化のタイミングを 想定できる!
初期化タイミングを制御できるなら ImplicitlyUnwrappedOptional
lazy var × ImplicitlyUnwrappedOptional
lazy var × ImplicitlyUnwrappedOptional 性質 lazy var ▶ 初期化を参照時まで遅らせる ▶ 初期値を決めておける ImplicitlyUnwrappedOptional ▶ 使うときに値が入っていることを約束する ▶ 値がないことを表現できる
ゾンビっぽい変数 が生まれる 😱 ※ ただしプロパティでの使用に限る
lazy var × ImplicitlyUnwrappedOptional 値を消しても復活する変数 class File { } lazy var content: Content! = Content(path: self.path) let file = File() // content を参照すると初期化される let content = file.content // ImplicitlyUnwrappedOptional なので nil を入れられる file.content = nil // nil のときに再び content を参照すると再初期化される let content = file.content
画像キャッシュの機能などに 応用できるかもしれない
lazy 超かっこいい … 😆
lazy var 注意したいところ ▶ 初期化されるタイミングが読めない ▶ 読み書きともに mutating 扱い ▶ 複数スレッドで衝突する可能性 ▶ 初期化式とは無関係に値を入れられる ▶ 単に “便利だから” という理由で使わない ▶ 初期化を遅らせたいだけなら ImplicitlyUnwrappedOptional も検討したい
lazy の 魔力にご用心
lazy var の特徴を知る まとめ 1. HIPSTER SWIFT 2. Lazy Variables の動き 3. 大域変数も lazy 扱い 4. ImplicitlyUnwrappedOptional は 動きが lazy var と似ている 5. lazy var × ImplicitlyUnwrappedOptional で 何度だって蘇る変数が生まれる 6. lazy の魔力にご用心