文章目錄
- 記憶體管理四大原則
- iOS底層對記憶體管理的方案
- ARC規則
- __strong修飾符
- 物件的所有者和物件的生命周期
- __strong物件相互賦值
- 方法引數中使用__strong
- __strong導致的回圈參考
- 失效階段的表示
- __weak修飾符
- __weak修飾符的參考計數的問題
- __unsafe_unretained
- 為什么說是不安全呢
- __autoreleasing修飾符
- 與MRC進行比較
- 自動呼叫
- 自動呼叫時的失效程序
- weak修飾符與autoreleasing修飾符
- 具體ARC規則
- 規則
- 不要顯式呼叫dealloc
- __bridge
- 屬性關鍵字與所有權修飾符
- ARC實作
- __Strong
- 自己生成并持有
- storeStrong
- objc_retain
- 不支持Nonpointer isa 的處理
- 支持Nonpointer isa的處理
- retain的總結
- release
- retainCount
- 非自己生成并持有
- objc_retainAutoreleasedReturnValue為什么非自己生成并持有時會出現?
- objc_retainAutoreleasedReturnValue是啥?
- 1. objc_autoreleaseReturnValue
- 1.1 prepareOptimizedReturn
- 1.2 __builtin_return_address
- 1.3 setReturnDisposition
- 1.4 callerAcceptsOptimizedReturn
- 2. objc_retainAutoreleasedReturnValue
- 2.1 acceptOptimizedReturn
- 非自己生成并持有的總結
記憶體管理四大原則
- 自己生成的物件自己持有
- 非自己生成的物件自己也能持有
- 不再需要自己持有的物件時釋放
- 非自己持有的物件無法釋放
iOS底層對記憶體管理的方案
- taggedPointer :很熟悉了存盤小的物件如NSNumber
- NONPOINTER_ISA :在 64 位架構下,isa 指標是占 64 位元位的,實際上只有 30 多位就 已經夠用了,為了提高利用率,剩余的位元位存盤了記憶體管理的相關資料內容,
nonpointer: 表示是否對 isa 指標開啟指標優化
? 0: 純 isa 指標
? 1: 不止是類物件地址, isa 中包含了類資訊、物件的參考計數等 - 散串列:復雜的資料結構 :復雜的資料結構,包括了參考計數表和弱參考表 通過 SideTables()結構來實作的,SideTables()結構下,有很多 SideTable 的資料結構, 而 sideTable 當中包含了自旋鎖,參考計數表,弱參考表, SideTables()實際上是一個哈希表,通過物件的地址來計算該物件的參考計數在哪個 sideTable 中,
ARC規則
__strong修飾符
__strong修飾符是id型別和物件型別默認的所有權修飾符,
不論呼叫哪種方法,強參考修飾的變數會持有該物件,如果已經持有則參考計數不會增加,
物件的所有者和物件的生命周期
持有強參考的變數在超出其作用域時被廢棄
隨著強參考的失效
參考的物件會隨之釋放
__strong物件相互賦值
__strong修飾符的變數不僅只在變數作用域中,在賦值上也能夠正確的管理其物件的所有者,
id __strong obj0 = [[NSObject alloc] init];//生成物件A
id __strong obj1 = [[NSObject alloc] init];//生成物件B
id __strong obj2 = nil;
obj0 = obj1;//obj0強參考物件B;而物件A不再被ojb0參考,被廢棄
obj2 = obj0;//obj2強參考物件B(現在obj0,ojb1,obj2都強參考物件B)
obj1 = nil;//obj1不再強參考物件B
obj0 = nil;//obj0不再強參考物件B
obj2 = nil;//obj2不再強參考物件B,不再有任何強參考參考物件B,物件B被廢棄
即如下表格

