2017-05-22 1 views
0

J'ai essayé de stocker et de récupérer à partir du NSUserDefaults un tableau d'objets personnalisés dans mon projet, mais il semble que quelque chose ne va pas. J'ai beaucoup étudié ce problème mais je n'ai pas trouvé de réponse à mon problème.Problèmes de stockage et de chargement d'objets personnalisés dans NSUserDefaults Objectif-C

Il semble que le stockage est fait OK parce que je ne reçois aucune erreur lors de l'enregistrement des données dans NSUserDefaults, le problème vient quand j'essaie de récupérer ces données: l'application se bloque complètement avec libc++abi.dylib: terminating with uncaught exception of type NSException erreur.

Voici mon code:

Wine.h

#import <Foundation/Foundation.h> 
@import UIKit; 

#define NO_RATING -1 

     @interface WineModel : NSObject <NSCoding> 
     @property(copy, nonatomic) NSString *type; 
     @property(strong, nonatomic) UIImage *photo; 
     @property(strong, nonatomic) NSURL *photoURL; 
     @property(strong, nonatomic) NSURL *wineCompanyWeb; 
     @property(copy, nonatomic) NSString *notes; 
     @property(copy, nonatomic) NSString *origin; 
     @property(nonatomic) int rating; 
     @property(strong, nonatomic) NSArray *grapes; 
     @property(copy, nonatomic) NSString *name; 
     @property(copy, nonatomic) NSString *wineCompanyName; 

     - (id) initWithCoder: (NSCoder *) decoder; 
     - (void) encodeWithCoder: (NSCoder *) encoder; 

//SOME OTHER METHODS...// 

-(id) initWithName: (NSString *) aName 
    wineCompanyName: (NSString *) aWineCompanyName 
       type: (NSString *) aType 
      origin: (NSString *) anOrigin 
      grapes: (NSArray *) arrayOfGrapes 
    wineCompanyWeb: (NSURL *) aURL 
      notes: (NSString *) aNotes 
      rating: (int) aRating 
      photoURL: (NSURL *) aPhotoURL; 

//For JSON 
-(id) initWithDictionary: (NSDictionary *) aDict; 

@end 

Wine.m

#import "WineModel.h" 
@implementation WineModel 

@synthesize photo = _photo; 

#pragma mark - Properties 
-(UIImage *) photo { 
    //SOME MORE CODE... 
    return _photo; 

} 

- (id) initWithCoder:(NSCoder *)decoder { 
    if (self = [super init]) { 
     self.name = [decoder decodeObjectForKey:@"name"]; 
     self.wineCompanyName = [decoder decodeObjectForKey:@"company"]; 
     self.type = [decoder decodeObjectForKey:@"type"]; 
     self.origin = [decoder decodeObjectForKey:@"origin"]; 
     self.grapes = [self extractGrapesFromJSONArray:[decoder decodeObjectForKey:@"grapes"]]; 
     self.wineCompanyWeb = [NSURL URLWithString:[decoder decodeObjectForKey:@"wine_web"]]; 
     self.notes = [decoder decodeObjectForKey:@"notes"]; 
     self.rating = [[decoder decodeObjectForKey:@"rating"] intValue]; 
     self.photoURL = [NSURL URLWithString:[decoder decodeObjectForKey:@"picture"]]; 

    } 
    return self; 
} 

- (void) encodeWithCoder:(NSCoder *)encoder { 
    [encoder encodeObject:self.name forKey:@"name"]; 
    [encoder encodeObject:self.wineCompanyWeb forKey:@"company"]; 
    [encoder encodeObject:self.type forKey:@"type"]; 
    [encoder encodeObject:self.origin forKey:@"origin"]; 
    [encoder encodeObject:self.grapes forKey:@"grapes"]; 
    [encoder encodeObject:self.wineCompanyWeb forKey:@"wine_web"]; 
    [encoder encodeObject:self.notes forKey:@"notes"]; 
    [encoder encodeInt:self.rating forKey:@"rating"]; 
    [encoder encodeObject:self.photoURL forKey:@"picture"]; 
} 

#pragma mark - Init 

