0

I have always been nervous when it comes to blocks and GCD because my mind tells me that it looks very complex!

I am getting a crash inside a block which ideally looks alright to me:

#pragma mark -
-(void)fetchOrdersListWithInfoDict:(NSDictionary*)infoDict completionBlock:(CompletionBlock)completionBlock errorBlock:(ErrorBlock)errorBlock
{
    __weak VTVehicleServiceNetworkManager *weakSelf = self;
    TaskBlock fetchOrdersListTaskBlock = ^()
    {
        __block __strong HLOrdersDataProvider *ordersDataProvider = nil;
        NSBlockOperation *fetchOrdersOperation = [NSBlockOperation blockOperationWithBlock:[^{
            ordersDataProvider = [[HLOrdersDataProvider alloc] init];
            [ordersDataProvider performFetchOrdersListWithInfoDict:infoDict
                                                   completionBlock:completionBlock
                                                        errorBlock:errorBlock];
        } copy]];
        [weakSelf.dataOperationQueue addOperation:fetchOrdersOperation];
    };

    [self fetchDataWithTaskBlock:[fetchOrdersListTaskBlock copy]
                      errorBlock:^(NSError *error) {
                          errorBlock(error);
                      }];
}

I was able to trace out the zombie object but I am not able to figure out why is this object turning out to be a zombie. Here is the snapshot from profile:

enter image description here

I have gone through the following guides (1, 2) to see if I can find out what I am doing wrong but I was no where near to find out what is going wrong.

Any help and reference text to what I am doing wrong will help.

Edit: I have tried what @Jerimy has suggested and in fact my code which I have posted earlier was exactly the same as required: Declaring and initializing ordersDataProvider inside the block operation itself. But since it was crashing at the same point I tried to declare it outside the block just to see if it addresses the crash.

Below is the new code I tested:

#pragma mark -
-(void)fetchOrdersListWithInfoDict:(NSDictionary*)infoDict completionBlock:(CompletionBlock)completionBlock errorBlock:(ErrorBlock)errorBlock
{
    __weak VTVehicleServiceNetworkManager *weakSelf = self;
    completionBlock = [completionBlock copy];
    errorBlock = [errorBlock copy];
    TaskBlock fetchOrdersListTaskBlock = ^()
    {
        NSBlockOperation *fetchOrdersOperation = [NSBlockOperation blockOperationWithBlock:^{
            HLOrdersDataProvider *ordersDataProvider = [[HLOrdersDataProvider alloc] init];
            [ordersDataProvider performFetchOrdersListWithInfoDict:infoDict
                                                   completionBlock:completionBlock
                                                        errorBlock:errorBlock];
        }];
        [weakSelf.dataOperationQueue addOperation:fetchOrdersOperation];
    };

    [self fetchDataWithTaskBlock:[fetchOrdersListTaskBlock copy]
                      errorBlock:^(NSError *error) {
                          errorBlock(error);
                      }];
}

The crash from Profile:

enter image description here

There is not much from the stack trace as well, SDMHTTPRequest is a library and am very sure there is nothing wrong there, and the HLOrdersDataProvider is the zombie object which I was able to trace out in Instruments app:

enter image description here

EDIT 2 Adding the interface and implementation of HLOrdersDataProvider for more details:

@interface HLOrdersDataProvider : HLDataProvider

-(void)performFetchOrdersListWithInfoDict:(NSDictionary*)infoDict completionBlock:(CompletionBlock)completionBlock errorBlock:(ErrorBlock)errorBlock;

@end

@implementation HLOrdersDataProvider

-(void)performFetchOrdersListWithInfoDict:(NSDictionary*)infoDict completionBlock:(CompletionBlock)completionBlock errorBlock:(ErrorBlock)errorBlock
{
    // Using SDMConnectivity
    NSString *queryString = [infoDict valueForKey:@"$filter"];
    NSString *appendStringForEndpoint = [kRowsetsKeyword stringByAppendingFormat:@"?%@", queryString];
    [self fetchDataFromServerWithEndPointAppendString:appendStringForEndpoint
                                      completionBlock:completionBlock
                                           errorBlock:errorBlock];
}

