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提供弱引用,弱引用不能持有對象的實例??聪旅娲a

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函數。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容