Objective-C高階程式設計讀書筆記(一)

kim_jin發表於2017-12-26

自動引用計數

自動引用計數(ARC, Automatic Reference Counting), 是指記憶體管理中對引用採用自動計數的計數,讓編譯器來進行記憶體管理

記憶體管理/引用計數

思考方式

  • 自己生成的物件,自己持有
  • 非自己生成的物件,自己也能持有
  • 不再需要自己持有的物件時釋放
  • 非自己持有的物件無法釋放

這四種方式,對應成oc的方法的話,就是:

物件操作 oc方法
生成並持有物件 alloc/new/copy/mutableCopy等方法
持有物件 retain方法
釋放物件 release方法
廢棄物件 dealloc方法

自己生成的物件,自己持有

使用以下名稱的方法意味著自己生成的物件只有自己持有:alloc, new, copy, mutableCopy. e.g.:

id obj = [[NSObject alloc] init];
複製程式碼

NSObject通過alloc類方法自己生成並持有物件,並將指向此物件的指標賦值給變數obj(obj實際上是一個指標)

非自己生成的物件,自己也能持有

id obj = [NSMutableArray array];
複製程式碼

上面這段程式碼中,變數obj並不持有NSMutableArray生成的類物件(可以通過retain方法進行持有)

不在需要自己持有的物件時釋放

釋放採用release方法

id obj = [[NSObject alloc] init];
[obj release]; // 釋放obj指向的物件,obj本身仍存在,但是其指向的物件已經被釋放
複製程式碼

非自己持有的物件不能釋放

alloc/retain/release/dealloc的實現

根據蘋果的文件,可以看出alloc的實際實現是:

+ alloc
+ allocWithZone:
class_createInstance
calloc
複製程式碼

autorelease

具體使用方法:

  1. 生成並持有NSAutoreleasePool物件
  2. 呼叫已分配物件的autorelease例項方法
  3. 廢棄NSAutoreleasePool物件

對於所有呼叫過autorelease例項方法的物件,在廢棄NSAutoreleasePool物件時,都將呼叫release例項方法。需要注意的是,如果生成大量的autorelease物件,而NSAutoreleasePool物件又不廢棄的話,大量物件得不到釋放,就會出現記憶體不足的現象。

autorelease的實現如下:

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例項方法
  }
}
複製程式碼

從原始碼中我們可以看到,autorelease方法的實現是通過動態陣列來進行自動釋放池的物件的儲存,在需要釋放的時候廢棄陣列中的物件。

ARC規則

所有權修飾符

在ARC有效時,id型別的所有權修飾符有4種:__strong, __weak, __unsafe_unretained__autoreleasing

__strong

__strong是預設的修飾符。表示對物件的強引用,在其作用域被廢棄的時候,隨著強引用的失效,引用的物件會隨之釋放

__weak

__weak防止迴圈引用引起的記憶體洩漏。比方說

id obj1 = [[NSObject alloc] init];
id obj2 = [[NSObject alloc] init];
obj1.obj = obj2;
obj2.obj = obj1;

// or
obj1.obj = obj;
複製程式碼

上面程式碼中兩個寫法都會造成迴圈引用,因為兩個物件之間有互相強引用,導致作用域被廢棄時,兩個物件之間得不到釋放。通過__weak可避免這種情況。除此之外,在持有某物件的弱引用時,如果物件被廢棄的話,弱引用將自動失效並且處於nil

__unsafe_unretained

__unsafe_unretained修飾的變數跟__strong__weak不同,是不屬於編譯器的記憶體管理物件。其變數既不持有強引用也不持有弱引用。

__autoreleasing

在ARC有效時,使用@autoreleasepool來替代NSAutoreleasePool類,用__autoreleasing替代autorelease方法。一般不會顯式附加。對於非自己生成當持有的物件來說,編譯器會自動檢查方法名是否以alloc/new/copy/mutableCopy開頭,如果不是的話會將物件註冊到自動釋放池當中。

使用__weak變數的時候,必定會訪問到註冊到自動釋放池中的物件。

因為__weak只持有物件的弱引用,在訪問引用物件的過程中,該物件隨時有可能被廢棄。但是隻要將物件註冊到自動釋放池中的話,則能保證在作用域結束之前,物件一直存在。

可以使用_objc_autoreleasePoolPrint()方法除錯自動釋放池上的物件

規則

  • 不能使用retain/release/retainCount/autorelease: 記憶體管理是編譯器的工作,沒必要使用記憶體管理的方法
  • 不能使用NSAllocateObject/NSDeallocateObject
  • 須遵守記憶體管理方法命名規則:alloc/new/copy/mutableCopy開頭的方法在返回物件時,必須返回給呼叫方所應當持有的物件。
  • 不要顯式呼叫dealloc
  • 使用@autoreleasepool替代NSAutoreleasePool
  • 不能使用NSZone:
  • 物件型變數不能作為C語言結構體的成員:即C語言中不能使用OC的物件
  • 顯式轉換idvoid *: 使用__bridge_retained__bridge_transfer(多用於OC物件和Core Foundation物件的轉換)

陣列

id __strong *array = nil;

