iOS: Passing data between views - objective-c

I have two views which are created programmatically. Let's name them view1 and view2. In view1 there is a picker and a button. The idea is when the user choose value and press the button selected value to be accessable from view2. For this I use NSNotificationCenter. Here is some code.
view1.m
-(void)registerNotification
{
NSDictionary *dict = [NSDictionary dictionaryWithObject:self.selectedOption forKey:#"data"];
[[NSNotificationCenter defaultCenter]
postNotificationName:#"pickerdata"
object:self
userInfo:dict];
}
-(void)loadSecondView
{
self.secondView = [[secondViewController alloc]init];
[self.view addSubview:self.secondView.view];
[self registerNotification];
[self.secondView release];
}
view2.m
-(id)init
{
if(self = [super init])
{
[[NSNotificationCenter defaultCenter]
addObserver:self
selector:#selector(reciveNotification:)
name:#"pickerdata" object:nil];
}
return self;
}
-(void)reciveNotification:(NSNotification *)notification
{
if([[notification name] isEqualToString:#"pickerdata"])
{
NSLog(#"%#", [NSString stringWithFormat:#"%#", [[notification userInfo] objectForKey:#"data"]]); // The output of NSLog print selected value
// Here is assignment to ivar
self.selectedValue = [NSString stringWithFormat:#"%#", [[notification userInfo] objectForKey:#"data"]];
}
}
The problem starts here. The logic which is interested of that value is implemented in loadView method. The problems is that loadView is executed before reciveNotification method and selectedValue does not contain needed information yet.
What to do so the information provided from NSNotificationCenter to be accessible from loadView method ?

I don't know if I fully understand your question, but wouldn't it be easier to pass the value directly to the viewController instead of dealing with notifications?
-(void)loadSecondView
{
self.secondView = [[secondViewController alloc]init];
self.secondView.selectedValue = self.selectedOption;
[self.view addSubview:self.secondView.view];
[self.secondView release];
}

Related

How to add notification observers in NSManagedObject subclass to avoid call to unrecognized selector

I've been experiencing a problem with saving managed objects (in a background thread) resulting in calls to unrecognised selectors which seems to be related to the way I'm handling the observation of NSManagedObjectContextObjectsDidChangeNotification. Intermittently it will fail with -[NSFetchRequest myManagedObjectContextDidChange:]: unrecognized selector sent to instance. It's not always a NSFetchRequest, sometimes it's a NSKeyValueObservance or unspecified which makes me believe that the observer is still around after a managed object has been released.
I'm adding and removing the observer to NSManagedObjectContextObjectsDidChangeNotification as seen below. Is there anything wrong with that?
#interface Foo ()
#property (assign, nonatomic, getter = isObserving) BOOL observing;
#end
#implementation Foo
#synthesize observing = _observing;
- (void)awakeFromInsert {
[super awakeFromInsert];
self.addedDate = [NSDate date];
self.modificationDate = [self.addedDate copy];
[self commonAwake];
}
- (void)awakeFromFetch {
[super awakeFromFetch];
[self commonAwake];
}
- (void)awakeFromSnapshotEvents:(NSSnapshotEventType)flags {
[super awakeFromSnapshotEvents:flags];
[self commonAwake];
}
- (void)commonAwake
{
if (self.isObserving) return;
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(myManagedObjectContextDidChange:)
name:NSManagedObjectContextObjectsDidChangeNotification
object:self.managedObjectContext];
self.observing = YES;
}
- (void)willTurnIntoFault
{
[[NSNotificationCenter defaultCenter] removeObserver:self name:NSManagedObjectContextObjectsDidChangeNotification object:self.managedObjectContext];
self.observing = NO;
[super willTurnIntoFault];
}
- (void)myManagedObjectContextDidChange:(NSNotification*)notification {
NSDictionary *userInfo = [notification userInfo];
NSMutableSet *changedObjects = [NSMutableSet new];
NSSet *objects = nil;
objects = [userInfo objectForKey:NSInsertedObjectsKey];
[changedObjects unionSet:objects];
objects = [userInfo objectForKey:NSUpdatedObjectsKey];
[changedObjects unionSet:objects];
objects = [userInfo objectForKey:NSDeletedObjectsKey];
[changedObjects unionSet:objects];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"SELF IN %#", self.bars];
NSSet *updatedObjects = [changedObjects filteredSetUsingPredicate:predicate];
if (updatedObjects.count > 0) {
NSDate *now = [NSDate date];
if (self.modificationDate == nil || [now timeIntervalSinceDate:self.modificationDate] > 1.0) {
self.modificationDate = now;
}
}
}
#end
Check to see when didTurnIntoFault is called. If it is called, try:
[[NSNotificationCenter defaultCenter] removeObserver:self]
Note that the contextDidChange notification might be called on a different thread then the didTurnIntoFault. And so you might have a race condition. Make sure the adding and removing of the observe is done on the same thread (which should be the one on which the managedOject and hence the managed object itself was created).

Sending a notification from another thread

I have a settings panel in my app. Whenever the user presses a button, the object is updated on another thread as the UI updates. I have a separate label on the main view that is supposed to update the object count when the object has finished updating (which I want to happen regardless of whether the settings panel is up or down). I've tried following the apple documentation regarding this very topic, but it doesn't seem to work out for me - that is, it seems that the main view controller never receives the notification for some reason. Does anyone have any suggestions on how to alert the main view controller that an object passed to another thread has finished updating? Here's the code I'm using (most of which was copied from that doc):
Object Class:
[[NSNotificationCenter defaultCenter] postNotificationName: #"ScaleCountUpdated" object: self];
Main View Controller
- (void)setUpThreadingSupport
{
if (self.notifications) {
return;
}
self.notifications = [[NSMutableArray alloc] init];
self.notificationLock = [[NSLock alloc] init];
self.notificationThread = [NSThread currentThread];
self.notificationPort = [[NSMachPort alloc] init];
[self.notificationPort setDelegate: self];
[[NSRunLoop currentRunLoop] addPort: self.notificationPort
forMode: (NSString *)kCFRunLoopCommonModes];
}
- (void)handleMachMessage:(void *)msg
{
[self.notificationLock lock];
while ([self.notifications count]) {
NSNotification *notification = [self.notifications objectAtIndex: 0];
[self.notifications removeObjectAtIndex: 0];
[self.notificationLock unlock];
[self processNotification: notification];
[self.notificationLock lock];
};
[self.notificationLock unlock];
}
- (void)processNotification:(NSNotification *)notification{
if ([NSThread currentThread] != self.notificationThread) {
// Forward the notification to the correct thread.
[self.notificationLock lock];
[self.notifications addObject: notification];
[self.notificationLock unlock];
[self.notificationPort sendBeforeDate: [NSDate date]
components: nil
from: nil
reserved: 0];
} else {
[self updateScaleCount];
}
}
- (void)updateScaleCount
{
NSLog(#"[ScalesViewController - updateScaleCount]: Scales updated from notification center.");
if([UserDefinedScales areScalesGrouped] == YES){
self.groupCountLabel.text = [NSString stringWithFormat: #"Group Count: %i", [[UserDefinedScales sortedKeys] count]];
} else {
self.groupCountLabel.text = #"Group Count: 1";
}
self.scaleCountLabel.text = [NSString stringWithFormat: #"Scale Count: %i", [UserDefinedScales scaleCount]];
}
Main View Controller - View Did Load:
[self setUpThreadingSupport];
[[NSNotificationCenter defaultCenter] addObserver: self
selector: #selector(processNotification:)
name: #"ScaleCountUpdated"
object: nil];
If you have any suggestions on how to alter this code to make it function correctly, or have another solution to offer for achieving this, it would be greatly appreciated! Thank you.
It looks to me like you are doing it correctly, i.e. register for notification and send it.
As far as I can see from your code and the information you give, you can basically completely forget about the setupThreadingSupport. You should definitely test it without it. Not sure what you want to achieve, but looks like overkill where probably a simple block would suffice. Is there a compelling reason to listen to the notification on a background thread? Why not let the notification center decide?
Log the sending and receiving of the notifications - addObserver and postNotification is really all this mechanism needs to work as expected.
I would go to a simpler implementation. The NSNotificationCenter already provides all the mechanisms you need to broadcast and receive messages across your app.
If you fire the notification from a background thread you can use a GCD dispatch_async to make make it delivered on the main thread.
Object Class
dispatch_async(dispatch_get_main_queue(), ^{
// You don't need to pass the object itself here as you are not using it later.
[[NSNotificationCenter defaultCenter] postNotificationName:#"ScaleCountUpdated"];
}
MainViewController
-(void)viewDidLoad
{
[super viewDidLoad];
// Register your controller as an observer for a specific message name
[[NSNotificationCenter defaultCenter] addObserver: self
selector: #selector(updateScaleCount)
name: #"ScaleCountUpdated"
object: nil];
}
- (void)updateScaleCount
{
NSLog(#"[ScalesViewController - updateScaleCount]: Scales updated from notification center.");
if([UserDefinedScales areScalesGrouped] == YES){
self.groupCountLabel.text = [NSString stringWithFormat: #"Group Count: %i", [[UserDefinedScales sortedKeys] count]];
} else {
self.groupCountLabel.text = #"Group Count: 1";
}
self.scaleCountLabel.text = [NSString stringWithFormat: #"Scale Count: %i", [UserDefinedScales scaleCount]];
}

Making an instance of a class staying longer in memory (under ARC)

I have an instance of a NSObject class that is supposed to parse a XML and save NSManagedObjects, it does everything ok. But I need to receive a NSManagedObjectContextDidSaveNotification inside it to merge CoreData contexts.
The thing is that my instance is being deallocated sooner then I receive the notification above.
How can I prevent my instance being deallocated sooner?
Here's when I call my instance
// in my ViewController implementation
WSNoticia *wsNoticia = [WSNoticia new]; // __strong by default right?
Here's the implementation of WSNoticia:
- (id)init {
self = [super init];
if (self) {
[self parseNews];
}
return self;
}
- (void)dealloc {
// called before mergeChanges: or updateContext:
}
#pragma mark - Private Methods
- (void)parseNews {
// save context in another thread
AppDelegate *appDelegate = (AppDelegate *)[[UIApplication sharedApplication] delegate];
NSManagedObjectContext *context = [[NSManagedObjectContext alloc] init];
[context setUndoManager:nil]; // performance benefit
[context setPersistentStoreCoordinator:[appDelegate persistentStoreCoordinator]];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(mergeChanges:) name:NSManagedObjectContextDidSaveNotification object:context];
NSBlockOperation *blockOperation = [NSBlockOperation blockOperationWithBlock:^{
// fetching things
}];
[blockOperation setCompletionBlock:^{
// updating and saving things
// here the NSManagedObjectContextDidSaveNotification is called (by doing [context save:nil];
}];
// add operation to queue
NSOperationQueue *operationQueue = [NSOperationQueue new];
[operationQueue addOperation:blockOperation];
}
// doesn't get called
- (void)updateContext:(NSNotification *)notification {
NSManagedObjectContext *mainContext = [self managedObjectContext];
[mainContext mergeChangesFromContextDidSaveNotification:notification];
[[NSNotificationCenter defaultCenter] postNotificationName:kNotificationNameNoticiasParsed object:self];
}
#pragma mark - NSNotificationCenter
// doesn't get called
- (void)mergeChanges:(NSNotification *)notification {
[[NSNotificationCenter defaultCenter] removeObserver:self];
AppDelegate *appDelegate = (AppDelegate *)[[UIApplication sharedApplication] delegate];
NSManagedObjectContext *mainContext = [appDelegate managedObjectContext];
if ([notification object] == mainContext) {
// main context save, no need to perform the merge
return;
}
[self performSelectorOnMainThread:#selector(updateContext:) withObject:notification waitUntilDone:YES];
}
The strong lifetime qualifier is applied by default, but the lifetime of your variable is just for the method in which you declare it.
If you declare wsNoticia as an instance variable, you should be fine.

NSOperationQueue becomes suspended at random?

I have an app that uses NSOperationQueue intensively.
Sometimes I've noticed that some of the NSOperationQueues would "lock up" or enter a "isSuspended" state randomly even though my code never calls the setSuspended: method.
It's impossible to replicate and very hard to debug because whenever I hook the device up to Xcode to debug it, the app would reload and the bug would go away.
I added a lot of NSLogs at all possible points that might have a problem and just had to use the app for a few days until the bug reemerged.
Looking at the device system logs, I've discovered that [myOperationQueue operationCount] would increment but the opeations in queue will not execute.
I haven't tried manually setting "setSuspended:NO" yet but is that really necessary?
What could be causing this?
Here is a little bit of my code
The view controller that calls operations
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
// Custom initialization
self.operationQueue = [[[NSOperationQueue alloc] init] autorelease];
[self.operationQueue setMaxConcurrentOperationCount:2];
self.sendOperationQueue = [[[NSOperationQueue alloc] init] autorelease];
[self.sendOperationQueue setMaxConcurrentOperationCount:2];
self.receiveOperationQueue = [[[NSOperationQueue alloc] init] autorelease];
[self.receiveOperationQueue setMaxConcurrentOperationCount:1];
}
}
- (void)dealloc
{
[[NSNotificationCenter defaultCenter] removeObserver:self];
[operationQueue cancelAllOperations];
[sendOperationQueue cancelAllOperations];
[receiveOperationQueue cancelAllOperations];
[operationQueue release];
[sendOperationQueue release];
[receiveOperationQueue release];
}
- (IBAction)sendMessage
{
if(![chatInput.text isEqualToString:#""])
{
NSString *message = self.chatInput.text;
SendMessageOperation *sendMessageOperation = [[SendMessageOperation alloc] initWithMatchData:matchData andMessage:message resendWithKey:nil];
[self.sendOperationQueue addOperation:sendMessageOperation];
[sendMessageOperation release];
}
}
NSOperation subclass SendMessageOperation
- (id)initWithMatchData:(MatchData*)data andMessage:(NSString*)messageString resendWithKey:(NSString*)resendKey
{
self = [super init];
if(self != nil)
{
if(data == nil || messageString == nil)
{
[self release];
return nil;
}
appDelegate = (YongoPalAppDelegate *) [[UIApplication sharedApplication] delegate];
context = [[NSManagedObjectContext alloc] init];
[context setPersistentStoreCoordinator:[appDelegate persistentStoreCoordinator]];
[context setMergePolicy:NSMergeByPropertyObjectTrumpMergePolicy];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(mergeContextChanges:) name:NSManagedObjectContextDidSaveNotification object:context];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(mergeMainContextChanges:) name:NSManagedObjectContextDidSaveNotification object:appDelegate.managedObjectContext];
self.matchData = (MatchData*)[context objectWithID:[data objectID]];
matchNo = [[matchData valueForKey:#"matchNo"] intValue];
partnerNo = [[matchData valueForKey:#"partnerNo"] intValue];
self.message = messageString;
self.key = resendKey;
apiRequest = [[APIRequest alloc] init];
}
return self;
}
- (void)dealloc
{
[[NSNotificationCenter defaultCenter] removeObserver:self];
self.matchData = nil;
self.message = nil;
self.key = nil;
[context release];
[apiRequest release];
[super dealloc];
}
- (void)start
{
if([self isCancelled] == YES)
{
[self willChangeValueForKey:#"isFinished"];
finished = YES;
[self didChangeValueForKey:#"isFinished"];
return;
}
else
{
[self willChangeValueForKey:#"isExecuting"];
executing = YES;
[self main];
[self didChangeValueForKey:#"isExecuting"];
}
}
- (void)main
{
#try
{
NSAutoreleasePool *pool = [NSAutoreleasePool new];
bool taskIsFinished = NO;
while(taskIsFinished == NO && [self isCancelled] == NO)
{
NSDictionary *requestData = nil;
if(key == nil)
{
requestData = [self sendMessage];
}
else
{
requestData = [self resendMessage];
}
NSDictionary *apiResult = nil;
if(requestData != nil)
{
apiResult = [self sendMessageToServer:requestData];
}
if(apiResult != nil)
{
[[NSNotificationCenter defaultCenter] postNotificationName:#"shouldConfirmSentMessage" object:nil userInfo:apiResult];
}
taskIsFinished = YES;
}
[self willChangeValueForKey:#"isFinished"];
[self willChangeValueForKey:#"isExecuting"];
finished = YES;
executing = NO;
[self didChangeValueForKey:#"isFinished"];
[self didChangeValueForKey:#"isExecuting"];
[pool drain];
}
#catch (NSException *e)
{
NSLog(#"Exception %#", e);
}
}
- (BOOL)isConcurrent
{
return YES;
}
- (BOOL)isFinished
{
return finished;
}
- (BOOL)isExecuting
{
return executing;
}
- (void)mergeContextChanges:(NSNotification *)notification
{
[[NSNotificationCenter defaultCenter] postNotificationName:#"mergeChatDataChanges" object:nil userInfo:[notification userInfo]];
}
- (void)mergeMainContextChanges:(NSNotification *)notification
{
NSSet *updated = [[notification userInfo] objectForKey:NSUpdatedObjectsKey];
for(NSManagedObject *thing in updated)
{
[[context objectWithID:[thing objectID]] willAccessValueForKey:nil];
}
[context mergeChangesFromContextDidSaveNotification:notification];
}
You're using:
setMaxConcurrentOperationCount:X
My hunch is that X operations in the queue have not finished, therefore causing any subsequent operations added to the queue to not run until this is the case.
From the NSOperationQueue's isSuspended method documentation:
If you want to know when the queue’s suspended state changes, configure a KVO observer to observe the suspended key path of the operation queue.
Another idea that you can implement in parallel with the observer is to create a subclass of NSOperationQueue that redefines the setSuspended: method. In the redefined version you can set a breakpoint if you are running on the debugger, or print a stacktrace to the log if you are running w/o debugger.
Hope this helps.
take a look at ASIHTTPRequestConfig.h, it has some flags that you can turn on to see what is really happening behind the scenes.

Action sheet getting caught on tabbar

This is a tricky one that I've been working on for a while. I have a an action sheet that appears in a view with a tabbar, sometimes when I dismiss the action sheet it gets "caught" in the tabbar and am unable to do anything.The app doesn't crash because if i minimize it and then come back in it is dimissed, it just appears that the modal behavior isn't being dismissed properly.
I recently changed how the action sheet is displayed from "showInView" to "showFromTabBar" and am not sure if this is creating the issue.
Thanks,
William
Edit:
Below is the code I use to dismiss my actionsheet:
-(void)doneButtonPressed:(id)sender{
if ([[self viewWithTag:1] isKindOfClass:[PickerView class]]) {
PickerView *picker = (PickerView *)[self viewWithTag:1];
if (picker.selectedRow == nil) {
[picker populateSelectRowForRow:0 andComponent:0];
}
NSNotification *note = [NSNotification notificationWithName:#"doneButtonPressed" object:self.indexPath userInfo:picker.selectedRow];
[[NSNotificationCenter defaultCenter] postNotification:note];
}else {
DatePickerView *picker = (DatePickerView *)[self viewWithTag:1];
NSDictionary *extraInfo = [[NSDictionary alloc] initWithObjects:[[NSArray alloc] initWithObjects:[self formatDateToString:[picker date]], nil] forKeys:[[NSArray alloc] initWithObjects:#"value", nil]];
NSNotification *note = [NSNotification notificationWithName:#"doneButtonPressed" object:self.indexPath userInfo:extraInfo];
[[NSNotificationCenter defaultCenter] postNotification:note];
}
[self dismissWithClickedButtonIndex:0 animated:YES];
}
and the notification method that is called:
-(void)pickerUpdate:(NSNotification *)note{
NSIndexPath *indexPath = [note object];
NSDictionary *extraInfo = [note userInfo];
NSDictionary *dict = [[tableController.sectionAndFields objectAtIndex:indexPath.section] objectAtIndex:(indexPath.row + kHeaderAndFooterOffset)];
[tableController.formDetails setObject:[extraInfo objectForKey:#"value"] forKey:[dict objectForKey:#"key"]];
NSArray *reloadArray = [[NSArray alloc] initWithObjects:indexPath, nil];
[indexPath release];
[self.tv reloadRowsAtIndexPaths:reloadArray withRowAnimation:NO];
[reloadArray release];
}
I did manage to solve this however it feels very much like a hack, I introduced a delay into the code using:
[self performSelector:#selector(dismissFromActionSheet) withObject:nil afterDelay:0.2];
With dismissFromActionSheet being:
-(void)dismissFromActionSheet{
[self dismissWithClickedButtonIndex:0 animated:YES];
}