OC block使用及底層原理、循環(huán)引用、__weak、__strong

本文主要總結(jié)了:

  1. block的基本語(yǔ)法
    有/無(wú)返回值和形參
    typedef定義block類(lèi)型
  2. 變量訪(fǎng)問(wèn),__block底層原理
  3. block的3種類(lèi)型
  4. 什么時(shí)候會(huì)觸發(fā)block的copy
  5. block的循環(huán)引用
    什么是循環(huán)引用
    block循環(huán)引用例子
    檢測(cè)工具instruments-Leaks
  6. __weak
    為什么__weak可以打破循環(huán)引用?
    常見(jiàn)使用
  7. __strong
    使用場(chǎng)景,例子(延遲函數(shù))

一、block基本語(yǔ)法

block用來(lái)保存一段代碼,封裝代碼段,在需要的時(shí)候用。
block的標(biāo)志:^

1. 沒(méi)有返回值和形參時(shí),可以省略后面的()

void (^myBlock) ()= ^ {
    //要保存的代碼段
};

// 調(diào)用方式與函數(shù)一樣:
myBlock();

2. 有返回值和形參

int (^sumBlock) (int , int) = ^(int a , int b){
    return a+b;
};
int c = sumBlock(10,12);

對(duì)比函數(shù)指針:

int sum(int a, int b){
    return a+b;
}

//調(diào)用:
int (*p)(int, int)=sum;
int d = p(10,12);
  • 再來(lái)一個(gè)例子:
void (^lineBlock)(int) = ^(int n){
      for(int i=0; i<n; i++){
        NSLog(@“_______”);
      }
};
lineBlock(5);

3. 使用typedef定義block類(lèi)型

//定義MyBlock是一個(gè)傳兩個(gè)int類(lèi)型的參數(shù),返回一個(gè)int的代碼塊

typedef  int (^MyBlock)(int, int);

這時(shí)
int (^sumBlock) (int , int) = ^(int a , int b){
return a+b;
};
int c = sumBlock(10,12);
就可以寫(xiě)成:

MyBlock sumBlock=^(int a, int b){
    return a+b;
};
sumBlock(1, 2);

二、變量訪(fǎng)問(wèn)

  • 在block中可以使用和改變全局變量
  • block內(nèi)部可以訪(fǎng)問(wèn)外面的變量
  • 局部變量,可以使用,不能改變
    本地變量,代碼塊會(huì)在定義的時(shí)候復(fù)制并保存他們的狀態(tài),作為常量獲取到
typedef double (^MulBlock)(void);
double a=10,b=20;
MulBlock b=^(void){
    return a*b;
}
a=20;
b=50;
NSLog(@“%f”,b(a, b));//此時(shí)還是會(huì)輸出200

*可以通過(guò)將變量標(biāo)記為全局(static)解決

  • 默認(rèn)情況下,block內(nèi)部不能修改外面的局部變量
  • 給局部變量加上_blcok關(guān)鍵字,這個(gè)局部變量就可以在block內(nèi)部捕捉到變量,并可以進(jìn)行修改
__block int b=20;

【注意】__block修飾時(shí),底層實(shí)現(xiàn)是將變量從??截愐环莸蕉阎?,從而獲得使用權(quán),用代碼打印一下:

        __block int a = 0;
        NSLog(@"a address %p",&a);
        void (^function)()=^(){
            NSLog(@"a address %p",&a);
            a = 100;
            NSLog(@"1 %d",a);
        };
        a = 20;
        function();
        NSLog(@"a address %p",&a);
        NSLog(@"2 %d",a);
//打印結(jié)果:
//  a address 0x7ffeefbff5d8,還在棧中
//  a address 0x10060fff8,拷貝到了堆中
//  1 100
//  a address 0x10060fff8
//  2 100,在block中對(duì)a修改成功
  • __block int a = 0;對(duì)a的內(nèi)存地址進(jìn)行修改。從棧copy到堆中,此時(shí)不會(huì)被隨意銷(xiāo)毀。
  • 棧的區(qū)間一般來(lái)說(shuō)是2M,變化區(qū)間過(guò)大內(nèi)存地址會(huì)發(fā)生變化,由高地址跑到低地址,進(jìn)入堆中。

附上內(nèi)存中堆棧的關(guān)系圖(參考了_block關(guān)鍵字的實(shí)現(xiàn)原理):

