NSCoding을 사용하여 사용자 지정 Swift 클래스를 UserDefaults에 저장
현재 사용자 지정 Swift 클래스를 NSUserDefaults에 저장하려고합니다. 내 플레이 그라운드의 코드는 다음과 같습니다.
import Foundation
class Blog : NSObject, NSCoding {
var blogName: String?
override init() {}
required init(coder aDecoder: NSCoder) {
if let blogName = aDecoder.decodeObjectForKey("blogName") as? String {
self.blogName = blogName
}
}
func encodeWithCoder(aCoder: NSCoder) {
if let blogName = self.blogName {
aCoder.encodeObject(blogName, forKey: "blogName")
}
}
}
var blog = Blog()
blog.blogName = "My Blog"
let ud = NSUserDefaults.standardUserDefaults()
ud.setObject(blog, forKey: "blog")
코드를 실행할 때 다음 오류가 발생합니다.
실행이 중단되었습니다. 이유 : 신호 SIGABRT.
마지막 줄에 ( ud.setObject
...)
메시지가있는 앱에서도 동일한 코드가 충돌합니다.
"형식에 유효하지 않은 속성 목록 : 200 (속성 목록은 'CFType'유형의 개체를 포함 할 수 없음)"
아무도 도울 수 있습니까? Maverick에서 Xcode 6.0.1을 사용하고 있습니다. 감사.
첫 번째 문제는 얽 히지 않은 클래스 이름이 있는지 확인해야한다는 것입니다.
@objc(Blog)
class Blog : NSObject, NSCoding {
그런 다음 객체를 NSData로 인코딩해야 사용자 기본값에 저장할 수 있습니다.
ud.setObject(NSKeyedArchiver.archivedDataWithRootObject(blog), forKey: "blog")
마찬가지로 개체를 복원하려면 보관을 취소해야합니다.
if let data = ud.objectForKey("blog") as? NSData {
let unarc = NSKeyedUnarchiver(forReadingWithData: data)
unarc.setClass(Blog.self, forClassName: "Blog")
let blog = unarc.decodeObjectForKey("root")
}
플레이 그라운드에서 사용하지 않는 경우 클래스를 직접 등록 할 필요가 없기 때문에 좀 더 간단합니다.
if let data = ud.objectForKey("blog") as? NSData {
let blog = NSKeyedUnarchiver.unarchiveObjectWithData(data)
}
Swift 4에서는 Codable을 사용하십시오.
귀하의 경우 다음 코드를 사용하십시오.
class Blog : Codable {
var blogName: String?
}
이제 개체를 만듭니다. 예를 들면 :
var blog = Blog()
blog.blogName = "My Blog"
이제 다음과 같이 인코딩하십시오.
if let encoded = try? JSONEncoder().encode(blog) {
UserDefaults.standard.set(encoded, forKey: "blog")
}
다음과 같이 디코딩합니다.
if let blogData = UserDefaults.standard.data(forKey: "blog"),
let blog = try? JSONDecoder().decode(Blog.self, from: blogData) {
}
@ dan-beaulieu가 내 질문에 답할 것을 제안했듯이 :
이제 작동하는 코드는 다음과 같습니다.
참고 : 코드가 플레이 그라운드에서 작동하기 위해 클래스 이름의 디맨 글링이 필요하지 않았습니다.
import Foundation
class Blog : NSObject, NSCoding {
var blogName: String?
override init() {}
required init(coder aDecoder: NSCoder) {
if let blogName = aDecoder.decodeObjectForKey("blogName") as? String {
self.blogName = blogName
}
}
func encodeWithCoder(aCoder: NSCoder) {
if let blogName = self.blogName {
aCoder.encodeObject(blogName, forKey: "blogName")
}
}
}
let ud = NSUserDefaults.standardUserDefaults()
var blog = Blog()
blog.blogName = "My Blog"
ud.setObject(NSKeyedArchiver.archivedDataWithRootObject(blog), forKey: "blog")
if let data = ud.objectForKey("blog") as? NSData {
let unarc = NSKeyedUnarchiver(forReadingWithData: data)
let newBlog = unarc.decodeObjectForKey("root") as Blog
}
Swift 2.1 및 Xcode 7.1.1로 테스트 됨
blogName이 선택 사항이 될 필요가 없다면 (그렇지 않다고 생각합니다) 약간 다른 구현을 권장합니다.
class Blog : NSObject, NSCoding {
var blogName: String
// designated initializer
//
// ensures you'll never create a Blog object without giving it a name
// unless you would need that for some reason?
//
// also : I would not override the init method of NSObject
init(blogName: String) {
self.blogName = blogName
super.init() // call NSObject's init method
}
func encodeWithCoder(aCoder: NSCoder) {
aCoder.encodeObject(blogName, forKey: "blogName")
}
required convenience init?(coder aDecoder: NSCoder) {
// decoding could fail, for example when no Blog was saved before calling decode
guard let unarchivedBlogName = aDecoder.decodeObjectForKey("blogName") as? String
else {
// option 1 : return an default Blog
self.init(blogName: "unnamed")
return
// option 2 : return nil, and handle the error at higher level
}
// convenience init must call the designated init
self.init(blogName: unarchivedBlogName)
}
}
테스트 코드는 다음과 같습니다.
let blog = Blog(blogName: "My Blog")
// save
let ud = NSUserDefaults.standardUserDefaults()
ud.setObject(NSKeyedArchiver.archivedDataWithRootObject(blog), forKey: "blog")
ud.synchronize()
// restore
guard let decodedNSData = ud.objectForKey("blog") as? NSData,
let someBlog = NSKeyedUnarchiver.unarchiveObjectWithData(decodedNSData) as? Blog
else {
print("Failed")
return
}
print("loaded blog with name : \(someBlog.blogName)")
마지막으로 NSUserDefaults를 사용하는 대신 NSKeyedArchiver를 사용하고 사용자 지정 개체 배열을 파일에 직접 저장하는 것이 더 쉬울 것이라는 점을 지적하고 싶습니다. 여기 에서 내 대답의 차이점에 대해 자세히 알아볼 수 있습니다 .
Swift 4에는 NSCoding 프로토콜을 대체하는 새로운 프로토콜이 있습니다. 호출 Codable
되고 클래스와 Swift 유형을 지원합니다! (열거 형, 구조체) :
struct CustomStruct: Codable {
let name: String
let isActive: Bool
}
다음은 Swift 4 & 5를 위한 완벽한 솔루션입니다 .
먼저 UserDefaults
확장 에서 도우미 메서드를 구현 합니다.
extension UserDefaults {
func set<T: Encodable>(encodable: T, forKey key: String) {
if let data = try? JSONEncoder().encode(encodable) {
set(data, forKey: key)
}
}
func value<T: Decodable>(_ type: T.Type, forKey key: String) -> T? {
if let data = object(forKey: key) as? Data,
let value = try? JSONDecoder().decode(type, from: data) {
return value
}
return nil
}
}
Dummy
2 개의 기본 필드가 있는 사용자 지정 개체를 저장하고로드하려고 합니다. Dummy
준수해야합니다 Codable
:
struct Dummy: Codable {
let value1 = "V1"
let value2 = "V2"
}
// Save
UserDefaults.standard.set(encodable: Dummy(), forKey: "K1")
// Load
let dummy = UserDefaults.standard.value(Dummy.self, forKey: "K1")
Swift 3 버전 :
class CustomClass: NSObject, NSCoding {
let name: String
let isActive: Bool
init(name: String, isActive: Bool) {
self.name = name
self.isActive = isActive
}
// MARK: NSCoding
required convenience init?(coder decoder: NSCoder) {
guard let name = decoder.decodeObject(forKey: "name") as? String,
let isActive = decoder.decodeObject(forKey: "isActive") as? Bool
else { return nil }
self.init(name: name, isActive: isActive)
}
func encode(with coder: NSCoder) {
coder.encode(self.name, forKey: "name")
coder.encode(self.isActive, forKey: "isActive")
}
}
내 구조체를 사용합니다. 훨씬 쉽습니다.
struct UserDefaults {
private static let kUserInfo = "kUserInformation"
var UserInformation: DataUserInformation? {
get {
guard let user = NSUserDefaults.standardUserDefaults().objectForKey(UserDefaults.kUserInfo) as? DataUserInformation else {
return nil
}
return user
}
set {
NSUserDefaults.standardUserDefaults().setObject(newValue, forKey: UserDefaults.kUserInfo)
}
}
}
사용:
let userinfo = UserDefaults.UserInformation
이 질문이 오래 되었다는 것을 알고 있지만 도움이 될만한 작은 라이브러리 를 작성했습니다 .
다음과 같이 개체를 저장합니다.
class MyObject: NSObject, Codable {
var name:String!
var lastName:String!
enum CodingKeys: String, CodingKey {
case name
case lastName = "last_name"
}
}
self.myStoredPref = Pref<MyObject>(prefs:UserDefaults.standard,key:"MyObjectKey")
let myObject = MyObject()
//... set the object values
self.myStoredPref.set(myObject)
그런 다음 개체를 원래 값으로 다시 추출하려면 :
let myStoredValue: MyObject = self.myStoredPref.get()
이처럼 히스토리 모델 클래스를 NSCoding에 만들어야합니다.
class SSGIFHistoryModel: NSObject, NSCoding {
var bg_image: String
var total_like: String
var total_view: String
let gifID: String
var title: String
init(bg_image: String, total_like: String, total_view: String, gifID: String, title: String) {
self.bg_image = bg_image
self.total_like = total_like
self.total_view = total_view
self.gifID = gifID
self.title = title
}
required init?(coder aDecoder: NSCoder) {
self.bg_image = aDecoder.decodeObject(forKey: "bg_image") as? String ?? ""
self.total_like = aDecoder.decodeObject(forKey: "total_like") as? String ?? ""
self.total_view = aDecoder.decodeObject(forKey: "total_view") as? String ?? ""
self.gifID = aDecoder.decodeObject(forKey: "gifID") as? String ?? ""
self.title = aDecoder.decodeObject(forKey: "title") as? String ?? ""
}
func encode(with aCoder: NSCoder) {
aCoder.encode(bg_image, forKey: "bg_image")
aCoder.encode(total_like, forKey: "total_like")
aCoder.encode(total_view, forKey: "total_view")
aCoder.encode(gifID, forKey: "gifID")
aCoder.encode(title, forKey: "title")
}
}
그런 다음 객체를 NSData에 보관 한 다음 사용자 기본값에 저장하고 사용자 기본값에서 검색 한 다음 다시 보관 취소해야합니다. 다음과 같이 보관 및 보관 취소 할 수 있습니다.
func saveGIFHistory() {
var GIFHistoryArray: [SSGIFHistoryModel] = []
GIFHistoryArray.append(SSGIFHistoryModel(bg_image: imageData.bg_image, total_like: imageData.total_like, total_view: imageData.total_view, gifID: imageData.quote_id, title: imageData.title))
if let placesData = UserDefaults.standard.object(forKey: "GIFHistory") as? NSData {
if let placesArray = NSKeyedUnarchiver.unarchiveObject(with: placesData as Data) as? [SSGIFHistoryModel] {
for data in placesArray {
if data.gifID != imageData.quote_id {
GIFHistoryArray.append(data)
}
}
}
}
let placesData2 = NSKeyedArchiver.archivedData(withRootObject: GIFHistoryArray)
UserDefaults.standard.set(placesData2, forKey: "GIFHistory")
}
이것이 문제를 해결하기를 바랍니다.
속성 목록에 객체를 직접 저장할 수 없습니다. 개별 문자열 또는 기타 기본 유형 (정수 등) 만 저장할 수 있으므로 다음과 같이 개별 문자열로 저장해야합니다.
override init() {
}
required public init(coder decoder: NSCoder) {
func decode(obj:AnyObject) -> AnyObject? {
return decoder.decodeObjectForKey(String(obj))
}
self.login = decode(login) as! String
self.password = decode(password) as! String
self.firstname = decode(firstname) as! String
self.surname = decode(surname) as! String
self.icon = decode(icon) as! UIImage
}
public func encodeWithCoder(coder: NSCoder) {
func encode(obj:AnyObject) {
coder.encodeObject(obj, forKey:String(obj))
}
encode(login)
encode(password)
encode(firstname)
encode(surname)
encode(icon)
}
참고 URL : https://stackoverflow.com/questions/26469457/saving-custom-swift-class-with-nscoding-to-userdefaults
'program tip' 카테고리의 다른 글
Xcode에서 새 C ++ 프로젝트를 어떻게 생성합니까? (0) | 2020.10.24 |
---|---|
C #을 사용하는 .NET 4.0에서 Task.Run의 대체 방법은 무엇입니까? (0) | 2020.10.24 |
LINQ Group By Multiple 필드-구문 도움말 (0) | 2020.10.24 |
UTF-8 이메일을 보내는 방법은 무엇입니까? (0) | 2020.10.24 |
Dapper.NET에서 CommandTimeout 조정? (0) | 2020.10.24 |