-(id) initWithName: (NSString *) aName 
    wineCompanyName: (NSString *) aWineCompanyName 
       type: (NSString *) aType 
      origin: (NSString *) anOrigin 
      grapes: (NSArray *) arrayOfGrapes 
    wineCompanyWeb: (NSURL *) aURL 
      notes: (NSString *) aNotes 
      rating: (int) aRating 
      photoURL: (NSURL *) aPhotoURL { 

    if(self==[super init]) { 
     _name = aName; 
     _wineCompanyName = aWineCompanyName; 
     _type = aType; 
     _origin = anOrigin; 
     _grapes = arrayOfGrapes; 
     _wineCompanyWeb = aURL; 
     _notes = aNotes; 
     _rating = aRating; 
     _photoURL = aPhotoURL; 
    } 

    return self; 
} 

#pragma mark - JSON 

-(id) initWithDictionary:(NSDictionary *)aDict { 
    return [self initWithName:[aDict objectForKey:@"name"] 
       wineCompanyName:[aDict objectForKey:@"company"] 
         type:[aDict objectForKey:@"type"] 
         origin:[aDict objectForKey:@"origin"] 
         grapes:[self extractGrapesFromJSONArray:[aDict objectForKey:@"grapes"]] 
       wineCompanyWeb:[NSURL URLWithString:[aDict objectForKey:@"wine_web"]] 
         notes:[aDict objectForKey:@"notes"] 
         rating:[[aDict objectForKey:@"rating"]intValue] 
        photoURL:[NSURL URLWithString:[aDict objectForKey:@"picture"]] 
      ]; 
} 

-(NSArray *) extractGrapesFromJSONArray: (NSArray *)JSONArray { 

    //SOME MORE CODE... 

    return grapes; 
} 

@end 

C'est la classe de vin. Il a le protocole <NSCoding> et les deux méthodes (id) initWithCoder: (NSCoder *) decoder; et (void) encodeWithCoder: (NSCoder *) encoder;. Jusqu'à présent, je regarde OK, permet de passer à la classe suivante:

Winery.h

#import <Foundation/Foundation.h> 
#import "Wine.h" 

#define RED_WINE_KEY @"Red" 
#define WHITE_WINE_KEY @"White" 
#define OTHER_WINE_KEY @"Others" 

@interface WineryModel : NSObject 

@property (strong, nonatomic) NSMutableArray *redWines; 
@property (strong, nonatomic) NSMutableArray *whiteWines; 
@property (strong, nonatomic) NSMutableArray *otherWines; 

@property(readonly, nonatomic) int redWineCount; 
@property(readonly, nonatomic) int whiteWineCount; 
@property(readonly, nonatomic) int otherWineCount; 


-(WineModel *) redWineAtIndex: (NSUInteger) index; 
-(WineModel *) whiteWineAtIndex: (NSUInteger) index; 
-(WineModel *) otherWineAtIndex: (NSUInteger) index; 

@end 

Winery.m

#import "Winery.h" 
@implementation WineryModel 

#pragma mark - Properties 

-(int) redWineCount { 
    return [self.redWines count]; 
} 

-(int) whiteWineCount { 
    return [self.whiteWines count]; 
} 

-(int) otherWineCount { 
    return [self.otherWines count]; 
} 

