1. 程式人生 > IOS開發 >oc-block相關

oc-block相關

一、三種block

1、全域性block

- (void)viewDidLoad
{
  [super viewDidLoad];
  
  void (^block)(void) = ^{
    NSLog(@"hehe");
  };
  NSLog(@"%@",block); // 列印block物件
}

--------------------
列印:
  <__NSGlobalBlock__:0x105f14088> //說明這是一個全域性block
複製程式碼

2、堆block

- (void)viewDidLoad
{
  [super viewDidLoad];
  
  int a = 10;
  void (^block)(void) = ^{
    NSLog(@"hehe - %d",a);
  };
  NSLog(@"%@",block); // 列印block物件
}

--------------------
列印:
  <__NSMallocBlock__:0x600002cceeb0> //說明這是一個堆block
複製程式碼

3、棧block

- (void)viewDidLoad
{
  [super viewDidLoad];
  
  int a = 10;
  
  NSLog(@"%@",^{
    NSLog(@"hehe - %d",a);
  }); // 列印block物件
}

--------------------
列印:
  <__NSStackBlock__:0x7ffee09cf800> //說明這是一個堆block
    
原因:程式碼塊在做=的賦值操作的時候隱藏了一個copy,從棧區拷貝到的堆區
複製程式碼

棧:0x7

堆:0x6

靜態變數:0x1

二、block的迴圈引用

1、引用計數與物件的釋放

  1. 當物件A引用物件B時,B的rentainCount +1
  2. 當A進行dealloc時,向B傳送release訊號,B的retainCount -1
  3. 當B的retainCount == 0時,B就會呼叫dealloc

2、迴圈引用

A引用了B,此時B的rentainCount==1,然後B引用了A,此時A的rentainCount==(n+1),所以A和B都無法呼叫dealloc

typedef void(^HHBlock)(void);
@interface ViewController ()
@property (nonatomic,copy) HHBlock block;
@property (nonatomic,copy) NSString *name;
@end
 
@implementation ViewController
 
// 迴圈引用版
- (void)viewDidLoad
{
  [super viewDidLoad];
  
  self.name = @"hehe";
  self.block = ^{
    self.name = @"haha";
  };
  
  /*
  此時,self 持有block,同時,block持有self
  */
  self.block;
}

// 升級版
- (void)viewDidLoad
{
  [super viewDidLoad];
  
  self.name = @"hehe";
  
  __weak typeof(self) weakSelf = self;
  // 此處開始self引用計數不會因為block持有而增加
  self.block = ^{
    __strong typeof(self) strongSelf = weakSelf;
    // 此處臨時變數會在autoreleasepool中自動釋放
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW,(int64_t)(2 * NSEC_PER_SEC)),dispatch_get_main_queue(),^{
      strongSelf.name = @"haha";
    });
  };
  self.block;;
}

// 升級版 2
- (void)viewDidLoad
{
  [super viewDidLoad];
  
  self.name = @"hehe";
  
  __block ViewController *vc = self;
  // __block可以copy一份當前的物件到新建的struct裡面
  // 目前self持有block持有vc持有self
  self.block = ^{
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW,^{
      vc.name = @"haha";
      vc = nil; //直接給變數釋放,不置為nil還是迴圈引用不能自動釋放
      //目前self持有block持有nil不再持有self
    });
  };
  self.block;;
}

- (void)dealloc
{
  NSLog(@"呼叫了dealloc");
}
複製程式碼
// 騷騷版 3
typedef void(^HHBlock)(ViewController *);
@interface ViewController ()
@property (nonatomic,copy) NSString *name;
@end
 
@implementation ViewController
 
- (void)viewDidLoad
{
  [super viewDidLoad];
  
  self.name = @"hehe";
  self.block = ^(ViewController *vc){
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW,^{
      vc.name = @"haha";
    });
  };
  
  self.block(self);
}

- (void)dealloc
{
  NSLog(@"呼叫了dealloc");
}
複製程式碼

三、bolck自動捕獲變數的特性

1、引出問題

  • 物件的底層:struct
  • block是個物件
  • block捕獲變數 -->即--> struct內部增加屬性

NSLog的本質是什麼?

答:是一套封裝了print的C語言函式,比較耗時

- (void)viewDidLoad
{
  __block int a = 10;
  NSLog(@"進去之前:%p",&a); // 0x7ffee3deb988 - 棧
  viod(^block)(void) =^{
    // 捕獲
    a++;
    NSLog(@"在裡面:%p",&a); // 0x600002647c78 - 捕獲後 到 堆區
  };
  NSLog(@"寫完block:%p",&a); // 0x600002647c78 - 看寫程式碼的順序,不要看呼叫順序
  block();
}
複製程式碼

2、原始碼初探

//  C語言
#include "stdio.h"

