[ios] 浅谈Block

<h6> 1. iOS Block用来封装一段代码块或者传递参数相对于代理使用起来方便,它本质上是一个匿名函数。</h6>

完整的block的申明: 返回值类型 (^blockName)(参数列表) = ^返回值类型 (参数列表){
// 表达式;what you want to do;
};
    void(^blockTest)(void) = ^void(void){
     
        printf("hello world");
       
    };

<h6>2.使用block传值</h6>

顺传:定义属性
逆向传值:使用代理、通知、block、单例、全局变量、等传值手段传值
---------------------------------
假设现在有a,b两个页面,a是b的上一级页面,b此刻处于栈顶,a,压在它下面。
想要把a中的一个name值传到b,顺传,定义一个属性接收。
想要把b中的一个name值传到a,逆传,此处只说block。
---------------------------------
逆传方法有两种:
1.b中申明可以带参数的block,在你需要的时机调用,传递参数。a中合适的时机处理传递回来的参数。
----------------------------------------------------
b.h
@interface BViewController : UIViewController
@property (nonatomic ,copy) void (^blkTest)(UIColor*);
@end
----------------------------------------------------
b.m
#import "BViewController.h"

@interface BViewController ()

@end

@implementation BViewController

- (void)viewDidLoad {
   [super viewDidLoad];
   self.title = @"bbbbb";
   self.view.backgroundColor = [UIColor yellowColor];
 
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
   
   if (self.blkTest) {
       self.blkTest([UIColor yellowColor]);
   }
}

@end
----------------------------------------------------
a.m
#import "AViewController.h"
#import "BViewController.h"
@interface AViewController ()

@end

@implementation AViewController

- (void)viewDidLoad {
   [super viewDidLoad];
   self.title = @"aaaaa";
   self.view.backgroundColor = [UIColor redColor];
 
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
   BViewController *vc = [[BViewController alloc] init];
   vc.blkTest = ^(UIColor *color){
       self.view.backgroundColor = color;
   };
   [self.navigationController pushViewController: vc animated:true];
}
@end
----------------------------------------------------
2个界面都变成黄色的了。

----------------------------------------------------
---------------------分割线----------------------
----------------------------------------------------
----------------------------------------------------

2.重写b初始化方法传入参数为一个block,在b的初始化方法中实现该block。
在a中创建b的时候就可以传递参数了。(这么做可以不暴漏属性给外界)
----------------------------------------------------
b.m
#import "BViewController.h"
@interface BViewController ()
@property (nonatomic ,copy) void (^blkTest)(UIColor*);
@end

@implementation BViewController
- (instancetype)initWithBlock:(void (^)(UIColor *))blkTest{
   self.blkTest = blkTest;
   return [self init];
}
- (void)viewDidLoad {
   [super viewDidLoad];
   self.title = @"bbbbb";
   self.view.backgroundColor = [UIColor yellowColor];
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
   if (self.blkTest) {
       self.blkTest([UIColor yellowColor]);
   }
}

@end

----------------------------------------------------
a.m
#import "AViewController.h"
#import "BViewController.h"
@interface AViewController ()
@end

@implementation AViewController
- (void)viewDidLoad {
   [super viewDidLoad];
   self.title = @"aaaaa";
   self.view.backgroundColor = [UIColor redColor];
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
   BViewController *vc = [[BViewController alloc] initWithBlock:^(UIColor * color) {
       self.view.backgroundColor = color;
   }];
   [self.navigationController pushViewController: vc animated:true];
}
@end
----------------------------------------------------

<h6>3.block的类型</h6>3种:

_NSConcreteStackBlock 栈
_NSConcreteGlobalBlock 程序的数据区域(.data 区)
_NSConcreteMallocBlock 堆

<h5>__NSConcreteGlobalBlock 程序的数据区域(.data 区)

1.全局block,在数据区。
#include <stdio.h>

void(^blockTest)(void) = ^void(void){
    printf("hello world");
};

int main(int argc, char * argv[]){
  
    blockTest();
    return 0;
    
}

