block本質探尋四之copy

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

說明: <1>閱讀本文,最好閱讀之前的block文章加以理解; <2>本文內容:三種block類型的copy情況(MRC)、是否深拷貝、錯誤copy; 一、MRC模式下,三種block類型的copy情況 //代碼 //列印 分析: <1>只有stack類型block實例對象copy後的類型變為mal ...


說明:

<1>閱讀本文,最好閱讀之前的block文章加以理解;

<2>本文內容:三種block類型的copy情況(MRC)、是否深拷貝、錯誤copy;

 

一、MRC模式下,三種block類型的copy情況

//代碼

void test1()
{
    int age = 10;
    
    void(^block1)(void) = ^{
        NSLog(@"-----");
    };
    
    void(^block2)(void) = ^{
        NSLog(@"-----%d", age);
    };
    
    id block3 = [block2 copy];
    
    NSLog(@"%@ %@ %@", [block1 class], [block2 class], [block3 class]);
    NSLog(@"%@ %@ %@", [[block1 copy] class], [[block2 copy] class], [[block3 copy] class]);
}

//列印

2019-01-11 14:14:06.902974+0800 MJ_TEST[2183:154918] __NSGlobalBlock__ __NSStackBlock__ __NSMallocBlock__
2019-01-11 14:14:06.903260+0800 MJ_TEST[2183:154918] __NSGlobalBlock__ __NSMallocBlock__ __NSMallocBlock__
Program ended with exit code: 0

分析:

<1>只有stack類型block實例對象copy後的類型變為malloc,這個前面文章已經討論過,沒有問題;

<2>global類型實例對象存儲在數據區,copy操作其實什麼也沒做;malloc在堆區,copy之後肯定還是在堆區,但不會開闢新的記憶體,只是引用計數加1——此處分析,可以通過clang和地址、引用計數列印來查看,此處不再贅述;

結論: 

補充:上述copy的操作是針對block實例對象,那麼類對象是存在哪個區呢?往下看

//代碼

int a = 20;

void test2()
{
    int b = 10;
    
    void(^block1)(void) = ^{
        NSLog(@"-----");
    };
    
    void(^block2)(void) = ^{
        NSLog(@"-----%d", b);
    };
    
    id block3 = [block2 copy];
    
    id block1Cls = object_getClass(block1);
    id block2Cls = object_getClass(block2);
    id block3Cls = object_getClass(block3);
    
    NSLog(@"a--global--%p", &a);
    NSLog(@"b--auto place--%p", &b);
    NSLog(@"alloc----%p", [[NSObject alloc] init]);
    NSLog(@"Person----%p", [Person class]);
    
    NSLog(@"------block---instance---");
    NSLog(@"block1----%@ %p", [block1 class], block1);
    NSLog(@"block2----%@ %p", [block2 class], block2);
    NSLog(@"block3----%@ %p", [block3 class], block3);
    
    NSLog(@"------block---Class---");
    NSLog(@"block1Cls----%@ %p", block1Cls, block1Cls);
    NSLog(@"block2Cls----%@ %p", block2Cls, block2Cls);
    NSLog(@"block3Cls----%@ %p", block3Cls, block3Cls);
}

//列印

2019-01-11 14:58:29.922125+0800 MJ_TEST[2443:177646] a--global--0x100002520
2019-01-11 14:58:29.922498+0800 MJ_TEST[2443:177646] b--auto place--0x7ffeefbff59c
2019-01-11 14:58:29.922525+0800 MJ_TEST[2443:177646] alloc----0x100526420
2019-01-11 14:58:29.922561+0800 MJ_TEST[2443:177646] Person----0x1000024f8
2019-01-11 14:58:29.922585+0800 MJ_TEST[2443:177646] ------block---instance---
2019-01-11 14:58:29.922639+0800 MJ_TEST[2443:177646] block1----__NSGlobalBlock__ 0x1000020c0
2019-01-11 14:58:29.922666+0800 MJ_TEST[2443:177646] block2----__NSStackBlock__ 0x7ffeefbff560
2019-01-11 14:58:29.922699+0800 MJ_TEST[2443:177646] block3----__NSMallocBlock__ 0x102812000
2019-01-11 14:58:29.922717+0800 MJ_TEST[2443:177646] ------block---Class---
2019-01-11 14:58:29.922736+0800 MJ_TEST[2443:177646] block1Cls----__NSGlobalBlock__ 0x7fffb33c3460
2019-01-11 14:58:29.922756+0800 MJ_TEST[2443:177646] block2Cls----__NSStackBlock__ 0x7fffb33c3060
2019-01-11 14:58:29.922777+0800 MJ_TEST[2443:177646] block3Cls----__NSMallocBlock__ 0x7fffb33c3160
Program ended with exit code: 0

分析:

