block本質探尋七之記憶體管理

来源:https://www.cnblogs.com/lybSkill/archive/2019/01/17/10278567.html
-Advertisement-
Play Games

說明: <1>閱讀本問,請參照block前述文章加以理解; <2>環境:ARC; <3>變數類型:基本數據類型或者對象類型的auto局部變數; 一、三種情形 //代碼 //列印 //clang:xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc - ...


說明:

<1>閱讀本問,請參照block前述文章加以理解;

<2>環境:ARC;

<3>變數類型:基本數據類型或者對象類型的auto局部變數;

一、三種情形

//代碼

void test1()
{
    int num = 10;
    __block int age = 20;
    Person *per = [[Person alloc] init];
    void(^block)(void) = ^{
        NSLog(@"%d %d %p", num, age, per);
    };
    block();
}

//列印

2019-01-16 15:42:38.974947+0800 MJ_TEST[2405:192414] 10 20 0x100654220
2019-01-16 15:42:38.975258+0800 MJ_TEST[2405:192414] -[Person dealloc]
Program ended with exit code: 0

//clang:xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc -fobjc-arc -fobjc-runtime=ios-9.0.0 main.m

struct __Block_byref_age_0 {
  void *__isa;
__Block_byref_age_0 *__forwarding;
 int __flags;
 int __size;
 int age;
};


struct __test1_block_impl_0 {
  struct __block_impl impl;
  struct __test1_block_desc_0* Desc;
  int num;
  Person *__strong per;
  __Block_byref_age_0 *age; // by ref
  __test1_block_impl_0(void *fp, struct __test1_block_desc_0 *desc, int _num, Person *__strong _per, __Block_byref_age_0 *_age, int flags=0) : num(_num), per(_per), age(_age->__forwarding) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};


static void __test1_block_copy_0(struct __test1_block_impl_0*dst, struct __test1_block_impl_0*src) {_Block_object_assign((void*)&dst->age, (void*)src->age, 8/*BLOCK_FIELD_IS_BYREF*/);_Block_object_assign((void*)&dst->per, (void*)src->per, 3/*BLOCK_FIELD_IS_OBJECT*/);}


static void __test1_block_dispose_0(struct __test1_block_impl_0*src) {_Block_object_dispose((void*)src->age, 8/*BLOCK_FIELD_IS_BYREF*/);_Block_object_dispose((void*)src->per, 3/*BLOCK_FIELD_IS_OBJECT*/);}


static struct __test1_block_desc_0 {
  size_t reserved;
  size_t Block_size;
  void (*copy)(struct __test1_block_impl_0*, struct __test1_block_impl_0*);
  void (*dispose)(struct __test1_block_impl_0*);
}



