#ios #objective-c #nsuserdefaults #nsmutabledictionary #nskeyedarchiver
#iOS #objective-c #iPhone #кодирование #nsuserdefaults
Вопрос:
Хорошо, итак, я немного покопался, и я понимаю свою проблему, но я не знаю, как это исправить. Я создал пользовательский класс для хранения некоторых данных. Я создаю объекты для этого класса, и мне нужно, чтобы они сохранялись между сеансами. Раньше я вводил всю свою информацию NSUserDefaults
, но это не работает.
-[NSUserDefaults setObject:forKey:]: Attempt to insert non-property value '<Player: 0x3b0cc90>' of class 'Player'.
Это сообщение об ошибке, которое я получаю, когда помещаю свой пользовательский класс «Player» в NSUserDefaults
. Теперь я прочитал, что, по-видимому NSUserDefaults
, хранятся только некоторые типы информации. Итак, как я получаю свои объекты NSUSerDefaults
?
Я читал, что должен быть способ «закодировать» мой пользовательский объект, а затем вставить его, но я не уверен, как это реализовать, помощь будет оценена! Спасибо!
****РЕДАКТИРОВАТЬ****
Хорошо, я поработал с приведенным ниже кодом (спасибо!), Но у меня все еще возникают некоторые проблемы. По сути, код вылетает сейчас, и я не уверен, почему, потому что он не выдает никаких ошибок. Возможно, я упускаю что-то основное, и я просто слишком устал, но посмотрим. Вот реализация моего пользовательского класса «Player»:
@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%dn%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;
}
Ээээ, извините за весь код. Просто пытаюсь помочь. По сути, приложение запустится, а затем немедленно завершится сбоем. Я сузил его до части шифрования приложения, вот где он выходит из строя, поэтому я делаю что-то не так, но я не уверен, что именно. Помощь была бы признательна еще раз, спасибо!
(Я еще не приступил к расшифровке, так как у меня еще не заработало шифрование.)
Комментарии:
1. Есть ли у вас трассировка стека или дополнительная информация о сбое, например, какой номер строки вызывает сбой? Я не сразу вижу ничего плохого в коде, поэтому было бы полезно использовать отправную точку.
2. В приведенном выше примере вы использовали encodeObject для хранения self. жизнь, которая является int. Вместо этого вы должны использовать encodeInt .
Ответ №1:
В вашем классе Player реализуйте следующие два метода (заменяя вызовы encodeObject чем-то, относящимся к вашему собственному объекту):
- (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
Комментарии:
1. Отредактировал мой пост выше, чтобы отразить мои изменения.
2. @chrissr у вас ошибка в NSUserDefaults defaults defaults = [NSUserDefaults standardUserDefaults]; … должно быть NSUserDefaults *defaults.
3. NSKeyedArchiver качается… кажется, он даже автоматически переходит в объекты NSArray или NSDictionary и кодирует любые кодируемые пользовательские объекты внутри.
4. Я не педантичен, просто искренний вопрос, разве это не противоречит рекомендациям Apple по использованию синтезированных установщиков, т.е. self.property в методах инициализации?
5. @chrissr Пожалуйста, измените
NSData *myEncodedObject = [NSKeyedArchiver archivedDataWithRootObject:object];
наNSData *encodedObject = [NSKeyedArchiver archivedDataWithRootObject:object];
. Переменные не совпадают.
Ответ №2:
Swift 4 представила протокол Codable, который выполняет все волшебные действия для таких задач. Просто согласуйте с ним свою пользовательскую структуру / класс:
struct Player: Codable {
let name: String
let life: Double
}
А для хранения в настройках по умолчанию вы можете использовать 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. Работает как шарм
2. Обратите внимание, что вы получаете
Codable
поведение бесплатно только тогда, когда все члены экземпляра являются самимиCodable
собой — в противном случае вам придется самостоятельно написать некоторый код кодирования,3. Или, скорее, просто соответствовать этим типам членов
Codable
. И так далее, рекурсивно. Только в очень нестандартных случаях вам нужно будет написать код кодирования.4. Самый простой способ с Swift 4.
Ответ №3:
Я создаю библиотеку RMMapper (https://github.com/roomorama/RMMapper ), чтобы помочь сохранить пользовательский объект в NSUserDefaults проще и удобнее, потому что реализация encodeWithCoder и initWithCoder очень скучна!
Чтобы пометить класс как архивируемый, просто используйте: #import "NSObject RMArchivable.h"
Как сохранить пользовательский объект в NSUserDefaults:
#import "NSUserDefaults RMSaveCustomObject.h"
NSUserDefaults* defaults = [NSUserDefaults standardUserDefaults];
[defaults rm_setCustomObject:user forKey:@"SAVED_DATA"];
Чтобы получить пользовательский объект из NSUserDefaults:
user = [defaults rm_customObjectForKey:@"SAVED_DATA"];
Комментарии:
1. Ваша библиотека предполагает, что у вас есть свойства для всего, что вы хотите сохранить, и что вы хотите сохранить все, для чего у вас есть свойства. Если это так, ваша библиотека, безусловно, может быть полезной, но я думаю, вы обнаружите, что во многих случаях это не так.
2. Полезно сохранять только простой объект, я бы сказал, что его использование очень похоже на Gson в Java. На какие случаи вы смотрите?
3. Это было действительно полезно, рад, что я продолжал читать ответы; D
4. как насчет того, когда мой пользовательский объект имеет в свойстве другой пользовательский объект?
5. @Shial: если вы сделаете другой пользовательский объект архивируемым, он тоже будет сохранен
Ответ №4:
Если кто-нибудь ищет быструю версию:
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]
}
Примечание: Здесь я сохраняю и извлекаю массив объектов пользовательского класса.
Ответ №5:
Принимая ответ @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"];
Ответ №6:
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")
}
}
для сохранения и извлечения:
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
}
Комментарии:
1. Просто напоминание:
self
не является обязательным перед переменными вinit
иencode
.2. Можете ли вы показать, как вы инициализируете объект с помощью NSCoder
3. @AbhishekThapliyal, я тебя не понимаю. инициализация (кодировщик aDecoder: NSCoder) инициализирует его
Ответ №7:
Синхронизируйте данные / объект, которые вы сохранили в NSUserDefaults
-(void)saveCustomObject:(Player *)object
{
NSUserDefaults *prefs = [NSUserDefaults standardUserDefaults];
NSData *myEncodedObject = [NSKeyedArchiver archivedDataWithRootObject:object];
[prefs setObject:myEncodedObject forKey:@"testing"];
[prefs synchronize];
}
Надеюсь, это вам поможет. Спасибо
Комментарии:
1. Обратите внимание, люди из будущего, что начиная с Swift 3, «синхронизация» не должна вызываться вами вообще.
2. @JozemiteApps почему? или вы можете опубликовать ссылку с объяснением по этой теме?
3. @code4latte Я не знаю, было ли это изменено, но в документации Apple указано, что «Метод synchronize(), который автоматически вызывается с периодическими интервалами, синхронизирует кэш в памяти с базой данных пользователя по умолчанию». Раньше говорилось, что вы должны вызывать его только в том случае, если вы уверены, что вам нужны данные, сохраненные мгновенно. Я пару раз читал, что пользователь больше не должен его вызывать.