iOS Block實現探究

使用clang的rewrite-objc filename 能夠將有block的c代碼轉換成cpp代碼。從中能夠看到block的實現。 函數

#include <stdio.h>
int main()
{
  void (^blk)(void) = ^{
    printf("Block\n");
  };
  blk();
  return 0;
}
使用clang rewrite-objc之後會看到block的實現
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;
  }
};

能夠看到其實block是一個正常的OC類 spa

來看看block是怎樣訪問外部變量的 指針

int main()
{
  int dmy = 256;
  int val = 10;
  const char *fmt = "val = %d\n";
  void (^blk)(void) = ^{
    printf(fmt, val);
  };
  return 0;
}

轉換以後,能夠看到 code

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  const char *fmt;
  int val;
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, const char *_fmt, int _val, int flags=0) : fmt(_fmt), val(_val) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
int main()
{
  int dmy = 256;
  int val = 10;
  const char *fmt = "val = %d\n";
  void (*blk)(void) = (void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, fmt, val);
  return 0;
}

block的變量會被複制進block中 get

若是當block要改變傳入的變量值怎麼辦?首先看一下全局變量和本地靜態變量 編譯器

int global_val = 1;
static int static_global_val = 2;
int main()
{
  static int static_val = 3;
  void(^blk)(void) = ^{
    global_val *= 1;
    static_global_val *= 2;
    static_val *= 3;
  };
  blk();
  return 0;
}
struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  int *static_val;
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int *_static_val, int flags=0) : static_val(_static_val) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  int *static_val = __cself->static_val; // bound by copy

    global_val *= 1;
    static_global_val *= 2;
    (*static_val) *= 3;
  }

因爲全局變量是在Data Section中,因此直接能夠訪問。局部靜態變量是經過將其指針傳入到block中,block就能夠對其值進行修改。 it

而後看一下__block修飾符變量 io

int main()
{
  __block int val = 10;
  void (^blk)(void) = ^{val = 1;};
  blk();
  return 0;
}
struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  __Block_byref_val_0 *val; // by ref
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_val_0 *_val, int flags=0) : val(_val->__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_val_0 *val = __cself->val; // bound by ref
(val->__forwarding->val) = 1;}
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->val, (void*)src->val, 8/*BLOCK_FIELD_IS_BYREF*/);}

__block修飾符的變量,會生成一個__Block_byref_val_0的struct,而後經過訪問其__forwarding來訪問val值。由於Block有多是在stack或者heap中,因此用__forwarding來訪問。之因此會將__block單獨生成一個struct是由於可能該變量會被多個block使用。 編譯

Block分三種類型 class

0) NSConcreteStackBlock    --stack

1) NSConcreteGlobalBlock   --data area

2) NSConcreteMallocBlock   --heap


自動copy block

當開啓ARC時,在某些狀況編譯器會自動copy block,從stack到heap。

typedef int (^blk_t)(int);
blk_t func(int rate)
{
  return ^(int count){return rate * count;};
}
blk_t func(int rate)
{
  blk_t tmp = &__func_block_impl_0(
    __func_block_func_0, &__func_block_desc_0_DATA, rate);

  tmp = objc_retainBlock(tmp);
  return objc_autoreleaseReturnValue(tmp);
}

有些狀況,編譯器是沒法檢測是否應該copy block: 

當block做爲參數傳遞到方法或函數中。

可是,若是該方法或函數在內部copy,就不用手動再copy:

0)cocoa framework方法, 有usingBlock

1) GCD API

- (id) getBlockArray
{
  int val = 10;
  return [[NSArray alloc] initWithObjects:
     [^{NSLog(@"blk0:%d", val);} copy],
     [^{NSLog(@"blk1:%d", val);} copy], nil];
}


__forwarding

當block從stack copy到 heap中時,block中用到的__block也會copy到heap中,而且copy到heap中的block擁有該__block。

__block int val = 0;
void (^blk)(void) = [^{++val;} copy];

++val;

blk();

NSLog(@"%d", val);



在block和外的++val都會變成 ++(val.__forwarding->val);

當block copy到heap中後, stack中的__forwarding會指向heap中的__block, heap中的__forwarding會指向本身的__block值,這樣保證了__forwarding指向的是同一個變量值。


Block何時會copy到heap中

0)對block調用copy方法。

1)block做爲一個函數的返回值。 編譯器自動copy

2)賦值給id或block type class 有__strong 修飾符的成員變量。   編譯器自動copy

3)usingBlock, GCD API。  在函數內copy


何時用該copy block?

0) block是函數返回值

1) block賦值給id或block type class 有__strong 修飾符的成員變量。 

2)3)usingBlock, GCD API。  在函數內copy

相關文章
相關標籤/搜索