[swift] 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을 사용하고 있습니다. 감사.



답변

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) {
}


답변

첫 번째 문제는 얽 히지 않은 클래스 이름이 있는지 확인해야한다는 것입니다.

@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)
}


답변

@ 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 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
    }
}

Dummy2 개의 기본 필드가 있는 사용자 지정 개체를 저장하고로드하려고 합니다. 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 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 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")
    }
}