<1>Person類對象:列印出的類對象Person的地址跟全局變數a和global類型block實例對象的地址類似度極高(都以"0x100002"開頭),我們知道全局變數a和global類型block實例變數都是存放在數據區(全局區),那麼可以肯定類對象也是存放在數據區中;

<2>block類對象:通過runtime的API我們拿到了三種類型block類對象,發現類對象的地址並不以"0x100002"開頭———其中的原因我就懵逼了(記憶體地址不是很瞭解),但是可以推斷應該也是在數據區,為什麼呢?往下看

//代碼

typedef void(^Block)(void);
Block block1;

void test3()
{
    int b = 10;
    
    block1 = ^{
        NSLog(@"-----%d", b);
    };
    
    NSLog(@"%p %p", block1, object_getClass(block1));
    
}

//設置全局斷點 

//列印

2019-01-11 16:34:06.281146+0800 MJ_TEST[3354:234187] 0x7ffeefbff538 0x7fffb33c3060
2019-01-11 16:34:06.281455+0800 MJ_TEST[3354:234187] ------272632520
2019-01-11 16:34:06.281477+0800 MJ_TEST[3354:234187] 0x7ffeefbff538 0xf9552b000e0
2019-01-11 16:34:06.281496+0800 MJ_TEST[3354:234187] 0x7ffeefbff588 0x7fffb33c3060

分析:

1)作為auto類型的局部變數,age的作用域僅限於test3()函數內,所以在main函數中再去回調block時,age已經被自動釋放(所占記憶體被回收),所以age的值顯示亂碼;而同時block1其實也被銷毀了,為什麼?往下看

<1>object_getClass(block1)每次返回的值都不同,而其他只都保持不變(已經反覆run了多次);

<2>當我們第二次去回調block1時,如上報出一個很經典的錯誤——野指針調用,即指針所指向的記憶體空間已經被回收(即被釋放),但是此時並沒有對該指針賦值一個新的記憶體地址或者nil值,該指針變成了一個野指針,指向不明確;

補充:記憶體泄露:是指指針一直指向某一片記憶體空間,但是程式已經不需要再用該記憶體空間了,但其他的程式又無法調用該記憶體空間(只能開闢新的記憶體空間),這樣很容易導致記憶體爆增;所以記憶體泄露跟野指針調用是完全相反的;

記憶體溢出:是指系統分配給程式的記憶體空間不夠用,這樣也很容易導致野指針調用的問題;

<3>對block1進行copy的情形:

//代碼

void test3()
{
    int b = 10;
    
    block1 = [^{
        NSLog(@"-----%d", b);
    } copy];
    
    NSLog(@"%p %p", block1, object_getClass(block1));
    
}



int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
//        test1();
//        test2();
        test3();
        block1();
        NSLog(@"%p %p", block1, object_getClass(block1));
        
        int age = 10;
        Block bl = ^{
            NSLog(@"%d", age);
        };
        NSLog(@"%p %p", bl, object_getClass(bl));
        block1();
        block1();
        block1();
        
//        test4();
//        block();
        
    }
    return 0;
}

//列印

2019-01-11 16:40:36.144529+0800 MJ_TEST[3397:237736] 0x100526670 0x7fffb33c3160
2019-01-11 16:40:36.144849+0800 MJ_TEST[3397:237736] -----10
2019-01-11 16:40:36.144864+0800 MJ_TEST[3397:237736] 0x100526670 0x7fffb33c3160
2019-01-11 16:40:36.144893+0800 MJ_TEST[3397:237736] 0x7ffeefbff588 0x7fffb33c3060
2019-01-11 16:40:36.144916+0800 MJ_TEST[3397:237736] -----10
2019-01-11 16:40:36.144934+0800 MJ_TEST[3397:237736] -----10
2019-01-11 16:40:36.144950+0800 MJ_TEST[3397:237736] -----10
Program ended with exit code: 0

分析:copy之後,block1一直沒有被釋放(堆區需要手動管理),即block1一直指向了合法的記憶體空間,因此不會出現野指針調用的bug;

綜上:block1是一個指針變數,其指向等號右邊的代碼塊本質是一個oc對象,存放在棧區中,當回調該代碼塊時,其已經被自動釋放,但是block1因為沒有重新賦值而變成了野指針,所以block1指向的代碼塊是已經被銷毀了的;

2)block1銷毀後,新創建的bl列印出的類對象的地址跟block1銷毀前列印出的地址都是0x7fffb33c3060,因為類對象在記憶體中只有一份,據此,block1的類對象並沒有隨著block1的銷毀而銷毀,所以block的類對象不可能存在於棧區,同一個block類對象供所有創建的block實例對象的isa指針訪問並且類對象是系統自動創建並管理的,因此也不可能存在於堆區,也不會存在於代碼區