無(wú)論訪(fǎng)問(wèn)的是棧里的__block還是堆里的__block都是以val->__forwarding的形式訪(fǎng)問(wèn),訪(fǎng)問(wèn)的都是堆上的__block所以地址改變值也修改成功


三、block的3種類(lèi)型:

1. NSGlobalBlock,全局block:靜態(tài)

  • 位于全局區(qū)
  • 在block內(nèi)部不使用外部變量,或者只使用靜態(tài)變量和全局變量
//例子1
- (void)viewDidLoad {
        // main thread [block copy]
        void (^block)(void) = ^{
                NSLog(@"hello block");
        }; //匿名函數(shù)
        block();
        NSLog(@"%@",block);  
        //萬(wàn)物皆對(duì)象   <__NSGlobalBlock__: 0x108656088>
        //雖然賦值給了強(qiáng)引用對(duì)象“block”(默認(rèn)__strong修飾),但是沒(méi)有使用外部變量,所以是globalBlock
}
//例子2
- (void)viewDidLoad {
        NSLog(@"%@",^{
        
        });
        //<__NSGlobalBlock__: 0x100001028>
        //沒(méi)有使用外部變量,所以還是globalBlock
}

2. NSMallocBlock,堆block

  • 位于堆區(qū)
  • 在block內(nèi)部使用局部變量或者OC屬性,并且賦值給強(qiáng)引用或者Copy修飾的變量
- (void)viewDidLoad {
        int a = 10;  //捕獲外部變量
        //下面括號(hào)里的block,默認(rèn)是用__strong修飾的強(qiáng)引用
        void (^block)(void) = ^{
                //訪(fǎng)問(wèn)內(nèi)存空間訪(fǎng)問(wèn)、捕獲外部變量
                NSLog(@"hello block %d",a); 
        }; //匿名函數(shù)
        block();
        NSLog(@"%@",block);  //<__NSMallocBlock__: 0x6000025c4ba0>
}

3. NSStackBlock,棧block

  • 位于棧區(qū)
  • 與mallocBlock一樣,可以在內(nèi)部使用局部變量或者OC屬性。但是!不能賦值給強(qiáng)引用或Copy修飾的變量。
//例子1
- (void)viewDidLoad {
        int a = 10;
        NSLog(@"%@",^{
                NSLog(@"%d",a);  //<__NSStackBlock__: 0x7ffee71e2930>
        });  
}
//例子2
- (void)viewDidLoad {
        int a  = 10;
        //不寫(xiě)"__weak"時(shí)默認(rèn)是__strong修飾
        void(^__weak block)(void) = ^{
            NSLog(@"%d",a);
        };
        NSLog(@"%@",block);  //<__NSStackBlock__: 0x7ffeefbff4c8>
}

四、什么情況下會(huì)觸發(fā)block的Copy

  1. 手動(dòng)copy?
- (void)viewDidLoad {
        int a = 10;
        void (^__weak myBlock)(void) = ^{
                NSLog(@"%d",a);
        }; 
        NSLog(@"%@",myBlock); //<__NSStackBlock__: 0x7ffeefbff4c8>
        NSLog(@"%@",[myBlock copy]);  //<__NSMallocBlock__: 0x1007003f0>
}
  1. Block作為返回值? 不一定會(huì)觸發(fā)copy
- (void)viewDidLoad {
        NSLog(@"%@", [self returnBlock]);
        //因?yàn)橄旅娣椒ㄖ械腷lock,并沒(méi)有使用局部變量,仍然是globalBlock
}

- (void(^)(void))returnBlock {
        return ^{

        };
}
  1. 被強(qiáng)引用或者Copy修飾?也要符合“在block內(nèi)部使用了局部變量”
  2. 系統(tǒng)API包含usingBlock
  • NSArray

五、block的循環(huán)引用

1. 什么是循環(huán)引用?

在堆上的對(duì)象與堆上的對(duì)象互相強(qiáng)引用造成的環(huán)。
可能是兩個(gè)對(duì)象,可能是N個(gè)對(duì)象

2. block循環(huán)引用例子
  • 例子1
    person->block, block->person
@interface Person : NSObject
@property (nonatomic, copy) void(^block)(void);
@end

