xcode 保留周期警告只是有时出现

xcode retain cycle warning only sometimes occurs

Xcode 似乎只是有时会发出警告 "Capturing 'self' strongly in this block is likely to lead to a retain cycle",如下面的代码片段所示。

第一个块在保留循环中实际上是安全的吗?如果是,为什么?或者它不安全并且 xcode 错误地没有发出警告?

OnDateChange 被发送到不同的对象,因此 Xcode 预计不会有任何保留周期(尽管理论上它仍然可能发生)。

AddOnTap 被发送给自己,所以它保持在块周围的可能性很高。因此警告。

这两个块都会导致一个保留周期。只是第一个更难检测到它,所以编译器不会报告它。

在您的第一个块中,我假设 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
}];

编译器将第二个块识别为可能具有保留循环的原因是因为编译器仅检查名称以 addset.[=20= 开头的函数的保留循环]

来自clang documentation

  1. 检查保留周期会验证选择器是否“setter 像”
/// 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);
}
  1. "Setter like" 方法以 addset
  2. 开头
/// 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 重命名为 addDateChangedCallbacksetDateChangedCallback,您很可能会收到相同的警告。