アクセス修飾子周りでSwiftのバグっぽいのみつけた
Swiftのバージョンは3.0
ViewController.swift
import UIKit class ViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() print(TypeA.name) } }
ProtocolA.swift
import Foundation private protocol ProtocolA { } extension ProtocolA { static var name: String { return "aaa" } } struct TypeA: ProtocolA { }
でビルドしようとするとこんなエラーが出る
Undefined symbols for architecture arm64: "protocol witness table for BugTestProj.TypeA : BugTestProj.(ProtocolA in _ED3794DE2981E83EA71158CCFF2975C6) in BugTestProj", referenced from: BugTestProj.ViewController.viewDidLoad () -> () in ViewController.o "static (extension in BugTestProj):BugTestProj.(ProtocolA in _ED3794DE2981E83EA71158CCFF2975C6).name.getter : Swift.String", referenced from: BugTestProj.ViewController.viewDidLoad () -> () in ViewController.o ld: symbol(s) not found for architecture arm64 clang: error: linker command failed with exit code 1 (use -v to see invocation)
ProtocolAはprivateなので、そこに定義されてるメソッド/プロパティには外からアクセスできないんじゃないかなーと思うんだけどどうなんだろう。
バグレポートは送った。
ビルドターゲットがフレームワークの時にCommonCryptoをimportしたい
この記事はSwift Advent Calendar 2016の2日目です。
TL;DR
結局イケてる解決はできませんでした
前提
- UIにかかわらない部分を切り離すため、Embedded Frameworkの仕組みを用いてモデル層を別モジュールにした
- API通信時にSHA256でトークンを作ってるところがある
- 切り離し前はアプリターゲットのBridging-headerでCommonCrypto.hを読み込んでいた
- フレームワークがターゲットのときにはBridging-headerが使えない(仕様)
- どうしよう
やりかた1: modulemapを自分で書く
[Swift] FrameworkプロジェクトにCommonCryptoをインポートする
これを真似てやったんだけど、Header Search Pathsに$(SDKROOT)/usr/include/CommonCrypto
を足してもビルド時にヘッダを見つけてもらえずにコケていた。しばらくコレにしがみついたけど解決しなそうなので次のやり方に。
やりかた2: modulemapに書いてあるヘッダのパスをBuild Phase内で動的に書き換える
やりかた1をいじいじする中で、相対パスで指定するとヘッダを見つけられずにコケるが、絶対パスで指定するとコンパイルが通ることがわかった。
そこで、フレームワークのビルド手順の一番最初に、modulemapに絶対パスを書き出すようなスクリプトを書いた(べた書きだと人によってパスが違うから)。
しかし、直列に実行されている(はず)のビルド手順で、modulemapに書き出した直後の手順でその書き出しが反映されていない事があり、(sleep 10
とかやると通る)成功したり失敗したりするのでCIを通せずにボツにした。
やりかた3: modulemapに絶対パスを書き出すところを独立したターゲットにして一番最初に動くようにする
ソースもヘッダもない、Run scriptだけがあるターゲットを作って、すべてのモジュールがそれに依存するように設定した。
これにより、そのターゲットが一番最初にビルドされるようになり、modulemapの書き換えもうまく反映されるのでは、と思った。
が、「ビルドキャッシュがない状態では必ずビルドに失敗する」とかいう状態になったので諦めた。
やりかた4: SDKROOTからヘッダファイルをコピーしてきてプロジェクトに直接突っ込んだ状態でCommonCryptoのフレームワークをビルドする
結局これかという感じになってしまった。
相対パスで探せないなら探せる場所に物理的にコピーしてしまえばいいということで、CommonCrypto用のXcodeプロジェクトを作って、そこにヘッダをコピーしてきて、$ carthage bootstrap --no-skip-current
でビルドした。
Allow non-modular ほげほげみたいなのをYESにして、各種ヘッダをpublicにして、アプリのプロジェクトに組み込んだら、ビルド通った。
結論
結局いけてない解決になってしまった感はあるものの、余計なworkaround(CIを通すために、一旦ビルドキャッシュを作るための100%コケるビルドをしてから本番のビルドをする、みたいな)を解消できたのは良かったのかな、とおもう
SushiCameraとかいうアプリをリリースした
お盆くらいから暇な時間を見つけてはちょこちょこ作ってたカメラアプリがようやくストアレビューに通ったのでリリースした。
SushiCameraというカメラアプリをリリースした🍣https://t.co/sdOzfDC6gA
— 人間らしい生活 (@hiragram) 2016年11月5日
iOSの"写真"アプリはGIFの再生をサポートしていないので、レビュアーは静止画が保存されてると勘違いしてたようで、やれシャッター音を鳴らせだの撮影していることをユーザーに明示しろだのごたごたとかみ合わないやり取りをしていたのだが、これ書き出してるのアニGIFやでって教えてやったら速攻審査通った。
GIFカメラアプリ、「カメラで映像撮るなら音鳴らせ」ってリジェクトされたのでLogicProでシャッター音作ってる
— 人間らしい生活 (@hiragram) 2016年11月3日
「シャッター音作らなくていいからデフォルトの奴ならせよw」ってリジェクトされた
— 人間らしい生活 (@hiragram) 2016年11月3日
Appleのレビュアーとの意思疎通すらまともにできないゴミでごめんなさい…
— 人間らしい生活 (@hiragram) 2016年11月3日
今まで個人の開発としてiOSアプリを出したことがなかったので、個人的には1つよい経験をしたなという感じがしている。 とりあえず世に出ているアプリが1個できたので、技術的な挑戦とか実験的な取り組みにどんどん使っていきたい。 直近で興味があるのは、アプリに広告を出すことと、課金周りの仕組みを触ってみること、そんで直接マネタイズが目的ではないもののあわよくばチャリンチャリンしたいなという感じ。
みんなよくそんな休日や寝る時間を削って個人の開発できるな自分には無理だって今まで思ってたけどみんなもっと早く家に帰ってるのではという気づき
— 人間らしい生活 (@hiragram) 2016年11月2日
とりあえずフォントを描画する座標がずれるバグは認識したので適当に直す。
"ReactiveCocoa vs RxSwift"の和訳
https://www.raywenderlich.com/126522/reactivecocoa-vs-rxswift
-
Functional Reactive ProgrammingはSwift開発者の間でますます人気が高まってきている方法論である。それは複雑な非同期処理のコードを書きやすく、理解しやすくすることができる。
この記事では、FRPの中で人気のあるライブラリ、RxSwiftとReactiveCocoaを比較する。
まずはFRPとは何かを簡単におさらいして、そのあと2つのフレームワークの細かい比較を見てもらう。読み終わる頃には、どちらのフレームワークが自分にあっているか判断ができるようになっているだろう。
リアクティブに行こう!
Functional Reactive Programmingとは何か?
Note: もしもう既にFRPのコンセプトをわかっているなら、次の"ReactiveCocoa vs RxSwift"セクションに進んで欲しい。
FRPは、Swiftがアナウンスされる前から既にオブジェクト指向プログラミングに対抗する方法論として人気を獲得していた。Haskellに始まりGo,JavaScriptなど、FRPの思想に影響を受けた実装が見つけられるだろう。なぜか?FRPの何が特別なのか?そしておそらく最も大事なのは、このパラダイムをSwiftでどう活用できるのか?
FRPはConal Elliottが提唱したプログラミングパラダイムである。彼の定義はとても具体的な意味論でここで見ることができる。雑に言うと、FRPは以下の2つのコンセプトの組み合わせである。
- Reactive Programming
- listenして、それに応じてアクションできるような非同期なデータストリームに着目。もっと学びたければこの素晴らしい説明を見て欲しい。
- Functional Programming
- 数学的な関数を用いた処理、不変性や表現性、そして変数や状態を減らすことに重きをおく。もっと学びたければSwift functional programming tutorialを見て欲しい。
Note: Andre StalzがオリジナルのFRPと実践的なアプローチについて彼の記事で説明している。"Why I cannot say FRP but i just did"
簡単な例
例を見るのが一番わかりやすい。ユーザの位置情報をトラックして彼女がコーヒーショップの近くに立ち寄った時にアラートしてくれるようなアプリを考えよう。
これをFRPの流儀に則ってプログラミングしようとすると
- それに応じてアクションできるような位置情報のストリームイベントを発するオブジェクトを作る。
- 発せられた位置情報イベントを見てコーヒーショップに近い位置情報があるかを探し、マッチするものをアラートする
ReactiveCocoaで書くと、こんな感じだ。
locationProducer // 1 .filter(ifLocationNearCoffeeShops) // 2 .startWithNext {[weak self] location in // 3 self?.alertUser(location) }
セクションごとに見ていこう。
locationProducer
は位置情報の変更を一定の周期で通知する。これをReactiveCocoaでは"signal"と呼び、RxSwiftでは"sequence"と呼ぶ。- 次に位置情報の更新に答えるために関数型プログラミングの手法を使う。
filter
メソッドはarrayのそれと同じように動き、それぞれの値をifLocationNearCoffeeShops
メソッドに渡す。もしtrue
が返されれば、そのイベントは次のステップの処理に進むことができる。 - 最後に、
startWithNext
はイベントが届いた時に実行されるクロージャを設定する。
上のコードは配列の値を変換するようなときに使われるものとよく似ている。しかし、これは少しだけ賢い…。このコードは非同期に動くのだ。filter関数とクロージャは"位置情報イベントの発生に応じて"実行される。
このシンタックスは少し妙だと感じるかもしれないが、このコードの根底にある意図はとてもわかりやすいだろう。これが関数型プログラミングの美しさであり、 values over time(宣言的で、どのようにではなく何が起きたかを示す)というコンセプトによくフィットするところである。
Note: もしもReactiveCocoaのシンタックスについて学びたいなら、私がGitHubに公開した例を見て欲しい。
イベントの変換
位置情報の例では、コーヒーショップの近くかどうかを見るためにフィルタリングする以外は何もせずにただストリームの受信を開始しただけであった。
もう一つのFRPの基本的な部分は、イベントを意味のある情報に組み合わせたり変換したりすることである。そのために、高階関数を用いる。
ご想像の通り、Swift functional programming tutorialの中にあった、map
,filter
,reduce
,combine
,そしてzip
などだ。
それでは先程のサンプルを、繰り返されたイベントをスキップし、近づいている位置情報をユーザフレンドリーなメッセージに変換するように変更してみよう。
locationProducer .skipRepeats() // 1 .filter(ifLocationNearCoffeeShops) .map(toHumanReadableLocation) // 2 .startWithNext {[weak self] readableLocation in self?.alertUser(readableLocation) }
追加された2行を見てみると
locationProducer
から発されたsignalにskipRepeats
オペレータを適用した。これは配列のようなものを持たないオペレーションで、ReactiveCocoa特有のものである。この関数は繰り返しになっている値を弾くだけのフィルターである。filter
関数が動作した後、イベントデータを他の型に変換するmap
が使われる。ここではCLLocation
からString
に変換している。
次は、これらFRPの旨味を知るためにはじめるべき
- シンプルなのに、パワフル
- 宣言的アプローチによってコードはよりわかりやすくなる
- 複雑なフローも管理や表示が簡単になる
ReactiveCocoa vs RxSwift
さて、FRPとはどういうもので、それがどのように複雑な非同期処理を簡潔にしてくれるか少しは分かったところで、2つの最も人気のあるFRPフレームワーク、ReactiveCocoaとRxSwiftについて、そしてどちらを選ぶのかを考えてみよう。
詳細に行く前に、それぞれのフレームワークの歴史を簡単に振り返ってみよう。
ReactiveCocoa
ReactiveCocoaはGitHubで生まれた。GitHubのMac版クライアントを作る中で、開発者はアプリケーションのデータフローに苦労していた。彼らはMicrosoftのCE向けライブラリReactiveExtensionsに影響され、Objective-C向けの実装を作り始めた。
チームがObjective-C版のv3.0のリリースに向けて進んでいた時に、Swiftがアナウンスされた。彼らはSwiftの関数的な部分はReactiveCocoaにとってとても補完的だと感じ、後にv3.0としてリリースされるSwift版の実装にすぐとりかかった。v3.0のシンタックスはとても関数的で、カリー化とパイプライン演算子を利用していた。
Swift2.0でprotocol-oriented programmingが提唱され、ReactiveCocoaに重要なAPI変更をもたらした。v4.0ではパイプ来年残滓が廃止され代わりにプロトコル拡張が使われる。
ReactiveCocoaは執筆時点でGitHubのスターが13000以上集まっている人気のライブラリである。
RxSwift
MicrosoftのReactiveExtensionsに影響されてJavaScript、Java、Scala、その他多くの言語にFRPの思想をもったフレームワークが生まれた。これらはFRPの実装に一般的なAPIを策定したReactiveXによって主導されており、これにより様々なフレームワークの作者がともに作業できるようになった。結果的に、RxScalaを使う開発者はRxJavaにも親しみやすくなった。
RxSwiftは最近ReactiveXファミリーに追加されたため、ReactiveCocoaのStar数には及ばない(執筆時点で4000)。しかしながら、RxSwiftはReactiveXの一部であり、今後の人気と長命さは約束されていると言えよう。
ここで面白いのは、RxSwiftもReactiveCocoaもともにReactiveExtensionsを祖先に持つということである!
RxSwift vs ReactiveCocoa
続いて詳細を見てみよう。RxSwiftとReactiveCocoaはFRPの実現の仕方に多少の違いがある。そのうちの幾つかを見てみよう。
Hot vs Cold Signals
通信のためのリクエストを作り、レスポンスをパースし、ユーザに見せるようなシーンを想像してみよう。
let requestFlow = networkRequest.flatMap(parseResponse) requestFlow.startWithNext {[weak self] result in self?.showResult(result) }
リクエストはsignalを購読した時に初期化される(startWithNext
したとき)。これらのsignalは、購読するまで"凍結された"状態なので、 cold と呼ばれる。
もう一つは hot なsignal。hot signalを購読した時、それは既に動作を開始しているかもしれないので、受け取ったイベントは3番目、4番目のイベントかもしれない。ぴったりな例を挙げると、キーボードのタップがそうである。キーボードのタップが「開始」するというのは妙だ。
おさらい:
- cold signal は購読して初めて動き始める。購読者ごとに動作が開始される。
requestFlow
を3回購読するということは、3つのリクエストを発行することを意味する。 - hot signal はもう既にイベントを送り始めてるかもしれない。新しく購読者が増えても、新しく開始されることはない。ふつうUIイベントはhot signalである。
ReactiveCocoaはhotとcoldそれぞれにSignal<T, E>
とSignalProducer<T, E>
という型を提供している。一方RxSwiftはObservable<T>
という型一つで両方を表現する。
hotとcoldの型が分かれているのといないのとで何が違うのか?
個人的には、signalの意味を知っているということは大事だと思っている。なぜなら特定のコンテキストでどのように動作するのかを知っておいたほうが良いからである。より複雑なシステムを作るときに、これは大きな違いとなる。
それぞれの型があるかどうかは関係なく、hotとcoldについて知っておくことは非常に大事である。
仮にhot signalを扱っているつもりでそれが実はcold signalだったとき、新たな購読者ごとに副作用が発生する。これはアプリケーションに甚大な影響を与えるだろう。よくある例だと、3,4個のエンティティがネットワークのリクエストをobserveしたがっているとき、それぞれの購読ごとに異なるリクエストが実行されるべきである。
ReactiveCocoaに1ポイント!
エラーハンドリング
エラーハンドリングの話をする前に、RxSwiftとReactiveCocoaにディスパッチされるイベントについて簡単に振り返ってみよう。両方とも、主に以下の3つのイベントを発行する。
Next<T>
- これは
T
型の新しい値がストリームにプッシュされるたびに発行される。位置情報の例だと、T
はCLLocation
である。
- これは
Completed
- イベントストリームの処理が完了したことを示す。これ以降に
Next<T>
やError<E>
が発生することはない。
- イベントストリームの処理が完了したことを示す。これ以降に
Error<E>
- エラーを示す。ネットワークリクエストの例だと、サーバーがエラーを返した時などに発生しうる。
E
はErrorType
に適合する型。これ以降にNext<T>
やCompleted
が発生することはない。
- エラーを示す。ネットワークリクエストの例だと、サーバーがエラーを返した時などに発生しうる。
お気づきかも知れないが、RxSwiftのObservable<T>
は持っていないがReactiveCocoaのSignal<T, E>
とSignalProducer<T, E>
は2つの型パラメータを持っている。E
という型はErrorType
のエラーを示す。RxSwiftでは内部でErrorType
のエラーを生成している。
これは一体どういうことか?
実践面では、RxSwiftではエラーを発するやり方はいくつかあることを意味する。
create { observer in observer.onError(NSError.init(domain: "NetworkServer", code: 1, userInfo: nil)) }
上のコードはsignal(RxSwiftではobservable)を作成して、すぐにエラーを発生させている。
あるいはこうも書ける。
create { observer in
observer.onError(MyDomainSpecificError.NetworkServer)
}
Observable
は「エラーはErrorTypeであればいい」ので、送りたいものを送ることができる。しかしそれは以下の様なケースですこし厄介になる。
enum MyDomanSpecificError: ErrorType { case NetworkServer case Parser case Persistence } func handleError(error: MyDomanSpecificError) { // Show alert with the error } observable.subscribeError {[weak self] error in self?.handleError(error) }
これは動かない。なぜならhandleError
が引数に取るのはMyDomainSpecificError
であり、ErrorType
ではない。あなたは次のことをしなければならない。
error
をMyDomainSpecificError
にキャストするerror
をキャストできなかったときのケースをハンドルする
1つめはas?
を使えば簡単に対応できるが、2つめは少し面倒だ。見込みのある解決策はUnknown
というcaseを追加することである。
enum MyDomanSpecificError: ErrorType { case NetworkServer case Parser case Persistence case Unknown } observable.subscribeError {[weak self] error in self?.handleError(error as? MyDomanSpecificError ?? .Unknown) }
ReactiveCocoaにおいては、Signal<T, E>
やSignalProducer<T, E>
を作った時、違う型のエラーを投げようとした時にコンパイラは文句をいえる。
ReactiveCocoaにもう1ポイント!
UIバインディング
UIKitのようなiOSの標準APIは、FRPの言葉をしゃべれない。もしもRxSwiftもReactiveCocoaも使わないならば、自分でAPIをブリッジしなければならない。例えば、tapのtarget actionをsignalやobservableに変換しなければならない。
ご想像の通り、多くの手間がかかる。だからReactiveCocoaとRxSwiftの両方はそれぞれ幾つかのブリッジとバインディングを提供している。
ReactiveCocoaにはObjective-Cの時代からの資産がある。Swiftにブリッジされたすでにある多くの資産も見つけることができる。これらの中にはUIバインドが入っているが、その他のいくつかの演算子はまだSwiftに書き換えられていない。もしObjective-Cで出来たクラスを使っているなら、Swiftの型に変換しなければならない(toSignalProducer
のようなメソッドも用意されている)。
それだけでなく、ドキュメントを読むよりもソースを読むほうが時間がかかると感じる。これは知っておくべきことだが、理論や考え方のドキュメントは夫妻となっている。使い方の部分のドキュメントはしっかりしてるけどね。
これを補うために、数十のチュートリアルが用意されている。
一方、RxSwiftのバインディングは使っていて楽しい!大きなカタログが有るだけでなく、山ほどの見本、完全なドキュメントがある。これだけでReactiveCocoaでなくRxSwiftを選択する理由になる人もいるだろう。
RxSwiftに1ポイント!
コミュニティ
ReactiveCocoaはRxSwiftよりもその歴史が長い。多数の開発者がいて、十分な量のチュートリアルがオンライン上にあり、SOのReactiveCocoaタグも非常に助けになるだろう。
ReactiveCocoaはSlackのチームを持っているが、209人しかいない小さなチームであり、多くの質問は回答されないままになってしまう。緊急の場合は、コアメンバーに直接メッセージを送らざるを得なかったし、おそらく他の人々もそうしているだろう。とはいえ、ほとんどの問題はオンライン上のチュートリアルで解決できるはずだ。
RxSwiftは新しくて、ほぼ一人の手で作り上げられている。Slackのチームもあり、961人のメンバーがいて、議論も活発である。疑問に答えてくれる人をいつでも見つけることができる。
全体的に、今のところいずれのコミュニティも異なる面で素晴らしく、ここでは引き分けとする。
どっちを選ぶべきなの?
Ash FurrowがReactiveCocoa vs RxSwiftで言うところによると
聞いてくれ、もしあなたが初心者なら問題ない。もちろん、技術的な違いはあるが、いずれも初心者にはとっつきづらい。まずは片方試してみて、そのあともう片方を試してみて欲しい。そして自分自身に問うてみると良い!そうすればなぜそれを選ぶのか理解できるだろう。
私も同じアドバイスをするだろう。
しかしながら、もしあなたがどちらかを選ばなければならなくて、かつ両方を試す時間が無い時のために、ここに私の提案を書いておく。
ReactiveCocoaを選ぶべきシーン
- システムをよりわかりやすく記述したい時。hotとcoldなsignalを型で区別すること、エラーの型をパラメータ化することは、システムをより良くするだろう。
- 実績があり、多くの人に使われ、多くのプロジェクトで使われたフレームワークを使いたい時。
RxSwiftを選ぶべきシーン
- UIバインディングがプロジェクトにとって重要なとき
- FRP初心者で手厚い支援が必要なとき
- RxJSもしくはRxJavaの経験があるとき。RxSwiftはReactiveXのorganization下にあるため、一つを知れば、他を知るのも容易である。
次はどこに行くべきか?
RxSwiftかReactiveCocoaか、どちらを選ぼうとももう後悔はしないだろう。いずれもシステムをよりわかりやすく記述するには素晴らしいフレームワークだ。
これもまた重要な事だが、一度RxSwiftやReactiveCocoaを知ると、互いを行き来するのはとても時間がかかるだろう。私の経験によると、ReactiveCocoaからRxSwiftに勉強のために移行した際は、エラーハンドリングが最もはまった場所だった。まあ、最も大きなマインドシフトはFRPそのものに慣れることで特定の実装は関係ないんだが。
以下のリンクは君のFRP、RxSwift、ReactiveCocoaとの旅の助けになるだろう。
- Conal Elliottのブログ
- Conal ElliottのStackOverflowの素晴らしい回答 What is (functional) reactive programming?
- Andre Staltzの必ず読むべき記事 Why I cannot say FRP but I just did
- RxSwiftのリポジトリ
- ReactiveCocoaのリポジトリ
- Rexのリポジトリ
- iOSエンジニアにとって究極のFRPの宝の埋蔵物。RxSwiftとReactiveCocoa両方のリソースがある。
- Marin TodorovによるRxSwift exploration
私はあなた達がこれらの素晴らしいライブラリの一つを将来のプロジェクトで使ってくれることを願っている。
Enums as configurationは別にアンチパターンでは無いのでは
ちょっと前の記事だけど。
Enums as configuration: the anti-pattern via @jesse_squires
この記事が言いたいのは
- enumでインスタンスの設定をするのは、提供された選択肢から選ぶしか無いので利用者側の自由度が低く良くない。
- ライブラリやフレームワークがそうなっていると、選択肢を増やしたかったらPR投げてマージしてもらわないといけない。
- Swiftのswitch文はenumを網羅しないとコンパイルエラーになるから、あちこちにエラーがでて悲しいからよくない。
ということらしい。
で、以下が僕の主張。
利用者側はおかしなことをするという前提で提供側が想定したパターンしか設定できないenumのほうが安全なような気がするなあ / “Enums as configuration: the anti-pattern · Jesse S…” https://t.co/phDORFzS0N
— ひらり (@hiragram) 2016年8月25日
Swiftのswitchはenumの中身を網羅してないとコンパイル通りませんよってやつは安全装置だと思ってるし、enumにcase足すとswitchが全部コンパイルエラーになるからenumじゃなくてstructを使えみたいなのはちょっと賛同できないなあ
— ひらり (@hiragram) 2016年8月25日
ビューのスタイルの設定をするパターンと、ネットワークの振る舞いを設定するパターンと説明してるけど、前者はstruct(=利用者側にカスタマイズ可能性を残す)でいいと思うけど、振る舞い決める方は提供側が想定してない使い方をされかねないのを考えるとうーんという感じ。
— ひらり (@hiragram) 2016年8月25日
何事も、使う側は常にバカだから提供側はおかしなことをそもそもやれないように作っておくべきなんじゃないのって思う
— ひらり (@hiragram) 2016年8月25日
こっちが提供する選択肢以外に利用側に自由に設定させても良いかなって気分になるのは色ぐらいかなあ。フォントサイズとか太さとかはレイアウト崩れかねないし
— ひらり (@hiragram) 2016年8月25日
僕はパターンを網羅していないとコンパイル通してくれないSwiftのswitchは好きだ。同じ理由でenum使ってswitchするときにdefaultを使うのは嫌いだ。
Swift3の新しいアクセス修飾子について考察
3行で
- Swift3から新たなアクセス修飾子
open
,fileprivate
が追加された private
の扱いが変わった- ゆるい順に、
open
,public
,internal
,fileprivate
,private
open
「モジュール外からアクセスでき、 サブクラス化(継承)可能である」ことを示すアクセス修飾子。以前まではpublic
がこれを示していたが、public
は「モジュール外からアクセスできるが、 サブクラス化(継承)は出来ない」に変更された。
open
はSwift2以前のpublic
にあたり、Swift3のpublic
はSwift2以前のpublic
にfinal
修飾を付け足したもの?
fileprivate
「同じファイル内からのみアクセス可能である」ことを示すアクセス修飾子。Swift2以前のprivate
と同じ。Swift3のprivate
は、「定義されたスコープ内からのみアクセス可能」に変更された。
利用
open
とpublic
、及びfileprivate
とprivate
の使い方、使い分けについて考察する。
まず、Xcode8でできるSwift3のシンタックスへのコンバートをかけると、
public
はそのままprivate
はfileprivate
になった。
近年Appleはプロトコル指向プログラミング(Protocol Oriented Programming, POP)を推進しており
- 参照型のクラスよりも値型の構造体を使いましょう(構造体は継承が無い)
- 継承を使わずにプロトコルとエクステンションで実現しましょう
と考えているようで、コンバート時にpublic
がopen
に置換されないのは「なるべく継承させないように作ろうね」というメッセージなのではと感じた。
private
とfileprivate
の使い分けについてはまだ考察中で、基本的にはprivate
に寄せて参照できるスコープを狭くするべきだと思うが、一方でSwiftには
struct Human { var name: String } extension Human: HogeProtocol { func hogeMethod() { print("hoge") } } extension Human: FugaProtocol { func fugaMethod() { print(name) } }
可読性の向上のためにプロトコルの実装をエクステンションとして切り出して書く派も存在し、もしプロパティname
がprivate
だった場合には2つのエクステンションからは参照できなくなる。
安全のためにスコープを狭くするべきか、可読性のために多少スコープの広がりを許容するかの問題だが、これはどちらを取るべきなんだろうか。
まとめ
基本的に自作の型でopen
をたくさん使うとしたらライブラリ作者くらいであろう。Appleが継承地獄から脱却しようとしているのは明らかなので、それに沿う形で順応していこうと思う。
fileprivate
については、世間がどう受け止めるかという感じがするので、もうすこし様子を見たい。
参考
複数のプロトコルに適合する型、という書き方
最近知りました。
//: Playground - noun: a place where people can play protocol Animal { func run() } protocol Vehicle { func drive() } struct Nekobasu: Animal, Vehicle { func run() { } func drive() { } } let bus: protocol<Animal, Vehicle> bus = Nekobasu() bus.drive() // ok bus.run() // ok