---------------------clang后的关键代码-------------------------------
int a = 0;
struct __block_impl {
    void *isa;//isa指针 所有对象都有,说明block也是一个对象
    int Flags;//字面意思,标记。记录block的一些附加信息
    int Reserved;//保留变量
    void *FuncPtr;//函数指针,指向具体的block实现的函数调用地址
};
struct __blockTest_block_impl_0 {//blockTest通过clang看到的c
代码就是__blockTest_block_impl_0结构体
  struct __block_impl impl;//定义一个结构体,纪录block的信息。即是上面的定义的结构体:
  struct __blockTest_block_desc_0* Desc;//纪录block大小,保留变量等信息
  __blockTest_block_impl_0(void *fp, struct __blockTest_block_desc_0 *desc, int flags=0) {//block的具体实现
    impl.isa = &_NSConcreteGlobalBlock;//isa指向全局block,data区
    impl.Flags = flags;//给其他属性赋值
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
static void __blockTest_block_func_0(struct __blockTest_block_impl_0 *__cself) {//静态的函数传入实现block-__blockTest_block_impl_0


    printf("hello world");//函数体
}

static struct __blockTest_block_desc_0 {//纪录block大小,保留变量等信息
  size_t reserved;//保留变量
  size_t Block_size;//大小
} __blockTest_block_desc_0_DATA = { 0, sizeof(struct __blockTest_block_impl_0)};//定义一个结构体变量,给上面属性赋值
static __blockTest_block_impl_0  __global_blockTest_block_impl_0((void *)__blockTest_block_func_0, &__blockTest_block_desc_0_DATA);// 给__blockTest_block_impl_0赋值。定义__blockTest_block_impl_0类型的变量__global_blockTest_block_impl_0,传入参数:函数指针 __blockTest_block_desc_0的地址
void(*blockTest)(void) = ((void (*)())&__global_blockTest_block_impl_0);//把上一步定义的__global_blockTest_block_impl_0复制给blockTest

当block定义在局部代码内,但没有访问外部变量时,也在数据区。(其他c代码基本上都差不太多,篇幅过大,(每一行c代码都有注释)这里不在复制。)
在block内修改变量a的值会报错。代码自行编译。
看代码可知道,在block中引用的变量a实际是在申明block时,被复制到main_block_impl_0结构体中的那个变量a。因为这样,我们就能理解,在block内部修改变量a的内容,不会影响外部的实际变量a。
想要修改要加上__block,这时看代码会发现
<下面的内容摘录自唐巧的博客。http://blog.devtang.com/blog/2013/07/28/a-look-inside-blocks/>

struct __Block_byref_i_0 { 
    void *__isa; 
    __Block_byref_i_0 *__forwarding; 
    int __flags; 
    int __size; 
    int i; 
}; 
 
struct __main_block_impl_0 { 
    struct __block_impl impl; 
    struct __main_block_desc_0* Desc; 
    __Block_byref_i_0 *i; // by ref 
    __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_i_0 *_i, int flags=0) : i(_i->__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_i_0 *i = __cself->i; // bound by ref 
 
    printf("%d\n", (i->__forwarding->i)); 
    (i->__forwarding->i) = 1023; 
} 
 
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->i, (void*)src->i, 8/*BLOCK_FIELD_IS_BYREF*/);} 
 
static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->i, 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() 
{ 
    __attribute__((__blocks__(byref))) __Block_byref_i_0 i = {(void*)0,(__Block_byref_i_0 *)&i, 0, sizeof(__Block_byref_i_0), 1024}; 
    void (*block1)(void) = (void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_i_0 *)&i, 570425344); 
    ((void (*)(__block_impl *))((__block_impl *)block1)->FuncPtr)((__block_impl *)block1); 
    return 0; 
} 

<h6>
1.增加了一个结构体,__Block_byref_i_0 ,用来保存我们要capture并且修改的变量i。
2.main_block_impl_0 中引用的是Block_byref_i_0的结构体指针,这样就可以达到修改外部变量的作用。
3.__Block_byref_i_0结构体中带有isa,说明它也是一个对象。
4.我们需要负责Block_byref_i_0结构体相关的内存管理,所以main_block_desc_0中增加了copy和dispose函数指针,对于在调用前后修改相应变量的引用计数

<h6>

<下面的内容摘录自《Objective-C 高级编程 iOS 与 OSX 多线程与内存管理》。http://vdisk.weibo.com/s/u65p8oUfGH52e>
除全局block外,定义block的时候,是分配在栈上的,block只在定义它的范围有效。
栈的生命周期结束,block销毁,此时想要保存数据或者__block变量用结构体成员__forwarding。就要用到堆block。oc中的block是从栈拷贝到堆上的。

