iOS与OS X之Block實現

基本語法:

   ^返回值類型 參數列表 表達式

其中返回值類型和參數列表均可省略!

使用Block語法將Block 賦值為 Block類型變量
int (^blk)(int) = ^(int count){ return count +1;};
由"^"開始的Block語法生成的Block被賦值給亦是blk中,因為與通常的變量相同,當然可以進行變量賦值
int (^blk1)(int) = blk;
int (^blk2)(int);
blk2 = blk1;

使用 typedef 簡化block代碼

typedef int (^blk_t)(int); //定義一個blk_t類型變量
//作用可作為參數
void func(blk_t blk){
}
//作為參數返回值
blk_t func(){
}

_block說明符 -- 在賦值的時候需要加上__block說明符

__block int val = 0; void (^blk)(void) = ^{val = 1;}; //只有加上__block,才可以在Block中修改值!

下面的代碼不是賦值,看起來只是使用數組,好像沒有總是,但運行會報錯!

    const char text[] = "hello";
    void ()(void) = ^{
        printf("%c\n",text[2]);
    };

這是因為在現在的Block中,並沒有實現對C語言數組的截獲,使用指針可以解決該問題

`const char *text = "hello";

Block的實質

通過 clang -rewrite-objc 源代碼文件名 可將Block轉換成C++源代碼

int main()
{
       void (^blk)(void) = ^{printf("Block\n");};
       blk();
       return 0;
}

通過轉換變成差不多有10萬行代碼,以下形式

struct __block_impl {
  void *isa;
  int Flags;
  int Reserved;
  void *FuncPtr;
};
...
static struct __main_block_desc_0 {
  size_t reserved;
  size_t Block_size;
}
struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc; //由於轉換後源代碼中加入其構造函數,其實結構體就上面兩個變量

//初始化__main_block_impl_0結構體的構造函數
  __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;
  }
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {//__csself就是指的Block本身
printf("Block\n");}   //就是源碼中的^{printf("Block\n");};

 __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};//分配Block的空間

int main(int argc, const char * argv[]) {//主函數

    void (*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));//這行為將棧上生成的__main_block_impl_0結構體實例的指針賦值給指針變量blk
    ((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);//去掉轉換部分(*blk->imp1.FuncPtr)(blk);這行為調用 blk();
    return 0;
}

  • __main_block_func_0 名稱的由來是因為block在main函數中且是第一次出現所以後面是0
  • 主函數main中構造函數的調用可簡化為->
  struct __main_block_imp1_0 tmp =  __main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA);
    struct __main_block_imp1_0 *blk = &tmp;
  • isa = &_NSConcreteStackBlock的理解
typedef struct objc_object {
    Class isa;
} *id;
struct objc_class {
      Class isa;
};

假設通過NSObject生成的一個結構體實例中會通過變量isa保持該類的結構體實例指針,如下圖所示


image.png
image.png

所以_NSConcreteStackBlock就相當於class_t結構體實例,因為Block為OC的對象,關於該類的信息放置於_NSConcreteStackBlock中,於是將其賦值給isa指針!

Block的實質-在Block修改外面的值

    __block int val = 7;
    void (^blk)(void) = ^{val = 1;};

轉換後的代碼如下

struct __Block_byref_val_0 {
  void *__isa;
__Block_byref_val_0 *__forwarding;
 int __flags;
 int __size;
 int val;
};

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; //通過__forwarding拿到__Block_byref_val_0實例的指針
}

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*/);}

static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->val, 8/*BLOCK_FIELD_IS_BYREF*/);}

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[]) {

    __attribute__((__blocks__(byref))) __Block_byref_val_0 val = {(void*)0,(__Block_byref_val_0 *)&val, 0, sizeof(__Block_byref_val_0), 7};
    void (*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_val_0 *)&val, 570425344));

    return 0;
}

  • 加了__block的變量變成了__Block_byref_val_0結構體實例,該實例的成員變量__forwarding指向該實例的指針,可通過此指針
    image.png

    所以上面賦值是這樣的形式--> (val->__forwarding->val) = 1

Block 存儲域

image.png

Block類型分以下幾種

  • _NSConcreteStackBlock 設置在棧上的Block
  • _NSConcreteGlobalBlock 設置在數據區(.data區)
  • _NSConcreteMallocBlock malloc函數分配的內存塊(堆區)
image.png
image.png

以下源代碼生成並持有NSMutableArray類的對象,由於附有__strong修飾符的賦值變量作用域立即結束,因此對象被立即釋放並廢棄

    {
        id array = [[NSMutableArray alloc]init];
    }

我們來看一下Block語法中使用該變量array的代碼

int main(int argc, const char * argv[]) {
    typedef void (^blk_t)(id);

    blk_t blk;
    {
        id array = [[NSMutableArray alloc]init];//如果用__block id __weak array2 = array;然後下面用array2的話,會因為走出大括號後,array的強引用消失,對象不存在,將array2指針變為Nil,會導致大括號後的打印全是0
        blk = [^(id obj){
            [array addObject:obj];
            NSLog(@"array count = %ld",[array count]);
            
        }copy];
    }
    blk([[NSObject alloc]init]);
    blk([[NSObject alloc]init]);
    blk([[NSObject alloc]init]);
    

    return 0;
}

轉換後代碼如下:

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  id array;
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, id _array, int flags=0) : array(_array) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself, id obj) {
  id array = __cself->array; // bound by copy

            ((void (*)(id, SEL, ObjectType))(void *)objc_msgSend)((id)array, sel_registerName("addObject:"), (id)obj);
            NSLog((NSString *)&__NSConstantStringImpl__var_folders_x0_xwklvzm967x54vp3bvrp8b2m0000gn_T_main_ea6c83_mi_0,((NSUInteger (*)(id, SEL))(void *)objc_msgSend)((id)array, sel_registerName("count")));

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

static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->array, 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*); //retain函數的實現在上面
  void (*dispose)(struct __main_block_impl_0*);//release函數的實現在上面
} 

__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[]) {
    typedef int (*blk_t)(id);

    blk_t blk;
    {
        id array = ((NSMutableArray *(*)(id, SEL))(void *)objc_msgSend)((id)((NSMutableArray *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSMutableArray"), sel_registerName("alloc")), sel_registerName("init"));
        blk = (blk_t)((id (*)(id, SEL))(void *)objc_msgSend)((id)((void (*)(id))&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, array, 570425344)), sel_registerName("copy"));


    }
    ((int (*)(__block_impl *, id))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk, ((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSObject"), sel_registerName("alloc")), sel_registerName("init")));
    ((int (*)(__block_impl *, id))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk, ((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSObject"), sel_registerName("alloc")), sel_registerName("init")));
    ((int (*)(__block_impl *, id))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk, ((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSObject"), sel_registerName("alloc")), sel_registerName("init")));


    return 0;
}
  • _Block_object_assign 相當於retain實例方法的函數
  • _Block_object_dispose 相當於release實例方法的函數
    但是上面的兩個函數只是賦值給了結構體__main_block_desc_0的成員變量中,並沒有調用,原因如下


    image.png
image.png

本以為在去掉copy後,因為變量的作用域結束會導致NSMutableArray對象也廢棄,但是通過運行後通過導出C++發現除了沒有調用copy,其他的都步驟都有做,所以才沒有導致崩潰!這是和書上不同的地方!!!

image.png

還是將書上的說明貼出來,


image.png

block循環引用

示例一:

如果在Block中使用附有__strong修飾符的對象類型自動變量,那麼當Block從棧複製到堆時,該對象為Block所持有,這樣容易引起循環引用

typedef void (^blk_t)(void);
@interface MyObject : NSObject
{
    blk_t blk_;
}

@end

@implementation MyObject

-(id)init{
    self = [super init];
    blk_ = ^{NSLog(@"self = %@",self);}; //self為帶有__strong修飾符的對象類型自動變量
    return self;
}

-(void)dealloc{
    NSLog(@"dealloc");
}

@end

int main(int argc, const char * argv[]) {
    
    id o = [[MyObject alloc]init];
    NSLog(@"%@",o);

    return 0;
}

循環救命如下圖


image.png

為避免循環使用,可聲明附有__weak修飾符的變量,將self賦值使用

-(id)init{
    self = [super init];
//    blk_ = ^{NSLog(@"self = %@",self);};
    id __weak tmp = self;
    blk_ = ^{NSLog(@"self = %@",tmp);};
    return self;
}//這樣就會調用MyObject的dealloc釋放函數了

示例二:

typedef void (^blk_t)(void);
@interface MyObject : NSObject
{
    blk_t blk_;
}

@end

@implementation MyObject

-(id)init{
    self = [super init];

    __block id  tmp = self;
    blk_ = ^{NSLog(@"self = %@",tmp);
//        tmp = nil;
        };
    return self;
}

-(void)execBlock{
    blk_();
}

-(void)dealloc{
    NSLog(@"dealloc");
}

@end

int main(int argc, const char * argv[]) {
    
    id o = [[MyObject alloc]init];
    [o execBlock];
    return 0;
}

tmp = nil這句代碼屏蔽便會引起循環引用,因為

  • MyObject 類對象持有Block
  • Block持有__block對象
  • __block 變量持有MyObject類對象
    tmp=nil加上便可以解循環,如下圖所示
image.png
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,185评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,445评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,684评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,564评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,681评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,874评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,025评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,761评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,217评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,545评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,694评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,351评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,988评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,778评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,007评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,427评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,580评论 2 349

推荐阅读更多精彩内容

  • 大多数的开发者在使用Java或C#等基于类的语言的过程中学会了面向对象编程。由于JavaScript没有对类的正式...
    WanLum阅读 588评论 0 4
  • 也許你還沒有理解構造函數和原型對象的時候已經在javascript的路上走了很久,但直到你很好的掌握它們之前你不會...
    WanLum阅读 406评论 0 1
  • 为何叫做 shell ? shell prompt(PS1) 与 Carriage Return(CR) 的关系?...
    Zero___阅读 3,142评论 3 49
  • 隨筆1-24(2015.6-10) 1、作者 才華不是財富,痛苦不是財富,用才華對痛苦進行思考和表達才是。於是有了...
    四葉阅读 1,487评论 3 14