重构代码后“缺少__block类型说明符”编译错误,但在重构之前确定

时间:2014-01-29 19:12:33

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

我有一些代码可以下载图像并将其分配到一个块中。代码当前有效,但是我想将它重构为一个单独的方法,但是在重构后我得到了一个编译错误。

这是编译并运行下载图像成功分配的原始代码:

- (void) someMethod
{
   …
   MyObject* object = [[MyObject alloc] init];
   [self.objects addObject: object];

   NSString* graphRequest = [NSString stringWithFormat:@"%@%@%@",  @"https://graph.facebook.com/",
                              fbid,
                              @"/picture?type=square"];
    FBRequest *fbRequest = [FBRequest requestForGraphPath: graphRequest];
    [fbRequest startWithCompletionHandler:
     ^(FBRequestConnection *connection, id result, NSError *theError)
     {
         NSDictionary<FBGraphObject> *dict = (NSDictionary<FBGraphObject> *) result;
         if (dict)
         {
             NSString* urlAsString = [dict objectForKey:@"id"];
             if ([urlAsString length] > 0)
             {
                 NSURL *url = [NSURL URLWithString: urlAsString];
                 NSData *data = [NSData dataWithContentsOfURL:url];
                 object.image = [UIImage imageWithData:data];
             }
         }
     }];
}

如果我将其重构为以下内容,则会出现编译错误

- (void) someMethod
{
   …
   MyObject* object = [[MyObject alloc] init];
   [self.objects addObject: object];
   [self fetchFbImage: object.image forFbid:fbid];
}


- (void) fetchFbImage:(UIImage*) targetImage forFbid:(NSString*) fbid
{
    NSString* graphRequest = [NSString stringWithFormat:@"%@%@%@",  @"https://graph.facebook.com/",
                              fbid,
                              @"/picture?type=square"];
    FBRequest *fbRequest = [FBRequest requestForGraphPath: graphRequest];
    [fbRequest startWithCompletionHandler:
     ^(FBRequestConnection *connection, id result, NSError *theError)
     {
         NSDictionary<FBGraphObject> *dict = (NSDictionary<FBGraphObject> *) result;
         if (dict)
         {
             NSString* urlAsString = [dict objectForKey:@"id"];
             if ([urlAsString length] > 0)
             {
                 NSURL *url = [NSURL URLWithString: urlAsString];
                 NSData *data = [NSData dataWithContentsOfURL:url];
                 targetImage = [UIImage imageWithData:data];
             }
         }
     }];
}

编译错误是分配到targetImage的行,“变量不可分配(缺少__block类型说明符)”。

我应该在哪里添加__block类型说明符?为什么在重构之后会出现问题但不是之前的问题?

由于

2 个答案:

答案 0 :(得分:4)

您似乎对参数在Objective-C中的工作方式感到困惑。

在原始代码中,您有:

MyObject* object = [[MyObject alloc] init];

object声明为方法的局部变量。然后在你写的块内:

object.image = [UIImage imageWithData:data];

因此,您的块引用局部变量object和(因为声明中没有__block属性)该块获取变量内容的常量副本。这很好,因为您没有更改object中的内容,MyObject是对someMethod实例的引用,而是在该实例上调用一个方法来更改该实例的内部状态。[*]

现在让我们来看看你的重构。您删除了fetchFbImage代码的一大块,并将其放入新方法someMethod中。在fetchFbImage中,您拨打[self fetchFbImage:object.image forFbid:fbid];

object.image

这会将fetchFbImage当前值传递给传递属性的方法,以便可以将其分配到{{} 1}}。参数targetImage的类型是UIImage * - 对UIImage的引用 - 它不是“UIImage *类型的属性” - 您不能拥有此类型的参数,属性不能只传递它们的值或引用具有属性的对象。

当一个方法被调用时,每个参数实际上是一个局部变量,它被初始化为传递的参数值,所以上面的方法调用有效地执行:

UIImage *targetImage = object.image;

其中targetImagefetchFbImage范围内的局部变量,objectsomeMethod范围内的局部变量。

现在在fetchFbImage内的块内写下:

targetImage = [UIImage imageWithData:data];

这有两个原因:

  1. 您无法在区块内分配targetImage。这是属于fetchFbImage的局部变量,该局部变量不归因于__block,因此该块具有它的常量副本 - 并且您不能分配给常量。这就是编译器发出错误消息的原因。
  2. 但是你的问题比这个要大,你假设对fetchFbImage的局部变量targetImage的赋值将会有一些如何调用属性object.image - 并且没有办法可以做到这一点。 targetImage只是一个局部变量,通过方法调用使用object.image进行初始化。
  3. 解决此问题的唯一方法是将fetchFbImage引用传递给MyObject实例,然后传递到fetchFbImage内的块中以分配给image属性正如您的预先反思的代码那样,该对象。

    所以你的代码看起来像是:

    - (void) fetchFbImage:(MyObject *)targetObject forFbid:(NSString *)fbid
    {
       ...
       targetObject.image = [UIImage imageWithData:data];
       ...
    }
    
    ...
    
    [self fetchFbImage:object forFbid:fbid];
    

    HTH

    <强>附录

    看到您对其他答案的评论,您似乎希望fetchFbImage不了解MyObject并能够获取图像,无论它们将从何处被引用。

    执行此操作的一种简单方法是遵循与FBRequest相同的设计 - 使用完成处理程序。为方便起见,定义完成块的类型:

    typedef void (^ImageConsumer)(NSImage *image);
    

    现在定义您的方法以采取以下方法之一:

    - (void) fetchFbImageForFbid:(NSString *)fbid completionHandler:(ImageConsumer)handler
    

    fetchFbImageForFbid内的块中将图像传递给处理程序:

    handler([UIImage imageWithData:data]);
    

    someMethod的调用中,传递一个块,该值将值分配给您的属性:

    [self fetchFbImageForFbid:fbid
            completionHandler:^(NSImage *image) { object.image = image; }
    ];
    

    [*]如果这令人困惑,请将参考文献视为房屋的地址。地址是不变的,房子里有多少人不是。你可以告诉某人“去这个地址,有一个伟大的聚会” - 房子的内容(“状态”)改变,它的地址没有。

答案 1 :(得分:1)

在你的第一个方法中(没有重构),你可以将图像设置为对象,因为你引用了对象,所以你做了

object.image = [UIImage imageWithData:data];

但是在重构之后,你无法以你的方式设置图像,你也应该发送对象。

- (void) someMethod
{
   …
   MyObject* object = [[MyObject alloc] init];
   [self.objects addObject: object];
   [self fetchFbImageForObj:object forFbid:fbid];
}


- (void) fetchFbImageForObject:(MyObject*)object forFbid:(NSString*) fbid
{
    NSString* graphRequest = [NSString stringWithFormat:@"%@%@%@",  @"https://graph.facebook.com/",
                              fbid,
                              @"/picture?type=square"];
    FBRequest *fbRequest = [FBRequest requestForGraphPath: graphRequest];
    [fbRequest startWithCompletionHandler:
     ^(FBRequestConnection *connection, id result, NSError *theError)
     {
         NSDictionary<FBGraphObject> *dict = (NSDictionary<FBGraphObject> *) result;
         if (dict)
         {
             NSString* urlAsString = [dict objectForKey:@"id"];
             if ([urlAsString length] > 0)
             {
                 NSURL *url = [NSURL URLWithString: urlAsString];
                 NSData *data = [NSData dataWithContentsOfURL:url];
                 object.image = [UIImage imageWithData:data];
             }
         }
     }];
}

试一试。如果有任何错误或其他原因,请在下方发表评论。