天天看點

iOS底層原理篇(十七) ---- Block初探

1.Block的分類

  • Block有六種:在libclosure的源碼中
    //常見的三種
      void * _NSConcreteStackBlock[32] = { 0 };
      void * _NSConcreteMallocBlock[32] = { 0 };
      void * _NSConcreteGlobalBlock[32] = { 0 };
      //系統級别的三種
      void * _NSConcreteAutoBlock[32] = { 0 };
      void * _NSConcreteFinalizingBlock[32] = { 0 };
      void * _NSConcreteWeakBlockVariable[32] = { 0 };
               
常見的三種block
  • 1.全局block : NSGlobalBlock
- (void)viewDidLoad {
    	[super viewDidLoad];
    
    	void (^block)(void) = ^{
        	NSLog(@"我是全局block");
    	};
    	block();
    	NSLog(@"%@",block);
	}
	//列印結果
	//Block探析[2300:140310] 我是全局block
	//Block探析[2300:140310] <__NSGlobalBlock__: 0x10d14e038>
           
  • 2.堆區block : NSMallocBlock
- (void)viewDidLoad {
    	[super viewDidLoad];
    
    	int a = 10;
    	void (^block)(void) = ^{
        	NSLog(@"我是堆區block---%d",a);
    	};
    	block();
    	NSLog(@"%@",block);
	}
	//列印結果:
	//Block探析[2416:146513] 我是全局block---10
	//Block探析[2416:146513] <__NSMallocBlock__: 0x600003db08d0>
           
  • 3.棧區block : NSStackBlock (block在被copy之前就是棧區block)
- (void)viewDidLoad {
    	[super viewDidLoad];
    	NSLog(@"%@",^{
        	NSLog(@"我是棧區block");
    	});
	}
	//列印結果:
	//Block探析[2460:149166] <__NSGlobalBlock__: 0x109b7b038>
           

2.Block捕獲外部變量

1.無__block修飾的局部變量
#include <stdio.h>
int main() {
    int a = 10;
    void (^block)(void) = ^{
        printf("我是block---%d",a);
    };
    block();
    return 0;
}
//clang生成.cpp檔案
//下面是cpp檔案中main函數的實作代碼
int main() {
    int a = 10;
    /**
    //我們上下代碼對比一下
    //下面這句代碼是block的聲明
    
    //傳入了三個參數
    //第一個__main_block_func_0 block的代碼塊	
	//第二個__main_block_desc_0_DATA
	//第三個為a的值
	
    //void (*block)(void) = __main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, a);
    
    //這裡面傳入的a為外部變量的值
    
    //(void *)__main_block_func_0:就是下面的結構
	static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
		//這裡把block聲明時内部儲存的a值給了一個臨時變量a
		//這個a和我們block通路的外部變量不是同一個值
		//這裡做的隻是淺拷貝,拷貝了a的值
		//是以我們在内部修改a的值時,外部的a是不會改變的
    	int a = __cself->a; // bound by copy
    	printf("我是全局block---%d",a);
	}
	//這裡将我們block代碼塊裡面的代碼給了函數__main_block_func_0
    //__main_block_impl_0:構造函數,傳回值為一個block!
    //block的本質:結構體
    struct __main_block_impl_0 {
  		struct __block_impl impl;
  		struct __main_block_desc_0* Desc;
  		//聲明的block内部會生成一個 int a 變量用來儲存外部捕獲的變量值!
  		int a;
  		
  		//這裡fp傳入的值就是__main_block_func_0
  		//desc是__main_block_desc_0_DATA
  		//int _a 就是 a
  		__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _a, int flags=0) : a(_a) {
  			//此時這裡還是棧區block,後期會研究一下在什麼時候變成了堆區block
    		impl.isa = &_NSConcreteStackBlock;
    		impl.Flags = flags;
    		//将代碼塊指派給了impl的FuncPtr
    		impl.FuncPtr = fp;//函數式儲存
    		Desc = desc;
  		}
	};
    */
    void (*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, a));
    
    /**
    //block結構體中的impl結構
    struct __block_impl {
  		void *isa;
  		int Flags;
  		int Reserved;
  		void *FuncPtr;//代碼塊的函數式儲存
	};
	//去掉強轉的代碼,這裡不就是block的調用:
	block->FuncPtr(block);
    */
    ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);

    return 0;
}
           