-(id) init { 
    if(self == [super init]) { 

     NSUserDefaults *userDefault=[NSUserDefaults standardUserDefaults]; 
//Check if there is data stored locally 
     if(([[[userDefault dictionaryRepresentation] allKeys] containsObject:@"redWines"]) 
      &&([[[userDefault dictionaryRepresentation] allKeys] containsObject:@"whiteWines"]) 
      &&([[[userDefault dictionaryRepresentation] allKeys] containsObject:@"otherWines"])) { 

      if([userDefault objectForKey:@"redWines"] != nil && [userDefault objectForKey:@"whiteWines"] != nil && [userDefault objectForKey:@"otherWines"] != nil) { 
       //Try to load data from NSUserDefaults 
       NSData *decodedRedWines = [userDefault objectForKey:@"redWines"]; 
       self.redWines = [[NSKeyedUnarchiver unarchiveObjectWithData: decodedRedWines] mutableCopy]; //IT WILL CRASH HERE 
       NSData *decodedWhiteWines = [userDefault objectForKey:@"whiteWines"]; 
       self.whiteWines = [[NSKeyedUnarchiver unarchiveObjectWithData: decodedWhiteWines] mutableCopy]; 
       NSData *decodedOtherWines = [userDefault objectForKey:@"otherWines"]; 
       self.otherWines = [[NSKeyedUnarchiver unarchiveObjectWithData: decodedOtherWines] mutableCopy]; 

      } 

     } else { 

      NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:@"http://url.com/wines.json"]]; //JSON URL 

      NSURLResponse *response = [[NSURLResponse alloc]init]; 
      NSError *error; 
      NSData *data = [NSURLConnection sendSynchronousRequest:request 
               returningResponse:&response 
                  error:&error]; 
      if(data != nil) { //No errors 

       //Passing from JSON to an NSArray 
       NSArray * JSONObjects = [NSJSONSerialization JSONObjectWithData:data 
                     options:kNilOptions 
                      error:&error]; 
       if (JSONObjects != nil) { 
        //No errors 
        for(NSDictionary *dict in JSONObjects){ 

         WineModel *wine = [[WineModel alloc] initWithDictionary:dict]; 
         if(wine.name != nil && wine.wineCompanyName != nil && wine.type != nil && wine.origin != nil) { 
          if ([wine.type isEqualToString:RED_WINE_KEY]) { 
           if (!self.redWines) { 
            self.redWines = [NSMutableArray arrayWithObject:wine]; 
           } 
           else { 
            [self.redWines addObject:wine]; 
           } 
          } 
          else if ([wine.type isEqualToString:WHITE_WINE_KEY]) { 
           if (!self.whiteWines) { 
            self.whiteWines = [NSMutableArray arrayWithObject:wine]; 
           } 
           else { 
            [self.whiteWines addObject:wine]; 
           } 
          } 
          else { 
           if (!self.otherWines) { 
            self.otherWines = [NSMutableArray arrayWithObject:wine]; 
           } 
           else { 
            [self.otherWines addObject:wine]; 
           } 
          } 
         } 
        } 
       } else { 
        NSLog(@"JSON parsing error: %@", error.localizedDescription); 
       } 
      } else { 
       NSLog(@"Server error: %@", error.localizedDescription); 
      } 
      //Storing the array of wine objects in the NSUserDefaults 
      NSData *encodedRedWines = [NSKeyedArchiver archivedDataWithRootObject:_redWines]; 
      [userDefault setObject:encodedRedWines forKey:@"redWines"]; 
      NSData *encodedWhiteWines = [NSKeyedArchiver archivedDataWithRootObject:_whiteWines]; 
      [userDefault setObject:encodedWhiteWines forKey:@"whiteWines"]; 
      NSData *encodedOtherWines = [NSKeyedArchiver archivedDataWithRootObject:_otherWines]; 
      [userDefault setObject:encodedOtherWines forKey:@"otherWines"]; 
     } 
    } 
    return self; 
} 

-(WineModel *) redWineAtIndex: (NSUInteger) index { 
    return [self.redWines objectAtIndex:index]; 
} 


-(WineModel *) whiteWineAtIndex: (NSUInteger) index{ 
    return [self.whiteWines objectAtIndex:index]; 
} 


-(WineModel *) otherWineAtIndex: (NSUInteger) index{ 
    return [self.otherWines objectAtIndex:index]; 
} 

@end 

Ainsi, la première fois que vous lancez l'application, il va télécharger les données à partir d'un Fichier JSON qui se trouve dans le site Web, puis stocker l'information dans le NSUserDefaults. Il semble que cette étape soit faite correctement (du moins, elle ne plante pas à ce stade). Le problème vient après le lancement de l'application la deuxième fois. Il vérifiera s'il existe un magasin de données local sous NSUserDefault, et si c'est le cas, il essaiera de charger les données et de les stocker dans un NSMutableAtray. Malheureusement, il ne le fera pas, Il se bloque ici self.redWines =[NSKeyedUnarchiver unarchiveObjectWithData: decodedRedWines]; avec le code d'erreur que j'ai écrit avant. Lors du débogage, je peux voir qu'il y a des données lors de la récupération de la clé redWines, mais il semble que quelque chose ne va pas. Notez que j'utilise un initialiseur personnalisé (initWithDictionary) pour créer mon objet vins au lieu de la méthode par défaut init. Je ne sais pas si ce pourrait être la raison de l'accident ...

Voici le journal complet:

