自前CocoaPodsライブラリで公開するヘッダファイルを限定する方法
CocoaPodsは、自動作成するumbrellaヘッダにデフォルトで全ヘッダファイルを含めます。
Objective-Cだけで書いてある場合、この振る舞いでも特に問題にはなりません。・・・ですが、ライブラリにC++コードなどSwiftで対応しない言語のファイルが含まれているとなれば、話は別です。
CocoaPodsでc++ソースを含む自前ライブラリを何も考えずに公開しようとしても、umbrellaヘッダにc++向けヘッダファイルまでも全て含まれてしまい、Swift環境でコンパイルできなくなってしまうのです。C++で作ったライブラリをSwiftで使う場合、Objective-C++で作成したファイルをブリッジとして、Swift側からはc++のコードが見えないようにしなければいけないのに、です。
umbrellaヘッダに含めるヘッダファイルを限定したい場合、.podspec
ファイルでs.public_header_files
を使います。
# umbrellaヘッダでのinclude対象になるファイルを指定するオプション s.public_header_files = ["Core/*.h", "Tree/**.h"]
s.source_files
は、コンパイルに必要なファイルを指定するのに対して、今回紹介するs.public_header_files
はumbrellaヘッダに含めるファイルを指定するのに使います。複数ある場合は配列で指定します。
以下はサンプルです。末尾にs.public_header_files
があります。
Pod::Spec.new do |s| s.name = "MyLibrary" s.version = "1.0.0" s.description = <<-DESC MyLibrary is an awesome library! DESC s.homepage = "https://github.com/foobar/MyLibrary" s.source = { :git => "https://github.com/foobar/MyLibrary.git", :tag => "v#{s.version}" } s.license = "New BSD" s.author = "Foo Bar" s.platform = :ios s.ios.deployment_target = "8.0" s.requires_arc = true s.frameworks = 'Foundation' s.module_name = "MyLibrary" s.source_files = "MyLibrary/**/*.{h,cpp,mm,swift}" # コンパイルに必要な全ソースファイル s.public_header_files = ["MyLibrary/MyLibrary.h", "MyLibrary/PublicHeader.h"] # umbrellaヘッダでのinclude対象 end
こっそり公開しているライブラリを自分でpod install
しようとしたらエラーになってしまい、慌てて直したわけで…とほほ。
参考サイト
RxSwiftでUIKitを扱うにはRxCocoaが必要
内容はタイトル通りです。引き続きRxSwiftについて。
「RxSwiftでUIKitを試してみよう。このAPIページを見ると色々とあるな…あれ?何も使えない?」と。
よくよく確認してみると、RxSwiftの下には別のpodspecファイルとして、RxCocoa.podspecがありました。RxSwiftだけでなくRxCocoaも必要なようです。RxCocoaという名前から、てっきりObjective-CのRxライブラリだとばかり思っていましたが、UIKitやAppKit向けのextensionがswiftで集められたものでした。
というわけで、Podfileに
platform :ios, '8.0' use_frameworks! target 'MyApp' do pod 'RxSwift', '~> 2.0' pod 'RxCocoa' # ここを追加 end
RxSwiftのMainSchedulerは何物か
遅まきながら、RxSwiftに手を付け始めました。
リアクティブプログラミングはまったく経験がないので、公式の「Getting Started」を読みながら、概念や基本を調べることにします。
ですが、まだ分からないことばかりで詳細には全然立入れていません。
オブザーバとジェネレータ
リアクティブプログラミングには「オブザーバ」と「ジェネレータ」の2つのキーワードがあるようです。このEquivalenceを理解することがRxホニャララを理解する上でもっとも重要なのだそう。
このページの記述を引用すると、それぞれは次のようにプッシュ型かプル型かで使い分けるとのこと。
- Push interface - Observer (observed elements over time make a sequence)
- Pull interface - Iterator / Enumerator / Generator
まだよく分からず。
スケジューラ
Rxは非同期(asynchronous)で動いているので、処理をどのスケジューラ(スレッド)で動かすかを指定しなければいけません。この点はGCDと同じです。
Getting Startedの中では、「Dispose」の節にてはじめてソースコードが登場し、Observable<SignedIntegerType>
のinterval()
メソッドが持つscheduler
なる引数で、スケジューラを指定しています。
let subscription = Observable<Int>.interval(0.3, scheduler: scheduler) .subscribe { event in print(event) } NSThread.sleepForTimeInterval(2) subscription.dispose()
これは0.3秒ごとに2秒になるまで(つまり合計6回)eventオブジェクトをコンソールに出力し続ける処理ですが、scheduler
として突然出てきている変数があります。これが処理実行のスレッドとして使われるようです。ただ、このコードだけを記述してもschedulerが未定義になってエラーになるので、何かきちんとしたインスタンスを指定する必要があります。メソッドの型宣言ではSchedulerType
型のインスタンスが使えるとのこと。
何を指定するのかなとGetting Startedをもう少し先に進んでみたところ、同様の処理で「MainScheduler.instance
」と書いてある箇所がありました。試しにこれを使ってみたところ、エラーなく実行できるようになりました。何者なのか、MainScheduler
の中身を見てみます。
public final class MainScheduler : SerialDispatchQueueScheduler { private let _mainQueue: dispatch_queue_t var numberEnqueued: AtomicInt = 0 private init() { _mainQueue = dispatch_get_main_queue() super.init(serialQueue: _mainQueue) } /** Singleton instance of `MainScheduler` */ public static let instance = MainScheduler() :
まだ処理の詳細は追えないものの、dispatch_get_main_queue()
を使っていることから、メインスレッドが使われると考えて良さそうです。
時間切れになったので、今回はここまで。
今回のまとめ
コマンドラインからApp Storeのアプリをインストール
下のブログで、コマンドラインからApp Storeのアプリをインストールするコマンドラインツール「mas」が公開されていることを知りました。
確かにわざわざAppStoreから1つずつインストールするのは面倒なので、自分の環境にも入れてみることにします。
brew
コマンドを使ってインストールします。
% brew install argon/mas/mas ==> Tapping argon/mas Cloning into '/usr/local/Library/Taps/argon/homebrew-mas'... remote: Counting objects: 3, done. remote: Compressing objects: 100% (2/2), done. remote: Total 3 (delta 0), reused 2 (delta 0), pack-reused 0 Unpacking objects: 100% (3/3), done. Checking connectivity... done. Tapped 1 formula (25 files, 16.7K) ==> Installing mas from argon/mas ==> Downloading https://github.com/argon/mas/releases/download/v1.0.2/mas-cli.zip ==> Downloading from https://github-cloud.s3.amazonaws.com/releases/40092232/db54c896-af61-11e5-81e6-82cf43672901.zip?X-Amz-Algorithm ######################################################################## 100.0% 🍺 /usr/local/Cellar/mas/1.0.2: 2 files, 4.0M, built in 10 seconds % mas Available commands: account Prints the primary account Apple ID help Display general or command-specific help install Install from the Mac App Store list Lists apps from the Mac App Store which are currently installed outdated Lists pending updates from the Mac App Store upgrade Performs all pending updates from the Mac App Store version Print version number
あっさり完了。ためしに、インストールされているアプリの一覧を出力するmas list
を使ってみます。
% mas list 404647179 OmniOutliner Pro 442947586 Invisor 406056744 Evernote 409183694 Keynote 417375580 BetterSnapTool 409789998 Twitter 425424353 The Unarchiver 682658836 GarageBand 890031187 Marked 2 443987910 1Password 992076693 MindNode 409203825 Numbers 409201541 Pages 408981434 iMovie 417568953 Jedit X Plus 477163052 Linguan 803453959 Slack 445189367 PopClip 568494494 Pocket 449589707 Dash
おお。ちなみにこのIDは、アプリのリンクURLに書かれているIDと同じで、StoreServices.framework
プライベートフレームワークにあるSSPurchase
クラスから得られる情報でした。「Keynote」や「Dash」のようなアプリ名を使うのでは他のアプリと被ってしまうことも多いので、妥当ですね。
インストールするときは、リストの最初にある数値を使う必要があるとのことです。Linguan(ID:477163052)を一度アンインストールして、インストールしてみます。
% mas install 477163052 ==> Downloading Linguan ==> Installed Linguan
初回はAppStoreアプリが開いてパスワードの入力を求められましたが、あとはターミナル上にプログレスが表示され、やがてインストールが終わりました。むむ、これは便利です。
spacemacsのトラックパッドスクロール量を調節する
spacemacsが思いのほか使いやすかったので、これまで育ててきた.emacsをバッサリ捨てて、乗り換えることにしました。思っていた以上にspacemacsは至れり尽くせりで動いてくれたので、
spacemacsはユーザー様の設定を~/.spacemacsの末尾、dotspacemacs/user-config
で行いますので(user-init
もあるけれども、最後の最後に実行されるのがuser-config
の方)、幾つかの設定を選んで持ってくることにしました。
(defun dotspacemacs/user-config () "Configuration function for user code. This function is called at the very end of Spacemacs initialization after layers configuration. You are free to put any user code." ;; ⌘-↑/↓でファイルの先頭・末尾へ移動 (global-set-key [s-up] 'beginning-of-buffer) (global-set-key [s-down] 'end-of-buffer) ;; ⌘-C-←/→でバッファ履歴を行き来する (global-set-key [C-s-left] 'previous-buffer) (global-set-key [C-s-right] 'next-buffer) ;; ⌘-C-↑/↓でヘッダファイルとソースファイルを切り替える (global-set-key [C-s-up] 'ff-find-other-file) (global-set-key [C-s-down] 'ff-find-other-file) ;; ⌘-rでファイルの再読み込み (global-set-key (kbd "s-r") 'revert-buffer) ;; トラックパッド用のスクロール設定 (defun scroll-down-with-lines () "" (interactive) (scroll-down 3)) (defun scroll-up-with-lines () "" (interactive) (scroll-up 3)) (global-set-key [wheel-up] 'scroll-down-with-lines) (global-set-key [wheel-down] 'scroll-up-with-lines) (global-set-key [double-wheel-up] 'scroll-down-with-lines) (global-set-key [double-wheel-down] 'scroll-up-with-lines) (global-set-key [triple-wheel-up] 'scroll-down-with-lines) (global-set-key [triple-wheel-down] 'scroll-up-with-lines) )
XcodeやSafariと同じように⌘-←/→
や⌘-↑/↓
でバッファを移動できるようにすること、トラックバッドの反応がデフォルトで良すぎるため、スクロール量を3行ずつに抑え込んで(人間が追えるスピードで)なめらかにしたことがポイントです。
std::basic_stringの「&foo[0]」は何を返すか
とあるコードを読んでいたら、
std::string foo = "abc"; printf("[%s]\n", &foo[0]); // [abc]
みたいな書き方をしていて「ん?stringなのに??」と思ったので、きちんと調べてみました。結論を書いてしまうと、
- 配列添字の演算子「
[]
」は、アドレス取得の演算子「&
」よりも優先度が高い std::basic_string
型の配列添字演算子はc_str()
の返す値を0番目としたi
番目の要素を返すよう、演算子オーバーロードされている
というルール2つによって、正しくC文字列のアドレスが返せているとわかりました。
…でも、こういう場合はstd::basic_string::c_str()
を使うべきですよね、見にくいったらありません。
(ようやく)SwiftのflatMap()を追いかける
前回(といっても大分前になってしまいましたが)、flatMap==flatten(map())であることから、flatMap()の準備としてflatten()を先に読みました。
Swiftのflatten()
は平坦化を直ちに行うのではなく、要素が必要になった時にはじめて処理が実行されるよう、遅延評価の仕組みが取り入れられたのでした。この「元配列を別視点から見られるようにする」という仕組みによって、実行コストを抑えたまま平坦化を実現する面白いメソッドになっていることが分かりました。
そんなflattenの振る舞いを踏まえて、今回flatMap()
に進みます。SwiftのflatMap()
は、はたしてどのような実装になっているのでしょうか。
…とその前に、ここまでにmap,flattenを見てきたところとして残っている疑問がありますので、先に書き出しておきます。
flatten()
の返す型はflatMap()
のそれとは異なるが、いつ、どうやって変換しているかflatten()
のunderestimateCount()
は常に0を返すけれど大丈夫?
どちらもflatMapを読んで解決できるといいのですが…。それでは読んでみましょう。
flatMap()を提供している型
flatMap()は、Optional
, ImplicitlyUnwrappedOptional
、SequenceType
の3つの型で利用できます。ソースコードの定義箇所は、それぞれ以下のとおりです。
Optional
: stdlib/public/core/Optional.swiftImplicitlyUnwrappedOptional
: stdlib/public/core/ImplicitlyUnwrappedOptional.swiftSequenceType
: stdlib/public/core/SequenceAlgorithms.swift.gyb
さらには、遅延評価型のLazySequenceType
とLazyCollectionType
でも定義されているので、合計5箇所です。
LazySequenceType
: stdlib/public/core/flatMap.swiftLazyCollectionType
: stdlib/public/core/flatMap.swift
OptionalとImplicitlyUnwrappedOptionalのflatMap()
Optional
とImplicitlyUnwrappedOptional
の2つは、以前の記事で読んでいるので省略します。
やっていることはとても単純で、アンラップしたオブジェクトに関数を適用して返すことをしていたのでした。
SequenceTypeのflatMap()
SequenceType
のflatMap()は、SequenceAlgorithms.swift.gybの中で、SequenceTypeのextensionとして一般化された内容で記されています。2つあり、それぞれ短い内容です。
// 1つ目 @warn_unused_result public func flatMap<S : SequenceType>( transform: (${GElement}) throws -> S ) rethrows -> [S.${GElement}] { var result: [S.${GElement}] = [] for element in self { result.appendContentsOf(try transform(element)) } return result }
// 2つ目 @warn_unused_result public func flatMap<T>( @noescape transform: (${GElement}) throws -> T? ) rethrows -> [T] { var result: [T] = [] for element in self { if let newElement = try transform(element) { result.append(newElement) } } return result }
ほとんど同じ内容ですが、1つ目がtransform引数の戻り型が「S
」であることからnilを返さないこと、2つ目は逆に「T?
」となっていて、Optionalで返すことができます。
ここでの${GElement}
は、同gybファイルの先頭でGenerator.Element
の言い換えであると定義されているものです。
# We know we will eventually get a SequenceType.Element type. Define # a shorthand that we can use today. GElement = "Generator.Element"
要素をtransform()
に渡しつつ、その結果をresultにappend()
あるいはappendContentsOf()
で追加しています。言うなればflatMap()
というよりconcatMap()
と表現した方がしっくりくる感じです。
LazySequenceTypeとLazyCollectionTypeのflatMap()
flatMap.swiftという、そのものの名前のファイルでは、LazySequenceType
とLazyCollectionType
のflatMap()が定義されています。これらは関数宣言が複雑に見えますが、やっていることはどれも同じで次の1行が記述されているのみです。
return self.map(transform).flatten()
これが1番シンプルですね。flatMap()の由来である「flatten(map(x))」そのものになっています。
まとめ
これまで長々とflatMap()に至る実装を追いかけてきましたが、ようやく一区切りつけることができました。
flatten(map(x))が由来のflatMap()ですが、SwiftではLazySequenceTypeとLazyCollectionType以外はflatten()を使っておらず、それぞれの型で異なる実装になっていることがわかりました。実装を区別しているのは、その方がパフォーマンスが良いのかもしれませんし、あるいは疑問の一つだったflatten()の特性「underestimateCount()が0を返す」から、期待する結果をベースに実装を考えているのかもしれません。
細かな不明点・疑問点は残っていますが、それはおいおい調べていこうと思います。
おまけ
RxJS, RxSwiftなどReactive Programmingライブラリのドキュメントで、flatMapを表すとても分かりやすい図があったので引用します。
配列(ストリーム)の要素に対して順に処理を実施し、処理では任意型の配列を返します。そして各結果をflattenして、最終的な別のストリームを返します。