NSCoding을 사용하여 사용자 지정 Swift 클래스를 UserDefaults에 저장


79

현재 사용자 지정 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을 사용하고 있습니다. 감사.


여기에 좋은 튜토리얼이 있습니다 : ios8programminginswift.wordpress.com/2014/08/17/… 여기에 좋은 reddit 토론이 있습니다 : reddit.com/r/swift/comments/28sp3z/… 또 다른 좋은 게시물 : myswiftjourney.me/2014/ 10 / 01 /… 전체 객체 저장을 위해 전체 NSCoding을 권장합니다. 아래의 두 가지 예, 전체 클래스 구조를 가진 두 번째 예 : stackoverflow.com/questions/26233067/… stackoverfl
Steve Rosenberg

답변:


62

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

영구적으로 저장하려면 어떻게 작동해야합니다. 미리 감사드립니다
Birju

1
나는 이것을 시도했지만 오류 메시지가 나타납니다. Type Blog가 Decodable 프로토콜을 준수하지 않습니다
vofili

@vofili Codable에서 Blog 클래스를 상속 받았습니까?
Ghulam Rasool

내 블로그 클래스에 [String : Any] 사전이있는 경우 어떻게해야합니까?
Ali Raza

46

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

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

1
감사. 그게됐다. NSKeyedUnarchiver는 누락 된 부분이었습니다. 작동하는 샘플로 내 질문을 업데이트하겠습니다. unarc.setClass를 생략하고 대신 unarc.decodeObjectForKey를-이 경우-Blog 클래스로 다운 캐스트 할 수있는 것 같습니다.
Georg

1
setClass플레이 그라운드에서 작업 하는 경우 에만 필요합니다. 여러 가지 이유로 클래스가 그곳에 자동으로 등록되지 않습니다.
David Berry

