読者です 読者をやめる 読者になる 読者になる

UnitTest 用のデータを共通化する【Swift】

iOS Swift テスト Xcode

テストをする際には複数のオブジェクトの初期化をする必要があり、この作業が面倒でテストを書くのが面倒になってしまうことがあったので、どうにかできないかと思い調べてみたところ、良さそうな方法がありました。

clean-swift.com

これを、自分が使いやすいと思った形に少しアレンジしたものを紹介してみようと思います。

Seeds Struct

以下のように Human というクラスがあったとします。

class Human {
    
    var name: String
    var age: Int
    var height: Double
    var job: String
    
    init(name: String, age: Int, height: Double, job: String) {
        self.name = name
        self.age = age
        self.height = height
        self.job = job
    }
    
}

この Human に関するテストを書くといったときに、以下のようにテストを書く度に値を指定して初期化していくかと思いますが、本当に全部の値を指定しなければならないというケースはそれほど多くはないのではないかと思います。

let takeru = Human(name: "takeru", age: 30, height: 180.0, job: "engineer")
let rika = Human(name: "rika", age: 28, height: 160.0, job: "designer")

こういった時に上記の記事に書いてあるようにして Seeds というテスト用のデータを持つ Struct を用意することで、Xcode 等の補完機能を用いながら以下のように簡単にデータを作成することができます。

struct Seeds {
    
    struct Humans {
        
        static let takeru = Human(name: "takeru", age: 30, height: 180.0, job: "engineer")
        static let rika = Human(name: "rika", age: 28, height: 160.0, job: "designer")
        
    }
    
}

Seeds.Humans.takeru
 => Human(name: "takeru", age: 30, height: 180.0, job: "engineer")
Seeds.Humans.rika
 => Human(name: "rika", age: 28, height: 160.0, job: "designer")

Problem

ですが、上記の様に Stored Property を static で宣言してしまうとシングルトンになってしまいます。
そのため、テスト間でオブジェクトのプロパティの値を変更する時に、思わぬところでテストが落ちてしまうということがあるかもしれないので、値を変更する際には気をつけなければなりません。

let takeru = Seeds.Humans.takeru
takeru.age = 10

// 一度プロパティの値を変更するとその後も維持されてしまう
Seeds.Humans.takeru.age
 => 10

Computed Property

そこで、テストデータの定義には以下のように Computed Property を用いるようにします。
こうすることで、以下のように Seeds からオブジェクトを呼ぶ出す度に異なるオブジェクトが返ってくるため、安心してプロパティの値を変更することができます。

struct Seeds {
    
    struct Humans {
        
        static var takeru: Human {
            return Human(name: "takeru", age: 30, height: 180.0, job: "engineer")
        }
        static var rika: Human {
            return Human(name: "rika", age: 28, height: 160.0, job: "designer")
        }
        
    }
    
}

let takeru = Seeds.Humans.takeru
takeru.age = 10

// 異なるオブジェクトなので 30 のまま
Seeds.Humans.takeru.age
 => 30

Unique Value

先ほどの Human に一意の値となる myNumber というプロパティを持たせる必要が生じたとします。

class Human {
    
    var name: String
    var age: Int
    var height: Double
    var job: String
    let myNumber: Int // 追加
    
    init(name: String, age: Int, height: Double, job: String, myNumber: Int) {
        self.name = name
        self.age = age
        self.height = height
        self.job = job
        self.myNumber = myNumber // 追加
    }
    
}

こういった、 オブジェクト毎に異なる一意の値を持たせたいという場合においても、Seeds に以下の uniqueInt の様なコンピューテッドプロパティを持たせることで、オブジェクトを呼び出す度に自動的に持たせることが可能となります。

struct Seeds {
    
    static var increment = 0
    static var uniqueInt: Int {
        increment += 1
        return increment
    }
    
    struct Humans {
        
        static var takeru: Human {
            return Human(name: "takeru", age: 30, height: 180.0, job: "engineer", myNumber: Seeds.uniqueInt)
        }
        static var rika: Human {
            return Human(name: "rika", age: 28, height: 160.0, job: "designer", myNumber: Seeds.uniqueInt)
        }
        
    }
    
}