int main(int argh, const char * argue[]) {
        Person *person = [Person new];
        person.block = ^{  //賦值時(shí)person持有了block
                NSLog(@"%@", person); //block內(nèi)部又捕獲person,引用+1
        };
        return 0;
}
  • 例子2
    在最外層ViewController中,創(chuàng)建BlockCoat類(lèi),BlockCoat中有有一個(gè)屬性是BlockTest類(lèi),BlockTest類(lèi)中有一個(gè)屬性是block。
    在ViewController中,調(diào)用BlockCoat的方法,通過(guò)方法調(diào)用完之后,BlockTest有無(wú)執(zhí)行dealloc方法,來(lái)判斷是否存在循環(huán)引用。
//  ViewController.m
#import "BlockCoat.h"

@implementation ViewController
- (void)viewDidLoad {
    NSLog(@"-----lychee1----");
    BlockCoat *bc = [BlockCoat new];
    [bc test];  //在最外層的vc中測(cè)試,blockTest是否能正常dealloc
    NSLog(@"-----lychee2----");
}
//  BlockCoat.h
#import <Foundation/Foundation.h>
@class BlockTest;

@interface BlockCoat : NSObject
@property(nonatomic, strong) BlockTest *bt;

- (void)test;
@end
//  BlockCoat.m
#import "BlockCoat.h"

@implementation BlockCoat

- (void)test {
    int a = 10;
    self.bt = [BlockTest new]; //self(blockCoat)持有了blockTest
    __weak typeof (self) weakSelf = self;
    [self.bt testBlock:^BOOL{  //參數(shù)block中,捕獲了self
        NSLog(@"%@",self);
 //    NSLog(@"%@", weakSelf);
        NSLog(@"%d",a);
        return YES;
    }];
}

@end
//  BlockTest.h
#import <Foundation/Foundation.h>

typedef BOOL(^myBlock)(void);

@interface BlockTest : NSObject
@property(nonatomic, copy) myBlock b;

- (BOOL)testBlock:(myBlock)block;

@end
//  BlockTest.m
#import "BlockTest.h"

@implementation BlockTest

- (BOOL)testBlock:(myBlock)block {
    //還未進(jìn)行賦值copy,傳進(jìn)來(lái)的block有捕獲外部變量(self:BlockCoat),打印出來(lái)是stackBlock (不捕獲外部變量時(shí)是globalBlock)
    NSLog(@"33---%@",block); 
    self.b = block;
    //賦值給了copy修飾的b屬性,此時(shí)BlockTest持有了block,有捕獲外部變量,打印的是mallocBlock
    NSLog(@"44---%@",self.b); 
    return block();
}

- (void)dealloc {
    NSLog(@"dealloc~~");
}

@end

由于在BlockCoat.m中,持有了BlockTest,同時(shí)block捕獲了self(即BlockCoat);
下面的BlockTest.m中,持有了block,變?yōu)榱薽allocBlock;
此時(shí)就造成了循環(huán)引用:BlockCoat->BlockTest->block->BlockCoat

打破循環(huán)引用的方法?
在BlockCoat.m中,調(diào)用方法的block中,self換成weakSelf

什么時(shí)候不存在循環(huán)引用?(打破循環(huán)鏈中的任一環(huán)節(jié))
(1)BlockCoat中,不把BlockTest作為屬性賦值,用到的時(shí)候直接創(chuàng)建
(2)或者,BlockTest中,直接調(diào)用傳來(lái)的block,不要賦值給自己的屬性(持有)
(3)或者,block中,不捕獲self(BlockCoat)
當(dāng)不存在循環(huán)引用時(shí),就不需要寫(xiě)__weak,直接用self

從上面這個(gè)例子中聯(lián)想,如果BlockTest是系統(tǒng)框架中的類(lèi),那么在我們自定義的類(lèi)中,如果持有了系統(tǒng)類(lèi)對(duì)象,并且傳參的block中捕獲了self,就要特別注意測(cè)試是否存在循環(huán)引用。
如果能夠確定系統(tǒng)庫(kù)/三方庫(kù)的類(lèi)里,沒(méi)有對(duì)block進(jìn)行持有,例如masonry,只執(zhí)行了block方法,那么就不會(huì)產(chǎn)生循環(huán)引用。

  • 例子3
    self -> _source -> block -> self
@interface ViewController()
@property (nonatomic, strong) dispatch_source_t source;
@end

@implementation ViewController
- (void)viewDidLoad {
        [self dispatch_source_block];
}

