UserDefaultsに保存する構造体があります。これが私の構造です
struct Song {
var title: String
var artist: String
}
var songs: [Song] = [
Song(title: "Title 1", artist "Artist 1"),
Song(title: "Title 2", artist "Artist 2"),
Song(title: "Title 3", artist "Artist 3"),
]
別のViewControllerには、この構造体に追加するUIButtonがあります
@IBAction func likeButtonPressed(_ sender: Any) {
songs.append(Song(title: songs[thisSong].title, artist: songs[thisSong].artist))
}
ユーザーがそのボタンをクリックするたびに構造体をUserDefaultsに保存し、ユーザーがアプリを終了してagianを開くたびに保存されるようにしたいのです。どうすればいいですか?
Swift 4では、これは非常に簡単です。 Codableプロトコルを採用しているとマークするだけで、構造体をコード可能にできます。
struct Song:Codable {
var title: String
var artist: String
}
それでは、いくつかのデータから始めましょう。
var songs: [Song] = [
Song(title: "Title 1", artist: "Artist 1"),
Song(title: "Title 2", artist: "Artist 2"),
Song(title: "Title 3", artist: "Artist 3"),
]
これをUserDefaultsに取り込む方法は次のとおりです。
UserDefaults.standard.set(try? PropertyListEncoder().encode(songs), forKey:"songs")
そして、後でそれを元に戻す方法は次のとおりです。
if let data = UserDefaults.standard.value(forKey:"songs") as? Data {
let songs2 = try? PropertyListDecoder().decode(Array<Song>.self, from: data)
}
これは私のメインスレッドのUserDefaults拡張です。getCodableオブジェクトをUserDefaultsに設定します
// MARK: - UserDefaults extensions
public extension UserDefaults {
/// Set Codable object into UserDefaults
///
/// - Parameters:
/// - object: Codable Object
/// - forKey: Key string
/// - Throws: UserDefaults Error
public func set<T: Codable>(object: T, forKey: String) throws {
let jsonData = try JSONEncoder().encode(object)
set(jsonData, forKey: forKey)
}
/// Get Codable object into UserDefaults
///
/// - Parameters:
/// - object: Codable Object
/// - forKey: Key string
/// - Throws: UserDefaults Error
public func get<T: Codable>(objectType: T.Type, forKey: String) throws -> T? {
guard let result = value(forKey: forKey) as? Data else {
return nil
}
return try JSONDecoder().decode(objectType, from: result)
}
}
更新これは私のものですバックグラウンドでのUserDefaults拡張機能、getを設定するCodableオブジェクトをUserDefaultsに
// MARK: - JSONDecoder extensions
public extension JSONDecoder {
/// Decode an object, decoded from a JSON object.
///
/// - Parameter data: JSON object Data
/// - Returns: Decodable object
public func decode<T: Decodable>(from data: Data?) -> T? {
guard let data = data else {
return nil
}
return try? self.decode(T.self, from: data)
}
/// Decode an object in background thread, decoded from a JSON object.
///
/// - Parameters:
/// - data: JSON object Data
/// - onDecode: Decodable object
public func decodeInBackground<T: Decodable>(from data: Data?, onDecode: @escaping (T?) -> Void) {
DispatchQueue.global().async {
let decoded: T? = self.decode(from: data)
DispatchQueue.main.async {
onDecode(decoded)
}
}
}
}
// MARK: - JSONEncoder extensions
public extension JSONEncoder {
/// Encodable an object
///
/// - Parameter value: Encodable Object
/// - Returns: Data encode or nil
public func encode<T: Encodable>(from value: T?) -> Data? {
guard let value = value else {
return nil
}
return try? self.encode(value)
}
/// Encodable an object in background thread
///
/// - Parameters:
/// - encodableObject: Encodable Object
/// - onEncode: Data encode or nil
public func encodeInBackground<T: Encodable>(from encodableObject: T?, onEncode: @escaping (Data?) -> Void) {
DispatchQueue.global().async {
let encode = self.encode(from: encodableObject)
DispatchQueue.main.async {
onEncode(encode)
}
}
}
}
// MARK: - NSUserDefaults extensions
public extension UserDefaults {
/// Set Encodable object in UserDefaults
///
/// - Parameters:
/// - type: Encodable object type
/// - key: UserDefaults key
/// - Throws: An error if any value throws an error during encoding.
public func set<T: Encodable>(object type: T, for key: String, onEncode: @escaping (Bool) -> Void) throws {
JSONEncoder().encodeInBackground(from: type) { [weak self] (data) in
guard let data = data, let `self` = self else {
onEncode(false)
return
}
self.set(data, forKey: key)
onEncode(true)
}
}
/// Get Decodable object in UserDefaults
///
/// - Parameters:
/// - objectType: Decodable object type
/// - forKey: UserDefaults key
/// - onDecode: Codable object
public func get<T: Decodable>(object type: T.Type, for key: String, onDecode: @escaping (T?) -> Void) {
let data = value(forKey: key) as? Data
JSONDecoder().decodeInBackground(from: data, onDecode: onDecode)
}
}
構造体にプロパティリスト準拠のプロパティのみが含まれる場合、プロパティpropertyListRepresentation
および対応するinit
メソッドを追加することをお勧めします
struct Song {
var title: String
var artist: String
init(title : String, artist : String) {
self.title = title
self.artist = artist
}
init?(dictionary : [String:String]) {
guard let title = dictionary["title"],
let artist = dictionary["artist"] else { return nil }
self.init(title: title, artist: artist)
}
var propertyListRepresentation : [String:String] {
return ["title" : title, "artist" : artist]
}
}
曲の配列をUserDefaults
writeに保存するには
let propertylistSongs = songs.map{ $0.propertyListRepresentation }
UserDefaults.standard.set(propertylistSongs, forKey: "songs")
配列を読み取るには
if let propertylistSongs = UserDefaults.standard.array(forKey: "songs") as? [[String:String]] {
songs = propertylistSongs.flatMap{ Song(dictionary: $0) }
}
title
およびartist
が変更されない場合、プロパティを定数(let
)として宣言することを検討してください。
この回答は、Swift 4がベータステータスの間に書かれました。一方、Codable
に準拠することがより良い解決策です。
この一連の曲をUserDefaultsに保存しようとしているだけで、これを使用するのが面倒な場合は:
//stores the array to defaults
UserDefaults.standard.setValue(value: songs, forKey: "yourKey")
//retrieving the array
UserDefaults.standard.object(forKey: "yourKey") as! [Song]
//Make sure to typecast this as an array of Song
重いアレイを保存する場合は、Swift 4のNSCodingプロトコルまたはCodable Protocolを使用することをお勧めします
コーディングプロトコルの例:-
struct Song {
var title: String
var artist: String
}
class customClass: NSObject, NSCoding { //conform to nsobject and nscoding
var songs: [Song] = [
Song(title: "Title 1", artist "Artist 1"),
Song(title: "Title 2", artist "Artist 2"),
Song(title: "Title 3", artist "Artist 3"),
]
override init(arr: [Song])
self.songs = arr
}
required convenience init(coder aDecoder: NSCoder) {
//decoding your array
let songs = aDecoder.decodeObject(forKey: "yourKey") as! [Song]
self.init(are: songs)
}
func encode(with aCoder: NSCoder) {
//encoding
aCoder.encode(songs, forKey: "yourKey")
}
}