2016-06-26 10:49:17 iuyo89007 阅读数 8801

注意:以下方法只可用于debug,而且在多线程等情况下返回值不是100%可信。

1.使用KVC

[obj valueForKey:@"retainCount"]

2.使用私有API

OBJC_EXTERN int _objc_rootRetainCount(id);
_objc_rootRetainCount(obj)

3.使用CFGetRetainCount

CFGetRetainCount((__bridge CFTypeRef)(obj))


2014-11-03 09:25:36 MIRAGE086 阅读数 754

英文原文:Automatic Reference Counting on iOS


自动引用计数(ARC)是在MacOS X 10.7与iOS 5中引入一项新技术,用于管理Objective-C中的对象。它废弃了显式的retain、release和autorelease消息,而且在两个平台的表现一致。

由于有限的内存以及手持设备续航能力的限制,iOS应用程序中的Objective-C对象的管理一直颇有挑战性。为了处理这些问题,苹果提出了一个方案——“自动引用计数”(ARC)。在这篇文章中,我会把ARC和显式的retain/release以及垃圾回收进行对比,除此之外还会展示如何在一个iOS项目中使用它,并且探讨一些ARC的使用准则。 

读者应当拥有Objective-C和Xcode IDE的使用经验。 

 

通过消息传送来实现

首先,我通过显示的消息传送来管理ObjC对象。我用alloc和init消息来创建对象(如图 1)。我发送retain消息来保持一个对象,并且发送release消息来释放掉它。通过alloc/init创建的ObjC对象会有一个内部的值为1的引用计数。retain消息会使这个引用计数加1,然而,release消息会使这个引用计数减1.当这个引用计数为0时,这个对象会自动销毁,释放它所持有的内存。 

 
Figure 1.


我们也可以用一个工厂方法来创建ObjC对象。这样就标记了这个对象是自动释放的,将他的指针加到自动释放池(如图 2)。我们可以通过autorelease消息来实现对alloc/init的对象达到发送release消息的目的。 

 
Figure 2.




在每一个事件周期中,自动释放内存池都会去检测自身的对象指针集。当它发现超出其作用域并且引用记数为1的对象,它就会通过发送一个release消息释放这个对象。当不想释放这个对象时,我们可以发送一个或多个retain消息给这个对象。否则,我们必须让这个对象发送的retain和release消息一样多,才能将它释放。 

显式发送消息的方式仍然是iOS应用程序中管理ObjC对象的一种有效方法。它一般不会花费很多精力,可以很容易的定位bug,同时拥有性能好的特点。 

在另一方面,显式发送消息的方式很容易导致出错。当retain和release消息不相等时,它会导致内存泄露或EXC_BAD_ACCESS错误。另外,显式地释放一个已经释放了的对象也会导致EXC_BAD_ACCESS错误。并且,对象容器(如数组,集合等)可能并不会对它包含的引用记数大于1的对象运行该对象的构造函数。
 

使用垃圾回收管理

MacOS X 10.5 (Leopard) 给我们另一个管理ObjC对象的方法— 垃圾回收。这里,每一个Cocoa应用程序得到自己的作为次级线程运行的收集服务  (Figure 3)。 

 
Figure 3.

这个服务标识所有在一起动就创建的根对象,然后跟踪每一个后来创建的对象。它检查每一个对象的范围以及对根对象的强引用。如果对象有这些特性,那么收集服务将它保留下来(用蓝色标记)。否则,它使用一个finalize消息释放这个对象(用红色标记)。

 

收集服务是保守的。当必须保证高性能时,它可以被中断,甚至暂停 。它是一个分代的服务。他假定最新被创建的对象寿命最短。 

通过类NSGarbageCollector来使用收集服务。使用这个类,我能够禁用这个服务或者改变它的行为。我甚至能指定新的根对象或者重置服务本身。


垃圾回收移除了显式的保留和释放消息的需要。它能够降低野指针也能够防止空指针。换句话说,它需要所有定制的ObjC对象被更新。清除代码必须进入到finalize方法,而不是 dealloc方法。ObjC对象也必须向他的父发送一个finalize消息。 

接下来,收集服务需要知道何时一个对象的引用是弱的。否则,他假设所有的引用都是强的。这可能导致循环引用和内存泄漏。这个服务也忽略使用withmalloc()创建的对象:那些对象应该被手动释放或者使用Cocoa函数NSAllocateCollectable()来创建。 

最后,这个服务依然会导致性能受到影响,尽管他是保守的。这就是垃圾回收在iOS上缺席的原因。 

 

进入 ARC

ARC是一种全新的方式,它拥有很多垃圾回收机制的优点,但却没有那样的性能损耗。 

从内部来看,ARC并不是一项运行时的服务。实际上它是由新的Clang front-end提供的两段过程。图4显示了这两段过程。在front-end段时,Clang检查每个预处理文件的对象和属性。然后它跟据一些固定的规则将正确的retain,release和autorelease语句加入其中。 

图4.

举例来说,如果对象被分配内存并处于一个方法当中,它会在这个方法的结尾处获得一个release语句。如果是一个类属性,它的release语句会加入到类的dealloc方法中。如果这个对象是用来返回的或者它是一个容器对象,它会加入一个autorelease语句。又如果这个对象是弱引用,把它放在一边不管它。

 

前端也为非局部对象插入保留语句。他更新所有使用@property指示符声明的访问器。他添加了对父的dealloc的调用,并且他报告任何的显式的管理调用和任何不清晰的所有权。 

在优化阶段,Clang 使修改过的代码遵从加载平衡。它统计每一个对象保留和释放的调用,然后把它们缩减到优化的最小值。这避免了过度的保留和释放,能够在性能上产生影响。

为了描述, 看看在Listing One中的实例代码。 

Listing One 

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
@class Bar;
@interface Foo
{
@private
    NSString *myStr;
}
@property(readonly) NSString *myStr;
  
- (Bar *)foo2Bar:(NSString *)aStr;
- (Bar *)makeBar;
//...
@end
  
  
@implementation Foo;
@dynamic myStr;
  
– (Bar *)foo2Bar:(NSString *)aStr
{
    Bar *tBar;
      
    if (![self.myStr isEqualToString:aStr])
    {
        myStr = aStr;
    }   
    return ([self makeBar]);
}
  
- (Bar *)makeBar
{
    Bar *tBar
    //...
    //... conversion code goes here
    //...
    return (tBar);
}
//...
@end
在这里,我展示了一个没有任何保留和释放消息的ObjC类。它有一个私有的属性myStr,它是一个NSString(第5行)的实例.它声明了一个只读的getter, 也命名为myStr(第7行).它定义了一个修饰符foo2Bar和一个内部函数makeBar(18-36行)。这个类也使用@class指示符导入了类Bar的header(第1行)。 
 

列表2列出了相同简单的经过ARC处理后的代码。 

列表2 

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
@class Bar;
@interface Foo
{
@private
    NSString *myStr;
}
@property (readonly) NSString *myStr;
  
- (Bar *)foo2Bar:(NSString *)aStr;
- (Bar *)makeBar;
//...
@end
  
  
@implementation Foo;
@dynamic myStr;
  
– (Bar *)foo2Bar:(NSString *)aStr
{
    Bar *tBar;
      
    if (![self.myStr isEqualToString:aStr])
    {
        [aStr retain];
        [myStr release];
        myStr = aStr;
    }   
    return ([self makeBar]);
}
  
- (Bar *)makeBar
{
    Bar *tBar
    //...
    //... conversion code goes here
    //...
    [tBar autorelease];
    return (tBar);
}
//...
  
- (void)dealloc
{
    [myStr release];
    [super dealloc];
}
@end
类接口并没有被改变。但是它的foo2Bar被新加入了两行代码。一个语句发送一个release消息给属性myStr(行24)。另一个发送一个retain消息给参数aStr(行25)。makeBar函数在返回之前发送一个autorelease消息给局部变量tBar,并将tBar作为返回值。最后,ARC重写了类的dealloc方法。在这个方法中,它释放了属性myStr(行44)并且调用父类的dealloc方法(行45)。如果一个dealloc方法已经存在,ARC会将它的代码作适当更新。
 

因为ARC独自决定ObjC对象如何被管理,它省掉了开发类代码的时间。它阻止了任何野指针和空指针。它甚至能够基于文件来禁用。最后一个特征让程序员可以复用被证明是稳定的遗留代码。 

但是Clang编译器被构建在LLVM 3.0中,它只能在Xcode 4.2或者更新的版本中获得.对于ARC的优化的运行时支持也仅仅出现在MacOS X 10.7 (Lion) 和 iOS 5.0。在iOS 4.3中通过粘合代码使用ARC是可能的。在后来提供的不使用任何弱指针的二进制文件中使用ARC也是可能的。 

然后,ARC只对ObjC代码起作用。对于PyObjC和AppleScriptObjC代码没有任何效果。但是,它的确影响那些桥接PyObjC,ASOC类到Cocoa的底层ObjC对象。也值得注意的是,一些第三方的框架在使用ARC开启的编译的时候也可能会导致问题 。请确保在更新版本时联系框架的开发者。 


 

为ARC做准备

在一个iOS项目中有两个方法支持ARC。一个是使用ARC-enabled的模板创建项目。另一个是使用Xcode改写一个现存的项目。 

假设你想开始一个新的iOS项目。启动Xcode,从文件菜单里选择新项目。在新建对话框中 (Figure 5),选择一个项目模板(在这个例子中,单视图项目),点击Next查看项目选项。在界面的字段中输入项目名称,公司ID,以及类前缀。 然后勾选Use Automatic Reference Counting (Figure 6). 点击Next查看项目位置。设置位置和源代码仓库 (可选),然后点击Create创建项目本身。 

 
Figure 5.

 
Figure 6.

为了验证ARC是否被开启,在项目窗口中的组和文件面板中选择项目图标 (Figure 7).从Target Setting工具条中点击Build Settings。,然后点击那个工具条下边的All。向下滚动并且定位到设置组Apple LLVM computer 3.0 — Language。 查找条目Objective-C Automatic Reference Counting — 它的值应该是Yes. 

 
Figure 7.

 

如果你想将一个已经存在的iOS工程转化成ARC要怎样做呢?打开这个工程进入Xcode并且点击Edit菜单。从Refactor子菜单(Figure 8),选中Convert to Objective-C ARC...,会弹出另一个帮助对话框,进行相应的操作步骤。 

图8

点击Next按钮可以看到一个构建目标列表(图9)。选中一个目标并点击Precheck来开始重构工程。如果重构失败,Xcode会警示用户,并在工程窗口中列出相应的重构错误。 

图9

 

如果重构成功,Xcode将进入对比模式(如图 10)。这个帮助对话框分割成三个面板。左面的面板展示了涉及到的文件,默认是被选中的。中间的面板展示了源文件,右边的面板展示了修改后的文件。检查建议的改变并且点击Save提交他们或者点击Cancel放弃修改。无论如何请确保有一个代码的备份。否则,你将不能把工程文件还原到ARC之前的状态。 

 
图片 10.

如果你想从ARC中移除一些工程文件会怎么样?如果是这样,在Xcode是对比模式时取消文件旁边复选框的选中状态(看图10)。如果这个文件可以加入到重构工程中,在文件面板的分组中选中工程图标。点击Target Settings 面板中Build Phases,并且向下滚动找到Compile Sources分组(如图 11)。点击分组头选择要移除的文件。然后点击Compile Flags列并且在模态对话框中输入-fno-objc-arc。点击Done设置标记,它将在每个文件中出现。 

 
图片 11.

 

ARC指导方针

当从头开始写ObjC代码的时候,你应该确保代码与ARC兼容。代码必须给ARC完成工作所必要的线索。否则,他可能在错误的地方插入代码,或者更糟,他可能在代码中报告错误。 

这些是写ARC兼容代码的指导方针。这些Apple官方文档和博客文章被编辑放在文章的最后。  

  • 让ARC决定如何以及何时保留或者释放对象。
    如前所述,ARC前端捕获所有的显式的保留和释放消息。 他将把它们当做错误报告,并且你将不得不手动删除它们以便让项目可以编译。因此永远也不要发送保留或者释放消息给ObjC对象。永远不要使用retainCount消息去检查对象的保留状态,因为那个消息不再是可靠的。另外,不要使用@selector指令去调用对象的保留和释放方法。
    至于@property访问器,不使用assign,copy, retain特性来声明他们。让ARC决定给每一个访问器什么特性。 
  • 让ARC管理自动释放池和它的对象。
    再一次声明,ARC前端补货所有的显式的自动释放消息。确保从你的ObjC代码中排除掉这些消息。也不要使用NSAutoreleasePool去创建池。相反,使用@autorelease指令标记池的位置和的范围。这告诉前端插入为ARC优化过的创建和释放池的代码。
    考虑一下在Listing Three中的例子代码。

    Listing Three 

    ?
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    void main()
    {
        NSArray *tArgs = [[NSProcessInfo processInfo]
            arguments];
        unsigned tCnt, tLmt = [tArgs count];
          
        @autorelease
        {
            for (tCnt = 0; tCnt < tLmt; tCnt++)
            {
                @autorelease
                {
                NSString *tDat;
                NSString *tNom;
                  
                tNom = [tArgs objectAtIndex:tCnt];
                tDat = [[[NSString alloc]
                    initWithContentsOfFile:tNom]
                    autorelease];
                  
                // Process the file, creating and
                // autoreleasing more objects
                }
            }
          
        // Do more tasks, creating and autoreleasing
        // more objects
        }
          
        // Do whatever cleanup is needed
        exit (EXIT_SUCCESS);
    }
 
在这里,main()入口函数使用了两个自动释放池。第一个池出现在函数的开始位置(第7行),他一直保持活动状态直到函数结束(第28行). 任何被这个函数标记为自动释放的对象都进入到这个池中。 
第二个池出现在每一个for循环的开始处(11-23行). 当循环结束时,他释放这个池并且创建一个新的。在每一次循环中被标记为自动释放的对象进入到这个池中,而不是第一个池。 
  • 显式的声明任何弱的和非保留引用。
    ARC假定所有的对象引用都是强的。当然,有一些例外, 最好的例子就是窗口视图(Figure 12)。这个视图保持每一个子视图和他包含的widget的强引用。换句话说,每一个子视图和widget仅仅保持一个对父视图的弱引用。

     
    Figure 12.


 

然而,两个对象之间相互强引用是可能的。这种情况称为循环引用。考虑列表 4中的例子。 

列表 4 

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@interface FooLink
{
    FooLink *fooNext;
    FooLink *fooPrev;
    NSData *fooData;
    NSInteger fooID;
}
@property(readwrite, assign) FooLink *nextLink;
@property(readonly) FooLink *prevLink;
 
- (NSData *)getData;
- (void)addToLink:(NSData *)aDat;
- (FooLink *)searchForLink:(NSInteger)anID;
- (BOOL)hasLink:(NSInteger )anID;
//...
@end

类FooLink实现了一个双向链表,每个节点拥有一个NSData对象。属性fooNext和fooPrev都指向另外的FooLink对象。由于两个指针形成了强引用,ARC将不会销毁这个对象。 

为了阻止这种事发生,声明其中一个属性为弱引用。在列表 5中,我在属性fooPrev前面加了一个__weak指令(第四行)。这样,当fooNext指向空并且FooLink对象超出范围时,ARC可以安全的给这个对象发送一个release消息。 

列表 5 

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@interface FooLink
{
    FooLink *fooNext;
    __weak FooLink *fooPrev;
    NSData *fooData;
    NSInteger fooID;
}
@property(readwrite, assign) FooLink *nextLink;
@property(readonly) FooLink *prevLink;
 
- (NSData *)getData;
- (void)addToLink:(NSData *)aDat;
- (FooLink *)searchForLink:(NSInteger)anID;
- (BOOL)hasLink:(NSInteger )anID;
//...
@end
 

__weak指示符既可以声明弱引用,也可以声明空引用。另一个指示符__unsafe_unretained 声明弱引用,而不是空引用。如果你计划自己处理空引用,那么使用这个指示符。 但是一定要去处理,否则你将最终会内存泄露。 

当然还有另外的一些编译器指示符,但是前面两个是你经常会用到的。 

  • 避免转换ObjC对象到C指针。
    指向ObjC对象的指针式是一般的typeid的指针。为了把他们当做一个C-routine的输入,你可能可能转换这个指针如Listing Six (3-9行)所示。这里,我使用了工厂方法stringWithString创建了一个NSString实例。接着我映射它到一个整形指针,并且把它传递给doFoo。

    Listing Six 

    ?
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    // Recasting an ObjC object pointer
    {
    id tStr;
    int *tPtr;
     
    tStr = [NSString stringWithString:@"foobar"];
    tPtr = (int *)tStr;
     
    doFoo(tPtr);
    }
     
    // Using a CFObject
    {
    CFStringRef tStr;
     
    tStr = CFSTR("foobar");
    doFoo(tStr);
    }
  •  
    但是,转换阻止ARC正确的管理对象。ARC将不知道什么时候去保留对象,什么时候释放对象。此外转换指针可能最终指向一个无效的对象。因此,不要转换,使用核心的基础APIs 去创建C兼容的对象。(14-17行). 
    • 避免将ObjC对象作为C结构体的字段。
      在一个C结构体中我们能够使用ObjC对象作为字段。Listing Seven的示例中的struct就有两个这样的字段(4-5行):一个是NSString实例,另一个是NSURL实例。如果 ObjC对象是自定义的,我们可能我们可能使用@def指示符渲染他的属性的可见性。

      Listing Seven 

      ?
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      // A C struct with ObjC fields
      typedef struct FooStruct
      {
          NSString *fooName;
          NSURL *fooPath;
          int fooCount;
          char *fooData;
      } Foo;
       
      // A C struct with CoreFoundation fields
      typedef struct BarStruct
      {
          CFStringRef *barName;
          CFURLRef *barPath;
          int barCount;
          char *barData;
      } Bar;
       
      // The C struct rewritten as an ObjC class.
      @interface BarClass
      {
          NSString *fooName;
          NSURL *fooPath;
          int fooCount;
          char *fooData;
      }
      //...property accessors goes here
      @end
      但是就是在这里,ARC将不能够管理在结构体中的ObjC对象。它可能不能识别对那些对象的引用也不能为那些对象提供正确的保留,释放和自动释放语句。 同样的,使用核心的基础APIs去为结构体创建对象(13-16行)。或者将这个结构体重写为一个简单的ObjC类(20-28行)。 
    • 自己管理所有的C和CF对象。
      就像垃圾回收,ARC忽略所有使用stdlib和核心基础APIs创建的对象。他将不插入被用来创建和释放结构体或者联合体的malloc()和free()调用。也不会为CF对象插入CFRetain()和CFRelease()调用。
      确保自己添加提供这些调用。小心平衡得为每一个malloc()调用一个free()并且为每一个CFRetain()调用一个CFRelease()。也确保避免在同一个对象上调用两次free()或者CFRelease()。 

     

    总结

    自动引用计数是一个创新的管理在MacOS X 10.7和iOS 5上的ObjC对象的方法。它远离了显式的保留,释放以及自动释放消息,并且他降低了潜在的内存泄露和空指针。它提供了优化的自动释放池,避免了垃圾回收那样的性能开销。他的行为在两个平台是一致的。但是,ARC有很多方面太复杂以至于不能在这篇文章中覆盖到。为了学习了解更多的与ARC相关的东西,请查看下边的参考列表。


    原文链接:http://www.oschina.net/translate/automatic-reference-counting-on-ios
    2019-11-17 22:24:19 u012094456 阅读数 22

    MRC(手动引用计数)和ARC(自动引用计数)

    1、MRC:alloc,retain,release,retainCount,autorelease,dealloc
    2、ARC:

    • ARC是LLVM和Runtime协作的结果
    • ARC禁止手动调用retain,release,retainCount,autorelease关键字
    • ARC新增weak,strong关键字

    3、引用计数管理:

    • alloc: 经过一系列函数调用,最终调用了calloc函数,这里并没有设置引用计数为1
    • retain: 经过两次哈希查找,找到其对应引用计数值,然后将引用计数加1(实际是加偏移量)
    • release:和retain相反,经过两次哈希查找,找到其对应引用计数值,然后将引用计数减1
    • dealloc:

    4、弱引用管理:

    • 添加weak变量:通过哈希算法位置查找添加。如果查找对应位置中已经有了当前对象所对应的弱引用数组,就把新的弱引用变量添加到数组当中;如果没有,就创建一个弱引用数组,并将该弱引用变量添加到该数组中。

    • 当一个被weak修饰的对象被释放后,weak对象怎么处理的?
      清除weak变量,同时设置指向为nil。当对象被dealloc释放后,在dealloc的内部实现中,会调用弱引用清除的相关函数,会根据当前对象指针查找弱引用表,找到当前对象所对应的弱引用数组,将数组中的所有弱引用指针都置为nil。

    5、自动释放池:

    在当次runloop将要结束的时候调用objc_autoreleasePoolPop,并push进来一个新的AutoreleasePool

    AutoreleasePoolPage是以栈为结点通过双向链表的形式组合而成,是和线程一一对应的。
    内部属性有parent,child对应前后两个结点,thread对应线程 ,next指针指向栈中下一个可填充的位置。

    • AutoreleasePool实现原理?

    编译器会将 @autoreleasepool {} 改写为:

    void * ctx = objc_autoreleasePoolPush;
        {}
    objc_autoreleasePoolPop(ctx);
    
    • objc_autoreleasePoolPush:
      把当前next位置置为nil,即哨兵对象,然后next指针指向下一个可入栈位置,
      AutoreleasePool的多层嵌套,即每次objc_autoreleasePoolPush,实际上是不断地向栈中插入哨兵对象。

    • objc_autoreleasePoolPop:
      根据传入的哨兵对象找到对应位置。
      给上次push操作之后添加的对象依次发送release消息。
      回退next指针到正确的位置。

    2015-04-21 07:44:13 YT111222466243360 阅读数 771

    ARC简介:内存管理中对引用采取自动引用计数的技术;

    在Apple LLVM 编译器中设置ARC为有效状态,就无需再次输入retain或release编码

    降低了程序崩溃、内存泄露的风险,减少了开发者的工作量。

    使用条件:使用Xcode4.2以上版本,使用LLVM编译器3.0以上版本,编译器中设置ARC为有效


    引用计数

    内存管理思考方式:

    自己生成的对象,自己持有

    非自己生成的对象,自己也能持有

    不再需要自己持有的对象时释放

    非自己持有的对象没法释放


    对象操作与Objective-C中方法的回应

    生成并持有对象 alloc/new/copy/mutableCopy等方法

    持有对象 retain方法

    释放对象 release方法

    废气对象 dealloc方法

     

    自己生成的对象,自己持有

    alloc/new/copy/mutableCopy这些方法名意味着自己生成的对象只有自己持有

    注:[NSObjective new] = [[NSObjective alloc] init],两种方法都为生成并持有对象

    copy方法利用基于NSCopying方法约定,由各类实现的copyWithZone:方法生成并持有对象的副本

    mutableCopy方法利用基于NSMutableCopying方法约定,由各类实现的mutableCopyWithZone:方法生成并持有对象的副本

    说法不一样但意义是一样的


    非自己生成的对象自己也能持有

    上述方法以外的方法取得的对象,因为非自己生成并持有,所以自己不是该对象的持有者,例:

    //取得非自己生成并持有的对象
            id obj = [NSMutableArrayarray];
            //取得的对象存在,但自己不持有对象
    //此时使用retain方法便可以持有该对象
            [obj retain];
            //自己持有对象


    通过retain方法,非自己持有的对象跟用alloc/new/copy/mutableCopy方法生成并持有的对象一样,成为了自己所持有的


    不再需要自己持有的对象时释放:自己持有的对象,一旦不再需要,持有者有义务释放该对象,释放使用release方法

    不论是用alloc/new/copy/mutableCopy生成并持有 还是用retain方法持有的对象都用release释放

    autorlease:此方法与release方法的区别是release会立即释放,而autorelease方法不立即释放,会将其注册到autoreleasepool中,pool结束时自动调用

    此方法可以使取得的对象存在,但不自己持有,例:

    id obj = [[NSObjectalloc] init];
            //自己持有对象
            [obj autorelease];
            //取得对象存在,但自己不持有
            return obj;


    也可以用retain方法将调用autorelease方法取得的对象变为自己持有


    无法释放非自己持有的对象

    释放了非自己持有的对象应用程序会崩溃




    alloc  /  retain  /  release  /  delloc实现

    alloc:

    GNUstep中的实现:

    +(instancetype)alloc
    {
        return [selfallocWithZone:NSDefaultMallocZone()];
    }
    
    +(instancetype)allocWithZone:(struct_NSZone *)zone
    {
        returnNSAllocateObject(self,0,zone);
    }



    NSAllocateObject函数实现

    struct obj_layout{
            NSUInteger retained;
        };
        inlineid
        id NSAllocateObject(Class aClass, NSUInteger extraBytes, NSZone *zone)
        {
            int size =计算容纳对象所需内存大小
            id new = NSZoneMalloc(zone, size);
            memset(new, 0, size);
            new = (id)&((struct obj_layout *) new)[1];
        }


    说明:NSZone:为防止内存碎片化而引入的结构,对内存分配的区域本身进行多重化管理,根据使用对象的目的、对象的大小分配内存,从而提高内存管理的效率。

    苹果现在的运行时系统忽略了区域的概念,运行时系统本身对内存的管理已极具效率,使用区域管理反而会降低效率。


    去掉NSZone后的源代码:

    struct obj_layout{
          NSUInteger retained;
    };
    +(instancetype)alloc
    {
        int size =sizeof(struct obj_layout) +对象的大小;
        structobj_layout *p = (structobj_layout *) calloc(1, size);
        return (id)(p +1);
    }



    alloc 类方法用struct obj_layout中的retained整数来保存引用计数

    对象的引用计数也可以用retainCount方法获得

    id obj = [[NSObject alloc] init];

    NSLog(@"retainCount = %d",[obj retainCount]);


    初始化完成时对象的引用计数为1;

    - (NSUInteger)retainCount
    {
        return NSExtraRefCount(self) + 1;
    }
    
    NSUInteger NSExtraRefCount(id object)
    {
        return (()struct obj_layout *) anObject)[-1].retained;
    }



    retained方法可以使retained变量加1,release方法使retained变量减1;


    retained方法:

    - (instancetype)retain
    {
        NSIncrementExtraRefCount(self);
        return self;
    }
    
    inline void 
    void NSIncrementExtraRefCount(id object)
    {
        if (((struct obj_layout *) anObject) [-1].retained = UINT_MAX - 1)
            [NSException raise:NSInternalInconsistencyException format:@"NSInternalInconsistencyException() asked to increament too far"];
        
        ((struct obj_layout *) anObject)[-1].retained++;
    }

    release方法:

    - (oneway void)release
    {
        if(NSDecrementExtraRefCountWasZero(self))
            [self dealloc];
    }
    
    bool
    NSDecrementExtraRefCountWasZero(id object)
    {
        if (((struct obj_layout *) anObject) [-1].retained == 0){
            return YES;
        } else {
            ((struct obj_layout *) anObject)[-1].retained--;
            return NO;
        }
        
    }

    当retained变量大于0时减1,等于0时调用delloc实例方法,废弃对象。

    dealloc

    - (void)dealloc
    {
        NSDeallocateObject(self);
    }
    
    inline void
    void NSDeallocateObject(id object)
    {
        struct obj_layout *o = &((struct obj_layout *) anObject)[-1];
        free(o);
    }


    以上就是alloc / retain / release / dealloc在GNUstep中的实现。具体总结如下:

    在objective-C的对象中存有引用计数这一整数值

    调用alloc或是retain ,引用计数值加1;

    调用release后,引用计数值减1;

    引用计数值为0时,调用dealloc方法废弃对象



    苹果的实现

    各个方法分别调用的方法和函数

    +alloc
    +allocWithZone:
    class_createInstance
    calloc

    -retainCount
    __CFDoExterRefOperation
    CFBasicHashGetCountOfKey

    -retain
    __CFDoExternRefOperation
    CFBasicHashAddValue

    -release
    __CFDoExterRefOperation
    CFBasicHashRemoveValue
    (CFBasicHashRemoveValue 返回0时,-release 调用dealloc)

    alloc类方法流程:allocWithZone:  -->class_createInstance--> calloc分配内存块;

    int __CFDoExternRefOperation(uintptr_t op,id obj)
    {
        CFBasicHashRef table = 取得对象的散列表(obj);
        int count;
        switch (op) {
            case OPERATION_retainCount:
                count = CFBasicHashGetCountOfKey(table,obj);
                return count;
            case OPERATION_retain:
                CFBasicHashAddValue(table,obj);
                return count;
            case OPERATION_release:
                count = CFBasicHashRemoveValue(table,obj);
                return 0 == count;
        }
    }
    

    retainCount / retain / release实例方法

    - (NSUInteger)retainCount
    {
        return (NSInteger)__CFDoExternRefOperation(OPERATION_retainCount, self);
    }
    
    - (instancetype)retain
    {
        return (NSInteger)__CFDoExternRefOperation(OPERATION_retain, self);
    }
    
    - (oneway void)release
    {
        return __CFDoExternRefOperation(OPERATION_release, self);
    }


    苹果用散列表管理引用计数,

    GNUstep将引用计数放在对象占用内存的头部的变量中,苹果的实现则是保存在引用计数表的记录中

    好处:

    CGUstep:

    少量代码即可完成

    能够统一管理引用计数用内存块与对象用内存块

    苹果:

    对象用内存块的分配无需考虑内存块头部

    引用计数表各记录中存有内存块地址,可从各个记录追溯到各对象的内存块


    autorelease

    使用方法:

    (1)生成并持NSAutoreleasePool对象;

    (2)调用已分配对象的autorelease实例方法;

    (3)废弃NSAutoreleasePool对象。


    NSAutoreleasePool对象的生命周期相当于C语言变量的作用域,调用过autorelease实例化方法的对象,在废弃NSAutoreleasePool对象时都将调用release实例方法。

    <span style="white-space:pre">	</span>//实例化NSAutoreleasePool对象
            NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
            //实例化一个id对象
            id obj = [[NSObject alloc] init];
            //将实例化的id对象obj放入pool中
            [obj autorelease];
            //释放pool对象
            [pool drain];
    

    在cocoa框架中,相当于主程序循环的NSRunLoop或者在其他程序可执行的地方,对NSAutoreleasePool对象进行生成、持有和废弃处理。因此,应用程序开发者不一定非得使用NSAutoreleasePool对象进行开发工作。

    NSRunLoop每次循环过程中NSAutoreleasePool对象被生成或废弃:生成NSAutoreleasePool对象 --> 应用程序主线程处理 --> 废弃NSAutoreleasePool对象


    注意:在大量产生autorelease对象时,只要不废弃NSAutoreleasePool对象,那么生成的对象就不能被释放,因此有时会产生内存不足的现象。

    例:读入大量图像的同时改变其尺寸。图像文件读入到NSData对象,并从中生成UIImage对象,改变该对象尺寸后生成新的UIImage对象。由于没有废弃NSAutoreleasePool对象,最终导致内存不足。

    <span style="white-space:pre">	</span>for (int i = 0; i < 100000000; ++i) {
                //读入图像
                //大量产生autorelease的对象
                //由于没有废弃NSAutoreleasePool对象
                //最终导致内存不足
            }


    在此情况下,有必要在适当的地方生成、持有或废弃NSAutoreleasePool对象。

    <span style="white-space:pre">	</span>for (int i = 0; i < 100000000; ++i) {
                NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
                //读入图像
                //大量产生autorelease的对象
                
                //在对象数量达到一定一定量时释放pool对象
                [pool drain];
                
                //通过[pool drain]将autorelease的对象一起release
            }


    cocoa框架中返回autorelease的对象

    id array = [NSMutableArray arrayWithCapacity:1];
    <span style="font-family: Arial, Helvetica, sans-serif;">等同于</span>
    id array1 = [[[NSMutableArray alloc] initWithCapacity:1] autorelease];


    autorelease的实现:

    GNUstep中:

    autorelease实例方法的本质就是调用NSAutoreleasePool的addObject类方法

    <span style="white-space:pre">	</span>[obj autorelease];
            
            
            - (instancetype)autorelease
            {
                [NSAutoreleasePool addObject:self];
            }

    假想一个简单的源代码:

    调用正在使用的NSAutoreleasePool对象的addObject方法

    + (void)addobject:(id)anOj
    {
        NSAutoreleasePool *pool = 取得正在使用的NSAutoreleasePool对象;
        if (pool != nil) {
            [pool addObject:anOj];
        } else {
            NSLog(@"NSAutoreleasePool非存在状态下调用autorelease");
        }
    }
    

    下面代码中被赋予pool变量的即为正在使用的NSAutoreleasePool对象

    <span style="white-space:pre">	</span>NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
            id obj = [[NSObject alloc] init];
            [obj autorelease];

    嵌套的NSAutoreleasePool,使用的是最内侧的对象

        NSAutoreleasePool *pool0 = [[NSAutoreleasePool alloc] init];
        
        NSAutoreleasePool *pool1 = [[NSAutoreleasePool alloc] init];
        
        NSAutoreleasePool *pool2 = [[NSAutoreleasePool alloc] init];
        
        id obj = [[NSObject alloc] init];
        [obj autorelease];
        
        [pool2 drain];
        
        [pool1 drain];
        
        [pool0 drain];

    GNUstep实现使用的是连接列表

    drain方法废弃正在使用的NSAutoreleasePool对象的过程

    - (void)drain
    {
        [self dealloc];
    }
    
    - (void)dealloc
    {
        [self emptyPool];
        [array release];
    }
    
    - (void) emptyPool
    {
        for (id obj in array) {
            [obj release];
        }
    }


    苹果的实现

    Class AutoreleasePoolPage
    {
        static inline void *push()
        {
            //相当于生成或持有NSAutoreleasePool对象;
        }
        static inline void *pop()
        {
            //相当于废弃NSAutoreleasePool类对象;
            releaseAll();
        }
        static inline id autorelease(id obj)
        {
            //相当于NSAutoreleasePool类的addObject类方法
            AutoreleasePoolPage *autoreleasePoolPage = 取得正在使用的AutoreleasePoolPage 实例;
            autoreleasePoolPage-->add(obj);
        }
        id *add(id obj)
        {
            //将对象追加到内部数组中;
        }
        void releaseAll()
        {
            //调用内部数组中对象的release实例方法;
        }
    };
    
    void *objc_autoreleasePoolPush(void)
    {
        return AutoreleasePoolPage::push();
    }
    
    void *objc_autoreleasePoolPop(void *ctxt)
    {
        AutoreleasePoolPage::pop(ctxt);
    }
    
    id *objc_autorelease(id obj)
    {
        return AutoreleasePoolPage::autorelease(obj);
    }
    


    查看NSAutoreleasePool类方法和autorelease方法的运行过程

        NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
        //等同于obj_autoreleasePoolPush()
        id obj = [[NSObject alloc] init];
        [obj autorelease];
        //等同于objc_autorelease(obj);
        [pool drain];
        //等同于objc_autoreleasePoolPage(pool)

    注:在autorelease NSAutoreleasePool对象时会发生异常;


    ARC规则

    ARC只是自动的帮开发者填写retain和release,并没有改变引用计数的规则,

    在ARC环境下可以设置某些类不使用ARC,-fno-objc-arc


    所有权修饰符:ARC有效时所有id类型或对象类型都需要加所有权修饰符:

    所有权修饰符(四种):

    __strong 修饰符

    __weak 修饰符

    __unsafe_unretained 修饰符

    __autoreleasing 修饰符


    __strong修饰符:是id类型和对象类型默认的所有权修饰符

        id obj = [[NSObject alloc] init];
        //相当于
        id __strong obj1 = [[NSObject alloc] init];
        
        //在非ARC环境中
        id obj = [[NSObject alloc] init];
        [obj release];

        {
        //自己生成并持有对象
        id __strong obj = [[NSObject alloc] init];
        
        //因为变量obj为强引用,所以自己持有对象
        }
        //因为对象超出了范围,强引用失效,所以自动释放自己持有的对象,对象的所有者不存在,因此废弃改对象


    取得非自己持有的对象时:

        id __strong obj = [NSMutableArray array];

        id __strong obj = [NSMutableArray array];
        
        {
            //取得非自己生成并持有的对象、
            id __strong obj = [NSMutableArray array];
            
            //因为变量obj为强引用,所以自己持有对象
        }
        //变量超出作用域,强引用失效,自动释放自己持有的对象,对象的所有者不存在,因此废弃该对象

    附有__strong修饰符的变量之间可以互相赋值

        id __strong obj0 = [[NSObject alloc] init];//对象A,obj0持有对象A的强引用
        id __strong obj1 = [[NSObject alloc] init];//对象B,obj1持有对象B的强引用
        id __strong obj2 = nil;     //不持有任何对象
        
        obj0 = obj1;
        //obj0持有obj1所持有的对象B的强引用,因此原先持有的A对象的强引用失效,对象A的所有者不存在,因此释放对象A;此时持有对象B的强引用变量为obj0和obj1
        
        obj2 = obj0;
        //obj2持有obj0所持有的对象B的强引用,此时持有对象B的强引用的变量为:obj0,obj1,obj2
        
        obj1 = nil;
        //obj1对B的强引用失效,此时持有对象B的强引用的变量为:obj0,obj2
        obj0 = nil;
        //obj0对B的强引用失效,此时持有对象B的强引用的变量为:obj2
        obj2 = nil;
        //obj0对B的强引用失效,对象B的所有者不存在了,所以B被废弃


    方法参数上也能使用__strong修饰符

    #import "TestObject.h"
    
    @interface TestObject ()
    {
        id __strong obj_;
    }
    
    - (void)setObject:(id __strong)obj;
    
    @end
    
    
    @implementation TestObject
    
    - (instancetype)init
    {
        self = [super init];
        return self;
    }
    
    - (void)setObject:(id __strong)obj
    {
        obj_ = obj;
    }
    
    @end


    该类的使用

    {
        id __strong test = [[TestObject alloc] init];
        //test持有TestObject对象
        
        [test setObject:[[NSObject alloc] init]];
        //test对象的obj_成员持有对NSObject对象的强引用
    }
    
    //超出test变量的作用域,强引用失效,自动释放TestObject对象
    //TestObject对象的强引用不存在,销毁该对象
    
    //废弃TestObject对象的同时,TestObject对象的obj_成员也被废弃,NSObject的强引用失效,自动释放NSObject对象,NSObject对象的所有者不存在,因此废弃该对象
    


    __weak修饰符:解决循环引用的问题

    使用上述TestObject的例子:

    - (void)test2
    {
        id test0 = [[TestObject alloc] init];
        //test0持有对象A的强引用
        
        id test1 = [[TestObject alloc] init];
        //test1持有对象B的强引用
        
        [test0 setObject:test1];
        //test0持有的对象A的obj_成员变量持有TestObject对象B的强引用
        
        [test1 setObject:test0];
        //test1持有的对象B的obj_成员变量持有TestObject对象A的强引用
        
    }
    
    //超出作用域,强引用都失效,自动释放TestObject对象A,和TestObject对象B,但发现TestObject对象A、B都有强引用没法释放,发生内存泄露

    单个对象也会发生循环引用

        [test0 setObject:test0];


    用__weak修饰符来修饰,使之成为弱引用:弱引用不能持有对象实例

    id __weak obj = [[NSObject alloc] init];
    这样写会出问题,编译器会发警告:实例化的对象得不到强引用会直接被释放掉应改为下面的写法:

    <p class="p1"><span class="s1">    {</span></p><p class="p2"><span class="s2">        </span><span class="s1">//</span><span class="s3">自己生成并持有对象</span></p><p class="p1"><span class="s1">        </span><span class="s4">id</span><span class="s1"> obj1 = [[</span><span class="s5">NSObject</span><span class="s1"> </span><span class="s6">alloc</span><span class="s1">] </span><span class="s6">init</span><span class="s1">];</span></p><p class="p3"><span class="s7">        </span><span class="s8">//</span><span class="s1">因为</span><span class="s8">obj1</span><span class="s1">变量为强引用,所以自己持有对象</span></p><p class="p1"><span class="s1">        </span><span class="s4">id</span><span class="s1"> </span><span class="s4">__weak</span><span class="s1"> obj = obj1;</span></p><p class="p2"><span class="s2">        </span><span class="s1">//obj</span><span class="s3">变量为弱引用</span></p><p class="p1"><span class="s1">    }</span></p><p class="p3"><span class="s7">    </span><span class="s8">//</span><span class="s1">超出作用域,强引用失效,自动释放持有的对象,因为对象的持有者不存在,所以废弃该对象</span></p>

    改写上面循环引用的例子,使之比避免循环引用:将成员变量的obj_用__weak修饰符修饰便可避免循环引用

    @interface TestObject ()
    {
        id __weak obj_;
    }
    
    - (void)setObject:(id __strong)obj;
    
    @end

    __weak修饰符修饰的变量持有的对象超出作用域时会被释放,__weak修饰符修饰的变量会变成空弱引用

    id __weak obj1 = nil;
        {
            //自己生成并持有对象
            id __strong obj0 = [[NSObject alloc] init];
            //因为obj0变量为强引用,所以自己持有对象
            obj1 = obj0;
            //obj1变量持有对象的弱引用
            NSLog(@"1--%@",obj1);
            //输出obj1变量持有的弱引用对象
        }
        //因超出作用域,强引用失效,所以自动释放自己持有的对象,因为对象无持有者,所以废弃该对象。废弃的同时,持有该对象弱引用的obj1变量弱引用失效,nil赋值给obj1
        NSLog(@"2--%@",obj1);
        //输出赋值给obj1变量中的nil
        
        //输出:《NSObject:。。。。。。》
        //输出:(null)


    __unsafe_unretained修饰符:不安全的所有权修饰符,用__unsafe_unretained修饰符的变量不属于编译器的内存管理对象

        id __unsafe_unretained obj = [[NSObject alloc] init];


    __unsafe_unretained  修饰的变量同附有__weak一样,上述代码会报警告,不能直接将自己生成并持有的对象不能继续为自己所有,所以生成的对象会立即被释放

    所以要改写成

        id __unsafe_unretained obj1 = nil;
        {
            //自己生成并持有对象
            id __strong obj0 = [[NSObject alloc] init];
            //因为obj0变量为强引用,所以自己持有对象
            obj1 = obj0;
            //虽然obj0变量赋值给obj1,但是obj1既不持有对象的强引用也不持有弱引用
            NSLog(@"1--%@",obj1);
            //输出obj1变量表示的对象
        }
        //因超出作用域,强引用失效,所以自动释放自己持有的对象,因为对象无持有者,所以废弃该对象。
        NSLog(@"2--%@",obj1);
        //输出obj1变量表示的对象
        //obj1变量表示的对象,已经被废弃,错误访问
        
        //输出:《NSObject:。。。。。。》
        //输出:《NSObject:。。。。。。》

    此时碰巧改对象对应的内存还没有被立即释放,但是在使用__unsafe_unretained修饰符时,赋值给附有__strong修饰符的变量时有必要确保被赋值的对象确实存在,否则会导致程序崩溃


    __autoreleasing修饰符

    在ARC有效和无效环境中的用法区别

        //ARC无效时
        NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
        id obj = [[NSObject alloc] init];
        [obj autorelease];
        [pool drain];
        
        //ARC有效时
        @autoreleasepool {
            id __autoreleasing obj = [[NSObject alloc] init];
        }

    可以理解为:在ARC有效时,用@autoreleasepool块替代NSAutoreleasePool类,用附有__autoreleasing修饰符的变量替代autorelease方法


    取得非自己生成并持有的对象时,虽然可以使用alloc/new/copy/mutablecopy 以外的方法来取得对象,但该对象已被注册到了autoreleasepool。这是由于编译器会检查方法名是否以alloc/new/copy/mutablecopy 开始,如果不是则自动将返回值的对象注册到autoreleasepool

        @autoreleasepool {
            //取得非自己生成并持有的对象
            
            id  __strong obj = [NSMutableArray array];
            
            //因为变量obj为强引用,所以自己持有对象,并且该对象由编译器判断其方法名后自动注册到autoreleasepool
            
        }
        
        //超出作用域,强引用失效,所以自己释放自己持有的对象;
        //同时随着@autoreleasepool块的结束,注册到autoreleasepool中的对象被释放,因为对象的所有者不存在,所以废弃对象

    @autoreleasepool也可以嵌套使用,最常见到@autoreleasepool的地方为main方法里

    NSRunLoop等实现不论ARC有效还是无效,均能够随时释放注册到autoreleasepool中的对象

    如果编译器版本为LLVM3.0以上,即使ARC无效@autoreleasepool块也能够使用。


    因为autoreleasepool范围以块级源代码表示,提高了程序的可读性,多以今后在ARC无效时也推荐使用@autoreleasepool块

    无论ARC是否有效,调试用的非公开函数_objc_autoreleasepoolPrint都可使用

    利用这个函数可以有效地帮助我们调试注册到autoreleasepool上的对象



    ARC使用规则

    1、不能使用retain/release/retainCount/autorelease

    2、不能使用NSAllocateObject/NSDeallocateObject

    3、须遵守内存管理的方法命名规则:alloc,new,copy,mutablecopy;ARC环境中又加了一个init

    4、不要显示调用dealloc      :不管ARC是否有效,对象的所有者不持有该对象时该对象都要废弃,对象被废弃时不管ARC是否有效,都会调用对象的dealloc方法;dealloc方法在大多数情况下还适用于删除已注册的代理或观察者对象

    5、使用@autoreleasepool块替代NSAutoreleasePool

    6、不能使用区域(NSZone):运行时系统中区域已被单纯的忽略了

    7、对象型变量不能作为C语言结构体(struct/union)的成员:C语言的结构体成员中,如果存在Objective-C对象型变量,便会引起编译错误;不得已情况下可以将对象型变量强转成void * 或是附加前面所述的__unsafe_unretained修饰符

    8、显示转换id和void* :id类型或对象型变量赋值给void * 或者逆向赋值时都需要进行特定的转化,如果只想单纯的赋值,则可以使用“__bridge转换”

     //非ARC状态下
        id obj = [[NSObject alloc] init];
        void *p = obj;
        
        id o = p;
        [o release];
        
        //ARC状态下
        id obj = [[NSObject alloc] init];
        void *p = (__bridge void *)obj;
        id o = (__bridge id)p;

    转换为void*的__bridge转换,其安全性与赋值给__unsafe_unretained修饰符很相近,甚至会更低。如果管理时不注意复制对象的所有者,就会因悬垂指针导致程序崩溃

    __bridge转换中有两种转换,分别是“__bridge_retained转换” 和 "__bridge_transfer转换"

    __bridge_retained转换可使要转换的变量也持有所赋值的对象,与retain类似

    __bridge_transfer转换提供了与此相反的动作被转换的变量所持有的对象在该变量被赋值给转换目标变量后立即释放,与release类似

    这些转换多数使用在Objective-C对象与core Foundation对象之间的相互转换,在这两种对象中转换不需要使用额外的CPU资源,因此也被成为“免费桥”


    Foundation和 core Foundation(可根据个人习惯选择)

    __bridge_retained CFBridgingRetained

    __bridge_transfer CFBridgingRelease

    retain CFRetain

    release CFRelease

    __bridge 不能替换__bridge_retained和CFBridgeRetain


    属性

    属性 所有权修饰符

    assign __unsafe_unretained修饰符

    copy _strong修饰符(但是赋值的是被复制的对象)

    retain _strong修饰符

    strong _strong修饰符

    unsafe_unretained __unsafe_unretained修饰符

    weak __weak修饰符

    同一个属性,声明时必须保证属性一致,如:


    数组

    附有__strong/__weak/__autoreleasing修饰符变量的数组也保证其初始化为nil,

    id *类型  默认为  id __autoreleasing *类型

    __strong 修饰符的id型变量被初始化为nil,但不保证附有__strong修饰符的id指针型变量被初始化为nil


    使用calloc函数确保想分配的附有__strong修饰符变量的容量占有的内存块




    ARC的实现

    __strong修饰符

    赋值给附有__strong修饰符的变量在实际的程序中的运行细节:

        {
            id __strong obj = [[NSObject alloc] init];
        }

        //编译器的模拟代码
        id obj = objc_msgSend(NSObject,@selector(alloc));
        objc_msgSend(obj,@selector(init));
        objc_release(obj);

    此段代码两次调用objc_msgSend方法(alloc,init),变量作用域结束时通过objc_release释放对象


    alloc / new / copy / mutableCopy以外的方法:

        {
            id __strong obj = [NSMutableArray array];
        }
        
        //编译器模拟代码
        id obj = objc_msgSend(NSMutableArray,@selector(array));
        objc_retainAutoreleasedRetureValue(obj);
        objc_release(obj);

     
    objc_retainAutoreleasedRetureValue<span style="font-family: Arial, Helvetica, sans-serif; background-color: rgb(255, 255, 255);">函数主要用于最优化程序运行,用于自己持有对象的函数,持有的对象应为返回注册在autoreleasepool中的对象的方法,或是函数的返回值</span>

    objc_retainAutoreleasedRetureValue 函数是成对的,与之对应的函数是objc_autoreleaseReturnValue,用于alloc / new / copy / mutableCopy以外的类似于NSArray的array方法返回对象的实现上

    + (id)array
    {
        return [[NSMutableArray alloc] init];
    }
    //编译器模拟代码
    +(id)array
    {
        id obj = objc_msgSend(NSMutableArray,@selector(alloc));
        objc_msgSend(obj,@selector(init));
        return objc_autoreleaseReturnValue(obj);
    }

    返回注册到autoreleasingpool中对象的方法使用了objc_retainAutoreleasedRetureValue函数范湖注册到autoreleasingpool中的对象。但是objc_retainAutoreleasedRetureValue函数同objc_autorelease函数不同,一半不仅限于注册对象到autoreleasepool中。

    objc_autoreleaseReturnValue函数会检查使用该函数的方法或函数调用方的执行命令表,如果方法或函数的调用方在调用了方法或函数后紧接着调用objc_retainAutoreleasedRetureValue()函数,那么就不将返回的对象注册到autoreleasepool中,而是直接传递到方法或函数的调用方。objc_retainAutoreleasedRetureValue函数与objc_retain函数不同,它即便不注册到autoreleasepool中而返回对象,也能够正确地获取对象。通过objc_autoreleaseReturnValue函数和objc_retainAutoreleasedRetureValue函数的协作,可以不将对象注册到autoreleasepool中而直接传递,这一过程达到了最优化。



    __weak修饰符

    若附有__weak修饰符的变量所引用的对象被废弃,则将nil赋值给该变量

    使用附有__weak修饰符的变量,即是使用注册到autoreleasepool中的对象

        {
            id __weak obj1 = obj;
        }
        
        //假设变量obj附加__strong修饰符且对象被赋值
        
        //编译器的模拟代码
        id obj1;
        objc_initWeak(&objq1,obj);
        objc_destroyWeak(&obj1);

    释放对象时,废弃谁都不持有的对象,程序的动作:

    1)objc_release

    2)因为引用计数为0,所以执行dealloc

    3)_objc_rootDealloc

    4)objc_dispose

    5)objc_destructInstance

    6)objc_clear_deallocating

    objc_clear_deallocating函数的动作如下:

    1)从weak表中获取废气对象的地址为键值记录

    2)将包含在记录中的所有附有__weak修饰符变量的地址,赋值为nil

    3)从weak表中删除记录

    4)从引用计数表中删除废弃对象的地址为键值的记录


    综上所述:__weak修饰符修饰的变量所引用的对象被废弃,则将nil赋值给该变量,如果大量使用附有__weak修饰符的变量,则会消耗响应的CPU资源。良策是只在需要避免循环引用时使用__weak修饰符。


    使用附有__weak修饰符的变量,即是使用注册到autoreleasepool中的对象

        {
            id __weak obj1 = obj;
            NSLog(@"%@",obj1);
        }
        
        //编译器模拟代码
        id obj1;
        objc_initWeak(&obj1,obj);
        id tmp = objc_loadWeakRetained(&obj1);
        objc_autorelease(tmp);
        NSLog(@"%@",tmp);
        objc_destoryWeak(&obj1);
    


    与赋值时相比,多了两个函数的调用:

    objc_loadWeakRetained :取出附有__weak修饰符变量所引用的对象并retain

    objc_autorelease:函数将对象注册到autoreleasepool中


    由此可知。因为附有__weak修饰符变量所引用的对象像这样被注册到autoreleasepool中,所以在@autoreleasepool块结束之前都可以放心使用。但是,如果大量地使用附有__weak修饰符的变量,注册到autoreleasepool的对象也会大量地增加,因此在使用附有__weak修饰符的变量时,最好先暂时赋值给附有__strong修饰符的变量后使用


    有些类不支持__weak修饰符,但在cocoa框架类中极为罕见

    还有一种情况也不能使用__weak修饰符

    当allowsWeakReference 和 retainWeakReference实例方法返回no的情况

    在赋值给__weak修饰符的变量时,如果赋值对象的allowsWeakReference方法返回NO,程序将异常终止

    在使用__weak修饰符的变量时,当被赋值对象的retainWeakReference方法返回NO的情况下,该变量将使用“nil”



    __autoreleasing修饰符

    将对象赋值给附有__autoreleasing修饰符的变量等同于ARC无效时调用对象的autorelease方法

        @autoreleasepool {
            id __autoreleasing obj = [[NSObject alloc] init];
        }
        
        //编译器模拟代码
        id pool = objc_autoreleasePoolPush();
        id obj = objc_msgSend(NSObject,@selector(alloc));
        objc_msgSend(obj,@selector(init));
        objc_autorelease(obj);
        objc_autoreleasePoolPop(pool);

    alloc / new / copy / mutableCopy方法群之外的方法中使用注册到autoreleasepool中的对象

        @autoreleasepool {
            id __autoreleasing obj = [NSMutableArray array];
        }
        
        //编译器模拟代码
        id pool = objc_autoreleasePoolPush();
        id obj = objc_msgSend(NSMutableArray,@selector(array));
        objc_retainAutoreleasedRetureValue(obj);
        objc_autorelease(obj);
        objc_autoreleasePoolPop(pool);



    引用计数

    获取引用计数的数值

        uintptr_t _obj_rootRetainCount(id obj)

        //__strong修饰符
        {
            id __strong obj = [[NSObject alloc] init];
            NSLog(@"retain count = %d",_objc_rootRetainCount(obj));
        }
        //输出1
        
        //__weak修饰符:weak为弱引用并不持有对象
        {
            id __strong obj = [[NSObject alloc] init];
            id __weak o = obj;
            NSLog(@"retain count = %d",_objc_rootRetainCount(obj));
        }
        //输出1
        
        //__autoreleasing修饰符:对象被强引用,并注册到autoreleasepool中
        @autoreleasepool {
            id __strong obj = [[NSObject alloc] init];
            id __autoreleasing o = obj;
            NSLog(@"retain count = %d",_objc_rootRetainCount(obj));
        }
        //输出2


    _objc_rootRetainCount:次方法不能完全信任,有些已经释放完待销毁的对象在用该方法返回引用计数也会的到1,在多线程中也不一定可信










    2019-03-08 14:48:51 weixin_43883776 阅读数 37

    在ARC环境下,iOS不允许通过obj.returnCount的方式直接输出引用计数。

    使用如下代码

    LHSimpleObject * simpObject = [LHSimpleObject alloc];
        NSLog(@"returnCount = %ld",CFGetRetainCount((__bridge CFTypeRef)(simpObject)));
        simpObject = [simpObject init];
        NSLog(@"returnCount = %ld",CFGetRetainCount((__bridge CFTypeRef)(simpObject)));
    

    注意结尾处一共三个有括号
    注意结尾处一共三个有括号
    注意结尾处一共三个有括号

    在这里插入图片描述

    没有更多推荐了,返回首页