- (void)dispatch_source_block {
    _source = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, dispatch_get_main_queue());
    dispatch_source_set_timer(_source, dispatch_time(DISPATCH_TIME_NOW, 0), 1, 3);
    dispatch_block_t block = ^{
        NSLog(@"%@",self);
        NSLog(@"12321");
    };
    dispatch_source_set_event_handler(_source, block); //_source持有block
    dispatch_activate(_source);
}

- (void)dealloc{
    NSLog(@"dealloc----");
}
3. 循環(huán)引用檢測(cè)工具

instruments:Leaks

  • 菜單欄 Xcode->Open developer tool->instruments
  • 選擇Blank
  • 點(diǎn)右上角“+”號(hào),下拉框中選Leaks
  • 左上角選擇要運(yùn)行的應(yīng)用程序,點(diǎn)擊紅點(diǎn)開(kāi)始

有紅色?說(shuō)明檢測(cè)到內(nèi)存泄露,根據(jù)下面顯示的信息,具體排查問(wèn)題。
(這里只是簡(jiǎn)單使用,具體還待進(jìn)一步對(duì)工具進(jìn)行學(xué)習(xí)、總結(jié)。)


instruments工具界面

六、 __weak 打破循環(huán)引用

為什么__weak可以打破循環(huán)引用?
(1)weak的作用:把self加入sideTables的弱引用表中,不對(duì)它產(chǎn)生強(qiáng)引用,在使用的時(shí)候取出來(lái)。當(dāng)self的強(qiáng)引用計(jì)數(shù)變?yōu)?時(shí),將其從表中移除,并自動(dòng)置為nil
(2)mallocBlock捕獲__weak修飾的變量時(shí),捕獲進(jìn)內(nèi)部的也是用__weak修飾。實(shí)際是在block內(nèi)部另外定義了一個(gè)__weak修飾的局部變量,指向外部的變量。

//例如這么寫(xiě)
 __weak typeof (self) weakSelf = self;
void(^block)(void) = ^{
        weakSelf;
};

//在底層,實(shí)際上,block內(nèi)部新建了__weak修飾的局部變量,來(lái)指向weakSelf
 __weak typeof (self) weakSelf = self;
void(^block)(void) = ^{
        //即 __weak NSObject *a = weakSelf;
        __weak typeof(weakSelf) a = weakSelf;
        a;
};

【tips】經(jīng)常用到的宏定義弱引用:

//用weakState(weakVar, strongVar)來(lái)指代后面的__weak typeof(strongVar) weakVar = strongVar
#define weakState(weakVar, strongVar) __weak typeof(strongVar) weakVar = strongVar;
//__weak表示修飾詞,typeof()獲取原始變量的類(lèi)型,定義同類(lèi)型的新變量weakVar
//將原始變量strongVar,賦值給weakVar
__weak typeof(strongVar) weakVar = strongVar;

此時(shí),在需要弱引用的地方只需要寫(xiě):
weakState(weakSelf, self);
后文中,就可以拿weakSelf來(lái)用

七、 __strong

  • 應(yīng)用場(chǎng)景:希望對(duì)象存活的時(shí)間久一點(diǎn)(持續(xù)到block結(jié)束),一般用在異步回調(diào)block中,防止執(zhí)行block時(shí),引用的對(duì)象已經(jīng)被釋放。OC中給nil發(fā)送消息是不響應(yīng)的。
  • 以延時(shí)函數(shù)為例
    (1)不存在循環(huán)引用的情況:
    block中的self,延遲函數(shù)延遲2秒后才釋放
    換成weakSelf,self將立即釋放,不會(huì)延遲2秒
- (void)viewDidLoad {
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        [self tp]; //這里沒(méi)有循環(huán)引用,直接使用self即可
        //如果使用了weakSelf,由于沒(méi)有強(qiáng)引用self,self釋放掉之后,weakSelf就指向了nil,不會(huì)執(zhí)行tp方法。
        //[weakSelf tp];
        NSLog(@"---延遲2秒");
    });
}

- (void)tp{
    NSLog(@"hahaha-----%@",self);
}

(2)存在循環(huán)引用,且block嵌套的情況

  • 因?yàn)閟elf->myBlock->self,所以myBlock中用到self的地方要用weakSelf替代
  • 由于延遲函數(shù),要執(zhí)行self的tp方法,就要求self要能夠延遲2秒后再釋放,這時(shí)就需要用到__strong。但是__strong是寫(xiě)在myBlock中呢?還是dispatch_after中呢?

