I am using Core Data and was wondering if I am doing things correctly. I am opening my UIManagedDocument from a singleton object using the completion handler and block below.
[[self managedDocument] openWithCompletionHandler:^(BOOL success) {
if(success) {
NSLog(#"DOCUMENT: Success, Opened ...");
// TODO: Things to do when open.
// ...
// ...
}
}];
On my UIViewController I have setup an observer to watch for a UIDocumentStateChangedNotification to indicate that I can start working with the document.
- (void)awakeFromNib {
NSLog(#"%s", __PRETTY_FUNCTION__);
NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter];
[notificationCenter addObserver:self selector:#selector(documentIsReady) name:UIDocumentStateChangedNotification object:nil];
}
This seams to work just fine, but I am conscious that I am not using the callback block. One solution might be to create my own notification and post that from the block, it does the same thing essentially but just makes the code more obvious to read. Any comments would be much appreciated.
I'd say that if you only need to notify one controller, once, and only when the document is opened (you have an app that uses a single UIManagedDocument that gets passed between controllers, like the CS193P demo), it would be better to leave only the code inside the completion block.
However, if your app is going to open and close the document many times, and multiple controllers have to be aware of that change, you should use notifications.
Related
I have developed an iphone application with Phonegap/Cordova v1.9.0.
I want to realize the following matter.
-When a background app comes back to be active(When the app icon is tapped), a displayed page is reloaded automatically-
Probably I should make some programs in a function, (void)applicationDidBecomeActive, in Appdelegate.m or MainViewController.m, but I have no idea what to do.
Please tell me how to solve this case.
You can use the NSNotification observer pattern. In your MainViewController.m file, and viewDidLoad, you can add an observer (registering for notifications):
- (void)viewDidLoad
{
[super viewDidLoad];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(appDidBecomeActive:)
name:UIApplicationDidBecomeActiveNotification
object:nil];
}
Then you must implement appDidBecomeActive: (you can give the selector any name, but you must implement a method of that name). In this example:
- (void)appDidBecomeActive:(NSNotification *)notification {
NSLog(#"App became active");
}
When the app is resumed, and should this view controller be active, it will simply log that to the console. You can put any code you wish inside that method (in your case, refreshing a page).
Don't forget to remove the observer when the view controller is deallocated in the dealloc method. This will remove all observers for you.
- (void)dealloc
{
[[NSNotificationCenter defaultCenter] removeObserver:self];
NSLog(#"Removed all notification observers");
}
Are you really using version 1.9?
Try this:
document.addEventListener("resume", onResume, false);
function onResume() {
// Handle the resume event, reload the page or content
}
I need to execute some code after I know the keyboard is hidden.
Ive been looking in to blocks but I'm just not understanding how they work enough to do this...
All I want to do is run [self hidekeyboard] then when that is complete (and the keyboard fully hidden) then I want to call a delegate.
What is the best way to handle this and how?
You want to use the UIKeyboardDidHide notification and run your code in there. Here is the link in the docs...
http://developer.apple.com/library/ios/#documentation/UIKit/Reference/UIWindow_Class/UIWindowClassReference/UIWindowClassReference.html
[[NSNotificationCenter defaultCenter]addObserver:self selector:#selector(onKeyboardDidHide:) name: UIKeyboardDidHideNotification object:nil];
And the onKeyboardDidHide:
-(void)onKeyboardDidHide:(NSNotification *)notification
{
// execute what you want.
}
Register a listener for the UIKeyboardDidHideNotification using the NSNotificationCenter class.
[[NSNotificationCenter defaultCenter]
addObserver:self
selector:#selector(keyboardHidden:)
name:UIKeyboardDidHideNorification
object:nil];
- (void)keyboardHidden:(NSNotification *)notif
{
// do stuff
}
(Don't forget to remove the observer in - dealloc so that no messages will erroneously be sent to deallocated objects.)
You probably want to register to receive notifications of UIKeyboardDidHideNotification.
http://developer.apple.com/library/ios/#documentation/StringsTextFonts/Conceptual/TextAndWebiPhoneOS/KeyboardManagement/KeyboardManagement.html
I'm writing an app that uses Core Data and is synced with iCloud. To do this, I have a UIManagedDocument that I set up as shown below:
UIManagedDocument *document = [[UIManagedDocument alloc] initWithFileURL:[self iCloudStoreURL]];
document.persistentStoreOptions = #{NSPersistentStoreUbiquitousContentNameKey: [document.fileURL lastPathComponent], NSPersistentStoreUbiquitousContentURLKey: [self iCloudCoreDataLogFilesURL], NSMigratePersistentStoresAutomaticallyOption: #YES, NSInferMappingModelAutomaticallyOption : #YES};
self.mydoc = document;
[document release];
[document.managedObjectContext setMergePolicy:NSMergeByPropertyObjectTrumpMergePolicy];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(documentContentsChanged:) name:NSPersistentStoreDidImportUbiquitousContentChangesNotification object:document.managedObjectContext.persistentStoreCoordinator];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(documentStateChanged:) name:UIDocumentStateChangedNotification object:document];
if (![[NSFileManager defaultManager] fileExistsAtPath:[self.mydoc.fileURL path]]) {
// does not exist on disk, so create it
[self.mydoc saveToURL:self.mydoc.fileURL forSaveOperation:UIDocumentSaveForCreating completionHandler:^(BOOL success) {
[self populateTable];//synchronous call. few items are added
[self iCloudIsReady];
}];
} else if (self.mydoc.documentState == UIDocumentStateClosed) {
// exists on disk, but we need to open it
[self.mydoc openWithCompletionHandler:^(BOOL success) {
[self iCloudIsReady];
}];
} else if (self.mydoc.documentState == UIDocumentStateNormal) {
// already open and ready to use
}
}
My issue with this approach is that I keep getting "Optimistic locking failure" when running the app in two devices. I read in Apple's Core Data documentation that a way to "avoid" this kind of issue was to set up the merge policy to NSMergeByPropertyObjectTrumpMergePolicy, something that I am already doing but for some reason is not working.
One thing I can't find is how to fix this. For example, if this is something that could happen, my app should be at least aware and prepared to handle this behavior. But I have no idea on how to handle this. For example, how do I get the conflicting objects and resolve them? Because every time this failure happens, I start getting UIDocumentStateSavingError when trying to save the document and the only way to stop getting this error is by killing the app and re-launching it.
I finally figured this out (at least, I think I did). Apparently, on iOS6+ (I have no idea about iOS5) UIManagedDocument takes care of all the merging for you. So, the observer below, which was only responsible for calling "mergeChangesFromContextDidSaveNotification:" was in fact merging what was just merged.
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(documentContentsChanged:) name:NSPersistentStoreDidImportUbiquitousContentChangesNotification object:document.managedObjectContext.persistentStoreCoordinator];
...
- (void)documentContentsChanged:(NSNotification *)notification {
[self.managedObjectContext mergeChangesFromContextDidSaveNotification:notification];
}
Calling "mergeChangesFromContextDidSaveNotification:" was the line responsible for triggering the "Optimistic locking failure". I removed it and everything started working as expected. Unfortunately this only lasted a couple of hours. Now I keep getting the "iCloud Timed Out" error, but this one I'm sure it's Apple's fault.
Anyway, after a ton of bugs and three different iCloud + Core Data approaches, I think I will hold on integrating iCloud into my app. It is far too unstable and buggy. I really wish Apple could have fixed this with iOS6, iCloud + Core Data is a very powerful tool, unfortunately, it is not ready yet.
Thanks to everyone who tried to help.
I have an iPad app, and I want to do this
-(IBAction) clicked {
image=download(#"http://....."); // this is on the main thread
}
The download function is going to call a whole bunch of non blocking functions to download a file from the internet, but download itself shouldn't return until the image is downloaded.
While the program is waiting for the download at the image=download(...) line above, I want the UI to be able to still function, for example be able to scroll a UITableView, click another button etc.
So what I did was this inside the download function I used a RunLoop
-(void) download:(NSString *)url
{
BOOL stillDownloading=TRUE;
while(stillDownloading) {
stillDownloading=downloadAFwBytes(...);
CFRunLoopRunInMode(kCFRunLoopCommonModes, 0, YES);
}
}
I thought the CFRunLoopRunInMode function will keep pumping UI messages, touches, scrolls through the main UI thread so that the UI will keep working and not freeze until the download finished, but for some reason, it only works for a short time, and eventually the UI freezes.
Do you know why, or how to fix?
The download function is called everywhere in the program, that expects it to wait for the download, so I can't change it to non blocking at the moment.
The direct answer to your question is, no, this is not what CFRunLoopRunInMode does. What you are effectively trying to do is have the current run loop "yield" so execution can continue while the loading operation continues. This is not how iOS and run loops work. Your download function blocks the thread it is on until downloading is complete so the only solution to your issue is to change the implementation so that downloading occurs on a background thread and the objects that care are notified when it is complete. Here's a relatively small change that can get you on the right track. This overall topic (concurrency, managing background tasks) is a bigger discussion and there are different considerations/tradeoffs. I'll cut to the chase and hopefully get you on the right track.
Define a couple NSNotification's that your download method can post for interested objects to observe:
// in the .h file of the class performing the download
extern NSString * const MyClassLoadingDidStartNotification;
extern NSString * const MyClassLoadingDidFinishNotification;
// in the .m file of the class performing the download
NSString * const MyClassLoadingDidStartNotification = #"MyClassLoadingDidStart";
NSString * const MyClassLoadingDidFinishNotification = #"MyClassLoadingDidFinish";
In your download routine, do the download in the background and post the appropriate notifications:
-(void) download:(NSString *)url
{
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[[NSNotificationCenter defaultCenter] postNotificationName:MyClassLoadingDidStartNotification object:self];
BOOL stillDownloading=TRUE;
while(stillDownloading) {
stillDownloading=downloadAFwBytes(...);
}
[[NSNotificationCenter defaultCenter] postNotificationName:MyClassLoadingDidFinishNotification object:self];
});
}
In any object that initiates a download, observe and handle the notifications
// in any class that initiates a download
- (void)init...
{
self = [super init...];
if (self) {
// other initialization
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(didStartLoading:) name:MyClassLoadingDidStartNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(didFinishLoading:) name:MyClassLoadingDidFinishNotification object:nil];
}
return self;
}
- (void)dealloc
{
[[NSNotificationCenter defaultCenter] removeObserver:self name:MyClassLoadingDidStartNotification object:nil];
[[NSNotificationCenter defaultCenter] removeObserver:self name:MyClassLoadingDidFinishNotification object:nil];
}
- (void)didStartLoading:(NSNotification *)notification
{
// update UI to show loading status (make sure you do UI changes on main thread)
// optionally check notification.object to ensure it's the loader class instance you care about
}
- (void)didFinishLoading:(NSNotification *)notification
{
// update UI to show loading status (make sure you do UI changes on main thread)
// optionally check notification.object to ensure it's the loader class instance you care about
}
Keep in mind that this is a very basic starting point. As you learn more and decide what you need you will definitely customize it. For example, you may want to add error handling, limit concurrent loading operations, provide other loading status, etc.
There are some architecture concerns with your question, but to properly download an image in the background and load it into a UIImageView or use it in any other way, I'd suggest taking a look at AFNetworking and read through their sample code for downloading resources.
Can you request to download the image on the background and then, return a static image asap in your method?
This way, the main thread returns fast and depending on your architecture, your background thread can update the image once it got it?
I think we need more details on your code (your download method is void so how is the downloaded data used?) to be able to really help.
Along with the 3 other guys telling you the same thing, I'll tell you to forget about your own run loop model, and just use the async download capabilities (assuming you're downloading over the net). You don't even have to build a thread, just start the async downloads and they will tell you when they're done. If you don't have the time to code it right, when will you have the time to fix your code?
Not sure if this is solved yet but can't you use?
[self.operationQueue addOperationWithBlock:^{
[self MethodName];
}];
and when you want something to happen in the UI like table updates then put this in the method code:
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
////perform something here ////
}];
hope this helps.
For a game I'm developing, I have several model classes that trigger notifications when their state changes. Then, the view subscribes to those notifications and can react on them.
I'm doing my unit tests for the model with OCUnit, and want to assert that the expected notifications were posted. For that, I'm doing something like this:
- (void)testSomething {
[[NSNotificationCenter defaultCenter] addObserver:notifications selector:#selector(addObject:) name:kNotificationMoved object:board];
Board *board = [[Board alloc] init];
Tile *tile = [Tile newTile];
[board addTile:tile];
[board move:tile];
STAssertEquals((NSUInteger)1, [notifications count], nil);
// Assert the contents of the userInfo as well here
[board release];
}
The idea is that the NSNotificationCenter will add the notifications to the NSMutableArray by calling its addObject: method.
When I run it, however, I see that addObject: is being sent to some other object (not my NSMutableArray) causing OCUnit to stop working. However, if I comment out some code (such as the release calls, or add a new unit test) everything starts working as expected.
I'm assuming this has to o with a timing issue, or NSNotificationCenter relying on the run loop in some way.
Is there any recommendation to test this? I know I could add a setter in Board and inject my own NSNotificationCenter, but I'm looking for a quicker way to do it (maybe some trick on how to replace the NSNotificationCenter dynamically).
Found the problem. When testing notifications you need to remove the observer after you have tested it. Working code:
- (void)testSomething {
[[NSNotificationCenter defaultCenter] addObserver:notifications selector:#selector(addObject:) name:kNotificationMoved object:board];
Board *board = [[Board alloc] init];
Tile *tile = [Tile newTile];
[board addTile:tile];
[board move:tile];
STAssertEquals((NSUInteger)1, [notifications count], nil);
// Assert the contents of the userInfo as well here
[board release];
[[NSNotificationCenter defaultCenter] removeObserver:notifications name:kNotificationMoved object:board];
}
If you fail to remove the observer, after a test runs and some local variables are released, the notification center will try to notify those old objects when running any subsequent test that triggers the same notification.
There are no timing issues or runloop related problems since everything in your code is non-concurrent and should be executed immediately. NSNotificationCenter only postpones notification delivery if you use an NSNotificationQueue.
I think everything is correct in the snippet you posted. Maybe there's an issue with the mutable array 'notifications'. Did you init and retain it correctly? Try to add some object manually instead of using the notification trick.
If you suspect your tests have timing issues - you may want to consider injecting your own notification mechanism into your board object (which is probably just a wrapper of the existing apple version).
That is:
Board *board = [[Board alloc] initWithNotifier: someOtherNotifierConformingToAProtocol];
Presumably your board object posts some notification - you would use your injected notifier in that code:
-(void) someBoardMethod {
// ....
// Send your notification indirectly through your object
[myNotifier pushUpdateNotification: myAttribute];
}
In your test - you now have a level of indirection that you can use for testing, so you can implement a test class the conforms to your AProtocol - and maybe counts up the pushUpdateNotification: calls. In your real code you encapsulate the code you probably already have in Board that does the notification.
This of course is a classic example of where MockObjects are useful - and there is OCMock which well let you do this without having to have a test class to do the counting (see: http://www.mulle-kybernetik.com/software/OCMock/)
your test would problably have a line something like:
[[myMockNotifer expect] pushUpdateNotification: someAttribute];
Alternatively you could consider using a delegate instead of notifications. There is a good pro/con set of slides here: http://www.slideshare.net/360conferences/nsnotificationcenter-vs-appdelegate.