【问题标题】:xcode retain cycle warning only sometimes occursxcode 保留周期警告仅有时会发生
【发布时间】:2017-03-11 14:41:08
【问题描述】:

Xcode 似乎只是有时会发出警告“在此块中强烈捕获 'self' 可能会导致保留周期”,如下面的我的代码中的 sn-p 所示。

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

【问题讨论】:

    标签: ios objective-c xcode automatic-ref-counting objective-c-blocks


    【解决方案1】:

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

    在您的第一个块中,我假设 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 Controller -> Main View -> Date Picker (as a subview) -> Block -> View Controller。任何事情都可能形成循环,您必须牢记您的对象图并确保不会发生这种情况。
    • 视图控制器通过view 属性保留其主视图。视图保留其子视图。
    • 好的,谢谢。这很可怕,不是我有意识地认为来自 java 背景的东西。你知道 Swift 是否会出现这样的保留周期问题吗?
    • 这是iOS编程中的难题之一。 Swift 像 Objective-C 一样被引用计数(而不像 java 那样被垃圾收集)应用相同的规则,并且您以完全相同的方式创建引用循环。虽然编译器在检测它们方面要好一些,但你仍然必须非常小心。
    【解决方案2】:

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

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

    【讨论】:

    • 这只是我截图的例子的巧合。我已经看到当 self 方法捕获调用 self 的块时它不会发出警告,反之亦然。
    【解决方案3】:

    编译器将第二个块识别为可能具有保留周期的原因是因为编译器仅检查名称以addset 开头的函数的保留周期。

    来自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 开头
    /// 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,您很可能会收到相同的警告。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2016-05-03
      • 2012-12-24
      • 2014-05-09
      • 1970-01-01
      • 2011-03-14
      • 1970-01-01
      • 1970-01-01
      • 2013-09-18
      相关资源
      最近更新 更多