————結論:block類對象跟其他OC實例對象的類對象一樣,都只存在於數據區!!!

 

二、block拷貝是否深拷貝

//代碼

void test4()
{
    int age = 10;
    int *agePtr = &age;
    NSLog(@"age---1:\n%d %p %d %p %p", age, &age, *agePtr, agePtr, &agePtr);
    
    block1 = [^{
        NSLog(@"age----2:\n%d %p %d %p %p", age, &age, *agePtr, agePtr, &agePtr);
    } copy];
    
}

//列印

2019-01-14 09:55:33.399468+0800 MJ_TEST[907:35484] age---1:
10 0x7ffeefbff59c 10 0x7ffeefbff59c 0x7ffeefbff590
2019-01-14 09:55:33.399735+0800 MJ_TEST[907:35484] age----2:
10 0x100400238 1 0x7ffeefbff59c 0x100400230
Program ended with exit code: 0

分析:

<1>copy後,age、agePtr自身的地址值都發生了變化,說明兩個變數都從棧區拷貝到了堆區;

<2>指針變數的值不再是10而是1(亂碼),因為指針變數依然指向age拷貝前的記憶體區域,而該記憶體區隨時可能被釋放;

 

我們再看看對nsstring字元串的深拷貝(mutableCopy)和淺拷貝(copy)操作

//代碼

void test5()
{
    NSString *strSource = @"abc";
    
    NSLog(@"source:\n%@ %p %p", strSource, strSource, &strSource);
    
    NSString *str1 = [strSource copy];
    
    NSLog(@"str1:\n%@ %p %p", str1, str1, &str1);
    
    NSString *str2 = [strSource mutableCopy];
    
    NSLog(@"str2:\n%@ %p %p", str2, str2, &str2);
    
}

//列印

2019-01-14 10:14:26.299400+0800 MJ_TEST[1066:45457] source:
abc 0x1000023a0 0x7ffeefbff598
2019-01-14 10:14:26.299783+0800 MJ_TEST[1066:45457] str1:
abc 0x1000023a0 0x7ffeefbff590
2019-01-14 10:14:26.299897+0800 MJ_TEST[1066:45457] str2:
abc 0x100507170 0x7ffeefbff588
Program ended with exit code: 0

分析:

<1>很明顯,淺拷貝只拷貝了指針變數str1(從代碼區(常量區)到堆區),該指針依然指向代碼區常量abc的記憶體區;

<2>深拷貝不僅指針變數被拷貝到堆區,而且常量abc也被拷貝到了堆區;

說明:深拷貝和淺拷貝區別,見參考鏈接:https://www.jianshu.com/p/63239d4d65e0;

 

綜上所述:block的拷貝均拷貝了指針和該指針指向的值到堆區,但是新的指針卻依然指向拷貝前的記憶體區域——因此,block的copy類似於深拷貝,不完全是深拷貝!

 

三、錯誤copy

//代碼

void(^block)(void);

void test6()
{
    int age = 10;
    NSLog(@"age----%p", &age);
    
    block = ^{
        NSLog(@"age----%p", &age);
        NSLog(@"----%d", age);
    };
    
    
    
    NSLog(@"block--1---%p", block);
    NSLog(@"block class--1---%p", [block class]);
    id coBlock = [block copy];
    NSLog(@"%@", [coBlock class]);
    NSLog(@"block--2---%p", coBlock);
    NSLog(@"block class--2---%p", [coBlock class]);
}

//列印

2019-01-14 10:31:32.767665+0800 MJ_TEST[1159:53399] age----0x7ffeefbff59c
2019-01-14 10:31:32.767975+0800 MJ_TEST[1159:53399] block--1---0x7ffeefbff578
2019-01-14 10:31:32.768027+0800 MJ_TEST[1159:53399] block class--1---0x7fff8e0fe060
2019-01-14 10:31:32.768075+0800 MJ_TEST[1159:53399] __NSMallocBlock__
2019-01-14 10:31:32.768094+0800 MJ_TEST[1159:53399] block--2---0x100729380
2019-01-14 10:31:32.768111+0800 MJ_TEST[1159:53399] block class--2---0x7fff8e0fe160
2019-01-14 10:31:32.768127+0800 MJ_TEST[1159:53399] age----0x7ffeefbff598
2019-01-14 10:31:32.768141+0800 MJ_TEST[1159:53399] -----272632456
Program ended with exit code: 0

分析:

<1>block:copy前後,block的地址發生了變化,因為block從棧區被拷貝到堆區了,這一點沒問題;那麼block的類對象地址也發生了變化,因為copy前block的類型為stack類型,之後是malloc類型(系統會自動創建一個類對象),前者存放在棧區,後者存放在堆區,所以也沒問題;

