2018年2月28日水曜日

SpriteKit で UserDefaults に独自に定義したクラスをセットする方法

概要

過去に SpriteKit 上で UserDefaults を使う方法を紹介しました
UserDefaults は Objective-c の機能になり NSString や NSInteger 型のデータははそのまま使うことができるのですが Swift で定義した独自のクラスのオブジェクトなどを set/get する場合には少し工夫が必要でした
今回はその方法を紹介します

環境

  • macOS 10.13.3
  • Xcode 9.2 (9C40b)

宣言する独自クラスに NSObject と NSCoding を継承する

例えば以下のような感じです

import Foundation

class GameConfig: NSObject, NSCoding {
...
}

Foundation がないと NSObject が継承できないので追加します
またただ継承しただけではダメで NSCoding 用のメソッドを定義しなければなりません

init と encode メソッドを追加する

先ほど継承したクラスを実装すると以下のようになります

import Foundation

class GameConfig: NSObject, NSCoding {
    var roomId = ""
    var player = ""
    var name = ""
    var msg = ""

    init(roomId: String) {
        self.roomId = roomId
        self.msg = "The room was lost..."
    }

    required convenience init(coder aDecoder: NSCoder) {
        let roomId = aDecoder.decodeObject(forKey: "roomId") as! String
        self.init(roomId: roomId)
        let player = aDecoder.decodeObject(forKey: "player") as! String
        let name = aDecoder.decodeObject(forKey: "name") as! String
        self.player = player
        self.name = name
    }

    func encode(with aCoder: NSCoder) {
        aCoder.encode(roomId, forKey: "roomId")
        aCoder.encode(player, forKey: "player")
        aCoder.encode(name, forKey: "name")
        aCoder.encode(msg, forKey: "msg")
    }
}

重要なのは required convenience initencode メソッドになります
どちらも UserDefaults の set/get をするときに自動で呼ばれるのですがこれがないと「Use of undeclared type 'NSObject' in Xcode」や「class 'GameConfig' does not implement methodSignatureForSelector」などのエラーが発生します

また required convenience init でエンコードしたデータをフィールドにセットする場合に既存のコンストラクタで全フィールドに対して値をセットできない場合は、その後でちゃんと各フィールドに対して値とセットしないと取り出したときに値がない状態になってしまいます

UserDefaults にセットする

あとは独自クラスから作成したオブジェクトをセットするだけですがここでも少し工夫が必要です

let defaults = UserDefaults.standard
defaults.set(NSKeyedArchiver.archivedData(withRootObject: config), forKey: "GameConfig")

config は上記で説明した GameConfig クラスのオブジェクトになります
NSKeyedArchiver.archivedData を使ってセットすることでデータを NSData 側に変更しています
こうすることで独自で定義したクラスを UserDefaults にセットできるようにしています

UserDefaults から取り出す

取り出すときも少し工夫が必要です
例えば以下のように取り出します

let defaults = UserDefaults.standard
// UserDefaults のデータがある場合
if let data = defaults.value(forKey: "GameConfig") as? NSData {
    // NSData から GameConfig に変更
    let config = NSKeyedUnarchiver.unarchiveObject(with: data as Data) as! GameConfig
    // これで参照できる
    print(config.name)
}

セットしたデータが存在するかしないかを確認してもし存在するならば NSKeyedUnarchiver.unarchiveObject を使ってデータを取り出し GameConfig クラスにキャストしてあげます
こうすることで元のクラスのオブジェクトとして扱うことができます

最後に

SpriteKit + UserDefaults で独自のクラスのオブジェクトを扱う方法を紹介しました
独自のクラスになったとたん急に複雑になってしまいます
これが面倒であればクラスの特定のフィールドだけを String などで共有する方が簡単なケースもあるかもしれません

そもそも SpriteKit で UserDefaults を使うケースですが自分は SKScene 側で生成したデータを AppDelegate.swift の applicationWillTerminate で扱うために UserDefaults を使いました
UserDefaults は永続的なデータの保存にはあまり向いていないのでアプリのライフサイクル内でのみ共有したいデータがあれば使うようにする感じが良いと思います

0 件のコメント:

コメントを投稿