NSBlockOperation calling a method inside NSOperation - iphone

I have a question.
I have the following code:
NSBlockOperation *op=[NSBlockOperation blockOperationWithBlock:^{
[[ClassA sharedInstance] someSingletonMethod:params1];
[ClassB classBMethod:params2];
[self currentClassMethod:params3];
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
[[NSNotificationCenter defaultCenter] postNotificationName:#"kSNotificationName" object:nil];
}];
}];
[self.myOperationQueue addOperation:op];
Is it safe to call singleton methods in a block? Is it safe to call class methods in a block? Is it safe to call "self" methods?
I have a following situation. I'm sending a batch of requests to server:
AFHTTPClient *client=[[AFHTTPClient alloc] initWithBaseURL:[NSURL URLWithString:baseURL]];
[client registerHTTPOperationClass:[AFJSONRequestOperation class]];
[client enqueueBatchOfHTTPRequestOperations:reqOps progressBlock:^(NSUInteger numberOfFinishedOperations, NSUInteger totalNumberOfOperations) {
NSLog(#"finished: %i of %i requests", numberOfFinishedOperations, totalNumberOfOperations);
[[PTDictionaryUpdate sharedInstance] debugPrint:[NSString stringWithFormat:#"finished: %i of %i requests", numberOfFinishedOperations, totalNumberOfOperations]];
} completionBlock:^(NSArray *operations) {
NSLog(#"operations finished");
Here how I'm handling responses.
I'm creating operations to handle completed requests.
for (int i=0; i<[operations count]; i++)
{
AFJSONRequestOperation *operation=[operations objectAtIndex:i];
if ((operation.error==nil) && (operation.response.statusCode==200))
{
id JSON=operation.responseJSON;
int handleMethodIndex=-1;
for (int j=0; j<[urls count]; j++)
{
if ([operation.request.URL isEqual:[urls objectAtIndex:j]])
{
handleMethodIndex=j;
};
};
switch (handleMethodIndex) {
case 0:
{
//[self countryUpdate:JSON];
NSInvocationOperation *invOp=[[NSInvocationOperation alloc] initWithTarget:self selector:#selector(countryUpdate:) object:JSON];
[invOp setQueuePriority:NSOperationQueuePriorityLow];
[handleJSONOperations addObject:invOp];
break;
}
case 1:
{
//[self regionsUpdate:JSON];
NSInvocationOperation *invOp=[[NSInvocationOperation alloc] initWithTarget:self selector:#selector(regionsUpdate:) object:JSON];
[invOp setQueuePriority:NSOperationQueuePriorityLow];
[handleJSONOperations addObject:invOp];
break;
}
//.......
//.......
}
After I created an array with operations which will handle (process and update database) JSON that I pulled from the server:
NSBlockOperation *op=[NSBlockOperation blockOperationWithBlock:^{
//first we need to tether countries, regions and cities
[[PTDataTetherer sharedInstance] tetherCountriesRegionsCitiesInContext:self.updateContext];
//generating fake agencies
//[PTFakeAgencyGenerator generateAgenciesInContext:context];
//generating fake clients
//[PTFakeClientGenerator generateClientsInContext:context];
//generating fake reports
[[PTFakeReportGenerator sharedInstance] generateReportsInContext:self.updateContext];
//generating fake presentations
[[PTFakePresentationGenerator sharedInstance] generatePresentationsInContext:self.updateContext];
//tethering
[[PTDataTetherer sharedInstance] tetherAgenciesWithOthersInContext:self.updateContext];
[[PTDataTetherer sharedInstance] tetherClientsWithOthersInContext:self.updateContext];
[[PTDataTetherer sharedInstance] tetherEventsWithOthersInContext:self.updateContext];
[[PTDataTetherer sharedInstance] tetherPresentationFoldersWithImagesInContext:self.updateContext];
[self saveContext];
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
[[NSNotificationCenter defaultCenter] postNotificationName:#"kSynchronizationFinishedNotification" object:nil];
}];
}];
[op setQueuePriority:NSOperationQueuePriorityLow];
if ([handleJSONOperations count]==0)
{
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
[[NSNotificationCenter defaultCenter] postNotificationName:#"kSynchronizationFinishedNotification" object:nil];
}];
}
else
{
[self.serverUpdateQueue addOperation:updateContextCreateOperation];
[handleJSONOperations addObject:op];
[self.serverUpdateQueue addOperations:handleJSONOperations waitUntilFinished:NO];
};
Basically I want to construct the queue in such way:
1. [context create operation]
2. [multiple context modify operations that will parse json received from server and save new/modify objects to/in context]
3. [some final methods that will also modify context and, at the end, that will call a save method to propagate changes to the storage and then using NSManagedObjectContextDidSaveNotifications to other context]

Is it safe to call singletons methods in block?
It's a bit board question, depends on what you inside your singleton's method.
Is it safe to call class methods in block?
Depends on what you do inside your method. From my experience and the code I do, yes.
Is it save to call "self" methods?
You are passing a reference of self to the block, which might cause a memory leak.

Related

Update UI after requestAccessToAccountsWithType

I'm developing an app to help me understand OBJECTIVE-X/OSX.
The app simply connects to Facebook and sends a notification using NSUserNotification.
It is working fine, but now I want to add some UI to the mix.
To make the example simpler, I want to update a label (NSTextField) to show the status of the Facebook connection.
Connecting…
Connected
Failed
I have the following code in one File FacebookRequest.m
- (void) connectFacebook{
if(self.account == nil){
self.account = [[ACAccountStore alloc]init];
}
ACAccountType *facebookAccount = [self.account
accountTypeWithAccountTypeIdentifier:ACAccountTypeIdentifierFacebook];
NSDictionary *options = #{
ACFacebookAppIdKey: #"MY_CODE",
ACFacebookPermissionsKey: #[#"email",
#"user_about_me",
#"user_likes",
#"manage_notifications",
#"user_activities"],
ACFacebookAudienceKey: ACFacebookAudienceFriends
};
[self.account requestAccessToAccountsWithType:facebookAccount
options:options
completion:^(BOOL success, NSError *error){
if(success){
NSArray *accounts = [self.account accountsWithAccountType:facebookAccount];
self.account = [accounts lastObject];
}
else{
NSLog(#"Erro %#", [error description]);
}
}];
}
and the following one in my AppDelegate.m
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
[self.statusFacebook setStringValue:#"Connecting…"];
FacebookRequest *request = [[FacebookRequest alloc]init];
[request connectFacebook];
}
What is the best way to update the UI after the request is complete and I have an account?
I'm having troubles since the request is asynchronous and I can't return any value inside the requestAccessToAccountsWithType block. Another point is that if I put some "ifs" to check if my account is nil after it, it will be executed before the block has finished executing, so the account would still be nil.
Thanks!
PS.: Sorry for the English if it is not clear enough.
You may use NSNotificationCenter for this purpose:
[self.account requestAccessToAccountsWithType:facebookAccount
options:options
completion:^(BOOL success, NSError *error){
if(success){
NSArray *accounts = [self.account accountsWithAccountType:facebookAccount];
self.account = [accounts lastObject];
// You post a notification that the UI should update here
[[NSNotificationCenter defaultCenter] postNotificationName:#"UpdateUI" object:nil];
}
else{
NSLog(#"Erro %#", [error description]);
}
}];
Then, you add your viewController that should update its UI as an observer of this notification:
- (void)viewDidLoad
{
[super viewDidLoad];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(updateUI) name:#"UpdateUI" object:nil];
}
- (void)updateUI {
// Here you actually update your UI
}
p.s. if you are not using arc you also remove the observer in dealloc:
- (void)dealloc {
[[NSNotificationCenter defaultCenter] removeObserver:self];

Objective-C GCD wait for Block to finish (AFNetworking)

I've been trying to experiment with some code from a tutorial, however not having much success due to not getting my head around GCD.
I have an class named API.m and here is the code regarding GCD:
+ (API *) sharedInstance
{
static API *sharedInstance = nil;
static dispatch_once_t oncePredicate;
dispatch_once(&oncePredicate, ^{
sharedInstance = [[self alloc] initWithBaseURL:[NSURL URLWithString:APIHost]];
});
return sharedInstance;
}
-(void)commandWithParams:(NSMutableDictionary*)params
onCompletion:(JSONResponseBlock)completionBlock
{
NSMutableURLRequest *apiRequest = [self multipartFormRequestWithMethod:#"POST"
path:APIPath
parameters:params
constructingBodyWithBlock: ^(id <AFMultipartFormData>formData) {
//TODO: attach file if needed
}];
AFJSONRequestOperation* operation = [[AFJSONRequestOperation alloc] initWithRequest: apiRequest];
[operation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {
//success!
completionBlock(responseObject);
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
//failure :(
completionBlock([NSDictionary dictionaryWithObject:[error localizedDescription] forKey:#"error"]);
}];
[operation start];
}
I make a simple test by implementing a button and getting an NSArray to print it's content to the output window:
- (IBAction)test:(id)sender {
NSMutableDictionary* params =[NSMutableDictionary dictionaryWithObjectsAndKeys:
#"pending", #"command",
[[[API sharedInstance] user] objectForKey:#"UserID"] , #"userID",
nil];
[[API sharedInstance] commandWithParams:params
onCompletion:^(NSDictionary *json) {
//result returned
if ([json objectForKey:#"error"]==nil) {
// Simple example
[self.users addObject:#"1"];
} else {
//error
[UIAlertView title:#"Error" withMessage:[json objectForKey:#"error"]];
}
}];
NSLog(#"%#", self.users);
}
Now when I first click the button an empty NSArray is printed to the output window, but when I press it again it print's "1". It's clear that the program is reaching NSLog before the completion block has time to fully execute. Could someone please help me modify the code so that I have the option to have the NSLog execute after the completion block has finished?
Not sure as to what you are trying to accomplish, but if the goal is to just have NSLog execute after the completion block, you can move the NSLog statement after
[self.users addObject:#"1"];
If you have some code which you want to execute after adding it to the array, you can have
[self methodName]; in the completion block and it will get called there.
Completion block, is the code which is run after execution of the code which you wanted run. The code which you wanted run, will happen asynchronously and on another thread. After that code is run, the completion block code will get executed.

returning UIImage from block

I have the following code:
- (UIImage *) getPublisherLogo
{
//check the cache if the logo already exists
NSString * imageUrl = [NSString stringWithFormat:#"%#/%#&image_type=icon", self.baseUrl, self.imageUrl_];
ASIHTTPRequest * imageRequest = [ASIHTTPRequest requestWithURL:[NSURL URLWithString:imageUrl]];
[imageRequest setTimeOutSeconds:30.0];
[imageRequest setDownloadCache:[ASIDownloadCache sharedCache]];
[imageRequest setCacheStoragePolicy:ASICachePermanentlyCacheStoragePolicy];
[imageRequest setCachePolicy:ASIAskServerIfModifiedWhenStaleCachePolicy|ASIFallbackToCacheIfLoadFailsCachePolicy];
[imageRequest setCompletionBlock:^(void){
UIImage *img = [UIImage imageWithData:[imageRequest responseData] ];
if (img){
return img;
}
}];
[imageRequest setFailedBlock:^(void){
NSLog(#"Error in pulling image for publisher %#", [[imageRequest error] userInfo]);
}];
[imageRequest startAsynchronous];
}
}
The issue is that the return value/UIImage is returned at a block. How do I avoid this?
You're unable to return anything from the completion block because it's returned void.
You'll probably need to create a new method like setLogo:(UIImage *)image on the object that's expecting the image to be set, and call that method from within the completion block.
You can place your img pointer outside the block and declare it __BLOCK and use it as a closure. But you really need to be asking yourself what do you plan to do with img, bearing in mind the call is made asynchronously. I would imagine you should make another call in the block to another method and pass in the populated image as a parameter.
For getting an object from the ASIHttpRequest response, I use notifications.
For example, in the calling viewController
- (void)viewDidLoad {
// Subscribe notifications
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(onGetPhoto:) name:#"getPhotoNotification" object:nil];
}
- (void)viewDidUnload {
[super viewDidUnload];
// Unsubscribe from notifications
[[NSNotificationCenter defaultCenter] removeObserver:self name:#"getPhotoNotification" object:nil];
}
- (void)onGetPhoto:(NSNotification *)notification {
...
}
in your completion block
[[NSNotificationCenter defaultCenter] postNotificationName:#"getPhotoNotification" object:self userInfo:userInfo];
With your photo in userInfo.

iphone :client-server communication not occuring

i had made the following programming for client server programming but it is not working. the server is not able to receive the request for connection setup.plz help.
#import "clientserverprogramViewController.h"
#import "secondview.h"
#import <CoreFoundation/CFSocket.h>
#include <sys/socket.h>
#include <netinet/in.h>
NSInputStream *iStream;
NSOutputStream *oStream;
#implementation clientserverprogramViewController
#synthesize name,filepath,display;
-(IBAction) print {
NSString *urlStr = serverIP;]
[display setText : urlStr];
if (![urlStr isEqualToString:#""]) {
NSURL *website = [NSURL URLWithString:urlStr];
if (!website) {
NSLog(#"%# is not a valid URL");
return;
}
NSHost *host = [NSHost hostWithName:[website host]];
[NSStream getStreamsToHost:host port:3000 inputStream:&iStream outputStream:&oStream];
[iStream retain];
[oStream retain];
[iStream setDelegate:self];
[oStream setDelegate:self];
[iStream scheduleInRunLoop:[NSRunLoop currentRunLoop]
forMode:NSDefaultRunLoopMode];
[oStream scheduleInRunLoop:[NSRunLoop currentRunLoop]
forMode:NSDefaultRunLoopMode];
[iStream open];
[oStream open];
}
}
-(IBAction) settings {
[self presentModalViewController:nextview animated: YES];
}
-(IBAction) cancel {
exit(0);
}
- (void)dealloc {
[super dealloc];
}
#end
You only open streams and don't do anything with them. It's like picking up a phone and not dialing a number. Use NSStreamDelegate protocol to implement data transmission code.
Update:
You have these lines that set the delegate for streams:
[iStream setDelegate:self];
[oStream setDelegate:self];
Now implement methods that are defined in NSStreamDelegate protocol in your own class (AFAIK - there's only one of them). See how to receive/send data from there.
Is there a specific reason you're using streams?
What about using NSURLConnection? Here's a piece of code from a project of mine. Both are in KANetworkManager. KANetworkTransactionType is simply a enum that helps me know how to parse the response.
+ (void) createAndStartUrlConnection:(NSMutableURLRequest *)request type:(KANetworkTransactionType)type target:(id)target callback:(SEL)callback;
{
[UIApplication sharedApplication].networkActivityIndicatorVisible = YES;
NSDictionary *requestDict = [NSDictionary dictionaryWithObjectsAndKeys:request, #"request", [NSNumber numberWithInt:type], #"type", target, #"target", [NSValue valueWithPointer:callback], #"callback", nil];
[KANetworkManager performSelectorInBackground:#selector(makeNetworkCall:) withObject:requestDict];
}
I'm able to made a synchronous network call because I always call this method on its own thread. It's a simpler way to achieve asynchronous network communications without dealing with delegates (although the delegate method provides some benefits). Your parseResponse method would need to be specific to whatever your web service it sending back. parseResponse would notify the callback method. Let me know if you have additional questions regarding this.
+ (void) makeNetworkCall:(NSDictionary *)params
{
// We assume this method won't be called from the main thread, so we need our own NSAutoreleasePool.
NSAutoreleasePool *autoreleasePool = [[NSAutoreleasePool alloc] init];
NSMutableURLRequest *request = [params objectForKey:#"request"];
KANetworkTransactionType type = [(NSNumber *)[params objectForKey:#"type"] intValue];
id target = [params objectForKey:#"target"];
SEL callback = (SEL)[[params objectForKey:#"callback"] pointerValue];
NSURLResponse *response;
NSError *err;
// We make a synchronous request assuming we're on a background thread.
NSData *data = [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:&err];
if (data.length > 0)
{
[self parseResponse:data type:type target:target callback:callback];
}
else
{
NSLog(#"Error occured during network call. %#", err);
}
[autoreleasePool drain];
}
JB gates,
In your code you inform iStream and oStream that your clientserverprogramViewController object is to be the delegate for each. However, a proper delegate needs actual implementation. Your class needs to implement this method:
– stream:handleEvent:
The details are documented here:
Reading From Input Streams
Writing To Output Streams
Also, your code will not work on a real iPhone. There is an updated Core Foundation API for creating the socket pair, details here.
Update
Just wondering if this is not a software issue but maybe the server is behind a firewall. Please give details what the server is, ie webserver, netcat, or simple TCP socket, etc.
Peter

I think I lost my Managed Object Context

So I'm working on a clone of CoreDataBooks.
It's a little different. When the '+' button is pushed, it launches a navController, containing 2 views. The first (AddPatientVC) asks for name of the Patient then its pushed to a 2nd View Controller (AddPatientDetailVC) which asks for more detailed information. It's the 2nd view controller that I've got the delegate set up with, not the first, like in CoreDataBooks.
For some reason, when the delegate method is fired, the notification method doesn't get fired, so I've somehow lost track of my MOC, either the specific MOC for adding a new Patient.
The specific error i get is:'+entityForName: could not locate an NSManagedObjectModel for entity name 'Patient''
Here's my code - addPatient, delegate method and notification method. Any suggestions on simplification would be appreciated. Thanx
-(void)addPatient:(id)sender
{
PatientAddViewController *patientAddViewController = [[PatientAddViewController alloc] initWithNibName:#"PatientAddViewController" bundle:nil];
PatientAddDetailViewController *patientAddDetailViewController = [[PatientAddDetailViewController alloc] initWithNibName:#"PatientAddViewController" bundle:nil];
patientAddDetailViewController.delegate = self;
//Create a new MOC for adding a book
NSManagedObjectContext *addingContext = [[NSManagedObjectContext alloc] init];
self.addPatientManagedObjectContext = addingContext;
[addingContext release];
[addPatientManagedObjectContext setPersistentStoreCoordinator:[[fetchedResultsController managedObjectContext] persistentStoreCoordinator]];
patientAddViewController.patient = (Patient *)[NSEntityDescription insertNewObjectForEntityForName:#"Patient" inManagedObjectContext:addingContext];
//patientAddViewController.addPatientManagedObjectContext = self.addPatientManagedObjectContext;
UINavigationController *addingNavController = [[UINavigationController alloc] initWithRootViewController:patientAddViewController];
[self.navigationController presentModalViewController:addingNavController animated:YES];
[addingNavController release];
[patientAddViewController release];
}
- (void)patientAddDetailViewController:(PatientAddDetailViewController *)controller didFinishWithSave:(BOOL)save
{
NSLog(#"Delegate Method fired");
if (save)
{
NSNotificationCenter *dnc = [NSNotificationCenter defaultCenter];
//The notification isn't firing becuase addPatientManagedObjectContext is null for some reason
[dnc addObserver:self selector:#selector(addControllerContextDidSave:) name:NSManagedObjectContextDidSaveNotification object:addPatientManagedObjectContext];
NSError *error;
//if (![patient.managedObjectContext save:&error])
if (![addPatientManagedObjectContext save:&error])
{
NSLog(#"Before Error");
//Handle the error...
NSLog(#"Unresolved Error %#, %#",error, [error userInfo]);
exit(-1);//Fail
NSLog(#"After Error");
}
[dnc removeObserver:self name:NSManagedObjectContextDidSaveNotification object:addPatientManagedObjectContext];
}
self.addPatientManagedObjectContext = nil;
[self.tableView reloadData];
[self dismissModalViewControllerAnimated:YES];
}
- (void)addControllerContextDidSave:(NSNotification*)saveNotification {
NSLog(#"Save Notification Fired");
NSManagedObjectContext *context = [fetchedResultsController managedObjectContext];
// Merging changes causes the fetched results controller to update its results
[context mergeChangesFromContextDidSaveNotification:saveNotification];
}
It looks like you create the context, and store it in self
NSManagedObjectContext *addingContext = [[NSManagedObjectContext alloc] init];
self.addPatientManagedObjectContext = addingContext;
[addingContext release];
But then you call the "add" method on the other controller:
patientAddViewController.patient = (Patient *)[NSEntityDescription
insertNewObjectForEntityForName:#"Patient" inManagedObjectContext:addingContext];
(remember, you released 'addingContext' up above, 'addingContext' is not guaranteed to contain anything valid at this point)
Looks like you should be passing self.addPatientManagedObjectContext rather than addingContext in your insertNewObjectForEntityForName:#"Patient" line.