Cách lưu trữ các đối tượng tùy chỉnh trong NSUserDefaults


268

Được rồi, vì vậy tôi đã thực hiện một số chọc, và tôi nhận ra vấn đề của mình, nhưng tôi không biết làm thế nào để khắc phục nó. Tôi đã thực hiện một lớp tùy chỉnh để giữ một số dữ liệu. Tôi tạo các đối tượng cho lớp này và tôi cần chúng kéo dài giữa các phiên. Trước khi tôi đưa tất cả thông tin của mình vào NSUserDefaults, nhưng điều này không hoạt động.

-[NSUserDefaults setObject:forKey:]: Attempt to insert non-property value '<Player: 0x3b0cc90>' of class 'Player'.

Đó là thông báo lỗi tôi nhận được khi tôi đặt lớp tùy chỉnh của mình, "Người chơi", trong NSUserDefaults. Bây giờ, tôi đã đọc lên rằng dường như NSUserDefaultschỉ lưu trữ một số loại thông tin. Vì vậy, làm thế nào tôi có được các đối tượng của tôi vào NSUSerDefaults?

Tôi đọc rằng nên có một cách để "mã hóa" đối tượng tùy chỉnh của mình và sau đó đặt nó vào, nhưng tôi không chắc cách triển khai nó, sự giúp đỡ sẽ được đánh giá cao! Cảm ơn bạn!

****BIÊN TẬP****

Được rồi, vì vậy tôi đã làm việc với mã được đưa ra dưới đây (Cảm ơn bạn!), Nhưng tôi vẫn gặp một số vấn đề. Về cơ bản, mã bị sập bây giờ và tôi không chắc tại sao, vì nó không đưa ra bất kỳ lỗi nào. Có lẽ tôi đang thiếu một cái gì đó cơ bản và tôi quá mệt mỏi, nhưng chúng ta sẽ thấy. Đây là cách triển khai lớp Tùy chỉnh của tôi, "Trình phát":

@interface Player : NSObject {
    NSString *name;
    NSNumber *life;
    //Log of player's life
}
//Getting functions, return the info
- (NSString *)name;
- (int)life;


- (id)init;

//These are the setters
- (void)setName:(NSString *)input; //string
- (void)setLife:(NSNumber *)input; //number    

@end

Tập tin thực hiện:

#import "Player.h"
@implementation Player
- (id)init {
    if (self = [super init]) {
        [self setName:@"Player Name"];
        [self setLife:[NSNumber numberWithInt:20]];
        [self setPsnCounters:[NSNumber numberWithInt:0]];
    }
    return self;
}

- (NSString *)name {return name;}
- (int)life {return [life intValue];}
- (void)setName:(NSString *)input {
    [input retain];
    if (name != nil) {
        [name release];
    }
    name = input;
}
- (void)setLife:(NSNumber *)input {
    [input retain];
    if (life != nil) {
        [life release];
    }
    life = input;
}
/* This code has been added to support encoding and decoding my objecst */

-(void)encodeWithCoder:(NSCoder *)encoder
{
    //Encode the properties of the object
    [encoder encodeObject:self.name forKey:@"name"];
    [encoder encodeObject:self.life forKey:@"life"];
}

-(id)initWithCoder:(NSCoder *)decoder
{
    self = [super init];
    if ( self != nil )
    {
        //decode the properties
        self.name = [decoder decodeObjectForKey:@"name"];
        self.life = [decoder decodeObjectForKey:@"life"];
    }
    return self;
}
-(void)dealloc {
    [name release];
    [life release];
    [super dealloc];
}
@end

Vì vậy, đó là lớp học của tôi, khá thẳng về phía trước, tôi biết nó hoạt động trong việc tạo ra các đối tượng của tôi. Vì vậy, đây là các phần có liên quan của tệp AppDelegate (nơi tôi gọi các chức năng mã hóa và giải mã):

@class MainViewController;

