Kの技ログ

モバイルソフトウェア開発者の技術記録

デザインパターンの基本をやってみて

普段作っているアプリを、変更に強く柔軟な作りにしたいなと思い、「Java言語で学ぶデザインパターン入門」を読んでみました。恥ずかしながら、デザインパターンを意識したことがあまりなかったので、実践例がいまいちイメージできないものもありましたが、読んでみた感想をまとめてみます。

抽象化(透過的に)すべきか考える

本書では抽象化をうまく使い、変化に強そうなコードに仕上げています。そういったコードを読んでいると、「こういうコード書いてみたいなぁ」と思う箇所がいくつもありました。そのせいか、複数のクラスに何かしら共通部分が多かったり、if文やswitch文の分岐が多くなってきた場合など、抽象化すべきかを考えるようになりました。仕様が漠然としていたり、共通部分が曖昧な場合は無理に抽象化すべきではありませんが、それでも一考するようにしています。

分割・移譲

オブジェクト指向でよく目にする「単一責任」と似たことですが、クラスを分割しておくことで目的が明確にし、修正範囲を限定することができます。また、自分が出来ない(役割ではない)部分に関しては移譲を使うことで、責任範囲を明らかにする手もあります。実際には単一責任のように細分する必要が無いケースもあると思いますが、複雑になりそうな(なってきた)場合は一考してみる必要がありそうです。

ライブラリやユーティリティに応用してみる

Swift2 系から Swift3 へのアップデート作業を行っていたときに、Swift3に対応していないライブラリがあったりして、ちょっと困ったことがありました。ライブラリは便利なので有効活用していきたいと思う一方で、活用している割合が少ないライブラリに関しては自作しても良いかなと思っています。

例えば通信まわりで有名な Alamofire ですが、単純な GET や POST だけを行うアプリであれば、URLSession で作ってしまうとか。その場合、APIの定義とうまく合わせたり、通信成功・失敗を外から扱いやすくしたりなど、考慮することは多いです。しかし、再利用性は高くなると思うのでチャレンジしてみたいです。

普段から使用していたパターンも多い

標準コンポーネントで使われているものや、考え方が似ているものを自作しているものも含め、ある程度の規模のプロダクトになるといくつかのデザインパターンが使われています。Delegate、Singleton、Prototype、Builder、Observer、Iterator パターンなどなど。ただ、それぞれ適材適所だと思っていて、例えば Singleton で作るのか Static な Class もしくは構造体で作るのか、Delegate なのか Notification なのかなど、何が適しているかは吟味したいところです。

Singleton

Singleton が必要なケースとしては、インスタンスが1つのみ必要で、複数存在してはならない場合だと思います。外から複数インスタンスを生成できてしまうと Singleton の意味が無くなってしまいます。そのため、Singleton を構造体で作ってしまうのも意味がなくなってしまいますね。構造体は値型なので、オブジェクトを複数生成してしますと参照先がばらばらになってしまいます。ユーティリティ的な役割が必要であれば、Static なメソッドを持つクラスや構造体で良いと思うし、どちらかというと構造体で作ってしまった方が分かりやすい気がしています。

Adapter と Delegate

きちんと理解できている人にとってはアホみたいな話かもしれませんが、Adapter と Delegate パターンの区別で迷ったので自分なりにまとめてみます。

Android には RecyclerView.Adapter など、Adapter という名前のついたクラスがあるので、Adapter パターンなのだろうと適当に思っていました。しかし、Delegate パターンと見比べてみると、RecyclerView.Adapter の使い方は UITableViewDataSource などと似ているなと思い始めました。

Delegate パターンの場合、既存クラスが Delegate クラス(インタフェースや抽象クラス)を利用することを前提として作られているのに対し、Adapter パターンを適用する場合は、そういった用意がされていないクラスと、処理に必要なデータなどを結びつける(変換する)機能を実装する目的があります。

そう考えてみると、Delegate パターンは UIKit などでもよく使われているので分かりやすいのですが、Adapter パターンはイメージしにくいです。特に Swift であれば、必要な箇所で private extension を使用することで Adapter パターンのようなことができてしまうと思います。stored property が必要であれば別ですが。Java で実装する場合だと、既存クラスのサブクラスを用意し、ターゲットとなるインタフェース等を implement するところですね。

