昨日はゲーム結果の表示を実装した。
これで残すはゲームの中断と再開のみ。
まずはモデルの保存と復元を実装していく。
GameInfoの修正
基本的にはYWFと同じ。
以下が参考になると思う。
同様に、GameInfoを修正して、保存と復元が出来るようにする。
//============================== // BirdHead //------------------------------ // GameInfo.swift //============================== import Foundation // 省略 class GameInfo: NSObject { // 省略 // for save and load private static let saveDataFileName: String = "gameinfo.plist" private static var saveDataURL: NSURL = { let fileManager = NSFileManager.defaultManager() let supportDirectory = try! fileManager.URLForDirectory(.ApplicationSupportDirectory, inDomain: NSSearchPathDomainMask.UserDomainMask, appropriateForURL: nil, create: true) return supportDirectory.URLByAppendingPathComponent(GameInfo.saveDataFileName) }() static let saveGameInfoKey: String = "com.ouka-do.BirdHead.saveGameInfo" static let deleteGameInfoKey: String = "com.ouka-do.BirdHead.deleteGameInfo" class func fromSaveData() -> GameInfo? { if let savedData = NSDictionary(contentsOfURL: GameInfo.saveDataURL) { let deck = Deck() let playerCount = savedData["playerCount"] as! Int let gameInfo = GameInfo(deck: deck, playerCount: playerCount) gameInfo.inDeal = savedData["inDeal"] as! Bool gameInfo.playerHands = savedData["playerHands"] as! [[Int]] gameInfo.trickCount = savedData["trickCount"] as! Int gameInfo.turnPlayerIndex = savedData["turnPlayerIndex"] as! Int gameInfo.minusPointCards = savedData["minusPointCards"] as! [[Int]] gameInfo.isEnd = savedData["isEnd"] as! Bool let usedCardCount = savedData["usedCardCount"] as! Array<Dictionary<String, Int>> for data in usedCardCount { let card = data["card"]! let count = data["count"]! gameInfo.usedCardCount[card] = count } let actionsInTrick = savedData["actionsInTrick"] as! Array<Dictionary<String, AnyObject>> for data in actionsInTrick { let action = data["action"] as! String let cards = data["cards"] as! [Int] if action == "Play" { gameInfo.actionsInTrick.append(Action.play(cards)) } else { gameInfo.actionsInTrick.append(Action.discard(cards)) } } let removedCardCount = savedData["removedCardCount"] as! Array<Dictionary<String, Int>> for data in removedCardCount { let card = data["card"]! let count = data["count"]! for _ in 0..<count { try! gameInfo.deck.removeCard(card) } } return gameInfo } else { return nil } } // 省略 private var deck: Deck private(set) var playerCount: Int private(set) var inDeal: Bool private var playerHands: [[Int]] private(set) var trickCount: Int private(set) var turnPlayerIndex: Int private(set) var actionsInTrick: [Action] private(set) var usedCardCount: [Int: Int] private(set) var minusPointCards: [[Int]] private(set) var isEnd: Bool private var observers: [ObjectIdentifier: GameInfoObserver] private var saveEventObserver: NSObjectProtocol! private var deleteEventObserver: NSObjectProtocol! init(deck: Deck, playerCount: Int, startPlayerIndex: Int = 0) { // 省略 self.saveEventObserver = nil self.deleteEventObserver = nil super.init() let notificationCenter = NSNotificationCenter.defaultCenter() self.saveEventObserver = notificationCenter.addObserverForName(GameInfo.saveGameInfoKey, object: nil, queue: nil) { notification in self.save() } self.deleteEventObserver = notificationCenter.addObserverForName(GameInfo.deleteGameInfoKey, object: nil, queue: nil) { notification in self.delete() } } // 省略 func save() { var saveData = [String: AnyObject]() saveData["playerCount"] = self.playerCount saveData["inDeal"] = self.inDeal saveData["playerHands"] = self.playerHands saveData["trickCount"] = self.trickCount saveData["turnPlayerIndex"] = self.turnPlayerIndex saveData["minusPointCards"] = self.minusPointCards saveData["isEnd"] = self.isEnd // Dictionary<Int, Int> cannot be saved! // convert it into Array<Dictionary<String, Int>>. var usedCardCount = Array<Dictionary<String, Int>>() for (card, count) in self.usedCardCount { usedCardCount.append(["card": card, "count": count]) } saveData["usedCardCount"] = usedCardCount // Array<Action> cannot be saved. // convert it into Array<Dictionary<String, AnyObject>>. var actionsInTrick = Array<Dictionary<String, AnyObject>>() for action in self.actionsInTrick { switch action { case let .Play(cards): actionsInTrick.append(["action": "Play", "cards": cards]) case let .Discard(cards): actionsInTrick.append(["action": "Discard", "cards": cards]) } } saveData["actionsInTrick"] = actionsInTrick // deck info var removedCardCount = Array<Dictionary<String, Int>>() for (card, count) in self.deck.removedCardCount { removedCardCount.append(["card": card, "count": count]) } saveData["removedCardCount"] = removedCardCount (saveData as NSDictionary).writeToURL(GameInfo.saveDataURL, atomically: true) } func delete() { let fileManager = NSFileManager.defaultManager() _ = try? fileManager.removeItemAtURL(GameInfo.saveDataURL) let notificationCenter = NSNotificationCenter.defaultCenter() notificationCenter.removeObserver(self.saveEventObserver, name: GameInfo.saveGameInfoKey, object: nil) notificationCenter.removeObserver(self.deleteEventObserver, name: GameInfo.deleteGameInfoKey, object: nil) } // 省略 }
やっている内容は、保存の方では各プロパティを辞書に格納してそれをplistファイルに書き出し、復元の方では逆にplistファイルから辞書を読みだして各プロパティにセットする、というもの。
ちょっとハマったこととして、Swiftの辞書でキーがIntのものをplistファイルに書き出そうとすると、アプリがクラッシュするという問題。
Swiftの世界では全部オブジェクトなんだけど、Objective-Cの世界では(Cの世界なので)整数とオブジェクト(=ポインタ)は別というのがおそらく原因で、辞書(Dictionary)のキーがオブジェクトでない場合、Objective-Cの辞書(NSDictionary)にうまく変換できず、問題が発生するのだと思う。
(これ自体はおそらくSwiftのバグ)
そこで、Dictionary<Int, Int>
を無理やりArray<Dictionary<String, Int>>
に変換して保存し、復元するときもその逆を行うということをしている。
保存の通知
モデルの保存が出来るようになったら、必要なタイミングで通知が来るようにする。
以下を参照。
AppDelegateを次のように修正。
//============================== // BirdHead //------------------------------ // AppDelegate.swift //============================== import UIKit @UIApplicationMain class AppDelegate: NSObject, UIApplicationDelegate { var window: UIWindow? func applicationWillResignActive(application: UIApplication) { NSNotificationCenter.defaultCenter().postNotificationName(GameInfo.saveGameInfoKey, object: nil) } func applicationWillTerminate(application: UIApplication) { NSNotificationCenter.defaultCenter().postNotificationName(GameInfo.saveGameInfoKey, object: nil) } }
これで、アプリが切り替わろうとしたり、終了させられるタイミングで、保存の通知が来て、モデルを保存できるようになった。
今日はここまで!