■
RxSwiftを勉強していますが、なかなか理解が進まないシリーズです。
RxSwift / RxCocoaのバージョンは2.2を対象にしています。
学習の手始めに「UIButton
がタップされたら、画面上にあるラベルをhidden
にする」というごくごく簡単な処理を書いてみようと思ったのですが・・・。どうにか動くものはできたものの、まだまだ予断を許さない状況です。
- ボタンがタップされたことを伝える
rx_tap
に、ラベルを隠すrx_hidden
を繋ぐだけでいいんじゃない?…と思ったけれど、タップと隠すのは別のオブジェクトだ。どうやってつなぐんだろう。 - どうやら別の
Observable
につなぐ場合はbindTo()
を使うと良さそうだ。でも、あれ?下のコードみたく書いてもエラーが出るだけだぞ?
button.rx_tap.bindTo(label.rx_hidden) // -> error: cannot convert value of type 'AnyObserver<Bool>' to expected argument type 'Variable<Void>' (aka 'Variable<()>')
- RxCocoaのサンプルを見ると
bindTo()
する前にmap()
を挟んでいるのが多いけれど、これはどういう意味だろう
と紆余曲折の結果、次のコードで動くようになりました。
button.rx_tap.map { true } .bindTo(label.rx_hidden) .addDisposableTo(bag)
問題点はなんだったかというと、
rx_hidden
はAnyObserver<Bool>
という、Boolを型とするObservable
でないといけなかったmap()
でtrue
を返しているのは、タップというUIControlイベントをtrue
/false
に型変換するため
ということでした。深く考えずつないでいけるかと思いきや、型の安全さにともなう制限があったわけですね。
まだまだ先は長そうです。
Emacsで選択した領域を検索対象にする
EmacsでC-s
で検索するときに領域が選択されていたら、その領域を検索対象として検索するマクロを、自分の.emacsに追加しました。
;; 選択した領域を使ってインクリメンタル検索を実行 (defadvice isearch-mode (around isearch-mode-default-string (forward &optional regexp op-fun recursive-edit word-p) activate) (if (and transient-mark-mode mark-active (not (eq (mark) (point)))) (progn (isearch-update-ring (buffer-substring-no-properties (mark) (point))) (deactivate-mark) ad-do-it (if (not forward) (isearch-repeat-backward) (goto-char (mark)) (isearch-repeat-forward))) ad-do-it))
C-s C-w
で単語を検索対象にできますが、これだとカーソルよりも前にあった内容は選択されないので不便に感じていました。そもそも検索しようとしてキーワードを選ぶのではなく、キーワードに注目してから検索するケースは多いので、なかなか便利です。
自前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行ずつに抑え込んで(人間が追えるスピードで)なめらかにしたことがポイントです。