Mediator

画面仕様が複雑になればなるほど、管理すべき対象が散らばっている状況は避けたくなります。Mediator パターンを使うことで、考え方がシンプルになりそうな気がしました。複数の部品が絡まっている複雑な処理を、Mediator 役がまとめるパターンですね。例としてログインフォームが出てきましたが、少なからずありそうな状況です。実際にはバリデーションに引っかかった項目を赤くするとか、通信中、データが無い時など、いろいろまとめられそうですね。

State

使ってみたいというか、実装する際に検討してみたいパターン。 UIはほぼ同じものでも、状況によって処理を分けることがあります。通信中やソフトウェアキーボードが表示されている時、アプリ全体で使いまわすデータに状態があるときなど、状態管理用の変数を用意しようとした時にちょっと考えてみようかと思います。

Strategy、Decorator

これも検討してみたいパターン。実践例があまり思いつかなかったのですが、例えばECサイトなんかでカートに入れた商品の合計額を求める場合、各商品の値段の合計やクーポンでの割引、大人一般か学生か小人か、誕生日や記念日での割引、期間限定での・・・など、いくつもの条件を計算に入れなければならない場合に使えないかなと考えてみました。

計算や条件式が複雑になるケースってたまにありますよね。なんとかしたいなと思いつつ、実装している最中に仕様が変わったりするケースもあるので、なかなか設計が難しかったりする時もあります。そういった時にも何かしらパターンを見つけられればなと思っています。

まとめ

デザインパターンは設計を考える際の1つの材料ですが、特定のパターンを使わないまでも、その考え方を身に着けておくことは非常に有意義だと感じました。オブジェクト指向の技術書を読んでいても、柔軟な設計にするためにいくつかのデザインパターンを使用するケースが出てきます。しかし実際の業務では、技術書に出てくる例よりも複雑な状況は多分にありうるので、慣れるまでは複数のパターンの吟味をし続けることになるかと思います。チームで開発していく以上、自分の設計をメンバーに理解してもらう責任があるので、やはりこういった考え方は重要で、共通認識としてのパターンを知っておくことも必要だと思いました。

DMM英会話を始めてみた

DMM英会話を始めました。オンライン英会話です。近々海外に行く予定で、少しでも英語を話せるように、聞けるようになるためです。もちろん英会話だけでは英語力は上がらないので、英単語や文法等も平行して学んでいます。これから英語の勉強は毎日やっていこうと思っていますが、今日はオンライン英会話1日目がどうだったのかを書きます。

Skype で相手の声が聴こえない

DMM英会話以外のオンライン英会話は経験がありませんが、Skypeを使うのは定番っぽいですね。そのSkypeで講師からCallされたので、緊張しながら受けたのですが、相手の声が全く聞こえませんでした。
結論から言うと、ウィルス対策ソフトの影響でした。Skypeによる通信を遮断していたようです。これに気づかず、無料体験レッスンとはいえ、1回分(25分)を無駄にしてしまいました。

講師が I can hear you とメッセージを送ってきたので、早急に原因がこちらにありそうだと分かったのは良かったのですが、レッスンの25分間はずっとググってました。レッスン前にとても緊張していた自分が悲しい。

しどろもどろな初レッスン

DMM英会話はレッスンで教材を使うかフリートークをするかなど、レッスンごとにいろいろオプションがあります。初めてだったので、自分の力量を知るためにもフリートークを選んでみました。かなりの頻度でフリーズしました。

全然英語が出てこない、講師の言っていることが聞き取れないなど、基本的な部分でアウトでした。それでも一応、困ったときのための英文を用意しておいたので助かったときもありました。例えば、相手が何と言ったのか分からなかったときに I couldn't catch what you said. と言ったら丁寧に言い直してくれたばかりでなく、メッセージで英文を送ってくれました。とても助かりました。

感想

思い返してみると、知らない単語があまり出てこなかったような気がしています。聞き取れなかっただけかもしれませんが。ほとんどの単語が知っているのに聞き取れない、話せないというのはとても悔しいですね。しっかり継続して口と耳を慣らしていこうと思います。

