NSUserDefaults में कस्टम ऑब्जेक्ट कैसे संग्रहीत करें


268

ठीक है, इसलिए मैं चारों ओर कुछ प्रहार कर रहा हूं, और मुझे अपनी समस्या का एहसास है, लेकिन मुझे नहीं पता कि इसे कैसे ठीक किया जाए। मैंने कुछ डेटा रखने के लिए एक कस्टम क्लास बनाया है। मैं इस वर्ग के लिए ऑब्जेक्ट बनाता हूं, और मुझे सत्रों के बीच उन्हें पिछले करने की आवश्यकता है। इससे पहले कि मैं अपनी सारी जानकारी अंदर डाल रहा था NSUserDefaults, लेकिन यह काम नहीं कर रहा है।

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

यह वह त्रुटि संदेश है जो मुझे मेरे कस्टम क्लास, "प्लेयर" में डालते समय मिलता है NSUserDefaults। अब, मैंने पढ़ा है कि जाहिरा तौर पर NSUserDefaultsकेवल कुछ प्रकार की जानकारी संग्रहीत करता है। तो मैं अपनी वस्तुओं को कैसे प्राप्त करूं NSUSerDefaults?

मैंने पढ़ा कि मेरी कस्टम ऑब्जेक्ट को "एन्कोड" करने का एक तरीका होना चाहिए और फिर इसे डाल देना चाहिए, लेकिन मुझे यकीन नहीं है कि इसे कैसे लागू किया जाए, मदद की सराहना की जाएगी! धन्यवाद!

**** संपादित ****

ठीक है, इसलिए मैंने नीचे दिए गए कोड के साथ काम किया (धन्यवाद!), लेकिन मुझे अभी भी कुछ मुद्दे आ रहे हैं। असल में, कोड अब दुर्घटनाग्रस्त हो गया है और मुझे यकीन नहीं है कि क्यों, क्योंकि यह कोई त्रुटि नहीं देता है। शायद मुझे कुछ बुनियादी याद आ रही है और मैं बहुत थक गया हूं, लेकिन हम देखेंगे। यहाँ मेरा कस्टम वर्ग, "प्लेयर" का कार्यान्वयन है:

@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

कार्यान्वयन फ़ाइल:

#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

तो यह मेरी कक्षा है, बहुत सीधे आगे, मुझे पता है कि यह मेरी वस्तुओं को बनाने में काम करता है। तो यहाँ AppDelegate फ़ाइल के प्रासंगिक भाग हैं (जहाँ मैं एन्क्रिप्शन और डिक्रिप्ट फ़ंक्शन को कॉल करता हूँ):

@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

और फिर कार्यान्वयन फ़ाइल के महत्वपूर्ण हिस्से:

    #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, सभी कोड के बारे में क्षमा करें। बस मदद करने का प्रयास कर रहा हूं। असल में, ऐप लॉन्च हो जाएगा और फिर तुरंत दुर्घटनाग्रस्त हो जाएगा। मैंने इसे ऐप के एन्क्रिप्शन वाले हिस्से तक सीमित कर दिया है, यहीं यह दुर्घटनाग्रस्त हो जाता है, इसलिए मैं कुछ गलत कर रहा हूं लेकिन मुझे यकीन नहीं है कि क्या। मदद फिर से सराहना की जाएगी, धन्यवाद!

(मैंने अभी तक डिक्रिप्टिंग के लिए आस-पास नहीं देखा है, क्योंकि मैंने अभी तक काम करना एन्क्रिप्ट नहीं किया है।)


क्या आपके पास क्रैश के बारे में स्टैक ट्रेस या अधिक जानकारी है, जैसे कि कौन सी पंक्ति संख्या दुर्घटना का कारण बन रही है? मैं तुरंत कोड के साथ कुछ भी गलत नहीं देख रहा हूं, इसलिए एक शुरुआती बिंदु मददगार होगा।
क्रिस