@interface MagicApp201AppDelegate : NSObject <UIApplicationDelegate> {
    UIWindow *window;
    MainViewController *mainViewController;
}

@property (nonatomic, retain) IBOutlet UIWindow *window;
@property (nonatomic, retain) MainViewController *mainViewController;

-(void)saveCustomObject:(Player *)obj;
-(Player *)loadCustomObjectWithKey:(NSString*)key;


@end

Và sau đó là các phần quan trọng của tệp thực hiện:

    #import "MagicApp201AppDelegate.h"
    #import "MainViewController.h"
    #import "Player.h"

    @implementation MagicApp201AppDelegate


    @synthesize window;
    @synthesize mainViewController;


    - (void)applicationDidFinishLaunching:(UIApplication *)application {
    NSUserDefaults *prefs = [NSUserDefaults standardUserDefaults];
        //First check to see if some things exist
        int startup = [prefs integerForKey:@"appHasLaunched"];
        if (startup == nil) {
//Make the single player 
        Player *singlePlayer = [[Player alloc] init];
        NSLog([[NSString alloc] initWithFormat:@"%@\n%d\n%d",[singlePlayer name], [singlePlayer life], [singlePlayer psnCounters]]); //  test
        //Encode the single player so it can be stored in UserDefaults
        id test = [MagicApp201AppDelegate new];
        [test saveCustomObject:singlePlayer];
        [test release];
}
[prefs synchronize];
}

-(void)saveCustomObject:(Player *)object
{ 
    NSUserDefaults *prefs = [NSUserDefaults standardUserDefaults];
    NSData *myEncodedObject = [NSKeyedArchiver archivedDataWithRootObject:object];
    [prefs setObject:myEncodedObject forKey:@"testing"];
}

-(Player *)loadCustomObjectWithKey:(NSString*)key
{
    NSUserDefaults *prefs = [NSUserDefaults standardUserDefaults];
    NSData *myEncodedObject = [prefs objectForKey:key ];
    Player *obj = (Player *)[NSKeyedUnarchiver unarchiveObjectWithData: myEncodedObject];
    return obj;
}

Eeee, xin lỗi về tất cả các mã. Chỉ cần cố gắng để giúp đỡ. Về cơ bản, ứng dụng sẽ khởi chạy và sau đó sụp đổ ngay lập tức. Tôi đã thu hẹp nó xuống phần mã hóa của ứng dụng, đó là nơi nó bị sập, vì vậy tôi đang làm gì đó sai nhưng tôi không chắc là gì. Giúp đỡ sẽ được đánh giá cao một lần nữa, cảm ơn bạn!

(Tôi chưa nhận được giải mã, vì tôi chưa mã hóa hoạt động được.)


Bạn có dấu vết ngăn xếp hoặc nhiều thông tin hơn về sự cố, chẳng hạn như số dòng nào gây ra sự cố? Tôi không thấy ngay lập tức bất cứ điều gì sai với mã, vì vậy một điểm khởi đầu sẽ hữu ích.
chrissr

Trong ví dụ trên, bạn đã sử dụng encodeObject để lưu trữ self.life là int. Bạn nên sử dụng encodeInt thay thế.
broot

Câu trả lời:


516

Trên lớp Trình phát của bạn, hãy triển khai hai phương thức sau (thay thế các lệnh gọi tới encodeObject bằng một cái gì đó có liên quan đến đối tượng của riêng bạn):

- (void)encodeWithCoder:(NSCoder *)encoder {
    //Encode properties, other class variables, etc
    [encoder encodeObject:self.question forKey:@"question"];
    [encoder encodeObject:self.categoryName forKey:@"category"];
    [encoder encodeObject:self.subCategoryName forKey:@"subcategory"];
}

