autosaveWithCompletionHandler: (UIDocument) overriding issue - iphone

Not overriding autosaveWithCompletionHandler:, whenever the document is changed(
[doc updateChangeCount: UIDocumentChangeDone]) autosaveWithCompletionHandler: is periodically called.
But if I override this method, it is called only once.
Document has been changed -> Time is passing... -> Overrided method has been called -> Document has been changed -> Time is passing... -> Time is passing... -> Document has been changed -> Time is passing... -> Time is passing...
I make the document change by calling [doc updateChangeCount: UIDocumentChangeDone].
(overriding method)
- (void) autosaveWithCompletionHandler: (void (^(BOOL success))completionHandler {
if ([self hasUnsavedChanges]) {
[self saveToURL: self.fileURL forSaveOperation: UIDocumentSaveForOverwriting completionHandler: ^(BOOL success) {
if (success) {
NSLog(#"%# has been autosaved", [self description]);
completionHandler(YES);
}
else {
NSLog(#"Failed to autosave %#", [self description]);
completionHandler(NO);
}
}];
}
} // autosaveWithCompletionHandler:
Thank you for your reading.

You shouldn't be overriding saveWithCompletionHandler: or autosaveWithCompletionHandler:; those methods make changes to private properties which help the system to deterine whether the object needs saving, and when you override the methods those changes don't get made. Instead, you should be overriding contentsForType:error:.

Related

Use variable inside Firebase snapshot function [duplicate]

I am wondering how to do the following correctly: I have a method that is to return an NSData object. It gets the NSData object from a UIDocument. The NSData object can get large, so I want to make sure it is fully loaded before the response starts. I would therefore like to return the value of the method from within the block itself. So something like this:
- (NSData*)getMyData {
MyUIDocument *doc = [[MyUIDocument alloc] initWithFileURL:fileURL];
[doc openWithCompletionHandler:^(BOOL success) {
if (success) {
return doc.myResponseData; // this is to be the return for the method not the block
}
}];
}
This causes an error because the return apparently refers to the block's return.
How can I accomplish this without having to make a thread blocking wait/while loop?
Thanks.
You can't. Embrace the fact that what you're trying to do is asynchronous and add a completion block parameter to your getMyData method which is called when the inner completion handler is called. (And remove the return from the method signature):
- (void)getMyDataWithCompletion:(void(^)(NSData *data))completion {
MyUIDocument *doc = [[MyUIDocument alloc] initWithFileURL:fileURL];
[doc openWithCompletionHandler:^(BOOL success) {
completion((success ? doc.myResponseData : nil));
}];
}
The same problem exists in swift and you can add a similar completion block:
func getMyData(completion: ((data: NSData?) -> Void) {
data = ...
completion(data)
}
The open method is asynchronous which is why you have to provide a block to be run when the open is completed. You need to copy this and make your method also receive a block of code that you will execute when the open is finished.
You should also pass through the success argument of the call you are wrapping or create an error, you need to do this so that the calling code can take the right action.
- (void)getMyDataWithCompletion:(void(^)(NSData *data, BOOL success))completion
{
MyUIDocument *doc = [[MyUIDocument alloc] initWithFileURL:fileURL];
[doc openWithCompletionHandler:^(BOOL success) {
completion(doc.myResponseData, success);
}];
}
Following Are method how to declare method with completionHandler:
Objective-C
- (void)getMyDataWithCompletionHandler:(void(^)(NSString *str))completionHandler
{
completionHandler(#"Test");
}
Swift-3
func showDatePicker(superc: UIViewController, completionHandler:#escaping (String) -> Void) {
completionHandler("Test")
}

CoreData thread safe object context save [duplicate]

The following method gets called in order to populate my Core-Data after AFNetworking fetches information from my app server.
The information seems to be perfectly working as when the table is updated I can see the new information being updated in the UITableView.
Now the problem that I have is that even tho I can see the information ( after it has been fetches from the server, stored into Core-data and refetches to display in my UITableView) If I then go and close my app and re open it, the information is not there anymore.
It seems as if the information is not persistent and the problem seems to be the thread. given that if I remove the thread option in my method everything works fine.
What am I missing?? I have tried most things that I came across but I can't seem to find a solution.
NSManagedObjectContext *childContext = [[NSManagedObjectContext alloc]
initWithConcurrencyType:NSPrivateQueueConcurrencyType];
childContext.parentContext = managedObjectContext;
myModel.context = childContext;
[childContext performBlock:^{
// ... Lots Controller logic code that then calls the class myModel where all my Core-Data save methods are
// Sort Wall Pictures
if ( [dataHolder[#"verb"] isEqualToString:#"addWallPicture"] ) {
data = #{ #"resourceID":dataHolder[#"_id"][#"$id"],
#"resourceName":dataHolder[#"details"][#"resourceName"],
#"author":#{ #"id":dataHolder[#"userId"][#"$id"],
#"username":dataHolder[#"details"][#"authorName"] },
#"likesNumber":#0,
#"likesPeople":#[]
};
[myModel saveSocialWall:data date:date verb:dataHolder[#"verb"] code:dataHolder[#"_id"][#"$id"] myUser:myUser];
continue;
}
[childContext save:&error];
}];
You have to save the main context as well at some point, e.g. after saving the child context.
Saving the child context saves only to the main context, and saving the main context saves to the store file.
Like this (written on the phone, there will
be syntax errors):
// ...
[childContext save:&error];
[mainContext performBlock:^{
[mainContext save:&error];
}];
In Swift 2.0 that would be:
do {
try childContext.save()
mainContext.performBlock {
do {
try mainContext.save()
} catch let err as NSError {
print("Could not save main context: \(err.localizedDescription)")
}
}
} catch let err as NSError {
print("Could not save private context: \(err.localizedDescription)")
}

Method inside another method completion wait

How can I wait for some method to complete and then continue work ??
- (void)loadMoreDataOnBottomOfTableview
{
NSLog(#"LOADING ITEMS ON BOTTOM");
[self refreshStream];
[self.mainTableView reloadData];
...
}
So I need to wait refreshStream method to complete and then reload tableview data and rest of loadMoreDataOnBottomOfTableview (...).
Use a completion block. That's what they were designed for.
See the completion handler section in this guide. http://developer.apple.com/library/ios/#featuredarticles/Short_Practical_Guide_Blocks/index.html
Redefine refreshStream
-(void)refreshStream:(void (^)(void))complete;
-(void)loadMoreDataOnBottomOfTableview
{
[self refreshStream:^{
[self.mainTableView reloadData];
}];
}
This should do you right also check out this page, using typedef is the unspoken standard.
http://developer.apple.com/library/mac/#featuredarticles/BlocksGCD/_index.html
[self performSelectorOnMainThread:<#(SEL)#> withObject:<#(id)#> waitUntilDone:<#(BOOL)#>];
You can call your method using this.
I can answer your query in swift. Similarly you can use completion block in your code to achieve the task.
class TestClosure
{
func calculateAdditionData() {
countNumbers({ (result) -> Void in
println("[self refreshStream] completed")
//[self.mainTableView reloadData];
})
}
func refreshStream(completion: (() -> Void)!) {
//Your refresh code
completion()
}
}
Completion blocks/Closures are a proper way to wait for something to complete.
You can use performSelectorOnMainThread:withObject:waitUntilDone: as follows:
[self performSelectorOnMainThread:#selector(refreshStream) withObject:nil waitUntilDone:YES]
[self.mainTableView reloadData];
Note however that this is NOT a recommended design pattern. Async calls should use callbacks (your refreshStream method should call back to a method in your view controller which should then trigger reloadData

Block inside block = EXC_BAD_ACCESS

I have a singleton class the handle all the Game Center logic:
typedef void (^GameCenterCallbackFinishUpdating)();
- (void)getAllMatches:(GameCenterCallbackFinishUpdating)onComplete
{
[GKTurnBasedMatch loadMatchesWithCompletionHandler:^(NSArray *matches, NSError *error)
{
//Do stuff here...
onComplete();
}];
}
From another viewController I use:
[[GameCenterHelper sharedHelper] getAllMatches:^{
[self.myTableView reloadData];
}];
It works great when I'm in the app, but once I close the app (background) and then start it up again, I get:
onComplete(); ---- Thread 1: EXC_BAD_ACCESS (code=2, address=0xc)
What am I doing wrong here?
some background info: the blocks are objects and if any block is nil and you try to call them, it crashes the application.
somewhere and somehow the block onComplete becomes nil before you call it. the following if (...) statement helps you to prevent to call a nil pointer, so the application won't crash.
if (onComplete) onComplete();
Thanks to #holex and #Paul.s for explaining it well.
I had the similar situation where I was sending block as method parameter(completionHandler).
- (void)callX:(NSString *)xyz withCompletionHandler:(void (^)(NSString *response))completion
{
completion(something);
}
And there are two situations either I am using this block like:
[MyClass sharedInstance] callX:#"abc" withCompletionHandler:^(NSString *response) {
if (response) {
//do something
}
}];
or this block could be nil as method parameter:
[MyClass sharedInstance] callX:#"abc" withCompletionHandler:nil];
In second case when block was being passed nil as method parameter this caused EXC_BAD_ACCESS on completion(). So as #holex states that the blocks are objects and if any block is nil and you try to call them, it crashes the application.
A single if saves lot of my time
- (void)callX:(NSString *)xyz withCompletionHandler:(void (^)(NSString *response))completion
{
if (completion)
completion(something);
}
P.S: this explanation only for NERDS like me. | ' L ' |

Passing an object between 2 view controllers not working

I'm trying to pass 3 objects to another VC but they are showing up as Null in the destination VC.
VC1:
- (void)specificExerciseTableViewController:(SpecificExerciseTableViewController *)specificExerciseTableViewController didSelectSpecificExerciseWithURL:(NSString *)exerciseURL muscleName:(NSString *)muscleName muscleURL:(NSString *)muscleURL;
{
[self addExercise];
}
-(void)addExercise
{
PFObject *exerciseInRoutine = [[PFObject alloc] initWithClassName:#"exerciseInRoutine"];
[exerciseInRoutine setObject:self.selectedExercise forKey:#"name"];
[exerciseInRoutine setObject:self.muscleName forKey:#"muscle"];
[exerciseInRoutine setObject:self.muscleURL forKey:#"picture"];
[exerciseInRoutine saveInBackgroundWithBlock:^(BOOL succeeded, NSError *error) {
if (!error) {
[self.tableView reloadData];
} else {
NSLog(#"Error: %# %#", error, [error userInfo]);
}
}];
}
The class that is passing the objects to VC1:
if ([self.delegate respondsToSelector:#selector(specificExerciseTableViewController:didSelectSpecificExerciseWithURL:muscleName:muscleURL:)])
{
NSString *exerciseName = [[self.exerciseArray objectAtIndex:selectedRowIndex.row] objectForKey:#"exerciseName"];
[self.delegate specificExerciseTableViewController:self didSelectSpecificExerciseWithURL:exerciseName muscleName:self.muscleName muscleURL:self.muscleURL];
[self dismissModalViewControllerAnimated:YES];
}
Edit:
I've updated my method to set the objects to the VC's properties, but have same problem:
- (void)specificExerciseTableViewController:(SpecificExerciseTableViewController *)specificExerciseTableViewController didSelectSpecificExerciseWithURL:(NSString *)exerciseURL muscleName:(NSString *)muscleName muscleURL:(NSString *)muscleURL;
{
self.muscleName = exerciseURL;
self.muscleName = muscleName;
self.muscleURL = muscleURL;
[self addExercise];
}
What do you expect to happen when you execute the code shown above?
You second code block calls -specificExerciseTableViewController:didSelectSpecificExerciseWithURL:muscleName:muscleURL: on some delegate but the implementation of that method in your "VC1" example ignores the arguments passed to that method. You don't seem to be doing anything with the data you are given.
The problem is you are passing arguments to the delgate object.but inside the delegate method you didnt either pass the arguments to the function addExercise or assign the parameters to your properties before you call the metho.