#pragma mark - Service Agent related
-(NSString*)collectionName
{
    return [NSString stringWithString:kRowsetsKeyword];
}

-(void)requestFinished:(SDMHttpRequest *)request
{
    NSError *error = nil;
    // Let's parse the response and send the results back to the caller
    NSString *collectionName = [self collectionName];
    NSData *responseData = [request responseData];
    NSArray *entitiesArray = [self parseODataEntriesWithData:responseData
                                          withCollectionName:collectionName
                                                       error:&error];
    if (error)
        [self triggerFailureBlockWithArgument:error];
    else
        [self triggerCompletionBlockWithArgument:entitiesArray];
}

@end

Also, HLOrdersDataProvider is inherited from HLDataProvider so below is the interface and implementation of this class too:

#import <Foundation/Foundation.h>
//#import "SDMHttpRequestDelegate.h"
#import "SDMRequestBuilder.h"
#import "SDMHttpRequest.h"
#import "SDMParser.h"
#import "HLConstant.h"
#import "HLConnectionData.h"

@interface HLDataProvider : NSObject <SDMHttpRequestDelegate>

@property (copy, atomic) CompletionBlock completionBlock;
@property (copy , atomic) ErrorBlock errorBlock;
@property (copy, atomic) CompletionBlockWithDataFetchStatus completionBlockWithDataFetchStatus;
+ (id)sharedInstance;
- (NSMutableArray*)parseODataEntriesWithData:(NSData*)data withCollectionName:(NSString*)collectionName;
- (NSMutableArray*)parseODataEntriesWithData:(NSData*)data withCollectionName:(NSString*)collectionName error:(NSError**)outError;
- (NSMutableArray*)parseJSONEntriesWithData:(NSData*)data;

-(NSArray*)fetchEntriesFromDatabaseWithEntityName:(NSString*)entityName relationshipObjects:(NSMutableArray*)relationships;

-(void)updateDatabaseWithEntries:(NSMutableArray*)scanEntries;
-(void)updateDatabaseWithJSONEntries:(NSMutableArray*)scanEntries;

-(id)parsedOdataResultFromEntries:(NSMutableArray*)entries;

-(void)fetchDataFromServerWithEndPointAppendString:(NSString*)appendStr completionBlock:(CompletionBlock)inCompletionBlock errorBlock:(ErrorBlock)inErrorBlock;

-(NSString*)collectionName;
-(void)triggerCompletionBlockWithArgument:(id)inArg;
-(void)triggerFailureBlockWithArgument:(NSError*)inArg;
@end


@implementation HLDataProvider
+ (id)sharedInstance
{
    //Subclassess will override this method
    return nil;
}
-(NSMutableArray*)parseODataEntriesWithData:(NSData*)data withCollectionName:(NSString*)collectionName
{
    return [self parseODataEntriesWithData:data
                        withCollectionName:collectionName
                                     error:NULL];
}

-(NSMutableArray*)parseODataEntriesWithData:(NSData*)data withCollectionName:(NSString*)collectionName error:(NSError**)outError
{
    NSMutableArray *entriesArray = nil;

    @try {
        entriesArray = sdmParseODataEntriesXML(data,
                                               [[[HLConnectionData metaDataDocument] getCollectionByName:collectionName] getEntitySchema],
                                               [HLConnectionData serviceDocument]);
    }
    @catch (NSException *exception) {
        NSLog(@"Got exception: %@", exception);
        if (outError)
        {
            *outError = [NSError errorWithDomain:@"Vehicle Service"
                                            code:-1001
                                        userInfo:[NSDictionary dictionaryWithObject:exception  forKey:NSLocalizedDescriptionKey]];
        }
    }
    @finally {

    }
    return entriesArray;
}