Seeds.Humans.takeru.myNumber
 => 1
Seeds.Humans.takeru.myNumber
 => 2

テストは人間が見過ごしてしまうような部分や人間がすべきでないような部分をカバーすることができるので書くに越したことはないのですが、やはり辛い面も多々あるかと思うので、テストを書く度に毎回同じ様なオブジェクトを用意していて辛いなあと感じたら、こういったテストデータ作るくんを用意してみてはいかがでしょうか!

Swift化開発合宿 in 熱海♨️

Swift iOS

先日、会社のモバイルエンジニア5名でSwift化開発合宿に行ってきました!
普段はなかなか Swift 化を進めることができないので、こういった機会を設けてガガガっと進めてしまおうということから、今回の合宿が決まりました。
この開発合宿記録を書いていきます。

1日目

移動

みんなでお昼ご飯を食べてから宿泊所の最寄駅である網代駅に向かいました。 特急等は一切使用しなかったので、電車だけで約2時間半かかりました。
着くと綺麗な梅の花がお出迎え。
普段は都会のビルばかりを見ているので、男5人で「梅が綺麗だね」とかなんとか言いながら、熱海に到着したことに浸っていました。 合宿場となる別荘は、駅から2kmほどの山奥にあるということを聞いていたので、早速タクシーを捕まえました。
タクシーの運転手さんと「この辺はイノシシがそこらへんにいるから気をつけなよ」「昔は良くイノシシを捕まえて食ったもんだよ」「俺らからしたら犬もイノシシも変わらんよ」と、いのししトークで盛り上がっているとあっという間に到着しました。

宿泊所

旅館ではなく、元々は別荘であっただろう場所だったので、部屋はとても広くて3LDK。山の中にあるということもあり、窓からは見えるオーシャンビューはとても綺麗でした!

f:id:komaji504:20170322222818j:plain

肝心なのは、ネット環境です。
事前情報としては WiFi 環境がないということだったので、 WiMax を持参していたのですが、山奥ということもあり、そもそもこの WiMax も使えるかということが不安でした。スイッチを入れてみるたのですが、やはり繋がらない… 部屋の中であちこち移動させてみるとなんとか繋がる場所があったのですが、これも不安定だったので、結局スマホテザリングも併用して乗り切りました。

ちなみに作業場はこんな感じです。

f:id:komaji504:20170322223133j:plain

Swift化

ひたすら Objective-C で書かれているコードを Swift に書き換えていきます。
前日に作戦会議をして担当範囲や進め方を決めていたので、各々、担当範囲を黙々と書き換えて行きました。

f:id:komaji504:20170322223516j:plain

夕食 & 温泉

山の中なので近くにお店もなく、下山するのも大変なので、事前に最寄駅のコンビニで大量に食材を買っておいて、それにありつきました。近くにお店がないような環境だと、自分以外が食べているもの全部が美味しそうに見えてきてしまうのが不思議ですね…

温泉ですが、ここの宿には本来露天風呂があるらしいのですが、修理中とのことなので、部屋についているもので我慢。
ですが、蛇口からはなんと温泉が出る!23時以降は温泉が止められてしまうとのことだったので、早めに温泉を溜めておいて浸かりました。やっぱり温泉は最高!

2日目

散歩

朝に軽く散歩をしました。
自動販売機があったのでコーヒーを買ったところ、サンプルと違う…!
コーヒーには違いがないので良しとしましょう。こんなことも熱海なら全然OKです。

f:id:komaji504:20170322223711j:plain

Swift化

散歩から帰ってきて、コンビニで買った朝食をとったらまたまたひたすら書き換えをしていきました。
2日目ということもあり、黙々とSwift化することにも慣れてガガガっと進めていきました。

夕食

最終日なのでうまいものを食べようと下山することにしました。
タクシーを呼ぼうと思ったのですが、全然捕まえることができなかったので自らの足で山を下りました。男5人が集まっても、やっぱり山はなんだか怖いので、各々が好きな音楽を流しながら気を紛らわせながら歩きました。2km といえども山道はとてもキツく、道が平坦になってきた頃には足は棒切れ状態でした…

