概要
過去に 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 init
と encode
メソッドになります
どちらも 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 は永続的なデータの保存にはあまり向いていないのでアプリのライフサイクル内でのみ共有したいデータがあれば使うようにする感じが良いと思います