クロージャでプロパティを初期化する

Caution! この記事はまだSwift2.2です!

普段何気なく使っているのですが、たぶん初めて見た人は混乱するだろうなと思ったので。

extension UIImage {
    private static var contentPlaceholder: UIImage = {
        return UIImage(named: "placeholder_b.png")!
    }()
    private static var personPlaceholder: UIImage = {
        return UIImage(named: "placeholder_a.png")!
    }()

    private var data: NSData {
        let provider = CGImageGetDataProvider(self.CGImage)
        return CGDataProviderCopyData(provider)!
    }

    var isPlaceholder: Bool {
        return self.data.isEqualToData(UIImage.contentPlaceholder.data)
            || self.data.isEqualToData(UIImage.personPlaceholder.data)
    }
}

一回メモリに載せたらそのあとはそのまま取っておきたいような時は通常普通にstatic変数でいいと思うのですが、初期化処理が1行で書けないみたいな時にこういう書き方ができます。(上の例は1行で書けそうですが)

なお上の場合それが目的ではないですが、lazyロードになりますね。

どこかでこの static varlazy var に再代入した後の挙動についてのスライドを見た記憶があるんですが、見つけられませんでした。

tvOS-10-Sampler

Hey guys I just created a tiny sample app demonstrating tvOS 10 new APIs.

github.com

I open-sourced it because the AVContentProposal API is still buggy and I wanted feedbacks from other developers from the world.

So this is the crash you can experience in tvOS10 Simulator. gyazo.com

The same app won't crash on the device, but user will left with weird screen like this. f:id:toshi0383:20160924190748j:plain

Sorry my AppleTV is in Japanese, but you can see the ContentProposalViewController in front of the first view.

This is the stacktrace.

2016-09-24 19:01:29.520 TVOSTest[87178:254281] Presenting view controllers on detached view controllers is discouraged <TVOSTest.ViewController: 0x7fb02c026e00>.
2016-09-24 19:01:29.543 TVOSTest[87178:254281] *** Terminating app due to uncaught exception 'NSGenericException', reason: 'Unable to activate constraint with anchors <NSLayoutXAxisAnchor:0x60800086f280 "AVFocusProxyView:0x7fb02b542db0.left"> and <NSLayoutXAxisAnchor:0x60800086f540 "AVPlayerLayerView:0x7fb02b40ec50.left"> because they have no common ancestor.  Does the constraint or its anchors reference items in different view hierarchies?  That's illegal.'
*** First throw call stack:

It's obviously AutoLayout's error, but there is nothing I can do because I'm not using any AutoLayouts in this app...

So it's depends on you whether you should use AVContentProposal or not. I will update if I found any workarounds.

Happy tvOS development!!!

ぼくのSierraアップデート記

諸事情でSierra & Xcode8に開発環境をアップグレードしたので人柱記録です。 と言っても大した問題はなく、普段使っているツールは大体対応してくれていました。 gitup も問題なしでした。 SwiftLintなど開発用のコマンドラインツール群も問題なし。 Siriを試そうと思って「Hey Siri」と言ったら家中のSiriが一斉に反応してしまって焦りました。

唯一アップグレード前に確認し忘れていたのはXVimでしたが、以下の手順の通りXcode8にインストールできました。(ElCapitanでも同じ状況だとは思います。)

XVim/INSTALL_Xcode8.md at master · XVimProject/XVim · GitHub

Xcodeを署名し直すとは随分と大胆ですね。。再署名自体は数分かかっていましたが、無事。

他にもあれ大丈夫だった?とか確認できますので、気軽に聞いてくれていいですよ。

追記

Homebrewの/usr/local問題がありましたが、すぐに解決しました。

$ brew update
Error: /usr/local is not writable. You should change the ownership
and permissions of /usr/local back to your user account:
  sudo chown -R $(whoami) /usr/local
$ sudo chown -R $(whoami) /usr/local
Password:
$ brew update
Updated Homebrew from dfcbeff to 12aad5c.

brew updateをしたら、/usr/local から /usr/local/Homebrew への移行も勝手にやってくれました。