ऊपर के उदाहरण में आपने स्वयं को संग्रहीत करने के लिए encodeObject का उपयोग किया है। जो एक int है। आपको इसके बजाय encodeInt का उपयोग करना चाहिए।
broot

जवाबों:


516

अपने प्लेयर वर्ग पर, निम्न दो विधियों को लागू करें (कॉल को अपने स्वयं के ऑब्जेक्ट के लिए प्रासंगिक कुछ के साथ एन्कोड करना है।):

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

से पढ़ना और लिखना 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;
}

कोड बेशर्मी से उधार: nsuserdefaults में बचत वर्ग


मेरे पोस्ट को मेरे परिवर्तनों को प्रतिबिंबित करने के लिए संपादित किया।
एथन मिक

@chrissr में आपको NSUserDefaults चूक में त्रुटि है [[NSUserDefaults standardUserDefaults]; ... NSUserDefaults * डिफ़ॉल्ट होना चाहिए।
मैगी

2
NSKeyedArchiver चट्टानें ... ऐसा लगता है कि यह स्वचालित रूप से NSArray या NSDictionary वस्तुओं में उतरता है और किसी भी प्रचलित कस्टम ऑब्जेक्ट्स को एनकोड करता है।
BadPirate

2
मैं पांडित्य नहीं कर रहा हूं, सिर्फ एक वास्तविक प्रश्न है, यह सिंथेसाइज्ड सेटलर्स यानी स्व.प्रतिष्ठित init के तरीकों का उपयोग करने के लिए दिशा निर्देशों के खिलाफ नहीं है?
समन सलाउद्दीन

2
@chrissr कृपया बदलने NSData *myEncodedObject = [NSKeyedArchiver archivedDataWithRootObject:object];के लिए NSData *encodedObject = [NSKeyedArchiver archivedDataWithRootObject:object];। चर मेल नहीं खाते।
GoRoS

36

मैं NSUserDefaults में कस्टम ऑब्जेक्ट को आसान और अधिक सुविधाजनक बनाने में मदद करने के लिए एक पुस्तकालय RMMapper ( https://github.com/roomorama/RMMapper ) बनाता हूं, क्योंकि एन्कोडोडिथकोडर और initWithCoder को लागू करना सुपर उबाऊ है!

एक वर्ग को अभिलेखीय के रूप में चिह्नित करने के लिए, बस उपयोग करें: #import "NSObject+RMArchivable.h"

NSUserDefaults में एक कस्टम ऑब्जेक्ट को बचाने के लिए:

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

NSUserDefaults से कस्टम obj प्राप्त करने के लिए:

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

आपकी लाइब्रेरी मानती है कि आपके पास हर चीज के लिए गुण हैं, जिसे आप जारी रखना चाहते हैं और जो आप के लिए गुण हैं, उसे जारी रखना चाहते हैं। यदि यह सच है, तो आपका पुस्तकालय निश्चित रूप से सहायक हो सकता है, लेकिन मुझे लगता है कि आप पाएंगे कि कई मामलों में यह नहीं है।
एरिक बी

यह केवल सादे वस्तु को बनाए रखने के लिए उपयोगी है, मैं कहूंगा कि इसका उपयोग जावा में Gson के समान है। आप किन मामलों को देख रहे हैं?
थोमसदाओ

7
यह वास्तव में मददगार था, खुशी है कि मैं जवाबों पर पढ़ता रहा; डी
अल्बरा

क्या होगा जब मेरे कस्टम ऑब्जेक्ट में प्रॉपर्टी के अन्य कस्टम ऑब्जेक्ट हैं?
शियाल

@ शायाल: यदि आप अन्य कस्टम ऑब्जेक्ट को संग्रहणीय बनाते हैं, तो इसे भी सहेजा जाएगा
thomasdao

35

स्विफ्ट 4

कोडेबल प्रोटोकॉल पेश किया जो इस प्रकार के कार्यों के लिए सभी जादू करता है। बस अपनी कस्टम संरचना / वर्ग को इसके अनुरूप बनाएं:

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

और Defaults में भंडारण के लिए आप PropertyListEncoder / Decoder का उपयोग कर सकते हैं :

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)

यह इस तरह की वस्तुओं के एरे और अन्य कंटेनर वर्गों के लिए भी काम करेगा:

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

1
एक आकर्षण की तरह काम करता है
नाथन बैरेटो

ध्यान दें कि आप केवल तभी Codableमुफ्त में व्यवहार कर सकते हैं जब सभी उदाहरण सदस्य स्वयं हों Codable- अन्यथा, आपको कुछ एन्कोडिंग कोड स्वयं लिखना होगा,
बॉलपॉइंटबैन

1
या बल्कि बस उन सदस्य प्रकारों के अनुरूप हैं Codable। और इतने पर, पुनरावर्ती। केवल बहुत ही कस्टम मामलों में आपको एन्कोडिंग कोड लिखना होगा।
सेर्गी टोडिरस्कु

स्विफ्ट 4. साथ सबसे आसान तरीका है
alstr

31

अगर किसी को एक तेज संस्करण की तलाश है:

1) अपने डेटा के लिए एक कस्टम क्लास बनाएं

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) फ़ंक्शन के बाद डेटा उपयोग को बचाने के लिए:

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

