Does anyone have example code using encodeBytes:length:forKey:? - ios

I need it for my NSCoder classes apparently and it would be useful if I can see a real example of this and its corresponding method.
The reason I feel I need it is when my method tries to encodeObject for an NSString*, it complains saying:
Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '* -encodeBytes:length:forKey: only defined for abstract class. Define -[JSONEncoder encodeBytes:length:forKey:]!'
Thanks.
The method in JSONEncoder: (https://github.com/ontometrics/JSONCoding) I couldn't get the static library thing to work, so I'm trying to work directly with the code.
- (void)encodeObject:(id)object {
[self push:object];
[object encodeWithCoder:self];
finalJSONObject = [NSDictionary dictionaryWithObject:[self topObject] forKey:[[[object class] description] camelcaseString]];
[self pop];
}

I think that the JSONEncoder is mainly meant to encode objects from custom classes (which must implement the NSCoding protocol). For example, if you define a class Person with a string property:
#interface Person : NSObject <NSCoding>
#property(strong, nonatomic) NSString *name;
#end
#implementation Person
- (id)initWithCoder:(NSCoder *)coder {
if ((self = [super init])) {
self.name = [coder decodeObjectForKey:#"name"];
}
return self;
}
- (void)encodeWithCoder:(NSCoder *)coder {
[coder encodeObject:self.name forKey:#"name"];
}
#end
then you can encode an object of this class without problems using JSONEncoder:
JSONEncoder *encoder = [JSONEncoder encoder];
Person *obj = [[Person alloc] init];
obj.name = #"John";
[encoder encodeObject:obj];
NSString *json = [encoder json];
NSLog(#"%#", json);
// Output: {"person":{"name":"John"}}
It works also with some "plain" Foundation types, e.g. with NSNumber, but the output is not very "nice", and I am not sure if JSONEncoder is meant to be used like that:
JSONEncoder *encoder = [JSONEncoder encoder];
NSNumber *obj = #1234;
[encoder encodeObject:obj];
NSString *json = [encoder json];
NSLog(#"%#", json);
// Output: {"__NSCFNumber":{"NS.intval":1234}}
In this case, the encodeInt64:forKey: method of JSONEncoder is called internally.
However, as you noticed, it does not work with NSString:
JSONEncoder *encoder = [JSONEncoder encoder];
NSString *obj = #"Hello world";
[encoder encodeObject:obj];
// Exception: *** -encodeBytes:length:forKey: only defined for abstract class. Define -[JSONEncoder encodeBytes:length:forKey:]!
The reason for the exception is that NSString is NSCoding compliant, but it uses the -encodeBytes:length:forKey: of the coder, and JSONEncoder does not implement this method.
Implementing that method is not too difficult, this is a quick-and-dirty attempt:
- (void)encodeBytes:(const uint8_t *)bytesp length:(NSUInteger)lenv forKey:(NSString *)key
{
NSString *s = [[NSString alloc] initWithBytes:bytesp length:lenv encoding:NSUTF8StringEncoding];
[self setObject:s forKey:key];
}
If you add this code to "JSONEncoder.m", then you can encode a plain NSString without getting an exception, the result looks like this:
{"__NSCFConstantString":{"NS.bytes":"Hello world"}}
But again, I am not sure if JSONEncoder is meant to be used like this.

Related

How to archive and unarchive an NSObject?

I am trying to archive an NSObject that I send through match data in a game center turn based game.
Below is my code for archiving my object
turnDataObject MyData = [[turnDataObject alloc] init];
data = [NSKeyedArchiver archivedDataWithRootObject:MyData];
This is my code for unarchiving my object
readMyData = [NSKeyedUnarchiver unarchiveObjectWithData:data] ;
However when I run this code I get an error
thread 1 exc bad access code
I think that this might have to do with sending addresses when I archive data. How do I send something that will be readable when I unarchive it?
Edit 1: I get the error on the next line after I unarchive. it says that the adress I am trying to access is null. I remember reading somewhere that I souldn't send adresses of my NSObject but I am not sure how to convert it to something else.
readMyData = [NSKeyedUnarchiver unarchiveObjectWithData:data] ;
NSLog(#"current game happens to be: %#", readMyData.currentGame);
Edit 2: here is my init with coder and encode with coder
- (instancetype)initWithCoder:(NSCoder *)decoder
{
self = [self init];
if (self) {
_currentGame = [decoder decodeObjectForKey:currentGameDataKey];
}
return self;
}
- (void)encodeWithCoder:(NSCoder *)encoder
{
//scores data keys
[encoder encodeObject:self.currentGame forKey:currentGameDataKey];
}
Edit 3: _currentGame is in my objects .h file
#property (assign, nonatomic) NSString *currentGame;
I would suggest creating NSKeyedArchiver and NSKeyedUnarchiver objects and using those instead of using the type (which it looks like you're doing.)
I usually program in Swift but here's a shot at example code:
theArchiver NSKeyedArchiver = [[theArchiver alloc] init];
data = [theArchiver archivedDataWithRootObject:MyData];
Then you would do the same thing with the NSKeyedUnarchiver.
Your initWithCoder implementation is wrong:
self = [self init];
That should be:
self = [super init];
You need to add NSCoding protocol to MyData Class, here is the code snippet with supporting NSCoding in order to add Archiving support to NSObjet.
MyData.h
#interface MyData : NSObject <NSCoding>
#property (nonatomic, strong) NSString *currentGame;
#end
MyData.m
//This method is optional, if you need constructor for current game
- (instancetype)initWithCurrentGame:(NSDictionary *)currentGame {
self = [super init];
if (self) {
self.currentGame = currentGame;
}
return self;
}
-(void)encodeWithCoder:(NSCoder *)encoder {
[encoder encodeObject:self.currentGame forKey:#"currentGame"];
}
-(id)initWithCoder:(NSCoder *)decoder {
self.currentGame = [decoder decodeObjectForKey:#"currentGame"];
return self;
}

How to test a simple data object class which implements NSCoding?

I have a small data object that needs to be serialized and deserialized. Lets say it is called WeatherDetails, and it looks like this:
WeatherDetails.h
#interface WeatherDetails : NSObject <NSCoding>
{
#private
#protected
}
#pragma mark - Properties
#property (nonatomic, copy) NSString *weatherCode;
#property (nonatomic, copy) NSString *weatherDescription;
#end
WeatherDetails.m
#import "WeatherDetails.h"
#implementation WeatherDetails
NSString *const WEATHER_DETAILS_WEATHER_CODE_KEY = #"s";
NSString *const WEATHER_DETAILS_WEATHER_DESCRIPTION_KEY = #"sT";
#pragma mark - Initialization, NSCoding and Dealloc
- (instancetype)initWithCoder:(NSCoder *)aDecoder
{
self = [super init];
_weatherCode = [aDecoder decodeObjectForKey:#"weatherCode"];
_weatherDescription = [aDecoder decodeObjectForKey:#"weatherDescription"];
return self;
}
- (void)encodeWithCoder:(NSCoder *)aCoder
{
[aCoder encodeObject:_weatherCode forKey:#"weatherCode"];
[aCoder encodeObject:_weatherDescription forKey:#"weatherDescription"];
}
Currently my tests look like this;
#import <XCTest/XCTest.h>
#import <OCMock/OCMock.h>
#import "WeatherDetails.h"
#interface WeatherDetailsTests : XCTestCase
#end
#implementation WeatherDetailsTests
- (void)testThatWeatherCodeIsEncoded
{
WeatherDetails *details = [[WeatherDetails alloc] init];
[details setWeatherCode:#"A"];
NSData *archive = [NSKeyedArchiver archivedDataWithRootObject:details];
WeatherDetails *unarchive = [NSKeyedUnarchiver unarchiveObjectWithData:archive];
XCTAssertEqualObjects(#"A", [unarchive weatherCode]);
}
- (void)testThatWeatherDescriptionIsEncoded
{
WeatherDetails *details = [[WeatherDetails alloc] init];
[details setWeatherDescription:#"A"];
NSData *archive = [NSKeyedArchiver archivedDataWithRootObject:details];
WeatherDetails *unarchive = [NSKeyedUnarchiver unarchiveObjectWithData:archive];
XCTAssertEqualObjects(#"A", [unarchive weatherDescription]);
}
I have a gut feeling that this approach to testing if all properties are correctly encoded is not really optimal as there is duplication, but I can't really think of a better approach. Does anyone have a tip for me on improving this?
What you really want to test is that the object is same before and after you archive it.
Implement a method in your WeatherDetails to compare the objects (or override isEqual:).
- (BOOL)isEqualToWeatherDetails:(WeatherDetails *)details
{
if (![details isKindOfClass:[WeatherDetails class]]) return NO;
return [self.weatherCode == details.weatherCode && self.weatherDescription isEqualToString:details.weatherDescription];
}
Then you can do all your equality comparisons at once:
- (void)testNSCoder
{
WeatherDetails *details = [[WeatherDetails alloc] init];
[details setWeatherCode:#"A"];
details.weatherDescription = #"Cloudy";
NSData *archive = [NSKeyedArchiver archivedDataWithRootObject:details];
WeatherDetails *unarchive = [NSKeyedUnarchiver unarchiveObjectWithData:archive];
XCTAssertTrue([details isEqualToWeatherDetails:unarchive]);
}
If you overrode isEqual: then you could compare doing this:
XCTAssertEqualObjects(details, unarchive);
Apple tends to add additional methods (isEqualToArray:, isEqualToDictionary:). isEqual: is used by collections like NSSet and NSDictionary
In the end I improved it by applying a custom assertion;
When To Use It
We should consider creating a Custom Assertion whenever any of the following are true:
We find ourselves writing (or cloning) the same assertion logic in test after test
We find ourselves writing Conditional Test Logic in the result verification part of our tests. That is, our calls to Assertion Methods are embedded in if statements or loops.
The result verification parts of our tests are suffering from Obscure Test because we are using procedural rather than declarative result verification in the tests.
We find ourselves doing Frequent Debugging whenever assertions fail because they do not provide enough information.
The custom assertion currently looks like this:
/*!
* #define XCTAssertEqualSerialized(value, object, selector)
* Serializes (\a object), then desirializes it and compares result's property (\a propertyName) to (\a value) with XCTAssertEqualObjects
* #param value Value to compare.
* #param object Object to serialize.
* #param selector Objects property to compare.
*/
#define XCTAssertEqualSerialized(value, object, selector) \
({ \
NSData *archived = [NSKeyedArchiver archivedDataWithRootObject:object]; \
NSObject *unarchived = [NSKeyedUnarchiver unarchiveObjectWithData:archived]; \
XCTAssertEqualObjects(value, [unarchived valueForKeyPath:NSStringFromSelector(selector)]); \
})
/*!
* #define XCTAssertNotNilSerialized(object, selector)
* Serializes (\a object), then desirializes it and checks result's property (\a propertyName) is not nil with XCTAssertNotNil
* #param object Object to serialize.
* #param selector Objects property to compare.
*/
#define XCTAssertNotNilSerialized(object, selector) \
({ \
NSData *archived = [NSKeyedArchiver archivedDataWithRootObject:object]; \
NSObject *unarchived = [NSKeyedUnarchiver unarchiveObjectWithData:archived]; \
XCTAssertNotNil([unarchived valueForKeyPath:NSStringFromSelector(selector)]); \
})
Which results in the tests looking like this:
- (void)testThatWeatherCodeIsEncoded
{
WeatherDetails *details = [[WeatherDetails alloc] init];
[details setWeatherCode:#"A"];
XCTAssertEqualSerialized(#"A", details, #selector(weatherCode));
}
- (void)testThatWeatherDescriptionIsEncoded
{
WeatherDetails *details = [[WeatherDetails alloc] init];
[details setWeatherDescription:#"A"];
XCTAssertEqualSerialized(#"A", details, #selector(weatherDescription));
}

Save data even if the app re-opens - Swift Xcode 6 iOS

I'm working on a app that can randomize love couples. Just a fun thing, okey!?!? :D
But the problem, or maybe not a problem but a thing that can be much better if I get this thing to be working. In the beginning you need to write in all the names. And thats takes some time... Should I use Core Date? I don't really knows what core data is so I'm not sure. I would love if a god come to me and wrote the full code that can remember an array even if the app and phone shuts down. I have done this in java, is that simpel that it is in java? That would be great!
//Thank, Anton
For Heavy, complex data structures you would want to use core data,
https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/CoreData/Articles/cdTechnologyOverview.html#//apple_ref/doc/uid/TP40009296-SW1
But seeing as you just want to store an array, You should look into NSUserDefaults.
NSUserDefaults will store given data as long as the app is not deleted. You will most likely want to create some kind of custom DataStorage class for this.
#interface DataStorage : NSObject <NSCoding>
#property (nonatomic, strong) NSMutableArray *arrayToStore;
+ (instancetype)sharedInstance;
- (void)save;
#end
Above is the .h file. As you can see, it follows NSCoding protocols. That provides access to methods which allow you to encode data. You will use the save method to write the data to disk.
#import "DataStorage.h"
#implementation DataStorage
#synthesize arrayOfPeople = _arrayToStore;
+ (DataStorage *)sharedInstance
{
static DataStorage *state = nil;
if ( !state )
{
NSData *data =[[NSUserDefaults standardUserDefaults] objectForKey:#"DataStorageKey"];
if (data)
{
state = [NSKeyedUnarchiver unarchiveObjectWithData:data];
}
else
{
state = [[DataStorage alloc] init];
}
}
return state;
}
- (id)init{
if (self = [super init]) {
if (!_arrayToStore) {
_arrayToStore = [[NSMutableArray alloc] init];
}
}
return self;
}
- (instancetype)initWithCoder:(NSCoder *)decoder
{
self = [self init];
if (self) {
if ([decoder decodeObjectForKey:#"DataStorageArrayToStore"]) {
_arrayToStore = [[decoder decodeObjectForKey:#"DataStorageArrayToStore"] mutableCopy];
}
}
return self;
}
- (void)encodeWithCoder:(NSCoder *)encoder {
[encoder encodeObject:_arrayToStore forKey:#"DataStorageArrayToStore"];
}
- (void)save
{
NSData *appStateData = [NSKeyedArchiver archivedDataWithRootObject:self];
[[NSUserDefaults standardUserDefaults] setObject:appStateData forKey:#"DataStorageKey"];
[[NSUserDefaults standardUserDefaults] synchronize];
}
#end
Here is the .m file, which pretty much evaluates to see if there is a saved instance of the class, and if not it will create one. [DataStorage sharedInstance]...
when you want to store some data, you will simply make the class available to said file, #import "DataStorage.m and then use
NSString *testData = [NSString stringWithFormat: #"Test Data String"];
[[DataStorage sharedInstance].arrayToStore addObject: testData];
[DataStorage sharedInstance] save];

Storing custom objects with Core Data

I'm trying to store a NSMutableArray consisting of VOs (NameVO) with Core Data but getting the following exception thrown:
'NSInvalidArgumentException', reason: '-[NameVO encodeWithCoder:]: unrecognized selector sent to instance 0x1096400a0'
My NameVO is just a simple value object, extending NSObject and contains two fields, a string and a NSMutableArray that itself contains strings. It also contains a compare function.
I'm trying to prepare this to be stored as a CD transformable attribute type with ('names' is my NameVO array):
NSData *tempNamesData = [NSKeyedArchiver archivedDataWithRootObject:names];
My question is: what do I need to do to make NameVO be accepted by NSKeyedArchiver to convert it successfully to a NSData?
I don't want NameVO to extend NSManagedObject because then I cannot instantiate and init it directly.
Now that we're in 2017, it's best to use the safer NSSecureCoding protocol instead of the older, less safe NSCoding protocol. The implementation changes are minimal:
1) ensure that your class declares its conformation to NSSecureCoding
#interface MyClass : NSObject<NSSecureCoding>
#property (nonatomic, strong) NSNumber *numberProperty;
#property (nonatomic, strong) NSString *stringProperty;
#end
2) NSSecureCoding protocol houses the same two instance methods methods in the NSCoding protocol, plus an additional class method, +supportsSecureCoding. You'll need to add that method, as well as slightly modify your -initWithCoder: method.
#implementation MyClass
// New Method for NSSecureCoding. Return YES.
+ (BOOL)supportsSecureCoding {
return YES;
}
// Your Encode Method Can Stay The Same, though I would use NSStringFromSelector whenever possible to get the keys to ensure you're always getting what you're looking for.
- (void)encodeWithCoder:(NSCoder *)aCoder {
[aCoder encodeObject:self.numberProperty forKey:NSStringFromSelector(#selector(numberProperty))];
[aCoder encodeObject:self.stringProperty forKey:NSStringFromSelector(#selector(stringProperty))];
}
// Slightly updated decode option
- (instancetype)initWithCoder:(NSCoder *)aDecoder {
self = [super init];
if (self) {
self.numberProperty = [aDecoder decodeObjectOfClass:[NSNumber class] forKey:NSStringFromSelector(#selector(numberProperty))];
self.stringProperty = [aDecoder decodeObjectOfClass:[NSString class] forKey:NSStringFromSelector(#selector(stringProperty))];
}
}
#end
Notice that NSCoder's -decodeObjectOfClass:withKey: requires you to specify the class that you're expecting to receive. This is a much safer way to do things.
Then, to store this decodable object in CoreData, simply create a Managed object that contains an NSData attribute and some identifying information (a string, a date, an id, or a number or something)
#interface MyClassMO : NSManagedObject
#property (nonatomic, strong) NSString *identifier;
#property (nonatomic, strong) NSData *data;
#end
#implementation MyClassMO
#dynamic identifier;
#dynamic data;
#end
In practice, it would look something like this:
- (void)storeObject:(MyClass *)object withIdentifier:(NSString *)identifier {
NSData *objectData = [NSKeyedArchived archivedDataWithRootObject:object];
NSManagedObjectContext *moc = ... // retrieve your context
// this implementation relies on new NSManagedObject initializers in the iOS 10 SDK, but you can create it any way you typically create managed objects
MyClassMO *managedObject = [[MyClassMO alloc] initWithContext:moc];
managedObject.data = objectData;
managedObject.identifier = identifier;
NSError *error;
[moc save:&error];
}
- (MyClass *)retrieveObjectWithIdentifier(NSString *)identifier {
NSManagedObject *moc = ... // retrieve your context
// This also relies on iOS 10 SDK's new factory methods available on NSManagedObject. You can create your request any way you typically do;
NSFetchRequest *request = [MyClassMO fetchRequest];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"identifier = %#", identifier];
request.predicate = predicate;
NSError *error;
NSArray<MyClassMO *> *results = [moc executeFetchRequest:request withError:&error];
// if you're only storing one object per identifier, this array should only be 1 object long. if not, you'll need to decide which object you're looking for. you also might want to implement an overwrite policy or a delete before store type thing.
MyClassMO *managedObject = results.firstObject;
NSData *objectData = managedObject.data;
MyClass *object = [NSKeyedUnarchiver unarchiveObject:objectData];
return object;
}
This solution is obviously a bit of an oversimplification, how and when you store stuff in the db is up to your needs, but the main idea is that, you'll need to make sure your custom class conforms to NSSecureCoding, and that you'll need to make a separate Managed Object class to store and retrieve your data.
As your exception says you need to implement NSCoding protocol to your class and you have to override two methods:
- (void)encodeWithCoder:(NSCoder *)aCoder;
- (id)initWithCoder:(NSCoder *)aDecoder;
It should sorted your issue.
// EXTENDED
- (id)initWithCoder:(NSCoder *)aDecoder
{
self = [super init];
if (self) {
_formId = [aDecoder decodeObjectForKey:#"FormID"];
}
return self;
}
- (void)encodeWithCoder:(NSCoder *)aCoder
{
[aCoder encodeObject:self.formId forKey:#"FormID"];
}
Use this initWithCoder and encodeWithCode method.I hope it will work for you. it works for me in same issue as u have...Use this sample code
-(id)initWithCoder:(NSCoder *)aDecoder
{
if(self = [super init]){
storePlaylist=[aDecoder decodeObjectForKey:#"storePlaylist"];
playlistName=[aDecoder decodeObjectForKey:#"playlistName"];
}
return self;
}
-(void)encodeWithCoder:(NSCoder *)encoder
{
[encoder encodeObject:storePlaylist forKey:#"storePlaylist"];
[encoder encodeObject:playlistName forKey:#"playlistName"];
}

Saving an NSMutableArray filled with custom objects

How do I save/read an NSMutableArray filled with objects I made a class for?
This is what I have so far... (where 'ObjectA' represents my class, and 'objects' is an array containing many instances of 'ObjectA')
//Creating a file
//1) Search for the app's documents directory
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
//2) Create the full file path by appending the desired file name
NSString *documentFileName = [documentsDirectory stringByAppendingPathComponent:#"save.dat"];
//Load the array
objects = [[NSMutableArray alloc] initWithContentsOfFile: _documentFileName];
if(objects == nil)
{
//Array file didn't exist... create a new one
objects = [[NSMutableArray alloc]init];
NSLog(#"Did not find saved list, Created new list.");
}
else
{
NSLog(#"Found saved list, Loading list.");
}
This will load an array if it exists. It works if the array is filled with property type objects like NSNumbers, I tested that. If I fill it with my own objects, it crashes! This is the error: (where 'objectAInt' is a private int belonging to 'ObjectA')
-[__NSCFNumber objectAInt]: unrecognized selector sent to instance 0x15d417f02013-11-19 17:28:58.645 My Project[1791:60b] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[__NSCFNumber objectAInt]: unrecognized selector sent to instance 0x15d417f0'
What do I need to do to make my class, 'ObjectA', work with the save/read process like it does with NSNumbers and other NS objects of type (id)?
Thank You!
P.S. My class implements and is NSCoding compliant. If more information is needed please don't hesitate to ask! :)
EDIT -- Here is my ObjectA (per request) (it is a subclass of NSObject)
//
// ObjectA.m
// My Project
//
// Created by Will Battel on 8/8/13.
//
//
#import "ObjectA.h"
#implementation ObjectA
#synthesize objectAString, objectAString2, objectAString3, objectAInt;
-(id)initWithName:(NSString *)_objectAString{
self = [super init];
objectAString = _objectAString;
objectAInt = 2;
objectAString3 = #"$0.00";
return self;
}
-(id)initWithCoder:(NSCoder *)decoder {
self = [super init];
if (self) {
NSLog(#"Decoding");
[self setObjectAString:[decoder decodeObjectForKey:#"objectAStringKey"]];
[self setObjectAString2:[decoder decodeObjectForKey:#"objectAString2Key"]];
[self setObjectAString3Price:[decoder decodeObjectForKey:#"objectAString3Key"]];
[self setobjectAInt:[decoder decodeIntForKey:#"objectAIntKey"]];
}
return self;
}
-(void)encodeWithCoder:(NSCoder *)encoder {
NSLog(#"Encoding");
[encoder encodeObject:objectAString forKey:#"objectAStringKey"];
[encoder encodeObject:objectAString2 forKey:#"objectAString2Key"];
[encoder encodeObject:objectAString3 forKey:#"objectAString3Key"];
[encoder encodeInt:objectAInt forKey:#"objectAIntKey"];
}
#end
Since the array contains NSCoding compliant objects, you can use the coding methods, like this...
- (NSMutableArray *)instancesFromArchive {
// compute documentFileName using your original code
if ([[NSFileManager defaultManager] fileExistsAtPath:documentFileName]) {
return [NSKeyedUnarchiver unarchiveObjectWithFile:documentFileName];
} else {
return [NSMutableArray array];
}
}
// archives my array of objects
- (BOOL)archiveInstances {
// compute documentFileName using your original code
return [NSKeyedArchiver archiveRootObject:self.objects toFile:documentFileName];
}
NSNumber class is NSCoding compliant. Are your own objects NSCoding compliant?

Resources