Özel nesneler NSUser'da nasıl saklanır


268

Pekala, bu yüzden etrafta alay yapıyorum ve sorunumu fark ettim, ama nasıl düzelteceğimizi bilmiyorum. Bazı verileri tutmak için özel bir sınıf yaptım. Bu sınıf için nesneler yaparım ve oturumlar arasında sürmeleri gerekir. Tüm bilgilerimi girmeden önce NSUserDefaults, ama bu işe yaramıyor.

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

Bu benim özel sınıf, "Oyuncu" koymak zaman aldığım hata mesajı NSUserDefaults. Şimdi, görünüşe göre NSUserDefaultssadece bazı bilgi türlerinin saklandığını okudum . Öyleyse nesnelerimi nasıl alırım NSUSerDefaults?

Ben özel nesne "kodlamak" ve sonra koymak için bir yol olması gerektiğini okudum, ama nasıl uygulanacağından emin değilim, yardım takdir edilecektir! Teşekkür ederim!

****DÜZENLE****

Tamam, bu yüzden aşağıda verilen kodla çalıştım (Teşekkür ederim!), Ama hala bazı sorunlar yaşıyorum. Temel olarak, kod şimdi çöküyor ve neden olduğundan emin değilim, çünkü herhangi bir hata vermiyor. Belki de temel bir şeyi özlüyorum ve çok yorgunum ama göreceğiz. İşte benim Özel sınıf, "Oyuncu" uygulanması:

@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

Uygulama Dosyası:

#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

İşte bu benim sınıfım, oldukça basit, biliyorum ki nesnelerimi yapmakta işe yarıyor. İşte AppDelegate dosyasının ilgili bölümleri (şifreleme ve şifre çözme işlevlerini çağırdığı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

Ve sonra uygulama dosyasının önemli bölümleri:

    #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, tüm kodlar için üzgünüm. Sadece yardım etmeye çalışıyorum. Temel olarak, uygulama başlatılacak ve hemen çökecektir. Uygulamanın şifreleme kısmına daralttım, çöktüğü yer bu yüzden yanlış bir şey yapıyorum ama ne olduğundan emin değilim. Yardım tekrar takdir edilecektir, teşekkür ederim!

(Henüz şifrelemeyi çalışmadığım için şifresini çözemedim.)


Bir yığın izlemeniz veya kilitlenmeye hangi satır numarasının neden olduğu gibi daha fazla bilgi var mı? Hemen bir kod yanlış bir şey görmüyorum, bu yüzden bir başlangıç ​​noktası yardımcı olacaktır.
chrissr

Yukarıdaki örnekte bir int olan self.life'ı depolamak için encodeObject kullandınız. Bunun yerine encodeInt kullanmalısınız.
broot

Yanıtlar:


516

Player sınıfınızda aşağıdaki iki yöntemi uygulayın (encodeObject yerine çağrıları kendi nesnenizle alakalı bir şeyle değiştirme):

- (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;
}

Okuma ve yazma 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;
}

Utanmadan ödünç alınan kod: nsuserdefaults'ta tasarruf sınıfı


Yukarıdaki yazımı, değişikliklerimi yansıtacak şekilde düzenledi.
Ethan Mick

@chrissr NSUserDefaults'ta bir hata var varsayılanlar = [NSUserDefaults standardUserDefaults]; ... NSUserDefaults * varsayılanları olmalıdır.
Maggie

2
NSKeyedArchiver kayalar ... otomatik olarak NSArray veya NSDictionary nesnelerine iner ve içindeki herhangi bir kodlanabilir özel nesneyi kodlar.
BadPirate

2
Bilgiçlik yapmıyorum, sadece gerçek bir soru değil, elma yönergelerine karşı sentezlenmiş ayarlayıcıları kullanmak değil mi? İnit yöntemlerinde self.property?
Samhan Salahuddin

2
@chrissr değiştirin NSData *myEncodedObject = [NSKeyedArchiver archivedDataWithRootObject:object];için NSData *encodedObject = [NSKeyedArchiver archivedDataWithRootObject:object];. Değişkenler eşleşmiyor.
GoRoS

36

EncodeWithCoder ve initWithCoder uygulamak çok sıkıcı olduğundan, özel nesneyi NSUserDefaults'a kaydetmeye yardımcı olmak için bir RMMapper kütüphanesi ( https://github.com/roomorama/RMMapper ) oluşturuyorum.