void test1()
{
    int num = 10;
    __attribute__((__blocks__(byref))) __Block_byref_age_0 age = {(void*)0,(__Block_byref_age_0 *)&age, 0, sizeof(__Block_byref_age_0), 20};
    Person *per = ((Person *(*)(id, SEL))(void *)objc_msgSend)((id)((Person *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("Person"), sel_registerName("alloc")), sel_registerName("init"));
    void(*block)(void) = ((void (*)())&__test1_block_impl_0((void *)__test1_block_func_0, &__test1_block_desc_0_DATA, num, per, (__Block_byref_age_0 *)&age, 570425344));
    ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
}

分析:根據前述文章可知

<1>ARC模式下,block對象(等號右邊大括弧)被強指針(等號左邊)變數持有(預設為strong)時,系統會自動將block對象從棧區copy到堆區;

<2>當訪問實例對象或者__block修飾的基礎數據類型變數時,block對象結構體中自動會增加兩個函數指針——copy和dispose,分別指向__test1_block_copy_0函數和__test1_block_dispose_0函數;

<3>__block修飾的基礎數據類型的變數,系統會自動生成一個新的對象(__Block_byref_age_0),block對象通過該新的對象指針變數(__Block_byref_age_0 *age)來訪問基礎數據類型變數(int age);

<4>num:直接被block捕捉到其結構體內部,隨block一起copy到堆區;其隨block對象本身一起銷毀(不管是棧區的block還是堆區的block);

<5>per:類對象的訪問,當block對象被copy到堆區時,block對象會通過copy函數指針來調用__test1_block_copy_0函數完成對per對象的拷貝;同時此時堆區的block對象會根據該類對象的類型(strong還是weak)來決定對其強引用還是弱引用;

說明:所謂的強引用,是指指針指向某個對象(實質為,存放指針變數的記憶體指向對象的記憶體);所謂的指向,即是對該對象的引用計數加1即retain操作(即此時該指針持有該對象),但是一個對象可能被多個指針持有,因此realease並不必然導致對象銷毀(記憶體回收)而只是被釋放即某個指針變數不再指向(持有)該記憶體區域,只有當對象的引用計數為0時,系統才會自動回收其記憶體;

註:引用計數為對象的屬性,而非指針;

<6>__block修飾的基本數據類型的變數和對象類型變數,block被拷貝時,都會通過調用__test1_block_copy_0函數中_Block_object_assign函數完成對自身的拷貝——其中,_Block_object_assign函數的第三個參數,8表示由__block修飾的基本數據類型變數,3表示訪問的事對象類型變數(那麼,__block修飾的對象類型是多少呢?往下看);

當block對象銷毀時,二者都是通過調用__test1_block_dispose_0函數中的_Block_object_dispose函數來被銷毀;

問題:為什麼要增加copy和dispose兩個函數指針呢?因為block對象要持有上述兩種對象(__block修飾的基本數據類型生成的對象和per實例對象),那麼自然要對其進行記憶體管理,達到持有/釋放的可控目的;

因此,__block修飾生成的對象(會隨著block的copy而一起被copy到堆區,而拷貝後的棧區的結構體依然會存在,只不過其作用域結束後,系統會對其記憶體自動回收),block對象要對其持有,肯定是強引用,否則弱引用,該對象的記憶體管理不受控制,那麼block內部修改變數的值存在極大風險——這點沒問題;

補充:__weak不能修飾基礎類型變數

如上,我麽知道,__weak僅能修飾對象類型變數和block指針類型——為什麼?

以上我們已經分析過,__strong和__weak:前者指針持有對象達到對該對象記憶體管理可控的目的(只要該對象的引用計數>0,其記憶體就不可能被回收,指針就可以合法指向該記憶體),會進行retain操作即對象的retainCount會自動+1;後者不持有,該對象的記憶體管理不可控(什麼時候釋放,跟該指針沒關係),不會retain,對象的引用計數不會自動+1;

所以,__strong和__weak修飾的目的是對堆區的記憶體管理是否管控,而只有對象類型的變數(在堆區創建)才會有管控的問題,基礎數據類型變數起始是在棧區存儲,其記憶體(創建/回收)由系統自動管理;

 

二、__forwarding指針

我們在前面的文章提到,block對int類型的age變數的訪問,為什麼還要通過__forwarding指針而不是直接訪問__Block_byref_age_0結構體中的age變數呢?

 

 分析:

<1>__forwarding指針本身是指向__Block_byref_age_0結構體本身;第一個age又是__Block_byref_age_0結構體類型的指針;第二個age是__Block_byref_age_0結構體中int型成員變數;

<2>在棧區:age->__forwarding->age <=> age->age 沒有任何問題;但是在堆區:因為block對象結構體會被copy到堆區,而原先留在棧區的block中的__forwarding指針會自動指向堆區的__Block_byref_age_0結構體;

<3>從上述分析,我們很清楚地知道,將__Block_byref_age_0結構體一併copy到堆區的目的就是堆區的block對象強引用該結構體,所以指向堆區的block對象的各類指針(包括對象本身)都可以通過該block對象達到對__Block_byref_age_0結構體中age變數值的改變等操作的目的,而不必擔心int型age變數記憶體隨時會被系統回收的風險

————問題來了:如果棧區的指針或者block對象本身要對age變數的值進行修改,是要面臨該風險的,那如何規避呢?

就是通過__forwarding指針,因為此時棧區的_Block_byref_age_0結構體中的__forwarding指針變數是指向堆區的_Block_byref_age_0結構體,除非堆區_Block_byref_age_0結構體記憶體被手動銷毀,否則會一直存在;

//圖解

 

 

三、__block修飾對象類型變數

//代碼

void test2()
{
    Person *per = [[Person alloc] init];
    per.age = 20;
    __block Person *blockPer = per;
    void(^block)(void) = ^{
        blockPer.age = 30;
        NSLog(@"%d", blockPer.age);
    };
    block();
}

//列印

2019-01-17 11:17:12.020409+0800 MJ_TEST[1304:76856] 30
2019-01-17 11:17:12.020869+0800 MJ_TEST[1304:76856] -[Person dealloc]
Program ended with exit code: 0

//clang

struct __Block_byref_blockPer_1 {
  void *__isa;
__Block_byref_blockPer_1 *__forwarding;
 int __flags;
 int __size;
 void (*__Block_byref_id_object_copy)(void*, void*);
 void (*__Block_byref_id_object_dispose)(void*);
 Person *blockPer;
};


struct __test2_block_impl_0 {
  struct __block_impl impl;
  struct __test2_block_desc_0* Desc;
  __Block_byref_blockPer_1 *blockPer; // by ref
  __test2_block_impl_0(void *fp, struct __test2_block_desc_0 *desc, __Block_byref_blockPer_1 *_blockPer, int flags=0) : blockPer(_blockPer->__forwarding) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};


static void __test2_block_copy_0(struct __test2_block_impl_0*dst, struct __test2_block_impl_0*src) {_Block_object_assign((void*)&dst->blockPer, (void*)src->blockPer, 8/*BLOCK_FIELD_IS_BYREF*/);}


static void __test2_block_dispose_0(struct __test2_block_impl_0*src) {_Block_object_dispose((void*)src->blockPer, 8/*BLOCK_FIELD_IS_BYREF*/);}


static struct __test2_block_desc_0 {
  size_t reserved;
  size_t Block_size;
  void (*copy)(struct __test2_block_impl_0*, struct __test2_block_impl_0*);
  void (*dispose)(struct __test2_block_impl_0*);
}

分析:

<1>我們發現,__block修飾的blockPerl實例對象,系統也會自動生成一個新的對象__Block_byref_blockPer_1;被引用的類對象以指針的形式存在於該結構體中(Person *blockPer),該指針指向[[Person alloc] init]這個實例對象(位於堆區);

<2>在__Block_byref_blockPer_1結構體中,還存在__Block_byref_id_object_copy和__Block_byref_id_object_dispose兩個函數指針,分別指向__Block_byref_id_object_copy_131函數和__Block_byref_id_object_dispose_131函數(作用同上),如下:

static void __Block_byref_id_object_copy_131(void *dst, void *src) {
 _Block_object_assign((char*)dst + 40, *(void * *) ((char*)src + 40), 131);
}
static void __Block_byref_id_object_dispose_131(void *src) {
 _Block_object_dispose(*(void * *) ((char*)src + 40), 131);
}

很顯然,這兩個函數的作用也是針對某個對象的記憶體管理,那是哪個對象呢?

首先,_Block_object_assign和_Block_object_dispose的第三個參數為131,是指__block修飾實例對象的情形;

其次,dst是__Block_byref_blockPer_1對象的地址,加40是什麼?我們算出Person *blockPer指針的地址偏移量正好為40(8+8+4+4+8+8,指針變數占8個位元組),那麼可以肯定,上述兩個函數就是對Person *blockPer指向的實例對象[[Person alloc] init]的記憶體管理;

所以,我們可以推測出以下結構:block對象__test2_block_impl_0通過其內部成員變數blockPer持有__Block_byref_blockPer_1對象,而__Block_byref_blockPer_1對象又通過其內部成員變數blockPer持有[[Person alloc] init]實例對象;

我們知道,前者通過_test2_block_copy_0函數和__test2_block_dispose_0函數進行記憶體管理,其持有必定是強引用,,這點沒問題;而後者的持有是通過__Block_byref_id_object_copy_131函數和__Block_byref_id_object_dispose_131函數進行記憶體管理,但其持有是強引用還是弱引用呢?往下看;

//__weak修飾

__block __weak Person *blockPer = per;

//clang:xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc -fobjc-arc -fobjc-runtime=ios-9.0.0 main.m(註:要設置runtime,否則編譯會報錯)

struct __Block_byref_blockPer_1 {
  void *__isa;
__Block_byref_blockPer_1 *__forwarding;
 int __flags;
 int __size;
 void (*__Block_byref_id_object_copy)(void*, void*);
 void (*__Block_byref_id_object_dispose)(void*);
 Person *__weak blockPer;
};

//列印

2019-01-17 12:17:20.776779+0800 MJ_TEST[1598:105742] 30
2019-01-17 12:17:20.777113+0800 MJ_TEST[1598:105742] -[Person dealloc]
Program ended with exit code: 0

分析:

<1>我們發現,如果沒有__weak修飾,blockPer格式為Person *blockPer,預設為strong類型;__weak修飾後,則會變成weak類型;

<2>根據之前的分析,其實__Block_byref_blockPer_1對象對[[Person alloc] init]實例對象的引用,取決於指向該實例對象的指針類型(因為對象引用是指針傳遞,前面已講過);

------!!!但是,該規則僅限於ARC模式的情形,MRC模式下,如果指針是strong類型,系統並不會執行retain操作!!!

這裡有個問題,為什麼__weak修飾後,Person實例對象列印前沒有被銷毀呢?因為該實例對象的作用域在test2()函數體內,而block的回調也在函數體內,因此回調時,該實例對象並沒有被銷毀;

接下來,我們可以驗證下: 

分析:此時,block回調前,Person實例對象就被銷毀了,說明block對象對實例對象的引用取決於Person對象指針的引用類型;

我們再切換到MRC模式下看看:

//代碼

void test4()
{
    Person *per = [[Person alloc] init];
    
    MyBlock block = [^{
        NSLog(@"%p", per);
    } copy];
    
    [per release];
    
    block();
    
    [block release];
}

//列印

2019-01-17 13:52:05.667590+0800 MJ_TEST[2106:151409] 0x10061add0
2019-01-17 13:52:05.668200+0800 MJ_TEST[2106:151409] -[Person dealloc]
Program ended with exit code: 0

分析:當per指針象release時,[[Person alloc] init]實例對象並沒有被釋放,而當block指針release時,[[Person alloc] init]實例對象才被釋放(block對象不再持有該實例對象),這也印證了上述的分析;

 

四、註意

針對第三點(__block修飾對象類型的auto局部變數),系統生成的__block對象(__Block_byref_blockPer_1),其通過內部成員指針變數(Person *__weak blockPer)來持有實例對象([[Person alloc] init]),持有的類型取決於成員指針變數blockPer的類型(強指針則強引用,弱指針則弱引用)——該規則,在第三點情形下並且在MRC模式下,如果是強指針類型,則系統不會進行retain操作(除此,其他情況不受影響)!————所以,在MRC模式下,__block修飾對象類型的auto局部指針變數,新生成的__block對象永遠不可能強引用該指針指向的實例對象!

 

GitHub


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 使用DOS命令進入MySQL:mysql -u root -p 按回車鍵輸入密碼顯示如下界面成功進入MySQL交互界面。 如果此時不知道MySQL有哪些資料庫,使用顯示所有資料庫名語句:show databases; (註意結尾有英文格式下的分號) 接下來,選擇你想操作的資料庫,使用語句:use m ...
  • 題目描述: 編寫一個 SQL 查詢,獲取 Employee 表中第二高的薪水(Salary) 。 例如上述 Employee 表,SQL查詢應該返回 200 作為第二高的薪水。如果不存在第二高的薪水,那麼查詢應返回 null。 SQL架構: 解題思路: 思路1:取第二高的薪水,可以用max()函數取 ...
  • 對於用戶而言,分區表是一個獨立的邏輯表,但是在底層由多個物理子表組成。實現分區的代碼實際上是對一組底層表的句柄對象的封裝,對分區表的請求都會通過句柄對象轉化成對存儲引擎的介面調用 ...
  • 題目描述: 表1: Person 表2: Address 編寫一個 SQL 查詢,滿足條件:無論 person 是否有地址信息,都需要基於上述兩表提供 person 的以下信息: SQL架構: 1 Create table Person (PersonId int, FirstName varcha ...
  • 需求描述 問題:subquery 1 union subquery2,其中union左右的兩個子查詢是否並行。 場景:業務中性能敏感的業務,希望能加快速度,如果資料庫能兩個子查詢並行執行,既可以節省時間,還可以節省網路開銷 測試結果 分析:mysql是一個連接分配一個進程,這樣的一個SQL也不會分成 ...
  • 如果你想插入的欄位取值方式不同,既有自己設定的值,又想插入某個表中的某個欄位數據,下麵就舉例說明 insert into Meters(metertypeid, meternumber, constant) Values('16','2',(select constant from FreezeDa ...
  • 優化關聯查詢 如果想要優化使用關聯的查詢,我們需要特別留意以下幾點: 1. 確保ON或者USING子句中的列上有索引。在創建索引的時候需要考慮到關聯的順序。當表A和表B用列c關聯的時候,如果優化器的關聯順序是B、A,那麼就不需要在B表的對應列上建索引。除非有其他理由,否則只需要在關聯順序的第二個表的 ...
  • [TOC] 2. 監聽器 一個控制項可以設置多個監聽器 綁定監聽器的步驟 獲取代表控制項的對象 定義一個類,實現監聽器介面 生成監聽器對象 為控制項綁定監聽器對象 3. 佈局 控制項佈局方法:就是控制控制項在Activity中的位置,大小,顏色以及其他控制項樣式屬性的方法 如何設置佈局 在佈局文件完成控制項佈局 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...