NSDictionary, CustomKeys, Comparison - ios

SOLVED
It seemed that both -referenceDictionary were not containing exact same information… Dang it ! Fixed it and now it works perfectly.
Initial question
It's been a few hours since I started struggling with this problem, here it is :
I have a CustomClassA which I use as NSDictionary key (obviously for the Object:ForKey: method).
At first it did not work at all, then I read this, that and a few other stuffs around the web…
So I tried overriding both -isEqual: and -hash by doing as said (maybe a little less :p) :
- (BOOL)isEqual:(id)anObject
{
if([anObject isKindOfClass:[self class]])
{
NSLog(#"HASH = %#", [anObject hash] == [self hash] ? #"YES" : #"NO");
NSLog(#"DICT = %#", [[self referenceDictionary] isEqualToDictionary:[anObject referenceDictionary]] ? #"YES" : #"NO");
return [[self referenceDictionary] isEqualToDictionary:[anObject referenceDictionary]];
}
return false;
}
- (NSUInteger)hash
{
NSUInteger prime = 31;
NSUInteger result = 1;
NSArray* values = [referenceDictionary allValues];
for(id k in values)
{
result = prime * result + [k hash];
}
return result;
}
Please, note that my NSDictionary will never contain nil values because I do take care of having these away.
Anyway, both of these methods do return YES on one of the NSDictionary entry when calling :[myDict objectForKey:myObject].
But still, it seems [myDict objectForKey:myObject] returns nil, even after "setting" it myself…
If anyone has time to help me solving my pb I'd be thankful !
Regards,
Cehm
EDIT:
Ok, for the purpose of being a bit more clear about this issue. Lets assume two classes : Artist and Single.
Then I want to create a NSDictionary which contains an Artist as the key and this Artist key points to a NSArray of "Single"s.
For instance I'd have the following dictionary :
#{
artistA : #[song1, song2, song3],
artistB : #[songZ],
artistC : #[songF]
}
But now the thing is I want to create this dictionary like this :
for(Single* aSong in ArrayOfSingle)
{
Artist* anArtist = [[Artist alloc] initWithArtistName:aSong.artistName];
if([data objectForKey: anArtist] == nil)
{
[data setObject:[[NSMutableArray alloc] init] forKey: anArtist];
} else
{
NSLog(#"exists");
}
[[data objectForKey: anArtist] addObject:aSong];
}
Taken into account that data is mutable (Simple NSMutableDictionary) and that I won't ever change anArtist (assuming ARC still keeps its instance…).
EDIT 2:
I overrode NSCopying :
- (id)copyWithZone:(NSZone *)zone {
return [[[self class] allocWithZone:zone] initWithDictionary:referenceDictionary];
}
As my CustomClass uses initWithDictionary: I assumed it would work just fine by doing this.
EDIT 3:
Here's the initWithDictionary:
-(id) initWithDictionary:(NSDictionary*) dictionary
{
self = [super initWithDictionary:dictionary];
if(self)
{
referenceDictionary = dictionary;
title = [super notNullObjectForKey:#"title"];
name = [super notNullObjectForKey:#"name"];
category = [super notNullObjectForKey:#"category"];
artist = [super notNullObjectForKey:#"artist"];
ad_city = [super notNullObjectForKey:#"ad_city"];
}
return self;
}

Related

Recursive method Objective-C

I'm checking is first letter of string is 0, if it is remove it and call again method to check is there is still 0. I've debugged this and it seems like when it accomplish number without 0, it goes backwards. Code:
-(NSString *)deleteZerosOnFirst:(NSString *)card
{
NSString *firstLetter = [card substringToIndex:1];
if ([firstLetter isEqualToString:#"0"]) {
card = [card substringFromIndex:1];
[self deleteZerosOnFirst:card];
NSLog(#"CARD: %#", card);
return card;
}
else {
NSLog(#"CARD: %#", card);
return card;
}
}
The main problem is that you're not using the result of the recursion. The line of code where you call yourself should say this:
card = [self deleteZerosOnFirst:card];
Also, you're calling deleteZerosOnFirst before you do the NSLog. Reverse the order of these two lines. That will at least give you your debug output in the right sequence.
Here's your recursive call:
[self deleteZerosOnFirst:card];
That doesn't modify the string that card references. It creates and returns a new string. You're ignoring the returned string. You want this:
card = [self deleteZerosOnFirst:card];
But this is really a lot simpler:
#implementation NSString (withoutLeadingZeroes)
- (NSString *)withoutLeadingZeroes {
NSString *s = self;
while ([s hasPrefix:#"0"]) {
s = [s substringFromIndex:1];
}
return s;
}
#end

Any ideas what I am doing wrong when trying to store json from webservice into NSDictionary

I am using webservice to pull out some information in json format. I am able to view them in my app. However when I try to store them in dictionary i keep getting error saying value "courseId" not compliant. This is what I have
NSMutableArray *results = [NSMutableArray array];
for (id courseDictionary in response) {
ListOfCourses *course = [[ListOfCourses alloc] initWithDictionary:courseDictionary];
[results addObject:course];
}
and in my ListOfCourses.m I have
- (id)initWithDictionary:(NSDictionary *)dictionary
{
self = [super init];
if (self) {
_courseId = [NSNumber numberWithInt:[[dictionary valueForKey:#"courseId"]
_room1 = [dictionary objectForKey:#"room1"];
_subjectCode = [dictionary objectForKey:#"subjectCode"];
}
return self;
}
json file
{
"Template":{
"Room1":"Day 1 room name/number; string; required; 10 characters",
"SubjectId":"Subject identifier; number; required; Subject identifier must exist",
{
"Links":[
{
"Rel":"self",
"Href":"/api/courses/2"
},
{
"Rel":"collection",
"Href":"/api/courses/"
}
],
"Item":{
"Link":{
"Rel":"self",
"Href":"/api/courses/2"
},
"Id":1,
"SubjectCode":"D9920",
"Room1":"S2153"
}
UPDATED: I am returning a collection of objects, when i do this
_courses = (NSArray *)[response objectForKey:#"Collection"];
I am able to view the objects. I am trying to put them each individually into dictionary now
When you are taking entities on and off a stream, you should use the NSCoding protocol. There are many ways to do that with JSON, e.g. JSONCoding.
These types of problems would qualify as refactor smells: you should not have to worry about how something was bundled up.
Change your line of code from:
_courseId = [NSNumber numberWithInt:[[dictionary valueForKey:#"courseId"]
to:
_courseId = [NSNumber numberWithInt[[dictionary valueForKey:#"Id"] intValue]];
If this won't help please post the json file.
//EDITED
You use [dictionary valueForKey:#"courseId"] but the key is Id, please change courseId to Id.
//EXTENDED
You have dictionary embedded in dictionary try this:
NSDictionary *dic = dictionary[#"Item"];
_courseId = [NSNumber numberWithInt:[dic[#"Id"] intValue]];
_room1 = dict[#"Room1"];
_subjectCode = dict[#"SubjectCode"];
If it doesn't work you have to post beginning of your json file.

Pattern for determining if object is fully or partially loaded

The question does not strictly tied with iOS, but since I encountered this in iOS app I will speak in terms of Objective-C.
My iOS app is a client, which gets some data from server. The data from server is json, and it is mapped with my classes. The problem appears when the server send only the necessary part of the object.
Lets say the full object is
{
"a" : 1,
"b" : 2,
"c" : 3
}
My class to which it is mapped is
#class MyObject
{
int a, b, c;
}
#property (nonatomic) int a, b, c;
-(id) initFromDictionary:(NSDictionary*)dict
#end
#implementation MyObject
-(id) initFromDictionary:(NSDictionary*)dict
{
self = [super init];
if (self)
{
a = [dict[#"a"] intValue];
b = [dict[#"b"] intValue];
c = [dict[#"c"] intValue];
}
return self;
}
#end
The server can send
{
"a" : 1,
"c" : 3
}
for request getAandC and
{
"a" : 1,
"b" : 2
}
for another - getAandB (these requests are not dependent, the only thing they are similar is the object they use). I do not need any information about b in the first one and about c in the second one.
The problem is the following. When I write code for these requests I surely know which fields are returned and do not use empty field, but after some time I may forget about which request returned partial object or full, and try to use empty field. So there can be a lot of errors, which could be hard to find.
Is there any practices for that case or maybe some pattern for determining if the object is fully or partially loaded and warning the developer about it?
You Can Implement it as :
#implementation MyObject
-(id) initFromDictionary:(NSDictionary*)dict
{
self = [super init];
if (self)
{
a = ([dict objectForKey: #"a"]) ? [[dict objectForKey: #"a"] intValue] : 0;
b = ([dict objectForKey: #"b"]) ? [[dict objectForKey: #"b"] intValue] : 0;
c = ([dict objectForKey: #"c"]) ? [[dict objectForKey: #"c"] intValue] : 0;
// Here you can replace 0 with nil in case if variable a , b , c
are object type i.e. (id) type or You can use default value here as per your convenience so that you can easily track it if it is partially or fully Loaded
if ((a == 0) || (b == 0) || (c == 0))
{
NSLog(#"object is Partially loaded with values a : %d , b : %d , c : %d", a,b,c);
}else{
NSLog(#"object is Completely loaded with values a : %d , b : %d , c : %d", a,b,c);
}
}
return self;
}
#end
Or
#implementation MyObject
-(id) initFromDictionary:(NSDictionary*)dict
{
self = [super init];
if (self)
{
NSArray *keys = [dict AllKeys];
for(NSString * key in keys)
{
[self setValueToItsVariableForKey:key fromDictionary: dict];
}
}
return self;
}
- (void)setValueToItsVariableForKey:(NSString *)key fromDictionary: (NSDictionary *)dict
{
switch ([key intValue])
{
case : a
a = [[dict objectForKey: key] intValue];
break;
case : b
b = [[dict objectForKey: key] intValue];
break;
case : c
c = [[dict objectForKey: key] intValue];
break;
}
}
#end
I would approach this using the KVO pattern. What you can do is setup a counter where you increase the count by adding a KVO on the property that you want to observe. You can then set a observer on the counter and if the conter has reached the predetermined count (of all properties to be loaded) you can consider the object to be fully loaded.

NSMutableDictionary remove object at key path?

I've got a layered NSMutableDictionary object and i'd like to be able to remove dictionaries deeper down in the hierarchy. Is there a quick and easy way to do this, for example, a removeObjectAtKeyPath-like method? Can't seem to find one.
Thanks!
Nothing built in, but your basic category method will do just fine:
#implementation NSMutableDictionary (WSSNestedMutableDictionaries)
- (void)WSSRemoveObjectForKeyPath: (NSString *)keyPath
{
// Separate the key path
NSArray * keyPathElements = [keyPath componentsSeparatedByString:#"."];
// Drop the last element and rejoin the path
NSUInteger numElements = [keyPathElements count];
NSString * keyPathHead = [[keyPathElements subarrayWithRange:(NSRange){0, numElements - 1}] componentsJoinedByString:#"."];
// Get the mutable dictionary represented by the path minus that last element
NSMutableDictionary * tailContainer = [self valueForKeyPath:keyPathHead];
// Remove the object represented by the last element
[tailContainer removeObjectForKey:[keyPathElements lastObject]];
}
#end
N.B. That this requires that the second-to-last element of the path -- the tailContainer be something that responds to removeObjectForKey:, probably another NSMutableDictionary. If it's not, boom!
You can create a category :
This is upto 1 level down:
#import "NSMutableDictionary+RemoveAtKeyPath.h"
#implementation NSMutableDictionary (RemoveAtKeyPath)
-(void)removeObjectAtKeyPath:(NSString *)keyPath{
NSArray *paths=[keyPath componentsSeparatedByString:#"."];
[[self objectForKey:paths[0]] removeObjectForKey:paths[1]];
}
#end
It is called as :
NSMutableDictionary *adict=[[NSMutableDictionary alloc]initWithDictionary:#{#"key1" : #"obj1", #"key11":#"obj11"}];
NSMutableDictionary *bdict=[[NSMutableDictionary alloc]initWithDictionary:#{#"key2" : adict}];
NSLog(#"%#",bdict);
NSLog(#"%#",[bdict valueForKeyPath:#"key2.key1"]);
[bdict removeObjectAtKeyPath:#"key2.key1"];
NSLog(#"After category : %#",bdict);
Minor improvement to Josh's answer, in order to handle keypaths which don't contain a period (i.e. keypaths which are actually keys):
- (void)removeObjectAtKeyPath:(NSString *)keyPath
{
NSArray *keyPathElements = [keyPath componentsSeparatedByString:#"."];
NSUInteger numElements = [keyPathElements count];
if (numElements == 1) {
[self removeObjectForKey:keyPath];
} else {
NSString *keyPathHead = [[keyPathElements subarrayWithRange:(NSRange){0, numElements - 1}] componentsJoinedByString:#"."];
NSMutableDictionary *tailContainer = [self valueForKeyPath:keyPathHead];
[tailContainer removeObjectForKey:[keyPathElements lastObject]];
}
}
I know this is an older post, but I needed to find the same solution in Swift 2.0, unable to find a simple answer I came up with this solution:
public extension NSMutableDictionary {
public func removeObjectAtKeyPath(keyPath:String) {
let elements = keyPath.componentsSeparatedByString(".")
let head = elements.first!
if elements.count > 1 {
let tail = elements[1...elements.count-1].joinWithSeparator(".")
if let d = valueForKeyPath(head) as? NSMutableDictionary {
d.removeObjectAtKeyPath(tail)
}
}else{
removeObjectForKey(keyPath)
}
}
}
I've added an extension to NSMutableDictionary using recursion to step thru the keyPath
I just iterate on jscs's accepted answer with minor improvement - working on the KVC key-path using NSString's built-in pathUtlities category.
#implementation NSMutableDictionary (NestedMutableDictionaries)
- (void)removeObjectForKeyPath: (NSString *)keyPath
{
NSArray *key = [keyPath pathExtension];
NSString *keyPathHead = [keyPath stringByDeletingPathExtension];
[[self valueForKeyPath:keyPathHead] removeObjectForKey:key];
}
#end
Just a little nicer, I think.

Instance method '-addOperation:waitUntilFinished:' not found (return type defaults to 'id')

is addOperation:waitUntilFinished available only for iOS 4.3 and above?
Why do I get this warning, am I missing out on something?
Even tough I get this warning message my app works and with it?
http://developer.apple.com/library/mac/#documentation/Cocoa/Reference/NSOperationQueue_class/Reference/Reference.html
Edited with some code:
My HttpRequestWrapper Operation class:
in .h:
#interface HttpRequestWrapper : NSOperation
in .m
+ (id)httpRequestWrapper:(NSString *)xmlString withUser : (NSString *) user andPassword: (NSString *) password
{
HttpRequestWrapper * operation = [[self alloc] initWithString:xmlString andUser: user andPass: password];
//return [operation autorelease];
return operation;
}
- (id)initWithString: (NSString*) xmlString andUser: (NSString* )user andPass: (NSString *) pass
{
self = [super init];
if (self == nil)
return nil;
_urlPart = [xmlString copy];
_userString = [user copy];
_passString = [pass copy];
_isExecuting = NO;
_isFinished = NO;
[self main];
return self;
}
My caller line is like this:
httpRequestWrapper = [HttpRequestWrapper httpRequestWrapper:[NSString stringWithFormat:#"/list?xsl="] withUser:NULL andPassword: NULL];
[appDelegate.operationQueue addOperation:httpRequestWrapper waitUntilFinished:YES];
Thanks for the help.
Lily
I can assume that warning can be caused by your call [self main];. Note: the main method is called inside operation queue. I cannot see the reason to call it by yourself.
Or. I see the problem. There is no call like this:
[appDelegate.operationQueue addOperation:httpRequestWrapper waitUntilFinished:YES];
See reference more close:
- (void)addOperations:(NSArray *)ops waitUntilFinished:(BOOL)wait
you should pass the array of operations.
Fix to:
NSArray *opsArray = [NSArray arrayWithObject:httpRequestWrapper];
[appDelegate.operationQueue addOperations:opsArray waitUntilFinished:YES];
The third - check if appDelegate.operationQueue is not nil, i.e. if you created operationQueue before.

Resources