Bir sınıfı arşivlenebilir olarak işaretlemek için şunu kullanın: #import "NSObject+RMArchivable.h"

Özel bir nesneyi NSUserDefaults'a kaydetmek için:

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

NSUserDefaults'tan özel obj almak için:

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

Kitaplığınız, kalıcı olmasını istediğiniz her şey için özelliklere sahip olduğunuzu ve özelliklerine sahip olduğunuz her şeyi devam ettirmek istediğinizi varsayar. Bu doğruysa, kitaplığınız kesinlikle yardımcı olabilir, ancak bence pek çok durumda bunun olmadığını göreceksiniz.
Erik B

Sadece düz nesneye devam etmek yararlıdır, kullanımının Java'daki Gson'a oldukça benzer olduğunu söyleyebilirim. Hangi vakalara bakıyorsunuz?
thomasdao

7
Bu gerçekten yardımcı oldu, cevapları okumaya devam
ettiğime

benim özel nesnenin özelliği başka bir özel nesne olduğunda ne olacak?
Shial

@Shial: diğer özel nesneleri arşivlenebilir yaparsanız, aynı zamanda kaydedilecektir
thomasdao

35

Hızlı 4

Bu tür görevler için tüm büyüyü yapan Codable protokolünü tanıttı . Özel yapınızı / sınıfınızı buna uymanız yeterlidir:

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

Varsayılanlar'da depolamak için PropertyListEncoder / Decoder'ı kullanabilirsiniz :

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)

Diziler ve bu tür nesnelerin diğer konteyner sınıfları için de böyle çalışacaktır:

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

1
Bir cazibe gibi çalışır
Nathan Barreto

CodableDavranışı yalnızca tüm örnek üyeleri kendileriyken ücretsiz olarak alacağınızı unutmayın Codable- aksi takdirde, kendiniz bazı kodlama kodları yazmanız gerekir,
BallpointBen

1
Ya da basitçe bu üye türlerine Codablede uymak . Ve böylece, özyineli olarak. Sadece çok özel durumlarda kodlama kodu yazmanız gerekir.
Sergiu Todirascu

Swift 4 ile en kolay yol
00'da Alstr

31

Herhangi biri hızlı bir sürüm arıyorsa:

1) Verileriniz için özel bir sınıf oluşturun

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) Verileri kaydetmek için aşağıdaki işlevi kullanın:

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

3) Almak için:

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

Not: Burada kaydedip özel sınıf nesnelerinin bir dizisini alıyorum.


9

@ Chrissr'ın cevabını alıp onunla çalışan bu kod, NSUserDefaultsözel nesneleri kaydetmek ve almak için güzel bir kategoriye uygulanabilir :

@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

Kullanımı:

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

7

Hızlı 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")
    }
    }

saklamak ve almak için:

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
        }

Sadece bir hatırlatma: ve selfiçindeki değişkenlerden önce zorunlu değildir . initencode
Tamás Sengel

NSCoder ile nesneyi nasıl değerlendirdiğinizi gösterebilir misiniz
Abhishek Thapliyal

@AbhishekThapliyal, seni anlamıyorum. init (kodlayıcı aDecoder: NSCoder) başlatıyor
Vyacheslav

6

Kaydettiğiniz verileri / nesneyi NSUserDefaults'a eşitleyin

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

Umarım bu size yardımcı olacaktır. Teşekkürler


Geleceğin Swift 3'ten itibaren "senkronize et" in sizin tarafınızdan çağrılmaması gerektiğini unutmayın.
Jose Ramirez

@JozemiteApps neden? veya bu konuyla ilgili açıklama içeren bir bağlantı gönderebilir misiniz?
code4latte

@ code4latte Değiştirilip değiştirilmediğini bilmiyorum, ancak Apple'ın belgeleri "Periyodik aralıklarla otomatik olarak çağrılan senkronize etme () yönteminin bellek içi önbelleği kullanıcının varsayılan veritabanıyla senkronize tuttuğunu" belirtiyor. Daha önce, sadece anında kaydedilen verilere ihtiyacınız olduğundan eminseniz onu çağırmanız gerektiği söylenmiştir. Birkaç kez kullanıcının artık aramaması gerektiğini okudum.
Jose Ramirez
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.