- (id)initWithCoder:(NSCoder *)decoder {
    if((self = [super init])) {
        //decode properties, other class vars
        self.question = [decoder decodeObjectForKey:@"question"];
        self.categoryName = [decoder decodeObjectForKey:@"category"];
        self.subCategoryName = [decoder decodeObjectForKey:@"subcategory"];
    }
    return self;
}

Đọc và viết từ NSUserDefaults:

- (void)saveCustomObject:(MyObject *)object key:(NSString *)key {
    NSData *encodedObject = [NSKeyedArchiver archivedDataWithRootObject:object];
    NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
    [defaults setObject:encodedObject forKey:key];
    [defaults synchronize];

}

- (MyObject *)loadCustomObjectWithKey:(NSString *)key {
    NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
    NSData *encodedObject = [defaults objectForKey:key];
    MyObject *object = [NSKeyedUnarchiver unarchiveObjectWithData:encodedObject];
    return object;
}

Mã đáng xấu hổ mượn từ: tiết kiệm lớp trong nsuserdefaults


Chỉnh sửa bài viết của tôi ở trên để phản ánh những thay đổi của tôi.
Ethan Mick

@chrissr bạn có lỗi trong NSUserDefaults mặc định = [NSUserDefaults standardUserDefaults]; ... phải là NSUserDefaults * mặc định.
Maggie

2
NSKeyedArchiver đá ... dường như nó thậm chí tự động đi xuống các đối tượng NSArray hoặc NSDipedia và mã hóa bất kỳ đối tượng tùy chỉnh mã hóa nào bên trong.
BadPirate

2
Tôi không phải là người phạm tội, chỉ là một câu hỏi chính hãng, không phải nó chống lại các hướng dẫn của táo để sử dụng setters tổng hợp tức là self.property trong các phương thức init?
Samhan Salahuddin

2
@chrissr Hãy thay đổi NSData *myEncodedObject = [NSKeyedArchiver archivedDataWithRootObject:object];cho NSData *encodedObject = [NSKeyedArchiver archivedDataWithRootObject:object];. Các biến không khớp.
GoRoS

36

Tôi tạo thư viện RMMapper ( https://github.com/roomorama/RMMapper ) để giúp lưu đối tượng tùy chỉnh vào NSUserDefaults dễ dàng và thuận tiện hơn, vì việc triển khai encodeWithCoder và initWithCoder rất nhàm chán!

Để đánh dấu một lớp là có thể lưu trữ, chỉ cần sử dụng: #import "NSObject+RMArchivable.h"

Để lưu một đối tượng tùy chỉnh vào NSUserDefaults:

#import "NSUserDefaults+RMSaveCustomObject.h"
NSUserDefaults* defaults = [NSUserDefaults standardUserDefaults];
[defaults rm_setCustomObject:user forKey:@"SAVED_DATA"];

Để nhận obj tùy chỉnh từ NSUserDefaults:

user = [defaults rm_customObjectForKey:@"SAVED_DATA"]; 

Thư viện của bạn giả định rằng bạn có các thuộc tính cho mọi thứ bạn muốn tồn tại và bạn muốn duy trì mọi thứ bạn có thuộc tính. Nếu điều này là đúng, thư viện của bạn chắc chắn có thể hữu ích, nhưng tôi nghĩ bạn sẽ thấy rằng trong nhiều trường hợp thì không.
Erik B

Nó chỉ hữu ích khi duy trì đối tượng đơn giản, tôi sẽ nói cách sử dụng của nó khá giống với Gson trong Java. Những trường hợp bạn đang xem xét?
thomasdao

7
Điều đó thực sự hữu ích, rất vui vì tôi tiếp tục đọc câu trả lời; D
Albara

Còn khi đối tượng tùy chỉnh của tôi có thuộc tính đối tượng tùy chỉnh khác thì sao?
Shial

@Shial: nếu bạn làm cho đối tượng tùy chỉnh khác có thể lưu trữ được, nó cũng sẽ được lưu
thomasdao

35

Swift 4

Giới thiệu giao thức Codable thực hiện tất cả các phép thuật cho các loại nhiệm vụ này. Chỉ cần tuân thủ cấu trúc / lớp tùy chỉnh của bạn với nó:

struct Player: Codable {
  let name: String
  let life: Double
}

Và để lưu trữ trong Mặc định, bạn có thể sử dụng Bộ giải mã / Bộ giải mã thuộc tính :

let player = Player(name: "Jim", life: 3.14)
UserDefaults.standard.set(try! PropertyListEncoder().encode(player), forKey: kPlayerDefaultsKey)

let storedObject: Data = UserDefaults.standard.object(forKey: kPlayerDefaultsKey) as! Data
let storedPlayer: Player = try! PropertyListDecoder().decode(Player.self, from: storedObject)

Nó cũng sẽ hoạt động như vậy đối với các mảng và các lớp container khác của các đối tượng như vậy:

try! PropertyListDecoder().decode([Player].self, from: storedArray)

1
Hoạt động như một bùa mê
Nathan Barreto

Lưu ý rằng bạn chỉ nhận được Codablehành vi miễn phí khi tất cả các thành viên cá thể là chính họ Codable- nếu không, bạn phải tự viết một số mã hóa,
BallpointBen

1
Hay chỉ đơn giản là phù hợp với các loại thành viên Codablequá. Và cứ thế, đệ quy. Chỉ trong trường hợp rất tùy chỉnh, bạn sẽ cần phải viết mã mã.
Sergiu Todirascu

Cách dễ nhất với Swift 4.
vào

31

Nếu bất cứ ai đang tìm kiếm một phiên bản nhanh chóng:

1) Tạo một lớp tùy chỉnh cho dữ liệu của bạn

