การจัดเก็บอ็อบเจ็กต์แบบกำหนดเองใน NSMutableArray ใน NSUserDefaults


101

เมื่อเร็ว ๆ นี้ฉันพยายามจัดเก็บผลการค้นหาของแอพ iPhone ของฉันในคอลเลกชัน NSUserDefaults ฉันยังใช้สิ่งนี้เพื่อบันทึกข้อมูลการลงทะเบียนผู้ใช้สำเร็จ แต่ด้วยเหตุผลบางประการที่พยายามจัดเก็บ NSMutableArray ของคลาส Location ที่กำหนดเองจะกลับมาว่างเปล่าเสมอ

ฉันพยายามแปลง NSMutableArray เป็นองค์ประกอบ NSData ในโพสต์นี้ แต่ได้ผลลัพธ์เหมือนกัน ( สามารถบันทึกอาร์เรย์จำนวนเต็มโดยใช้ NSUserDefaults บน iPhone ได้หรือไม่ )

ตัวอย่างโค้ดที่ฉันได้ลองคือ:

บันทึก:

[prefs setObject:results forKey:@"lastResults"];
[prefs synchronize];

หรือ

NSData *data = [NSData dataWithBytes:&results length:sizeof(results)];
[prefs setObject:data forKey:@"lastResults"];

หรือ

NSData *data = [NSKeyedArchiver archivedDataWithRootObject:results];
[prefs setObject:data forKey:@"lastResults"];

โหลด:

lastResults = (NSMutableArray *)[prefs objectForKey:@"lastResults"];

หรือ

NSData *data = [prefs objectForKey:@"lastResults"];
memcpy(&lastResults, data.bytes, data.length);  

หรือ

NSData *data = [prefs objectForKey:@"lastResults"];
lastResults = [NSKeyedUnarchiver unarchiveObjectWithData:data];

หลังจากทำตามคำแนะนำด้านล่างฉันได้ติดตั้ง NSCoder ในออบเจ็กต์ของฉันแล้ว (ละเว้นการใช้ NSString มากเกินไป):

#import "Location.h"


@implementation Location

@synthesize locationId;
@synthesize companyName;
@synthesize addressLine1;
@synthesize addressLine2;
@synthesize city;
@synthesize postcode;
@synthesize telephoneNumber;
@synthesize description;
@synthesize rating;
@synthesize priceGuide;
@synthesize latitude;
@synthesize longitude;
@synthesize userLatitude;
@synthesize userLongitude;
@synthesize searchType;
@synthesize searchId;
@synthesize distance;
@synthesize applicationProviderId;
@synthesize contentProviderId;

- (id) initWithCoder: (NSCoder *)coder
{
    if (self = [super init])
    {
        self.locationId = [coder decodeObjectForKey:@"locationId"];
        self.companyName = [coder decodeObjectForKey:@"companyName"];
        self.addressLine1 = [coder decodeObjectForKey:@"addressLine1"];
        self.addressLine2 = [coder decodeObjectForKey:@"addressLine2"];
        self.city = [coder decodeObjectForKey:@"city"];
        self.postcode = [coder decodeObjectForKey:@"postcode"];
        self.telephoneNumber = [coder decodeObjectForKey:@"telephoneNumber"];
        self.description = [coder decodeObjectForKey:@"description"];
        self.rating = [coder decodeObjectForKey:@"rating"];
        self.priceGuide = [coder decodeObjectForKey:@"priceGuide"];
        self.latitude = [coder decodeObjectForKey:@"latitude"];
        self.longitude = [coder decodeObjectForKey:@"longitude"];
        self.userLatitude = [coder decodeObjectForKey:@"userLatitude"];
        self.userLongitude = [coder decodeObjectForKey:@"userLongitude"];
        self.searchType = [coder decodeObjectForKey:@"searchType"];
        self.searchId = [coder decodeObjectForKey:@"searchId"];
        self.distance = [coder decodeObjectForKey:@"distance"];
        self.applicationProviderId = [coder decodeObjectForKey:@"applicationProviderId"];
        self.contentProviderId = [coder decodeObjectForKey:@"contentProviderId"];
    }
}