<2>age:並沒有被copy 到堆區,block回調時,已經被釋放,其值為亂碼,這點沒問題;但是age的地址值這麼發生變化了?我們再往下看

//代碼

void test7()
{
    int age = 10;
    int *agePtr = &age;
    NSLog(@"1----\n%d %p %d %p %p", age, &age, *agePtr, agePtr, &agePtr);
    
    block = ^{
        NSLog(@"1----\n%d %p %d %p %p", age, &age, *agePtr, agePtr, &agePtr);
    };
    
    id coBlock = [block copy];
}

//列印

2019-01-14 10:54:30.119695+0800 MJ_TEST[1281:64385] 1----
10 0x7ffeefbff59c 10 0x7ffeefbff59c 0x7ffeefbff590
2019-01-14 10:54:30.119992+0800 MJ_TEST[1281:64385] 1----
10 0x7ffeefbff588 32766 0x7ffeefbff59c 0x7ffeefbff580
Program ended with exit code: 0

分析:

<1>不論是age還是agePtr,block回調時,本身的地址都會發生變化,因為所占記憶體都被釋放,記憶體地址不回固定,系統會重新編排(個人YY,具體不清楚);

<2>但是,儘管age的值變成亂碼,而指針變數agePtr的值卻沒變依然是原age的地址值——為什麼指針變數的記憶體值不是亂碼呢?也許是因為代碼區(常量區)跟棧區、堆區的存儲規則的區別,指針變數本身已經被釋放,其值變與不變好像沒有多大的意義——但是,從代碼規範角度,被釋放後,應當將指針變數置為nil,防止野指針調用!

結論:所謂錯誤的copy只是copy了block指針變數(等號左邊),並非block代碼塊本身(等號右邊)——因此,被引用的外部auto類型的局部變數不會被copy到堆區;

 

GitHub


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

-Advertisement-
Play Games
更多相關文章
  • 打開資料庫時報錯,提示應用程式組件中發生了無法處理的異常。如果單擊“繼續”,應用程式將忽略此錯誤並嘗試繼續。 針對此類問題的解決辦法是:將路徑C:\Documentsand Settings\Administrator\Application Data\microsoft\Microsoft SQL ...
  • 相信很多小伙伴都對【數據字典】很頭疼。 小編剛入職的時候,老大丟一個項目過來,就一個設計文檔,數據字典木有,欄位說明木有, 全部都需要靠“聯繫上下文”來猜。所以小伙伴門一定要養成說明欄位的習慣哦。 說明欄位後我們無需特意建立數據字典,直接建立一個存儲過程就可以查詢欄位意義了。 存儲過程建立如下,小伙 ...
  • 資料庫 1. 資料庫事務的 4 個特性是:原子性、一致性、持續性、隔離性 1) 原子性:事務是資料庫的邏輯工作單位,它對資料庫的修改要麼全部執行,要麼全部不執行。 2) 一致性:事務前後,資料庫的狀態都滿足所有的完整性約束。 3) 隔離性:併發執行的事務是隔離的,一個不影響一個。如果有兩個事務,運行 ...
  • 一. 單個資料庫伺服器的缺點 資料庫伺服器存在單點問題 資料庫伺服器資源無法滿足增長的讀寫請求 高峰時資料庫連接數經常超過上限 二. 如何解決單點問題 增加額外的資料庫伺服器,組建資料庫集群 同一集群中的資料庫伺服器需要具有相同的數據 集群中的任一伺服器宕機後,其它伺服器可以取代宕機伺服器 三. M ...
  • 創建頁面的兩種方式: 1.通過創建文件夾的方式創建(.wxml/.wxss/.json/.js/) 2.在app.json的"pages": []中添加路徑"pages/news/news",並保存,會自動生成(推薦) 如下: "pages": [ "pages/news/news", "pages ...
  • 本文由雲+社區發表 iOS開發過程中難免會遇到卡頓等性能問題或者死鎖之類的問題,此時如果有調用堆棧將對解決問題很有幫助。那麼在應用中如何來實時獲取函數的調用堆棧呢?本文參考了網上的一些博文,講述了使用mach thread的方式來獲取調用棧的步驟,其中會同步講述到棧幀的基本概念,並且通過對一個dem ...
  • 實際開發中都是多控制器的;用一個控制器(父)管理多個控制器(子) ios提供2個特殊的(父)控制器 UINavigationControler 簡介 導航控制器,可以輕鬆完成多個控制器之間的切換,其結構包含導航條(y=20)、棧頂控制器的view、導航控制器的view。導航控制器需要設置一個根控制器 ...
  • 在日常的開發中,多控制器之間的跳轉除了使用push的方式,還可以使用 present的方式,present控制器時,就避免不了使用 presentedViewController、presentingViewController ,這兩個概念容易混淆,簡單介紹一下。 1:present 控制器的使用 ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...