[iOS] BackboneのModelをSwiftでも使いたいたいので部分的に書いてみた

2015/02/12

こんにちは。きんくまです。

Backbone.jsのModelとかCollectionをSwiftでも使いたいなと思って、機能限定だけど作ってみました。
JavaScriptのオリジナルのソースを見つつSwiftに書き直してみました。

学習目的で作ったものだと思ってくださいまし。
>> BBModel

作ったのは3つのクラスです。
Events, Model, Collection

サーバーとの通信、Collectionクラスのsortとwhereなどが抜けています。

インストール

3つのswiftクラスのファイルをコピペしてください。
iOS8 からはFrameworkでいれられるかも。

Events

on(イベント名)でコールバックが登録できます。

        var event:Events = Events()
        
        event.on("myevent") {
            (events, options) -> Void in
            println("callback 1")
        }
        
        event.trigger("myevent") // callback 1

off(イベント名)でコールバックが全て削除できます。
またonで登録したときにクロージャのidを発行するので、それをとっておけばそのクロージャーだけを削除することができます。
あとクロージャ内でselfを使うものを登録するときは、メモリリークを防ぐため unowned self を使います。
>> Resolving Strong Reference Cycles for Closures

それと現状だと、あるEventsオブジェクトにonで登録した場合、そのオブジェクトを破棄する際には全てのコールバックを削除する必要があります。(ここはなんとかしたいですね、、)
off() で引数を省略すると全てのコールバックを削除できます。

        var event:Events = Events()
        
        event.on("myevent") {
            (events, options) -> Void in
            println("callback 1")
        }
        
        event.trigger("myevent") // callback 1
        
        let callbackId = event.on("myevent") {
            [unowned self] (events, options) -> Void in
            println("self \(self)")
            println("callback 2'")

            //optionsの中にこのcallbackのidが入っている
            let id = options["callbackId"]

            //それを使って内側からこのコールバックを削除することも可能
            //event.off("myevent", callbackId:id) 
        }
        
        event.on("myevent") {
            (events, options) -> Void in
            println("callback 3")
        }
        
        event.trigger("myevent") //callback 1, 2, 3
        
        //remove callback 2 only
        event.off("myevent", callbackId: callbackId)
        
        event.trigger("myevent") //callback 1, 3
        
        //remove all callbacks
        event.off("myevent")
        
        event.trigger("myevent") //no callback

Model

こんなModelのサブクラスを作ります

class Person: Model{
    var name:String?{
        get{ return self.get("name") as? String }
        set{ self.set("name", newValue!) }
    }
    var age:Int?{
        get{ return self.get("age") as? Int }
        set{ self.set("age", newValue!) }
    }
    override init(attributes: [String : Any]?) {
        super.init(attributes: attributes)
    }
    
    init(name:String = "", age:Int = 0) {
        super.init()
        self.name = name
        self.age = age
    }
}

実際に使ってみます。

        //Modelのinitを使って
        var sato = Person(attributes: ["name":"Sato", "age":21])
        
        //Personのinitを使って
        var tanaka = Person(name:"Tanaka", age:18)
        
        //Changeイベントに登録
        tanaka.on(Model.Event.CHANGE) {
            (model, options) in
            
            let aTanaka = model as Person
            for (key, attr) in aTanaka.changedAttributes {
                println("tanaka changed key:\(key), value:\(attr)")
                println("tanaka previous value:\(aTanaka.previous(key))")
            }
        }
        
        //まとめて
        tanaka.set(["name":"Tanaka san", "age":24])
        
        //1つだけ
        tanaka.set("name", "Tanaka dayo")

        println("--")
        
        //プロパティにアクセスして
        tanaka.name = "Super Tanaka"
        tanaka.age = 30
        
        println("--")
        
        //イベント発行しないときは silent
        tanaka.set("name", "Special Tanaka", options:["silent": true])

Collection

Modelをまとめて扱うためのクラスです。
こんな感じのCollectionがあったとして

class PersonCollection: Collection {
    
    override init(models: [Model]?, options: [String : Any]?) {
        super.init(models: models, options: options)
        
        //Modelクラス登録
        self.model = Person.self
    }
}

実際に使ってみます

        var tanaka = Person(name:"Tanaka")
        var yamada = Person(name:"Yamada")
        var sato = Person(name:"sato")
        
        var personCollection =  PersonCollection(models: nil, options: nil)
        
        //Addイベント登録
        personCollection.on(Collection.Event.ADD) {
            (model, collection, options) in
            let p = model as Person
            println("Person \(p.name) is added")
        }
        
        //Removeイベント登録
        personCollection.on(Collection.Event.REMOVE) {
            (model, collection, options) in
            let p = model as Person
            println("Person \(p.name) is removed")
        }
        
        //ひとつ
        personCollection.add(tanaka)
        
        //複数
        personCollection.add([yamada, sato])
        
        //ひとつ
        personCollection.remove(yamada)
        
        //複数
        personCollection.remove([tanaka, sato])
        
        println("collection length = \(personCollection.length)") // 0
        
        personCollection.add([yamada, sato])
        
        //setは差分で登録
        //すでにあれば上書き、なければ登録、引数に入ってなければ削除
        personCollection.set([tanaka, sato])
        
        //そのほかのメソッド
        /*
        personCollection.pop()
        personCollection.push()
        personCollection.shift()
        personCollection.unshift()
        personCollection.at()
        personCollection.has()
        personCollection.indexOf()
        */

で、これを使ってTodoアプリを作ってみたので次回はそれを書いてみます。

あと、実際に書いてみた感想。

・swiftの文法的なところにいちいちひっかかっていたけど、書いていくうちに慣れてきた。

・JSのライブラリの中身をちゃんと見る機会がほとんどなかったので、ソースを読み解く勉強になった。

・読み解くことでドキュメントにしっかり書いてない挙動とかがわかった。stackoverflowとかいろんなところで、どうしてこの人はこんなにこのライブラリの使い方を知っているんだろう?と思うことがよくあったのだけど、たぶんソースを見てるってことですね。やっぱソース大事なんだなと思いました。

LINEで送る
Pocket

自作iPhoneアプリ 好評発売中!
フォルメモ - シンプルなフォルダつきメモ帳
ジッピー電卓 - 消費税や割引もサクサク計算!

ページトップへ戻る