主頁 > 移動端開發 > [iOS開發]ARC

[iOS開發]ARC

2021-07-21 14:03:49 移動端開發

文章目錄

  • 記憶體管理四大原則
  • 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
        • 非自己生成并持有的總結

記憶體管理四大原則

  1. 自己生成的物件自己持有
  2. 非自己生成的物件自己也能持有
  3. 不再需要自己持有的物件時釋放
  4. 非自己持有的物件無法釋放

iOS底層對記憶體管理的方案

  1. taggedPointer :很熟悉了存盤小的物件如NSNumber
  2. NONPOINTER_ISA :在 64 位架構下,isa 指標是占 64 位元位的,實際上只有 30 多位就 已經夠用了,為了提高利用率,剩余的位元位存盤了記憶體管理的相關資料內容,
    nonpointer: 表示是否對 isa 指標開啟指標優化
    ? 0: 純 isa 指標
    ? 1: 不止是類物件地址, isa 中包含了類資訊、物件的參考計數等
  3. 散串列:復雜的資料結構 :復雜的資料結構,包括了參考計數表和弱參考表 通過 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修飾符的變數不屬于編譯器的記憶體管理物件,

為什么說是不安全呢

  1. weak 修飾的指標變數,在指向的記憶體地址銷毀后,會在 Runtime 的機制下,自動置為 nil, _Unsafe_Unretain 不會置為 nil,容易出現 懸垂指標,發生崩潰,但是 _Unsafe_Unretain 比 __weak 效率高,
    懸垂指標 指標指向的記憶體已經被釋放了,但是指標還存在,這就是一個 懸垂指標 或者說 迷途指標,野指標,沒有進行初始化的指標,其實都是 野指標

  2. 附有__unsafe_unretained修飾符的變數同附有__weak修飾符的變數一樣,生成的物件會立即釋放,

在使用__unsafe_unretained修飾符時,賦值給附有__strong修飾符的變數時有必要確保被賦值的物件確實存在,如果不存在,那么程式就會崩潰,

__autoreleasing修飾符

與MRC進行比較

  • MRC中autorelease的使用方法
    1. 生成并持有NSAutoreleasePool物件,
    1. 呼叫已分配物件的autorelease方法【將物件注冊到pool中】
    1. 廢棄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);
}

其做了四件事

  1. 檢查輸入的 obj 地址 和指標指向的地址是否相同,
  2. 持有物件,參考計數 + 1 ,
  3. 指標指向 obj,
  4. 原來指向的物件參考計數 - 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處理邏輯
    1. 先判斷是否為 其一定支持Nonpointer isa的架構,但是isa沒有額外資訊
      如果沒有額外資訊 那就和不支持意義一樣(判斷是否有優化) 參考計數存盤在sidetable中,走sidetable的參考計數+1的流程
    2. 判斷物件是否正在釋放,如果正在釋放則執行dealloc流程,
    3. 有存盤額外資訊,包含參考計數,我們嘗試對isa中的extra_rc++加一進行測驗
      3.1 如果沒有溢位越界的情況,我們將isa的值修改為extra_rc++之后的值
      3.2 如果有溢位 將一半的計數存盤到extra_rc,另一半存盤到sidetable中去 設定設定標志位位true
      在這里插入圖片描述
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就很好理解了

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

其實和retain一樣 不過release操作變成-1 并且需要注意從sidetable中的一半減一放入

retainCount
  1. 當物件的isa經過優化,首先獲取isa位域extra_rc中的參考計數,默認會+1(防止你沒持有就要列印),uintptr_t rc = 1 + bits.extra_rc;然后獲取散串列的參考計數表中的參考計數,兩者相加得到物件的最終的參考計數
  2. 當物件的isa沒有經過優化,則直接獲取散串列的參考計數表中的參考計數,回傳,
    為什么alloc/new后參考計數值為1 ?
  3. 當我們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

標籤:其他

上一篇:Android——Intent組件