什么时候栈上的 Block 会复制到堆?
调用 Block 的 copy 实例方法时
Block 作为函数返回值或者参数时
将 Block 赋值给附有 __strong 修饰符 id 类型的类或 Block 类型成员变量时
在方法名中含有 usingBlock 的 Cocoa 框架方法或 GCD 的 API 中传递 Block 时

Snip20170215_11.png

<下面的内容摘录自唐巧的博客。http://blog.devtang.com/blog/2013/07/28/a-look-inside-blocks/>
NSConcreteMallocBlock 类型的block的实现
NSConcreteMallocBlock类型的block通常不会在源码中直接出现,因为默认它是当一个block被copy的时候,才会将这个block复制到堆中。以下是一个block被copy时的示例代码(来自这里),可以看到,在第8步,目标的block类型被修改为_NSConcreteMallocBlock。

static void *_Block_copy_internal(const void *arg, const int flags) { 
    struct Block_layout *aBlock; 
    const bool wantsOne = (WANTS_ONE & flags) == WANTS_ONE; 
 
    // 1 
    if (!arg) return NULL; 
 
    // 2 
    aBlock = (struct Block_layout *)arg; 
 
    // 3 
    if (aBlock->flags & BLOCK_NEEDS_FREE) { 
        // latches on high 
        latching_incr_int(&aBlock->flags); 
        return aBlock; 
    } 
 
    // 4 
    else if (aBlock->flags & BLOCK_IS_GLOBAL) { 
        return aBlock; 
    } 
 
    // 5 
    struct Block_layout *result = malloc(aBlock->descriptor->size); 
    if (!result) return (void *)0; 
 
    // 6 
    memmove(result, aBlock, aBlock->descriptor->size); // bitcopy first 
 
    // 7 
    result->flags &= ~(BLOCK_REFCOUNT_MASK);    // XXX not needed 
    result->flags |= BLOCK_NEEDS_FREE | 1; 
 
    // 8 
    result->isa = _NSConcreteMallocBlock; 
 
    // 9 
    if (result->flags & BLOCK_HAS_COPY_DISPOSE) { 
        (*aBlock->descriptor->copy)(result, aBlock); // do fixup 
    } 
 
    return result; 
} 

<h6>
在ARC开启的情况下,将只会有 NSConcreteGlobalBlock和 NSConcreteMallocBlock类型的block。原本的NSConcreteStackBlock的block会被NSConcreteMallocBlock类型的block替代。证明方式是以下代码在XCode中,会输出 <NSMallocBlock: 0x100109960>。在苹果的官方文档中也提到,当把栈中的block返回时,不需要调用copy方法了。
</h6>

#import <Foundation/Foundation.h> 
 
int main(int argc, const char * argv[]) 
{ 
    @autoreleasepool { 
        int i = 1024; 
        void (^block1)(void) = ^{ 
            printf("%d\n", i); 
        }; 
        block1(); 
        NSLog(@"%@", block1); 
    } 
    return 0; 
} 

我个人认为这么做的原因是,由于ARC已经能很好地处理对象的生命周期的管理,这样所有对象都放到堆上管理,对于编译器实现来说,会比较方便。

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

推荐阅读更多精彩内容

  • 前言 Blocks是C语言的扩充功能,而Apple 在OS X Snow Leopard 和 iOS 4中引入了这...
    小人不才阅读 3,757评论 0 23
  • 摘要block是2010年WWDC苹果为Objective-C提供的一个新特性,它为我们开发提供了便利,比如GCD...
    西门吹雪123阅读 903评论 0 4
  • Block基础回顾 1.什么是Block? 带有局部变量的匿名函数(名字不重要,知道怎么用就行),差不多就与C语言...
    Bugfix阅读 6,748评论 5 61
  • Blocks Blocks Blocks 是带有局部变量的匿名函数 截取自动变量值 int main(){ ...
    南京小伙阅读 911评论 1 3
  • 2.1 Blcoks概要 2.1.1 什么是Blocks Blocks是C语言的扩充功能——“带有自动变量(即局部...
    SkyMing一C阅读 2,320评论 6 18