iPhone Development: nil userInfo when receiving a notification - iphone

I post the notifications like this in an operation:
DownloadStatus * status = [[DownloadStatus alloc] init];
[status setMessage: #"Download started"];
[status setStarted];
[status setCompleteSize: [filesize intValue]];
[userInfo setValue:status forKey:#"state"];
[[NSNotificationCenter defaultCenter]
postNotificationName:[targetURL absoluteString]
object:nil userInfo:userInfo];
[status release];
DownloadStatus is an object that contains some information abou the download that is being currently downloaded. userInfo is a property of the object that has been initialized in the init part and is kept for the complete duration of the operation. It is created so:
NSDictionary * userInfo = [NSDictionary dictionaryWithObject:targetURL
forKey:#"state"];
"targetURL" is a NSString, I use this just to make sure everything is working fine. When I receive the event - I registered like this:
[[NSNotificationCenter defaultCenter]
addObserver:self selector:#selector(downloadStatusUpdate:)
name:videoUrl
object:nil];
Here "videoUrl" is a string that contains the url being downloaded, so that I will receive notification about an url I'm waiting to see downloaded.
The selector is implemented like this:
- (void) downloadStatusUpdate:(NSNotification*) note {
NSDictionary * ui = note.userInfo; // Tried also [note userInfo]
if ( ui == nil ) {
DLog(#"Received an update message without userInfo!");
return;
}
DownloadStatus * state = [[ui allValues] objectAtIndex:0];
if ( state == nil ) {
DLog(#"Received notification without state!");
return;
}
DLog(#"Status message: %#", state.message);
[state release], state = nil;
[ui release], ui = nil; }
But this selector always receives a null userInfo. What am I doing wrong?
MrWHO

One way or another, you seem to be initialising your userInfo object incorrectly. The line as given:
NSDictionary * userInfo = [NSDictionary dictionaryWithObject:targetURL
forKey:#"state"];
Would create an autoreleased NSDictionary and store it to a local variable. The value would not be propagated up to your member variable.
Supposing that's a snippet, followed by e.g.
self.userInfo = userInfo;
to assign the local to the member, retaining it at the same time, then your code should generate an exception at this line:
[userInfo setValue:status forKey:#"state"];
Since it attempts to mutate an immutable object. It's therefore much more likely that the value of userInfo isn't stored and you're messaging nil at that point.
So, I would think that — assuming you have userInfo declared as a 'retain' type property, you want to replace:
NSDictionary * userInfo = [NSDictionary dictionaryWithObject:targetURL
forKey:#"state"];
With:
self.userInfo = [NSMutableDictionary dictionaryWithObject:targetURL
forKey:#"state"];

Related

iOS instance variables not initialised from within a block

i am using parse.com as backend for my app.
i need to get information from my backend and init an instance with this information.
i use this code in order to do so:
- (id) initWithTeamId:(NSString *)teamId
{
__block NSString *str;
__block FrFTeam *blockSelf = self;
PFQuery *query = [PFQuery queryWithClassName:#"teams"];
[query getObjectInBackgroundWithId:teamId block:^(PFObject *object, NSError *error) {
str = [object objectForKey:#"teamName"];
(void)[blockSelf initWithName:str players:nil thumb:nil];
}];
return self;
}
when this code is done self.name is set to null,
what am i doing wrong?
thank you!
Try this code:
// call init on the object, then setup the team id
- (id)initWithTeamId:(NSString *)teamId
{
self = [super init];
if (self) {
[self setupWithTeamId:teamId];
}
return self;
}
- (void) setupWithTeamId:(NSString *)teamId
{
__weak FrFTeam *blockSelf = self;
PFQuery *query = [PFQuery queryWithClassName:#"teams"];
[query getObjectInBackgroundWithId:teamId block:^(PFObject *object, NSError *error) {
NSString *name = [object objectForKey:#"teamName"];
NSLog(#"Received name: %# from object: %#", name, object);
[blockSelf setName:name];
}];
}
Then, change the name of the method from initWithName:... because this isn't really an init method because you have already done the init before calling setupWithTeamId:.
If you need the parse bit to be done before the init method returns, you should:
Call parse to get the details before calling init on the object
Use getObjectWithId: --- not recommended as this blocks the thread in init, bad idea
Pretty sure the reason is in the method name you are calling -getObjectInBackgroundWithId:block: (it specifies InBackground, which suggests the block is called at some later stage and not immediately)
This would suggest that you end up with self == nil (as you are not calling any other initialiser in the method.
Initialisation of an object has to be synchronous.

iOS parsing JSON from static library returning null

I'm trying to test out using static libraries, and am calling this method (which is in the static library)
-(NSMutableDictionary *)parseJSONfromURL:(NSURL *)url{
__strong NSMutableDictionary *json;
[self.delegate isParsing:(url != nil)];
if (url == nil) {
[NSException raise:NSArgumentDomain format:#"The passed url argument cannot be nil"];
}
NSError *err;
json = [NSJSONSerialization JSONObjectWithData:[NSData dataWithContentsOfURL:url] options:NSJSONReadingMutableLeaves error:&err];
if (err) {
[json setObject:err forKey:#"error"];
}
while (json == nil) {
NSLog(#"waiting...");
}
[self.delegate isParsing:NO];
[self.delegate didFinishParsing:(json != nil)];
return json;
}
I would expect the while loop to be infinite since json is returning null, but the delegate method didFinishParsing gets sent, meaning it isn't null.
like this (ACParser is a class in the library)
ACParser *p = [[ACParser alloc] initWithDelegate:self];
dictionary = [p parseJSONfromURL:[NSURL URLWithString:#"http://www.a-cstudios.com/text.json"]];
dictionary is declared like this
__strong NSMutableDictionary *dictionary;
the JSON at that URL is very simple
{
"text" : "testing"
}
however, every time this is called, in the delegate method didFinishParsing:, logging dictionary returns (null). What am I doing wrong here? Is it because I'm calling it from a static library?
Try adding this:
NSLog(#"%#", [NSJSONSerialization JSONObjectWithData:[NSData dataWithContentsOfURL:[NSURL URLWithString:url]] options:NSJSONReadingAllowFragments error:nil]);
in there and see if it prints out your expected JSON. If it doesnt, your URL is wrong. If it does, then your data isnt being retained. Try instantiating your json variable like this:
NSMutableDictionary *json = [NSMutableDictionary dictionaryWithDictionary:[NSJSONSerialization JSONObjectWithData:[NSData dataWithContentsOfURL:url] options:NSJSONReadingMutableLeaves error:&err]];
"json" within "parseJSONfromURL" is autoreleased / out of scope as soon as that method returns so it never has a chance to get assigned to your strong "dictionary" property.
That's why you are seeing NULL.
Try setting the property within your parseJSONfromURL method and see if that works, or create a non-autoreleased dictionary and return that.

Struggling to get the Dictionary passed with NSNotification

I received the notification so I handle it like so
-(void) dateSelected:(NSNotification *) notification
{
NSLog(#"Value: %#", [[notification userInfo] valueForKey:#"date"] );
NSMutableDictionary * dateDict = [[NSDictionary alloc] initWithDictionary:[notification userInfo]];
NSLog(#"The Date Dict: %#", dateDict );
}
The logs I get are
2012-07-20 11:32: TestApp[10701:40b] Value: (null)
2012-07-20 11:32: TestApp[10428:40b] The Date Dict: {
}
If I NSLog out the notification itself it looks valid:
2012-07-20 11:33: TestApp[10457:40b] Notification: NSConcreteNotification 0x16629460 {name = date_selected_; object = {
date = 20120705;
}}
I've done this before and its worked.
I'm sure its simple, but today I cannot see the issue.
Any help would be appreciated.
Thanks,
-Code
It's pretty simple, look at your log output ... There's no userInfo set in your notification. There's just name and object. Compare your output with this one ...
NSNotification *notification = [NSNotification notificationWithName:#"NAME"
object:self
userInfo:[NSDictionary dictionaryWithObjectsAndKeys:#"obj", #"key", nil]];
NSLog( #"NOT: %#", notification );
...
NOT: NSConcreteNotification 0x73586f0 {name = NAME;
object = <CMAppDelegate: 0x884a4e0>; userInfo = {
key = obj;
}}
... see the difference? There's name, object, but also userInfo in log output.
So the answer is - your notification does not contain userInfo dictionary. Look at code which fires this notification.

Pass object with NSNotificationCenter to other view

I am trying to pass an object from my main view class to other notification receiver in another class.
I want to pass an object named country, that loads all the cities from an SOAP Request in the Main Controller and i want to send it to my next view.
country = [[Country alloc] init];
Country header:
#interface Country : NSObject
{
NSString *name;
NSMutableArray *cities;
}
#property (nonatomic,retain) NSString *name;
- (void)addCity:(Cities *)city;
- (NSArray *)getCities;
- (int)citiesCount;
#end
I found a way to pass data with NSNotificatios is using a NSDictionary in UserInfo. But its not possible to send the whole object instead of converting to an NSDictionary? Or what's the best way to transfer it? Im stuck trying to figure out how to pass the objects.
Actually i got working this simple NSNotification on my App.
NSNotification in the Main View Controller implementation:
//---Call the next View---
DetailViewController *detail = [self.storyboardinstantiateViewControllerWithIdentifier:#"Detail"];
[self.navigationController pushViewController:detail animated:YES];
//--Transfer Data to2View
[[NSNotificationCenter defaultCenter] postNotificationName:#"citiesListComplete" object:nil];
NSNotification in 2View Controller implementation:
// Check if MSG is RECEIVE
- (void)checkMSG:(NSNotification *)note {
NSLog(#"Received Notification");
}
- (void)viewDidLoad
{
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(checkMSG:)
name:#"citiesListComplete" object:nil];
Oooooo, so close. I have a feeling you do not understand what an NSDictionary is though.
Post your notification with this:
Country *country = [[[Country alloc] init] autorelease];
//Populate the country object however you want
NSDictionary *dictionary = [NSDictionary dictionaryWithObject:country forKey:#"Country"];
[[NSNotificationCenter defaultCenter] postNotificationName:#"citiesListComplete" object:nil userInfo:dictionary];
then get the country object like this:
- (void)checkMSG:(NSNotification *)note {
Country *country = [[note userInfo] valueForKey:#"Country"];
NSLog(#"Received Notification - Country = %#", country);
}
You don't need to convert your object into a NSDictionary. Instead, you need to send a NSDictionary with your object. This allows you to send lots of information, all based on keys in the NSDictionary, with your NSNotification.
For Swift
You can pass dictionary with using the below code
NSNotificationCenter.defaultCenter().postNotificationName(aName: String, object anObject: AnyObject?, userInfo aUserInfo: [NSObject : AnyObject]?)
for example
NSNotificationCenter.defaultCenter().postNotificationName("OrderCancelled", object: nil, userInfo: ["success":true])
And read this dictionary from
func updated(notification: NSNotification){
notification.userInfo?["success"] as! Bool
}

Objective-C static field issue

I've created a small class that loads dictionary items from a plist file. The getSettingForKey method works the first time I call the static method, however after a few more calls the dictionary throws a SIGABRT exception for a call with the same key that worked on a previous call. Any ideas?
static NSDictionary *dictionary = nil;
static NSLock *dictionaryLock;
#implementation ApplicationSettingsHelper
+ (void) initialize
{
dictionaryLock = [[NSLock alloc] init];
// Read plist from application bundle.
NSString *path = [[NSBundle mainBundle] bundlePath];
NSString *finalPath = [path stringByAppendingPathComponent:#"Xxxx.plist"];
dictionary = [NSDictionary dictionaryWithContentsOfFile:finalPath];
// dump the contents of the dictionary to the console.
for(id key in dictionary)
{
NSLog(#"bundle: key=%#, value=%#", key, [dictionary objectForKey:key]);
}
}
+ (NSDictionary *)dictionaryItems
{
[dictionaryLock lock];
if (dictionary == nil)
{
[self initialize];
}
[dictionaryLock unlock];
return dictionary;
}
+(id)getSettingForKey:(NSString *)key
{
return [[self dictionaryItems] objectForKey:key];
}
#end
Moshe - I've taken your suggestion and updated to use NSUserDefaults instead:
+ (void)load
{
// Load the default values for the user defaults
NSString* pathToUserDefaultsValues = [[NSBundle mainBundle]
pathForResource:#"Xxxx"
ofType:#"plist"];
NSDictionary* userDefaultsValues = [NSDictionary dictionaryWithContentsOfFile:pathToUserDefaultsValues];
// Set them in the standard user defaults
[[NSUserDefaults standardUserDefaults] registerDefaults:userDefaultsValues];
}
+ (id)getSettingForKey:(NSString *)key
{
return [[NSUserDefaults standardUserDefaults] valueForKey:key];
}
Your dictionary has probably been deallocated, causing an invalid memory access. When you create a dictionary using the dictionaryWithContentsOfFile: method, it is autoreleased, which means it will automatically be released in the future. Since you never retain the dictionary, that release will cause the dictionary to be deallocated.
Also, most of your dictionaryItems method is useless.
[dictionaryLock lock];
if (dictionary == nil) {
[self initialize];
}
[dictionaryLock unlock];
The +initialize method is automatically called by the runtime before any other method is called on your class, unless you have a +load method. Since the runtime will call it for you and it will attempt to create the dictionary, the dictionary can only be nil in the dictionaryItems method if there wasn't enough memory to create it, in which case it will fail again. Also, if you don't use the lock anywhere else, it is unnecessary also, since removing that check would cause it to be locked and immediately unlocked. Therefore, you can remove the lock and change your dictionaryItems method to simply:
+ (NSDictionary *)dictionaryItems {
return dictionary;
}
In addition to #ughoavgfhw's answer, you are also initializing dictionaryLock after you are locking it. Unless you are initializing dictionaryLock somewhere else, I'm surprised your code is getting as far as it is.
Edit: I see from #ughoavgfhw's edit that +initialize is called before anything else, so your lock is initialized there.