【问题标题】:ARC, non-ARC, and inheritanceARC、非 ARC 和继承
【发布时间】:2012-06-13 15:20:54
【问题描述】:

我还没有使用过 ARC,只是在它通过 3rd 方代码强制进入项目时处理它。我已经阅读了所有 ARC 文档,但还没有看到这个问题的答案:

如果我有一个在使用-fobjc-arc 编译的模块中定义的类,我可以在一个不支持 ARC 的模块中从中派生一个新类吗?

在我看来,只要派生类不尝试触及根类中的任何 ivars,它就应该可以正常工作。在我看来,即使有一个调用 [super dealloc] 的 dealloc 方法在派生类中也可以。

那么,反过来呢?我可以从非 ARC 类派生启用 ARC 的类吗?应该也能正常工作吧?

加分项:在混合 ARC 和非 ARC 代码时,我应该让自己注意什么问题?

【问题讨论】:

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


    【解决方案1】:

    我没有发现任何问题。您必须意识到 ARC 类似于源代码预处理器,在编译期间为您添加内存管理调用。当您到达链接阶段时,您无法真正区分 ARC 代码和非 ARC 代码。 (这可能是一种过度简化,但应该适合您的目的。)如果您的派生类具有正确的内存管理并且超类具有正确的内存管理,那么结果将正常工作。

    关于我能想到的唯一区别是处理weak 属性。但我不太了解这些内容,是否可以使用具有弱属性的 ARC 和 MRC 代码的某种组合来得出有缺陷的代码。

    【讨论】:

    • 这似乎是共识。感谢您的帮助。
    【解决方案2】:

    这是一条评论,但考虑到它,我想扩展它所说的内容。

    您是否尝试过从普通子类继承 ARC 类?我的想法(也没有尝试过)是这行不通。首先,如果 ARC 类具有公共属性或使用 ARC 关键字的 ivars,例如weak,我认为您会在从头文件编译时出错。其次,我不知道dealloc 是如何工作的。您是否需要致电[super dealloc]?我不知道。

    无论如何,如果您的超类是 ARC,为什么不在任何子类中使用 ARC?这样做根本没有任何好处。

    我可以从非 ARC 类派生支持 ARC 的类吗?应该也能正常工作吧?

    我本来想说那也行不通,但我错了。几乎所有东西都必须从手动引用计数的NSObject 继承。

    【讨论】:

    • 从 MRC 代码中启用 ARC 的类派生是有意义的,例如,当您链接到使用 ARC 的库并且您无法重写主项目时。 (我这样做了,它似乎可以工作。)你不能在 ARC 下调用 [super dealloc],我认为编译器会为你插入调用。
    • @zoul - 是的,这也是我们的场景;第 3 方模块需要 ARC,但我们不准备将 ARC 用于我们自己的模块。
    • 拥有一个 ARC 类的非 ARC 子类也是有意义的。具体来说,如果子类必须大量使用 CF API,则有时使用 MRR 代码比使用 ARC 代码更容易。
    • @JeremyP - 为什么我们会按照您的建议出现编译器错误?除了继承之外,MRC 代码总是可以#import ARC 类的标头并使用它,并且该标头中可能包含 ARC 关键字。是吗?
    【解决方案3】:

    是的,您既可以从 ARC 父类实现非 ARC 祖先,也可以从非 ARC 父类实现 ARC 祖先。

    实际上,ARC 是一种语法糖,或者您可以说,它只是在编译步骤分析您的源代码并在您的代码中插入适当的 [release] 和 [retain] 调用的预处理器。在运行时级别没有任何更改(weak 属性除外)。

    【讨论】:

      【解决方案4】:

      ARC 意味着编译器负责内存管理,非 ARC 意味着你负责它,但在这两种情况下内存管理的工作方式完全相同:

      • 如果一个对象必须保持活动状态,则它的保留计数器会增加(这就是 retain 所做的)
      • 如果不再需要某个对象,则其保留计数器会在对它的引用丢失之前减少(这就是 release 所做的)
      • 如果你完成了一个对象但它还不能死掉,例如由于您需要将其作为方法结果返回(并且您不想返回死对象),因此必须将其添加到自动释放池中,以便稍后减少其保留计数(这就是 autorelease 所做的,这就像说“在未来某个时间在该对象上调用 release。”)
      • 新创建的对象的保留计数为1
      • 如果保留计数达到零,则释放对象。

      无论是您自己完成所有这些,还是编译器为您完成所有这些,它都不起作用。编译后,这些方法也会被调用,ARC 也是如此,但是使用 ARC,编译器会为您决定何时调用哪个方法。还有一些额外的魔法,例如在将对象作为方法结果返回时,ARC 并不总是必须将对象添加到自动释放池中,这通常可以被优化掉,但是您不必关心,因为只有在调用者和被调用方法都使用时才应用这种魔法弧;如果其中一个不是,则使用普通的autorelease(它在 ARC 中仍然像以前一样工作)。

      您唯一需要注意的是保留周期。无论您是否使用 ARC,引用计数都无法处理保留周期。这里没有区别。

      陷阱?小心使用免费桥接。 NSString *CFStringRef 实际上是一回事,但 ARC 不知道 CF 世界,所以虽然 ARC 负责 NSString,但您必须负责 CFString。使用 ARC 时,需要告诉 ARC 如何桥接。

      CFStringRef cfstr = ...;
      NSString * nsstr = (__bridge_transfer NSString *)cfstr;
      // NSString * nsstr = [(NSString *)cfstr autorelease];
      

      上面的代码表示“ARC,请拥有该CFString 对象的所有权,并在完成后立即释放它”。该代码的行为类似于下面评论中显示的代码;非常小心,cfstr 的保留计数应该至少为 1,而 ARC 将至少释放一次,只是还没有。反过来:

      NSString * nsstr = ...;
      CFStringRef cfstr = (__bridge_retained CFStringRef)cftr;
      // CFStringRef cfstr = (CFStringRef)[nsstr retain];
      

      上面的代码表示“ARC,请给我NSString 的所有权,我会在完成后处理它的发布”。当然,你必须信守诺言!有时你必须调用CFRelease(cfstr),否则你会泄漏内存。

      最后是(__bridge ...),它只是一个类型转换,没有所有权转移。这种类型的转换是危险的,因为如果你试图保留转换结果,它会产生悬空指针。通常你只在将 ARC 对象提供给期望 CF 对象的函数时使用它,因为 ARC 肯定会保持对象存活直到函数返回,例如这总是安全的:

      doSomethingWithString((__bridge CFStringRef)nsstr); 
      

      即使 ARC 被允许在任何时候释放nsstr,因为该行下面的代码不再访问它,它肯定不会在此函数返回之前释放它,并且根据定义,函数参数只能保证保持活动状态,直到函数返回(如果函数想要让字符串保持活动状态,它必须保留它,然后 ARC 在释放它后不会释放它,因为保留计数不会变为零)。

      大多数人似乎难以解决的问题是将 ARC 对象作为 void * 上下文传递,就像您有时必须使用旧 API 一样,但实际上这非常简单:

      - (void)doIt {
         NSDictionary myCallbackContext = ...;
         [obj doSomethingWithCallbackSelector:@selector(iAmDone:) 
              context:(__bridge_retained void *)myCallbackContext
          ];
          // Bridge cast above makes sure that ARC won't kill
          // myCallbackContext prior to returning from this method.
          // Think of:
          // [obj doSomethingWithCallbackSelector:@selector(iAmDone:) 
          //    context:(void *)[myCallbackContext retain]
          // ];
      }
      
      // ...
      
      - (void)iAmDone:(void *)context {
          NSDictionary * contextDict = (__bridge_transfer NSDictionary *)context;
          // Use contextDict as you you like, ARC will release it
          // prior to returning from this method. Think of:
          // NSDictionary * contextDict = [(NSDictionary *)context autorelease];
      }
      

      而且我必须为你找到真正的大问题,乍一看并不那么明显。请考虑以下代码:

      @implementation SomeObject {
          id _someIVAR;
      }
      
      - (void)someMethod {
          id someValue = ...;
          _someIVAR = someValue;
      }
      

      此代码在 ARC 和非 ARC 中不同。在 ARC 中,默认情况下所有变量都是强变量,因此在 ARC 中,这段代码的行为就像这段代码一样:

      @interface SomeObject
          @property (retain,nonatomic) id someIVAR;
      @end
      
      @implementation SomeObject
      
      - (void)someMethod {
          id someValue = ...;
          self.someIVAR = someValue;
      }
      

      分配someValue 将保留它,对象保持活动状态!在非 ARC 中,代码的行为如下:

      @interface SomeObject
          @property (assign,nonatomic) id someIVAR;
      @end
      
      @implementation SomeObject
      
      - (void)someMethod {
          id someValue = ...;
          self.someIVAR = someValue;
      }
      

      注意属性是不同的,因为非ARC中的ivar既不是strong也不是weak,它们什么都不是,它们只是指针(在ARC中称为__unsafe_unretained,这里的关键字是不安全)。

      因此,如果您的代码直接使用 ivars 并且不使用带有 setter/getter 的属性来访问它们,那么从非 ARC 切换到 ARC 可能会导致过去具有健全内存管理的代码中的保留周期。另一方面,从 ARC 迁移到非 ARC,这样的代码可能会导致悬空指针(指向以前的对象的指针,但由于对象已经死亡,这些指针指向无处并且使用它们会产生不可预知的结果),因为过去的对象以前还活着,现在可能会意外死去。

      【讨论】:

        猜你喜欢
        • 2012-07-18
        • 2015-08-12
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多