Associated Value を 条件式で処理する

enum で Associated Value を定義した際に、switch 文でしか条件式を組めないと思っていたのですが、if-case文やfor-case文なるものが swift2 から追加されていました。
下記のような感じです。

enum GameTitle {
    case dragonQuest(Int)
    case persona(Int)
}

let gameTitle: GameTitle = .dragonQuest(1)

if case .dragonQuest(1) = gameTitle {
    print("一人で竜王に立ち向かうとかマジ勇者")
}

if case .persona(let number) = gameTitle, number == 3 {
    print("映画になった")
}

let playedTitle: [GameTitle] = [.dragonQuest(1), .dragonQuest(2), .dragonQuest(6), .persona(3), .persona(4)]

for case .dragonQuest(let number) in playedTitle where number <= 3 {
    print("ドラクエ \(number) はリメイク版でならプレイしたことある")
}

UITableView の reloadSections アニメーションの不具合

ヘッダーをタップすることでセルを開閉できる、アコーディオンのようなテーブルビューを作ったときの話です。ヘッダーだけ残した状態で、セルとフッターを隠したり表示したりを切り替えたくて、 reloadSections:withRowAnimation: で開閉を実装しました。ところが、とある条件下だと開閉時のアニメーションが期待通りに動作しないことがありました。

問題

ヘッダーだけ残し、セルとフッターが隠されている状態のときに、そのセクションの1つ下のセクションの開閉アニメーションが期待通りに動作していませんでした。隠したいセクションのセルの数を numberOfRowsInSection で0を返していたのですが、ここに問題があるような気がしています。セクションのヘッダーは表示されているのにセルが0の場合は、セクション毎非表示にするのが正しいのでしょうか。
期待しない動作というのも、言葉では言いづらいのですが、アニメーション(UITableViewRowAnimation.Automatic)の開始位置がずれてしまうというものです。セルの数が0のセクションの下にセルの数が1個以上のセクションがある場合、セルが1個以上のセクションの開閉アニメーションの開始位置が上のセクションのヘッダー部分となってしまっていました。

期待する動作

とりあえず、セクションが空であるかどうかに関わらず、reloadSections 実行時のセルの開閉アニメーションの開始位置が、開閉するセクションのヘッダー部分であることとします。

対応

うまい方法が思いつかなかったので、下記の方法で対処しました。 * numberOfRowsInSection で0を返していた箇所で1を返す * cellForRow(at:) でダミーとなるセルを返す * tableView(_:heightForRowAt:) で高さを CGFloat.min を返す

まとめ

そもそも insert​Rows(at:​with:​)delete​Rows(at:​with:​)を使用する手もあるのですが、今回はセルと一緒にフッターも開閉する必要があり、reloadSections をどうしても使いたかったという背景があります。が、こういう問題があるのはつらいなぁと思いました。

iOS8 を含むSwift3対応時の不具合対応

Swift 2.1 から 3 へ移行する際に、大きな不具合が1つあったので、その対応を考えてみました。
※ ただし、多くの端末・バージョンで検証したものではありません。

問題

UITableViewDelegate の table​View(_:​height​For​Row​At:​) に渡される indexPath の値が間違っていました。具体的には、indexPath.row の値が、0, 1, 2, 3, … の順番で渡されるはずが、0, 0, 1, 2, … とずれてしまっていました。いざ表示してみると問題ないように見えるのですが、画面遷移時にレイアウトが崩れてしまいます。

不具合の内容や対応方法に関しては、こちらを参考にさせていただきました。

期待する動作

今回は、「table​View(_:​height​For​Row​At:​) で正しい値を取得する」ということでやってみます。

対応

まずはコードを貼ります。

Objective-C の Method Swizzling という、既存のメソッドを入れ替える機能を利用しました。Swift から Objective-C のコードを扱う方法は省きます。

