MacのFinderのTags
仕事のプロジェクトでFinderを使いこなしている人がいたので、仕組みについてちょっと調べてみました。
こういうやつです。
右クリックのメニューからタグを選択できます。
ターミナルで ls
すると、@
というStickyBitがついています。
$ ls -l total 40 -rw-r--r--@ 1 toshi0383 staff 168 Sep 30 18:13 Bar.java -rw-r--r--@ 1 toshi0383 staff 205 Dec 17 13:00 Foo.java -rw-r--r--@ 1 toshi0383 staff 224 Jul 5 2015 Hello.java -rw-r--r--@ 1 toshi0383 staff 938 Jul 5 2015 README.md -rw-r--r-- 1 toshi0383 staff 4 Sep 30 15:08 version
Tagをつけてもファイル自体の差分は出ないようです。
$ git status On branch topic/1236_2 nothing to commit, working directory clean
コマンドラインからも、 xattr
というコマンドでTagを含むmetadataを編集できるようです。manページによると、これらのメタデータはファイル自体には含まれない、ということでした。
Extended attributes are arbitrary metadata stored with a file, but separate from the filesystem attributes (such as modification time or file size).
どこかにMapping情報を持っているのでしょうね。
ファイルを探すとき、色でイメージできるのはかなり直感的な操作になると思います。仕事は赤、趣味は緑、みたいな。これから使っていこうかな。
CoreAnimatorを使ってみました
本日から始まったtry! Swiftカンファレンスで@TimOliverAU が紹介していたCoreAnimator。気になったので早速購入して使ってみました。
https://itunes.apple.com/jp/app/core-animator/id934434650?l=en&mt=12&at=10l8JW&ct=hatenablog
たっけー。ま、自分でもアプリ作るし、先行投資ってやつです。
まずは、こちらのチュートリアルを3秒くらいで適当にご覧ください。ちょー便利そうですよね。
こちらは早速自分でも使ってみた様子です。最新版はUIの使い勝手も向上しているようです。この状態で右上のExportをすると、
Untitled from Toshihiro Suzuki on Vimeo.
こんなパネルが出てきました。いろいろ設定できるようです。このままExportを押すと、
こんな感じで、200行くらいのUIViewサブクラスが生成されました。
swiftfile from Toshihiro Suzuki on Vimeo.
キャンバスのサイズがそのままUIViewのRectになるようですので、正確に調整した方が良いと思います。解像度別に画像を用意したい場合は、最初CoreAnimatorに貼り付ける画像ファイル名に@3x
のsuffixをつけておけば、Exportの時に @2x
と @1x
のimageも同時に縮小して書き出してくれます。images
というフォルダに書き出され、生成されたコードの方は @1x
サイズのものを参照するため、最終的にはこれらを Assetカタログに移して、SwiftGenでenumを生成して、生成されたコードをごにょって、とかは必要になるでしょう。
http://www.coreanimator.com/support/#canvas
そのままXcodeに突っ込んだところ、シミュレータでもちゃんと動きました。
app from Toshihiro Suzuki on Vimeo.
似たようなアプリで、QuartzCode というのもあるようです。こちらはCAReplicatorLayerなどもサポートしているようです。いやーやりますねえ。
QuartzCode - Turn your animations to objective-c or swift OS X / iOS animations code
明日も早いのにこんな時間になってしまいました。ひとまずCoreAnimatorさえあれば、CoreAnimationがグッと身近になる気がしました。複雑なアニメーションでも臆せずチャレンジできそうです。皆さんも使ってみてはいかがでしょうか?
それでは!
参考
TVMLKitchen
こんにちは、皆さんAppleTV楽しく使っていますか?私はなんと毎日使っています。なぜなら案件についているからです!
というのは実は(!)冗談で、今このゲームにハマっているからです。
Gameloft Video Game Developer Worldwide
Audiのファミリーカーでマルチプレイに参戦し、速そうなクルマを追い抜かして楽しんでいます。AppleTVのリモコンはハンドルにもなるんですよ!ご存知でしたか?
ちなみにハンドルネームは「練馬最速の男」です。周辺地域でのランキングは7位くらいです。ヨロシクお願いします。
余談はさておき、今日は最近作ったライブラリの紹介をしたいと思います。
年末にTVMLとネイティブアプリのハイブリッドを実現する方法について発表したのですが、その仕組みを簡単に使えるようにしたものになります。
使い方は簡単で、AppDelegateのdidFinishLaunchingWithOptions
でこんなおまじないを唱えるだけで、
Kitchen.prepare(launchOptions)
あとはこのようにどこからでも簡単にTVMLのページを表示することができます。
Kitchen.serve(jsFile: "Sample.xml.js")
上は、アプリのMainBundleのSample.xml.js を読み込む例ですが、MainBundleからでなくても、サーバのURLを指定したり、
Kitchen.serve(urlString: "http://.../Catalog.xml.js")
xml文字列を直接読み込ませたり、
let xmlString = // get XML string Kitchen.serve(xmlString: xmlString)
いろいろな使い方ができます。
使用上のポイントがいくつかありますので説明します。
UINavigationControllerがTVMLと共有される
まず1つ目は、実装の都合によりnavigationControllerがTVML側と共有になるということです。同じUIWindowにある場合は、navigationControllerは基本的にTVML側と共有になります。
つまりTVML => Native => TVML と遷移した場合は、Remote(AppleTVのリモコン) のMenuボタンでバックすることができるわけです。なんて素敵なんでしょう!
共有のnavigationControllerにアクセスするにはちょっとコツがあって、UINavigationControllerを直接辿れる場合は、いつも通り下記のようにすることができますが、
func showDetailViewController() { let vc = DetailViewController() navigationController.presentViewController(vc, animated: true, completion: nil) }
UINavigationControllerのインスタンスが辿れない場合は下記のようにします。
Kitchen.navigationController.presentViewController(vc, animated: true, completion: nil)
actionHandler
もう1つは、TVML側で使用するJavaScriptは、基本的には最初のおまじないの時にしか注入できないということです。つまり、Kitchen.serve(urlString: "http://..../Catalog.xml.js")
というような形でTVMLのページを表示したとして、このボタンを押したらどうするということは、ライブラリで独自に提供するactionHandlerの仕組みを利用して、起動時に定義しておく必要があるということです。こんな風に。
Kitchen.prepare(launchOptions, actionIDHandler:{ actionID in switch actionID { case "openHogeViewController": print("Hoge") default: print("Hage") } })
actionIDは、TVML側で次のようにして定義します。
<lockup actionID="openHogeViewController" titleId="1234"> <img src="${this.BASEURL}music_1.lcr" width="308" height="308" /> <title class="whiteText">Title 1</title> </lockup>
これで、actionIDが設定された要素をClickすると、actionHandlerがactionIDの値を引数にして呼び出される、というわけです。
なぜTVMLなのか
TVMLはマークアップ言語ですので、HTMLがちょっとかけるくらいのデザイナーの方でもとっつきやすいと思います。簡単なページであればTVMLでサクッと作ってしまえば工数の節約になりますし、後から運用ができるというのも利点だと思います。まああまり複雑な使い方はせず、お知らせやライセンス情報のページを表示する、くらいにしておくのが幸せだと思います。
以上になります。実はデータ構造を渡すだけで複雑なビューも実現してくれるレシピ機能の実装も目論んでいたのですが、そこまでするならNativeでも良さそうなので、一旦途中でやめています。皆さんのコントリビュート、お待ちしています。
追記
ちなみにTVMLKitchen というネーミングは、jpsim氏のSourceKitten を真似しました。美味しそうでしょう?
テストコードを読んでライブラリの難しいコードを理解する
こんにちは、@toshi0383 です。
この1年間振り返ると様々なライブラリにお世話になってきました。ライブラリは中身の実装は知らなくても使えるよう、「まアいつものアレでしょ」という処理が抽象化されているというのが便利な点なわけですが、ソースコードを読んで動きを理解する必要がどうしても出てくると思います。ドキュメントが充実しているライブラリばかりではないですしね。今日はそんな時に効率よく理解を深めるために私が実践している方法をご紹介します。
ズバリ一言で、「テストコードを読め」です。
例えば、このReactiveKitの処理があります。
var operation: Operation<Int, TestError>! operation = create { observer in observer.next(1) observer.next(2) observer.next(3) observer.success() simpleDisposable = SimpleDisposable() return simpleDisposable }
はじめちょっと何をやっているのかわからなかったのですが、対応するテストコードはこうなっていました。
var disposable: DisposableType! disposable = operation.observeNext(on: ImmediateExecutionContext) { observedEvents.append($0) } ... ... expect(observedEvents) == [1, 2, 3]
これだけ見れば、observeNextにImmediateExecutionContext とかいうのを渡すとoperationが発火して、値が観測できるようだ、ということがわかります。 このレベルであれば、下手にググるよりはテストコードを読んだ方が、素早く正しい使い方がわかるわけです。
もう一つ例を載せておきます。SwiftStateのテストコードです。
var invokeCount = 0 let machine = StateMachine<MyState, NoEvent>(state: .State0) { machine in // add 0 => 1 => 2 machine.addRouteChain(.State0 => .State1 => .State2) { context in invokeCount++ return } } // tryState 0 => 1 => 2 machine <- .State1 machine <- .State2 XCTAssertEqual(invokeCount, 1, "Handler should be performed.")
addRouteChainは、指定された遷移通りに状態が遷移した時に一度だけCallbackを呼び出す、ということが理解できますね。
そんなところで、我々はコードを読み書きしているわけで、コードの解説記事を読むくらいだったら、コードを説明しているコードを読んだ方が早いケースもそれなりにあるかな、と。まあ後は、気軽に 作者に聞いちゃう、ということで、よろしくお願いいたします。
草々
Swift Package Manager のPackageがlinkerエラーでビルド失敗する
久しぶりにSwift Package Managerを触ったら、 この構成で、
$ ls Package.swift main.swift $ cat main.swift print("hello") $ cat Package.swift import PackageDescription let package = Package( name: "Queue", dependencies: [] )
こんなエラーが出るようになりました。
$ swift build Undefined symbols for architecture x86_64: "__TFSSCfT21_builtinStringLiteralBp8byteSizeBw7isASCIIBi1__SS", referenced from: _main in main.swift.o "__TFs5printFTGSaP__9separatorSS10terminatorSS_T_", referenced from: _main in main.swift.o "__TIFs5printFTGSaP__9separatorSS10terminatorSS_T_A0_", referenced from: _main in main.swift.o "__TIFs5printFTGSaP__9separatorSS10terminatorSS_T_A1_", referenced from: _main in main.swift.o "__TMSS", referenced from: _main in main.swift.o "__TTSg5P____TFs27_allocateUninitializedArrayurFBwTGSax_Bp_", referenced from: _main in main.swift.o "__TZvOs7Process11_unsafeArgvGSpGSpVs4Int8__", referenced from: _main in main.swift.o "__TZvOs7Process5_argcVs5Int32", referenced from: _main in main.swift.o "_globalinit_33_1BDF70FFC18749BAB495A73B459ED2F0_func5", referenced from: _main in main.swift.o "_globalinit_33_1BDF70FFC18749BAB495A73B459ED2F0_token5", referenced from: _main in main.swift.o ld: symbol(s) not found for architecture x86_64 <unknown>:0: error: link command failed with exit code 1 (use -v to see invocation) <unknown>:0: error: build had 1 command failures error: exit(1): ["/Library/Developer/Toolchains/swift-2.2-SNAPSHOT-2016-01-11-a.xctoolchain/usr/bin/swift-build-tool", "-f", "/Users/toshi0383/dev/tmp/sample-package/.build/debug/Queue.o/llbuild.yaml"]
-v
をつけてビルドしたらこういうコマンドがこけていたようなので、
/Library/Developer/Toolchains/swift-2.2-SNAPSHOT-2016-01-11-a.xctoolchain/usr/bin/swiftc -o /Users/toshi0383/dev/tmp/sample-package/.build/debug/Queue /Users/toshi0383/dev/tmp/sample-package/.build/debug/Queue.o/main.swift.o -target x86_64-apple-macosx10.10 -Xlinker -all_load -g -sdk /Applications/Xcode-beta.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.11.sdk -L/usr/local/lib
いろいろ試しているうちに、最後の -L/usr/local/lib
というのをなくしたらビルドが通りました。
$ /Library/Developer/Toolchains/swift-2.2-SNAPSHOT-2016-01-11-a.xctoolchain/usr/bin/swiftc -o /Users/toshi0383/dev/tmp/sample-package/.build/debug/Queue /Users/toshi0383/dev/tmp/sample-package/.build/debug/Queue.o/main.swift.o -target x86_64-apple-macosx10.10 -Xlinker -all_load -g -sdk /Applications/Xcode-beta.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.11.sdk -L/usr/local/lib $ .build/debug/Queue hello
参考までに記事にしました。
前回触った時からしたことといえば、
- Swiftをビルドした
- Xcode7.3beta をインストールした
くらいなのですが。
原因不明なのですがひとまず。
sniper, the OSX app terminator in Swift
sniper
というコマンドラインツールをSwiftで作ったので、作る過程で得られた知見も交えて紹介したいと思います。
TL;DR
sniper
は、アプリのプロセスを探して殺すことができる簡単なコマンドです。bin/sniper
をどこかにコピーして使ってください。sniper
はCIプロセスで使うとたまに便利だと思います。sniper
はOSX以外のアプリは見つけられないし殺せません。- アプリのプロセスを殺すにはNSRunningApplicationを使いました。
- コマンドラインツールにはDynamic Frameworkは組み込めないため、壮大なワークアラウンドが必要。(なので諦めましょう。)
- サバゲーが楽しみすぎて
sniper
とかちょっと恥ずかしい名前つけてしまった。
背景
下記の課題がありました。
- UIテストをCIプロセスに組み込みたいが、初回起動時にしか出ないダイアログとかがある
- 使用するSimulatorを決めてアプリを使い回すよりは、毎回リセットして初回起動として扱いたい
- Simulatorをリセットするには、泥臭いAppleScript を書くか、 Simulatorを削除(
rm -rf ~/Library/Developer/CoreSimulator/Devices/*
) するしかない模様だった - AppleScriptは書きたくない
- でもSimulatorが起動している状態で上記の
rm
コマンドで削除すると、次回ちゃんと起動しなくなってテスト失敗する - Simulatorを駆逐したい
というわけで、Simulatorを殺すうまい方法はないかと調べていたわけです。
ps
コマンドからの kill ${pid}
のことを思い出したんですが、なんとなくOSXではご法度な気がするし。。
で、見つかりました。
NSRunningApplication
NSRunningApplication というのがあって、これに terminate()
というメソッドがあります。こいつを取れれば、殺せそうです。
取得する方法はいくつかあるみたいで、
NSRunningApplication.runningApplicationsWithBundleIdentifier(bundleId)
もしくは
NSWorkspace.sharedWorkspace().runningApplications
でもこれで本当に自分以外のプロセスも取れちゃうんでしょうか。やってみましょう。
$ echo "import AppKit; NSWorkspace.sharedWorkspace().runningApplications.forEach{print(\$0)}" | xcrun swift Welcome to Apple Swift version 2.1 (700.1.101.6 700.1.76). Type :help for assistance. <NSRunningApplication: 0x10060aa50 (com.apple.loginwindow - 89)> <NSRunningApplication: 0x10060aae0 (com.apple.inputmethod.Kotoeri - 271)> <NSRunningApplication: 0x10060abe0 (com.apple.internetaccounts - 277)> <NSRunningApplication: 0x10060ace0 (com.apple.AirPlayUIAgent - 279)> <NSRunningApplication: 0x10060ade0 (com.apple.iChat - 296)> <NSRunningApplication: 0x10060aee0 (com.apple.systemuiserver - 303)> <NSRunningApplication: 0x10060afe0 (com.apple.dock - 302)> <NSRunningApplication: 0x10060b0e0 ((null) - -1)> <NSRunningApplication: 0x10060b1e0 (com.apple.finder - 304)> <NSRunningApplication: 0x10060b2e0 (com.apple.sharingd - 309)> <NSRunningApplication: 0x10060b3e0 (com.adobe.accmac.ACCFinderSync - 312)> <NSRunningApplication: 0x10060b4e0 (com.apple.dock.extra - 315)> <NSRunningApplication: 0x10060b5e0 (com.apple.Spotlight - 317)> <NSRunningApplication: 0x10060b6e0 (com.apple.storeuid - 335)> <NSRunningApplication: 0x10060b7e0 (com.apple.MailServiceAgent - 458)> <NSRunningApplication: 0x10060b8e0 (com.apple.iTunesHelper - 486)> <NSRunningApplication: 0x10060b9e0 (com.kensington.trackballworks.helper - 488)> <NSRunningApplication: 0x10060bae0 (com.security.apple.Keychain-Circle-Notification - 468)> <NSRunningApplication: 0x10060bbe0 (com.apple.TISwitcher - 487)> <NSRunningApplication: 0x10060bce0 (com.apple.notificationcenterui - 470)> <NSRunningApplication: 0x10060bde0 (com.fabriceleyne.hourlite - 491)> <NSRunningApplication: 0x10060bee0 (com.linebreak.CloudAppMacOSX - 492)> <NSRunningApplication: 0x10060bfe0 (com.apple.wifi.WiFiAgent - 481)> <NSRunningApplication: 0x10060c0e0 (com.adobe.acc.AdobeCreativeCloud - 478)> <NSRunningApplication: 0x10060c1e0 (com.adobe.AdobeIPCBroker - 504)> <NSRunningApplication: 0x10060c2e0 (com.adobe.acc.AdobeDesktopService - 514)> <NSRunningApplication: 0x10060c3e0 (com.adobe.acc.HEXHelper - 515)> <NSRunningApplication: 0x10060c4e0 (com.adobe.accmac - 517)> <NSRunningApplication: 0x10060c5e0 ((null) - -1)> <NSRunningApplication: 0x10060c6e0 (com.apple.lateragent - 534)> <NSRunningApplication: 0x10060c7e0 (com.apple.cloudphotosd - 729)> <NSRunningApplication: 0x10060c8e0 (com.apple.EscrowSecurityAlert - 1049)> <NSRunningApplication: 0x10060c9e0 (com.apple.nbagent - 1341)> <NSRunningApplication: 0x10060cae0 (com.apple.Terminal - 1447)> <NSRunningApplication: 0x10060cbe0 (com.apple.iCal - 52911)> <NSRunningApplication: 0x10060cce0 (co.gitup.mac - 65045)> <NSRunningApplication: 0x10060cde0 (com.apple.Safari - 16036)> <NSRunningApplication: 0x10060cee0 (com.apple.WebKit.Networking - 16038)> <NSRunningApplication: 0x10060cfe0 (com.tinyspeck.slackmacgap - 16258)> <NSRunningApplication: 0x10060d0e0 (com.apple.qtkitserver - 16266)> <NSRunningApplication: 0x10060d1e0 (com.apple.WebKit.Plugin.64 - 16272)> <NSRunningApplication: 0x10060d2e0 (com.apple.WebKit.Databases - 84902)> <NSRunningApplication: 0x10060d3e0 (com.apple.WebKit.WebContent - 2098)> <NSRunningApplication: 0x10060d4e0 (com.github.atom - 5135)> <NSRunningApplication: 0x10060d5e0 (com.github.atom.helper - 5141)> <NSRunningApplication: 0x10060d6e0 (com.apple.coreservices.uiagent - 13010)> <NSRunningApplication: 0x10060d7e0 (com.apple.WebKit.WebContent - 13061)> <NSRunningApplication: 0x10060d8e0 (com.apple.WebKit.WebContent - 13066)> <NSRunningApplication: 0x10060d9e0 (com.apple.WebKit.WebContent - 13067)> <NSRunningApplication: 0x10060dae0 (com.apple.WebKit.WebContent - 13068)> <NSRunningApplication: 0x10060dbe0 (com.apple.dt.Xcode - 13088)> <NSRunningApplication: 0x10060dce0 (com.apple.iphonesimulator - 13512)> <NSRunningApplication: 0x10060dde0 (com.apple.WebKit.WebContent - 13632)> <NSRunningApplication: 0x10060dee0 (com.apple.WebKit.WebContent - 13633)> <NSRunningApplication: 0x10060dfe0 (com.apple.WebKit.WebContent - 13636)>
めっちゃとれちょる。というわけでこれでいけそうです。
コマンドラインツールの作成
で、CIに組み込むのでコマンドラインツールにしたかったんです。
以前 kitasuke氏のスライド を見て試したことがあって、非常に大変だったことは覚えていたのですが、まあとりあえずやってみようということで。
で、今回は案外簡単にできました。
Xcodeのプロジェクトテンプレートも Command Line Tool です!
オプションのパース用にライブラリが必要だったのですが、たまたま選んだSwiftCLI がgit-submoduleでのインストール推奨にしてくれていたため、DynamicFrameworkのための壮大なワークアラウンドが必要なかったのです。
というかまあソースコード直接突っ込めばなんでもいけるわけです。ただ、SwiftはModuleのnamespaceがあるので、ClassPrefixをつけるとかいうObjective-Cの素晴らしい文化は、基本的にはありません。よってライブラリ同士でクラス名が競合することが多く、その場合どうするかというと。。考えたくもないですね。(自分でprefixつけて回る?いやいやいや。。)
なぜDynamicFrameworkがコマンドラインツールに直接組み込めないのかというと... まあいまはそういう仕様なんです。諦めましょう。
どうしても組み込みたい方は上記kitasukeさんのslideとか、これも参考になるかと思います。
使い方
使い方は、こんな感じになりました。README.mdから抜粋です。
# List all running apps $ sniper target 37 apps found. com.apple.loginwindow: 89 . . . # Filter by keyword $ sniper target firefox 1 app found. org.mozilla.firefox: 4822 # Terminate process by bundle ID $ ./sniper shoot -b org.mozilla.firefox Terminating org.mozilla.firefox: 4822 ... # Terminate process by PID $ sniper shoot -p 4703 Terminating com.apple.iphonesimulator: 4703 ...
で、今回の目的 "Simulatorを駆逐する" を達成するには、こうすると良いと思いました。
$ sniper shoot -b com.apple.iphonesimulator # shutdown before reset(remove) all simulators $ rm -rf ~/Library/Developer/CoreSimulator/Devices/*
TravisやCircle、bitrise のようなCIサービスであればコンテナが毎回使い捨てだと思うので、なにも考えなくても毎回すべてのアプリが駆逐された状態かと思いますが、余っているMacにJenkins立てているケースも多々あるかと思いますので、そういった場合に使えるかと思います。
まとめ
というわけで、Swiftで簡単にコマンドラインツールを作ることができました。
簡単な処理だと他のscript言語の方が簡単にかけるケースの方がよっぽど多いと思いますが、Macアプリのプロセスをどうこうするとかの場合は、AppKitを使うのが一番手っ取り早そうだなというのが所感でした。
以上になります。
参考
Swift array map の性能測定の続き
前回の続き
前回の記事に対して@es_kumagai さんからフィードバックをいただきました。
今週忙しくて、やっと見る時間が取れました。
結論から言うと、今回の検証方法(ターミナルからswiftやswiftc を利用する)においては、標準のmapより速い実装が手に入りました。
たった1000件での比較ですが、こうなりました。前回から計測方法も多少改善しました。手抜きですみません。
map : 0.000211954116821289 map7: 0.000133037567138672
まあ1/10000 秒の差とか、もはやモバイル開発においてはどうでもいい話ですね。めちゃくちゃ大量のデータ扱うこともそう滅多にないでしょうし。
ボトルネックはやはりarrayの初期化だったようです。
コードはフィードバックでいただいたものを貼っておきますね。ありがとうございます。
-O
オプション
なお、-O
をつけるとDouble値の出力がおかしくなりました。
どうして。。?
$ xcrun -sdk macosx swiftc -O map.swift $ ./map === : 0.000318050384521484 map : 0.000821113586425781 map : 5.79357147216797e-05 map7: 3.00407409667969e-05 map : 5.10215759277344e-05 map7: 2.88486480712891e-05 map : 5.10215759277344e-05 map7: 2.62260437011719e-05 map : 4.98294830322266e-05 map7: 2.59876251220703e-05
計測に使用したコードはこちらにあげました。
なおコマンドラインから指定できるコンパイルオプションはこうなっているようです。
-Onone Compile without any optimization -Ounchecked Compile with optimizations and remove runtime safety checks -O Compile with optimizations
通常は-Onone
が使われているようです。
まとめ
ひとまず今回の検証方法においては、標準のmapより速い実装が手に入りました。
ちなみに、Playgroundではこうなります。
map : 0.181938886642456 map7 : 0.619832992553711
モヤっとしますね。
まだまだ不明点だらけで、勉強しないといけないことが山積みだということがわかりました。
以上です。