web-dev-qa-db-ja.com

構造体をUserDefaultsに保存

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を開くたびに保存されるようにしたいのです。どうすればいいですか?

49
Jacob Cavin

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)
}
213
matt

これは私のメインスレッドの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)
    }
}
16
YannSteph

構造体にプロパティリスト準拠のプロパティのみが含まれる場合、プロパティ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に準拠することがより良い解決策です。

14
vadian

この一連の曲を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")
    }

}
3
Dark Innocence

From here:

デフォルトのオブジェクトは、プロパティリスト、つまりNSData、NSString、NSNumber、NSDate、NSArray、またはNSDictionaryのインスタンス(またはコレクションの場合はインスタンスの組み合わせ)でなければなりません。他のタイプのオブジェクトを保存する場合、通常はNSDataのインスタンスを作成するためにオブジェクトをアーカイブする必要があります。

NSKeydArchiverを使用する必要があります。ドキュメントは here および例 here および here にあります。

2
Valdmer