- (void) encodeWithCoder: (NSCoder *)coder
{
    [coder encodeObject:locationId forKey:@"locationId"];
    [coder encodeObject:companyName forKey:@"companyName"];
    [coder encodeObject:addressLine1 forKey:@"addressLine1"];
    [coder encodeObject:addressLine2 forKey:@"addressLine2"];
    [coder encodeObject:city forKey:@"city"];
    [coder encodeObject:postcode forKey:@"postcode"];
    [coder encodeObject:telephoneNumber forKey:@"telephoneNumber"];
    [coder encodeObject:description forKey:@"description"];
    [coder encodeObject:rating forKey:@"rating"];
    [coder encodeObject:priceGuide forKey:@"priceGuide"];
    [coder encodeObject:latitude forKey:@"latitude"];
    [coder encodeObject:longitude forKey:@"longitude"];
    [coder encodeObject:userLatitude forKey:@"userLatitude"];
    [coder encodeObject:userLongitude forKey:@"userLongitude"];
    [coder encodeObject:searchType forKey:@"searchType"];
    [coder encodeObject:searchId forKey:@"searchId"];
    [coder encodeObject:distance forKey:@"distance"];
    [coder encodeObject:applicationProviderId forKey:@"applicationProviderId"];
    [coder encodeObject:contentProviderId forKey:@"contentProviderId"];

}

คำตอบ:


177

สำหรับการโหลดออบเจ็กต์ที่กำหนดเองในอาร์เรย์นี่คือสิ่งที่ฉันเคยใช้เพื่อคว้าอาร์เรย์

NSUserDefaults *currentDefaults = [NSUserDefaults standardUserDefaults];
NSData *dataRepresentingSavedArray = [currentDefaults objectForKey:@"savedArray"];
if (dataRepresentingSavedArray != nil)
{
    NSArray *oldSavedArray = [NSKeyedUnarchiver unarchiveObjectWithData:dataRepresentingSavedArray];
    if (oldSavedArray != nil)
        objectArray = [[NSMutableArray alloc] initWithArray:oldSavedArray];
    else
        objectArray = [[NSMutableArray alloc] init];
}

คุณควรตรวจสอบว่าข้อมูลที่ส่งคืนจากค่าเริ่มต้นของผู้ใช้ไม่ใช่ศูนย์เพราะฉันเชื่อว่าการยกเลิกการเก็บจากศูนย์ทำให้เกิดข้อขัดข้อง

การจัดเก็บทำได้ง่ายโดยใช้รหัสต่อไปนี้:

[[NSUserDefaults standardUserDefaults] setObject:[NSKeyedArchiver archivedDataWithRootObject:objectArray] forKey:@"savedArray"];

ดังที่ f3lix ชี้ให้เห็นคุณต้องทำให้ออบเจ็กต์ที่กำหนดเองของคุณสอดคล้องกับโปรโตคอล NSCoding การเพิ่มวิธีการดังต่อไปนี้ควรทำเคล็ดลับ:

- (void)encodeWithCoder:(NSCoder *)coder;
{
    [coder encodeObject:label forKey:@"label"];
    [coder encodeInteger:numberID forKey:@"numberID"];
}

- (id)initWithCoder:(NSCoder *)coder;
{
    self = [super init];
    if (self != nil)
    {
        label = [[coder decodeObjectForKey:@"label"] retain];
        numberID = [[coder decodeIntegerForKey:@"numberID"] retain];
    }   
    return self;
}

3
คุณไม่มี "ส่งคืนตนเอง" ในตอนท้ายของวิธีการ initWithCoder: การเพิ่มที่ดูเหมือนว่าจะอนุญาตให้ยกเลิกการเก็บถาวรได้
Brad Larson