賦值的本質是強參考的轉變
方法引數中使用__strong
廢棄Test物件的同時,Test物件的obj_成員變數也被廢除
即成員變數的生存周期是與物件同步的
__strong導致的回圈參考
記憶體泄漏:在記憶體該被釋放的時候沒有釋放,導致記憶體被浪費使用了
我們還是以方法引數中的例子來說
Test類中有一個強參考型別的成員變數obj_
同時設定其set方法
@interface Test : NSObject {
id __strong obj_;
}
- (void)setObject:(id __strong)obj;
@end
#import "Test.h"
@implementation Test
- (id)init {
self = [super init];
return self;
}
- (void)setObject:(id __strong)obj {
obj_ = obj;
}
@end
主函式
int main(int argc, const char * argv[]) {
@autoreleasepool {
id test0 = [[Test alloc] init];//生成TestA
id test1 = [[Test alloc] init];//生成TestB
[test0 setObject:test1];
[test1 setObject:test0];
//NSLog(@"%lu",CFGetRetainCount((__bridge CFTypeRef)test0));
}
return 0;
}
我們宣告兩個物件 test0和test1
其對應內部也有自己的成員變數obj_
通過set方法給 兩個物件的成員變數分別賦值另一個物件所持有的TestA/TestB物件
我們就會造成如下結果
test0 持有 TestA
test0.obj 持有 TestB
test1 持有 TestB
test1.obj 持有 TestA
失效階段的表示

testA 不能dealloc 因為test1.obj還持有testA
想要廢除test1.obj就是要遵行成員變數的生命周期是與物件同步的這個觀點
所以我們需要廢除testB
而對于testB來說也是這樣
想廢除test0.obj就是要廢除testA
所以大家都是強參考 我為什么要讓你
這就造成回圈參考的問題
__weak修飾符
緊接著__strong 來學習一下__weak修飾符
__strong 強參考 ()
__weak 弱參考(參考計數不會加一 物件隨時可能會被dealloc)
內部使用__autoreleasing來維持該物件不被dealloc
物件的參考計數是記錄在一張表上的,不在物件本身或者指標中,系統通過訪問這張表來確定是否釋放該物件,
將上面相互參考例子中的成員變數變為weak,即可避免相互參考,
weak還有個作用,在持有某物件的弱參考時,若該物件被廢棄,則此若參考將自動失效且處于nil被賦值的狀態(空弱參考),

weak提供弱參考,弱參考不持有物件,NSObject物件會被銷毀 所以會報一個警告
我們可以像下面這樣將物件賦值給附有__strong修飾符的變數之后再賦值給附有__weak修飾符的變數,就不會發生警告了
__weak修飾符的參考計數的問題
那么問題來了 下面這兩個的參考計數的值是多少呢
id __strong obj0 = [[NSObject alloc] init];//生成物件A
id __weak obj1 = obj0;
NSLog(@"%lu",CFGetRetainCount((__bridge CFTypeRef)obj0));
NSLog(@"%lu",CFGetRetainCount((__bridge CFTypeRef)obj1));
按常理應該是1 1
__weak修飾符不持有物件 參考計數值兩個都應該為1
列印一下結果

蕪湖 一個1 一個2 __weak修飾的反而為2了
這是為什么呢?
打開匯編看一下

給兩個NSLog加上斷點看一下
第一個NSLog

第二個NSLog

對比一下就能發現 兩行NSLog的匯編代碼并不一樣
第二個__weak修飾符的NSLog在于開始先loadWeakRetained
然后在列印結束后有一個release操作
所以在列印__weak的參考計數時 NSLog先將其以強參考 為了安全起見,如果不強參考,防止萬一還沒列印就被釋放了,在NSLog結束時,會呼叫objc_release使參考計數減一,
__unsafe_unretained
__unsafe_unretained修飾符正如其名unsafe所示,是不安全的所有權修飾符,
附有__unsafe_unretained修飾符的變數不屬于編譯器的記憶體管理物件,
為什么說是不安全呢
-
weak 修飾的指標變數,在指向的記憶體地址銷毀后,會在 Runtime 的機制下,自動置為 nil, _Unsafe_Unretain 不會置為 nil,容易出現 懸垂指標,發生崩潰,但是 _Unsafe_Unretain 比 __weak 效率高,
懸垂指標 指標指向的記憶體已經被釋放了,但是指標還存在,這就是一個 懸垂指標 或者說 迷途指標,野指標,沒有進行初始化的指標,其實都是 野指標 -
附有__unsafe_unretained修飾符的變數同附有__weak修飾符的變數一樣,生成的物件會立即釋放,
在使用__unsafe_unretained修飾符時,賦值給附有__strong修飾符的變數時有必要確保被賦值的物件確實存在,如果不存在,那么程式就會崩潰,
__autoreleasing修飾符
與MRC進行比較
- MRC中autorelease的使用方法
-
- 生成并持有NSAutoreleasePool物件,
-
- 呼叫已分配物件的autorelease方法【將物件注冊到pool中】
-
- 廢棄NSAutoreleasePool物件,
在ARC環境下
__autoreleasing如下

