Calling a method which executes delegate methods - iphone

I have a view controller called myViewController. Then i have an NSObject called myLogic. myLogic has a method called executeLogic() which will call some delegate methods. (Im guessing the delegate methods are called in separate threads). I am calling executeLogic() from myViewController and i want it to execute all the delegate methods before returning to execute the next command on myViewController. Right now i get this error
[URLRequestDBStoreLogic respondsToSelector:]: message sent to deallocated instance 0x5d2cff0
which i am guessing is because it returns to the main thread before the delegate methods are called? I am not sure. Any solutions? Will be deeply grateful. :)
This is where the URLRequestDBStoreLogic class is called (note this is an NSObject Class)
URLRequestDBStoreLogic * urlRequestLogicDBStoreLogic = [[URLRequestDBStoreLogic alloc]init];
[urlRequestLogicDBStoreLogic loadObjectsAtRemoteServer];
This is what the method that is called does
- (void) loadObjectsAtRemoteServer {
RKURL * url = [RKURL URLWithBaseURLString:#"http://y.co.uk/share/proxy/alfresco/slingshot/search?site=&term=&tag=x&maxResults=251&sort=&query=&repo=false"];
objectManager = [RKObjectManager managerWithBaseURL:url];
//Some other Code
[[RKObjectManager sharedManager] loadObjectsAtResourcePath:#"?tag=x" delegate:self];
}
and this is where it breaks in the if statement.
// Setup the NSURLRequest. The request must be prepared right before dispatching
- (BOOL)prepareURLRequest
{
[_URLRequest setHTTPMethod:[self HTTPMethod]];
if ([self.delegate respondsToSelector:#selector(requestWillPrepareForSend:)]) {
[self.delegate requestWillPrepareForSend:self];
}
}

Related

iOS Blocks - use of undeclared identifier self

I am new to blocks. I am inside a singleton and I do this
void (^ myBlock)() = ^(){ [self doStuff]; };
I receive this error use of undeclared identifier self.
doStuff is a method inside the singleton.
but if this block is declared inside another method, Xcode is OK.
Why is that? thanks.
you can define the block in your interface and initialize in any of your methods (including initializers ) in your #implementation file like below:
#interface YourClass {
void (^ myBlock)();
}
#implementation YourClass
- (void)yourMethod {
myBlock = ^(){ [self doStuff]; };
}
#end
You shouldn't call self directly in a block.
Rather you should make a safe block-pointer from self and access it inside your block.
__block id safeBlockSelf = self;
void (^ myBlock)() = ^(){ [safeBlockSelf doSomething]; };
See How do I avoid capturing self in blocks when implementing an API? for more details.
because every method gets passed self as a hidden param. self is a variable like any other and the block can 'see it/capture it' if in the method
if it is not in a method, self is not a variable set anywhere and the block cant 'see it'

How to ensure NSManagedObjectContext when opened asynchronously through UIManagedDocument

I have an application with different controllers that all operate on the same NSManagedObjectContext.
My approach was to initialize the NSManagedObjectContext in my AppDelegate and inject it into all the controllers.
I am initializing my NSManagedObjectContext by opening a UIManagedDocument like this:
UIManagedDocument* databaseDoc = [[UIManagedDocument alloc] initWithFileURL:url];
if (![[NSFileManager defaultManager] fileExistsAtPath:[databaseDoc.fileURL path]]) {
[databaseDoc saveToURL:databaseDoc.fileURL forSaveOperation:UIDocumentSaveForCreating completionHandler:^(BOOL success) {
myController.managedObjectContext = databaseDoc.managedObjectContext;
}];
} else if (databaseDoc.documentState == UIDocumentStateClosed) {
[databaseDoc openWithCompletionHandler:^(BOOL success) {
myController.managedObjectContext = databaseDoc.managedObjectContext;
}];
} else if (databaseDoc.documentState == UIDocumentStateNormal){
myController.managedObjectContext = databaseDoc.managedObjectContext;
}
Now my problem is, that opening the UIManagedDocument happens asynchronously and the NSManagedObjectContext is only available in the completion block.
How do I ensure that the controllers always have a valid NSManagedObjectContext to work with? Of course the problems happen at startup i.e. when a controller wants to use the NSManagedObjectContext in his "viewDidLoad" method, and the completion block in the AppDelegate has not yet run ...
One approach would probably be to "wait" in the AppDelegate until the UIDocument has opened, but as far as I gather this is not recommended ...
I would like to avoid to "pollute" my controllers with code that deals with the asynchronous nature of opening a NSManagedObjectContext... but maybe this is a naive wish?
In your appDelegate:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
MyWaitViewController* waitController = [[MyWaitViewController new] autorelease];
self.window.rootViewController = waitController;
// then somewheres else, when you get your context
[databaseDoc saveToURL:databaseDoc.fileURL forSaveOperation:UIDocumentSaveForCreating completionHandler:^(BOOL success) {
myContextController.managedObjectContext = databaseDoc.managedObjectContext;
self.window.rootViewController = myContextController;
// note that at this point when the viewDidLoad method will get called
// it will have his managedObjectContext and his view already available.
// you can change your rootController, or push another viewController into the
// stack. Depending on what u want from the GUI side
}];
return YES;
}
Note that you dispose the GUI logic into the MyWaitViewController + AppDelegate side. But you keep your "myContextController" away from that logic control, since he get called / created only when a context exist.
I was struggling with the same issue, and I came up with it by using NSNotificationCenter.
When initializing your NSManagedObjectContext in the success handler, add send a notification.
Then, add a listener to to the viewDidLoad of whatever your first ViewController is.
I used that listener to call a reloadData method. In a heavy app, this could be a problem, as the viewcontroller loads blank, and then reloads the data, but this is a lite one, and it's noticeable at all - the viewController loads instantaneously with the managedObjectContext.

Strange custom delegate actions

Ok -- this one is weird. I have a singleton class that loads information from an XML file. I am using a delegate definition as follows (I define the delegate in a separate header file to make life easier):
#protocol ResourceClassDelegate <NSObject>
#optional
- (void)picturesDidStartLoading;
- (void)picturesDidFinishLoading;
#end
In the resource file, the delegate is defined correctly (I believe):
#property (assign) id<ResourceClassDelegate> delegate;
When using the delegate, the code in the resource class is as follows:
-(void)refreshPiecesOfHistoryWithOperation {
NSLog(#"Operation Started");
if ([delegate respondsToSelector:#selector(picturesDidStartLoading)])
[delegate picturesDidStartLoading];
self.picturePacks = [HistoryXMLParser loadPicturePacks];
[self.allPiecesOfHistory removeAllObjects];
// now lets put all of them in one big file...
for (PicturePack *pp in self.picturePacks) {
for (int ct = 0; ct < [[pp piecesOfHistory] count] ; ct++) {
[self.allPiecesOfHistory addObject:(PieceOfHistory *)[[pp piecesOfHistory] objectAtIndex:ct]];
}
}
NSLog(#"Operation Ended");
if ([delegate respondsToSelector:#selector(picturesDidFinishLoading)])
[delegate picturesDidFinishLoading];
}
Now... in the class that is listening to the delegate, it is assigned:
- (void)viewDidLoad {
[super viewDidLoad];
// now for the part that makes the loading all happen...
[[ResourceClass sharedResourceClass] setDelegate:self];
}
And in the listening class, the methods are defined....
#pragma mark ResourceClassDelegate
-(void)picturesDidStartLoading {
if (loadingActivity == nil)
loadingActivity = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhiteLarge];
[self.view addSubview:loadingActivity];
[loadingActivity setCenter:[self.view center]];
[loadingActivity startAnimating];
}
-(void)picturesDidFinishLoading {
if (loadingActivity != nil) {
[loadingActivity stopAnimating];
[loadingActivity removeFromSuperview];
}
[self.tableView reloadData];
}
Now for the problem... every single time, in the listening class, the method (void)picturesDidFinishLoading is called. The method (void)picturesDidStartLoading never is called.
When I debug the code, in the resource class, the line
if ([delegate respondsToSelector:#selector(picturesDidStartLoading)])
[delegate picturesDidStartLoading];
never reaches the delegate method call - even if I remove the if statement. The line
if ([delegate respondsToSelector:#selector(picturesDidFinishLoading)])
[delegate picturesDidFinishLoading];
is always called.
any ideas?
Ok -- I figured it out....
The delegate was nil during the first call. The reason it is nil is because the function using the delegate was called in the source during the init method. The init method was not complete when the first test of the delegate was performed. At this time the delegate was nil because it is not instantiated until the the init method completes. The reason the second test of the delegate worked is because I submitted the process using an NSOperationQueue.
To fix the problem I have to move things around a bit... it's all about the timing!
Well now that was fun....
That's weird, try to remove #optional in the protocol declaration, and see if you get some warnings.
Try to print a log inside the method as well, other than that it looks fine.

problem with delegate of object which is delegate of NSURLConnection

I've a class PictureDownloader for the purpose of asynchronously loading images from a server. It assigns itself as a delegate of NSURLConnection and as such, is retained by NSURLConnection. I create several of those PictureDownloader in a DetailViewController to fetch the corresponding images, so the DetailViewController is a delegate of each PictureDownloader.
When the user leaves the DetailViewController, all remaining downloads are cancelled, however sometimes it seems to be the case, that a PictureDownloader has finished loading an image (connectionDidFinishedLoading called) before the connection was cancelled, but the DetailViewController doesn't exist anymore (but the PictureDownloader does, because it's retained by NSURLConnection), so the call
[self.delegate didLoadPictureWithID:self.ID];
inside PictureDownloader will give an EXC_BAD_ACCESS or sometimes a "unrecognized selector sent to instance".
Here are the relevant parts of the source code:
creation of the PictureDownloader inside the DetailViewController
- (void)startPictureDownload:(Picture *)pic withPictureId:(NSString *)pId forID:(int)ID
{
PictureDownloader *downloader = [self.downloadsInProgress objectForKey:[NSNumber numberWithInt:ID]];
if(!downloader)
{
downloader = [[PictureDownloader alloc] init];
downloader.picture = pic;
downloader.pictureId = pId;
downloader.ID = ID;
downloader.delegate = self;
[self.downloadsInProgress setObject:downloader forKey:[NSNumber numberWithInt:ID]];
[downloader startDownload];
[downloader release];
}
}
canceling the downloads (called when the DetailViewController returns to the overview)
- (void)cancelAllDownloads
{
[self.downloadsInProgress enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop){
[obj cancelDownload];
}];
}
delegate method which is called when PictureDownloader finished loading
- (void)didLoadPictureWithID:(int)dID;
{
PictureDownloader *downloader = [self.downloadsInProgress objectForKey:[NSNumber numberWithInt:dID]];
if(downloader)
{
UIImageView *imageView = (UIImageView *)[self.view viewWithTag:dID];
imageView.image = [UIImage imageWithData:downloader.imageData];
[self.downloadsInProgress removeObjectForKey:[NSNumber numberWithInt:dID]];
}
}
cancelDownload method inside PictureDownloader
- (void)cancelDownload
{
[self.imageConnection cancel];
self.imageConnection = nil;
self.imageData = nil;
}
connectionDidFinishedLoading inside PictureDownloader
- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{
if(self.picture)
{
self.picture.data = self.imageData;
NSError *error = nil;
[self.picture.managedObjectContext save:&error];
}
if(self.delegate != nil && [self.delegate respondsToSelector:#selector(didLoadPictureWithID:)] ) //place of failure
[self.delegate didLoadPictureWithID:self.ID];
self.imageData = nil;
self.imageConnection = nil;
}
Can someone give me a hint, how I can deal with this problem?
Help is much appreciated.
To avoid situations like this, I usually add a check like this at the top of connectionDidFinishLoading: and other NSURLConnection delegate methods:
if (connection != self.imageConnection) return;
As another option, you could set the delegate on each PictureDownloader to nil as you cancel it in cancelAllDownloads. Or you could set self.delegate = nil in cancelDownload.
You should check for the existence of the delegate object (and ideally the method/selector) before you attempt to make the call.
For example:
if(self.delegate && [[self.delegate] respondsToSelector:#selector(didLoadPictureWithID:)]) {
...
}
By doing this, you'll ensure that your not attempting to call a delegate that's no longer there. For more information on the respondsToSelector method, see the NSObject Protocol Reference.
When your DetailViewController goes out of scope - dealloc -, set the PictureDownloader's delegate property to nil as well.
Your issue is interesting in that the NSUrlConnection's delegate can't be set to nil in the same way. e.g. When you're PictureDownloader is de-alloced. All you can do is cancel the NSUrlConnection.
The NSURLConnection docs say the following:
Unless a NSURLConnection receives a cancel message, the delegate will receive one and only one of connectionDidFinishLoading:, or connection:didFailWithError: message, but never both. In addition, once either of messages are sent, the delegate will receive no further messages for the given NSURLConnection.
Indicating that the you can verify that the delegate will not be called back after the above messages are received.

Iphone stop an ASIFormDataRequest

i have a problem in my viewController when i have a pending ASIFormDataRequest (started as an asynchronous task) that is still executing and the user presses the back button (in order to pop the view).
Is there any way to stop that asynchronous task?
I have read that is a method called "clearDelegatesAndCancel" but i don't know if it is what i'm looking for.
Thanks
Thing is, to call clearDelegatesAndCancel, you have to have a handle to the ASIFromDataRequest object that's running asynchronously. That means you should set it up as an property, like...
#interface MyViewController : UIViewController <ASIHTTPRequestDelegate>
{
ASIFormDataRequest *theRequest
...
}
#property (nonatomic, retain) ASIFormDataRequest *theRequest;
Then in your .m, don't declare a new request object, just assign your formdatarequest to the class's iVar:
#synthesize theRequest;
-(void)viewDidLoad //or whatever
{
self.theRequest = [ASIFormDataRequest requestWithUrl:myUrl];
// then configure and fire the request, being sure to set .delegate to self
}
-(void)viewWillDisappear:(BOOL)animated //or whatever
{
[self.theRequest clearDelegatesAndCancel];
}
-(void)dealloc
{
[theRequest release]; //don't not do this.
}
Point is, you need to set yourself up so that you've GOT the request to talk to while it's running asynchronously.
By the way, this is REALLY good practice. If your viewcontroller goes away (say by getting popped off the UINavController stack) before your request returns, it'll try to call the delegate method on a deallocated object, and boom.
From ASI docs (http://allseeing-i.com/ASIHTTPRequest/How-to-use)
To cancel an asynchronous request (either a request that was started with
[request startAsynchronous] or a request running in a queue you created),
call [request cancel]. Note that you cannot cancel a synchronous request.
Note that when you cancel a request, the request will treat that as an error,
and will call your delegate and/or queue’s failure delegate method. If you do
not want this behaviour, set your delegate to nil before calling cancel, or
use the clearDelegatesAndCancel method instead.