【问题标题】:Nesting blocks in iOSiOS 中的嵌套块
【发布时间】:2014-07-26 22:55:08
【问题描述】:

我试图能够从另一个completionHandler 块(在异步URL 请求之后调用)内部调用“completionHandler”块。然而,这会导致应用程序崩溃并显示以下消息(我正在使用 Zombie 对象):
*** -[CFRunLoopTimer hash]: message sent to deallocated instance

使用工具我发现问题是由于块被释放,但我不知道如何让它保留足够长的时间。是因为我从另一个异步块调用块吗?我的代码如下(MyCompletionHandler 返回void 并采用void):

-(void)requestWithRequest:(NSURLRequest*)request withCompletionHandler:(MyCompletionHandler)serverCompletionHandler{

    [NSURLConnection sendAsynchronousRequest:request queue:[NSOperationQueue mainQueue] completionHandler:^(NSURLResponse *response, NSData *data, NSError *connectionError) {
        // do stuff…


            if (serverCompletionHandler) {
                dispatch_async(dispatch_get_main_queue(), ^{
            serverCompletionHandler();
         });
    }];
}

但是,此代码是通过提供 serverCompletionHandler 参数的另一种方法调用的(这可能是问题吗?)。

比如上面的方法会被这个方法调用:

-(void)createAndSendRequestWithCompletionHandler:(MyCompletionHandler)serverCompletionHandler{

    NSMutableURLRequest* request = //..
    [self requestWithRequest:request withCompletionHandler:serverCompletionHandler];
}

仪器显示一个块被释放或删除(我假设我正在调用的块),这将解释被调用的释放对象。

任何帮助将不胜感激。

编辑
计时器的代码(似乎已被释放)是:

if ([timer isValid]) {
    [timer invalidate];
}
timer = [NSTimer scheduledTimerWithTimeInterval:10 target:self selector:@selector(createAndSendRequestWithCompletionHandler:) userInfo:nil repeats:YES];

令人困惑的是,在我添加 completionHandlers 之前,代码运行良好。

【问题讨论】:

    标签: ios objective-c asynchronous objective-c-blocks


    【解决方案1】:

    不,那不是问题,您可以在另一个块中访问该块。我认为问题是您已经在 mainQueue ([NSOperationQueue mainQueue]) 上,您再次尝试在 mainQueue 上 getMainQueue。因为 sendAsynchronousRequest 使用 @987654324 @ of queue 当您再次请求主队列时,它会被释放,因为您已经在主队列中。您可以检查您是否已经在主队列中,只需调用 serverCompletionHandler 否则 dispatch on mainqueue。在这种情况下,您可以跳过此检查你确定你是main queue,可以打电话给serverCompletionHandler()

    [NSURLConnection sendAsynchronousRequest:request queue:[NSOperationQueue mainQueue] completionHandler:^(NSURLResponse *response, NSData *data, NSError *connectionError) {
        // do stuff…
    
    
    if([[NSOperationQueue currentQueue] isEqual:[NSOperationQueue mainQueue]]){ //check for main queue
        if (serverCompletionHandler) {
                serverCompletionHandler();
          }
    }
    else{
        if (serverCompletionHandler) {    if not than dispatch on main queue
                dispatch_async(dispatch_get_main_queue(), ^{ //No need to do that
            serverCompletionHandler();
         });
    }
    
    }];
    

    编辑:感谢编辑的代码。正如您正在使用的那样

    timer = [NSTimer scheduledTimerWithTimeInterval:10 target:self selector:@selector(createAndSendRequestWithCompletionHandler:) userInfo:nil repeats:YES];
    

    现在你没有通过completionhandler,所以通过这样做createAndSendRequestWithCompletionHandler:计时器将自己传递给你的serverCompletionHandler。 所以serverCompletionHandler 本身包含timer 对象而不是任何block 对象。如果您尝试在requestWithRequest 中使用NSLog serverCompletionHandler,您会发现它是计时器对象。现在当dispatch_async 尝试调用serverCompletionHandler 时不阻塞就会崩溃。

    这两行写在createAndSendRequestWithCompletionHandler

    NSLog(@"serverCompletionHandler obnj %@",serverCompletionHandler );
    NSLog(@"class %@",NSStringFromClass([serverCompletionHandler class] ));
    

    编辑 2

    如果你真的想传递完成处理程序而不是传递计时器对象的userInfo。使用下面的代码

    #import "YourViewController.h"
    typedef void (^MyCompletionHandler)(void);
    
    @interface YourViewController ()
    {
        NSTimer *timer;
    }
    @end
    
    @implementation YourViewController
    
    - (void)viewDidLoad
    {
        [super viewDidLoad];
        // Do any additional setup after loading the view, typically from a nib.
    
        if ([timer isValid]) {
            [timer invalidate];
        }
    
        MyCompletionHandler com = ^{
            NSLog(@"Hi this is completion handler");
        };
    
        timer = [NSTimer scheduledTimerWithTimeInterval:10 target:self selector:@selector(createAndSendRequestWithCompletionHandler:) userInfo:@{@"serverCompletionHandler":com} repeats:YES];
    
    }
    
    - (void)didReceiveMemoryWarning
    {
        [super didReceiveMemoryWarning];
        // Dispose of any resources that can be recreated.
    }
    
    -(void)requestWithRequest:(NSURLRequest*)request withCompletionHandler:(MyCompletionHandler)serverCompletionHandler{
    
        [NSURLConnection sendAsynchronousRequest:request queue:[NSOperationQueue mainQueue] completionHandler:^(NSURLResponse *response, NSData *data, NSError *connectionError) {
            // do stuff…
    
    
            if (serverCompletionHandler) {
                dispatch_async(dispatch_get_main_queue(), ^{
                    serverCompletionHandler();
                });
            }
    
        }];
    }
    
    
    -(void)createAndSendRequestWithCompletionHandler:(NSTimer *)timerObj{
    
    //    NSLog(@"serverCompletionHandler obnj %@",serverCompletionHandler );
    //    NSLog(@"class %@",NSStringFromClass([serverCompletionHandler class] ));
    
    
        //get completion handler from `userInfo`
        NSMutableURLRequest* request = [[NSMutableURLRequest alloc] init];
        [self requestWithRequest:request withCompletionHandler:timerObj.userInfo[@"serverCompletionHandler"]];
    }
    
    
    @end
    

    【讨论】:

    • 同意,但是当您知道自己在主队列上时,为什么还要检查自己是否在主队列上? sendAsynch 的第一个参数...指定将调用完成处理程序的队列。
    • 如果您在另一个线程上并在其他队列上操作此sendAsynchronousRequest,因为通常不会在main queue上操作此方法。在这种情况下我们可以跳过此检查
    • 感谢您的建议,但它仍然会释放块。我试图找出正在被释放的对象(通过内存地址),但到目前为止我运气不佳。 createAndSendRequestWithCompletionHandler 方法也由带有 userInfo:nil 的 NSTimer 调用,如果这很重要的话。
    • 好的,看来计时器是发送消息的那个(地址匹配),但已被释放。可能是什么原因?计时器是单例中的一个实例变量,所以我不明白为什么它会被释放。此外,坠机似乎发生在第二次火灾中(可能并非总是如此......)
    • 不要做任何事情只是createAndSendRequest 并删除参数和timer = [NSTimer scheduledTimerWithTimeInterval:10 target:self selector:@selector(createAndSendRequestWithCompletionHandler) userInfo:nil repeats:YES]; 通知: 从调用中删除:
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-03-15
    • 2022-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多