==> Migrating HOMEBREW_REPOSITORY (please wait)...
==> Migrated HOMEBREW_REPOSITORY to /usr/local/Homebrew!
Homebrew no longer needs to have ownership of /usr/local. If you wish you can
return /usr/local to its default ownership with:
  sudo chown root:wheel /usr/local

/usr/localの権限は元に戻していいとのことだったので、戻しておきました。

$ sudo chown root:wheel /usr/local

追記 2016/10/02

ちょっと古いバージョン(7.3)のXcodeiPhoneアプリをリリースする必要があったのですが、Submitで以下のエラーになってしまいました。

xcode7 - ERROR ITMS - 90167 No. app bundles found in the package - Stack Overflow

Sierraだけで発生する問題のようです。なんと公式版でも解決されていません。。
コメントにある通り、Xcode7系でビルドしたxcarchiveを、Xcode8のOrganizerからSubmitするとうまくいきました。

RxSwiftのbindToについて

RxSwiftにはbindToが大きく分けて2種類あります。

引数にobserverを取るものと、binderを取るものです。 引数にvariableを取るものは基本observableを取るものと動きが同じです。

以下のような特徴があります。

引数にobserver/variableを取るもの

内部的に即subscribeが呼ばれる。

実装はこうなっています。(複数ありますが、簡単なほうを抜粋)

    /**
    Creates new subscription and sends elements to observer.
    
    In this form it's equivalent to `subscribe` method, but it communicates intent better, and enables
    writing more consistent binding code.
    
    - parameter observer: Observer that receives events.
    - returns: Disposable object that can be used to unsubscribe the observer.
    */
    @warn_unused_result(message="http://git.io/rxs.ud")
    public func bindTo<O: ObserverType where O.E == E>(observer: O) -> Disposable {
        return self.subscribe(observer)
    }

A=>B というシーケンスを作って即実行したい時に使えます。

引数にbinderを取るもの

内部的には引数のbinder: Self -> R のクロージャがselfを引数に実行されるだけ。

実装はこうなっています。(複数ありますが、簡単なほうを抜粋)

    /**
    Subscribes to observable sequence using custom binder function.
    
    - parameter binder: Function used to bind elements from `self`.
    - returns: Object representing subscription.
    */
    @warn_unused_result(message="http://git.io/rxs.ud")
    public func bindTo<R>(binder: Self -> R) -> R {
        return binder(self)
    }

A=>Bというシーケンスだけ作っておいて、後で別のシーケンスとつなげたり、subscribeのタイミングをコントロールしたりする時に役立ちます。

