【问题标题】:Why does the direct dispatch version leak under ARC? (and how can I fix that?)为什么ARC下直接派发版本会泄露? (我该如何解决?)
【发布时间】:2019-05-23 13:56:05
【问题描述】:

我有一段代码需要优化——所以我做了直接调度。直接分派是有效的(就像代码执行一样,做正确的事情并且不会崩溃),但是 ARC 不知何故失去了对客户端对象的跟踪,并且它永远不会被释放。标准调度版本可以工作,并且不会泄漏。如何修复直接调度版本?

标准调度版本:

     Client * client;
     client = [Client newClientForServerSocket: serverSocket];

直接派发版本:

     Client * client;
     Class clientClass = Client.class;
     client = (*IMP_newClientForServerSocket)(clientClass,@selector(newClientForServerSocket:),serverSocket);

(旁注:奇怪的是,如果我将 Client.class 直接粘贴在调度的“self”参数中,调度会崩溃。可能是一个线索。)

【问题讨论】:

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


    【解决方案1】:

    ARC 假定名称以 new 开头的方法返回 +1 保留计数。

    您的Client *client 变量默认为__strong

    在正常的调度代码中,当ARC将newClientForServerSocket:返回的引用分配给client时,它不会调整Client对象的保留计数,因为它转移了@987654328返回的+1的所有权@ 到 client 变量。

    在直接调度代码中,ARC 不知道IMP_newClientForServerSocket 函数返回+1 保留计数。它假定函数返回 +0 保留计数。因此,当 ARC 将引用分配给 client 变量时,它会保留该对象。因此该对象被泄露,因为IMP_newClientForServerSocket 返回的+1 从未被释放。

    您可以通过告诉 ARC IMP_newClientForServerSocket 返回 +1 保留计数来解决此问题。 Clang ARC 文档解释了“Retained return values” 下的操作:

    返回可保留对象指针类型的函数或方法可以标记为返回保留值,表示调用者期望获得 +1 保留计数的所有权。这是通过将ns_returns_retained 属性添加到函数或方法声明来完成的,如下所示:

    id foo(void) __attribute((ns_returns_retained));
    - (id) foo __attribute((ns_returns_retained));
    

    此属性是函数或方法类型的一部分。

    当从这样的函数或方法返回时,ARC 会在返回语句的求值点保留值,然后再离开所有局部作用域。

    当接收到来自此类函数或方法的返回结果时,ARC 会释放包含在其中的完整表达式末尾的值,但会受到局部值的通常优化。

    这是我的测试程序。如果没有ns_returns_retained 属性,它的内存使用会无限增长。有了这个属性,它的内存使用很快就稳定下来了。

    @import Foundation;
    
    @interface Client: NSObject
    + (instancetype _Nonnull)newClient;
    @end
    
    @implementation Client
    + (instancetype)newClient { return [self new]; }
    @end
    
    int main(int argc, const char * argv[]) {
        @autoreleasepool {
            typedef Client *(*IMP_newClient_type)(id, SEL) __attribute((ns_returns_retained));
            IMP_newClient_type IMP_newClient = (IMP_newClient_type)[Client methodForSelector:@selector(newClient)];
            while (true) {
                Client *c = IMP_newClient(Client.class, @selector(newClient));
                [c self]; // do something with c to avoid an unused variable warning
            }
        }
        return 0;
    }
    

    【讨论】:

      【解决方案2】:

      我不确定它是否会起作用,但我会尝试以下方法:

      #import <CoreFoundation/CoreFoundation.h>
      

      然后当你完成client

      CFRelease((__bridge CFTypeRef)(client));
      

      ARC 不起作用也就不足为奇了。它不太可能被设计为以这种方式分配对象。 ARC 在 Objective-C 级别上运行,而不是在底层 C 东西上运行

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2012-05-04
        • 2014-12-06
        • 1970-01-01
        • 1970-01-01
        • 2023-03-28
        • 1970-01-01
        • 2017-06-19
        相关资源
        最近更新 更多