探尋Block的本質(5)—— 對象類型的變量捕獲

Block傳送門🦋🦋🦋

探尋Block的本質(1)—— 基本認識ios

探尋Block的本質(2)—— 底層結構markdown

探尋Block的本質(3)—— 基礎類型的變量捕獲iphone

探尋Block的本質(4)—— Block的類型函數

探尋Block的本質(6)—— __block的深刻分析post

首先咱們來看這麼一段代碼案例ui

*********************CLPerson.h*********************
#import <Foundation/Foundation.h>

@interface CLPerson : NSObject
@property (nonatomic,assign) int age;
@end


*********************CLPerson.m*********************
#import "CLPerson.h"

@implementation CLPerson
-(void)dealloc {
    NSLog(@"%s",__func__);
}
@end

*********************main.m*********************
#import <Foundation/Foundation.h>
#import "CLPerson.h"

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        {//臨時做用域開始
            CLPerson *person = [[CLPerson alloc] init];
            person.age = 10;
        }//臨時做用域結束
        
        NSLog(@"-----------flag1");
    }
    return 0;
}

複製代碼

image 經過在打印標記flag1處斷點調試可看出,在臨時做用域裏面的person對象只要出了做用域就會被釋放,這一點是很好理解的。atom

上面的代碼加入block,調整以下spa

*********************CLPerson.h*********************
#import <Foundation/Foundation.h>

@interface CLPerson : NSObject
@property (nonatomic,assign) int age;
@end


*********************CLPerson.m*********************
#import "CLPerson.h"

@implementation CLPerson
-(void)dealloc {
    NSLog(@"%s",__func__);
}
@end

*********************main.m*********************
#import <Foundation/Foundation.h>
#import "CLPerson.h"

typedef void(^CLBlock)(void);//➕➕➕

int main(int argc, const char * argv[]) {
    @autoreleasepool {
	
		CLBlock myBlock;//➕➕➕
        {//臨時做用域開始
            CLPerson *person = [[CLPerson alloc] init];
            person.age = 10;

			 myBlock = ^{//➕➕➕
                NSLog(@"---------%d",person.age);
            };
        }//臨時做用域結束
        
        NSLog(@"-----------flag1");
    }
    return 0;
}
複製代碼

再次在打印標記flag1處斷點調試運行一下 在這裏插入圖片描述 結果告訴咱們,出了臨時做用域,person對象沒有被釋放。這裏有兩個注意點:3d

  • 因爲如今是ARC環境,myBlock屬於強指針,所以在將block對象賦值給myBlock指針的時候,編譯器會自動對block對象執行copy操做,所以賦值完成後,myBlock指向的是一個堆空間上的block對象副本。

順便,經過終端命令xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m -o main.cpp,拿到編譯後的.cpp文件,咱們先查看一下當前代碼編譯底層樣式,經整理簡化後以下指針

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  CLPerson *person;

  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, CLPerson *_person, int flags=0) : person(_person) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};


static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  CLPerson *person = __cself->person; // bound by copy

                NSLog((NSString *)&__NSConstantStringImpl__var_folders_7__p19yp82j0xd2m_1k8fpr77z40000gn_T_main_2cca58_mi_0,((int (*)(id, SEL))(void *)objc_msgSend)((id)person, sel_registerName("age")));
            }



static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {
            _Block_object_assign((void*)&dst->person, (void*)src->person, 3/*BLOCK_FIELD_IS_OBJECT*/);
}




static void __main_block_dispose_0(struct __main_block_impl_0*src) {
            _Block_object_dispose((void*)src->person, 3/*BLOCK_FIELD_IS_OBJECT*/);
}




static struct __main_block_desc_0 {
  size_t reserved;
  size_t Block_size;
  void (*copy)(struct __main_block_impl_0*, struct __main_block_impl_0*);
  void (*dispose)(struct __main_block_impl_0*);
} __main_block_desc_0_DATA = { 
                                0, 
                                sizeof(struct __main_block_impl_0), 
                                __main_block_copy_0, 
                                __main_block_dispose_0
                              };



