Bitrise-iOSをArchiveした
Archiveしました。 Bitrise-iOSは元々本家のWebページのペインポイントを解消するためのコミュニティツールとして生まれましたが、現在本家のWebページはかなり使いやすくなりました。 以前はビルドをAbortするのにビルド詳細ページを開く必要があったのですが、今はビルド一覧にAbortボタンが出ていますね。 最高です。
個人的には最もスターをいただけたOSSのひとつなのですが、そんなに思い残すこともないです。 その時必要なものを作っていけばいいのです。
その他にArchiveしたものだとこんなのもあります。 こちらは英国からコントリビューターがついてくれて、一緒に開発していました。 かなり貴重な体験でした。
tvOSにはTVMLというものがありましてね..(省略) 今もあるのかな。
それでは。
UIMenuControllerが闇だった 前編
こんな感じでカスタム項目(Print this word
)を追加するのは簡単なのですが、以下のようなケースを考えます。
Copy
などの標準項目は隠しつつカスタム項目だけ表示する- TextViewごとに表示する
UIMenuItem
を切り替える
カスタム項目入門
まずは簡単なケースですが、これで良いはずです。
override func viewDidLoad() { super.viewDidLoad() UIMenuController.shared.menuItems = [.init(title: "Print this word", action: #selector(ViewController.printThisWord))] } @objc func printThisWord() { guard let range = textView.selectedTextRange, let text = tv.text(in: range), !text.isEmpty else { return } print("text: \(text)") }
UIMenuController.shared.menuItems = [.init(title: "Print this word", action: #selector(ViewController.printThisWord))]
の部分はAppDelegate
にあっても動きます。
便利ですねUIResponder
。
Copy
などの標準項目は隠しつつカスタム項目だけ表示する
これはちょっと厄介です。
なぜかというと、 UITextView.canPerformAction(_:withSender:)
の標準実装が、以下の3つに対してはtrue
を返すからです。
copy: |
_define |
_share: |
---|---|---|
つまり UITextView
のサブクラスを作ればこれらに対してfalse
を返せることになります。パッと作るならそれでもいいと思います。
私はもう少しスケールする実装にしたかったので、Method Swizzlingを活用して以下のようなハックを実装してみました。
private var enableMenuItemsKey = 0 extension UITextView { enum MenuItem: String, CaseIterable { case copy = "copy:" case define = "_define:" case select = "select:" ...(省略) } var enableMenuItems: [MenuItem]? { set { objc_setAssociatedObject(self, &enableMenuItemsKey, newValue, .OBJC_ASSOCIATION_RETAIN) } get { return objc_getAssociatedObject(self, &enableMenuItemsKey) as? [MenuItem] } } static func swizzle() { do { let originalMethod = class_getInstanceMethod(Self.self, #selector(canPerformAction(_:withSender:)))! let swizzledMethod = class_getInstanceMethod(Self.self, #selector(swizzle_canPerformAction(_:withSender:)))! method_exchangeImplementations(originalMethod, swizzledMethod) } } @objc func swizzle_canPerformAction(_ action: Selector, withSender sender: Any?) -> Bool { let original = swizzle_canPerformAction(action, withSender: sender) guard let enableMenuItems = enableMenuItems else { return original } if enableMenuItems.isEmpty { return false // ignore original or result from subclass } return enableMenuItems.map({ $0.rawValue }).contains(action.description) && original } }
なかなかハゲしい感じになりましたが、これで標準項目は隠してカスタム項目のみにするコードがかなりスッキリしました。
Copy
だけ追加することもできました。
しかしまだこの行だけ見ると Print this word
どこからきたんだ感がすごいです。。
TextViewごとに表示する UIMenuItem
を切り替える
鬼門にやってきました。
異なる画面やUITextView
ごとに項目を切り替えたいのですが、 UIMenuController.shared
はシングルトンです。
UIMenuController.init()
を実装することはできるのですが、ランタイムでエラーになります。
2020-05-19 21:44:44.506837+0900 MenuControllerSample[43094:6685424] *** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'There can only be one UIMenuController instance.'
これでは、同じ画面に複数のUITextView
があって、それぞれでカスタム項目を出し分けたい場合の対応に困りそうです。
なおcanPerformAction
で UIMenuController.shared.menuItems
を登録し直すのは事情により禁じ手になります。。 https://github.com/cxa/MenuItemKit/blob/master/MenuItemKit/Swift/Swizzlings.swift#L31 この辺りで無限ループすることがあるので。。まあ第一ちょっと設計として分かりづらいですしね。
まだ色々試している段階なので、今回はここまでにしたいと思います。気になる方は次回の投稿をお待ちください。もしくは既に発明されている便利な車輪があるようでしたらぜひこっそり教えてください。
多分 AppDelegate
をEvent Hub的な役割にしてレスポンダチェーンに放り投げるような形がいいのかなーとか考えてます。
それでは!
Macのディスクの容量がいっぱいです
どんな大容量SSDのMacを使っていてもなぜか定期的に遭遇するこの問題。
やはりこのご時世で自宅作業が増えたことでディスク容量が増える速度も以前とは比べ物にならないようです。 今日ビルド中に残り300MBになってすごくハラハラしました。
というわけで個人的な対処方法をまとめておきます。
システム情報から管理画面を開く
最近のmacOSには便利な機能がついて、ストレージタブの「管理...」をクリックするとこういう画面が出てきます。 ここでオススメされたものを消したりします。
ファイルサイズが大きいものを表示するビューもあります。
ですが、大抵これだけでは不十分なので、以下の通り泥臭い作業が必要になります。
キャッシュ発掘作業
時間はかかりますがターミナルから以下の要領で容量を食っているディレクトリを特定するのが一番近道です。
$ du -d 1 . | sort -nr | head 99739072 . 66876560 ./Library 8222944 ./.android 7003920 ./.cocoapods 3556704 ./work 3223392 ./.gradle 2604432 ./Documents 1632552 ./.cabal 1435632 ./dev 1427656 ./Software
いうまでもないと思いますが一番左の数字がざっくりとした容量です。 -h
をつけるとGB,MBなどの表示になり見やすくなりますがあんまり気にしないと思うのでそのまま行きます。
Library/
が一番重いので、掘り下げていきます。
$ cd Library/ $ du -d 1 . | sort -nr | head 66908920 . 21692480 ./Android 20229192 ./Developer 10662456 ./Caches 10046112 ./Application Support 2207200 ./Containers 1090392 ./Ethereum 251632 ./Safari 163784 ./MediaStream 135144 ./Group Containers
大抵は Cache
とか cache
ディレクトリが見つかるので、要領を食っていて消して大丈夫そうなものはどんどん掃除していきます。自分の場合は使わなくなったツールのキャッシュとかよく残っていますね。
コマンドにまとめる
上記作業をするとなんとなく傾向が見えてくると思うので、aliasやコマンドにまとめておくと良いと思います。
自分の場合は以下のような感じで ~/.bashrc
にまとめています。
最近はiOSとAndroidを触ることが多いのでそれ関連が増えてきてました。
function removeCaches() { rm -rf `brew --cache` rm -rf ~/Library/Developer/Xcode/DerivedData/ rm -rf ~/Library/Developer/CoreSimulator/Caches/* rm -rf ~/Library/Developer/Xcode/iOS\ Device\ Logs/* rm -rf ~/Library/Caches/org.carthage.CarthageKit/ rm -rf ~/Library/Caches/CocoaPods/ rm -rf ~/.gradle/caches/* # Simulatorを個別にダウンロードした場合にdmgが溜まっていた rm -rf ~/Library/Caches/com.apple.dt.Xcode/Downloads/* }
以上の作業をこの記事を書きつつ大体1時間くらい続けて13GB程度は確保することができました。 Xcodeをアップデートする時に空き容量がXcode自体の倍はないと要領不足で失敗するので、これくらいがギリギリ許容範囲だと思います。
ご覧の通り2TBのHDDもマウントしているし500GBの外付けSSDも持っているので、もっと活用するべきなのかもしれません。 キャッシュディレクトリの変更機能とかあれば便利なのかもしれないですが、HDDをキャッシュディレクトリにしたら読み出し速度も遅いし、キャッシュとしては本末転倒は気はしますよね..
他の要領節約ハック
筆者は他にも以下のようなハックをしてdisk容量をケチケチ節約しています。ご参考まで!
- 写真ライブラリはHDD側に
- iOSシミュレータのランタイムもHDD側に
結論
会社のMacと同じように迷わず256GBのSSDにアップグレードすればよかった!と思ってますが、容量あったらあったでなぜか使い切ってしまうんですよねえ。。不思議なものです。 定期的にクリーンアップして開発中に困らないようにしたいと思います。
Carthageユーザ必携の便利スクリプトを公開しました
これは何
以下のリポジトリのcarthageディレクトリ以下に便利ツールを公開しました。
https://github.com/toshi0383/scripts
cmdshelfでのご利用が一部必須になっています。この機会にどうぞ。
https://github.com/toshi0383/cmdshelf
Carthageのイケてないところ
まずはなぜこういうものが必要だったのかの動機についてです。
手作業多い
CocoaPodsだと自動でやってくれるようなことも、Carthageでは手作業が必要になります。 単純なことしかしないのがいいところなのですが、例えば以下のような作業は結構面倒なものです。
- ライブラリ追加/削除時にBuild Phaseに
carthage copy-frameworks
を追加/削除する - 更新時に古いbcsymbolmapを探して削除する
- CodeCoverage設定問題
XcodeGenを使うと1つ目は気にしなくて良くなるのですが、どうしても後の2つは手作業と指差し確認が必要ですね。
不要なFetchが走り、しかもキャンセルできない
例えばライブラリ1つだけを更新したくて carthage update CircleProgressButton
をしても、 Cartfile
にある全部のリポジトリをfetchしてしまいます。
もちろん依存関係がある場合に必要なのはわかるのですが、明らかに依存がないことがわかっている場合は、不要な処理ですよね。私の環境だと特にRxSwiftのfetchが延々終わらないことがあったので、そうなると結構辛いです。
しかも間違ってもControl+Cでキャンセルしてはいけません。NSTaskはchild processをkillすることができないらしく、非同期のfetch&buildのプロセスがゾンビ化します。しかもガンガン標準出力に書いてきます。やめてくれ。。
こうなると、 ps
して kill
するか、終わるまでおとなしく待つしかなくなります。
解決策
carthage/update
https://github.com/toshi0383/scripts/blob/master/carthage/update
このスクリプトは上で挙げた課題のうち以下の2つに対応しつつ、 carthage update
を実行します。
- 更新時に古いbcsymbolmapを探して削除する
- CodeCoverage設定問題
どうしてもbcsymbolmapが見つからない場合もあるので、そういう場合はメッセージを出力してます。
またCodeCoverage問題が見つかった場合自動で直しますが、その場合ライブラリ管理者に連絡するよう促しています。
上記のライブラリはcodeCoverageEnabled = YESになっていました。ライブラリ管理者に連絡するべきかもしれません。
carthage/update-with-hack.sh
https://github.com/toshi0383/scripts/blob/master/carthage/update-with-hack.sh
こちらはまずCartfileとCartfile.resolvedをいじることで指定されたライブラリ以外を見ないようにして、その上で carthage/update
を実行します。終わったらちゃんとCartfileとCartfile.resolvedは元に戻して、更新を反映します。
基本的にはどちらも carthage update
と書くのと同じ感覚で
cmdshelf run carthage/update cmdshelf run carthage/update-with-hack.sh CircleProgressButton
とか書いてもらえれば便利に使えます。 tvOSの場合は
PLATFORM=tvOS cmdshelf run carthage/update-with-hack.sh CircleProgressButton
のようにプロセスに環境変数を渡します。
まあどちらのスクリプトも泥臭い処理をベタベタ書いてるだけなので、ここでの解説は省きます。 各スクリプト冒頭にドキュメントがあるので気になる方はご確認ください。
まとめ
仕事で必要に駆られて作ったものですが、公開したほうが色々いいことがあると思うので、公開できない部分は削除した上でとりあえず公開しました。
AbemaTVのiOSチームでは、Carthageライブラリ更新の際は必ず上の cmdshelf run carthage/update
を使うことになっています。
内部的に --use-binaries
や --use-ssh
を指定しているので、運用と合わない場合はごめんなさい。
多分みなさんも似たようなスクリプトを書いて運用されているとは思うので、「こうしたほうがいい」や「もっといいツールがある」などあればぜひ教えてください。
ツッコミや質問などブコメやTwitterで受け付けますのでお気軽にどうぞ。
リポジトリへのプルリクもお待ちしています。
*1:0.26.0以降のCarthageでは、CodeCoverage設定問題は解決しているようです。 https://github.com/Carthage/Carthage/issues/2056#issuecomment-329971591
*2:今 carthage update --platform ios CircleProgressButton をやってみたら不要なFetchが終わらなくなることはなかったので、もしかしたら最新版では挙動改善したのかもしれません。Carthage 0.28.0で確認しました。
iOSDC Japan 2017でLT登壇して来ました
iOSDC Japan 2017、控えめにいって最高でした😂
同じくらいの規模だと以前360iDevというUSのカンファレンスに行ったことがあってエントリも書いたのですが、ちょっと割高な印象だったのですよね。それでもまあ会場の広さは余裕があるし、とてもアットホームで雰囲気が良かったし、でもトークはどれも刺激的で、という感じでした。それと同じか私にとってはそれ以上の楽しい時間を過ごせたので、このチケットの価格はもはや恐ろしいなという感じでした。運営すごすぎ。
ところで5月に子供が産まれまして、最近はなるべく明るいうちにお家に帰って奥さんの手伝いをしています。以前にも増して勉強会に行く頻度が減っていたこともあり、ひさびさに顔を合わせる方が多く、いやーほんと、忘れられてなくて良かったと思いました。話しかけるときドキドキしました。
今回はCfPに応募を出したところLTが通りまして、以下の発表をさせてもらいました。
ただ、15分くらいならそれなりに経験はあるんですけど、そういえばLTって焦っちゃうのでもともと苦手だったのをすっかり忘れてたんですよね。マイペースなんで。。 練習はしてたし内容は伝わったかと思うのですが、最初と最後がマイペースにやりすぎてちょっとグダッとなってしまって、心もとない印象を与えてしまったかも。せっかく選んでもらったのに、悔しいです。これを糧にというか、今後は月1くらいで軽い発表をしていけるようにして、来年リベンジしたいと思ってます。 何より最終日の最後って体力がないと全然もたないので、やはり私にいま一番必要なのは筋肉だと思いました🍖
こういう大きいカンファレンスで発表側に立ったのってもしかしたら初めてだったのですが、他の偉大な発表者の方々とも距離が近くて(気のせい?)、結構いろんな方とお話しできたような気がします。新しい繋がりも私にしては結構できたような気がします。普段TwitterやQiitaでしか関わりがないと忘れがちな、それぞれの人間っぽい部分に触れることができて、やっぱりリアルでのコミュニケーションは大切だなと思いました。 @yimajo さんが実は冒頭「このセッションに来てくれてありがとうございます」のくだりで感極まっていて誤魔化すために必死だったというエピソードがあったりして、ほっこりしました。全然わからなかった笑
という感じで、様々な知見はもちろん、とても前向きなエネルギーをもらいました。 今から来年が楽しみです!
React NativeのAndroid版のJSブリッジはどうやってるの?
本日はおひさしぶりにyidev に参加。
今回も始終和やかな雰囲気で和みました。
その中で@motokiee さんのReact Nativeの発表を受け、React NativeのAndroid版はどうやってJSとブリッジしているのだろう?という疑問が私を含む一部で湧いたので、Android詳しいわけでもないのですが、軽く調査。
Android側のブリッジってどういう仕組みでやってんだろ? #yidev
— ワニ@tmk (@alligator_tama) 2017年2月11日
メモ程度ですが何かのお役に立てば幸い。
com/facebook/react/bridge
ふむ、この辺に色々ありますな。。
com/facebook/jni
お、jniがいるということは。。
ReactCommon/cxxreact/JSCExecutor.cpp
いましたーそれっぽいc++のクラス。loadApplicationScriptという関数でJSをロードしているっぽいですね。
loadApplicationScript内のJSCreateCompiledSourceCode 関数
JSCreateCompiledSourceCodeという関数が使われており、名前からしてこいつがJSから実行形態に動的にコンパイルしているっぽい。しかしこの関数、ググっても出てこない。jscというモジュールがそれっぽいのだけど。
com/facebook/react/bridge/JavaJSExecutor.java
で、こいつがJSCExecutor.cppにブリッジしている子っぽいです。アプリ側からはこいつを介してJSオブジェクト取ってきたりJSの関数実行したりとかやるわけですね。
ひとまずここまでですー。詳しい方是非とも教えてください!
SoLoader (別のライブラリ)
ちなみにこの記事の話題とは直接関係ないですが、jni層(.so)のロードにはfacebook/SoLoaderというのが使われているようでした。
GitHub - facebook/SoLoader: Native code loader for Android
READMEを読むと、
SoLoader is a native code loader for Android. It takes care of unpacking your native libraries and recursively loads dependencies on platforms that don't support that out of the box.
と書いてあり、どうやら再帰的に.soの依存関係を解決してくれるらしいです。へー何だそれどうなってんだ。
そういえばお久しぶりの投稿になりました。今後も気が向いた時にゆるく投稿していければと思います。
Bitriseをオープンソースプロジェクトで利用したい!
最近Travisのワーカーが一部で不足しており、Xcode8.1をターゲットにすると、ビルドが始まるまで1時間待たされるなんてことがありました。 これではちょっと回らないと思ったので、お試しでBitriseに乗り換えてみました。普段仕事では有料契約をして使っているので、問題ないだろう、と。 なおBitriseは、iOSとAndroidに特化したCI/Deliverlyサービス(PaaS)です。
しかし、現状オープンソースで使うには以下の問題があることが判明しました。
- ビルド結果とログはオーナー及びメンバー以外は見れない。フリープランだとオーナーに対して2名しか登録できない。しかもオーナーアカウントに対して2名までで、プロジェクトに対して2名までではない。 => 2017.1.13追記:変更されたかも
- ビルド自動再実行の機能がない。Travisだと、プルリクClose->Reopenでビルドが再スタートしますよね。
- 200 builds/month (これもオーナーのアカウント単位)の制限があり、すぐに超過してしまいそう。超過分はFailedになります。
これではコントリビュータが増えてくると使えないので、slackチャンネルで問い合わせてみました。すると、すぐに以下の回答がありました。
viktorbenei [7:50 PM] @toshi0383 thanks for the request/infos! for the first point please vote & comment here: https://bitrise.uservoice.com/forums/235233-general/suggestions/8795035-allow-public-or-publicly-viewable-apps for the auto rebuild: please create a new feature request for unlimited builds per month for OSS project: just send us an email and we can do this for you (if you have one)
というわけで
ビルド結果とログの公開
みなさんvoteよろしく!!!
2017-02-10 追記:URLが変更になりました。 discuss.bitrise.io
ビルド自動再実行機能
機能リクエストしてくれとのことなので、頑張っちゃおうかな!!1
=> 機能リクエスト投稿しました。voteよろしく!!
https://bitrise.uservoice.com/forums/235233-general/suggestions/17225678--auto-rebuild-for-a-failed-build-received-from-nobitrise.uservoice.com
2017-02-10 追記:
discuss.bitrise.io
にvoteが足りず移行されなかったので、放置することにします。多分、"allow public build" に含まれる想定でいいのだと思います。
OSSプロジェクトのビルド数制限解除
なんと、言えば対応してくれるらしい!!!神!!!
まとめ
以上、速報でした。 ちなみにBitriseのslack/emailのサポートは対応が神なので、もし何かわからないときは気軽に問い合わせてみると良いと思います。