食事はやっぱり寿司!!!にしようと思ったのですが、寿司屋はこの辺にはないとのことを伺っていたのでとりあえず魚が食べられるお店へ GO!
刺身定食を食べたのですが、必死の思いで下山したこともあり最高に美味しかったです。刺身は見た目も豪華だしとにかく最高!

f:id:komaji504:20170322222827j:plain

帰りは運良くタクシーを捕まえられたので歩かずに済みました。歩く気力も体力もなかったので本当に良かった…

合宿を終えて

こんな感じで、海と山に囲まれて美味しい魚を食べることもできたので Swift 化という単調で楽しいわけではない作業もガガガっと進められて、開発合宿ってやっぱり最高!という気持ちになりました。普段の機能開発の時間とは別に、こういった機会があると普段なかなか行えないようなことも行えて良いですね。

合宿の環境としては、やっぱり WiFi が用意されているところを探したほうが安心でした。
山の中ということに関しては、景色は綺麗で良かったのですが、やっぱりお店が近くにないのも少し不便だったので、コンビニくらいは近くにある場所が良さそうと思いました。
次回、開発合宿をやるなら上記を満たすような場所を探そうと思います。

何より、こういった開発合宿に関して、積極的に支援してくれる会社に感謝です!

成果

合宿前の Swift の割合は約 30% でした。

f:id:komaji504:20170323001552p:plain


そして、



合宿後の Swift の割合が…



こちらです!!!

f:id:komaji504:20170323003519p:plain

約 5% の Objevtive-C のコードが Swift に置き換わったということですね。
ちなみに、置き換えたコード量でいうと約 5000 行でした。

まだまだ Objective-C のコードがわんさかいるので、引き続き撲滅作業を進めていきたいと思います!

iOSアプリ開発で実機のログに filter をかける

iOS Xcode

アプリ起動中のログであれば Xcode のコンソールから確認できますが、 アプリを起動したり終了させたときのログを確認したい場合には、Xcode のコンソールでは確認できないので実機のログを確認する必要があるかと思います。

このとき Xcodeツールバーの Window -> Devices から実機を選択すれば Xcode 上でログを確認することができますが、この方法だとログに対して find はできるのですが、 filter はかけられないので結構不便だったりします。

そこで、なんとか filter をかける方法がないかなと思って探してみたら方法がありました〜

libmobiledevice

www.libimobiledevice.org

こちらを使うことでターミナルでログを確認できるので、それを grep すればよいということです。

Homebrew から入れることができるのですが、 iOS10 の実機のログを確認する場合には、以下のように HEAD を指定して install しないといけないようです。

