タイトル
WEB+DB PRESS vol.85
著者
菅原 元気 (著), 磯辺 和彦 (著), 山口 与力 (著), 澤登 亨彦 (著), 濱田 章吾 (著), & 20 その他
出版社
技術評論社
Amazonで購入する

WEB+DB PRESS vol.85』の特集2. 速習 リアクティブプログラミング を寄稿しました。 リアクティブ という、去年辺りに盛り上がったキーワードを、Webプログラミングの観点から整理するとこういうものだよね?っていうふんわりした感じで解説しています。

定義や概念などは、語るコンテキストで捉え方が違ったり、論争の元になるので、今回はあえてこういう定義付けをさけ、実践で利用する場合にどういうメリットがあるのか、どういうふうに使えばいいのかに焦点を当てました。

一部、寝ぼけていたのかおかしな部分があったりもしますが(React.js が Flux アーキテクチャに則っているとか変なこと書いてます。。。)、今号はいろいろおもしろい話が満載ですので、是非読んでみてください。

iOS8 から separatorInset だけだと区切り線が広がらない

こんにちは。iOS8 から SeparatorInsets をゼロにする方法が変わりました。今までの UITableViewseparatorInsetUIEdgeInsetsZero を入れるだけだと、iOS7 では効きますが、iOS8 では効かなくなりました。

1
tableView.separatorInset = UIEdgeInsetsZero

"図1"

iOS8 にも対応するには

iOS8 からは UIView に追加された layoutMargins というプロパティにも UIEdgeInsetsZero をセットする必要があります。ただこのプロパティ、iOS8 以降でしか利用できないプロパティなので、分岐が必要になります。。

1
2
tableView.layoutMargins = UIEdgeInsetsZero
cell.layoutMargins = UIEdgeInsetsZero

全体に適用する簡単な例

全体に適用するには UIAppearance を使うと便利です。iOS7 と iOS8 に対応したコードは次のように書けます。

1
2
3
4
5
6
7
8
let version = NSString(string: UIDevice.currentDevice().systemVersion).doubleValue

UITableView.appearance().separatorInset = UIEdgeInsetsZero
UITableViewCell.appearance().separatorInset = UIEdgeInsetsZero
if version >= 8 {
    UITableView.appearance().layoutMargins = UIEdgeInsetsZero
    UITableViewCell.appearance().layoutMargins = UIEdgeInsetsZero
}

"図2"

iPhone6 と iPhone6 Plus が発売になり、本格的に iOS でも Android のように複数解像度に対応したやり方をしないといけなくなってきました。 iPhone5S までは、縦幅が伸びただけだったので、なんとなく Auto Layout を使っていても問題は表面化しづらかったのですが、 iPhone6 で横幅が伸びてしまったことで、適当に Auto Layout を使ってコーディングしていると残念なことになっているアプリが結構あります。

僕も iPhone アプリを開発しているのでこのあたりは結構気を使ってはいるんですが、いかんせん自分のやり方がほんとに正しいのか、やや疑問なところもあります。 そこで、今の自分のやり方を晒して、世の iPhone アプリ開発者の人に突っ込んでもらえればと思い記事を書くことにしました。

間違っている箇所もあると思いますので、ドンドンツッコミをお願いします!

最近 ReactiveCocoa を使いまくってます。そのなかで調べた Signal の使い方イディオムのメモです。

RACCommand で検索処理とかしてるときに、ローディング画面やインジケータを表示したいということがあると思います。そういうときに使える Signal のイディオムです。コードは Swift で書いています。

MVVM で viewModel が searchCommand を実装しているとします。また、ローディングインジケータの表示には MBProgressHUD を使っているとします。

MyViewController#viewDidLoad
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
searchButton.rac_command = viewModel.searchCommand
searchButton.rac_command.executing
    .subscribeNext({ [weak self] (executing) in
        if let weakSelf = self {
            if executing as Bool {
                MBProgressHUD.showHUDAddedTo(weakSelf.view, animated: true)
            } else {
                MBProgressHUD.hideHUDForView(weakSelf.view, animated: true)
            }
        }
    })