2
คุณไม่ควรจัดเก็บอาร์เรย์ไว้ใน nsuserdefaults เนื่องจากอาร์เรย์อาจใหญ่เกินไปสำหรับ nsuserdefaults ควรเก็บไว้ในไฟล์แทนโดยใช้ + (BOOL) archiveRootObject: (id) rootObject toFile: (NSString *) path ทำไมคำตอบนี้ถึงได้รับการโหวตมาก?
mskw

1
@mskw - คุณถูกต้องใน NSUserDefaults ที่ไม่ควรใช้ในการจัดเก็บอาร์เรย์ขนาดใหญ่ (ควรใช้ข้อมูลหลักหรือ SQLite แบบดิบแทน) แต่เป็นการดีที่จะเก็บอาร์เรย์ขนาดเล็กของวัตถุที่เข้ารหัสไว้ที่นั่น ไม่ว่าในกรณีใดคำถามคือถามว่าจะทำอย่างไรซึ่งเป็นสิ่งที่รหัสข้างต้นให้ไว้ เท่าที่โหวตการคาดเดาของคุณดีพอ ๆ กับฉัน หลายคนต้องอยากทำสิ่งนี้ในช่วงสี่ปีที่ผ่านมา
Brad Larson

2
@ Goodsum - ไม่ทำงานได้ดี ลองใช้มันจะส่งคืน NSMutableArray ที่ไม่แน่นอน มันเป็นเพียงการเริ่มอาร์เรย์ใหม่โดยการเพิ่มอาร์เรย์ของรายการนั้นเข้าไป
Brad Larson

1
@AlbertRenshaw - andSyncคุณเพิ่มโค้ดด้านบนมาจากไหน? นั่นไม่ใช่ลายเซ็นวิธีการจริงสำหรับ NSUserDefaults นอกจากนี้ยังsynchronizeไม่มีประโยชน์อย่างที่เคยเป็น: twitter.com/Catfish_Man/status/647274106056904704
Brad Larson

13

ฉันคิดว่าคุณได้รับข้อผิดพลาดในเมธอด initWithCoder ของคุณอย่างน้อยในโค้ดที่ให้มาคุณจะไม่ส่งคืนอ็อบเจ็กต์ 'self'

- (id) initWithCoder: (NSCoder *)coder
{
    if (self = [super init])
    {
        self.locationId = [coder decodeObjectForKey:@"locationId"];
        self.companyName = [coder decodeObjectForKey:@"companyName"];
        self.addressLine1 = [coder decodeObjectForKey:@"addressLine1"];
        self.addressLine2 = [coder decodeObjectForKey:@"addressLine2"];
        self.city = [coder decodeObjectForKey:@"city"];
        self.postcode = [coder decodeObjectForKey:@"postcode"];
        self.telephoneNumber = [coder decodeObjectForKey:@"telephoneNumber"];
        self.description = [coder decodeObjectForKey:@"description"];
        self.rating = [coder decodeObjectForKey:@"rating"];
        self.priceGuide = [coder decodeObjectForKey:@"priceGuide"];
        self.latitude = [coder decodeObjectForKey:@"latitude"];
        self.longitude = [coder decodeObjectForKey:@"longitude"];
        self.userLatitude = [coder decodeObjectForKey:@"userLatitude"];
        self.userLongitude = [coder decodeObjectForKey:@"userLongitude"];
        self.searchType = [coder decodeObjectForKey:@"searchType"];
        self.searchId = [coder decodeObjectForKey:@"searchId"];
        self.distance = [coder decodeObjectForKey:@"distance"];
        self.applicationProviderId = [coder decodeObjectForKey:@"applicationProviderId"];
        self.contentProviderId = [coder decodeObjectForKey:@"contentProviderId"];
    }

    return self; // this is missing in the example above


}

ฉันใช้

NSData *data = [NSKeyedArchiver archivedDataWithRootObject:results];
[prefs setObject:data forKey:@"lastResults"];

และ

