EXC_BAD_ACCESS for an object created inside a Block

為{幸葍}努か 提交于 2019-12-12 01:42:12

问题


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:

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:

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:

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;
}

回答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];
}


来源:https://stackoverflow.com/questions/35171066/exc-bad-access-for-an-object-created-inside-a-block

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!