自動呼叫
編譯器會檢查方法名是否以alloc/new/copy/mutableCopy開始,如果不是講自動將回傳值的物件注冊到autoreleasepool中
下面情況不使用__autoreleasing修飾符也能使物件注冊到autoreleasepool中,
+ (id) array {
return [[NSMutableArray alloc]init];
}
如下:
+ (id) array {
id obj = [[NSMutableArray alloc]init];
return obj;
}
由于return使得物件變數超出其作用域,所以該強參考對應的自己持有的物件會被自動釋放,但該物件作為函式的回傳值,編譯器也會自動注冊到自動釋放池
自動呼叫時的失效程序
隨著obj超出其作用域,強參考失效,所以自動釋放自己持有的物件,
同時,隨著@autoreleasepool塊的結束,注冊到autoreleasepool中的所有物件被自動釋放, 因為物件的擁有者不存在,所以廢棄物件,
weak修飾符與autoreleasing修飾符
書上提到 weak修飾符的實作需要借助autoreleasing修飾符
就是把weak修飾符修飾的物件放到池子中
即
id __strong obj0 = [[NSObject alloc] init];//生成物件A
id __weak obj1 = obj0;
等同于
id __strong obj0 = [[NSObject alloc] init];//生成物件A
id __weak obj1 = obj0;
id __autoreleasing obj2 = obj1;
但通過lldb對自動釋放池進行列印

可以看到__weak的變數并沒有把變數放入自動釋放池
所以認為作者可能僅僅想提醒我們最好使用自動釋放池來對物件進行維護 但這樣自動釋放池也會改變其參考計數 在池子銷毀時會被release
具體ARC規則
規則
- 不能使用retain/release/retainCount/autorelease
- 不能使用NSAllocateObject/NSDeallocateObject
- 必須遵守記憶體管理的方法名規則
- 不要顯式呼叫dealloc
- 使用@autorelease塊代替NSAutoreleasePool
- 不能使用區域(NSZone)
- 物件型變數不能作為C語言結構體的成員
- 顯式轉換id和void*
不要顯式呼叫dealloc
dealloc無法釋放不屬于該物件的一些東西,需要我們重寫時加上去,例如
- 通知的觀察者,或KVO的觀察者
- 物件強委托/參考的解除(例如XMPPMannerger的delegateQueue)
- 做一些其他的注銷之類的操作(關閉程式運行期間沒有關閉的資源)
直接打開官方檔案
In the implementation of dealloc, do not call the implementation of superclass. You should try to avoid using dealloc to manage the lifetime of limited resources, such as file descriptors.
You never send a dealloc message directly. Instead, the dealloc method of the object is called by the runtime.
在dealloc的實作中,不要呼叫超類的實作,您應該盡量避免使用dealloc管理有限資源(如檔案描述符)的生存期,
不要直接發送dealloc訊息,與直接發送dealloc訊息不同,物件的dealloc方法由運行時呼叫,
Special Considerations
When not using ARC, your implementation of dealloc must invoke the superclass’s implementation as its last instruction.
特別注意事項
當不使用ARC時,dealloc的實作必須呼叫父類(super)的實作作為它的最后一條指令,
__bridge

屬性關鍵字與所有權修飾符

ARC實作
__Strong
自己生成并持有
和Block一樣 我們直接Clang轉成 LLVM 中間碼
OC:
void defaultFunction() {
id __strong obj0 = [NSObject new];
}
轉為LLVM中間碼:
define void @defaultFunction() #0 {
%1 = alloca i8*, align 8
%2 = load %struct._class_t*, %struct._class_t** @"OBJC_CLASSLIST_REFERENCES_$_", align 8
%3 = bitcast %struct._class_t* %2 to i8*
%4 = call i8* @objc_opt_new(i8* %3)
%5 = bitcast i8* %4 to %0*
%6 = bitcast %0* %5 to i8*
store i8* %6, i8** %1, align 8
call void @llvm.objc.storeStrong(i8** %1, i8* null) #1
ret void
}
仔細分析一下主要就由以下部分組成
id obj0 = objc_alloc_init(i8* %3)
objc_storeStrong(obj0, null)
其實就是兩步
新建一個物件
呼叫storeStrong這個函式
storeStrong
在runtime檔案中找到這個函式
如下
objc_storeStrong(id *location, id obj)
{
id prev = *location;
if (obj == prev) {
return;
}
objc_retain(obj);
*location = obj;
objc_release(prev);
}
其做了四件事
- 檢查輸入的 obj 地址 和指標指向的地址是否相同,
- 持有物件,參考計數 + 1 ,
- 指標指向 obj,
- 原來指向的物件參考計數 - 1(釋放物件),
對于這里傳入的NULL來說
這就等同于向物件發送release訊息
所以兩步變為新建一個obj0物件
作用域結束時候釋放這個物件
StoreStrong在賦值時也可以使用

