Multithreading method calls in Objective-C - ios

Say we have a class A and we call its method:
- (void)saveCompanyOnServerWithTitle:(NSString *)title {
[self.interactor performSaveCompanyNetworkRequestWithCompanyTitle:title];
}
And Interactor implements the method as follows:
- (void)performSaveCompanyNetworkRequestWithCompanyTitle:(NSString *)title {
typeof(self) __weak weakSelf = self;
[self.uploadService addUploadOperationWithCompanyTitle:title completion:^(id result, NSError *error) {
if (error){
[weakSelf.presenter didPerformSaveCompanyNetworkRequestWithError:error];
return;
}
[weakSelf.presenter didPerformSaveCompanyNetworkRequestWithResponse:result];
}];
}
UploadService's method implementation looks like:
- (void)addUploadOperationWithCompanyTitle:(NSString *)title completion:(RCNUploadOperationBlock)completionBlock {
NSURLRequest *request = [RCNRequestFactory createCompanyUploadingRequestWithCompanyName:title];
RCNUploadOperationBlock block = ^void(id result, NSError *error){
if (error){
completionBlock(result, error);
} else {
completionBlock(result, nil);
}
};
RCNCompanyUploadingOperation *uploadingOperation = [[RCNCompanyUploadingOperation alloc] initWithRequest:request
completionBlock:[block copy]];
uploadingOperation.name = title;
[self.uploadingQueue addOperation:uploadingOperation];
}
Now imagine we call saveCompanyOnServerWithTitle: in several queues.
Should I wrap the method call in something like
dispatch_async(dispatch_serial_queue, ^{
[self.interactor performSaveCompanyNetworkRequestWithCompanyTitle:title]
});
Or should I just wrap the method's implementation, e.g.
typeof(self) __weak weakSelf = self;
dispatch_async(dispatch_serial_queue, ^{
[weakSelf.uploadService addUploadOperationWithCompanyTitle:title completion:^(id result, NSError *error) {
if (error){
[weakSelf.presenter didPerformSaveCompanyNetworkRequestWithError:error];
return;
}
[weakSelf.presenter didPerformSaveCompanyNetworkRequestWithResponse:result];
}];
});
I want to make "addUploadOperationWithCompanyTitle:" calls serial. I know the method itself is an asynchronous one.

Related

How do you manage retain counts in nested dispatch queue blocks

In the following code, how would you avoid nested blocks increasing the retain count of 'self'.
This is how I avoid nested blocks
-(void)openSession {
[self.loginManager logInWithReadPermissions:#[#"user_photos"]
fromViewController:[self.datasource mediaAccountViewControllerForRequestingOpenSession:self]
handler:[self loginHandler]];
}
-(void(^)(FBSDKLoginManagerLoginResult *result, NSError *error))loginHandler {
__weak typeof (self) weakSelf = self;
return ^ (FBSDKLoginManagerLoginResult *result, NSError *error) {
__strong typeof(weakSelf) strongSelf = weakSelf;
if (error) {
[strongSelf.delegate mediaAccount:strongSelf failedOpeningSessionWithError:error];
} else if (result.isCancelled) {
[strongSelf.delegate mediaAccountSessionOpeningCancelledByUser:strongSelf];
} else {
[strongSelf.delegate mediaAccountDidOpenSession:strongSelf];
}
[strongSelf notifyWithCompletion:[strongSelf completionHandler]]
};
}
-(void)notifyWithCompletion:(void(^)(void))completion {
[self notify];
completion();
}
-(void(^)(void))completionHandler {
return ^ {
//do something
};
}
But how do you avoid many nested blocks, which is often the case when you use GCD within a block ?
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
[self parseLoadsOfData];
dispatch_async(dispatch_get_main_queue(), ^{
[self updateUI];
});
});
Are there retain cycles here ?
__weak typeof(self) *weakSelfOuter = self;
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
__strong typeof(self) *strongInnerSelf1 = weakSelfOuter;
[strongInnerSelf1 parseLoadsOfData];
__weak typeof(self) *weakInnerSelf = strongInnerSelf1;
dispatch_async(dispatch_get_main_queue(), ^{
__strong typeof(self) *strongInnerSelf2 = weakInnerSelf;
[strongInnerSelf2 updateUI];
});
});

Can somebody tell me why block doesn't wait for objc handler?