- (NSMutableArray*)parseJSONEntriesWithData:(NSData*)data
{
    NSError *error = nil;
    id object = [NSJSONSerialization
                 JSONObjectWithData:data
                 options:0
                 error:&error];

    NSMutableArray *resultArray = nil;

    if(error) { /* JSON was malformed, act appropriately here */ }
    if([object isKindOfClass:[NSDictionary class]])
    {
        resultArray = [NSMutableArray arrayWithObject:object];
    }
    else if ([object isKindOfClass:[NSArray class]])
    {
        resultArray = [NSMutableArray arrayWithArray:object];
    }

    return resultArray;
}


#pragma mark -
#pragma mark - Data Fetch - Server - SDMConnectivity
-(void)fetchDataFromServerWithEndPointAppendString:(NSString*)appendStr completionBlock:(CompletionBlock)inCompletionBlock errorBlock:(ErrorBlock)inErrorBlock;
{
    self.errorBlock = inErrorBlock;
    self.completionBlock = inCompletionBlock;

    id<SDMRequesting> request = nil;

    //NSString *clientStr = @"&sap-client=320&sap-language=EN";

    NSString *urlStr =[NSString stringWithFormat:@"%@/%@",[HLConnectionData applicationEndPoint], appendStr];
    urlStr = [urlStr stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
    [SDMRequestBuilder setRequestType:HTTPRequestType];
    request=[SDMRequestBuilder requestWithURL:[NSURL URLWithString:urlStr]];
    [request setUsername:kUserName];
    /*Set Password in SDMRequesting object*/
    [request setPassword:kPassword];
    [request setRequestMethod:@"GET"];
    [request setTimeOutSeconds:kTimeoutInterval];

    /*set the Delegate. This class must adhere to SDMHttpRequestDelegate to get the callback*/
    [request setDelegate:self];

    /*Call  startAsynchronous API to request object to retreive Data asynchrnously in the call backs  */
    [request startSynchronous];
}

-(void)updateDatabaseWithEntries:(NSMutableArray*)scanEntries
{
    //Subclasses will override this
}

-(void)updateDatabaseWithJSONEntries:(NSMutableArray*)scanEntries
{
    //Subclasses will override this
}


-(id)parsedOdataResultFromEntries:(NSMutableArray*)entries
{
    //Subclasses will override this
    return nil;
}

-(void)deleteExistingEntriesFromCoredata
{
    //Subclasses will override this
}

-(NSArray*)fetchEntriesFromDatabaseWithEntityName:(NSString*)entityName relationshipObjects:(NSMutableArray*)array
{
    //Subclasses will overide this method
    return nil;
}

#pragma mark - SDMHTTPRequestDelegate methods
- (void)requestStarted:(SDMHttpRequest*) request
{

}
- (void)requestFinished:(SDMHttpRequest*) request
{
    // For service doc and metadata we instantiate HLDataProvider, so we send this raw SDMHTTPRequest object as-is. For other service agents like HLOrdersDataProvider we send the parsed information, check the subclass' implementation of -requestFinished: method
    [self triggerCompletionBlockWithArgument:request];
}
-(void)triggerCompletionBlockWithArgument:(id)inArg
{
    self.completionBlock(inArg);
}
- (void)requestFailed:(SDMHttpRequest*) request
{
    [self triggerFailureBlockWithArgument:request.error];
}
-(void)triggerFailureBlockWithArgument:(NSError*)inArg
{
    self.errorBlock(inArg);
}
- (void)requestRedirected:(SDMHttpRequest*) request
{

}

#pragma mark - Service Agent related
-(NSString*)collectionName
{
    // Should be overridden by the subclasses
    return nil;
}
Raj Pawan Gumdal
  • 7,390
  • 10
  • 60
  • 92
  • Post the stacktrace. – trojanfoe Feb 03 '16 at 07:44
  • @Raj Any special reason for ordersDataProvider outside the block? Why not just put HLOrdersDataProvider *ordersDataProvider = [[HLOrdersDataProvider alloc] init]; directly inside the block. – Allen Feb 03 '16 at 07:49
  • Sorry for the delayed reply, I had ordersDataProvider outside the block coz it was crashing when I had it inside. I will post the stacktrace once I get to work on it again. – Raj Pawan Gumdal Feb 05 '16 at 07:05

1 Answers1

1

The referenced code is very convoluted (you're doing things in a very complicated way).

First off, you should not be creating the copy of your blocks in the caller. Make the copy in the callee if necessary (ie: to copy it to heap instead of using the stack-allocated block if you are going to call it after the stack has been popped). In almost all APIs using blocks, it is not the caller's responsibility to ensure that a block is on heap.

There is no reason for your ordersDataProvider variable to be declared in the scope of fetchOrdersListTaskBlock because it is only ever used inside of fetchOrdersOperation's block.

I don't immediately see the cause of your crash, but I suspect that simplifying your code will help reveal the problem. Perhaps the issue is in HLOrdersDataProvider's initializer.

Try something like:

-(void)fetchOrdersListWithInfoDict:(NSDictionary*)infoDict completionBlock:(CompletionBlock)completionBlock errorBlock:(ErrorBlock)errorBlock
{
    completionBlock = [completionBlock copy];
    errorBlock = [errorBlock copy];

    __weak VTVehicleServiceNetworkManager *weakSelf = self;
    TaskBlock fetchOrdersListTaskBlock = ^{
        NSBlockOperation *fetchOrdersOperation = [NSBlockOperation blockOperationWithBlock:^{
            HLOrdersDataProvider *ordersDataProvider = [[HLOrdersDataProvider alloc] init];
            [ordersDataProvider performFetchOrdersListWithInfoDict:infoDict
                                                   completionBlock:completionBlock
                                                        errorBlock:errorBlock];
        }];
        [weakSelf.dataOperationQueue addOperation:fetchOrdersOperation];
    };

    [self fetchDataWithTaskBlock:fetchOrdersListTaskBlock
                      errorBlock:errorBlock];
}

Or better yet, re-design your class to work like this (I don't see a need for NSBlockOperation in your example):

-(void)fetchOrdersListWithInfoDict:(NSDictionary*)infoDict completionBlock:(CompletionBlock)completionBlock errorBlock:(ErrorBlock)errorBlock
{
    completionBlock = [completionBlock copy];
    errorBlock = [errorBlock copy];

    TaskBlock fetchOrdersListTaskBlock = ^{
        HLOrdersDataProvider *ordersDataProvider = [[HLOrdersDataProvider alloc] init];
        [ordersDataProvider performFetchOrdersListWithInfoDict:infoDict
                                               completionBlock:completionBlock
                                                    errorBlock:errorBlock];
    };

    [self fetchDataWithTaskBlock:fetchOrdersListTaskBlock
                      errorBlock:errorBlock];
}
Jeremy Huddleston Sequoia
  • 22,938
  • 5
  • 78
  • 86
  • "which is executed potentially after fetchOrdersListTaskBlock has been freed and thus the ordersDataProvider variable is no longer allocated." This is incorrect. As long as some scope has access to that variable, it's allocated. – newacct Feb 06 '16 at 10:00
  • Ah right. ordersDataProvider is __block, so it'll be moved to heap when the referencing block (fetchOrdersOperation's block) is copied to heap. I missed that when reading through. Thanks for pointing that out. My suggestions and code comments still remain. And simplifying the code will likely help reveal the source of the problem. I'll make an edit above. – Jeremy Huddleston Sequoia Feb 07 '16 at 06:11
  • I have added more details in the question above, I will have to stick with the Operation queue for that is how the design of the network layer is and I will have to adhere to it. I have checked with your second approach and the app doesn't crash with the zombie object problem, so definitely there is something to do with Operation Blocks inside a block. I am breaking my head over it! – Raj Pawan Gumdal Feb 08 '16 at 09:29
  • Given that, we really need to see more of the implementation of HLOrdersDataProvider (mainly the initializer). – Jeremy Huddleston Sequoia Feb 09 '16 at 04:27
  • @JeremyHuddlestonSequoia - Added more details in the question above. – Raj Pawan Gumdal Feb 09 '16 at 09:27