objc_retain
來學習一下objc_object的具體實作
objc_retain(id obj)
{
if (!obj) return obj;
if (obj->isTaggedPointer()) return obj;
return obj->retain();
}
看下一層
objc_object::retain()
{
assert(!isTaggedPointer());
if (fastpath(!ISA()->hasCustomRR())) {
return rootRetain();
}
return ((id(*)(objc_object *, SEL))objc_msgSend)(this, SEL_retain);
}
系統會對是否支持NONPOINTER_ISA進行不同的處理
每個OC物件都含有一個isa指標,__arm64__之前,isa僅僅是一個指標,保存著物件或類物件記憶體地址,在__arm64__架構之后,apple對isa進行了優化,變成了一個共用體(union)結構,同時使用位域來存盤更多的資訊,
union isa_t
{
Class cls;
uintptr_t bits;
struct {
uintptr_t nonpointer : 1;//->表示使用優化的isa指標
uintptr_t has_assoc : 1;//->是否包含關聯物件
uintptr_t has_cxx_dtor : 1;//->是否設定了解構式,如果沒有,釋放物件更快
uintptr_t shiftcls : 33; // MACH_VM_MAX_ADDRESS 0x1000000000 ->類的指標
uintptr_t magic : 6;//->固定值,用于判斷是否完成初始化
uintptr_t weakly_referenced : 1;//->物件是否被弱參考
uintptr_t deallocating : 1;//->物件是否正在銷毀
uintptr_t has_sidetable_rc : 1;//1->在extra_rc存盤參考計數將要溢位的時候,借助Sidetable(散串列)存盤參考計數,has_sidetable_rc設定成1
uintptr_t extra_rc : 19; //->存盤參考計數
};
};

支持Nonpointer isa的處理
objc_object::rootRetain()
{
return rootRetain(false, false);
}
不支持的處理
objc_object::rootRetain()
{
if (isTaggedPointer()) return (id)this;
return sidetable_retain();
}
可以看到不支持Nonpointer isa的處理就是直接sidetable_retain,這是由于計數都存盤在sidetable中了,處理邏輯較支持Nonpointer isa的情況要簡單一些,
不支持Nonpointer isa 的處理
去sidetable取出計數資訊 執行加一操作
支持Nonpointer isa的處理
- 首先判斷是否為標簽指標型別 如果是 直接回傳
- 進入do-while處理邏輯
-
- 先判斷是否為 其一定支持Nonpointer isa的架構,但是isa沒有額外資訊
如果沒有額外資訊 那就和不支持意義一樣(判斷是否有優化) 參考計數存盤在sidetable中,走sidetable的參考計數+1的流程 - 判斷物件是否正在釋放,如果正在釋放則執行dealloc流程,
- 有存盤額外資訊,包含參考計數,我們嘗試對isa中的extra_rc++加一進行測驗
3.1 如果沒有溢位越界的情況,我們將isa的值修改為extra_rc++之后的值
3.2 如果有溢位 將一半的計數存盤到extra_rc,另一半存盤到sidetable中去 設定設定標志位位true