先寫(xiě)答案,要寫(xiě)在myBlock中,然后在dispatch_after中捕獲外層的strongSelf。

- (void)viewDidLoad {
    __weak typeof (self) weakSelf = self;
    self.myBlock = ^(int a) {
        __strong typeof(weakSelf) strongSelf = weakSelf; //正確寫(xiě)法
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
//            __strong typeof(weakSelf) strongSelf = weakSelf; //錯(cuò)誤寫(xiě)法
            [strongSelf tp];
            NSLog(@"---延遲2秒");
        });
        [weakSelf tp];
    };
    self.myBlock(0);
}

原因是:變量在block中是層層傳遞的。

結(jié)合上個(gè)小節(jié)中block捕獲__weak變量的原理,可以畫(huà)出下圖:
當(dāng)self生命周期結(jié)束釋放時(shí),self、weakSelf變?yōu)閚il,但myBlock中的a由于有被強(qiáng)引用,暫時(shí)不會(huì)釋放,dispatch_after中捕獲的strongSelf也還存活,直到延時(shí)函數(shù)執(zhí)行完,再釋放。

而如果__strong寫(xiě)在dispatch_after的block中,則會(huì)變成:

當(dāng)self釋放時(shí),self、weakSelf、a由于沒(méi)有被強(qiáng)引用都將被釋放變?yōu)閚il,此時(shí)di spatch_after捕獲的變量a2、strongSelf也是nil,無(wú)法執(zhí)行對(duì)應(yīng)方法

【延伸】
后面這種寫(xiě)法,雖然無(wú)法“延長(zhǎng)變量存活時(shí)間”,但反其道行之,既然正常不存在循環(huán)引用的情況下,不會(huì)執(zhí)行對(duì)應(yīng)方法。那么如果方法有執(zhí)行,就說(shuō)明存在循環(huán)引用?
FBRetainCycleDetector + MLeaksFinder 閱讀中,指出MLeaksFinder關(guān)鍵源碼中,就有這樣的寫(xiě)法:

- (BOOL)willDealloc {
     NSString *className = NSStringFromClass([self class]);
     if ([[NSObject classNamesWhitelist] containsObject:className]) return NO; 
     NSNumber *senderPtr = objc_getAssociatedObject([UIApplication sharedApplication], kLatestSenderKey);
     if ([senderPtr isEqualToNumber:@((uintptr_t)self)]) return NO; 
      __weak id weakSelf = self;
     dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        __strong id strongSelf = weakSelf;
       [strongSelf assertNotDealloc]; 
        //此處,如果方法有響應(yīng),說(shuō)明對(duì)象沒(méi)有正常釋放,存在內(nèi)存泄露情況??!
     });
     return YES;
}

(flag: MLeaksFinder源碼之后抽空學(xué)習(xí)總結(jié)!)

以上~如有錯(cuò)誤還望不吝指出。

最后編輯于
?著作權(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)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

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

  • Block使用場(chǎng)景,可以在兩個(gè)界面的傳值,也可以對(duì)代碼封裝作為參數(shù)的傳遞等。用過(guò)GCD就知道Block的精妙之處。...
    Coder_JMicheal閱讀 814評(píng)論 2 1
  • 前言 Blocks是C語(yǔ)言的擴(kuò)充功能,而Apple 在OS X Snow Leopard 和 iOS 4中引入了這...
    小人不才閱讀 3,852評(píng)論 0 23
  • 1. Java基礎(chǔ)部分 基礎(chǔ)部分的順序:基本語(yǔ)法,類(lèi)相關(guān)的語(yǔ)法,內(nèi)部類(lèi)的語(yǔ)法,繼承相關(guān)的語(yǔ)法,異常的語(yǔ)法,線(xiàn)程的語(yǔ)...
    子非魚(yú)_t_閱讀 34,638評(píng)論 18 399
  • iOS代碼塊Block 概述 代碼塊Block是蘋(píng)果在iOS4開(kāi)始引入的對(duì)C語(yǔ)言的擴(kuò)展,用來(lái)實(shí)現(xiàn)匿名函數(shù)的特性,B...
    smile刺客閱讀 2,467評(píng)論 2 26
  • 摘要 Blocks是C語(yǔ)言的擴(kuò)充功能, iOS 4中引入了這個(gè)新功能“Blocks”,那么block到底是什么東西...
    CholMay閱讀 1,310評(píng)論 2 10

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