问题 使用ARC的Objective-C:自定义setter不会保留


注意:这是由于一些XCode测试版中的错误已经修复。如果您遇到ARC问题,此问题和答案可能无法帮助您。


我正在将我的项目从手动引用计数迁移到ARC,并偶然发现了一个问题: 如何确保保留属性的自定义setter实际保留?

myClass.h,我宣布了一个财产: @property (retain) NSDate *date。我是否手动设置一个并不重要 __strong ivar或自动生成。

在实施中,我当然有 @synthesize date,并实现了一个自定义setter(或只是 下载演示Xcode项目):

- (void)setDate:(NSDate *)newDate
{
  if (allowedToSetNewDate)
  {
    date = newDate;
  }
}

这似乎没有保留日期,并给了我 message sent to deallocated instance 当newName是(自动)释放它来自哪里时,尝试访问时 myClass.date 稍后(提供Zombie启用;否则,它只是静默崩溃)。

更改要使用的setter date = [newDate copy] 解决错误,但不是我想要的。删除自定义setter也有效,但显然不是必需的。

我在这里想念的是什么?如何确保保留属性的自定义setter实际保留在ARC环境中?这似乎是一项基本而且常见的任务,我认为我忽略了一些非常明显的事情。

(注意:这不属于任何Apple NDA的条款,因为ARC作为LLVM的一部分公开发布)

编辑:我已经创建了一个小型Xcode项目来演示该问题并将其上传到github。随意地 下载它 和玩耍。我的机智已经结束了(尽管我的机智今天并不是最好的,不可否认)。

编辑:对于此示例项目,此问题已解决(请参阅接受的答案)。不幸的是,在我无法分享的更大的项目中,问题仍然存在。作为一种解决方法,我添加了重复 strong 具有合成setter的属性(ivars不起作用)。新的自定义setter现在看起来像这样:

- (void)setDate:(NSDate *)newDate
{
  if (allowedToSetNewDate)
  {
    self.date_arcretain = newDate; //this property is only there as a workaround. ARC properly retains it, but only if the setter is synthesized
    date = newDate;
  }
}

5916
2017-08-14 10:18


起源

这可能有点帮助,关于你应该调用你的NSString的(非原子的,保留)的使用: stackoverflow.com/questions/1380338/... - Luke
@Luke,我不知道这与这个问题有什么关系。我确实理解保留计数是如何工作的,以及属性声明是什么意思 - 正如我在问题中写的那样,我正在从一个项目迁移(用手动计数,而不是GC - 将这个添加到ARC)。编辑:我现在看,你的意思是字符串应该是 copyd。我将更新我的示例,因为这不仅涉及字符串。 - fzwo
不太确定,但我认为你必须符合条件 newDate 参数为 __strong。 - omz
@omz谢谢,我考虑过这个,但它似乎没有用(或者我做错了,这完全有可能)。我将上传一个非常小的项目,在几分钟内演示这一点,我会邀请大家一起玩,看看它是否适合他们以及他们如何解决它。坦率地说,我不知道我应该做些什么不同,看看我怎么不能手动保留。 - fzwo
或者您可能需要将实例变量声明为 __strong。坦率地说,到目前为止我还没有真正使用过ARC,所以这主要是从我对文档的理解中猜测出来的。 - omz


答案:


这看起来像是我的错误;你的代码应该没问题。如果您还没有这样做,请在http:// bugreport.apple.com上提交一个错误,并附上您的示例项目。

编辑:在进一步检查您的示例项目时,这不是错误。

示例项目中过度发布的对象是  该 NSDate 实例。你可以注释掉 tc.date = now 完全调用您的示例项目,您仍然会看到相同的崩溃。事实上,你可以完全取出NSDate的东西。过度释放的对象实际上是 TestVC 对象本身。

这是正在发生的事情。

在iOS 4.0中, UIWindow 有一个 rootViewController 属性。以前你只需要打电话 [self.window addSubview:myRootcontroller.view] 在启动应用程序时,此更改现在意味着窗口实际上将引用根视图控制器。这对于传递旋转通知等很重要。在过去,我相信UIWindow会在添加第一个子视图时自动尝试设置rootViewController(如果它尚未设置),但是在您的示例项目中显然没有发生。这可能是由于您创建视图的方式,或者可能是由于iOS 5.0中的更改。无论哪种方式,都没有记录行为,所以你不能依赖它发生。

在大多数情况下,您的app委托将有一个指向根视图控制器的ivar。这不是严格要求的,但通常会发生什么。但是,在您提供的示例项目中,视图控制器不属于应用程序委托。你也没把它设置为窗口的根视图控制器。结果,在结束时 -application:didFinishLaunchingWithOptions: 方法,有 没有 留下了对视图控制器的强引用。您的应用代表没有抓住它,窗口本身并没有坚持下去。因此,ARC将其视为局部变量(它是),并在方法结束时释放它。

当然,这不会改变你的事实 UIButton 仍然有一个针对您的控制器的动作方法。但正如文档中所述, -addTarget:action:forControlEvents: 不保留目标。所以 UIButton 有一个悬挂式引用你的视图控制器,现在已被解除分配,因为没有人强烈引用它。因此崩溃。

解决此问题的方法是在应用委托中更改此行:

[self.window addSubview:tc.view];

对此:

self.window.rootViewController = tc;

通过这一次改变,现在一切正常。

编辑:还要确保“ARC迁移的预检代码”设置未打开,因为这会导致编译器将代码视为手动管理,并且这不会插入正确的保留/释放调用。


12
2017-08-14 15:32



干得好,非常感谢!我将不得不看看我的大项目是否也是如此,但很可能。我把你的答案标记为正确。如果问题持续存在,我会回复你。顺便说一句,你抓到的东西甚至苹果开发没有:) - fzwo
不幸的是,这不是解决方案。尽管你的解释非常好,但ViewController本身并没有被解除分配。我已经更新了示例项目,并添加了一个NSLog来清楚地显示它。我还明确地设置了rootViewController,因为它的设计很好(感谢指针!)。我正在取消接受,并将此报告为错误。 - fzwo
您对项目的更新发生了更多变化 self.rootViewController = tc;  - 您还更改了“将代码从MRR迁移到ARC”构建设置 Do not run any phase of the migration 至 PRECHECK code for ARC incompatibilities。我已经看到其他关于ARC构建设置开启时行为异常的报告,事实上:关闭预检,再次示例项目工作得很好。顺便提一下,这个预检问题肯定是一个值得提交错误的问题。 - BJ Homer
再次感谢BJ Homer。我忘了我已经玩过那个设置,看它是否有所作为。如果您愿意添加这些信息(禁用预检),我会再次接受您的回答。 - fzwo
问题现在解决了。非常感谢你!设置“Precheck ...”构建设置时,请确保为每个目标设置它,而不仅仅是项目。我发现了一些白发,试图找出它为什么适用于一个项目而不是另一个项目。 - fzwo


我用你的项目把它固定在我的Mac上。错误发生在您的AppDelegate中,而不是在您的日期属性中。使视图控制器成为AppDelegate中的可保留属性。目前,您的视图控制器已自动释放,包括其所有属性。


2
2017-08-14 16:02



在我的模拟器(最新版本)或设备(最新固件)中不是这种情况。 VC也没有自动释放,也没有明确设置它来解决问题。我已经更新了示例项目,但它仍然会崩溃。 - fzwo