まず参考サイトのように、問題となっている箇所を探っていくと、iOS8の UIMutableIndexPath *1get​Indexes:​range:​ で取得できる値が間違っている可能性があることがわかりました。しかし、UIMutableIndexPath は開発者側には非公開となっているので、直接書き換えることができませんでした。そのため、NSIndexPath の get​Indexes:​range:​ の値を正しい値を取得できる index​At​Position:​ で書き換えたところ、うまく動作しました。

まとめ

今回は、iOS 8 のtable​View(_:​height​For​Row​At:​) で渡される indexPath の値が間違っていることに対し、Objective-C のコードで不具合のあるメソッドを書き換え、正しい値を返す対応を考えてみました。

ただ、やはりこの不具合は怖いので、Swift3 対応をリリースする際にはなるべく iOS8 は切りたいと思っています。

*1:憶測ですが、UIMutableIndexPath はNSIndexPath を継承しているようで、iOS9 移行は使用されていないようです。

mapとforEachとfor

mapとforEach

返り値を必要とするならmapメソッド、要らないならforEachメソッドを使います。書き方は同じ。
以下forEachの例(アルファベットの一覧を作成)。

// a to Z
var chars : [Character] = []
["a", "A"].forEach {
    let scalar = $0.unicodeScalars
    let code = scalar[scalar.startIndex].value
    (0..<26).forEach {
        chars.append(Character(UnicodeScalar(code + $0)))
    }
}

forとforEach

上記コードの「(0..<26).forEach」の部分をfor文で書くと下のようになります。

for i : UInt32 in 0..<26 {
    chars.append(Character(UnicodeScalar(code + i)))
}

定数 i の型を指定するか、内部でキャストする必要があります。
詳しく調べていないので(途中でギブアップ)推測ですが、mapやforEachの内部定数は、それが使われる時に正確な型が決まるのではないかと思っています。用途によってはforEachとかの方が扱いやすくていい。

Form follows function.

"HELLO WORLD 「デザイン」が私たちに必要な理由"を読んでいて気になった、Form follows function =「形態は機能に従う」という言葉から考えたこと。

意味

あらゆるものの外観は、その機能の目的によって決まるということ。デザインするときは、それがどういう機能を有しているかを外観で表現した方が良いということになると思います。

アフォーダンスを備えたものをイメージすると分かり易いかもしれません。例えば、スプーンは指先で持ち、窪んだ部分に食べ物を入れて口に運びやすいようになっています。

もともとはアメリカの建築家、ルイス・サリヴァンの言葉で、Form ever follows function = 「形態は常に機能に従う」というのが本来の言葉だそうです。しかし、スマホのように、形態が機能に従わないケースも出てきました。

対話のデザイン

形態が機能に従わない場合、見る人使う人はそのものに触れなければ理解することができません。どういう機能を持っているのかを外観から判断できないので当然です。

使われている状態を見せるのも1つの解決方法です。僕はiPhone 6sのCMを見て欲しくなったのですが、それは新しい機能を数十秒で理解して、使ってみたいと思ったからだと思います。 また、他の製品でもあることですが、機器の説明書を読まない人も多いかもしれません。分かりにくいとか長いとか、とにかく使ってみたいとかいろいろあると思いますが、説明書も含めて一貫してデザインされていると良いのかなと思います。

もう1つの解決方法は、使いながら理解させる方法。この場合、ユーザの操作と結果が結びつかなければひどい製品だと思われる可能性もあります。対話のデザインが必要になるのだろうと思います。ユーザが目的のために無駄に悩むことなく操作し続けることができれば、それが良いUIデザインであり、僕が大事にしたいポイントでもあります。

繊細な仕事

良いUIデザインは、ユーザがその良さに気付きにくいという表面もあり、報われない仕事だと感じるデザイナーがいるらしいことは本書でも書かれています。しかし言い換えると、ちょっとでも間違ったデザインはすぐに悪い評価を下されてしまう、とても繊細な仕事のようにも聞こえてきます。質の悪いものは誰も作りたくないと思うので、とても価値のある仕事だと僕は思います。

形態が機能に従わなくなったとき、デザイナーはすぐに、人とモノとがコミュニケートするための新たな方法を見つけなければならなくなった。

グッドデザイン賞では、円形のディスプレイもありました。こういうものとどうコミュニケーションさせていくか、とても興味あります。