- 先判斷是否為 其一定支持Nonpointer isa的架構,但是isa沒有額外資訊
retain的總結
所以如果isa可以存盤額外資訊,那么有extra_rc位用來存盤參考計數,當參考計數滿了之后 就會存盤到sidetable中 ,
retain的流程也是針對isa是否支持存盤資訊分別進行處理
當extra_rc存盤溢位了,這個時候是一半(extra_rc能表示的最大值+1的一半)在extra_rc一半存盤在sidetable中,這里跟release的操作是對應的(extra_rc不夠減了也是去sidetable借extra_rc最大值的一半的計數),這樣設計的好處避免了頻繁的去sidetable中讀取計數資訊—假如我們溢位了把計數全部存到sidetable中去,那么有release的時候,extra_rc也不夠減了,又去借,這就大大降低了效率,比起直接操作isa,
release
// 真正的release方法
// 兩個引數分別是 是否需要呼叫dealloc函式,是否需要處理 向下溢位的問題
ALWAYS_INLINE bool
objc_object::rootRelease(bool performDealloc, bool handleUnderflow)
{
// 如果是TaggedPointer 不需要進行release操作
if (isTaggedPointer()) return false;
// 區域變數sideTable是否上鎖 默認false
bool sideTableLocked = false;
// 兩個區域變數用來記錄這個物件的isa指標
isa_t oldisa;
isa_t newisa;
retry:
do {
// 加載這個isa指標
oldisa = LoadExclusive(&isa.bits);
newisa = oldisa;
// 如果沒有進行nonpointer優化
if (slowpath(!newisa.nonpointer)) {
ClearExclusive(&isa.bits);
// 如果是類物件直接回傳false 不需要釋放
if (rawISA()->isMetaClass()) return false;
// 如果sideTableLocked 則解鎖 這里默認是false
if (sideTableLocked)
sidetable_unlock();
// 呼叫sidetable_release 進行參考計數-1操作
return sidetable_release(performDealloc);
}
// 溢位標記位
uintptr_t carry;
// newisa 物件的extra_rc 進行-1操作
newisa.bits = subc(newisa.bits, RC_ONE, 0, &carry); // extra_rc--
// 如果-1操作后 向下溢位了 結果為負數
if (slowpath(carry)) {
// don't ClearExclusive()
// 呼叫underflow 進行向下溢位的處理
goto underflow;
}
// 開啟回圈,直到 isa.bits 中的值被成功更新成 newisa.bits
} while (slowpath(!StoreReleaseExclusive(&isa.bits,
oldisa.bits, newisa.bits)));
//走到這說明參考計數的 -1 操作已完成
if (slowpath(sideTableLocked)) sidetable_unlock();
return false;
underflow:
//newisa的extra_rc在執行-1操作后導致了向下溢位
// 放棄對newisa的修改 使用之前的oldisa
newisa = oldisa;
// 如果 isa 的 has_sidetable_rc 標志位標識參考計數已溢位
// has_sidetable_rc 用于標識是否當前的參考計數過大,無法在isa中存盤,
// 而需要借用sidetable來存盤,(這種情況大多不會發生)
if (slowpath(newisa.has_sidetable_rc)) {
// 是否需要處理下溢
if (!handleUnderflow) {
// 清除原 isa 中的資料的原子獨占
ClearExclusive(&isa.bits);
// 如果不需要處理下溢 直接呼叫 rootRelease_underflow方法
return rootRelease_underflow(performDealloc);
}
// 如果sidetable是上鎖狀態
if (!sideTableLocked) {
// 解除清除原 isa 中的資料的原子獨占
ClearExclusive(&isa.bits);
// sidetable 上鎖
sidetable_lock();
sideTableLocked = true;
// 跳轉到 retry 重新開始,避免 isa 從 nonpointer 型別轉換成原始型別導致的問題
goto retry;
}
// sidetable_subExtraRC_nolock 放回要從sidetable移動到isa的extra_rc的值
// 默認是獲取extra_rc可存盤的長度一半的值
size_t borrowed = sidetable_subExtraRC_nolock(RC_HALF);
// To avoid races, has_sidetable_rc must remain set
// even if the side table count is now zero.
// 為了避免沖突 has_sidetable_rc 標志位必須保留1的狀態 及時sidetable中的個數為0
if (borrowed > 0) {
// 將newisa中參考計數值extra_rc 設定為borrowed - 1
// -1 是因為 本身這次是release操作
newisa.extra_rc = borrowed - 1;
// 然后將修改同步到isa中
bool stored = StoreReleaseExclusive(&isa.bits,
oldisa.bits, newisa.bits);
// 如果保存失敗
if (!stored) {
// Inline update failed.
// Try it again right now. This prevents livelock on LL/SC
// architectures where the side table access itself may have
// dropped the reservation.
// 從新裝載isa
isa_t oldisa2 = LoadExclusive(&isa.bits);
isa_t newisa2 = oldisa2;
// 如果newisa2是nonpointer型別
if (newisa2.nonpointer) {
// 下溢位標志位
uintptr_t overflow;
// 將從 SideTables 表中獲取的參考計數保存到 newisa2 的 extra_rc 標志位中
newisa2.bits =
addc(newisa2.bits, RC_ONE * (borrowed-1), 0, &overflow);
//
if (!overflow) {
// 如果沒有溢位再次將 isa.bits 中的值更新為 newisa2.bits
stored = StoreReleaseExclusive(&isa.bits, oldisa2.bits,
newisa2.bits);
}
}
}
// 如果重試之后依然失敗
if (!stored) {
// 將從sidetable中取出的參考計數borrowed 重新加到sidetable中
sidetable_addExtraRC_nolock(borrowed);
// 重新嘗試
goto retry;
}
// Decrement successful after borrowing from side table.
// This decrement cannot be the deallocating decrement - the side
// table lock and has_sidetable_rc bit ensure that if everyone
// else tried to -release while we worked, the last one would block.
// 完成對 SideTables 表中資料的操作后,為其解鎖
sidetable_unlock();
return false;
}
else {
// 在從Side table拿出一部分參考計數之后 Side table為空
// Side table is empty after all. Fall-through to the dealloc path.
}
}
// 如果當前的物件正在被釋放
if (slowpath(newisa.deallocating)) {
ClearExclusive(&isa.bits);
// 如果sideTableLocked被鎖 那么解鎖
if (sideTableLocked) sidetable_unlock();
// 兌現被過度釋放
return overrelease_error();
// does not actually return
}
// 將物件被釋放的標志位置為true
newisa.deallocating = true;
// 將newisa同步到isa中 如果失敗 進行重試
if (!StoreExclusive(&isa.bits, oldisa.bits, newisa.bits))
goto retry;
// 如果sideTableLocked= true
if (slowpath(sideTableLocked))
// Side table解鎖
sidetable_unlock();
__c11_atomic_thread_fence(__ATOMIC_ACQUIRE);
// 如果需要執行dealloc方法 那么呼叫該物件的dealloc方法
if (performDealloc) {
((void(*)(objc_object *, SEL))objc_msgSend)(this, @selector(dealloc));
}
return true;
}