class customData: NSObject, NSCoding {
let name : String
let url : String
let desc : String

init(tuple : (String,String,String)){
    self.name = tuple.0
    self.url = tuple.1
    self.desc = tuple.2
}
func getName() -> String {
    return name
}
func getURL() -> String{
    return url
}
func getDescription() -> String {
    return desc
}
func getTuple() -> (String,String,String) {
    return (self.name,self.url,self.desc)
}

required init(coder aDecoder: NSCoder) {
    self.name = aDecoder.decodeObjectForKey("name") as! String
    self.url = aDecoder.decodeObjectForKey("url") as! String
    self.desc = aDecoder.decodeObjectForKey("desc") as! String
}

func encodeWithCoder(aCoder: NSCoder) {
    aCoder.encodeObject(self.name, forKey: "name")
    aCoder.encodeObject(self.url, forKey: "url")
    aCoder.encodeObject(self.desc, forKey: "desc")
} 
}

2) Để lưu dữ liệu sử dụng chức năng sau:

func saveData()
    {
        let data  = NSKeyedArchiver.archivedDataWithRootObject(custom)
        let defaults = NSUserDefaults.standardUserDefaults()
        defaults.setObject(data, forKey:"customArray" )
    }

3) Để truy xuất:

if let data = NSUserDefaults.standardUserDefaults().objectForKey("customArray") as? NSData
        {
             custom = NSKeyedUnarchiver.unarchiveObjectWithData(data) as! [customData]
        }

Lưu ý: Ở đây tôi đang lưu và lấy một mảng các đối tượng lớp tùy chỉnh.


9

Lấy câu trả lời của @ chrissr và chạy với nó, mã này có thể được triển khai thành một danh mục đẹp NSUserDefaultsđể lưu và truy xuất các đối tượng tùy chỉnh:

@interface NSUserDefaults (NSUserDefaultsExtensions)

- (void)saveCustomObject:(id<NSCoding>)object
                     key:(NSString *)key;
- (id<NSCoding>)loadCustomObjectWithKey:(NSString *)key;

@end


@implementation NSUserDefaults (NSUserDefaultsExtensions)