int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
        CLBlock myBlock;

        {
            CLPerson * person = objc_msgSend(objc_msgSend(objc_getClass("CLPerson"), 
                                                          sel_registerName("alloc")
                                                          ), 
                                             sel_registerName("init")
                                             );
            

            objc_msgSend(person, 
                         sel_registerName("setAge:"), 
                         30
                         );


            myBlock = objc_msgSend(&__main_block_impl_0(__main_block_func_0, 
                                                        &__main_block_desc_0_DATA, 
                                                        person, 
                                                        570425344), 
                                   sel_registerName("copy")
                                   );


        }


    }


    return 0;
}
複製代碼

以上是 【ARC環境-->堆上的block-->強指針CLPerson *person 】所對應的運行結果以及底層實現。咱們發現於以前捕獲一個基本類型的auto變量所不一樣的是,當block捕捉對象類型的auto變量的時候,__main_block_desc_0結構體裏面多了兩個彩蛋

  • 函數指針copy,也就是__main_block_copy_0(),內部調用了_Block_object_assign()
  • 函數指針dispose,也就是__main_block_dispose_0(),內部調用了_Block_object_dispose()

捕捉對象類型的auto變量時__main_block_desc_0的變化 這裏還須要注意的是,ARCCLPerson *person被認爲是強指針,等價於_strong CLPerson *person,而弱指針須要顯式地表示爲__weak CLPerson *person。經過終端命令xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc -fobjc-arc -fobjc-runtime=ios-9.0.0 main.m -o main.cpp,能夠看到block的內捕獲到的person指針以下 image

爲了對比,咱們再分別看一下下面三種 場景分別是什麼狀況的:

  • ARC環境-->堆上的block-->弱指針__weak CLPerson *person
  • ARC環境-->棧上的block-->強指針CLPerson *person
  • ARC環境-->棧上的block-->弱指針__weak CLPerson *person


ARC環境-->堆上的block-->弱指針__weak CLPerson *person】 案例以下

***********************main.m*************************
#import <Foundation/Foundation.h>
#import "CLPerson.h"

typedef void(^CLBlock)(void);


int main(int argc, const char * argv[]) {
    @autoreleasepool {
        CLBlock myBlock;
        
        {//臨時做用域開始
            __weak CLPerson * person = [[CLPerson alloc] init];
            person.age = 30;
            
            myBlock = ^{
                NSLog(@"---------%d",person.age);
            } ;
            
        }//臨時做用域結束

        NSLog(@"-------------");
        
    }
    
    NSLog(@"------main autoreleasepool end-------");
    
    return 0;
}

複製代碼

block的底層結構以下 image ARC->堆block->弱指針運行結果 運行結果顯示堆上的block使用弱指針__weak CLPerson *person,沒有影響person所指向對象的生命週期,出了臨時做用域的以後就被釋放了。



ARC環境-->棧上的block-->強指針CLPerson *person

***********************main.m*************************
#import <Foundation/Foundation.h>
#import "CLPerson.h"

typedef void(^CLBlock)(void);


int main(int argc, const char * argv[]) {
    @autoreleasepool {
        CLBlock myBlock;
        
        {//臨時做用域開始
            CLPerson * person = [[CLPerson alloc] init];
            person.age = 30;
            
            ^{
                NSLog(@"---------%d",person.age);
            } ;
            
        }//臨時做用域結束

        NSLog(@"-------------");
        
    }
    
    NSLog(@"------main autoreleasepool end-------");
    
    return 0;
}
複製代碼

block底層結構以下 image

ARC->棧block->強指針運行結果

運行結果顯示棧上的block使用強指針 CLPerson *person,沒有影響person所指向對象的生命週期,出了臨時做用域的以後就被釋放了。



ARC環境-->棧上的block-->弱指針__weak CLPerson *person

***********************main.m*************************
#import <Foundation/Foundation.h>
#import "CLPerson.h"

typedef void(^CLBlock)(void);


int main(int argc, const char * argv[]) {
    @autoreleasepool {
        CLBlock myBlock;
        
        {//臨時做用域開始
            __weak CLPerson * person = [[CLPerson alloc] init];
            person.age = 30;
            
            ^{
                NSLog(@"---------%d",person.age);
            } ;
            
        }//臨時做用域結束

        NSLog(@"-------------");
        
    }
    
    NSLog(@"------main autoreleasepool end-------");
    
    return 0;
}

複製代碼

block底層結構爲 image

ARC->棧block->弱指針運行結果

運行結果顯示棧上的block使用弱指針__weak CLPerson *person,沒有影響person所指向對象的生命週期,出了臨時做用域的以後就被釋放了。



