我见过同样形式的其他几个问题,但我要么a)无法理解所提供的答案,要么b)看不出那些情况与我的相似。
我正在UIView上编写一个类别,以递归方式评估UIView的所有子视图,并返回通过测试的子视图数组。我已经注意到我的编译器警告发生在哪里:
-(NSArray*)subviewsPassingTest:(BOOL(^)(UIView *view, BOOL *stop))test {
__block BOOL *stop = NO;
NSArray*(^__block evaluateAndRecurse)(UIView*);
evaluateAndRecurse = ^NSArray*(UIView *view) {
NSMutableArray *myPassedChildren = [[NSMutableArray alloc] init];
for (UIView *subview in [view subviews]) {
BOOL passes = test(subview, stop);
if (passes) [myPassedChildren addObject:subview];
if (stop) return myPassedChildren;
[myPassedChildren addObjectsFromArray:evaluateAndRecurse(subview)];
// ^^^^ Compiler warning here ^^^^^
// "Capturing 'evaluateAndRecurse' strongly in this block
// is likely to lead to a retrain cycle"
}
return myPassedChildren;
};
return evaluateAndRecurse(self);
}
此外,如果我在块的声明__block
中未包含(^__block evaluateAndRecurse)
修饰符,则会导致bad_access失败。如果有人能够解释为什么会这样,那也是非常有帮助的。谢谢!
答案 0 :(得分:8)
这里的问题是你的块evaluteAndRecurse()
捕获了自己,这意味着,如果它被复制(我不相信它会在你的情况下,但在稍微不那么简单的情况下它可能)然后它将保留自己,因此永远存在,因为没有什么可以打破保留周期。
编辑:Ramy Al Zuhouri提出了一个很好的观点,使用__unsafe_unretained
对该块的唯一引用是危险的。只要块保留在堆栈上,这将起作用,但是如果需要复制块(例如,它需要转义到父作用域),则__unsafe_unretained
将导致它被释放。以下段落已使用推荐的方法进行了更新:
您可能想要做的是使用标有__unsafe_unretained
的单独变量,该变量也包含块,并捕获该单独的变量。这样可以防止它自行保留。你可以使用__weak
,但是因为你知道在调用它时块必须是活的,所以没有必要打扰弱引用的(非常轻微的)开销。这将使您的代码看起来像
NSArray*(^__block __unsafe_unretained capturedEvaluteAndRecurse)(UIView*);
NSArray*(^evaluateAndRecurse)(UIView*) = ^NSArray*(UIView *view) {
...
[myPassedChildren addObjectsFromArray:capturedEvaluateAndRecurse(subview)];
};
capturedEvaluateAndRecurse = evaluteAndRecurse;
或者,您可以捕获指向块的指针,该指针具有相同的效果,但允许您在块实例化之前而不是之后获取指针。这是个人偏好。它还允许您省略__block
:
NSArray*(^evaluateAndRecurse)(UIView*);
NSArray*(^*evaluteAndRecursePtr)(UIView*) = &evaluateAndRecurse;
evaluateAndRecurse = ^NSArray*(UIView*) {
...
[myPassedChildren addObjectsFromArray:(*evaluateAndRecursePtr)(subview)];
};
至于需要__block
,这是一个单独的问题。如果您没有__block
,则块实例将实际捕获变量的先前值。请记住,创建块时,任何未标记为__block
的捕获变量实际上都会在块实例化的位置存储为其状态的const
副本。并且由于在之前创建了块,它被分配给变量,这意味着它在赋值之前捕获capturedEvaluteAndRecurse
变量的状态,这将是nil
(在ARC;否则,它将是垃圾内存。)
从本质上讲,您可以将给定的块实例视为实际上是每个捕获变量都具有ivar的隐藏类的实例。因此,使用您的代码,编译器基本上将其视为:
// Note: this isn't an accurate portrayal of what actually happens
PrivateBlockSubclass *block = ^NSArray*(UIView *view){ ... };
block->stop = stop;
block->evaluteAndRecurse = evaluateAndRecurse;
evaluteAndRecurse = block;
希望这可以清楚地说明为什么它会捕获evaluateAndRecurse
的先前值而不是当前值。
答案 1 :(得分:0)
我做过类似的事情,但以不同的方式减少分配新阵列的时间,并且没有任何问题。你可以尝试调整你的方法看起来像这样:
- (void)addSubviewsOfKindOfClass:(id)classObject toArray:(NSMutableArray *)array {
if ([self isKindOfClass:classObject]) {
[array addObject:self];
}
NSArray *subviews = [self subviews];
for (NSView *view in subviews) {
[view addSubviewsOfKindOfClass:classObject toArray:array];
}
}