Tokyo iOS Meetup で発表してきました
Tokyo iOS Meetupというイベントの主催者に声をかけられて、tvOSアプリの開発について発表してきました。 www.meetup.com
内容は、先日のyidevで発表した内容を最新の情報で更新して、新たに得たtipsを追加したものになります。
なおtvOSのUIKitとAVKitについて下記の3つ話したのですが、こちらはスライドではなく口頭でパパッと説明しちゃったので、別途記事に起こそうと思っています。
- UITabBar
- UITableVIew
- AVPlayerViewController
英語で発表するのなんてほとんど経験がなかったのですが、まあプログラミングネタは半分以上英語だし伝わるだろくらいのテンションで乗り込みました。 実はちょっと心配していた質疑応答も滞りなくこなすことができてよかったです。
勉強会に参加するということ
今回英語で発表してみて、なんとなくこういう勉強会についての想いを綴りたくなったので、書いてみます。
「そんないきなり知らない勉強会によく飛び込んでいけるね」と、会社の面談や家でもよく言われます。 最初こういうのに参加し始めた頃は確かに緊張しましたけど、だんだん「ああみんなオフ会みたいなもんなんだな」というのがわかってくるので、そんなに気負うこともないんですよね。 隣の席の人に話しかけたら実は既にTwitterでフォロワー同士だったとか。 基本開き直って話しかける勇気だけ持っていけば楽しめるはずです。 勉強会に行く意義?楽しいからでしょ。
ちなみに英語縛りの場合ハードルが高いかもしれませんが、英語圏の人は一般的に明るくて話好きな人が多いです。 どう話しかけたらいいかわからない人は、まずは主催者に「Thank you for great Benkyokai」とか話しかけましょう。Benkyokaiという単語を使うのがポイントです。これで相手は「あ、こいつ英語苦手なんだな」と察してくれます。私がよく使う手です。
さらに主催者というのはほぼ例外なく面倒見が良いしだいたい日本語ができるので、英語が話せなくてもなんとかなります。 どうしても通じなかったら、諦めて日本語に切り替えましょう。きっと相手が困惑して近くの日本人を引っ張ってきてくれるはずです。終わる頃には、言葉なんて関係ないんだな、ということがわかると思います。
もちろん面倒くさければ無理にコミュニケーションを取る必要もないと思います。彼らは本当に議論するのが好きなので、議論が白熱してきたら私はだいたい「まーた始まったよ」とか思いながらMacを開いてブログを読んだりしています。今回はBDDやTDDをどうやるかでアツくなっていました。
仮に一言も理解できなかったとしても、そこに行ったということはあなたの新しい行動なので、いいことしかないと思います。まあ理解できた方が楽しいと思いますけどね。
ちなみに私の英語に関しては学生時代に10ヶ月ホームステイした経験がアドバンテージにはなっています。が、流暢とは言い難いレベルだと思います。 TOEIC(Reading & Listening)は810点ですけどWWDCの動画とかHuluの映画やドラマを見まくっているせいでちょっと耳がいいだけです。 New York Timesとか読めない。 基本単語並べとけば伝わるだろくらいのノリです。
普段はこういうところでイベントを探したり企画したりしているのですが、
英語話者が集まる勉強会やイベントはこちらで探せそうでした。今回のiOS Meetupもこちらを長いこと利用しているようです。「代々木公園でお花見しようず!」みたいな集まりもありますね。
どのサイトも、イベントの後に参加者同士のつながりを促進する仕組みが用意されています。日本では調整さんなどのサイトが今でも活躍していると思いますが、こういうサイトを使うと運営も楽だし、コミュニティも成長しやすいと思います。Facebookグループでもいいですが、Facebookやりたくない人というのも一定数いると思いますので。
筒がない感じでつらつらと書いてみました。
ジャズのアドリブもそうなんですが、英語はとにかく基礎練習と同じくらい、場数を踏むことが重要です。会話というのは相手がいないと成り立たないので、単語をたくさん覚えているだけでは意味がないのです。
別に私は英会話の先生でもなんでもないですが、英会話教室に通わなくても、エンジニアの皆さんはまずはこういう勉強会に顔を出してみてはいかがでしょうか?
enumの値チェック
enumをrawValueから生成するときに、値を制限したいことはよくあると思います。
Himotokiと組み合わせることを前提に、rawValueが不正だった場合に詳細なエラーメッセージを含むDecodeError.TypeMismatch
をthrowするinitializerを作ったので共有します。
なおRawValueがIntの場合限定です。有効範囲を0から11として無理やりinitializerを呼んでみて、ダメだったらエラーにするというなんとも無理やりな実装です。
こんな風に使います。
enum Status: Int { case Initial = 0, Active = 1, Inactive = 2 } struct Bar: Decodable { let status: Status static func decode(e: Extractor) throws -> Bar { return Bar( status: try Status(int: try e <| "status", keyPath: "status") ) } }
以下、init(int:)
の実装です。
import Foundation import Himotoki extension RawRepresentable where Self.RawValue == Int { /// Failable initializer for any RawRepresentable(such as enum) types /// If passed rawValue didn't match expectation, /// throws detailed error with `expected` and passed in `keyPath`. /// /// - parameter int: the rawValue /// - parameter keyPath: /// - throws: DecodeError.TypeMismatch /// - seealso: for Error type, see Himotoki init(int: Self.RawValue, keyPath: KeyPath = "") throws { guard let _ = Self(rawValue: int) else { throw DecodeError.TypeMismatch(expected: expectedIntValues(Self.self), actual: "\(int)", keyPath: keyPath) } self.init(rawValue: int)! } init?(optionalInt: Self.RawValue?, keyPath: KeyPath = "") throws { guard let int = optionalInt else { return nil } try self.init(int: int) } } private func expectedIntValues<T: RawRepresentable where T.RawValue == Int> (type: T.Type) -> String { return (0...11) /// Note: Assuming that the valid rawValues are between 0 to 11 . .flatMap{type.init(rawValue: $0)} .reduce("") {acc, e in "\(acc == "" ? "" : acc+",")\(e.rawValue)"} }
フィードバックいただけると嬉しいです。
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 をインストールした
くらいなのですが。
原因不明なのですがひとまず。