你如何使一个块成为一个类的成员?

时间:2012-05-24 19:01:17

标签: objective-c objective-c-blocks

我创建了一个类,它是NSURLConnection的包装器,类的用户传递在连接的各个阶段调用的块。包装类如下所示,简称为简洁:

typedef void (^ConnectionFinishedWithErrorBlock)(NSError* error);

@interface MYHTTPConnection : NSObject <NSURLConnectionDelegate>
@property (copy, nonatomic)     ConnectionFinishedWithErrorBlock   theErrorBlock;
@property (strong, nonatomic)   NSURLConnection *connection;

- (void) establishConnectionForURL:(NSURL*) url 
      andConnectionFinishedWithErrorBlock: (ConnectionFinishedWithErrorBlock) errorBlock;
@end

到目前为止,我一直在使用内联声明的块,即

[self.httpConnection establishConnectionForURL: url
            andConnectionFinishedWithErrorBlock:^(NSError* error)
            {
                ...
            }]; 

但是现在我的情况是我有一个非常长的错误处理块,并且将所有代码置于内联将变得混乱(因为API需要其他块未在此处显示)。

我知道我可以这样做:

void (^httpConnectionFinishedWithError)(NSError*) = 
^(NSError* error) 
{
}

然后将httpConnectionFinishedWithError传递给establishConnectionForURL。但httpConnectionFinishedWithError包含对self的调用。当块被内联声明时,对self的调用很好,但是当如上所述完成时则不然,因为这会产生编译错误。

所以我想知道是否/如何将块作为调用类的块属性? (我之前已经使用过块作为类的属性,就像在上面的MYHTTPConnection类中所做的那样工作正常,但是在这种情况下我无法正确地添加传递给establishConnectionForURL作为属性的块的语法。调用类)。

或者,httpConnectionFinishedWithError可以调用另一个函数,但是在那种情况下我必须将self作为参数传递,在这种情况下如何在块中获取self?

还有其他更优雅的解决方案吗?

非常感谢

编辑: 已更新以显示编译错误示例

void (^httpConnectionFinishedWithError)(NSError*) = 
^(NSError* error) 
{
    self.boolProperty = NO; // here
}

2 个答案:

答案 0 :(得分:2)

如果你正在使用ARC,你可以定义一个引用self的弱变量:

__weak ClassOfSelf *_self = self;

并在块中使用它。如果你不使用ARC我相信你想使用__block而不是弱。在这两种情况下,块都不会保留自身,因此可以避免保留周期。

我知道在类中存储/使用块的最好方法是先键入它:

typedef void (^HttpConnectionFinishedWithError)(NSError*);

然后为块存储创建一个实例变量:

HttpConnectionFinishedWithError httpCompletionBlock;

在init中创建块并将其分配给实例变量。只需记住先复制块以便它移动到堆中(块在堆栈上创建,这意味着它们会在它们创建的范围消失后消失):

- (id) init{
  if (self = [super init]){
    __weak id _self = self; //or use class of self instead of id
    httpCompletionBlock = [^(NSError *err){
      .....code here, use _self instead of self
    } copy];
  }
  return self;
}

(使用ARC的例子)

现在httpCompletionBlock可以像在课堂上一样正常使用。只要您在块代码中使用_self而不是self并且,您不会直接引用块中的任何实例变量,就不会创建保留周期。请记住,引用_iVarself->_iVar相同。因此该块将捕获self。而是使用:_self->_iVar或创建一个property / get-method来访问实例变量。

如果您使用的是ARC,但定位的是iOS 4,则无法使用__weak,因为它不可用且您无法使用{{1}因为它不会阻止块在ARC下保留变量。相反,您可以使用__block。请记住,如果变量引用的对象是dealloc,那么__unsafe_unretained无法解析为__unsafe_unretained,因此它不安全。你需要小心如何使用它。例如,如果您传递另一个对象此块,nil将获得dealloc&#39; d。对_self的任何引用都会导致错误。解决这个问题的一种方法是创建一个内联&#34;中间&#34;只有块需要才能保留self的块。例如,不要直接传递块,而是执行以下操作:

self

在上面的例子中,self.httpConnection establishConnectionForURL: url andConnectionFinishedWithErrorBlock:^(NSError* error) { httpCompletionBlock(error); }]; 是一个实例变量,因此httpcompletionBlock由本地范围内的块捕获。您现在确信selfself执行后才会消失。您仍然避免了保留周期,因为只有内联块保留httpCompletionBlock,而不是存储块。

答案 1 :(得分:0)

使用属性的替代方法是从方法返回块,如下所示:

-(ConnectionFinishedWithErrorBlock)connectionFinishedWithErrorBlock {
    return ^(NSError* error) {
        self.boolProperty = NO;
    };
}

[self.httpConnection establishConnectionForURL:url
           andConnectionFinishedWithErrorBlock:[self connectionFinishedWithErrorBlock]]; 

没有保留周期,因为您的类没有保留块的实例。在回调之后httpConnection将不会处理块并且您的对象将泄漏。如果你没有httpConnection所拥有的,请确保你理解它的语义是什么,并在Instruments中测试,以确保它不会泄漏连接完成块。