check id isKindOfType CFType - objective-c

Shortly: how to determine if id is CFType or not at runtime
i'm implementing dynamic core data attributes and in willSave method of ExtendedManagedObject i wanna check if the id value is CFType to store it into plist file.
If I'm trying to save to plist UIImage, that is not toll-free bridged with CF (apple docs), I am getting an error:
2011-11-17 17:16:25.294 [490:707] Error saving extended data: Property list invalid for format (property lists cannot contain objects of type 'CFType')
Can I check it with some method or I have to implement by myself (just isKindOfClass from docs)?
I don't want to implement accessors in NSManagedObject subclass, I dont know exactly how many urls I'll get from entities properties. Question is about dynamic extended attributes at runtime.
- (void)willSave
{
NSDictionary *changes = [self valueForKey:#"extendedChanges"];
if (changes!=nil) {
// merge changes into snapshot
NSMutableDictionary *dict = [[self extendedSnapshot] mutableCopy];
NSEnumerator *e = [changes keyEnumerator];
NSString *key;
while (key=[e nextObject]) {
id value = [changes objectForKey:key];
if (value==[NSNull null])
[dict removeObjectForKey:key];
else if (#ugly and I'm not shure is thread safe **else if ([value isKindOfClass:[NSArray class]] || [value isKindOfClass:[NSDictionary class]] || [value isKindOfClass:[NSDate class]] || [value isKindOfClass:[NSData class]] || [value isKindOfClass:[NSString class]] || [value isKindOfClass:[NSNumber class]])**)
[dict setObject:value forKey:key];
}
// archive as binary plist
NSData *data = nil;
if ([dict count]>0) {
NSString *error=nil;
data = [NSPropertyListSerialization dataFromPropertyList:dict
format:NSPropertyListBinaryFormat_v1_0 errorDescription:&error];
if (error!=nil) {
NSLog(#"Error saving extended data: %#", error);
[error release];
}
}
[dict release];
[self setPrimitiveValue:data forKey:#"extendedData"];
}
[super willSave];
}

That's a wrong way to approach the problem. The blog post you referenced saves the extended attributes as a serialized plist. The plist can contain only the following types of objects, as written in the official doc:
NSArray, NSDictionary, NSDate, NSData, NSString and NSNumber.
Other classes are just not allowed. Don't add objects of any other class to the attributes extended this way.
Also, saving an image file in a CoreData database is not a good idea, generally speaking, read On Blobs in the official doc. Instead, save the UIImage in a file and write the file path in the CoreData database.
If you just have a transient property which is not saved to the database, you don't even have to go through the trouble of creating extended attributes. Just add it as a property of a subclass of NSManagedObject:
#interface MyManagedObject: NSManagedObject {
}
#property(retain, nonatomic) UIImage* thumbnail;
#property(retain, nonatomic) NSDictionary* thumbnailDictionary;
#end
without adding thumbnail in the CoreData model. Then do the corresponding #synthesize in the .m file. The property added this way to a managed object is just not saved.
If you want to keep unknown number of thumbnails, you can put an NSDictionary (or NSArray) containing UIImage*s.
If you really do want to get CFType from an NSObject*, do the following:
NSObject* x= ...;
CFTypeID typeID=CFGetTypeID((CFTypeRef)x);
if(typeID != CFStringGetTypeID() &&
typeID != CFDateGetTypeID() &&
typeID != CFDataGetTypeID() &&
typeID != CFDictionaryGetTypeID() &&
typeID != CFArrayGetTypeID() ) {
... it's a non-plistable type ...
}
The ugly if clause is there because there's no public function which gives you the CFTypeID of an un-bridged Objective-C object.
Read CFType reference. But I don't recommend doing this.

Trying to store an image in UIImage format also results in "property lists cannot contain objects of type CFType error. Best way is to convert UIImage to NSData before writing to pList.

Related

valueForKeyPath failing with nested NSMutableDictionary objects

On my class I have two data objects: 'dataDict' (property) and 'prevDict' (instance variable) which are both NSMutableDictionary objects. In these multi-level dictionaries all the 'nodes' are themselves NSMutableDictionary objects and all the 'leaves' are NSString values.
Most of the time, these dictionaries will compare to be the same. But, when the new incoming data is different, I want to capture those changes via KVO and then save the new incoming dictionary. The recursive calls are the thing that fires off all the KVO changes.
Below is the code for recursively determining if the new incoming data (in property 'dataDict') is different than what's in the 'prevDictionary'. The initial call is:
[self postChangesFromPrevDict:prevDict usingKeyPath:#"dataDict"];
prevDict = [[NSMutableDictionary alloc] initWithDictionary:self.dataDict copyItems:YES];
If there's a change on a given leaf, it should update that in the dictionary and produce a KVO notification.
A sample 'newPath' string value could be "dataDict.group.name.points" -- in which the 'key' value is "points".
In the debugger breakpoint noted, I can see the values of 'curStr' and 'newStr' as, say, "120" and "121" with the these two values being correctly obtained from prevDict and dataDict, respectively.
If I obtain a value from a given keyPath then set it again using that SAME key path, why am I getting an NSUnknownKeyException?
I do know that all my dictionaries involved are mutable.
-(void) postChangesFromPrevDict:(NSMutableDictionary *)prevDictionary
usingKeyPath:(NSString *)keyPath
{
NSArray *keys = [prevDictionary allKeys];
for (NSString *key in keys) {
id obj = [prevDictionary objectForKey:key];
NSString *newPath = [keyPath stringByAppendingString:[NSString stringWithFormat:#".%#",key]];
if ([obj isKindOfClass:[NSDictionary class]])
[self postChangesFromPrevDict:obj usingKeyPath:newPath];
else {
NSString *curStr = obj;
NSString *newStr = [self valueForKeyPath:newPath];
//debug breakpoint
if (![newStr isEqualToString:curStr]) {
#try {
[self setValue:newStr forKeyPath:newPath];
}
#catch (NSException *__unused exception) {
NSLog(#"Can't set key on %#",newPath);
}
}
}
}
}
PS: To recurse, I test for an NSDictionary class which is fine as NSMutableDictionary is a subclass thereof.

How to store objects in plist

I have some CGPoints that I need to store in a NSDictionary then write to a file. Later, I need to be able to load the file into a NSDictionary and access the CGPoint within.
self.dict is the NSDictionary I want to store points in.
- (void)setPoint:(CGPoint)point forKey:(NSString *)key {
NSValue *value = [NSValue valueWithCGPoint:point];
[self.dict setValue:value forKey:key];
}
I also want the information to be encrypted. So I convert the NSDictionary to NSData to encrypt it.
- (void)encryptDictionaryWithKey:(NSData *)key writeToFile:(NSString *)file {
NSData *encryptedDict = [[NSKeyedArchiver archivedDataWithRootObject:self] encryptWithKey:key];
[encryptedDict writeToFile:file atomically:YES];
}
Then to get the information from the file, decrypt it, and put it in NSDictionary form:
+ (NSDictionary *)dictionaryWithContentsOfEncryptedData:(NSData *)data decryptWithKey:(NSData *)key {
NSData *decryptedData = [data decryptedWithKey:key];
return (NSDictionary *)[NSKeyedUnarchiver unarchiveObjectWithData:decryptedData];
}
I can put some other values (like NSNumber) into the NSDictionary, encrypt it and write it to file, then get it from file and decrypt it... and the value is still in tact. So my code seems to be fine. But it won't work with NSValue.
I use this to get CGPoint from NSValue. At this point, self.plist may have been (but not necessarily) encrypted, written to file, then set to an unencrypted version of the file.
- (CGPoint)pointForKey:(NSString *)key {
NSValue *value = [self.prefs objectForKey:key];
return [value CGPointValue];
}
This code only returns 0,0 (and value == nil) if self.plist has been encrypted, written to file, then loaded from the file and unencrypted.
So the NSValue with CGPoint seems to be set to nil during the process of writing to the file. I have no idea what I did wrong, so any help is appreciated . Thanks!
You can convert the CGPoint into an object that can be stored in a plist. For example, the
CGPointCreateDictionaryRepresentation() function will convert a CGPoint into an NSDictionary (or rather, a CFDictionaryRef which can be cast to an NSDictionary). You can store that in the plist, and then convert it back to a CGPoint using the CGPointMakeWithDictionaryRepresentation() companion function when you are loading the plist.

What its __NSDictionaryM. Why NSStringFromClass don't return NSMutableDictionary?

I'm getting something weird that I can't get. I'm using a thirdy-party control (https://github.com/brunow/TableKit.m) and it do:
+ (NSSet *)cellMappingsForObject:(id)object mappings:(NSDictionary *)mappings {
NSString *objectStringName = NSStringFromClass([object class]);
return [mappings objectForKey:objectStringName];
}
It's create a mapping based in the name of the class:
[TKCellMapping mappingForObjectClass:[NSMutableDictionary class] block:^(TKCellMapping *cellMapping) {
//CODE
}];
And it check the datasource objects against it mappings. The objects are build for me as:
while ([rs next]) {
NSMutableDictionary *dict = [NSMutableDictionary dictionary];
for (NSString *fieldName in [props allKeys]) {
//CODE
[dict setValue:fieldValue forKey:fieldName];
}
[list addObject :dict]; //<--This is the datasource. The map is against each dict
}
So, everywhere I'm telling it is NSMutableDictionary. But then this weird thing happend.
When it calls NSStringFromClass([NSMutableDictionary class]) it say NSMutableDictionary but when it calls NSStringFromClass([rowDict class]) it say __NSDictionaryM!
Why happend this? I check for the header of __NSDictionaryM and Xcode navigate to NSMutableDictionary (?). I can't create objects from __NSDictionaryM.
BTW, what I need to do for replace NSStringFromClass to get exactly NSMutableDictionary?
It's a private subclass of NSMutableDictionary. It is an NSMutableDictionary. Read more about class clusters here.
i've done it with a subclass check:
if( [[o class] isSubclassOfClass:[NSMutableDictionary class]] ) {
// o is at least a subclass of NSMutableDictionary
}

iOS - Best way to find object inverse

I'm writing a recursive method that basically traverses a NSManagedObject object and converts it to a JSON dictionary. I have the bulk of this done, but I am running into an issue where the method goes into an infinite loop when it comes to an object's inverse.
For example, let's say the method starts off with an object that has a class of Job, and inside of that Job object there is a property called surveys. The surveys is an NSSet that contains multiple JobSurvey objects. Each JobSurvey object contains an inverse back to the original Job object and the property is called "Job".
When I run this through my method it starts the infinite loop by going into the job and starts to process each property. Once the method gets to the surveys property, it'll be called again to process each JobSurvey object as expected. The method then processes each property until it reaches the Job (inverse) object. At that time it'll continue to process that object, and thus creates the infinite loop.
Any thoughts on how I can fix this? I'm trying to write this method without having to create custom object classes with object mapping as it needs to be able to be used with any type of object I pass into it. Below is the code I have thus far.
- (NSDictionary *)encodeObjectsForJSON:(id)object
{
NSMutableDictionary *returnDictionary = [[NSMutableDictionary alloc] init];
// get the property list for the object
NSDictionary *props = [VS_PropertyUtilities classPropsFor:[object class]];
for (NSString *key in props) {
// get the value for the property from the object
id value = [object valueForKey:key];
// if the value is just null, then set a NSNull object
if (!value) {
NSNull *nullObj = [[NSNull alloc] init];
[returnDictionary setObject:nullObj forKey:key];
// if the value is an array or set, then iterate through the array or set and call this method again to encode it's values
} else if ([value isKindOfClass:[NSArray class]] || [value isKindOfClass:[NSSet class]]) {
NSMutableArray *retDicts = [[NSMutableArray alloc] init];
// encode each member of the array to a JSON dictionary
for (id val in value) {
[retDicts addObject:[self encodeObjectsForJSON:val]];
}
// add to the return dictionary
[returnDictionary setObject:retDicts forKey:key];
// else if this is a foundation object, then set it to the dictionary
} else if ([value isKindOfClass:[NSString class]] || [value isKindOfClass:[NSNumber class]] || [value isKindOfClass:[NSNull class]] || [value isKindOfClass:[NSDate class]]) {
[returnDictionary setObject:value forKey:key];
// else this must be a custom object, so call this method again with the value to try and encode it
} else {
NSDictionary *retDict = [self encodeObjectsForJSON:value ];
[returnDictionary setObject:retDict forKey:key];
}
}
return returnDictionary;
}
This is what I did. It is similar to what you are trying to achieve.
link to da codes
It is a bit more explicit than i think you are looking for but I thought I'd suggest.

iOS NSDictionary description with members' type

I've a NSDictionary that contain non omogeneus stuff. I need to understand what types of memebers there are into.
With the [dict description] I understand the values into, but not the type.
There is a way to do that?
Thanks
You can ask each object for it's class (type) and then check if it's the type you want, using NSObject's -isKindOfClass: method. Note that if your object is, say, an NSMutableArray this would return true for [myMutableArray isKindOfClass:[NSArray class]]
Example:
id object = [myDict objectForKey:aKey];
if ([object isKindOfClass:[NSString class]])
// It's an NSString!
Source: developer.apple.com