下一篇:uniapp應用原生隱私協議彈窗的場景

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 【從零開始擼一個App】Dagger2

    Dagger2是一個IOC框架,一般用于Android平臺,第一次接觸的朋友,一定會被搞得暈頭轉向。它延續了Java平臺Spring框架代碼碎片化,注解滿天飛的傳統。嘗試將各處代碼片段串聯起來,理清思緒,真不是件容易的事。更不用說還有各版本細微的差別。 與Spring不同的是,Spring是通過反射 ......

    uj5u.com 2020-09-10 06:57:59 more
  • Flutter Weekly Issue 66

    新聞 Flutter 季度調研結果分享 教程 Flutter+FaaS一體化任務編排的思考與設計 詳解Dart中如何通過注解生成代碼 GitHub 用對了嗎?Flutter 團隊分享如何管理大型開源專案 插件 flutter-bubble-tab-indicator A Flutter librar ......

    uj5u.com 2020-09-10 06:58:52 more
  • Proguard 常用規則

    介紹 Proguard 入口,如何查看輸出,如何使用 keep 設定入口以及使用實體,如何配置壓縮,混淆,校驗等規則。

    ......

    uj5u.com 2020-09-10 06:59:00 more
  • Android 開發技術周報 Issue#292

    新聞 Android即將獲得類AirDrop功能:可向附近設備快速分享檔案 谷歌為安卓檔案管理應用引入可安全隱藏資料的Safe Folder功能 Android TV新主界面將顯示電影、電視節目和應用推薦內容 泄露的Android檔案暗示了傳說中的谷歌Pixel 5a與折疊屏新機 谷歌發布Andro ......

    uj5u.com 2020-09-10 07:00:37 more
  • AutoFitTextureView Error inflating class

    報錯: Binary XML file line #0: Binary XML file line #0: Error inflating class xxx.AutoFitTextureView 解決: <com.example.testy2.AutoFitTextureView android: ......

    uj5u.com 2020-09-10 07:00:41 more
  • 根據Uri,Cursor沒有獲取到對應的屬性

    Android: 背景:呼叫攝像頭,拍攝視頻,指定保存的地址,但是回傳的Cursor檔案,只有名稱和大小的屬性,沒有其他諸如時長,連ID屬性都沒有 使用 cursor.getInt(cursor.getColumnIndexOrThrow(MediaStore.Video.Media.DURATIO ......

    uj5u.com 2020-09-10 07:00:44 more
  • Android連載29-持久化技術

    一、持久化技術 我們平時所使用的APP產生的資料,在記憶體中都是瞬時的,會隨著斷電、關機等丟失資料,因此android系統采用了持久化技術,用于存盤這些“瞬時”資料 持久化技術包括:檔案存盤、SharedPreference存盤以及資料庫存盤,還有更復雜的SD卡記憶體儲。 二、檔案存盤 最基本存盤方式, ......

    uj5u.com 2020-09-10 07:00:47 more
  • Android Camera2Video整合到自己專案里

    背景: Android專案里呼叫攝像頭拍攝視頻,原本使用的 MediaStore.ACTION_VIDEO_CAPTURE, 后來因專案需要,改成了camera2 1.Camera2Video 官方demo有點問題,下載后,不能直接整合到專案 問題1.多次拍攝視頻崩潰 問題2.雙擊record按鈕, ......

    uj5u.com 2020-09-10 07:00:50 more
  • Android 開發技術周報 Issue#293

    新聞 谷歌為Android TV開發者提供多種新功能 Android 11將自動填表功能整合到鍵盤輸入建議中 谷歌宣布Android Auto即將支持更多的導航和數字停車應用 谷歌Pixel 5只有XL版本 搭載驍龍765G且將比Pixel 4更便宜 [圖]Wear OS將迎來重磅更新:應用啟動時間 ......

    uj5u.com 2020-09-10 07:01:38 more
  • 海豚星空掃碼投屏 Android 接收端 SDK 集成 六步驟

    掃碼投屏,開放網路,獨占設備,不需要額外下載軟體,微信掃碼,發現設備。支持標準DLNA協議,支持倍速播放。視頻,音頻,圖片投屏。好點意思。還支持自定義基于 DLNA 擴展的操作動作。好像要收費,沒體驗。 這里簡單記錄一下集成程序。 一 跟目錄的build.gradle添加私有mevan倉庫 mave ......

    uj5u.com 2020-09-10 07:01:43 more
最新发布
  • 歡迎頁輪播影片

    如圖,引導開始,球從上落下,同時淡入文字,然后文字開始輪播,最后一頁時停止,點擊進入首頁。 在來看看效果圖。 重力球先不講,主要歡迎輪播簡單實作 首先新建一個類 TextTranslationXGuideView,用于影片展示 文本是類似的,最后會有個圖片箭頭影片,布局很簡單,就是一個 TextVi ......

    uj5u.com 2023-04-20 08:40:31 more
  • 【FAQ】關于華為推送服務因營銷訊息頻次管控導致服務通訊類訊息

    一. 問題描述 使用華為推送服務下發IM訊息時,下發訊息請求成功且code碼為80000000,但是手機總是收不到訊息; 在華為推送自助分析(Beta)平臺查看發現,訊息發送觸發了頻控。 二. 問題原因及背景 2023年1月05日起,華為推送服務對咨詢營銷類訊息做了單個設備每日推送數量上限管理,具體 ......

    uj5u.com 2023-04-20 08:40:11 more
  • 歡迎頁輪播影片

    如圖,引導開始,球從上落下,同時淡入文字,然后文字開始輪播,最后一頁時停止,點擊進入首頁。 在來看看效果圖。 重力球先不講,主要歡迎輪播簡單實作 首先新建一個類 TextTranslationXGuideView,用于影片展示 文本是類似的,最后會有個圖片箭頭影片,布局很簡單,就是一個 TextVi ......

    uj5u.com 2023-04-20 08:39:36 more
  • 【FAQ】關于華為推送服務因營銷訊息頻次管控導致服務通訊類訊息

    一. 問題描述 使用華為推送服務下發IM訊息時,下發訊息請求成功且code碼為80000000,但是手機總是收不到訊息; 在華為推送自助分析(Beta)平臺查看發現,訊息發送觸發了頻控。 二. 問題原因及背景 2023年1月05日起,華為推送服務對咨詢營銷類訊息做了單個設備每日推送數量上限管理,具體 ......

    uj5u.com 2023-04-20 08:39:13 more
  • iOS從UI記憶體地址到讀取成員變數(oc/swift)

    開發除錯時,我們發現bug時常首先是從UI顯示發現例外,下一步才會去定位UI相關連的資料的。XCode有給我們提供一系列debug工具,但是很多人可能還沒有形成一套穩定的除錯流程,因此本文嘗試解決這個問題,順便提出一個暴論:UI顯示例外問題只需要兩個步驟就能完成定位作業的80%: 定位例外 UI 組 ......

    uj5u.com 2023-04-19 09:16:23 more
  • FIDE重磅更新!性能飛躍!體驗有禮!

    FIDE 開發者工具重構升級啦!實作500%性能提升,誠邀體驗! 一直以來不少開發者朋友在社區反饋,在使用 FIDE 工具的程序中,時常會遇到諸如加載不及時、代碼預覽/渲染性能不如意的情況,十分影響開發體驗。 作為技術團隊,我們深知一件趁手的開發工具對開發者的重要性,因此,在2023年開年,FinC ......

    uj5u.com 2023-04-19 09:16:15 more
  • 游戲內嵌社區服務開放,助力開發者提升玩家互動與留存

    華為 HMS Core 游戲內嵌社區服務提供快速訪問華為游戲中心論壇能力,支持玩家直接在游戲內瀏覽帖子和交流互動,助力開發者擴展內容生產和觸達的場景。 一、為什么要游戲內嵌社區? 二、游戲內嵌社區的典型使用場景 1、游戲內打開論壇 您可以在游戲內繪制論壇入口,為玩家提供沉浸式發帖、瀏覽、點贊、回帖、 ......

    uj5u.com 2023-04-19 09:15:46 more
  • iOS從UI記憶體地址到讀取成員變數(oc/swift)

    開發除錯時,我們發現bug時常首先是從UI顯示發現例外,下一步才會去定位UI相關連的資料的。XCode有給我們提供一系列debug工具,但是很多人可能還沒有形成一套穩定的除錯流程,因此本文嘗試解決這個問題,順便提出一個暴論:UI顯示例外問題只需要兩個步驟就能完成定位作業的80%: 定位例外 UI 組 ......

    uj5u.com 2023-04-19 09:14:53 more
  • FIDE重磅更新!性能飛躍!體驗有禮!

    FIDE 開發者工具重構升級啦!實作500%性能提升,誠邀體驗! 一直以來不少開發者朋友在社區反饋,在使用 FIDE 工具的程序中,時常會遇到諸如加載不及時、代碼預覽/渲染性能不如意的情況,十分影響開發體驗。 作為技術團隊,我們深知一件趁手的開發工具對開發者的重要性,因此,在2023年開年,FinC ......

    uj5u.com 2023-04-19 09:14:08 more
  • 游戲內嵌社區服務開放,助力開發者提升玩家互動與留存

    華為 HMS Core 游戲內嵌社區服務提供快速訪問華為游戲中心論壇能力,支持玩家直接在游戲內瀏覽帖子和交流互動,助力開發者擴展內容生產和觸達的場景。 一、為什么要游戲內嵌社區? 二、游戲內嵌社區的典型使用場景 1、游戲內打開論壇 您可以在游戲內繪制論壇入口,為玩家提供沉浸式發帖、瀏覽、點贊、回帖、 ......

    uj5u.com 2023-04-19 09:08:34 more