Attempt to insert non-property list object for Key - objective-c

I'm trying to store an NSString value in NSUserDefaults. It takes some UUID-like format (e.g. 7da0b3fd-25ca-42c5-a934-bd89362a016b) if the user is logged in or NULL otherwise.
However occasionally my app gets crashed reporting that the data being stored is incompatible with property-list types.
The property is defined as follows:
#property (nonatomic, retain) NSString *userId;
Here is the code I write this property with:
NSUserDefaults *prefs = [NSUserDefaults standardUserDefaults];
if (self.userId != NULL) {
[prefs setValue:self.userId forKey:#"UserId"];
} else {
[prefs setValue:NULL forKey:#"UserId"];
}
[prefs synchronize];
And here are the examples of errors being reported:
Stack Trace: Attempt to insert non-property list object
GEOMapService: 0x280c80f00 for key UserId
Stack Trace: Attempt
to insert non-property list object GPBEnumDescriptor: 0x28370a480
for key UserId
Stack Trace: Attempt to insert non-property list
object NWConcrete_nw_protocol_stack: 0x283000800 for key UserId
Stack Trace: Attempt to insert non-property list object UIImageConfiguration:0x2830f3540 traits=(DisplayScale = 3) for key UserId
Stack Trace: Attempt to insert non-property list object
GEOMapRegion: 0x2838eea00 { eastLng = "-81.86280557675238"; mapRegionSourceType = "REV_POINT_PADDED"; northLat = "28.56967626614077"; southLat = "28.56069311329958"; westLng = "-81.87303376983076"; } for key UserId
and some other different crashes like this
How to fix this error?

Related

NSUserDefaults set the default value of integer variable to empty

I used NSUserDefaults to store an integer value. When I run the project there's a default value of 0 in it. How do I remove the 0 value? I just want it to be empty until the user put something in it.
Here is my code:
- (IBAction)save:(id)sender{
int port = [[portField text] integerValue];
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
[defaults setInteger:port forKey:#"port"];
[defaults synchronize];
}
- (void)viewDidLoad {
int port = [defaults integerForKey:#"port"];
NSString *portString = [NSString stringWithFormat:#"%lu",(unsigned long)port];
portField.text = portString;
[super viewDidLoad];
}
Well, this line:
int port = [defaults integerForKey:#"port"];
will always assign some value to port.
Also, this:
NSString *portString = [NSString stringWithFormat:#"%lu",(unsigned long)port];
will always produce a non-empty string. There's no value you could pass for a %lu specifier that will produce an empty string.
So, what you want to do is ask for the object that's stored in the preferences, without converting it to a integer value:
id portObject = [defaults objectForKey:#"port"];
if (portObject)
{
NSInteger port;
if ([portObject respondsToSelector:#selector(integerValue)])
port = [portObject integerValue];
else
{
// portObject is some unexpected class of object, such as an array or dictionary. Take some appropriate action. Or:
port = 0;
}
portField.text = [NSString stringWithFormat:#"%ld", (long)port];
}
else
{
// There was no value either stored or registered for the key "port"
portField.text = #"";
}
Note that for this to work, you must not have registered a default value for the "port" key using -[NSUserDefaults registerDefaults:].
As far as I know, NSUserDefaults always return default values for primitive types when they are not exists.
If the default value is a valid value you use, I suggest to put a blank object or just NSString object to determine if the associated value is valid. e.g.
NSObject *objValid = [userDefaults objectForKey:#"integerIsValid"];
if(objValid != nil) {
// User stored integer value exists.
NSInteger i = [userDefaults integerForKey:#"SomeInteger"];
} else {
// Have not saved integer value.
}
When user save the integer value, also save the associated object:
[userDefaults setObject:[[NSObject alloc]init] forKey:#"integerIsValid"];
[userDefaults setInteger:i forKey:#"SomeInteger"];
UPDATE
If your value range can transform to another value range by some rules, then you can do it another way.
All values are in range [0, 100) for example, you can store in range [1, 101) and retrieve the real value by subtracting 1.

Convert NSArray to CFStringRef *

I need a way to convert an NSArray to a null terminated list compatible with the arguments option of DADiskMountWithArguments.
The documentation specifies the argument option to be a "Null terminated list" of type CFStringRef arguments[].
I have created a Mount method that I want to pass an NSArray with the arguments, and in my method I need to convert the NSArray to a CFStringRef *.
I've tried myself but I always get in trouble with ARC, and I have not been able to find any good way to do this yet.
I've looked at the project Disk-Arbitrator in GitHub https://github.com/aburgh/Disk-Arbitrator/blob/master/Source/Disk.m for inspiration, and the creator of that application uses this method:
- (void)mountAtPath:(NSString *)path withArguments:(NSArray *)args
{
NSAssert(self.isMountable, #"Disk isn't mountable.");
NSAssert(self.isMounted == NO, #"Disk is already mounted.");
self.isMounting = YES;
Log(LOG_DEBUG, #"%s mount %# at mountpoint: %# arguments: %#", __func__, BSDName, path, args.description);
// ensure arg list is NULL terminated
id *argv = calloc(args.count + 1, sizeof(id));
[args getObjects:argv range:NSMakeRange(0, args.count)];
NSURL *url = path ? [NSURL fileURLWithPath:path.stringByExpandingTildeInPath] : NULL;
DADiskMountWithArguments((DADiskRef) disk, (CFURLRef) url, kDADiskMountOptionDefault,
DiskMountCallback, self, (CFStringRef *)argv);
free(argv);
}
But that is not allowed in ARC, and I can't find a way to do it.
Update for better clarity:
This line:
id *argv = calloc(args.count + 1, sizeof(id));
Gives the following error message:
Implicit conversion of a non-Objective-C pointer type 'void *' to
'__strong id *' is disallowed with ARC
Pointer to non-const type 'id' with no explicit ownership.
To fix that i try to do this:
id argv = (__bridge id)(calloc(args.count + 1, sizeof(id)));
Then this line:
[args getObjects:argv range:NSMakeRange(0, args.count)];
Gives the following errors:
[ERROR] Implicit conversion of an Objective-C pointer to
'__unsafe_unretained id *' is disallowed with ARC
[WARN] Incompatible pointer types sending '__string id' to parameter
of type '__unsafe_unretained id *'
The declaration of -getObjects:range: look like this:
- (void)getObjects:(id [])aBuffer range:(NSRange)aRange
So from the error message i got I assume i have to pass an '__unsafe_unretained id *' to 'getObjects:(id [])aBuffer'. So to fix that i declare my id as __unsafe_unretained like this:
__unsafe_unretained id argv = (__bridge __unsafe_unretained id)(calloc(args.count + 1, sizeof(id)));
And update this line like this:
[args getObjects:&argv range:NSMakeRange(0, args.count)];
Now i don't have any errors there, but in the call to DADiskMountWithArguments i get the following error:
Cast of an Objective-C pointer to 'CFStringRef *' (aka 'const struct
__CFString **) is disallowed with ARC
So here I got stuck as I have not been able to fix this error, and I don't know if I made mistakes earlier or if I haven't found the right way send the CFStringRef, therefore I decided to ask for guidance here.
This is how it looks in context, where args is an NSArray declared earlier:
__unsafe_unretained id argv = (__bridge __unsafe_unretained id)(calloc(args.count + 1, sizeof(id)));
[args getObjects:&argv range:NSMakeRange(0, args.count)];
DADiskMountWithArguments((DADiskRef) disk, (__bridge CFURLRef) url, kDADiskMountOptionDefault, NULL, (__bridge void *)self, (CFStringRef *)argv );
So my question is either, how can this method be made ARC-friendly, or is there another/better way to get from an NSArray to a NULL-terminated CFStringRef *
Try this:
CFStringRef *argv = calloc(args.count + 1, sizeof(CFStringRef));
CFArrayGetValues((__bridge CFArrayRef)args, CFRangeMake(0, args.count), (const void **)argv );
DADiskMountWithArguments((DADiskRef) disk, (CFURLRef) url, kDADiskMountOptionDefault,
DiskMountCallback, self, argv);
free(argv);
There are no Core Foundation/Cocoa memory management issues because CFArrayGetValues() doesn't give you ownership of the returned values.

Deprecated Xcode issues

im getting errors and warnings with the following code:
Warning undeclared selector uniqueIdentifier at the first if statement;
Error Property uniqueIdentifier not found on object of type UIDevice
Error Cast of C pointer type CFStringRef
// Get the users Device Model, Display Name, Unique ID, Token & Version Number
UIDevice *dev = [UIDevice currentDevice];
NSString *deviceUuid;
if ([dev respondsToSelector:#selector(uniqueIdentifier)]) // Warning #1
deviceUuid = dev.uniqueIdentifier; // Error #1
else {
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
id uuid = [defaults objectForKey:#"deviceUuid"];
if (uuid)
deviceUuid = (NSString *)uuid;
else {
CFStringRef cfUuid = CFUUIDCreateString(NULL, CFUUIDCreate(NULL));
deviceUuid = (NSString *)cfUuid; // Error #2
CFRelease(cfUuid);
[defaults setObject:deviceUuid forKey:#"deviceUuid"];
}
}
UIDevice uniqueIdentifier property is deprecated in iOS 5 and above
udid = [[[UIDevice currentDevice] identifierForVendor] UUIDString];
NSLog(#"UDID : %#", udid);
You must create a method with name uniqueIdentifier
UIDevice does not have a property called uniqueIdentifier. See this answer.
Look at this answer.

Take a number from JSON and use it to setValue to Int 32 in Core Data

I have an array, pulled from JSON received from a web service. When I use NSLOG to view the contents of the array, it displays like this:
{ ? Category = "New Products";
CategoryID = 104;
SubCategories = (
);
}
I need to take the Category and CategoryID values and assign them to the managed object "newProductCategory". I have no problem assigning the Category value, which corresponds to a string type, and I have no problem assigning a hard-coded number to the int 32 type that's supposed to receive the Category ID. But I've been struggling when it comes to converting the CategoryID value into anything that will be accepted as the int 32 type.
What's the proper syntax for converting that value into something digestible for this line of code, in place of the zero?
[newProductCategory setValue : 0 forKey : #"productCategoryID"];
Here's one of my failed attempts that might be informative. When I try...
// Pull category record "i" from JSON
NSArray * categoryProperties = [categories objectAtIndex:i];
NSNumber * productCategoryID = [categoryProperties valueForKey:#"CategoryID"];
... then I attempt to assign it in the above format, using productCategoryID in place of the zero, it produces the following error:
'NSInvalidArgumentException', reason: 'Unacceptable type of value for attribute: property = "parentCategoryID"; desired type = NSNumber; given type = __NSCFString; value = 104.'
Even if you specify int32 in CoreData, you will pass a NSNumber object, and it seems that you get a NSString from the json parsing (you can try a log of NSStringFromClass([productCategoryID class]) to be sure).
You can try :
NSString * productCategoryID = [categoryProperties valueForKey:#"CategoryID"];
newProductCategory.productCategoryID = #([productCategoryID intValue]);
//or
newProductCategory.productCategoryID = [NSNumber numberWithInt:[productCategoryID intValue]];
You need to set NSNumber, there are 2 ways:
[newProductCategory setValue:#(0) forKey:#"productCategoryID"];
or
[newProductCategory setValue:[NSNumber numberWithInt:0] forKey:#"productCategoryID"];

How to allow NSMutableDictionary to accept 'nil' values?

I have this statement:
[custData setObject: [rs stringForColumnIndex:2] forKey: #"email"];
where [rs stringForColumnIndex:2] obtained from a SQLite3 d/b has a value of nil. The app crashes giving me the error:
NSCFDictionary setObject:forKey:]: attempt to insert nil value (key: email)'
Is there a way to prevent this? (like a setting for NSMutableDictionary?)
UPDATE: this is what I finally did:
[custData setObject: ([rs stringForColumnIndex:2] != nil? [rs stringForColumnIndex:2]:#"") forKey: #"email"];
There is a non-nil object called NSNull that is built specifically to represent nils in situations where "plain" nil is not acceptable. If you replace your nils with [NSNull null] object, NSDictionary will take them. You would need to check for NSNull on the way out, though.
Note that this is important only when you must differentiate between a value not being set and a value being set to nil. If your code is such that it can interpret a missing value as nil, you do not need to use NSNull at all.
It is not possible with a pure NSMutableDictionary, and in most cases you want to convert nil values into [NSNull null] or just omit them from the dictionary. Sometimes (very seldom), though, it is convenient to allow nil values, and in those cases you can use CFMutableDictionary with custom callbacks.
If you go this way, I recommend that you use CoreFoundation API for all accesses, e.g. CFDictionarySetValue and CFDictionaryGetValue.
However, if you know what you're doing, you can use toll-free bridging and cast that CFMutableDictionary to NSMutableDictionary or NSDictionary. This may be useful if you have a bunch of helpers that accept NSDictionary, and you want to use them on your modified nil-capable dictionary. (Of course, make sure that the helpers aren't surprised by nil values.)
If you do the bridging, note that:
1) NSMutableDictionary setter raises errors on nil values before bridging, so you need to use CFDictionarySetValue to set values that are potentially nil.
2) technically, we're violating a contract of NSMutableDictionary here, and things may break (e.g. in future OS updates)
3) a lot of code will be very surprised to find nil values in a dictionary; you should only pass the bridged frankendictionaries to the code that you control
See ridiculousfish's post on toll-free bridging for an explanation of why a bridged CFDictionary behaves differently from NSDictionary.
Example:
#import <Foundation/Foundation.h>
const void *NullSafeRetain(CFAllocatorRef allocator, const void *value) {
return value ? CFRetain(value) : NULL;
}
void NullSafeRelease(CFAllocatorRef allocator, const void *value) {
if (value)
CFRelease(value);
}
const CFDictionaryValueCallBacks kDictionaryValueCallBacksAllowingNULL = {
.version = 0,
.retain = NullSafeRetain,
.release = NullSafeRelease,
.copyDescription = CFCopyDescription,
.equal = CFEqual,
};
int main(int argc, const char * argv[])
{
#autoreleasepool {
CFMutableDictionaryRef cfdictionary = CFDictionaryCreateMutable(NULL, 0, &kCFTypeDictionaryKeyCallBacks, &kDictionaryValueCallBacksAllowingNULL);
CFDictionarySetValue(cfdictionary, #"foo", #"bar");
CFDictionarySetValue(cfdictionary, #"boz", nil);
NSMutableDictionary *dictionary = CFBridgingRelease(cfdictionary);
NSLog(#"dictionary[foo] = %#", dictionary[#"foo"]);
NSLog(#"dictionary[foo] = %#", dictionary[[#"fo" stringByAppendingString:#"o"]]);
NSLog(#"dictionary[boz] = %#", dictionary[#"boz"]);
NSLog(#"dictionary = %#", dictionary);
NSLog(#"(dictionary isEqualTo: dictionary) = %d", [dictionary isEqualToDictionary:dictionary]);
}
return 0;
}
outputs:
dictionary[foo] = bar
dictionary[foo] = bar
dictionary[boz] = (null)
dictionary = {
boz = (null);
foo = bar;
}
(dictionary isEqualTo: dictionary) = 1
I needed to set a NSDictionary value to one that may or may not be set yet from NSUserDefaults.
What I did was wrap the values in a stringwithFormat call. Both values are not yet set so start as null. When I run without the stringwithFormat call the app crashes. So I did this and in my situation worked.
-(NSDictionary*)userDetailsDict{
NSDictionary* userDetails = #{
#"userLine":[NSString stringWithFormat:#"%#",[[NSUserDefaults standardUserDefaults]stringForKey:kSelectedLine] ],
#"userDepot":[NSString stringWithFormat:#"%#",[[NSUserDefaults standardUserDefaults]stringForKey:#"kSelected Duty Book"]]
};
return userDetails;
}