從上面的四種狀況的對比看出,只有堆上block使用強指針CLPerson *person的時候,纔會影響該指針所指對象的生命週期,這是怎麼一回事呢?

這個時候就要回到咱們上面發現的那兩個彩蛋了】也就是__main_block_desc_0中的函數指針copydispose。實際上這兩個函數指針分別是在block從棧空間拷貝到堆空間的時候,以及堆空間的block被釋放的時候使用的。

block從棧拷貝到堆時,系統會經過block中的函數指針copy調用函數__xxx_block_copy_x

__xxx_block_copy_x(struct __main_block_impl_0*dst, 
				   struct __main_block_impl_0*src
				   );
複製代碼

注意這個函數的兩個參數,

  • 第一個dst表明拷貝以前棧空間的block
  • 第二個src表明拷貝以後堆空間上的block

這個函數裏面調用的是_Block_object_assign函數

_Block_object_assign((void*)&dst->person, 
					 (void*)src->person, 
					 3/*BLOCK_FIELD_IS_OBJECT*/
					 );
複製代碼

這個函數的做用是把dst(棧block)內部所捕獲的那個person對象(指針)的值賦值(assign)給src(堆block)內的person指針。 若是是ARC環境,會根據person的強弱性(也就是以前的修飾詞 __weak__strong)來決定src(堆block)是否持有對象。若是person是被__strong修飾,就會經過 [person retain],使得person所指向的對象的引用計數+1,這樣,src(堆block)就持有了(也就是強引用person所指向的對象。

當堆上的block即將被釋放的時候,系統會經過block中的函數指針dispose調用函數__xxx_block_dispose_x

__main_block_dispose_0(struct __main_block_impl_0*src);
複製代碼

很明顯其中的參數src就是堆上的block,該函數內部調用了_Block_object_dispose函數

_Block_object_dispose((void*)src->person, 
					  3/*BLOCK_FIELD_IS_OBJECT*/
					  );
複製代碼

ARC下,該函數的做用是,根絕src(堆block)內部的person指針的強弱性(__weak/__strong),決定如何處理person所指向的對象,若是是__strong,說明src持有了person所指向的對象(也就是強引用),由於此時src即將被釋放,因此須要放開持有,也便是調用一下[person release],使得person所指對象的引用計數-1

爲何上面我都強調了ARC環境下 呢,由於MRC裏面是沒有__weak__strong這兩個東西的。ARC就是根據咱們代碼中的__weak/__strong標記,來自動進行一些內存管理相關的處理。相信上面的分析應該能讓你有所領悟。

若是回到MRC,其實內存管理本質原則沒有變化,無非就是retainrelease操做,我我的認爲過程會相對ARC來講更簡單一些。 首先,內部使用了auto變量(基礎類型/對象類型)的block是在棧空間上的,它不會對對象類型的auto變量進行retain操做,也就是不會持有該對象。 當咱們對一個棧block調用copy方法進行拷貝操做的時候,會跟ARC同樣以相同的方式最終調用_Block_object_assign函數,只不過此時會直接對堆block上 所捕獲的對象進行調用retain方法,進行持有,不會進行__weak/__strong的判斷,由於MRC裏面根本沒有這兩個小兄弟。 當一個堆上的block即將釋放的時候,也會最終調用_Block_object_dispose函數,對該block 所捕獲的對象調用release方法,這樣堆上的block就放開了對該對象的持有(強引用)。

最後,在經過幾張圖示,來講明一下【block對於對象類型的auto變量的捕獲原理MRC下棧block捕獲對象類型的auto變量

MRC下堆block捕獲對象類型的auto變量

ARC下棧block捕獲對象類型的auto變量,__strong

ARC下棧block捕獲對象類型的auto變量,__weak

ARC下堆block捕獲對象類型的auto變量,__strong

ARC下堆block捕獲對象類型的auto變量,__weak

以上就是block對於對象類型auto變量的捕獲過程。



Block傳送門🦋🦋🦋

探尋Block的本質(1)—— 基本認識

探尋Block的本質(2)—— 底層結構

探尋Block的本質(3)—— 基礎類型的變量捕獲

探尋Block的本質(4)—— Block的類型

探尋Block的本質(6)—— __block的深刻分析

相關文章
相關標籤/搜索