2017-05-22 20:31:30.354640+0200 App[1905:891526] -[NSTaggedPointerString objectForKey:]: unrecognized selector sent to instance 0xa5c064950b08843b 
2017-05-22 20:31:30.354932+0200 App[1905:891526] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[NSTaggedPointerString objectForKey:]: unrecognized selector sent to instance 0xa5c064950b08843b' 
*** First throw call stack: 
(0x18e0bafe0 0x18cb1c538 0x18e0c1ef4 0x18e0bef54 0x18dfbad4c 0x1000591d8 0x100057dec 0x18eb0a430 0x18eb10f10 0x18eaa684c 0x18eb0a430 0x18eb09b68 0x18eb08d94 0x100061118 0x1000621d0 0x10005c120 0x19425d204 0x194469738 0x19446f1e0 0x194483d18 0x19446c474 0x18fc63884 0x18fc636f0 0x18fc63aa0 0x18e06942c 0x18e068d9c 0x18e0669a8 0x18df96da4 0x194256384 0x194251058 0x100060b90 0x18cfa559c) 
libc++abi.dylib: terminating with uncaught exception of type NSException 

Toutes les idées ??

Merci d'avance !!

+0

Avez-vous essayé de placer un point d'arrêt sur 'objc_exception_throw' dans libobjc.A.dylib ou' [augmentation de NSException] 'en CoreFoundation? Je suppose que vous trouverez le problème assez rapidement si vous pouvez le retracer jusqu'à l'élément exact sur lequel il s'étouffe. – fullofsquirrels

+0

Pouvez-vous enregistrer '[NSKeyedUnarchiver unarchiveObjectWithData: decodedRedWines]' cette ligne et me dire le type de l'objet retourné? Comme ceci: 'NSLog (@"% @ ", [NSKeyedUnarchiver unarchiveObjectWithData: decodedRedWines]);' Cela vous aidera à savoir exactement ce que vous avez stocké dans 'NSUserDefaults'. – Hemang

+0

Salut Hemang, merci pour votre commentaire. J'ai essayé votre suggestion mais je n'ai pas eu une sortie dans NSLog, à la place j'ai eu cette erreur: [NSTaggedPointerString objectForKey:]: sélecteur non reconnu envoyé à l'instance 0xa5c064950b08843b App [3437: 1872740] *** Terminant l'application due à l'exception uncaught ' NSInvalidArgumentException », raison: '- [NSTaggedPointerString objectForKey:]: sélecteur non reconnu envoyé à l'instance 0xa5c064950b08843b' *** Première pile d'appel de lancer: (0x18e0bafe0 0x18cb1c538 0x18e0c1ef4 ... 0x194251058 0x1000c4acc 0x18cfa559c) libC++ abi.dylib: se terminant par uncaught exception de type NSException –

Répondre

0

Il y a une faute de frappe dans votre méthode de initWithCoder:

self.wineCompanyName = [décodeur decodeObjectForKey: @ "comapny"]; Si cela ne le résout pas, je regarderais de plus près la documentation de NSUserDefaults - il dit "Les valeurs retournées par NSUserDefaults sont immuables, même si vous définissez un objet mutable comme valeur." Votre propriété redWines est définie comme NSMutableArray.

+0

Merci pour votre commentaire. J'ai corrigé la faute de frappe (je déteste ces erreurs stupides) et j'ai aussi essayé d'ajouter trois nouveaux NSArrays non modifiables pour vérifier si c'était le problème mais j'ai toujours les mêmes résultats ... Je mettrai à jour le fil afin de vous montrer maintenant. –

0

Pour faire un objet immuable mutable il suffit d'appeler mutableCopy

@property (strong, nonatomic) NSMutableArray *redWines; 

... 

self.redWines = [[NSKeyedUnarchiver unarchiveObjectWithData: decodedRedWines] mutableCopy]; 
+0

Je vais essayer aussi, mais je ne suis pas sûr si le problème était que j'utilisais des tableaux non mutables au lieu de mutables. –

+0

Votre code avec les deux types semble confus. Si vous avez vraiment besoin d'objets mutables, supprimez ceux qui sont immuables. Et il serait utile d'ajouter la raison réelle de l'exception. – vadian

+0

Merci, vous avez raison, les deux types le rend un peu confus. Je vais mettre à jour le thread à nouveau afin que vous puissiez voir les modifications, mais je reçois toujours le même plantage :( –