简体   繁体   中英

xcode retain cycle warning only sometimes occurs

Xcode appears to only sometimes gives the warning "Capturing 'self' strongly in this block is likely to lead to a retain cycle", as shown in the below snippet from my code.

在此处输入图片说明

Is the first block actually safe from a retain cycle, and if so why, or is it unsafe and xcode is incorrectly giving no warning?

Both blocks will lead to a retain cycle. It's just a little harder to detect it for the first one so the compiler doesn't report it.

In your first block, I assume datePicker is a property of your object. So your object retains the date picker, which retains the block which retains your object (by capturing self). It's a cycle with 3 objects but a cycle nonetheless.

In your second block this is much more straightforward : your object retains the block and the block retains your object (by capturing self). Its a cycle with only 2 objects easy to identify (hence the warning).

In both cases you should capture self weakly to avoid the retain cycle.

__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
}];

OnDateChange is sent to a different object, so Xcode doesn't expect there to be any retain cycles (though theoretically it could still happen).

AddOnTap is sent to self, so possibility of it keeping around the block is high. Hence the warning.

The reason why the compiler identifies the second block as potentially having a retain cycle is because the compiler only checks retain cycles for functions with names starting with add or set .

From the clang documentation :

  1. Checking retain cycles verifies selectors that are "setter like"
/// 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" methods start with add or 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());
}

In your code, if you rename onDateChangedCallback to addDateChangedCallback or setDateChangedCallback , you'll most probably have the same warning.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM