iOS-Block類型簡(jiǎn)單介紹

__block發(fā)揮作用的原理
將棧上用__block修飾的自動(dòng)變量封裝成一個(gè)結(jié)構(gòu)體,讓其在堆上創(chuàng)建,以方便從棧上或堆上訪問和修改同一份數(shù)據(jù)。

Block 幾種類型通過代碼驗(yàn)證:

沒有訪問 auto變量 的block 就是 __NSGlobalBlock

auto變量:自動(dòng)變量,離開作用域就會(huì)銷毀,一般我們創(chuàng)建的局部變量都是auto變量 ,比如 int age = 10,系統(tǒng)會(huì)在默認(rèn)在前面加上auto int age = 10
如下:

int main(int argc, char * argv[]) {
    @autoreleasepool {
        static int age = 10;
        void(^block)(void) = ^{
            NSLog(@"---block:Hello, World! %d",age);
        };
    }
    
    return  0;
}

控制臺(tái)輸出:NSGlobalBlock

訪問了auto變量 的block 就是 __NSStackBlock

如下:

int main(int argc, char * argv[]) {
    @autoreleasepool {
        int age = 10;
        void(^block)(void) = ^{
            NSLog(@"---block:Hello, World! %d",age);
        };
    }
    
    return  0;
}

打?。?/p>

2022-02-21 14:59:11.430473+0800 Test[35944:1599369] ---block class:__NSMallocBlock__

為什么打印NSMallocBlock,剛才不是說訪問了auto變量就是__NSStackBlock嗎?
因?yàn)檫@里我們使用的是ARC,在ARC環(huán)境下,Xcode編譯器再某些情況會(huì)默認(rèn)幫我們做調(diào)用copy 變成堆block ,我們?cè)贐uild Settings中把ARC設(shè)置成MRC,

2022-02-21 15:00:21.668419+0800 Test[36114:1601315] ---block class:__NSStackBlock__

使用__NSStackBlock在訪問外部變量時(shí),會(huì)有什么問題?
會(huì)出現(xiàn)野指針crash 所以在ARC壞境Xcode幫我們處理成了堆block(NSMallocBlock)防止出現(xiàn)釋放了還去訪問導(dǎo)致野指針crash

void(^testBlock)(void);
void test(){
    int age = 10;
    testBlock = ^{
        printf("---block age:%d",age);
    };
}

int main(int argc, char * argv[]) {
    @autoreleasepool {
        test();
        testBlock();
    }
    
    return  0;
}

testBock 訪問了auto變量,所以是staticBlock,是存在棧內(nèi)存上的,它捕獲的變量age 也是保存在棧上的,出了其作用域就會(huì)自動(dòng)銷毀,所以我們?cè)僬{(diào)用testBock()時(shí),訪問的是野指針,
如打印日志:

  ---block age:-303768616 (野指針)

當(dāng)一個(gè)__NSStackBlock調(diào)用了copy操作,返回的就是一個(gè)__NSMallocBlock

int main(int argc, char * argv[]) {
    @autoreleasepool {
        int age = 10;
        void(^block)(void) = [^{
            NSLog(@"---block:Hello, World! %d",age);
        } copy];
        NSLog(@"---block class:%@",[block class]);
        
    }
    
    return  0;
}

日志:

2022-02-21 15:29:37.848290+0800 WidgetTest[37740:1628194] ---block class:__NSMallocBlock__

注意以上都是在MRC 下。

Block為什么使用copy修飾?

a、block在創(chuàng)建的時(shí)候默認(rèn)分配的內(nèi)存是在棧上,而不是在堆上。這樣的話其本身的作用域是屬于創(chuàng)建時(shí)候的作用域,一旦在創(chuàng)建的作用域之外調(diào)用就會(huì)導(dǎo)致程序的崩潰。所以使用了copy將其拷貝到堆內(nèi)存上。
b、block創(chuàng)建在棧上,而block的代碼中可能會(huì)用到本地的一些變量,只有將其拷貝到堆上,才能用這些變量

在ARC下 編譯器會(huì)根據(jù)情況自動(dòng)將棧上的block復(fù)制到堆上,
比如以下幾種情況:

1.block作為函數(shù)返回值時(shí)

typedef void(^testBlock)(void);
testBlock test(){
    int age = 10;
    return ^{
        NSLog(@"---block age:%d",age);
    };
//MRC 正確的用法
/*
testBlock = [^{
        printf("---block age:%d",age);
    } copy];

*/
    
}

int main(int argc, char * argv[]) {
    @autoreleasepool {
        testBlock block = test();
        block();
        NSLog(@"---block class: %@",[block class]);
        
    }
    
    return  0;
}

testBock 訪問了auto變量,所以是staticBlock,是存在棧內(nèi)存上的,它捕獲的變量age 也是保存在棧上的,
但是由于是在ARC 環(huán)境下,編譯器自動(dòng)將棧上的block復(fù)制到堆上
日志

2022-02-21 15:38:57.650081+0800 WidgetTest[38392:1637183] ---block age:10
2022-02-21 15:38:57.650534+0800 WidgetTest[38392:1637183] ---block class: __NSMallocBlock__

2. 將block賦值給__strong指針時(shí)

int main(int argc, char * argv[]) {
    @autoreleasepool {
        int age = 10;
        testBlock block = ^{
            NSLog(@"---block age:%d",age);
        };
        block();
        NSLog(@"---block class: %@",[block class]);
        
    }
    
    return  0;
}

testBock 訪問了auto變量,所以是staticBlock,是存在棧內(nèi)存上的,它捕獲的變量age 也是保存在棧上的,但是被強(qiáng)指針引用了, 在ARC 環(huán)境下,編譯器自動(dòng)將棧上的block復(fù)制到堆上

日志:

2022-02-21 15:46:59.251046+0800 WidgetTest[38850:1644202] ---block age:10
2022-02-21 15:46:59.251656+0800 WidgetTest[38850:1644202] ---block class: __NSMallocBlock__

3.block作為Cocoa API方法名含有UsingBlock的方法參數(shù)時(shí)

 NSArray *arr = @[];
        [arr enumerateObjectsUsingBlock:^(id  _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
                    
        }];

4.block作為GCD API的方法參數(shù)時(shí)

 static dispatch_once_t onceToken;
 dispatch_once(&onceToken, ^{
            
   });

dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 1*NSEC_PER_SEC), dispatch_get_main_queue(), ^{
            
 });

5.block調(diào)用copy方法

總結(jié):

1:一共有三種類型的Block.分為__NSGlobalBlock,__NSStackBlock,__NSMallocBlock.
沒有訪問 auto變量 的block 就是 __NSGlobalBlock
訪問了auto變量 的block 就是 __NSStackBlock
當(dāng)一個(gè)__NSStackBlock調(diào)用了copy操作,返回的就是一個(gè)__NSMallocBlock
2:在ARC環(huán)境下,編譯器會(huì)自動(dòng)把棧上的block copy到堆上

關(guān)于強(qiáng)弱引用

只要Block在棧上 不管是ARC 還是 MRC 都不會(huì)對(duì)捕獲的auto變量進(jìn)行強(qiáng)引用或者retain操作

當(dāng)block被拷貝到堆上時(shí):

1.會(huì)調(diào)用Block內(nèi)部copy函數(shù) 這個(gè)函數(shù)內(nèi)部會(huì)調(diào)用_Block_object_assign函數(shù)

2._Block_object_assign函數(shù)會(huì)對(duì)__block變量形成強(qiáng)引用(retain) (__block修飾的變量也會(huì)被拷貝到堆上)

3.只要是被__block修飾的變量 copy后都是強(qiáng)引用 如果沒被__block修飾的對(duì)象 會(huì)根據(jù)對(duì)象的修飾符來確定是強(qiáng)引用還是弱引用

當(dāng)Block從堆中移除的時(shí)候:

如果Block從堆上移除 會(huì)調(diào)用block內(nèi)部的dispose函數(shù) dispose函數(shù)內(nèi)部會(huì)調(diào)用_Block_object_dispose函數(shù) 這個(gè)函數(shù)會(huì)自動(dòng)斷開引用的auto變量(斷開這個(gè)引用) 相當(dāng)于release

block內(nèi)部的copy函數(shù)和dispose函數(shù) 只會(huì)在捕獲對(duì)象auto變量的時(shí)候才有(因?yàn)閷?duì)象需要內(nèi)存管理) 捕獲簡(jiǎn)單的數(shù)據(jù)變量比如Int的時(shí)候 是沒有的

Tip:修改為MRC,
整個(gè)項(xiàng)目修改:在Targets->Build Settings->Apple Clang-Language-Objective-C-> Objective-C Automatic Reference Counting 為YES,
也可以對(duì)單個(gè)文件設(shè)置,還可以在Targets->Build Phases->Compils Source中設(shè)置某個(gè)文件的Compilter Flags 為-fno-objc-arc

參考:
http://www.itdecent.cn/p/31f10267d6c7
https://www.cnblogs.com/junhuawang/p/14951598.html
http://www.itdecent.cn/p/0a555501ade3
https://www.cnblogs.com/huanying2000/p/13950221.html

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容