3) पुनः प्राप्त करने के लिए:

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

नोट: यहां मैं कस्टम क्लास ऑब्जेक्ट की एक सरणी को सहेज रहा हूं और पुनः प्राप्त कर रहा हूं।


9

@ Chrissr का उत्तर लेना और उसके साथ चलना, इस कोड को अच्छी NSUserDefaultsवस्तुओं को सहेजने और पुनः प्राप्त करने के लिए एक अच्छी श्रेणी में लागू किया जा सकता है :

@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

उपयोग:

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

7

स्विफ्ट 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")
    }
    }

स्टोर और पुनः प्राप्त करने के लिए:

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
        }

बस एक चेतावनी: selfमें चर से पहले अनिवार्य नहीं है initऔर encode
तमसेन सेंगेल

क्या आप दिखा सकते हैं कि आप NSCoder के साथ वस्तु को कैसे पहचान रहे हैं
अभिषेक थपलियाल

@ अभिषेक थपलियाल, मैं आपको नहीं समझती। init (कोडर aDecoder: NSCoder) इसे इनिशियलाइज़ करता है
व्याचेस्लाव

6

NSUserDefaults में आपके द्वारा सेव किए गए डेटा / ऑब्जेक्ट को सिंक्रनाइज़ करें

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

आशा है कि यह आपकी मदद करेगा। धन्यवाद


भविष्य के लोगों पर ध्यान दें कि स्विफ्ट 3 के रूप में, "सिंक्रनाइज़" आपको बिल्कुल भी नहीं बुलाया जाना चाहिए।
जोस रामिरेज़

@JozemiteApps क्यों? या आप इस विषय के लिए स्पष्टीकरण के साथ एक लिंक पोस्ट कर सकते हैं?
code4latte

@ code4latte मुझे नहीं पता कि इसे बदल दिया गया था, लेकिन Apple के प्रलेखन में कहा गया है कि "समकालिक () विधि, जो स्वचालित रूप से आवधिक अंतराल पर लागू होती है, इन-मेमोरी कैश को उपयोगकर्ता के डिफॉल्ट डेटाबेस के साथ सिंक में रखती है।" इससे पहले, यह कहा गया था कि आपको केवल इसे कॉल करना चाहिए यदि आप सुनिश्चित हैं कि आपको तुरंत सहेजे गए डेटा की आवश्यकता है। मैंने लगभग एक-दो बार पढ़ा है कि उपयोगकर्ता को अब इसे कॉल नहीं करना चाहिए।
जोस रामिरेज़
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.