【发布时间】:2017-03-11 14:41:08
【问题描述】:
Xcode 似乎只是有时会发出警告“在此块中强烈捕获 'self' 可能会导致保留周期”,如下面的我的代码中的 sn-p 所示。
第一个块在保留循环中实际上是安全的吗?如果是,为什么,或者它不安全并且 xcode 错误地没有给出警告?
【问题讨论】:
标签: ios objective-c xcode automatic-ref-counting objective-c-blocks
Xcode 似乎只是有时会发出警告“在此块中强烈捕获 'self' 可能会导致保留周期”,如下面的我的代码中的 sn-p 所示。
第一个块在保留循环中实际上是安全的吗?如果是,为什么,或者它不安全并且 xcode 错误地没有给出警告?
【问题讨论】:
标签: ios objective-c xcode automatic-ref-counting objective-c-blocks
这两个块都会导致一个保留周期。只是第一个比较难检测,所以编译器不会报告它。
在您的第一个块中,我假设 datePicker 是您对象的属性。因此,您的对象保留了日期选择器,该选择器保留了保留您的对象的块(通过捕获自我)。这是一个包含 3 个对象的循环,但仍然是一个循环。
在您的第二个块中,这要简单得多:您的对象保留块,块保留您的对象(通过捕获自我)。它是一个只有 2 个易于识别的对象的循环(因此发出警告)。
在这两种情况下,您都应该弱捕获 self 以避免保留循环。
__weak typeof(self) weakSelf = self;
[self methodThatRetainsABlock: ^{
typeof(weakSelf) strongSelf = weakSelf;
if (strongSelf == nil) {
return;
}
// Don't ever use self here, as it will capture it strongly.
// Use only strongSelf
}];
【讨论】:
datePicker 是一个属性,如果不是,那么它是否安全?
view 属性保留其主视图。视图保留其子视图。
OnDateChange 被发送到不同的对象,因此 Xcode 不希望有任何保留周期(尽管理论上它仍然可能发生)。
AddOnTap 被发送给自己,因此它保留在块周围的可能性很高。因此发出警告。
【讨论】:
编译器将第二个块识别为可能具有保留周期的原因是因为编译器仅检查名称以add 或set 开头的函数的保留周期。
/// Check a message send to see if it's likely to cause a retain cycle.
void Sema::checkRetainCycles(ObjCMessageExpr *msg) {
// Only check instance methods whose selector looks like a setter.
if (!msg->isInstanceMessage() || !isSetterLikeSelector(msg->getSelector()))
return;
// Try to find a variable that the receiver is strongly owned by.
RetainCycleOwner owner;
if (msg->getReceiverKind() == ObjCMessageExpr::Instance) {
if (!findRetainCycleOwner(*this, msg->getInstanceReceiver(), owner))
return;
} else {
assert(msg->getReceiverKind() == ObjCMessageExpr::SuperInstance);
owner.Variable = getCurMethodDecl()->getSelfDecl();
owner.Loc = msg->getSuperLoc();
owner.Range = msg->getSuperLoc();
}
// Check whether the receiver is captured by any of the arguments.
for (unsigned i = 0, e = msg->getNumArgs(); i != e; ++i)
if (Expr *capturer = findCapturingExpr(*this, msg->getArg(i), owner))
return diagnoseRetainCycle(*this, capturer, owner);
}
add 或 set 开头
/// Check for a keyword selector that starts with the word 'add' or
/// 'set'.
static bool isSetterLikeSelector(Selector sel) {
if (sel.isUnarySelector()) return false;
StringRef str = sel.getNameForSlot(0);
while (!str.empty() && str.front() == '_') str = str.substr(1);
if (str.startswith("set"))
str = str.substr(3);
else if (str.startswith("add")) {
// Specially whitelist 'addOperationWithBlock:'.
if (sel.getNumArgs() == 1 && str.startswith("addOperationWithBlock"))
return false;
str = str.substr(3);
}
else
return false;
if (str.empty()) return true;
return !islower(str.front());
}
在您的代码中,如果您将 onDateChangedCallback 重命名为 addDateChangedCallback 或 setDateChangedCallback,您很可能会收到相同的警告。
【讨论】: