OC高级编程之内存管理

一、内存管理的思考方式
下文会常用到的术语解释
生成对象:创建对象
持有对象:引用计数+1
释放对象:引用计数-1
废弃对象:引用计数为0,回收内存

引用计数式内存管理的思维方式:
自己生成的对象,自己持有
非自己生成的对象,自己也能持有
不再需要自己持有的对象时释放
非自己持有的对象,无法释放

对象操作与OC方法的对应
对象操作与OC方法的对应关系.png

1.自己生成的对象,自己持有
使用以下名称开头的方法名意味着,自己生成的对象只有自己持有:
alloc
new
copy
mutableCopy

   //  自己生成的对象,自己持有
    id obj = [[NSObject alloc] init];
    id obj = [NSObject new];

copy方法基于NSCopying方法约定,由各类实现的copyWithZone:方法生成并持有对象的副本。与copy方法类似,mutableCopy方法基于NSMutableCopying方法约定,由各类实现mutableCopyWithZone:方法生成并持有对象的副本。两者的区别在于,copy生成不可变更的对象,mutableCopy生成可变更的对象。
2.非自己生成的对象,自己也能持有

   //取得非自己生成的对象
    id obj = [NSMutableArray array];
  // 在非arc下,取得的对象存在,但是自己并不持有
  // 自己持有对象
    [obj retain]; 

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

    // 自己生成的对象,自己持有
    id obj = [[NSObject alloc] init];
    // 释放对象
    [obj release];
    // 指向对象的指针,仍然被保留在obj中,貌似可以访问。
    // 但是对象一经释放绝对不可访问
    // 此时在arc下,obj 是一个野指针
    //取得非自己生成的对象
    id obj = [NSMutableArray array];
    // 持有对象
    [obj retain];
    // 释放对象
    [obj release];
    // 指向对象的指针,仍然被保留在obj中,貌似可以访问。
    // 但是对象一经释放绝对不可访问
    // 野指针,同上

用某个方法生成对象,并将其返还给方法的调用者,源码是怎样的?

- (id)allocObject
{
  // 自己生成并持有对象
  id obj = [[NSObject alloc] init];
  // 自己持有对象
  return obj;
}

// 取得自己生成并持有的对象
id obj = [self allocObject];

那么类似[NSMutableArray array]是怎么实现的呢?

- (id)object
{
  id obj = [[NSObject alloc] init];
  // 自己持有对象
  [obj autorelease];
  // 取得的对象存在,但自己不持有对象
  return obj;
}
// 使用autorelease 可以使取得的对象存在,但自己并不持有对象。
id obj = [self object];
// 取得的对象存在,但自己不持有对象
id obj1 = [obj retain];
//自己持有对象

4.无法释放非自己持有的对象
释放非自己持有的对象时,会崩溃。

二、alloc/retain/releaase/dealloc 实现
GNUstep是Cocoa的互换框架,GNUstep的源代码与Cocoa的行为和方式是一样的。下面分别来看它们在GNUstep的NSObject的实现。
alloc

+ (id)alloc
{
  return [self allocWithZone:NSDefaultMallocZone()];
}

+ (instancetype)allocWithZone:(struct _NSZone *)zone
{
  return NSAllocateObject(self, 0, zone);
}

//NSAllocateObject函数

struct obj_layout {
    NSUInteger retained;
};
in line 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];
}

//NSAllocateObject 函数通过调用NSZoneMalloc 函数来分配存放对象所需内存空间,之后将内存空间置0,最后返回作为对象而使用的指针。
// NSZone 是为了防止内存碎片而引入的结构。

//简化版的alloc函数如下
struct obj_layout {
    NSUInteger retained;
};
+ (id) alloc
{
  int size = sizeof(struct obj_layout) + 对象大小。
  struct obj_layout *p = (struct obj_layout *)calloc(1, size);
  return (id)(p+ 1);
// alloc 方法用struct obj_layout 中的retained整数来保存引用计数,并将其写入对象内存头部,该对象内存块全部置0后返回。
alloc返回对象的内存图.png

三、苹果中的实现
NSObject类的alloc方法和函数调用栈如下

+alloc
+allocWithZone:
class_createInstance 
calloc

retainCount/retain/release方法实现如下:
retainCount:
__CFdoExternRefOperation
CFBasicHashGetCountOfKey

retain:
__CFdoExternRefOperation
CFBasicHashAddValue

release:
__CFdoExternRefOperation
CFBasicHashRemoveValue

可以看到各个方法都调了同一个函数__CFdoExternRefOperation
它简化后的源码如下:

int __CFDoExternRefOperation(uintptr_t op, id obj) {

    CFBasicHashRef table = 取得对象的散列表(obj);
    int count;

    switch (op) {
    case OPERATION_retainCount:
        count = CFBasicHashGetCountOfKey(table, obj);
        return count;
        break;

    case OPERATION_retain:
        count = CFBasicHashAddValue(table, obj);
        return obj;

    case OPERATION_release:
        count = CFBasicHashRemoveValue(table, obj);
        return 0 == count;
    }
}

那么由此推测retainCount,retain,release方法的实现如下:

- (NSUInteger)retainCount
{
    return (NSUInteger)____CFDoExternRefOperation(OPERATION_retainCount,self);
}

- (id)retain
{
    return (id)____CFDoExternRefOperation(OPERATION_retain,self);
}

//这里返回值应该是id,原书这里应该是错了
- (id)release
{
    return (id)____CFDoExternRefOperation(OPERATION_release,self);
}

可以看出,苹果的实现大概就是采用散列表来管理引用计数


通过散列表(hash)管理引用计数.png

四、autorelease
1、 使用方法:
(1)生成NSAutoreleasePool对象
(2)调用已分配对象的autorelease实例方法
(3)废弃NSAutoreleasePool对象

所有调用过autorelease方法的对象,在废弃NSAutoreleasePool对象时,都将调用release方法(引用计数-1):

NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
id obj = [[NSObject alloc] init];
[obj autorelease];
[pool drain];  
//最后一句等同于[obj release]

每个runloop中都创建一个Autorelease Pool,并在runloop的末尾进行释放,
所以,一般情况下,每个接受autorelease消息的对象,都会在下个runloop开始前被释放。也就是说,在一段同步的代码中执行过程中,生成的对象接受autorelease消息后,一般是不会在代码段执行完成前释放的。

当然也有让autorelease提前生效的办法:自己创建Pool并进行释放

NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
NSArray * array = [[[NSArray alloc] init] autorelease];
[pool drain];

上面的array就会在[pool drain]执行时被释放。
所以对于你遇到的问题,可以在for循环外嵌套一个Autorelease Pool进行管理,例如

NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
for (int i = 0; i < 10000; i++)
{
    // ... 
}
[pool drain];

但由于生成的每个实例可能会很大。只在循环外嵌套,可能导致在pool释放前,内存里已经有很多个实例存在,造成瞬间占用内存过大的情况。
因此,如果你的每个实例仅需要在单次循环过程中用到,那么可以考虑可以在循环内创建pool并释放

for (int i = 0; i < 10000; i++)
{
    NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
    // ...
    [pool drain];
}

2、autorelease的实现
可通过objc4/NSObject.mm来确认苹果中autorelease的实现:

objc4/NSObject.mm AutoreleasePoolPage

class AutoreleasePoolPage
{
    static inline void *push()
    {
        //生成或者持有 NSAutoreleasePool 类对象
    }

    static inline void pop(void *token)
    {
        //废弃 NSAutoreleasePool 类对象
        releaseAll();
    }

    static inline id autorelease(id obj)
    {
        //相当于 NSAutoreleasePool 类的 addObject 类方法
        AutoreleasePoolPage *page = 取得正在使用的 AutoreleasePoolPage 实例;
       autoreleaesPoolPage->add(obj)
    }

    id *add(id obj)
    {   
        //将对象追加到内部数组中
    }

    void releaseAll()
    {
        //调用内部数组中对象的 release 方法
    }
};

//压栈
void *objc_autoreleasePoolPush(void)
{
    if (UseGC) return nil;
    return AutoreleasePoolPage::push();
}

//出栈
void objc_autoreleasePoolPop(void *ctxt)
{
    if (UseGC) return;
    AutoreleasePoolPage::pop(ctxt);
}

外部调用如下:

NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
// 等同于 objc_autoreleasePoolPush

id obj = [[NSObject alloc] init];
[obj autorelease];
// 等同于 objc_autorelease(obj)

[NSAutoreleasePool showPools];
// 查看 NSAutoreleasePool 状况

[pool drain];
// 等同于 objc_autoreleasePoolPop(pool)

?autorelease NSAutoreleasePool对象
crash
由于任何对象调用autorelease 实际是调用NSObject类的autorelease实例方法。但是对于NSAutoreleasePool类,autorelease实例方法已经被该类重载,因此会运行时错误。

前面介绍的是基于MRC下的内存管理。下面我们来看下一下基于ARC下的内存管理。
实际上“引用计数式内存管理”的本质在ARC中并没有改变。就像“自动引用计数”这个名称表示是那样,ARC自动地帮助我们处理“引用计数”的相关部分。
1、所有权修饰符
ARC下,id类型和对象类型同C语言其他类型不同,其类型上必须附加所有权修饰符。所有权修饰符一共有4中:
__strong
__weak
__unsafe_unretained
__autoreleasing

__strong修饰符是id类型和对象类型默认的所有权修饰符。如果没有显式的声明修饰符,那么编译器会默认为__strong。
它表示对对象的“强引用”,持有强引用的变量在超出其作用域时被废弃,随着强引用是失效,引用的对象会随之被释放。

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

下面思考取得非自己生成并持有的对象时的情况。

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

由此看到,这两种情况是相同的。对象的所有者和对象的生存周期都是明确的。
附有__strong修饰符的变量之间可以相互赋值

  {
    //obj0持有A的强引用
    id __strong obj0 = [[NSObject alloc] init]; //对象A
    //obj1持有B的强引用
    id __strong obj1 = [[NSObject alloc] init]; //对象B
    //obj2不持有对象
    id __strong obj2 = nil;
    
    /*obj0持有由obj1赋值的对象B的强引用
     因为obj0被赋值,原先持有的对对象A的强引用失效。对象A的所有者不存在,因此废弃对象A
     此时持有对象B的强引用为obj0和obj1
    */
    obj0 = obj1;
    
    /*
     同上 此时持有对象B的强引用为obj0和obj1,obj2
     */
    obj2 = obj0;
    
    /*
     因为nil被赋值给obj0,它对B的强引用失效,
     此时持有对象B的强引用为obj1,obj2
     */
    obj0 = nil;
  }

除了变量,__strong修饰符也可以在成员变量,方法的参数上使用。

@interface Test : NSObject
{
  id __strong obj_;
}

- (void)setObject:(id __strong)obj;

@end

@implementation Test

- (instancetype)init
{
  self = [super init];
  return self;
}

- (void)setObject:(id)obj
{
  obj_ = obj;
}

@end

下面看上面Test类的使用

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

此外,__strong 修饰符和__weak、__autoreleasing一样,可以保证将附有这些修饰符的自动变量初始化为nil。
例如
id __strong obj; 等同于 id __strong obj = nil;
因为id类型和对象类型的所有权修饰符默认为__strong ,所以不需要显式的写上“__strong”。

__weak修饰符
引用计数式内存管理方式,必然会引起的一个重大问题是“循环引用”,导致内存泄漏。

  {
    
    id test0 = [Test alloc] init]; //对象A
    // test0持有Test对象A的强引用
    id test1 = [Test alloc] init]; //对象B
    // test1持有Test对象B的强引用
    
    [test0 setObject:test1];
    // test0的obj_成员变量持有Test对象B的强引用
    // 此时B的强引用变量为A的obj_和test1
    [test1 setObject:test0];
    // test1的obj_成员变量持有Test对象A的强引用
    // 此时A的强引用变量为B的obj_和test0
  }
  // test0变量超出作用域,被废弃,同时释放对对象A的强引用
  // test1变量超出作用域,被废弃,同时释放对对象B的强引用
  // 然而此时对象A的强引用变量还有B的obj_,
  // 对象B的强引用变量还有A的obj_,内存泄漏
循环引用.png

既然有__strong修饰符,那么肯定也有__weak修饰符。__weak可以避免循环引用。
__weak提供弱引用,弱引用不能持有对象的实例。看下面代码

id __weak obj = [[NSObjet alloc] init];

上面的代码,编译器会发出警告:
Assigning retained object to weak variable; object will be released after assignment
上面源代码将自己生成并持有的对象赋值给__weak修饰符的变量obj。obj持有对象的若引用,也就是说并不持有对象,那么对象会立即被释放。可以这样解决:

  {
    //自己生成并持有对象
    //obj持有对象的强引用
    id __strong obj = [[NSObject alloc] init];
    //obj1持有对象的弱引用
    id __weak obj1 = obj;
  }
  /*
   obj变量超出作用域,强引用失效,
   所以自动释放自己持有的对象。因为对象的所有者不存在,所以废弃该对象
   */

也就是说 用__weak修饰的对象,需要保证除了附有__weak修饰符的变量外,还有其他变量对对象有强引用。
__weak修饰符还有另外一个优点。在持有某对象的若引用时,若该对象被废弃,则此若引用将自动失效,且处于nil被赋值的状态。

如下所示:

    id __weak obj1 = nil;
    {
      id __strong obj0 = [[NSObject alloc] init];
      obj1 = obj0;
      NSLog(@"A:%@",obj1);
    }
    
    NSLog(@"B:%@",obj1);
    
    //打印结果如下:
    //A:<NSObject: 0x753234>
    //B:null

解释如下:

    id __weak obj1 = nil;
    {
      //自己生成并持有对象,obj0位强引用,自己持有对象
      id __strong obj0 = [[NSObject alloc] init];
      //obj1变量持有对象的弱引用
      obj1 = obj0;
      //输出obj1变量持有的若引用的对象
      NSLog(@"A:%@",obj1);
    }
    
    /*
     obj0超出作用域,强引用失效,释放持有的对象
     对象没有持有者,被废弃
     对象被废弃的同时,持有对象的obj1的若引用失效,nil被赋值给obj1
     */
    NSLog(@"B:%@",obj1);

__unsafe_unretained
iOS5以后支持__weak,之前可以使用__unsafe_unretained。虽然现在已经基本不用了,但是还是简单介绍下。它与__weak区别在于,引用的对象被废弃以后,弱引用的变量不会被自动赋值成nil,此时这个弱引用变量是个野指针(悬垂指针)。

__autoreleasing
ARC下不能使用autorelease
但在ARC下可以使用__autoreleasing

    @autoreleasepool {
      id __autoreleasing obj = [[NSObject alloc] init];
    }

在ARC下,将对象赋值给附加了__autoreleasing修饰符的变量等价于在MRC下调用对象的autorelease方法,即对象被注册到autoreleasepool。


@autoreleasepool和附有autoreleasing修饰符的变量.png

但是,一般情况下,我们都是非显式的使用__autoreleasing 修饰符。
这是由于编译器会检查方法名是否以alloc/new/copy/mutableCopy开始,如果不是,则自动将返回值注册到autoreleasepool。(注意:init方法返回值的对象不注册到autoreleasepool)
举例:

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

像这样,不使用__autoreleasing,也能将对象注册到autoreleasepool。

另外,在访问__weak修饰符的变量时,必定要访问注册到autoreleasepool的对象。

id __weak obj1 = obj0;
NSLog(@"class=%@",[obj1 class]);

//以下代码和上面的相同
id __weak obj1 = obj0;
id __autoreleasing tmp = obj1;
NSLog(@"class=%@",[tmp class]);

因为__weak修饰符只是持有对象的弱引用,而在访问对象的过程中,该对象的有可能被废弃。如果把对象注册到autoreleasepool中,那么在@autoreleasepool代码块结束之前,都能确保该对象存在。

最后一个可非显式使用__autoreleasing修饰符的例子是id的指针和对象的指针。
例如

id *obj; 
//等价于
id __autoreleasing *obj

NSObject **obj; 
//等价于
NSObject * __autoreleasing *obj;

注意:赋值给对象指针时,所有权修饰符必须一致,否则会编译错误。例如

NSError *error = nil;
NSError **perror = &error;

上面的代码编译器会报错。此时对象指针必须附加__strong

NSError *error = nil;
NSError * __strong *perror = &error;
// 编译正确

当然其他修饰符也是一样,需在给对象指针赋值时保持一致。

下面看方法里面的的指针传递。有如下方法。

- (BOOL)performOperationWithError:(NSError * __autoreleasing *)error
{
  *error = [[NSError alloc] initWithDomain:NSGlobalDomain code:0 userInfo:nil];
  return NO;
}

  NSError __strong *error = nil;
  BOOL result = [self performOperationWithError:&error];

像上面的调用,编译器不会报错。这是因为编译器自动做了转换。

  NSError __strong *error = nil;
  NSError __autoreleasing *tmp = error;
  BOOL result = [self performOperationWithError:&tmp];
  error = tmp;

在ARC下需要遵循一定的规则。具体如下
(1)不能使用retain/release/retainCount/autorelease
(2)不能使用NSAllocateObject/NSDeallocateObject
(3)必须遵守内存管理的方法名规则
(4)不要显式调用dealloc
(5)使用@autorelease块代替NSAutoreleasePool
(6)不能使用区域(NSZone)
(7)对象型变量不能作为C语言结构体的成员
(8)显式转换id和void*
下面对其中几项做出解释
(3)必须遵守内存管理的方法名规则:
alloc/new/copy/mutableCopy/init 以这些名称为开头的方法在返回对象时,必须返回给调用方所持有的对象。
其中init返回的对象并不注册到autoreleasepool,基本上只是对alloc方法返回的对象进行初始化处理并返回该对象。
(7)对象型变量不能作为C语言结构体的成员
C语言的结构体如果存在Objective-C对象型变量,便会引起错误,因为C语言在规约上没有方法来管理结构体成员的生存周期 。
(8)显式转换id和void*

//在MRC下编译正常
id obj = [[NSObject alloc] init];
void *p = obj;
id o = p;
[o release]
//

//arc下编译错误,需要显式__bridge转换
  id obj = [[NSObject alloc] init];
  void *p = (__bridge void *)obj;
  id o = (__bridge id)p;

注:__bridge_retained 、 __bridge_transfer
__bridge_retained可使要转换的变量也持有所赋值的对象。

id obj = [[NSObject alloc] init];
void *p = (__bridge_retained void *)obj;

上面代码在MRC下的代码如下:

id obj = [[NSObject alloc] init];
void *p = (__bridge_retained void *)obj;
[(id)p retain];
变量p和obj同时持有对象。

__bridge_transfer提供相反的动作,被转换的变量所持有的对象在该变量在被复制给转换目标后随之释放。

id obj = [[NSObject alloc] init];
void *p = (__bridge_transfer void *)obj;

上面代码在MRC下的代码如下:

id obj = [[NSObject alloc] init];
void *p = (__bridge_transfer void *)obj;
[(id)p retain];
[obj release];
变量p和obj同时持有对象。

__bridge_retained类似于retain;__bridge_transfer类似于release。

上述转换用在OC对象和CF对象上。
下面举一个相互转换的例子。

  CFMutableArrayRef cfObject = NULL;
  {
    // 变量obj生成并持有对象的强引用,对象引用计数为1
    id obj = [[NSMutableArray alloc] init];
    // 通过CFBridgingRetain将对象CFRetain,赋值给变量cfObject
    // 此时cfObject retainCount为2;
    cfObject = CFBridgingRetain(obj); // 等价于 cfObject = (__bridge_retained CFMutableArrayRef)obj;
}
  // obj超出作用域,释放对象,强引用失效,引用计数为1。
  printf("retain count after scope = %ld\n",CFGetRetainCount(cfObject));
  //CFRelease后引用计数为0
  CFRelease(cfObject);
}

如果用__bridge 替换CFBridgingRetain或者__bridge_retained,如下:

  CFMutableArrayRef cfObject = NULL;
  {
    // 变量obj生成并持有对象的强引用,对象引用计数为1
    id obj = [[NSMutableArray alloc] init];
    // __bridge并不会改变对象的持有状况
    // 持有对象的只有obj
    cfObject = (__bridge CFMutableArrayRef)(obj);
  }
  // obj超出作用域,释放对象,强引用失效,引用计数为0。
  // 此时cfObject是野指针,访问会有危险
  printf("retain count after scope = %ld\n",CFGetRetainCount(cfObject));
  CFRelease(cfObject);

现在看CF对象转成OC对象

  {
    // CF框架API生成并持有对象,引用计数为1
    CFMutableArrayRef cfobject =
    CFArrayCreateMutable(kCFAllocatorDefault, 0, NULL);
    
    //通过CFBridingRelease赋值,变量obj持有对象的强引用同时,
    //对象通过CFBridgingRelease释放。此时引用计数为1
    id obj = CFBridgingRelease(cfobject);//等价于id obj = (__bridge_transfer CFMutableArrayRef)cfobject;
    
    //因为只有变量obj持有对象的强引用,所以引用计数为1
    //另外由于经过CFBridgingRelease转换后,赋值给cfobject的指针也指向仍然存在的对象,
    //所以它仍然可以访问。

如果用__bridge 替换CFBridgingRelease或者__bridge_transfer,如下:

  {
    // CF框架API生成并持有对象,引用计数为1
    CFMutableArrayRef cfobject =
    CFArrayCreateMutable(kCFAllocatorDefault, 0, NULL);
    
    //变量obj持有对象的强引用。对象没有进行CFRelease,所以引用计数为2
    id obj = (__bridge NSMutableArray*)(cfobject);
  }
  
  //因为变量obj超出作用域,释放对象
  //此时引用计数仍为1,发生内存泄漏

由此可见CF对象和OC对象之间的转换需要谨慎处理。

属性。
下面的表来表示属性声明的属性和所有权修饰符的对应关系


属性与所有权修饰符对应关系.png

上面只有copy不是简单的赋值。它赋值是通过NSCopying接口的copyWithZone:方法复制赋值源所生成的对象。
另外类成员变量声明和属性的属性应保持一致。

五、ARC的实现
ARC的实现是由clang(LLVM编译器)3.0以上 和 Objc4 Objective-C运行时库来共同完成的
下面来基于实现研究ARC。

  1. __strong修饰符
{
  id __strong obj = [[NSObject alloc] init];
}

编译器模拟代码如下

  id obj = objc_msgSend(NSObject,@selector(alloc));
  objc_msgSend(obj,@selector(init));
  objc_release(obj);
{
  id __strong obj = [NSMutableArray array];
}

编译器模拟代码如下

  id obj = objc_msgSend(NSMutableArray,@selector(array));
  objc_retainAutoreleasedReturnValue(obj);
  objc_release(obj);

可以发现多了一个objc_retainAutoreleasedReturnValue函数。它主要用于最优化程序运行,它是用于自己持有对象的函数。
这种objc_retainAutoreleasedReturnValue的函数是成对的。与之相对的函数是objc_autoreleaseReturnValue。它适用于alloc/new/copy/mutableCopy方法外的NSMutableArray类的array类方法等返回对象的实现上。
下面是NSMutableArray类的array类通过编译器会进行怎么样的转换。

+ (id)array
{
  return [[NSMutableArray array] init];
}
// 转换后
+ (id)array
{
  id obj = objc_msgSend(NSMutableArray,@selector(alloc));
  objc_msgSend(obj,@selector(init));
  return objc_autoreleaseReturnValue(obj);
}

像上面,返回注册到autoreleasepool中对象的方法使用了objc_autoreleaseReturnValue函数返回注册到autoreleasepool中的对象。但是objc_autoreleaseReturnValue与objc_autorelease不同,它不仅限于注册对象到autoreleasepool中。

objc_autoreleaseReturnValue函数会检查使用该函数的方法或函数调用方的执行命令列表,如果方法或者函数的调用方在调用了方法或函数后紧接着调用了objc_retainAutoreleasedReturnValue()函数,那么就不将返回的对象注册到autoreleasepool中。而是直接传递到方法或者函数的调用方。objc_retainAutoreleasedReturnValue与objc_retain函数不同,即便不注册到autoreleasepool中而返回对象,也能够正确地获取对象。


省略autoreleasepool注册.png

2、weak修饰符

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

通过objc_initWeak函数初始化含有__weak修饰符的变量。在变量作用域结束时,调用objc_destroyWeak函数释放该变量。

上面代码与下面的相同

id obj1;
obj1 = 0;
objc_storeWeak(&obj1,obj);
//这里是先将指针objc1置成0,再调用objc_storeWeak函数使得obj1指向obj对象。
//接下来的objc_destoryWeak函数的实际操作如下:
objc_storeWeak(&obj1, 0);  

objc_storeWeak函数把第二个参数的复制对象的地址作为键值,将第一个附有__weak修饰符的变量的地址注册到weak表中(散列表)。如果第二个参数为0,则把变量的地址从weak表中删除。weak表用散列表实现,这样将废弃对象的地址作为键值进行搜索,就能高速地获取对应的附有__weak修饰符的变量的地址。另外由于一个对象可以被多个__weak变量弱引用,所以一个键值,可以对应多个变量的地址。
释放对象时,废弃谁都不持有的对象的同时,对象通过objc_release释放,过程如下:
(1)objc_release
(2)因为引用计数为0,所以执行dealloc
(3)objc_rootDealloc
(4)object_dispose
(5)objc_destructInstance
(6)objc_clear_deallocating
对象被废弃时最后调用的objc_clear_deallocating动作如下:
(1)从weak表中获取废弃对象的地址为键值的记录。
(2)将包含在记录里面的所有附有__weak修饰符的变量的地址赋值为nil。
(3)从weak表中删除该记录
(4)从引用计数表中删除废弃对象的地址为键值的记录。由此可知,大量使用附有__weak修饰符的变量,则会消耗cpu资源,所有只在需要避免循环引用时使用__weak修饰符。

立即释放对象:

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

前面介绍过,上述源代码生成的对象没有变量持有,直接被释放。

[[NSObject alloc] init];这样的代码在MRC下一定会有内存泄漏。但是ARC下不会有,因为没有变量持有对象,所以编译器自动生成立即调用objc_release函数的源代码。

[[[NSObject alloc] init] hash];改代码可变为如下形势:

// 编译器模拟代码
id tmp = objc_msgSend(Nsobjcet,@seletor(alloc));
objc_msgSend(tmp,@seletor(init));
objc_msgSend(tmp,@seletor(hash));
objc_release(tmp);

下面来看__weak的另一功能:使用附有__weak修饰符的变量,就是使用注册到autoreleasepool中的对象。

  {
    id __weak obj1 = obj;
    NSLog(@"%@",obj1);  //使用__weak的变量
  }
  
  //该源代码可转换成如下形式
  id obj1;
  objc_initWeak(&obj1,obj);
  id tmp = objc_loadWeakRetaind(&obj1);
  objc_autorelease(tmp);
  NSLog(@"%@",tmp);
  
  objc_destroyWeak(&obj1);

与前面只是对__weak的赋值相比,在使用附有__weak修饰符变量的情形下,增加了
objc_loadWeakRetaind和objc_autorelease的调用。其中
(1)objc_loadWeakRetaind函数取出附有__weak修饰符变量所引用的对象并retain。
(2)objc_autorelease函数将对象注册到autoreleasepool中。
因此在使用__weak的变量时,在autoreleasepool块结束之前,都可以放心的使用变量。但是每使用一次,都会将对象加入到autoreleasepool中。因此在使用__weak
变量的时候,最好先赋值给附有__strong修饰符的变量后再使用。

3、__autoreleasing修饰符
将对象赋值给__autoreleasing修饰符的变量等同于在MRC下调用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();

ARC和MRC下,添加对象到autoreleasepool都是objc_autorelease函数。
下面是在alloc/new/copy/mutableCopy以外的方法生成对象加入autoreleasepool

  @autoreleasepool {
    id __autoreleasing obj = [NSMutableArray array];
  }
 
//等同也于下面模拟代码
  id pool = objc_autoreleasePoolPush();
  id obj = objc_msgSend(NSMutableArray),@selector(array));
  objc_retainAutoreleasedReturnValue(obj);
  objc_autorelease(obj);
  objc_autoreleasePoolPop();

与前面相比,虽然持有对象方法从alloc变为objc_retainAutoreleasedReturnValue,但是添加对象到autoreleasepool仍是objc_autorelease函数。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,015评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,262评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,727评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,986评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,363评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,610评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,871评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,582评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,297评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,551评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,053评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,385评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,035评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,079评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,841评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,648评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,550评论 2 270

推荐阅读更多精彩内容