Again, question about GCD. I don't know why they were invented as they don't work properly or I misunderstood something.
I've got some controller for adding new Coupon. It is responsible to saving details into Core Data. Then ImagesCollection is used to pick up some imgs and used to convert UIImage into NSData.
It looks something like that:
- (IBAction)saveButtonClicked:(id)sender {
__weak typeof(self) weakSelf = self;
__block NSSet *imgs = nil;
if (!imagesCollection)
imgs = [self.coupon couponImages];
[imagesCollection saveImagesWithCompletion:^(NSSet *images, NSError *error) {
if (error) {
[[[UIAlertView alloc] initWithTitle:error.localizedDescription
message:error.localizedFailureReason
delegate:nil
cancelButtonTitle:NSLocalizedString(#"OK", nil) otherButtonTitles:nil, nil] show];
} else {
imgs = [images copy];
}
}];
------- PhotosCollection --------
#interface...
typedef void (^ImagesCompletionBlock)(NSSet *images, NSError *error);
-(void)saveImagesWithCompletion:(ImagesCompletionBlock)completion;
#implementation...
ImagesCompletionBlock _block;
-(void)saveImagesWithCompletion:(void (^)(NSSet *images, NSError *error))success {
_block = [success copy];
NSError *error = nil;
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
NSMutableSet *processedImages = [NSMutableSet new];
for (UIImage *image in images) {
[processedImages addObject:[NSData dataWithData:UIImagePNGRepresentation(image)]];
}
dispatch_async(dispatch_get_main_queue(), ^{
if (_block) {
_block([processedImages copy],error);
}
});
});
}
How it doesn't wait for completion handler to be fired??
Thanks for any help,
Frustrated - Adrian.

What's the executive order of block as function argument in objective-c?

As apple's documentation says,
"Blocks are also used for callbacks, defining the code to be executed when a task completes."
So block should execute after the body of the function in which the block is passed executes. But I wrote the following test code:
void testBlock(void(^test)()){
NSLog(#"1");
};
int main(int argc, const char * argv[]) {
#autoreleasepool {
testBlock(^{
NSLog(#"2");
});
}
}
and the output is only "1".
So where's the NSLog(#"2")?
#Julian Król
But look at this function in AFNetworking:
- (void)setCompletionBlockWithSuccess:(void (^)(AFHTTPRequestOperation *operation, id responseObject))success
failure:(void (^)(AFHTTPRequestOperation *operation, NSError *error))failure
{
// completionBlock is manually nilled out in AFURLConnectionOperation to break the retain cycle.
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-retain-cycles"
#pragma clang diagnostic ignored "-Wgnu"
self.completionBlock = ^{
if (self.completionGroup) {
dispatch_group_enter(self.completionGroup);
}
dispatch_async(http_request_operation_processing_queue(), ^{
if (self.error) {
if (failure) {
dispatch_group_async(self.completionGroup ?: http_request_operation_completion_group(), self.completionQueue ?: dispatch_get_main_queue(), ^{
failure(self, self.error);
});
}
} else {
id responseObject = self.responseObject;
if (self.error) {
if (failure) {
dispatch_group_async(self.completionGroup ?: http_request_operation_completion_group(), self.completionQueue ?: dispatch_get_main_queue(), ^{
failure(self, self.error);
});
}
} else {
if (success) {
dispatch_group_async(self.completionGroup ?: http_request_operation_completion_group(), self.completionQueue ?: dispatch_get_main_queue(), ^{
success(self, responseObject);
});
}
}
}
if (self.completionGroup) {
dispatch_group_leave(self.completionGroup);
}
});
};
#pragma clang diagnostic pop
}
This function doesn't explicitly call the block and the block parameter doesn't even has a name so it seems that the block should not be executed. But as I use this function as following:
AFHTTPRequestOperation *operation = [[AFHTTPRequestOperation alloc] initWithRequest:request];
operation.responseSerializer = [AFXMLParserResponseSerializer serializer];
operation.responseSerializer.acceptableContentTypes = [NSSet setWithObject:#"application/rss+xml"];
[operation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {
NSXMLParser *XMLParser = (NSXMLParser *)responseObject;
[self.parserDictionary setObject:XMLParser forKey:urlString];
[XMLParser setShouldProcessNamespaces:YES];
XMLParser.delegate = self;
[XMLParser parse];
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
//When an error occurs while parsing.
UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:#"Error Loading Data"
message:[error localizedDescription]
delegate:nil
cancelButtonTitle:#"Ok"
otherButtonTitles:nil];
[alertView show];
[MBProgressHUD hideHUDForView:self.tableView animated:YES];
}];
And the block is truly executed. What's is the reason here?
you do not call passed block inside this is why you have on the output only NSLog(#"2");
You should have something like this:
void testBlock(void(^test)()){
NSLog(#"1");
test();
};
You should also check whether passed block is a nil (as calling it in case it is nil will crash)
You can think of blocks as chunks of code that can be passed around like variables. You call a block like a function so in your case you would do something like !test ?: test(); This is semantically equivalent to:
if (test != nil) {
test();
}
You may find the following example useful:
// Define block that returns void and takes void arguments
typedef void (^MyBlock)(void);
// Define block that takes two ints and returns an int
typedef int (^AddBlock)(int a, int b);
#interface MyClass : NSObject
// NOTE: You have to COPY the block variables.
#property (copy) MyBlock blockOne;
#property (copy) AddBlock blockTwo;
#end
#implementation ...
- (void)runBlocks
{
!self.blockOne ?: self.blockOne();
if (self.blockTwo != nil) {
int sum = self.blockTwo(1, 2);
}
}
#end
function in AFNetworking:
A couple of things:
1) You should not have a failure and a success block. Instead define one block like this:
typdef void (^CompletionHandler)(AFHTTPRequestOperation *op, id responseObj, NSError *error);
Then when you implement the block you can check for an error like so:
...^(AFHTTPRequestOperation *op, id responseObj, NSError *error) {
if (error != nil) {
// Handle the error
} else {
// YAY everything went well
}
}];
2) You are ignoring retain cycles here which is NOT a good thing to do. Instead you should define a weak version of self that you can reference in the block like so:
__weak __block MyClass *welf = self;
myBlock = ^{
welf.coolProperty = coolValue;
};
Check out this SO question for info on __weak and __block

send a second request after the first is completed

I have a method (requestData) that can be called several times in my ViewController but the first time the ViewController is loaded (in ViewDidLoad method) I need to call it two times BUT the second request should be sent only after the first request has completed:
- (void)viewDidLoad {
[super viewDidLoad];
dataForPlot = 1;
[self requestData: dataForPlot];
dataForPlot = 2;
[self requestData: dataForPlot];
}
- (void) requestData: (int) forPlot {
...
[urlRequest startWithCompletion:^(URLRequest *request, NSData *data, NSError *error, BOOL success) {
if (success) {
if (forPlot == 1) {
...
}
else if (forPlot == 2) {
...
}
}
}
I know I probably need to use blocks but, even if I've tried to read some tutorials, I don't know how.
Anyone could help me ?
Thanks, Corrado
Here is what I've implemented following Duncan suggestion:
typedef void(^myCompletion)(BOOL);
- (void)viewDidLoad {
[super viewDidLoad];
[self requestData:^(BOOL finished) { // first request
if(finished) {
NSLog(#"send second request");
[self requestData: ^(BOOL finished) {}]; // second request
}
}];
- (void) requestData: (myCompletion) compblock {
...
[urlRequest startWithCompletion:^(URLRequest *request, NSData *data, NSError *error, BOOL success) {
if (success) {
...
NSLog(#"request completed");
compblock(YES);
}
}
Don't call the second request until the first completes:
- (void) requestData: (int) forPlot {
...
[urlRequest startWithCompletion:^(URLRequest *request, NSData *data, NSError *error, BOOL success) {
if (success) {
if (forPlot == 1) {
...
dataForPlot = 2;
[self requestData: dataForPlot];
}
else if (forPlot == 2) {
...
}
}
}
Refactor your requestData method to take a completion block.
In your requestData method, When the url request completes, invoke the completion block.
In viewDidLoad, use a completion block that calls requestData a second time, this time with an empty completion block.
In your other calls to the requestData method, pass in a nil completion block (or whatever other action you need to trigger when the request finishes)

Nested completion block not being called

I've got a nested completion block set to initialize an SKProduct. If I put a breakpoint right before the completion block, the inside block executes OK (I can poll for _product) but the completion block never fires.
If I call completion() immediately within this block it executes, but not if I call it within the completion block of the nested block.
- (void)initializeProduct:(NSString*)bundleId completion:(void(^)(BOOL finished, NSError* error))completion
{
// completion(YES, nil); If I call completion here, it executes
NSSet* dataSet;
dataSet = [[NSSet alloc] initWithArray:#[bundleId]];
[IAPShare sharedHelper].iap.production = NO;
if(![IAPShare sharedHelper].iap) {
NSSet* dataSet = [[NSSet alloc] initWithObjects:bundleId, nil];
[IAPShare sharedHelper].iap = [[IAPHelper alloc] initWithProductIdentifiers:dataSet];
[IAPShare sharedHelper].iap.products = #[bundleId];
}
[[IAPShare sharedHelper].iap requestProductsWithCompletion:^(SKProductsRequest* request,SKProductsResponse* response)
{
if(response > 0 ) {
_product = [[IAPShare sharedHelper].iap.products objectAtIndex:0]; // We get this far
// ^ breakpoint on this line shows _product is now an SKProduct
completion(YES, nil); // but this never fires
}
else
{
completion(YES, error);
// ^ yes, error is defined in my code, I'm being lazy
}
}];
}
I'm calling it in a method like this with another completion block (and it never enters the block):
- (void)priceForBundleId:(NSString *)bundleId completion:(void(^)(NSString* price))completion
{
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, NO), ^{
[self initializeProduct:bundleId completion:^(BOOL finished, NSError *error) {
// Breakpoint placed here never catches
if (!error)
{
NSString* price = [RSStore priceAsStringForLocale:_product.priceLocale price:_product.price];
[defaults setValue:price forKey:#"bigBoxPrice"];
completion(price);
}
else
{
completion(nil);
}
}];
});
}

Resources