iOS: Posting code blocks within userInfo using NSNotificationCenter? - ios

Can i safely post messages containing code blocks within my program address space? Tests works but is this legal?
typedef void (^EmitBlock)(NSDictionary* args);
- (void) subscribe {
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(on_emit:) name:kEmit object:nil];
}
- (void) on_emit:(NSNotification*) notification
{
EmitBlock completion = [[notification userInfo] valueForKey:#"completion"];
completion(#{#"result" : #"Ok"});
}
- (void) post:(EmitBlock) completion {
[[NSNotificationCenter defaultCenter] postNotificationName:kEmit
object:nil userInfo:#{#"completion":
^(NSDictionay* args) { NSLog(#"%#", args); }
}];
}

Blocks are Objective-C objects, so you can put them into an dictionary and use them
in the userinfo of a notification.
But note that according to the Transitioning to ARC Release Notes:
… You still need to use [^{} copy] when passing “down” the stack into
arrayWithObjects: and other methods that do a retain.
you should put a copy of the block into the dictionary:
[[NSNotificationCenter defaultCenter] postNotificationName:#"kEmit"
object:nil
userInfo:#{
#"completion" : [^(NSDictionary* args) { NSLog(#"%#", args); } copy]
}
];

You can use one from libraries, for example https://github.com/cflesner/NSNotificationCenter-CLFBlockNotifications for using blocks with notifications
I think it is more safer approach

Related

NSNotification object being sent is (null)?

When I receive data from a socket and pass the data to another VC via NSNotificationCenter, the passed object always logs (null), despite the object being present in the other class.
there is where I pass the data through.
UPDATED:
-(void) initSIOSocket {
[SIOSocket socketWithHost:#"http://192.168.1.4:8080" response:^(SIOSocket *socket) {
self.socket = socket;
NSLog(#"%# from initSIOSocket", self.socket);
[self.socket on:#"q_update_B" callback:^(NSArray *args) {
NSArray *tracks = [args objectAtIndex:0];
[[NSNotificationCenter defaultCenter] postNotificationName:#"qUpdateB" object:nil userInfo:[NSDictionary dictionaryWithObject:tracks forKey:#"tracksData"]];
}];
..
- (void)receiveUpdateBNotification:(NSNotification *)notification
{
// Do parse respone data method and update yourTableViewData
NSArray *tracks = [[notification userInfo] objectForKey:#"tracksData"];
NSLog(#"%#", tracks);
self.tracks = tracks;
[self.tableView reloadData];
}
Console is STILL logging as (null) object. The notification is successful, no data is sent.
For passing data using NSNotification you need to use the userInfo dictionary.
Post it like:
[[NSNotificationCenter defaultCenter] postNotificationName:#"qUpdateB" object:nil userInfo:[NSDictionary dictionaryWithObject:tracks forKey:#"MyData"]];
And retrieve it using:
- (void)receiveUpdateBNotification:(NSNotification *)notification
{
self.tracks = [[notification userInfo] objectForKey:#"MyData"];
[self.tableView reloadData];
}
Object property is not intended for passing data.
object
The object associated with the notification. (read-only) Declaration
#property(readonly, retain) id object Discussion;
This is often the object that posted this notification. It may be nil.
Typically you use this method to find out what object a notification
applies to when you receive a notification.
For example, suppose you’ve registered an object to receive the
message handlePortDeath: when the “PortInvalid” notification is posted
to the notification center and that handlePortDeath: needs to access
the object monitoring the port that is now invalid. handlePortDeath:
can retrieve that object as shown here:
- (void)handlePortDeath:(NSNotification *)notification
{
...
[self reclaimResourcesForPort:notification.object];
...
}
Reference
I needed to use my Singleton to pass the data using the NSNotificationCenter, like so.
-(void) initSIOSocket {
[SIOSocket socketWithHost:#"http://192.168.1.4:8080" response:^(SIOSocket *socket) {
self.socket = socket;
NSLog(#"%# from initSIOSocket", self.socket);
[self.socket on:#"q_update_B" callback:^(NSArray *args) {
NSArray *tracks = [args objectAtIndex:0];
self.setListTracks = tracks;
[[NSNotificationCenter defaultCenter] postNotificationName:#"qUpdateB" object:nil];
}];
}];
}
..
- (void)receiveUpdateBNotification:(NSNotification *)notification
{
if ([[notification name] isEqualToString:#"qUpdateB"])
NSLog (#"Successfully received the test notification!");
// Do parse respone data method and update yourTableViewData
NSArray *tracks = [[SocketKeeperSingleton sharedInstance]setListTracks];
self.tracks = tracks;
[self.tableView reloadData];
}

unable to reference array outside block

I have an array called 'venues' that I'm generating in the following method. I'm trying to reuse it in a different method, but every time I try to access it in a different method the array is 'null'. The method is:
- (void)startSearchWithString:(NSString *)string {
[self.lastSearchOperation cancel];
self.lastSearchOperation = [Foursquare2
venueSearchNearLocation:#"Chicago" query:nil limit:nil intent:intentCheckin radius:nil categoryId:string
callback:^(BOOL success, id result){
if (success) {
NSDictionary *dic = result;
NSArray *venuesDic = [dic valueForKeyPath:#"response.venues"];
self.venues = [SearchVenue placesWithArray:venuesDic];
NSLog(#"self.venues inside is %#",self.venues);
} else {
NSLog(#"%#",result);
}
}];
NSLog(#"self.venues outside is %#",self.venues);
}
self.venues inside logs content.
self.venues outside logs (null).
I'm trying __block but without success.
In my .h I have:
#property (strong, nonatomic) NSArray *venues;
The callback block is executed asynchronously after venueSearchNearLocation has finished.
your log code:
NSLog(#"self.venues outside is %#",self.venues);
probably gets executed before your callback block and therefore self.venues are still nil.
You must access self.venues after your callback-block has finished.
How you do that really depends on your app. One way of lose coupling is to post a notification after you have set your array.
[[NSNotificationCenter defaultCenter] postNotificationName:
#"VENUES_RECEIVED " object:nil];
and in some initializer, you need to listen to that notification:
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(receivedVenus)
name:#"VENUES_RECEIVED" object:nil];
and of course you need a method where you can work with your array
- (void) receivedVenus
{
NSLog(#"self.venues outside is %#",self.venues);
}
Do not forget to remove your observer (i.e. in dealloc)
[[NSNotificationCenter defaultCenter] removeObserver:self];
Try
NSArray *venuesDic = [NSArray arrayWithArray:[dic valueForKeyPath:#"response.venues"]];

Pass data from one view to another

I am trying to pass data from one controller to another (without doing any type of animation or segue) to populate a table with an array. Populating the table is not an issue as I have been able to do that, but getting the data to the controller with the tableview has proven to be impossible.
I've been trying to figure out how to do this for the last 4 days. I've read through tons of helpful posts including: Passing Data between View Controllers, with absolutely no luck. I'm fairly new to iOS dev so maybe I'm just completely overlooking something that should be simple.
I am using SWRevealViewController for the sidebar.
WebAppViewController.m
- (void)webViewDidFinishLoad:(UIWebView *)webView
{
NSString *html = [webView stringByEvaluatingJavaScriptFromString:
#"document.getElementById('json-ios').innerHTML"];
NSData *jsonData = [html dataUsingEncoding:NSUTF8StringEncoding];
NSError *e;
NSDictionary *dict = [NSJSONSerialization JSONObjectWithData:jsonData options:nil error:&e];
NSArray *jsonArray = [dict objectForKey:#"results"];
// This is where I would like to pass jsonArray to my sidebar to populate a table (SidebarViewController) each time the page loads
.....
try using NSNotification or Singleton
NSNotification:
#implementation TestClass
- (void) dealloc
{
// If you don't remove yourself as an observer, the Notification Center
// will continue to try and send notification objects to the deallocated
// object.
[[NSNotificationCenter defaultCenter] removeObserver:self];
[super dealloc];
}
- (id) init
{
self = [super init];
if (!self) return nil;
// Add this instance of TestClass as an observer of the TestNotification.
// We tell the notification center to inform us of "TestNotification"
// notifications using the receiveTestNotification: selector. By
// specifying object:nil, we tell the notification center that we are not
// interested in who posted the notification. If you provided an actual
// object rather than nil, the notification center will only notify you
// when the notification was posted by that particular object.
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(receiveTestNotification:)
name:#"TestNotification"
object:nil];
return self;
}
- (void) receiveTestNotification:(NSNotification *) notification
{
// [notification name] should always be #"TestNotification"
// unless you use this method for observation of other notifications
// as well.
if ([[notification name] isEqualToString:#"TestNotification"])
NSLog (#"Successfully received the test notification!");
}
#end
... somewhere else in another class ...
- (void) someMethod
{
// All instances of TestClass will be notified
[[NSNotificationCenter defaultCenter]
postNotificationName:#"TestNotification"
object:self];
}
source
Singleton:
The standard way of creating a singleton is like...
Singleton.h
#interface MySingleton : NSObject
+ (MySingleton*)sharedInstance;
#end
Singleton.m
#import "MySingleton.h"
#implementation MySingleton
#pragma mark - singleton method
+ (MySingleton*)sharedInstance
{
static dispatch_once_t predicate = 0;
__strong static id sharedObject = nil;
//static id sharedObject = nil; //if you're not using ARC
dispatch_once(&predicate, ^{
sharedObject = [[self alloc] init];
//sharedObject = [[[self alloc] init] retain]; // if you're not using ARC
});
return sharedObject;
}
#end
source
P.S.There is no way singleton won't work.
Singleton is like a static object. You would want to save data here that will be accessible by the whole app without much changes in code.(Its like a global object) whereas NSNotification is self explanatory and googlable
Use protocol or NSNotification for simple data passing ..

NSNotificationCenter to post a C structure and retrieve it back from the notification callback function

I have a complex C structure and it's hard to convert this structure to NSDictionary one by one.
How do I post this C structure via NSNotificationCenter and retrieve it back from the NSNotificationCenter callback function?
Any suggestion will be appreciated, thanks!
Store and retrieve as NSValue
Store:
CustomStruct instanceOfCustomStruct = ...;
NSValue * valueOfStruct = [NSValue valueWithBytes:&instanceOfCustomStruct objCType:#encode(CustomStruct)];
[[NSNotificationCenter defaultCenter] postNotificationName:#"YourNotification" object:self userInfo:#{#"CustomStructValue" : valueOfStruct}];
Retrieve:
NSValue * valueOfStruct = note.userInfo[#"CustomStructValue"];
CustomStruct instanceOfCustomStruct;
[valueOfStruct getValue:&instanceOfCustomStruct];
Personally, I find it easiest to stuff the structure into an NSData and then send it as part of the notification. Here's the declaration of the sample structure used in the code that follows.
typedef struct
{
int a;
int b;
}
ImportantInformation;
Here's the code to send the structure as part of a notification. The last part of the third line puts the NSData object into an NSDictionary and passes that dictionary as the userInfo. The key for the NSData in the dictionary is #ImportantInformation.
ImportantInformation info = { 555, 321 };
NSData *data = [NSData dataWithBytes:&info length:sizeof(info)];
[[NSNotificationCenter defaultCenter] postNotificationName:#"ImportantChange" object:self userInfo:#{ #"ImportantInformation" : data }];
Here's the code that adds an observer for the notification and defines the block that runs when the notification is received.
NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
self.observer = [center addObserverForName:#"ImportantChange"
object:nil
queue:nil
usingBlock:^(NSNotification *notif)
{
// this is the structure that we want to extract from the notification
ImportantInformation info;
// extract the NSData object from the userInfo dictionary using key "ImportantInformation"
NSData *data = notif.userInfo[#"ImportantInformation"];
// do some sanity checking
if ( !data || data.length != sizeof(info) )
{
NSLog( #"Well, that didn't work" );
}
else
{
// finally, extract the structure from the NSData object
[data getBytes:&info length:sizeof(info)];
// print out the structure members to prove that we received that data that was sent
NSLog( #"Received notification with ImportantInformation" );
NSLog( #" a=%d", info.a );
NSLog( #" a=%d", info.b );
}
}];
Note: be sure to remove the observer at some point.
[[NSNotificationCenter defaultCenter] removeObserver:self.observer];

Why does this NSArray in an NSNotification block (apparently) never get released in an ARC environment?

I have a view controller that's subscribing to notifications broadcast elsewhere. This is part of a large object graph that has some cleanup that needs to be done when the children are dealloc'd. But dealloc was never being called in one of the children (ARC environment), which (if I understand) means something somewhere was still retained, thus causing ARC to never dealloc even when the VC is dismissed. I've traced the offending code to the following two lines. The first version causes the VC and children to never be fully dealloc'd. In the second version, things work fine and everything gets dealloc'd when this VC is dismissed.
My question is why? With ARC I can't understand why this additional NSArray would not get properly released.
Hopefully this is enough code. Can post more if need be.
Here's the version that leads to the VC (and children, etc) never being fully dealloc'd:
// Subscribe to notifications that the number of trackables has changed
[[NSNotificationCenter defaultCenter] addObserverForName:kUpdatedNumberofTrackables object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note) {
if ([note.object isKindOfClass:[NSArray class]]) {
NSArray *activeTrackableNames = note.object; // <-- offending line
[self trackableUpdate:activeTrackableNames]; // <-- offending line
} else {
NSLog(#"Observer error. Object is not NSArray");
}
}];
But when I do it the following way, everything gets released properly when the view is unloaded, and thus dealloc is called on child VCs:
// Subscribe to notifications that the number of trackables has changed
[[NSNotificationCenter defaultCenter] addObserverForName:kUpdatedNumberofTrackables object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note) {
if ([note.object isKindOfClass:[NSArray class]]) {
[self trackableUpdate:note.object]; // <-- seems to work
} else {
NSLog(#"Observer error. Object is not NSArray");
}
}];
A few other relevant methods in this VC that may(?) play a role:
- (void)viewDidDisappear:(BOOL)animated {
[super viewDidDisappear:animated];
self.trackablesVisible_private = nil;
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
#pragma mark - Target Handling
-(void)trackableUpdate:(NSArray *)trackablesArray {
NSLog(#"Trackable Changed");
if (([trackablesArray count] > 0) && [self.delegate respondsToSelector:#selector(foundValidTargets:)]) {
[delegate foundValidTargets:trackablesArray];
}
self.trackablesVisible_private = trackablesArray;
}
I don't understand what's going on here. Could someone please enlighten me?
Thanks!
EDIT:
So as noted in the replies, part of the issue is a retain cycle from failing to use a weak self in the block, however apparently there's another issue going on here because I'm using Notification Center. The solution is described here: Dealloc Not Running When Dismissing Modal View from Block
My final working code is as follows:
In #interface
__weak id observer;
In loadView
__weak ThisViewController *blockSelf = self; // Avoids retain cycle
observer = [[NSNotificationCenter defaultCenter] addObserverForName:kUpdatedNumberofTrackables object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note) {
if ([note.object isKindOfClass:[NSArray class]]) {
ThisViewController *strongSelf = blockSelf; // Avoids retain cycle
NSArray *activeTrackableNames = note.object;
[strongSelf trackableUpdate:activeTrackableNames];
} else {
NSLog(#"Observer error. Object is not NSArray");
}
}];
And in viewWillDisappear
[[NSNotificationCenter defaultCenter] removeObserver:observer];
I don't yet fully understand why you have to save the returned observer object, rather than just removing self, but this works.
You have a retain cycle in your block...here is what you want
__weak MyViewControllerName *bSelf = self;
// Subscribe to notifications that the number of trackables has changed
[[NSNotificationCenter defaultCenter] addObserverForName:kUpdatedNumberofTrackables object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note) {
if ([note.object isKindOfClass:[NSArray class]]) {
**NSArray *activeTrackableNames = note.object;
[bSelf trackableUpdate:activeTrackableNames];**
} else {
NSLog(#"Observer error. Object is not NSArray");
}
}];
For more info on blocks and retain cycles, check out http://zearfoss.wordpress.com/2012/05/11/a-quick-gotcha-about-blocks/

Resources