ちょっと例が雑ですが、以下のようにログイン状態の時とそうでない時でシーケンスを変えるなんて使い方はいかがでしょうか。

    var loggedIn = true
    override func viewDidLoad() {
        super.viewDidLoad()

        let number1Seq01 = number1.rx_text.bindTo(verifyString).retry(3)
        if loggedIn {
            number1Seq01
                .bindTo(doThisOnlyWhenLoggedIn)
                .bindTo(number4.rx_text)
                .addDisposableTo(disposeBag)
        } else {
            number1Seq01
                .bindTo(number4.rx_text)
                .addDisposableTo(disposeBag)
        }
    }

    func verifyString(observable: ControlProperty<String>) -> Observable<String> {
        return Observable.create {
            observer in
            /* Do something useful... */
            return observable.subscribeNext {
                value in
                print(#function)
                observer.onNext(value)
            }
        }
    }
    func doThisOnlyWhenLoggedIn(observable: Observable<String>) -> Observable<String> {
        return Observable.create {
            observer in
            /* Do something useful... */
            return observable.subscribeNext {
                value in
                print(#function)
                observer.onNext(value)
            }
        }
    }

まとめ

先日参加した 第2回RxSwift勉強会 @ Sansanにて「bindToはsubscribeと同じ」という言及があって、アレそうだったっけ?と思って再度検証してみました。

厳密にはsubscribeと同じ意味のものと、そうでないものがありました。bindToのbinderファンクションの仕組みを利用する事で、複雑なシーケンスも普通の変数として取り回す事も出来るようになりました。

実際には、私は起動シーケンスの処理でこの Observable.create とbindTo を多用しています。色々な処理を様々な依存関係で記述する必要があるような時に、あると便利な道具だと思います。

ご意見募集中です。

同じワークスペース内で作ったSwiftモジュールを使う

こういう感じの構成で、libの中をPokemonKitとしてモジュールにして、sourcesをアプリにしたいと思った。

app $ tree
.
├── Makefile
├── README.md
├── build.ninja
├── lib
│   └── pokemon.swift
└── sources
    └── main.swift

モジュールにしなければ、swiftc lib/pokemon.swift sources/main.swift -o build/main で済む話だし、どこかのGitリポジトリのライブラリを持ってくるのであればSwiftPMを使えばいい話である。

どうしてもSwiftPMを使いたくないというケースも考慮して、一応このまま突き進もう。

module = swiftc -emit-module -module-name PokemonKit -o build/PokemonKit
module-obj = swiftc -emit-library -emit-object -module-name PokemonKit
app-obj = swiftc -I build/ -emit-object
executable = swiftc
lib = lib/pokemon.swift
app = sources/main.swift

rule kit
    command = $module -o build/PokemonKit $in && mv build/PokemonKit build/PokemonKit.swiftmodule && $module-obj $in -o $out

rule app-obj
    command = $app-obj $in -o $out

rule app
    command = $executable $in -o $out

build build/pokemon.o: kit $lib
build build/main.o: app-obj $app
build build/main: app build/pokemon.o build/main.o

default build/main

ninja-buildを使って見た。これで差分ビルドにも対応している。

-emit-module の時にswiftdocも一緒に生成するのだが、出力場所を指定する方法がわからなかった。仕方ないので -o をえいやで指定すると .swiftmoduleのサフィクスがつかなかったので、自分でリネームする処理を入れた。惜しい。

ビルド方法は以下のようになっている。

$ cat Makefile
emitted = build/*

.PHONY: clean build

clean:
    rm $(emitted)

build:
    ninja -j 1

ninja -j 1 で並列にビルドしないようにしている点がポイントである。 こうしないと、pokemon.oとmain.o を同時に作ろうとして失敗する。

ビルドした状態。

app $ tree
.
├── Makefile
├── README.md
├── build
│   ├── PokemonKit.swiftdoc
│   ├── PokemonKit.swiftmodule
│   ├── main
│   ├── main.o
│   └── pokemon.o
├── build.ninja
├── lib
│   └── pokemon.swift
└── sources
    └── main.swift

動かしてみよう。

app $ ./build/main
[ATTACK] Pikachu:Lv.1:Scratch!
[ATTACK] Bulbasaur:Lv.5:Scratch!
[ATTACK] Pikachu:Lv.11:ThunderShock!
[ATTACK] Pikachu:Lv.4:Scratch!
[ATTACK] Pikachu:Lv.20:ThunderShock!

ちなみにコードはこんな感じである。

import PokemonKit

let a = PokeDeck(pokemons: [
    Pikachu(level: 1),
    Bulbasaur(level: 5),
    Pikachu(level: 11),
    Pikachu(level: 4),
    Pikachu(level: 20)
])

for pokemon in a.pokemons {
    do {
        try pokemon.attack(move: ElectricMove.ThunderShock)
    } catch {
        try! pokemon.attack(move: NormalMove.Scratch)
    }
}

無駄に凝ってしまった。

記事にしてしまったし一応コードをアップしておいた。ご参考まで。

https://github.com/toshi0383/PokemonAttackDemo

参考

AppleTVの時刻ズレ問題

Forumに投稿しました。
https://forums.developer.apple.com/message/139631

AppleTVは、当然ですが内部に時計を持っています。Siriに「今何時?」と尋ねると、ちゃんと答えてくれます。しかし、答えてくれる時間がおかしいことが、たまにあります。

状況を箇条書きにすると、

  • AppleTV内部のクロックは、電源に刺さっていない状態だと進まない。(iPhoneは進みますよね)
  • 起動時やネットワーク接続時にNTPサーバに問い合わせて時刻を取得しているようだが、これが結構失敗したり、NTP接続自体していないように見えることがある。
  • 失敗すると、時刻がずれたままアプリが使えてしまう。
  • 時刻をシビアに扱うアプリケーションの場合、アウト。
  • ユーザが自分で端末の時刻を修正する方法は、ない。

という感じです。

解決策を効き目がない順番に記載します。

  • 待つ。ほっとく。
  • ネットワークに接続しなおす。
  • ネットワークにつながった状態で 設定=>システム=>再起動 する。

確認した範囲では、再起動以外に方法はなさそうで、アプリ側でもそのような対応を促すメッセージを表示する仕様となりました。再起動してもダメな時もありました。

今回はDRM関係の処理で再生開始時の時刻を厳しめにチェックしているところがあって、時刻がずれていると動画が再生できない!という割とクリティカルな問題に発展してしまいました。

電源を抜き差しするなんてことは一般のユーザだとそんなにないだろうという思想なのでしょうか。

正直アプリ側ではどうしようもないので、Appleさんがなんらか修正してくれることを期待しています。

ちなみにAppleTVの時刻は上記のSiri Remoteか、XcodeのDevice画面のコンソールで確認できます。 tvOSアプリ開発者の皆さんはご注意ください。

コードを綺麗にする誘惑に君は耐えられるか

try! Swiftのセッションの翻訳を読んで、ちょうど今考えていたことと関連したので、記事にしようと思いました。 https://realm.io/jp/news/tryswift-daniel-steinberg-blending-cultures/realm.io

まだObjective-Cでメンテナンスが続いているアプリもたくさんあるわけですが、せっかくSwiftにしたんだったら、新しいパラダイムも取り入れていきたいですよね。 でも実装スピードを優先するのであれば、それぞれのメンバーが慣れている方法でとりあえず突き進んで仕舞う方がいいと思います。

フェーズ1はとりあえず動く事を優先して、コードはあえてぐちゃぐちゃのまま残す。 そうすればとりあえず期限内に完成して余裕が生まれるはずなので、その隙にガンガンリファクタを入れてスタイルを統一するなり新しい技術を試すなりすればいいと思います。

Swiftは頑張れば頑張るほど綺麗なコードが書けるので、誘惑に負けてしまう人も多いと思います。私がその例です。 SwiftLintを入れてチームのコードスタイルを統一する人もいると思います。私がその例です。 もっと綺麗に書ける!と思ってRxSwiftを導入するチームもあると思います。私がその例です。 しかしあまり綺麗なコードにこだわっていると、一歩一歩が重たくなってしまいます。コードの再利用を考えるのはほどほどにして、とにかくその機能なり画面を完成させましょう。

ただあまりぐちゃぐちゃすぎるとあとでリファクタできなくなるので、開発の早い段階でコードレビューなり勉強会なり開いて意識の統一を図るべきだと思います。 そうすればメンバーの開発スピードが上がってきた頃には、コードレビューが要らなくなると思います。実際いまは、速度を最大限優先してコードレビューはなしにしています。気になったら勝手に見てね、という感じです。不具合や仕様変更があればその人が責任持って直せばいいので。

特に画面遷移の処理を共通化するのは、簡単そうに思えて、そして実装するとなんとなく動くのですが、全体のバランスを取るのが実は大変だったりします。こっちの画面からだと開かないんだけど。。とか、こっちの画面から遷移して閉じたら余計なものまで閉じちゃう!といった問題が起こります。後回しにした方がいいかもしれません。

開発中のプロジェクトは今ちょうど佳境に入っていまして、少し反省も兼ねて今の気持ちを綴ってみました。

ネットには綺麗なコードが溢れていますが、現実のプロジェクトでそれを最初から実現しようとするのは、もしかしたら無理があるかもしれません。現実の状況をよく見極めましょう。 自分のチームに最適な進め方を見つけて、その価値観を早いうちにチームで共有できると、ベストだと思います。

みなさんのご武運をお祈りいたします。