id *型別預設為id __autoreleasing * 型別,因此需要顯式指定為__strong。但是,這僅保證了__strong修飾的id型別變數被初始化為nil,並不保證id指標型變數被初始化為nil

ARC的實現

__strong

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

// 編譯器的模擬程式碼
id objc = objc_msgSend(NSObject, @selector(alloc));
objc_msgSend(obj, @selector(init));
objc_release(obj);

{
  id __strong obj = [NSMutableArray array];
}

// 編譯器的模擬程式碼
id objc = objc_msgSend(NSMutableArray, @selector(array));
objc_retainAutoreleasedReturnValue(obj); // 用於最優化程式執行。用於自己持有物件的函式,但它返回的物件應為返回註冊在自動釋放池中的物件的方法或者是返回值
objc_release(obj);
複製程式碼

objc_retainAutoreleasedReturnValueobjc_autoreleaseReturnValue一般是配套使用的,通過這兩個方法,可以獲取到原本應註冊到釋放池中的物件,省略了註冊和查詢的步驟,實現優化處理。

__weak

  • 若使用__weak的變數所引用的物件被廢棄的話,則將nil賦值給該變數
  • 使用__weak的變數,即是使用註冊到autoreleasepool中的物件。
{
  id __weak obj1 = obj; // obj是__strong修飾的值
}

// 編譯器模擬程式碼
id obj1;
objc_initWeak(&obj1, obj); // 初始化後呼叫objc_storeWeak函式
-> obj1 = 0; objc_storeWeak(&obj1, obj);
objc_destoryWeak(&obj1);

// 等價於
id obj1;
obj1 = 0;
objc_storeWeak(&obj1, obj);
objc_storeWeak(&obj1, 0);
複製程式碼

objc_storeWeak將物件的地址作為key,將__weak修飾的變數地址作為value註冊到weak表(一個雜湊表,跟引用計數表相同)。

釋放物件的步驟:

  1. objc_release
  2. 因為引用計數為0, 所以執行dealloc
  3. _objc_rootDealloc
  4. object_dispose
  5. objc_destructInstance
  6. objc_clear_deallocating

在最後一個步驟中,會執行以下動作:

  1. 從weak表中獲取廢棄物件的地址為key的記錄
  2. 將包含在記錄中的所有__weak修飾的變數地址賦值為nil
  3. 從weak表中刪除
  4. 從引用計數表中刪除廢棄物件地址為key的記錄

由上可知,如果大量使用__weak修飾的變數的話,會消耗相應的CPU資源

下面驗證下__weak修飾的變數即為自動釋放池中的物件

{
  id __weak obj1 = obj;
  NSLog(@"%@", obj1);
}

// 編譯器模擬程式碼
id obj1;
objc_initWeak(&obj1, obj);
id tmp = objc_loadWeakRetained(&obj1); // 取出__weak修飾符變數所引用的物件並retain
objc_autorelease(tmp); // 將物件註冊到自動釋放池
NSLog(@"%@", tmp);
objc_destoryWeak(&obj1);
複製程式碼

__autoreleasing

@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);
複製程式碼

引用計數

使用_objc_rootRetainCount()方法來獲取引用計數的數值(CFGetRetainCount((__bridge CFTypeRef)(obj))也可) 使用_objc_autoreleasePoolPrint()可列印自動釋放池中的引用物件的狀態:

// 需要通過extern宣告
extern void _objc_autoreleasePoolPrint();
extern uintptr_t _objc_rootRetainCount(id obj);

int main(int argc, char * argv[]) {
    
    id obj = [[NSObject alloc] init];
    id obj1 = obj;
    NSLog(@"%ld", _objc_rootRetainCount(obj));
    
    @autoreleasepool {
        id obj = [[NSObject alloc] init];
        _objc_autoreleasePoolPrint();
        id __weak o = obj;
        NSLog(@"before using __weak: retain count = %d", _objc_rootRetainCount(obj));
        NSLog(@"class = %@", [o class]);
        NSLog(@"after using __weak: retain count = %d", _objc_rootRetainCount(obj));
        _objc_autoreleasePoolPrint();
    }
    return 0;
}
複製程式碼

輸出如下:

2017-05-03 00:18:20.415 ObjectiveCDemo[7740:740389] 2
objc[7740]: ##############
objc[7740]: AUTORELEASE POOLS for thread 0x1108bc3c0
objc[7740]: 0 releases pending.
objc[7740]: [0x1]  ................  PAGE (placeholder)
objc[7740]: [0x1]  ################  POOL (placeholder)
objc[7740]: ##############
2017-05-03 00:18:20.417 ObjectiveCDemo[7740:740389] before using __weak: retain count = 1
2017-05-03 00:18:20.417 ObjectiveCDemo[7740:740389] class = NSObject
2017-05-03 00:18:20.418 ObjectiveCDemo[7740:740389] after using __weak: retain count = 1
objc[7740]: ##############
objc[7740]: AUTORELEASE POOLS for thread 0x1108bc3c0
objc[7740]: 1 releases pending.
objc[7740]: [0x7f82e2003000]  ................  PAGE  (hot) (cold)
objc[7740]: [0x7f82e2003038]  ################  POOL 0x7f82e2003038
objc[7740]: ##############

複製程式碼

相關文章