blog에 위의 변수는 사용자 지정 대상이 아닌 if let data = ud.objectForKey("blog") as? NSData { let blog = NSKeyedUnarchiver.unarchiveObjectWithData(data) }내 경우에는 내가 이렇게 내 사용자 정의 객체에 캐스팅 할 필요if let data = ud.objectForKey("blog") as? NSData { let blog = NSKeyedUnarchiver.unarchiveObjectWithData(data) if let thisblog = blog as? Blog { return thisblog //this is the custom object from nsuserdefaults } }
CaffeineShots

21

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

내 사용자 정의 클래스에 UIImage 또는 Data 유형이 있으면 어떻게됩니까? 그런 종류의 클래스를 UserDefaults에 어떻게 저장할 수 있습니까?
Neelam Pursnani

12

다음은 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")


이것은 데이터를 영구적으로 저장하지 않습니다. 응용 프로그램이 종료 된 후 데이터를 저장하고 싶습니다.
Birju

9

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를 사용하고 사용자 지정 개체 배열을 파일에 직접 저장하는 것이 더 쉬울 것이라는 점을 지적하고 싶습니다. 여기 에서 내 대답의 차이점에 대해 자세히 알아볼 수 있습니다 .


참고 : blogName이 선택 사항 인 이유는 객체가 사용자가 보유한 실제 블로그를 미러링하기 때문입니다. 사용자가 새 블로그에 대한 URL을 추가하면 블로그 이름은 제목 태그에서 자동으로 파생됩니다. 따라서 객체 생성시에는 블로그 이름이 없으며 새 블로그를 "이름 없음"또는 이와 유사한 이름으로 부르는 것을 피하고 싶었습니다.
Georg

7

Swift 4에는 NSCoding 프로토콜을 대체하는 새로운 프로토콜이 있습니다. 호출 Codable되고 클래스와 Swift 유형을 지원합니다! (열거 형, 구조체) :

struct CustomStruct: Codable {
    let name: String
    let isActive: Bool
}

클래스가 구현 Codable 및 문자열의 선택적 배열을 가지고 :'Attempt to insert non-property list object
Vyachaslav Gerchicov

이 대답은 완전히 틀 렸습니다. 대체 Codable하지 않습니다NSCoding . 객체를 직접 저장하는 유일한 방법 UserDefaults은 클래스를 NSCoding준수 하도록 만드는 것 입니다. 이후 NSCodingA는 class protocol이를 구조체 / ENUM 타입에 적용될 수 없다. 그러나 여전히 struct / enum을 Coding준수하도록 만들고 JSONSerializer.NET에 Data저장할 수있는 로 인코딩하는 데 사용할 수 있습니다 UserDefaults.
Josh

4

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

1

내 구조체를 사용합니다. 훨씬 쉽습니다.

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

1

이처럼 히스토리 모델 클래스를 NSCoding에 만들어야합니다.

스위프트 4 & 5.

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

이것이 문제를 해결하기를 바랍니다.


1

UserDefaults에 사용자 지정 개체 를 저장하는 간단한 예 는 다음과 같습니다.

THE GREAT 'CODABLE'의 도움으로 더 이상 객체를 저장 / 검색하기위한 상용구 코드를 작성할 필요가 없습니다. 이것이 짜증나는 수동 인코딩 / 디코딩으로부터 구하기위한 것입니다.

따라서 이미 NSCoding을 사용하고있는 경우 코드에서 아래 두 가지 방법을 제거하고 Codable (Encodable + Decodable의 조합) 프로토콜로 전환하십시오.

required init(coder decoder: NSCoder) // NOT REQUIRED ANY MORE, DECODABLE TAKES CARE OF IT

func encode(with coder: NSCoder) // NOT REQUIRED ANY MORE, ENCODABLE TAKES CARE OF IT

Codable의 단순성부터 시작하겠습니다.

UserDefaults에 저장할 사용자 정의 클래스 또는 구조체 를 만듭니다.

struct Person : Codable {
    var name:String
}

OR

class Person : Codable {

    var name:String

    init(name:String) {
        self.name = name
    }
}

아래와 같이 UserDefaults에 개체 저장

 if let encoded = try? JSONEncoder().encode(Person(name: "Dhaval")) {
     UserDefaults.standard.set(encoded, forKey: "kSavedPerson")
 }

아래와 같이 UserDefaults에서 개체로드

guard let savedPersonData = UserDefaults.standard.object(forKey: "kSavedPerson") as? Data else { return }
guard let savedPerson = try? JSONDecoder().decode(Person.self, from: savedPersonData) else { return }

print("\n Saved person name : \(savedPerson.name)")

그게 다야.


난 그냥 당신이 두 가지 질문 (에 대한 동일한 답변을 게시 한 것을 발견 한 이것이것 ). 하나의 답으로 답할 수있는 두 개의 질문이 보이면 중복 플래그를 올리십시오. 두 질문이 다른 경우 각 질문에 대한 답변을 조정하십시오. 감사!
Halfer

0

이 질문이 오래 되었다는 것을 알고 있지만 도움이 될만한 작은 라이브러리 를 작성했습니다 .

다음과 같이 개체를 저장합니다.

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()

0

클래스에 Codable 프로토콜을 구현 한 후 PropertyListEncoder를 사용하여 사용자 지정 개체를 UserDefaults에 저장할 수 있습니다. 사용자 정의 클래스를 User로 지정

class User: NSObject, Codable {

  var id: Int?
  var name: String?
  var address: String?
}

Codable은 Decodable 및 Encodable 프로토콜을 모두 구현 함을 의미합니다. 이름에서 알 수 있듯이 Encodable 및 Decodable을 구현하면 클래스에 JSON 또는 속성 목록과 같은 외부 표현으로 인코딩 및 디코딩 할 수있는 기능이 제공됩니다.

이제 속성 목록으로 번역 된 모델을 저장할 수 있습니다.

if let encodedData = try? PropertyListEncoder().encode(userModel){
  UserDefaults.standard.set(encodedData, forKey:"YOUR_KEY")
  UserDefaults.standard.synchronize();
}

그리고 PropertyListDecoder를 사용하여 모델을 검색 할 수 있습니다. 속성 목록으로 인코딩했기 때문입니다.

if let data = UserDefaults.standard.value(forKey:"YOUR_KEY") as? Data {
     return try? PropertyListDecoder().decode(User.self, from:data)
}

-3

속성 목록에 객체를 직접 저장할 수 없습니다. 개별 문자열 또는 기타 기본 유형 (정수 등) 만 저장할 수 있으므로 다음과 같이 개별 문자열로 저장해야합니다.

   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)
   }
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.