iOS:Block變量捕獲

這篇博客咱們從一個很常見的題目入手。c++

int age = 10;   
void (^myblock)(void) =  ^{
  NSLog(@"%d",age);
};
age  = 20;
myblock();

這個題目就涉及到了block內訪問外部變量,block有個變量捕獲機制,數組

咱們新建一個mac的命令行工程,把上面代碼寫進去,而後用clang把main.m文件編譯爲cpp的文件看一下。xcode

具體的block底層結構上一篇文章咱們已經說過了,這裏咱們針對結構就不在贅述,直接說核心點。函數

auto類型局部變量

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

能夠看到,在block的內部,多了一個age變量。並且從初始化的函數來看,這個__main_block_impl_0中age的值是外面傳進來賦給他的。咱們看一下main函數中。命令行

int age = 10;

void (*myblock)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, age));

age = 20;

((void (*)(__block_impl *))((__block_impl *)myblock)->FuncPtr)((__block_impl *)myblock);

能夠看出來在這個block初始化的過程當中,就把age的值,也就是10,傳了進去,賦值給了block內部的age變量。指針

那我再看一下打印的時候的age是什麼狀況。code

static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  int age = __cself->age; // bound by copy
  NSLog((NSString *)&__NSConstantStringImpl__var_folders_6l_80sfw0tn35bg4dxc51jlq_bw0000gn_T_main_3b5bf4_mi_0,age);
}

打印的這個age就是block本身內部的這個ege變量(值爲10),因此咱們在執行block以前,改變外面的age值爲20,其實改變的不是內部要打印的這個age了,因此打印出來結果仍是10。對象

好的,咱們定義的這個age就是一個普通的局部變量,其實就是auto類型的局部變量(C語言基礎)。那咱們下面改變一下這個變量的屬性,嘗試一下靜態變量(static)和全局變量。內存

static類型局部變量

首先是靜態變量。博客

//定義變量時加static標識
static int age = 10;

運行結果是20。咱們依然要看一下clang編譯一下,看c++代碼。

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

咱們來分析一下兩次的異同,首先block都對變量進行了捕獲,但不一樣的是static類型的變量是捕獲了變量的指針,那咱們應該就能夠理解了,通常狀況下這種基本數據類型若是是傳指針,就意味要修改值的。

咱們從main函數中也能夠看出來,在初始化block的時候傳入了外部age的指針,

static int age = 10;
void (*myblock)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, &age));

並且在打印的時候也是打印了這個指針指向的數據。

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

  NSLog((NSString *)&__NSConstantStringImpl__var_folders_6l_80sfw0tn35bg4dxc51jlq_bw0000gn_T_main_d618bf_mi_0,(*age));
}

因此咱們在執行block以前更改了age的值,在執行block的時候打印的也是同一塊內存的值,因此值改變了。

最後咱們在試一下全局變量。

全局變量

在定義age的時候,定義成一個全局變量。(拿到main方法外面)。

int age = 10;


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;
    Desc = desc;
  }
};

從編譯後的代碼能夠看出,在底層其實也是生成了一個age=10的全局變量,而後在block內部並無捕獲這個變量。

在無論是從新賦值,仍是輸出打印,都是操做的這個全局的age,因此這個值也是能被改變的。

可能有人會問若是是

static int age = 10;

這個樣子的全局變量呢?

全局變量加不加static都是同樣的。這裏就不上代碼細說了。

小結

局部變量:

  1. 會被block捕獲到內部
  2. auto類型的是值傳遞,內部不能修改值
  3. static是指針傳遞,能夠修改值。

全局變量:

  1. 不會被捕獲到block內部
  2. 能夠修改值

上面咱們使用的是基本數據類型,那對象是否是也同樣呢?

其實對象類型的也是同樣的,可是有一個小點咱們須要瞭解一下。

看下面這個例子吧。

NSMutableArray * arr = [NSMutableArray new];
   
void (^myblcok)(void) = ^{
  [arr addObject:@"1"];
  NSLog(@"%@",arr);
};
   
myblcok();

按照咱們上面總結的這種auto類型的局部變量是要被捕獲到內部的,可是應該不能夠修改值。

上面代碼執行後打印數組,是有@「1」這個元素的,這裏咱們就要搞清楚,咱們調用[arr addObject:@"1"];並無修改arr的值,只是只用了這個指針。

若是咱們在block的回調中讓arr=nil;,這算是改變arr的值,可是xcode就會報錯的了。

相關文章
相關標籤/搜索