int main()
{
  void(^block)(void) = ^{
    printf("hehe");
  };
  block;
  
  return 0;
}

---------------------- 執行clang看其內部實現
clang -rewrite-objc testBlcok.c -o blockCPP.cpp
---------------------- 結果很亂但是不用看,看下面簡化版的就行
int main()
{
  void(*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0,&__main_block_desc_0_DATA));
  ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);

  return 0;
}
---------------------- 簡化版
int main()
{
  void(*block)(void) = __main_block_impl_0(__main_block_func_0,__main_block_desc_0_DATA); //  f(a,b)
  block->FuncPtr(block); // 其實就是 block();

  return 0;
}
複製程式碼
// __main_block_impl_0
struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  __main_block_impl_0(void *fp,struct __main_block_desc_0 *desc,int flags=0)
  {
    // 初始化
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    // 儲存程式碼塊的能力
    impl.FuncPtr = fp; // 屬性函式 - 儲存函式 - __main_block_func_0 其實就是^{ ··· }程式碼塊
    Desc = desc;
  }
};
複製程式碼

3、捕獲變數的程式碼

  1. 不加__block的int

    #include "stdio.h"
    
    int main()
    {
      int a = 10;
      void(^blockNN)(void) = ^{
        printf("--- %d",a);
      };
      blockNN();
      
      return 0;
    }
    ------------
    int main()
    {
      int a = 10;
      void(*blockNN)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0,&__main_block_desc_0_DATA,a));
      ((void (*)(__block_impl *))((__block_impl *)blockNN)->FuncPtr)((__block_impl *)blockNN);
    
      return 0;
    }
    ------------
    int main()
    {
      int a = 10;
      void(*blockNN)(void) = __main_block_impl_0(__main_block_func_0,__main_block_desc_0_DATA,a)); // f(a,b,c)
      
      (blockNN->FuncPtr)(blockNN);
    
      return 0;
    }
    複製程式碼
    struct __main_block_impl_0 
    {
      struct __block_impl impl;
      struct __main_block_desc_0* Desc;
      
      
      
      int a; // 自動捕獲到了變數
      
      
      
      __main_block_impl_0(void *fp,int _a,int flags=0) : a(_a) {
        impl.isa = &_NSConcreteStackBlock;
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
      }
    };
    複製程式碼
    static void __main_block_func_0(struct __main_block_impl_0 *__cself) 
    {
      // 建立新的變數
      // __cself就是(blockNN->FuncPtr)(blockNN)中的blockNN
      int a = __cself->a; // bound by copy 
    
        printf("--- %d",a);
      }
    複製程式碼
  2. 加__block的int

    #include "stdio.h"
    
    int main()
    {
      __block int a = 10;
      void(^blockNN)(void) = ^{
        printf("--- %d",a++);
      };
      blockNN();
      
      return 0;
    }
    ----------------
    int main()
    {
      __attribute__((__blocks__(byref))) __Block_byref_a_0 a = {(void*)0,(__Block_byref_a_0 *)&a,0,sizeof(__Block_byref_a_0),10};
      void(*blockNN)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0,570425344));
      ((void (*)(__block_impl *))((__block_impl *)blockNN)->FuncPtr)((__block_impl *)blockNN);
    
      return 0;
    }
    -----------------
    int main()
    {
      // 生成了一個含有五個屬性的結構體
      __Block_byref_a_0 a = {
        0,&a,// 引用原來的a的地址
        0,10  // 原來的a的值
      };
      
      
      void(*blockNN)(void) = __main_block_impl_0(__main_block_func_0,570425344));
      // 其中的&a就是上面結構體的指標地址
      
      
      ((void (*)(__block_impl *))((__block_impl *)blockNN)->FuncPtr)((__block_impl *)blockNN);
    
      return 0;
    }
    複製程式碼
    struct __main_block_impl_0 {
      struct __block_impl impl;
      struct __main_block_desc_0* Desc;
      
      
      __Block_byref_a_0 *a; // by ref
      // 自動捕獲__Block_byref_a_0型別的*a的指標
      
      
      __main_block_impl_0(void *fp,__Block_byref_a_0 *_a,int flags=0) : a(_a->__forwarding) {
        impl.isa = &_NSConcreteStackBlock;
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
      }
    };
    複製程式碼
    static void __main_block_func_0(struct __main_block_impl_0 *__cself) 
    {
      // 指標傳遞
      __Block_byref_a_0 *a = __cself->a; // bound by ref
      // 此*a就是原來的結構體__Block_byref_a_0 a,包含了原來a的地址和值
    
        printf("--- %d",(a->__forwarding->a)++);
      }
    複製程式碼
  3. 總結

    沒有__block修飾的int a只是傳進來一個值,沒有原來的a的地址,不能修改,是隻讀狀態

    __block修飾的int a就是指標傳遞,可讀可寫