Pass object with NSNotificationCenter to other view - iphone

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
}

Related

Objective C NSMutableDictionary memory management

I have a model class that keeps track record being built by multiple views. It has a NSMutableDictionary that has the fields and values I eventually write to the database. It is saved to a plist and loaded back when needed. I thought that I was keeping track of my memory, but it throws a EXC_BAD_ACCESS when I try to release the Dictionary. Here is my interface:
#import <Foundation/Foundation.h>
#interface CurrentEntryModel : NSObject {
NSMutableDictionary *currentEntry;
}
#property (nonatomic, retain) NSMutableDictionary *currentEntry;
- (void) setValue: (NSString *)value;
- (NSString *) getValue;
#end
My understanding is that currentEntry should be retained and I would have to release it during dealloc.
Here is my implementation (this isn't the entire class just the relevant parts):
#import "CurrentEntryModel.h"
#implementation CurrentEntryModel
#synthesize currentEntry;
-(id) init {
if ( self = [super init] )
{
//check for file
NSFileManager *fileManager = [NSFileManager defaultManager];
NSString *file;
file = #"location.plist";
if ([fileManager fileExistsAtPath:file]){
NSLog(#"file exists");
currentEntry = [[NSMutableDictionary alloc] initWithContentsOfFile:file];
}else {
NSLog(#"file doesn't exist");
currentEntry = [[NSMutableDictionary alloc ] initWithCapacity:1];
NSDate *testDate = [NSDate date];
[currentEntry setObject:testDate forKey:#"created"];
[currentEntry writeToFile:file atomically:YES];
}
}
return self;
}
- (void) setValue: (NSString *)value {
[currentEntry setObject:value forKey:#"location"];
}
- (NSString *) getValue {
return [currentEntry objectForKey:#"location"];
}
- (void) dealloc{
[currentEntry release];
[super dealloc];
}
#end
If I init this class it will automatically create the dictionary and if I call one of the set or get methods it seems like the dictionary is retained as it will dealloc correctly. If the class is just initialized and then no methods are called it will throw the EXC_BAD_ACCESS errors. If I am not mistaken when the file doesn't exist I don't initialize the dictionary correctly because the method starts with dictionary and not init. Although every time I run this the file is there so it always uses the the file found logic and I thought that that will retain the variable.
Am I not initializing the dictionary correctly?
Edit - changed the code on the convenience method to reflect the proper way. Everyone take note of what Squeegy has to say.
This is bad bad bad.
else {
NSLog(#"file doesn't exist");
currentEntry = [[NSMutableDictionary alloc ] dictionaryWithCapacity:1];
dictionaryWithCapacity: is a class method on NSMutableDictionary which returns an autoreleased object, and you don't retain it. So the run loop ends, and the dictionary gets autoreleased. Then you run [currentEntry release] in your dealloc and it explodes because that object is deallocated already.
you probably wan't initWithCapacity: instead. Always pair alloc with a method that starts with init.
Also, when using retained properties like this, I usually let the property figure this out for me, and only work with autoreleased objects. You just have to remember less rules, and there are less gotchas.
- (id)init {
// ...
self.currentEntry = [NSMutableDictionary dictionWithContentsOfFile:file];
// ...
}
- (void)dealloc {
//...
self.currentEntry = nil;
//...
}
This way you never have to call retain or release directly on the object. In my experience, this results in less confusing bugs. But it's also point of style among many ObjC programmer that not everyone agrees with.
Joshua -
+ (id)dictionaryWithCapacity:(NSUInteger)numItems
is a class method of NSDictionary. So when you call it, it should be:
[NSMutableDictionary dictionaryWithCapacity:1];
Not:
[[NSMutableDictionary alloc] dictionaryWithCapacity:1];
Further, [NSMutableDictionary dictionaryWithCapacity:] returns an autoreleased object. If you want to keep the dictionary as an ivar and not have it autoreleased on the next cycle of the run loop, you should call:
[currentEntry retain];
So, basically, change it to:
currentEntry = [[NSMutableDictionary alloc] initWithCapacity:1];
or:
currentEntry = [[NSMutableDictionary dictionaryWithCapacity:1] retain];
The first one probably makes more sense, since the connivence class methods were designed to be used when you wanted an autoreleased instance.

How to return an object from a class that uses NSURLConnection and it's delegate classes?

I'm in the process of trying to move code from a UITableViewController class to a "helper" class.
The code utilizes NSURLConnection to grab and parse JSON and then populate an NSMutableArray.
What I'd like to do is call a method in my helper class that returns a NSMutableArray. What I don't understand is how to return the array from the connectionDidFinishLoading delegate class of NSURLConnection (where the array is actually built) as though it was from the originally called method that started the connection. In other words, how does the method that calls NSURLConnection get control back so it can return a value from the whole operation?
Here are the relevant methods from the helper class. How do I get the getMovies method to return the listOfMovies that is built in the connectionDidFinishLoading delegate class?
-(NSMutableArray)getMovies:(NSURL*)url {
responseData = [[NSMutableData data] retain];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
//NSURLRequest* request = [NSURLRequest requestWithURL: url cachePolicy: NSURLRequestUseProtocolCachePolicy timeoutInterval: 30.0];
connection = [[NSURLConnection alloc] initWithRequest:request delegate:self];
}
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response {
[responseData setLength:0];
}
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
[responseData appendData:data];
}
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error {
//TODO error handling for connection
}
- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
//---initialize the array---
listOfMovies = [[NSMutableArray alloc] init];
tmdbMovies = [[NSArray alloc] init];
posters = [[NSArray alloc] init];
thumbs = [[NSDictionary alloc] init];
NSString *responseString = [[NSString alloc] initWithData:responseData encoding:NSUTF8StringEncoding];
SBJsonParser *json = [[SBJsonParser new] autorelease];
tmdbMovies = [json objectWithString:responseString];
// loop through all the top level elements in JSON
for (id movie in tmdbMovies) {
// 0 - Name
// 1 - Meta
// 2 - Url
if ((NSNull *)[movie objectForKey:#"name"] != [NSNull null]) {
if (![[movie objectForKey:#"name"] isEqualToString:#""]) {
name = [movie objectForKey:#"name"];
}
}
if ((NSNull *)[movie objectForKey:#"info"] != [NSNull null]) {
if (![[movie objectForKey:#"info"] isEqualToString:#""]) {
meta = [movie objectForKey:#"info"];
}
}
if ((NSNull *)[movie objectForKey:#"thumb"] != [NSNull null]) {
if (![[movie objectForKey:#"thumb"] isEqualToString:#""]) {
thumbUrl = [movie objectForKey:#"thumb"];
}
}
NSLog(#"Name: %#", name);
NSLog(#"Info: %#", meta);
NSLog(#"Thumb: %#", thumbUrl);
NSMutableArray *movieData = [[NSMutableArray alloc] initWithObjects:name,meta,thumbUrl,nil];
// add movieData array to listOfJMovies array
[listOfMovies addObject:movieData];
[movieData release];
}
//FIXME: Connection warning
if (connection!=nil) {
[connection release];
}
[responseData release];
[responseString release];
}
What you really need to do here is create a #protocol that creates a delegate for your helper class. Then change -(NSMutableArray)getMovies:(NSURL*)url to -(void)getMovies:(NSURL*)url
The class that is calling your helper method needs to implement your helper method's delegate.
Then - (void)connectionDidFinishLoading:(NSURLConnection *)connection calls the delegate method(s). It's best to have a one for success and one for failure.
=Update Begin=
You will need to also define an id delegate in your helper file which the calling class sets to self after init but before calling -(void)getMovies:(NSURL*)url. That way the helper file knows where to call back to.
getMovies *movieListCall = [[getMovies alloc] init];
movieListCall.delegate = self;
[movieListCall getMovies:<your NSURL goes here>];
You will see some additional lines for the inclusion of a delegate in both the getMovies.h and getMovies.m files.
=Update End=
in your getMovies.h file add:
#protocol getMoviesDelegate
#required
- (void)getMoviesSucceeded:(NSMutableArray *)movieArray;
- (void)getMoviesFailed:(NSString *)failedMessage;
#end
#interface getMovies : NSOBject {
id delegate;
}
#property (nonatomic, assign) id delegate;
in your getMovies.m file add:
#synthesize delegate;
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error {
//TODO error handling for connection
if ([delegate respondsToSelector:#selector(getMoviesFailed:)]) {
[delegate getMoviesFailed:[error localizedDescription]];
}
}
- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
//finishes with
if ([delegate respondsToSelector:#selector(getMoviesSucceeded:)]) {
[delegate getMoviesSucceeded:listOfMovies];
}
}
update your calling class .h file to use getMoviesDelegate:
#interface MoviesView : UIViewController <getMoviesDelegate>{
.
.
.
}
add the getMoviesDelegate methods to your calling class .m file
- (void)getMoviesSucceeded:(NSMutableArray *)movieArray {
//deal with movieArray here
}
- (void)getMoviesFailed:(NSString *)failedMessage {
//deal with failure here
}
This is not tested but hopefully gives you a road map to work with.
Protocols are nice because you can make both required and optional delegate methods and it helps in refining your helper methods to become very reusable across projects. The compiler will also warn you if you have implemented a protocol but not implemented the protocol's required delegate methods. If you follow this path be sure to use conformsToProtocol: and respondsToSelector:
Fundamentally, what's happening is that you're starting an asynchronous network load (asynchronous is the right way to do this, almost assuredly), and then you need some way to resume whatever operation you were doing before the load began. You have a few options:
Create your own delegate protocol. Your UITableViewController would then set itself as the helper's delegate, and the helper would call helperDidLoad or whatever you named that method. There's more information on writing delegates in the Cocoa Programming Guide.
Use blocks and continuation passing style. This is a bit more advanced but I like it. In your UITableViewController you'd write something like this:
[helper doSomething:^ (id loaded) {
[modelObject refresh:loaded]; // or whatever you need to do
}];
And then in your helper you'd write:
- (void)doSomething:(void ^ (id))continuation {
_continuation = continuation;
//kick off network load
}
- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
_continuation(_data);
}
Use notifications. Read the NSNotificationCenter docs.
Use KVO. The KVO programming guide has a lot of good info on Key-Value Observing.
How to i get the getMovies method to return the listOfMovies that is built in the connectionDidFinishLoading delegate class?
I'm going to argue that you should not do that.
Network requests should be made asynchronously. If your getMovies were to make a synchronous request and return only when it had data you would block that entire thread while you waiting for a network connection to finish. This is a bad idea in general and a terrible idea if your main thread is calling getMovies. Blocking the main thread will prevent you from responding to touches or updating the UI, your app will appear frozen, and the OS will terminate it if your users don't quit in frustration first.
Instead have the helper class notify the caller when data is available (or when it failed to retrieve data) through a delegate call back, notification, KVO, or whatever mechanism you prefer.
Here are the steps, pseudocode like style:
[helperInstance setDelegate:self]; // where self is your UITableViewController class
in your helper class, in the connectionDidFinishLoading do something like this:
[delegate finishedLoadingData:JSONData];
Also you can define a protocol for your delegate, and the declare the delegate like this in your helper class:
#property (nonatomic, assign) id<YourProtocol> delegate;
Hope this helps,
Moszi

iPhone Development: nil userInfo when receiving a notification

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"];

How to pass userInfo in NSNotification?

I am trying to send some data using NSNotification but get stuck. Here is my code:
// Posting Notification
NSDictionary *orientationData;
if(iFromInterfaceOrientation == UIInterfaceOrientationLandscapeRight) {
orientationData = [NSDictionary dictionaryWithObject:#"Right"
forKey:#"Orientation"];
}
NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter];
[notificationCenter postNotificationName:#"Abhinav"
object:nil
userInfo:orientationData];
// Adding observer
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(orientationChanged)
name:#"Abhinav"
object:nil];
Now how to fetch this userInfo dictionary in my selector orientationChanged?
You get an NSNotification object passed to your function. This includes the name, object and user info that you provided to the NSNotificationCenter.
- (void)orientationChanged:(NSNotification *)notification
{
NSDictionary *dict = [notification userInfo];
}
Your selector must have : to accept parameters.
e.g.
#selector(orientationChanged:)
then in the method declaration it can accept the NSNotification parameter.
You are posting the notification correctly.
Please modify the Notification Observer like following.
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(orientationChanged:)
name:#"Abhinav" object:nil];
- (void)orientationChanged:(NSNotification *)notification
{
NSDictionary *dict = [notification userInfo];
}
I hope, this solution will work for you..
In swift
To get userinfo object
let dict = notification.userInfo
print(dict)

Cocoa: Problem passing a CGPoint with NSNotification and NSDictionary

I'm trying to fire a Notification in a method called setPosition in one class, that triggers setViewPointCenter in another class. However, I'm trying to send a CGPoint along with it. But Xcode isn't liking it one bit.
-(void)setPosition:(CGPoint)point
{
NSDictionary *dict = [[NSDictionary alloc] initWithObjectsAndKeys:#"sp", point, nil];
[[NSNotificationCenter defaultCenter]
postNotificationName:#"SpriteDidSetPosition"
object:self
userInfo:dict];
[super setPosition:point];
}
Triggers this method in another class, but throws the indicated error
-(id) init{
// Usual stuff, blah blah blah...
[[NSNotificationCenter defaultCenter]
addObserver:self
selector:#selector(setViewPointCenter:)
name:#"BownceSpriteDidSetPosition"
object:nil];
}
-(void) setViewPointCenter:(NSNotification *)notification
{
// ERROR: Invalid Initializer
CGPoint point = [[notification userInfo] valueForKey:#"sp"];
// more code here....
}
I've been digging around, and found this solution, but I still get an error.
-(void)setPosition:(CGPoint)point
{
// ERROR: Incompatile type for argument 1 of "Value With Point"
NSValue *pointAsObject = [NSValue valueWithPoint:point];
NSDictionary *dict = [[NSDictionary alloc]
initWithObjectsAndKeys:#"sp",
pointAsObject,
nil];
[[NSNotificationCenter defaultCenter]
postNotificationName:#"SpriteDidSetPosition"
object:self
userInfo:dict];
[super setPosition:point];
}
It's driving me nuts. And to confuse me even further, changing CGPoint to NSPoint like this
-(void)setPosition:(NSPoint)point
{
NSValue *pointAsObject = [NSValue valueWithPoint:point];
NSDictionary *dict = [[NSDictionary alloc] init];
[dict initWithObjectsAndKeys:#"sp", pointAsObject, nil];
[[NSNotificationCenter defaultCenter]
postNotificationName:#"SpriteDidSetPosition"
object:self
userInfo:dict];
[super setPosition:CGPointMake(point.x, point.y)];
}
Get's rid of the error in setPosition, but I'm still screwed in setViewPointCenter. And as I understand it, CGPoint and NSPoint should equal the same thing, but it doesn't look like they do.
Does anyone have a working example of how to pass a CGPoint in a Dictionary? I can't figure it out.
This is for the iPhone, incase that makes a difference.
Try using +[NSValue valueWithCGPoint].
I'd use the NSStringFromCGPoint() function to convert it to a string, and then use the CGPointFromString() function to convert it back.
You could encapsulate the x and y values from the CGPoint into NSNumber objects using +numberWithFloat: and then add the two resulting NSNumber objects into the dictionary. You can then reconstruct the CGPoint on the other side using:
CGPoint myPoint;
myPoint.x = [myNumberObject floatValue];
myPoint.y = [myNumberObject2 floatValue];
The reason it didn't work in the first try was that CGPoint isn't an object, it's a C struct.
Its been too long time after #Ben Gottlieb have given an answer, his answer is well, but for future I'm keeping an example for reference.
// In example, I want to send CGPoint with notification
[[NSNotificationCenter defaultCenter] postNotificationName:#"MyNotification" object:#{#"someKeyToHoldCGPoint":[NSValue valueWithCGPoint:CGPointMake(10, 10)]}];
- (void) getPoints:(NSNotification *)notification {
//get the dictionary object from notification
NSDictionary *p = (NSDictionary *)notification.object;
//get the NSValue object from dictionary p
NSValue *value = [p valueForKey:#"someKeyToHoldCGPoint"];
//convert the value to CGPoint
CGPoint points = [value CGPointValue];
//check if we've the correct value
NSLog(#"%#",NSStringFromCGPoint(points));
}
It should log, (10,10).
I haven't read up on GC and NSPoints, but what data-types can NSDictionary hold? Check the docs, maybe you should cast it to NSData.