searchButton.rac_command.errors
    .subscribeNext({ (error) in
        // エラー時のメッセージ表示処理
        println(error.localizedDescription)
    })

executing Signal はこういう風につかうんですね。

Xcode6 GM でましたね! Beta1 の頃から Swift 触ってますが、まぁ言語仕様がよく変わること(笑。

で、いつか直るだろうと思ってたけど、GM でも直っていない Swift の多次元配列の定義の謎に関してです。だれか理由分かる人いたら教えてください。。

下のようなコードがあるとします。

1
2
3
4
5
6
7
8
9
typealias NameAndValue = Dictionary<String, String>

let dict: [[NameAndValue]] = [
  [
     ["name": "",    "value": ""],
  ],
  [
     ["name": "",    "value": ""],
  ]]

typealias で別名つけて2次元配列を定義しているだけです。要素が Dictionary になっています。ここまでは普通にコンパイルが通ります。

ですが、これをこんなふうに…

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
let dict: [[NameAndValue]] = [
  [
     ["name": "",    "value": ""],
     ["name": "",    "value": ""],
     ["name": "",    "value": ""],
     ["name": "",    "value": ""],
     ["name": "",    "value": ""],
     ["name": "",    "value": ""],
     ["name": "",    "value": ""],
     ["name": "",    "value": ""],
     ["name": "",    "value": ""],
     ["name": "",    "value": ""],
     ["name": "",    "value": ""],
     ["name": "",    "value": ""],
     ["name": "",    "value": ""],
     ["name": "",    "value": ""],
     ["name": "",    "value": ""],
     ["name": "",    "value": ""],
     ["name": "",    "value": ""],
     ["name": "",    "value": ""],
     ["name": "",    "value": ""],
  ],
  [
     ["name": "",    "value": ""],
  ]]

配列の要素を増やしていきます。そうするとだいたい 10件くらい からコンパイルが遅くなり、15件過ぎたくらいから コンパイルエラーが出るようになります。

1
Cannot convert the expression's type '[[NameAndValue]]' to type 'StringLiteralConvertible'

意味がわからんです。。

仕方ないので

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
var dict: [[NameAndValue]] = [[],[]]
dict[0].append(["name": "",    "value": ""])
dict[0].append(["name": "",    "value": ""])
dict[0].append(["name": "",    "value": ""])
dict[0].append(["name": "",    "value": ""])
dict[0].append(["name": "",    "value": ""])
dict[0].append(["name": "",    "value": ""])
dict[0].append(["name": "",    "value": ""])
dict[0].append(["name": "",    "value": ""])
dict[0].append(["name": "",    "value": ""])
dict[0].append(["name": "",    "value": ""])
dict[0].append(["name": "",    "value": ""])
dict[0].append(["name": "",    "value": ""])
dict[0].append(["name": "",    "value": ""])
dict[0].append(["name": "",    "value": ""])
dict[0].append(["name": "",    "value": ""])
dict[0].append(["name": "",    "value": ""])
dict[0].append(["name": "",    "value": ""])
dict[0].append(["name": "",    "value": ""])

dict[1].append(["name": "",    "value": ""])
dict[1].append(["name": "",    "value": ""])
dict[1].append(["name": "",    "value": ""])
dict[1].append(["name": "",    "value": ""])
dict[1].append(["name": "",    "value": ""])

こうやって、プログラムでデータ登録するようにしました。。

タイトル
APIデザインの極意 Java/NetBeansアーキテクト探究ノート
著者
Jaroslav Tulach (著), 柴田 芳樹 (翻訳)
出版社
インプレスジャパン
Amazonで購入する

本書は プログラミング API の設計本です。Web API の設計極意に関して知りたい人は 『RESTful Webサービス』や『Webを支える技術 -HTTP、URI、HTML、そしてREST』を読むのがいいと思います。本書は GoF のデザインパターン EFFECTIVE JAVA を読んだ開発者が次に読むべき本として紹介されています。

RESTful Webサービス Webを支える技術 -HTTP、URI、HTML、そしてREST オブジェクト指向における再利用のためのデザインパターン EFFECTIVE JAVA 第2版

本書は、NetBeans API を構築した筆者がデザインパターンやコーディング作法だけではうまくいかない問題、後方互換性を維持したままライブラリを発展させる方法、を重点的に解説しています。なるほど、 デザインパターンや設計方法を学んだ次に読むべき本 として挙げられている理由がよくわかる内容になっています。

ReactiveCocoacatchTo の使い方を覚えたのでメモ。

ReactiveCocoa

リアクティブプログラミングの詳細は省くとして、RACSignal のイベントには nextcompletederror があります。それぞれ、subscribeNextsubscribeCompletedsubscribeError でハンドリングできるやつです。

で、HTTP API 等を呼び出す際に API 呼び出しの結果を JSON にパースして、結果をモデルに設定するみたいなことをやりたい時に、次のように行います。(Swift で記述しています。)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
RAC(self, "model") <~ API.loadData()

class API {
    class func loadData() -> RACSignal {
        return RACSignal.createSignal({ (subscriber: RACSubscriber!) in
            let url = NSURL(string: "http://localhost:300/search")
            let configuration = NSURLSessionConfiguration.defaultSessionConfiguration()
            configuration.HTTPAdditionalHeaders = ["Accept": "application/json"]
            let session: NSURLSession = NSURLSession(configuration: configuration)
            let task: NSURLSessionDataTask = session.dataTaskWithURL(url, completionHandler: { (data: NSData!, response: NSURLResponse!, error: NSError!) in
                if error == nil {
                    // data をパースしてモデル化
                    subscriber.sendNext(model)
                    subscriber.sendCompleted()
                } else {
                    subscriber.sendError(error)
                }
                session.invalidateAndCancel()
            })
            task.resume()
            return nil
        })
    }
}

こうすると、API の呼び出しが成功した場合には subscriber.sendNext の結果がきちんと self.model に設定されるわけですが、API の呼び出しが失敗して subscriber.sendError が呼び出されてしまうと、例外がなげられます。

ではどうするか。

catchTo を使って sendError に備える

そこで catchTo を使います。

1
RAC(self, "model") <~ API.loadData().catchTo(RACSignal.empty())

catchTosendError が呼び出された場合に代わりに投げる RACSignal を指定します。そうすることで、ネットワークエラー等で API の呼び出しが失敗した場合には、RACSignal.empty() で何も起きなかったことになる(正確には sendCompleted が呼び出される)ようになります。

sendError 時に処理を行いたい場合には catch を代わりに使用します。

1
2
3
4
RAC(self, "model") <~ API.loadData()
    .catch({ (error: NSError!) -> RACSignal! in
        return RACSignal.empty()
    })

以上です。

Swift で Dictionary を扱うときのメモです。API 呼び出しのレスポンスを JSON で扱いたい時に Dictionary<String, AnyObject> として扱う際のポイントです。

1
2
3
4
5
6
7
typealias JSONDictionary = Dictionary<String, AnyObject>
let json = NSJSONSerialization.JSONObjectWithData(data, options: .MutableContainers, error: nil) as JSONDictionary

let str1 = json["foobar"]! as String    // String
let str2 = json["foobar"] as AnyObject? as? String  // String?

let str3 = json["foobar"] as String     // これはコンパイルエラー '(String, AnyObject)' is not convertible to 'String'

最後のはなぜコンパイルエラーになるかというと、Dictionarysubscript が2種類定義されていて、期待したのと違う方が呼び出されているからです。

1
2
3
4
5
6
struct Dictionary<Key : Hashable, Value> : CollectionType, DictionaryLiteralConvertible {
    ...
    subscript (i: DictionaryIndex<Key, Value>) -> (Key, Value) { get }
    subscript (key: Key) -> Value?
    ...
}

json["foobar"] の戻り値は (Key, Value)Value? のどちらかですが、as String を付けた際に Optional ではないと判断されてしまい (Key, Value) が戻り値の型と判定されます。それでコンパイルエラーになるわけですね。

期待した通りに取得するには、json["foobar"] の戻り値を Value? として扱う必要があるので、

1
2
json["foobar"]!
json["foobar"] as AnyObject?

のどちらかでアクセスする必要があるわけです。! を付けると unwrap されるので nil が入っていると実行時エラーになります。逆に as AnyObject? as? String でアクセスすると Optional 型になってしまいます。

API のインターフェースと相談して、どちらの型で処理するか決めるといいんじゃないかと思います。

この記事は Xcode6 beta4 を元に記述しています。

Swift の配列は mutable か immutable

Swift では配列を mutable/immutable の区別なく定義することができるようになりました。

1
2
3
var numbers = [1, 2, 3]
numbers[0] = 0
println(numbers)    // [0, 2, 3]

let で定義すると immutablevar で定義すると mutable になり、配列の代入はすべてコピーで行われるようになっています。

Force UnWrapping 型!

で、ハマったのが次のようなコードです。

1
2
var numbers: [Int]! = [1, 2, 3]
numbers[0] = 0    // error: [email protected] $T7' is not identical to 'Int'

Force Unwrapping 型とでも言うんでしょうかね?変数に必ず値が入ってることを保証するために ! が付いた型です。 この型で定義した配列には、値の追加や変更ができなくなっています。。

これで何が困るかというと、UIKit 使って UIViewController のサブクラスにプロパティを定義する際に 初期化を viewDidLoad で行う時には、! をつけないと initializer が必要になってしまうので、プロパティの定義には ! を付けていました。

そうすると、mutable で扱いたかったプロパティなのに、変更できないという問題にぶち当たったわけです。。どうすんだこれ。。。

コンパイルエラーの例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class MyViewController: UIViewController {

  var numbers: [Int]!

  override func viewDidLoad() {
    super.viewDidLoad()

    self.numbers = [1, 2, 3]
  }


  func myFunc() {
    self.numbers[0] = 0     // コンパイルエラー
  }
}

うまくいく例

対応としては、一旦変数で受けて、変更後に元に戻すっていうので何とかなりますが、コンパイラの方でなんとかならんもんですかね。。

1
2
3
4
5
  func myFunc() {
    var nums: [Int] = self.numbers
    nums[0] = 0
    self.numbers = nums
  }

Swift で enum を扱う際にハマった問題です。確認は Xcode6 beta4 で行っています。

Swift の enum は AnyObject 型変数に代入できない

Swift では enumAnyObject 型の変数に入れることができません。コンパイルエラーになります。Any 型なら代入できます。

1
2
3
4
5
6
7
8
9
10
11
enum SomeType: Int {
    case None = 0
    case Something
}

let something = SomeType.None
something                                     // Enum Value

let anything: AnyObject = SomeType.Something  // error: type 'SomeType' does not conform to protocol 'AnyObject'

let any: Any = SomeType.Something             // Enum Value

Swift の enumAnyObject protocol を実装していないので、AnyObject 型の変数に入れることが出来ません。

ReactiveCocoa とかと組み合わせるときにこまる

これの何が不便かというと、enum は UIKit でよく使われていて、ReactiveCocoa を Swift で使う際に enum で設定する例えば UITableViewCellAccessoryType なんかを使いたい場合にコンパイルエラーになってしまいます。

1
2
3
4
5
6
7
8
9
// これはコンパイルエラーになる
RAC(self, "cell.accessoryType") = RACObserve(self, "viewModel.checked").map { ($0 as Bool) ? UITableViewCellAccessoryType.Checkmark : UITableViewCellAccessoryType.None }

// こうしないといけない。。
RACObserve(self.viewModel, "checked")
    .subscribeNext { [weak self] arg in
        let checked = arg as Bool
        self!.accessoryType = checked ? .Checkmark : .None
}

既存の Objective-C ライブラリで id が使われている箇所が、Swift では AnyObject に対応するので、 Closure 等で id を引数に取ったり、id を戻り値にしていたりする箇所が AnyObject になってしまい、 enum を利用することができません。

変数に代入するだけなら Any 型にすればいいんですが、既存ライブラリとの連携では難しそうです。 1 これ、何かやり方ないんでしょうかね。。ReactiveSwift 待ちかなぁ


  1. Any 型にすれば代入できることを @takabosoft さんに教えてもらいました。ありがとうございます!