NSUserDefaults *currentDefaults = [NSUserDefaults standardUserDefaults];
NSData *dataRepresentingSavedArray = [currentDefaults objectForKey:@"lastResults"];
if (dataRepresentingSavedArray != nil)
{
        NSArray *oldSavedArray = [NSKeyedUnarchiver unarchiveObjectWithData:dataRepresentingSavedArray];
        if (oldSavedArray != nil)
                objectArray = [[NSMutableArray alloc] initWithArray:oldSavedArray];
        else
                objectArray = [[NSMutableArray alloc] init];
}

และทำงานได้อย่างสมบูรณ์แบบสำหรับฉัน

ด้วยความนับถือ,

สเตฟาน


3
สเตฟานคุณเป็นผู้ช่วยชีวิตดูที่นี่ บรรทัดนั้นช่วยชีวิตฉัน "if (self = [super init])" stackoverflow.com/questions/1933285/…
fyasar

ฉันได้รับ ERROR_BAD_ACCESS แต่คุณช่วยฉันไว้ กำลังใช้คุณสมบัติกับการเก็บรักษาเท่านั้นและเพิ่ม self.variable แก้ไขได้
เดวิด

0

ฉันคิดว่าดูเหมือนว่าปัญหาเกี่ยวกับรหัสของคุณไม่ได้บันทึกอาร์เรย์ผลลัพธ์ กำลังโหลดข้อมูลลองใช้

lastResults = [prefs arrayForKey:@"lastResults"];

สิ่งนี้จะส่งคืนอาร์เรย์ที่ระบุโดยคีย์


0

ดู "เหตุใด NSUserDefaults จึงไม่สามารถบันทึก NSMutableDictionary ใน iPhone SDK" ( เหตุใด NSUserDefaults จึงล้มเหลวในการบันทึก NSMutableDictionary ใน iPhone SDK )


หากคุณต้องการ (de) ทำให้เป็นอนุกรมออบเจ็กต์ที่กำหนดเองคุณต้องจัดเตรียมฟังก์ชันเพื่อ (de) ทำให้ข้อมูลเป็นอนุกรม (โปรโตคอล NSCoding) โซลูชันที่คุณอ้างถึงใช้ได้กับ int array เนื่องจากอาร์เรย์ไม่ใช่วัตถุ แต่เป็นหน่วยความจำที่ต่อเนื่องกัน


ฉันคิดว่าสิทธิ์ของคุณฉันจะดู NSCoding (เว้นแต่คุณจะมี linky ที่แนะนำ) อ็อบเจ็กต์นั้นประกอบขึ้นด้วย NSStrings นั่นคือทั้งหมดดังนั้นฉันคิดว่าฉันสามารถเขียน serialiser ของตัวเองและใส่ทั้งหมดลงในอาร์เรย์สตริง
Anthony Main

โอเคฉันได้ติดตั้ง NSCode แล้ว แต่ยังไม่มีความสุขกับการใช้ NSArchiver
Anthony Main

0

ฉันขอแนะนำไม่ให้พยายามจัดเก็บสิ่งของเช่นนี้ในค่าเริ่มต้น db

SQLite นั้นค่อนข้างง่ายในการหยิบและใช้งาน ฉันมีตอนหนึ่งใน screencast ของฉัน ( http://pragprog.com/screencasts/v-bdiphone ) เกี่ยวกับเสื้อคลุมแบบธรรมดาที่ฉันเขียน (คุณสามารถรับรหัสได้โดยไม่ต้องซื้อ SC)

การจัดเก็บข้อมูลแอปในพื้นที่แอปจะสะอาดกว่ามาก

ทั้งหมดที่กล่าวว่าหากยังคงเหมาะสมที่จะใส่ข้อมูลนี้ลงในค่าเริ่มต้น db ให้ดูโพสต์ f3lix ที่โพสต์


1
ขอบคุณสำหรับคำแนะนำ แต่ฉันไม่ต้องการที่จะต้องเริ่มเขียนแบบสอบถาม SQL (ตามที่ฉันเห็นในตัวอย่างของคุณ) สำหรับวัตถุที่มีสตริงไม่กี่สาย
Anthony Main
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.