- (void)saveCustomObject:(id<NSCoding>)object
                     key:(NSString *)key {
    NSData *encodedObject = [NSKeyedArchiver archivedDataWithRootObject:object];
    [self setObject:encodedObject forKey:key];
    [self synchronize];

}

- (id<NSCoding>)loadCustomObjectWithKey:(NSString *)key {
    NSData *encodedObject = [self objectForKey:key];
    id<NSCoding> object = [NSKeyedUnarchiver unarchiveObjectWithData:encodedObject];
    return object;
}

@end

Sử dụng:

[[NSUserDefaults standardUserDefaults] saveCustomObject:myObject key:@"myKey"];

7

Swift 3

class MyObject: NSObject, NSCoding  {
    let name : String
    let url : String
    let desc : String

    init(tuple : (String,String,String)){
        self.name = tuple.0
        self.url = tuple.1
        self.desc = tuple.2
    }
    func getName() -> String {
        return name
    }
    func getURL() -> String{
        return url
    }
    func getDescription() -> String {
        return desc
    }
    func getTuple() -> (String, String, String) {
        return (self.name,self.url,self.desc)
    }

    required init(coder aDecoder: NSCoder) {
        self.name = aDecoder.decodeObject(forKey: "name") as? String ?? ""
        self.url = aDecoder.decodeObject(forKey: "url") as? String ?? ""
        self.desc = aDecoder.decodeObject(forKey: "desc") as? String ?? ""
    }

    func encode(with aCoder: NSCoder) {
        aCoder.encode(self.name, forKey: "name")
        aCoder.encode(self.url, forKey: "url")
        aCoder.encode(self.desc, forKey: "desc")
    }
    }

để lưu trữ và truy xuất:

func save() {
            let data  = NSKeyedArchiver.archivedData(withRootObject: object)
            UserDefaults.standard.set(data, forKey:"customData" )
        }
        func get() -> MyObject? {
            guard let data = UserDefaults.standard.object(forKey: "customData") as? Data else { return nil }
            return NSKeyedUnarchiver.unarchiveObject(with: data) as? MyObject
        }

Chỉ cần một lời nhắc nhở: selfkhông bắt buộc trước các biến trong initencode.
Tamás Sengel

Bạn có thể chỉ ra cách bạn đang hướng nội đối tượng với NSCoder
Abhishek Thapliyal

@AbhishekThapliyal, tôi không hiểu bạn. init (coder aDecoder: NSCoder) khởi tạo nó
Vyacheslav

6

Đồng bộ hóa dữ liệu / đối tượng mà bạn đã lưu vào NSUserDefaults

-(void)saveCustomObject:(Player *)object
{ 
    NSUserDefaults *prefs = [NSUserDefaults standardUserDefaults];
    NSData *myEncodedObject = [NSKeyedArchiver archivedDataWithRootObject:object];
    [prefs setObject:myEncodedObject forKey:@"testing"];
    [prefs synchronize];
}

Hy vọng điều này sẽ giúp bạn. Cảm ơn


Lưu ý mọi người từ tương lai kể từ Swift 3, bạn không nên gọi "đồng bộ hóa".
Jose Ramirez

@JozemiteApps tại sao? hoặc bạn có thể gửi một liên kết với lời giải thích cho chủ đề này?
code4latte

@ code4latte Tôi không biết liệu nó có bị thay đổi hay không nhưng tài liệu của Apple nói rằng "Phương thức đồng bộ hóa (), được tự động gọi trong các khoảng thời gian định kỳ, giữ cho bộ nhớ cache trong bộ nhớ được đồng bộ hóa với cơ sở dữ liệu mặc định của người dùng." Trước đây, nó nói rằng bạn chỉ nên gọi nó NẾU bạn chắc chắn rằng bạn cần dữ liệu được lưu ngay lập tức. Tôi đã đọc khoảng một vài lần rằng người dùng không nên gọi nó nữa.
Jose Ramirez
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.