$ brew install -v --HEAD --fresh --build-from-source libimobiledevice`

refs:

[SOLVED] Unable to start syslog with iOS 10 on Windows · Issue #325 · libimobiledevice/libimobiledevice · GitHub

使い方

$ idevicesyslog -u 実機のUUID

でターミナルにログを出力することができます。
もちろん UUID を確認するコマンドもあります。

$ idevice_id -l

以下のように出力されるログに対して grep すれば filter をかけることができます!

$ idevice_id -l | xargs idevicesyslog -u | grep hogehoge

これで快適なデバッグライフを送ることができそうです!

try! Swift 記録 2日目

iOS カンファレンス Swift

try! Swift に参加してきました!

www.tryswift.co

ので、メモっていたことを書きます。 本当に雑なので、間違っているかもしれません。 2日目の分です。 1日目の分はこちら

テスト可能なコードを書くということの2つの側面

なぜテスト

  • バグを防げる
  • テストができるコードだけ書くようにしている
  • 関数型のように
  • テストをドキュメント化している
  • それに向かって振る舞うように実装している
  • ファイルを読み出してその内容を計算してコンソールに出力して結果を返すという場合
    • 入力と出力を分ける
    • 入力は引数以外にも、関数の内部から直接呼び出しているものも隠された入力と言える
    • 明示的なインプットと暗黙的なインプットがあるということ

アウトプットに関して

  • 難しいのは side-effect があるから
  • でも sideeffect はコード上で判断できるようにしておくべき
  • どうやるか
    • print の引数を直接返すようにする

インプットに関して

  • 難しいのは co-effects があるから
    • これは新しい分野なので詳しい説明はない
  • 何が操作されるのかが見えないといけない
  • グローバルに扱うものに関しては singleton などを直接使わずに間に struct を挟む
    • グローバルに定義しないでグローバル用の一つの struct を用意して入れてしまう
    • currentUser とか date も cookieStrage も langage も apiClient も scheduler も userDefaults も
    • 基本的に全て protocol を間に挟んで行う
    • こうして挟んだ protocol を使って引数を挟むようにする。

Q.どのタイミングでテスト書いているか
テスト駆動開発でやっている

Q.プロパティベースのテストは行なっているか
行なっていないが行いたい

誰もが知りたいSequenceとCollectionのすべて

Sequence

  • エレメントのリスト
  • 有限である
  • 一度だけしかイテレートできない
  • Sequence Protocol は map や fileter が適宜されている protocol
    • これに沿って何かを実装するれば、 map や filter が使えるようになる
  • 型に条件を持たせるには where を使う
    • 副作用として複雑になってしまう

BidirectionalCollection

  • 前にも後ろ戻りもできる Collection
  • 他にも Collection が3つあるのでそこら辺見ると良さそう

Iterator の何が嬉しいか

  • 基本的は内部で使っているだけなのであまり使うことがない
  • 自分で何か順番で見ていく処理を追加したい時に Iterator を見るというようにすることができる。

iOSにおけるDocument IndexingとApp Search

App Search を使う

  • Core SpotLigth API
  • spotlight で検索したときにからアプリ内のデータを返してタップしたらアプリを開く
  • 実装
    • import CoreSpotlight
    • CSSearchableItemIndexDelegate protocol を実装する
    • 情報を最新にしておく
  • beginBatch is background thread
  • apple のドキュメントはない
  • endBatch を呼んでから beginBatch する
  • スレッド処理は手動でやらないとならない
  • developer setting の identifer で Extension を取り出せる

きちんと型付けされたメッセージ

  • 可読性の高いコードを書く
  • メソッドのインターフェースに着目する
  • 引数にどういう処理がされて返り値が返ってくるのか
  • 返り値が何のための値なのか

方法

返り値をどう分かりやすくするか

  • コメント
  • 返り値の型を typealias で作る
    • この型がどういう定義なのかを確認しなければいけなくなる
    • 一度しか使わないのであればデメリットの方が大きい

返り値が optional の場合にはどうするか

  • typealias
  • enum で valid, invalid を作る
  • これらをやると結局複雑になっているので必ず良いとは言えない

weak の値をつかう場合

  • weak はいかを表す
    • 保持されていない
    • 要件ではない
    • 変更可能
  • これは本当に必要なものなのか
  • こういったことは慣習を元に用意されている
  • これを防ごうとしてラッパーを用意するとまた複雑になってくる
    • 慣習であるならば隠蔽すると逆にわかりづらくなってくるのでは

結論

  • 最適解というのはない
  • 他者がどういうところを疑問に持つのかを考える
  • メンバーの価値観を把握してコードを書くことが大事

モックオブジェクトをより便利にする

  • メソッドが呼ばれたことを判断するフラグは Bool じゃなくて Int にすれば回数まで担保できる
  • フラジャイルテストは避ける
    • 壊れて欲しくないときにも壊れてしまうようなテスト
  • エラーメッセージを良いものにしよう
    • 引数に file や line を メッセージ をメソッドに渡す
  • Hamcrest Matchers おすすめ

Client-Side Deep Learning

  • MPSCNN
  • metal は早いのでリアルタイムの画像解析が行える
  • ニューラルネットワークをつかうことでクライアントサイドでディープラーニングができるようになった
  • リアルタイムでサーバサイドと通信すると負荷がすごくなるのでクライアントサイドでやれると嬉しい

try! Swift 記録 1日目

Swift iOS カンファレンス

try! Swift に参加してきました!

www.tryswift.co

ので、メモっていたことを書きます。 本当に雑なので、間違っているかもしれません。 1日目の分です。2日目の分はこちら

Swift開発者が知りたかったけど聞きにくい機械学習のすべて

  • 機械学習ではデータを集めて関数を定義する
  • 誤差をエラーという
  • 身n長を平均する関数の場合
    • 男女で分ければ誤差が違うので精度が上がる
    • 複数の要素の係数を定義して足し合わせていくと正確度が上がっていく
    • 線形回帰
  • アプリ開発者に影響があるか
    • ある
  • 自分でも理解が深くなくてもできるか
    • できる
    • 機械学習は言語でいうとまだ ver1.0 みたいなもの
    • 普及してきていて勉強できる機会が増えている
      • グーグルの TensorFlow とか
  • 機械学習はSwiftでできるのか
    • 以下の2STEPでできる
      • モデル定義して訓練
      • デプロイして動かす
    • モデルにはパイソンとテンサーフローを使うべき
    • テンサーフローのライブラリをSwiftでバンドルすることができる
    • Swiftでモデルのポーティングができる
  • どういう時に使える
    • AVFoundationやCoreImageを使うようなもの
  • UIデザインを使うようなものに似ている
    • 確実ではないものに対して
  • 機械学習のモデルのインプットはUIのような曖昧なものに向いている(例えば入力フォームとか)
    • iOS は人間の体のような曖昧なもので成り立っている
    • そういった曖昧なインプットを扱っているので機械学習が向いている
  • どこから始められる

Q.例で挙げたメガネの作り方は
サーバで演算して結果をモバイルに送ってレンダリングしている
ニューラルネットワークはすごいので今後はサーバ側でやっていることもモバイルでやりたい

Swift on Android

  • SilverSwift の話ではない
  • C言語は全ての橋渡しとなる言語
    • Swift はそのC言語を触るのが得意
  • UIKit は Android に移植されることはない
  • Swift が Android で動くことを考えるので UIKit とかのライブラリとかは関係ない
  • 本当の AndroidSDK(java)で書かなければならなかった
    • NDK がでて来てC, C++でかけるようになった
    • C,C++ は Swift は動かない
  • NDK はお粗末
  • Lua
  • Blurr SDK
    • CMake でプリロードしてそれをシッピングする
  • twitter は @codingswift

Q.メモリ管理の違い
SDKではNDKを使っている
AndroidJavaメモリ管理を使っている
NDK ではあらゆるメモリを使えるようにしている

SwiftのPointy Bits

  • Swiftを安全にしているもの
  • 安全とは
    • クラッシュするコードもかけてしまう
    • 予期せぬ振る舞いの時だけクラッシュする
    • 予期せぬ振る舞いを防ぐという意味で安全
  • メモリアドレスはbiteが最小単位
  • バッファポインタはメモリアドレスのrangeを格納する

アプリを新次元に導く3D Touch

メリット

  • 3DTouch はやりたいことに早くアクセスできる
  • 3DTouch 特集という取り上げられ方もある
  • 3DTouch API は使いやすいよ

実装

  • ホーム画面で3D Touchした時には static と dynamic アクションがある
  • static は Info.plist で定義する
    • 必須項目が二つあって二の項目はオプション
  • dynamic はコード上で動的に定義する

Peek & Pop

  • プレビューするやつ
  • ユーザが3DTouch使えるか確認する必要がある
    • viewDidLoad とかで forceTouchCapability == .availabe をチェックする
    • 3D Touch 使えない場合はロングプレスで代替機能を実装できる
  • updateもする(表示するデータの更新?)
  • デリゲートメソッドは二つだけ
  • UIPreviewInteraction で圧力の強さも0~1の範囲でアクセスできる

Q.テストできるのか?
まだ試していない
Q.後方互換としてロングプレスを実装することで一応対応できるといったがラッパークラスは用意しているか OSS があるのでそれが使えそう

Pixcels プロセスと情熱

  • 高価なものを安価に利用できるようにするためにアプリを作った
  • 大事なこと
    • 課題に注目するが大事
    • 技術ではなくて人のモチベーション
    • 自分のコネを生かす

毎日リアクティブ

どのような時に使うのか

  • 複雑なコードがある場合に一定までは複雑になるがそれを越えると簡素化される
  • 非同期なイベント(UIや通信)に有効

問題

  • call stack が使えないのでデバッグがしづらい
    • 一定のストリームごとでデバッギングする
  • KVO がパワルのなのでどこでも使いたくなる
    • オーバーヘッドがでかく、パフォーマンスが悪くなる。
    • バインディングを使っていくことで解決できる
  • サイドエフェクトに注意深くinjectしなければならない
  • 混沌を避ける
    • 必要ないところでは使おうとしない
    • state はイミュターブルにすると良い
    • 使いどころは選ぼう
    • シンプルにできるところで使おう
    • Appleフレームワークに沿っていないので厳しくなるときもあるので

Q.初見の人がいる場での導入できるかと学習コストはどうか 学ぶのは楽ではないがそれほど問題ないだろう
毎日触れていれば大丈夫
リアクティブを試すコミュニティも増えている

クックパッドアプリのテストを味合う

  • 全コードは10万行くらい
  • Swift コードは 30%
  • リリースサイクルは2週間から最近は1ヶ月
  • 狩野モデルにいて最低限必要なこと(must be quality)は画面遷移でクラッシュしないこと
  • なぜUIテストを実装し続けてきたか
    • クラッシュを防げる
  • tarnip(ターニップ)でシナリオを書いている
  • 設定とかでパスが必要な場合はバージョンに依存してしまうのでハードコーディングしない
  • UITest は image diff でやると良い
  • API の request 回数のテストも書いている
  • UIの8割のテストをカバーしている
    • そのおかげで早く安定して開発を行えている
  • Appium を使っている

Q.Appium を選択した理由
アーキテクチャの関係

独自のツールを構築する

  • ネイティブはコンパイル遅いからリアクトネイティブやってみた
  • 抽象化してみようとやってみたところドメイン思考にいきつく
    • 再利用できないという結論に
  • プロジェクトを分けて差分だけビルドしようと思ったけど長期的に見ると複雑になって大変そう
  • API志向にネイティブ開発は向いていないがリアクトネイティブは向いている

Q.どんなアプリでもリアクトネイティブにできるか - 高度なアニメーションをしたいときにはリアクトネイティブが適切かというとどうだろうか - やろうと思えばできるだろうが

Q.???
- ポッドで入れた - ライブラリーのような扱いで入れた - メインのアプリケーション起動ではアプリとして扱うようにした

instantiateInitialViewController() が nil になる

Xcode Swift UIKit iOS

同じことで何度もはまっている気がするのでメモ。

SampleViewController の Storyboard と Class を追加して、Storyboard から以下のようにインスタンスを作ろうとすると、nil になってしまう。

let storyboard = UIStoryboard(name: "SampleViewController", bundle: nil)
let viewController = storyboard.instantiateInitialViewController() // => nil

typo もないし、何でだ〜〜〜!となっていたけど、

Storyboard の Show the Attributes Inspector で Is Initial View Controller がチェックされていなかった、、、

Swift で循環参照するケース

Swift iOS

循環参照という言葉を最近よく耳にしていて、なんとなく相互参照の結果メモリリークしてしまうというのは分かっていたのですが、じゃあどうしたら起きてしまうのかというところがあやふやだったので調べてみました。

循環参照

インスタンスは参照カウンタという値を持っており、変数に代入されたりして強参照されることで参照カウンタが 1 増えます。この参照カウンタが 0 になるとインスタンスは解放されます。

class Sample {}

// Sample クラスのインスタンスが生成されるが参照カウンタが 0 のままなのですぐに解放される
Sample()

// Sample クラスのインスタンスが生成されて sample に代入されているので参照カウンタが 1 となり解放されない
var sample: Sample? = Sample()

// nil を代入すると参照がなくなるのでインスタンスの参照カウンタが 0 になり解放される
sample = nil

循環参照とは、参照カウンタが 0 になり得ない状態をつくってしまう参照のことです。
これにより、インスタンスは解放されることなく残り続けてしまい、メモリリークとなってしまいます。

具体的なケースをいくつか書いてみます。 ( Swift 3.0 )
図では、赤枠が変数、青枠がインスタンス、緑枠がクロージャを表しています。

ケース1

f:id:komaji504:20161218022711p:plain:w500

class Computer {
    var printer: Printer?
}

class Printer {
    var computer: Computer?
}

var computer: Computer? = Computer() // Computer の参照カウンタ1
var printer: Printer? = Printer() // Printer の参照カウンタ1

computer.printer = printer // Printer の参照カウンタ2
printer.computer = computer // Computer の参照カウンタ2

computer = nil // Computer の参照カウンタ1
printer = nil // Printer の参照カウンタ1

// 各インスタンスの参照カウンタは1なので解放されていない
// しかしプログラムから参照することはできない

インスタンスのプロパティで一方のインスタンスを強参照しているので、変数からの参照がなくなった後も各インスタンスは解放されません。
変数からインスンタンスへの参照がなくなったことによりプログラムからインスタンスを参照することができなくなってしまい、無駄なインスタンスが生じている状態になっています。

対処例

このケースはプロパティに対して弱参照を用いることで解決できます。

class Computer {
    var printer: Printer?
}

class Printer {
    // weak をつけることで弱参照となる
    weak var computer: Computer?
}

var computer: Computer? = Computer() // Computer の参照カウンタ 1
var printer: Printer? = Printer() // Printer の参照カウンタ 1

computer.printer = printer // Printer の参照カウンタ 2
printer.computer = computer // Computer の参照カウンタ 1
// 弱参照は参照カウンタを増やさない

computer = nil // Computer の参照カウンタ 0 , Printer の参照カウンタ 1
// computer が解放されることで computer.printer からの Printer への参照もなくなる

printer = nil // Printer の参照カウンタ 0

weak をつけてプロパティ定義をすると、そのプロパティからインスタンスへの参照は弱参照となります。
弱参照とは、参照カウンタを増やさずに参照する方法で、これにより循環参照を防ぐことができます。

delegate を実装するときに delegate プロパティに対して weak をよくつけるかと思いますが、それはこのケースのためです。

ケース2

f:id:komaji504:20161218022731p:plain:w200

class Printer {
    var printClosure: (() -> Void)
    
    init() {
        self.printClosure = {
            print("Printer name is", self)
        }
    }
    
    func run() {
        printClosure?()
    }
}

var printer: Printer? = Printer() // Printer の参照カウンタ 2 , クロージャの参照カウンタ 1

printer = nil // Printer の参照カウンタ 1

クロージャは、生成されたタイミングでクロージャ内で扱われているインスタンスへの参照をキャプチャします。つまり、クロージャが解放されるまで参照が保持されるので、強参照をしている場合はインスタンスの参照カウンタが 1 増えたままということになります。

そのため、上記のケースでは、 Printer のインスタンスが生成されたときに self ( = Printer ) を強参照するクロージャが生成されているので、 Printer のインスタンスの参照カウントが 1 になります。

そして、このクロージャをプロパティへ代入しているため、 Printer のインスタンスクロージャを強参照し、クロージャもまた Printer のインスタンスを強参照しているので循環参照となっています。

対処例

ケース1と同様にプロパティを弱参照とすることもできますが、クロージャがキャプチャする参照を弱参照へと変更する方法もあります。
その場合は以下のように [weak self] と記述します。

class Printer {
    var printClosure: (() -> Void)
    
    init() {
        // self の参照を弱参照にする
        self.printClosure = { [weak self] in
            print("Printer name is", self ?? "")
        }
    }
    
    func run() {
        printClosure?()
    }
}

var printer: Printer? = Printer() // Printer の参照カウンタ 1 , クロージャの参照カウンタ 1

printer = nil // Printer の参照カウンタ 0, クロージャの参照カウンタ 0

これにより循環参照を防ぐことができます。
しかし、弱参照とすることで参照先のインスタンスが解放されることを許容することになるので、クロージャ内ではオプショナル型として扱われます。上記の場合では、 self はオプショナル型となっています。そのため、オプショナルバインディング等でアンラップする必要があります。

unowned

ちなみに unowned というのもあり、これをつけることで参照カウンタを増やさずに参照することができます。
weak との違いは、 オプショナル型と暗黙的オプショナル型の違いのように、 参照先が解放されてしまっている ( nil になっている ) ときに実行時エラーとなるかどうかということのようです。

weak は実行時エラーになりませんが、 unowned は実行時エラーとなります。

ですが、 unowned の場合はオプショナルバインディング等でアンラップする必要がありません。

ケース3

f:id:komaji504:20161219002036p:plain:w500

class Computer {
    var printer: Printer?
    
    func printName() {
        printer?.run {
            print("Computer name is", self)
        }
    }
}

class Printer {
    var printClosure: (() -> Void)?
    
    func run(printClosure: @escaping () -> Void) {
        self.printClosure = printClosure
        run()
    }
    
    func run() {
        printClosure?()
    }
}

var computer: Computer? = Computer() // Computer の参照カウンタ 1
var printer: Printer? = Printer() // Printer の参照カウンタ 1

computer?.printer = printer // Printer の参照カウンタ 2
computer?.printName() // Computer の参照カウンタ 2 , クロージャの参照カウンタ 1

computer = nil // Computer の参照カウンタ 1
printer = nil // Printer の参照カウンタ 1

このケースでは、 func printName() 実行した時点で循環参照となってしまっています。
このメソッド内では クロージャを受け取るメソッド func run(printClosure: @escaping () -> Void) を実行しているのですが、渡したクロージャが Printer のプロパティへと代入されてしまっているのでクロージャへの強参照が生じています。

これにより、クロージャが Computer のインスタンスself で強参照する、 Computer のインスタンスがプロパティで Printer のインスタンスを強参照する、 Printer のインスタンスがプロパティでクロージャを強参照するという循環参照となってしまっています。

対処例

class Computer {
    var printer: Printer?
    
    // self の参照を弱参照にする
    func printName() { [weak self] in
        printer?.run {
            print("Computer name is", self ?? "")
        }
    }
}

class Printer {
    var printClosure: (() -> Void)?
    
    func run(printClosure: @escaping () -> Void) {
        self.printClosure = printClosure
        run()
    }
    
    func run() {
        printClosure?()
    }
}

var computer: Computer? = Computer() // Computer の参照カウンタ 1
var printer: Printer? = Printer() // Printer の参照カウンタ 1

computer?.printer = printer // Printer の参照カウンタ 2
computer?.printName() // Computer の参照カウンタ 1 , クロージャの参照カウンタ 1

computer = nil // Computer の参照カウンタ 0 , Printer の参照カウンタ 1
printer = nil // Printer の参照カウンタ 0 , クロージャの参照カウンタ 0

他と同様に弱参照を用いて防ぎます。

このケースの厄介な部分は、 func printName() の処理を見ただけだとクロージャがどう扱われているのかわからないという点です。 今回は func run(printClosure: @escaping () -> Void) の処理を見たときに、クロージャをプロパティへ代入していることがすぐにわかりますが、メソッド内の処理が複雑になってくると、クロージャがどう扱われるのかというのが把握しづらくなってしまいます。

@escaping

そこで @escaping というものがあります。

func run(printClosure: @escaping () -> Void) という部分で使われていますが、 これが付いていると、そのクロージャがどこかで強参照されるということを保証してくれます。そのため、このメソッドにクロージャを渡す際は、クロージャ内で扱うインスタンス[weak self] のように弱参照としておく必要があるということがすぐわかります。

まとめ

なんとなくわかった気がしたのですが、普段コードを書いていてもシュッと「これは循環参照するぞ」なんてわからなそうなので、迷ったらとりあえず weak をつけておけばいいのかなあという感じがしました。