0
votes

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.

enter image description here

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?

3

3 Answers

2
votes

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
}];
1
votes

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.

0
votes

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.