2.__block修飾的局部變量
#include <stdio.h>
	int main() {
    	__block int a = 10;
    	void (^block)(void) = ^{
    		a++;
        	printf("我是全局block---%d",a);
    	};
    	block();
    	return 0;
	}
	
	//clang生成.cpp檔案
	//下面是cpp檔案中main函數的實作代碼
	
	int main() {
		//__block int a = 10;的代碼處理
		//__Block_byref_a_0類型的a
		/**
		這個與下面的__Block_byref_a_0 a 初始化對比
		struct __Block_byref_a_0 {
  			void *__isa;//0
			__Block_byref_a_0 *__forwarding;//a的位址指針
 			int __flags;//0
 			int __size;//空間大小
 			int a;a的值
		};
		*/
    	__attribute__((__blocks__(byref))) __Block_byref_a_0 a = {
    											 	   (void*)0,
    									(__Block_byref_a_0 *)&a, 
    														  0, 
    								  sizeof(__Block_byref_a_0), 
    														10};
    	//
    	/**
    	這裡block初始化的時候,傳進來的是__Block_byref_a_0結構體類型的指針a;
    	struct __main_block_impl_0 {
  			struct __block_impl impl;
  			struct __main_block_desc_0* Desc;
  			__Block_byref_a_0 *a; // by ref
  			//初始化的時候,傳入的就是儲存了a的位址指針及值的一個結構體的指針__Block_byref_a_0 *_a
  			__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_a_0 *_a, int flags=0) : a(_a->__forwarding) {
    			impl.isa = &_NSConcreteStackBlock;
    			impl.Flags = flags;
    			impl.FuncPtr = fp;
    			Desc = desc;
  			}
		};
    	*/
    	void (*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_a_0 *)&a, 570425344));
    	//調用
    	/**
    	static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
    		//深拷貝,位址
    		//__Block_byref_a_0類型的a中__forwarding指針指向的a值
    		//a->__forwarding->a的值++
    		(a->__forwarding->a)++
    		__Block_byref_a_0 *a = __cself->a;
        	printf("我是全局block---%d",(a->__forwarding->a));
    	}
    	*/
    	((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
    	return 0;
	}
           

3.Block循環引用

- (void)viewDidLoad {
    	[super viewDidLoad];
    	self.name = @"iOS";
    	self.block = ^{
        	NSLog(@"我是---%@",self.name);
    	};
    	self.block();
	}
	//這樣一段代碼,不用說,都知道會發生循環應用
	//這是因為self->block->self形成了一個閉環,并且都是強持有!
	//是以這裡vc和block都無法釋放
	//也就是說,隻要打破這個循環,不會循環應用了!
	//我們平時最多的使用是 
	//定義一個弱引用weakSelf,指針指向self,因為是weak修飾,是以不對引用計數做任何操作
	//__weak typeof(self) weakSelf = self;隻需将self.name改為weakSelf.name就可打破循環
	//這裡使用了中介者設計模式
	//weakSelf(弱引用表)->self->block->weakSelf
	//看需求及邏輯需要,隻有這一句代碼有可能會有問題,
	//比如把上面代碼改為:
	- (void)viewDidLoad {
    	[super viewDidLoad];
    	self.view.backgroundColor = [UIColor whiteColor];
    	
    	self.name = @"iOS";
    	__weak typeof(self) weakSelf = self;
    	self.block = ^{
        	dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            	NSLog(@"我是---%@",weakSelf.name);
        	});
    	};
    	self.block();
	}

	- (void)dealloc {
    	NSLog(@"釋放了");
	}
	//列印結果就是:
	//Block探析[3895:288440] 釋放了
	//Block探析[3895:288440] 我是---(null)
	//列印的時候,vc已經釋放,屬性name肯定也被釋放了
	//如果遇到這種情況,我們還需要做一步操作:
	//__strong typeof(weakSelf) strongSelf = weakSelf;
	//weakSelf.name改為strongSelf.name
	//strongSelf臨時變量出了作用域會被釋放
	//strongSelf->weakSelf(弱引用表)->self->block->weakSelf->strongSelf
	//延遲釋放vc
	//Block探析[3922:291133] 我是---iOS
	//Block探析[3922:291133] 釋放了
           

這裡簡單的介紹一下block,下一篇會從底層出發,揭開block的神秘面紗…