學明白了retain release就很好理解了
- 依舊是判斷是否為taggedPointer
- 判斷是否有優化 如果沒有 就直接操作散串列,使參考計數-1
- 判斷是參考計數為否為0 如果是0則執行dealloc流程
- 若isa有優化,則物件的isa位建議,且通過carry判斷是否向下溢位了 結果為負數(下圖有點問題 應該是判斷是有向下溢位),如果是,如果到-1 就放棄newisa改為old,并將散串列中一半參考計數取出來,然后將這一半參考計數減一在存到isa的extra_rc
- 如果sidetable的參考計數為0,物件進行dealloc流程

其實和retain一樣 不過release操作變成-1 并且需要注意從sidetable中的一半減一放入
retainCount
- 當物件的isa經過優化,首先獲取isa位域extra_rc中的參考計數,默認會+1(防止你沒持有就要列印),uintptr_t rc = 1 + bits.extra_rc;然后獲取散串列的參考計數表中的參考計數,兩者相加得到物件的最終的參考計數
- 當物件的isa沒有經過優化,則直接獲取散串列的參考計數表中的參考計數,回傳,
為什么alloc/new后參考計數值為1 ? - 當我們alloc一個物件時,然后呼叫retainCount函式,得到物件的參考計數為1,這是因為在底層rootRetainCount方法中,參考計數默認+1了,這里只有對參考計數的讀取操作,是沒有寫入操作的,簡單來說就是:為了防止alloc創建的物件被釋放(參考計數為0會被釋放),所以在編譯階段,程式底層默認進行了+1操作,實際上在extra_rc中的參考計數仍然為0
所以 通過alloc或者new這樣賦值來新建一個物件 ARC MRC環境下都是1 這個1是底層默認的回傳值加一 沒有呼叫retain 其他強參考 才會呼叫objc_retain來持有
可以看下圖 alloc后rc參考計數熱然為0



非自己生成并持有
前面走了不少彎路
其實直接看匯編更簡單
id __strong obj = [NSMutableArray array];

上面的正常的都可以理解 還有幾個問題
objc_retainAutoreleasedReturnValue為什么非自己生成并持有時會出現?
這個就涉及很多問題了
一點一點來說
Autorelease物件什么時候釋放?
對這個問題的第一反應
“當前作用域大括號結束時釋放”
其實并不是的
在沒有手動加Autorelease Pool的情況下 Autorelease物件是在當前的runloop迭代結束時釋放的,而它能夠釋放的原因是系統在每個runloop迭代中都加入了自動釋放池Push和Pop
done
后面的原理后面再說 先回到正題
objc_retainAutoreleasedReturnValue是啥?
objc_retainAutoreleasedReturnValue 是優化程式運行的,將直接return物件不會注冊到autorelease之中,
它的作用就是持有物件,持有的物件來自自動釋放池中,因為obj是強參考,所以要持有這個物件,這個方法將來自自動釋放池的物件的參考計數+1.使用alloc/new/copy/mutableCopy之外的方法就會插入這條
那么就有這樣一個問題
array的物件不是應該放到自動釋放池中嗎,為什么沒有呢
這就是iOS對其進行的優化了
這就要提及他的另一個好兄弟 objc_autoreleaseReturnValue了
我們在哪可以看到它呢
新建一個類
我們在這個類重寫一下array方法

就能看到array時我們自動回傳了這個objc_autoreleaseReturnValue了
打開runtime庫搜索一下這倆兄弟


看一下倆兄弟各自是干嘛的
1. objc_autoreleaseReturnValue
1.1 prepareOptimizedReturn
// Try to prepare for optimized return with the given disposition (+0 or +1).
// Returns true if the optimized path is successful.
// Otherwise the return value must be retained and/or autoreleased as usual.
static ALWAYS_INLINE bool
prepareOptimizedReturn(ReturnDisposition disposition)
{
assert(getReturnDisposition() == ReturnAtPlus0);
if (callerAcceptsOptimizedReturn(__builtin_return_address(0))) {
if (disposition) setReturnDisposition(disposition);
return true;
}
return false;
}
核心是判斷callerAcceptsOptimizedReturn為true的時候才回傳true,開啟優化;那么這里是怎么知道需要優化的了,需要看看callerAcceptsOptimizedReturn的具體實作;在這之前先了解下__builtin_return_address和setReturnDisposition的作用,
1.2 __builtin_return_address
搜一下
其作用就是得到函式的回傳地址,0–表示回傳當前函式的回傳地址,1–表示回傳當前函式的呼叫方的回傳地址;
1.3 setReturnDisposition
enum ReturnDisposition : bool {
ReturnAtPlus0 = false, ReturnAtPlus1 = true
};
static ALWAYS_INLINE void
setReturnDisposition(ReturnDisposition disposition)
{
tls_set_direct(RETURN_DISPOSITION_KEY, (void*)(uintptr_t)disposition);
}
作用就是將disposition的值存盤到TLS(Thread Local Storage)中
1.4 callerAcceptsOptimizedReturn
看名字意思就是呼叫方是否接受優化的回傳
當呼叫方在回傳值之后如果呼叫了objc_retainAutoreleasedReturnValue或者objc_unsafeClaimAutoreleasedReturnValue的時候就表示可以優化;
至此我們就引出來objc_retainAutoreleasedReturnValue這個東西了
2. objc_retainAutoreleasedReturnValue
objc_retainAutoreleasedReturnValue(id obj)
{
if (acceptOptimizedReturn() == ReturnAtPlus1) return obj;
return objc_retain(obj);
}
2.1 acceptOptimizedReturn
// Try to accept an optimized return.
// Returns the disposition of the returned object (+0 or +1).
// An un-optimized return is +0.
static ALWAYS_INLINE ReturnDisposition
acceptOptimizedReturn()
{
ReturnDisposition disposition = getReturnDisposition();//從TSL中讀取對應的值
setReturnDisposition(ReturnAtPlus0); // reset to the unoptimized state 將資料置為ReturnAtPlus0
return disposition;
}
acceptOptimizedReturn區域讀取TLS中1.3存盤的值,同時讀取之后置為ReturnAtPlus0
讀取回傳值 如果通過1.4的判斷 后面有呼叫 那么此時標志位為ture
那么就直接回傳這個物件
否則,就回傳objc_retain?(這里還沒看懂)
我暫時的理解就是 如果標志位不為ture 就回傳到自動緩沖池中讓自動緩沖池進行持有

非自己生成并持有的總結
所以對這兩兄弟理解就是
- objc_retainAutoreleasedReturnValue會檢驗呼叫者是否會對該物件執行retain,如果會的話就不執行autorelease,直接設定標志符
- objc_autoreleaseReturnValue在檢驗到標志服后,也不retain了,直接回傳物件本身
轉載請註明出處,本文鏈接:https://www.uj5u.com/yidong/289327.html
標籤:其他
