Block学习笔记

什么是Block

Block是对C语言的扩展,可以简单总结为“带有局部变量的匿名函数”,它类似于js中的闭包,是一种不需要定义名字、可以在使用的时候临时定义、并且能够访问不是在Block内部定义的全局/局部/静态变量的"函数"。目前Block已经广泛应用于iOS开发中,常用于GCD、动画及各类回调。

Block的声明、赋值与调用

Block的声明

// Block声明的一般格式为:返回值类型(^Block名)(参数列表);
// eg: 声明一个没有返回值类型,有一个int类型参数叫做block_1的block

void (^block_1)(int a);

// 其中形参名字可以省略
void (^block_1)(int);

Block的赋值

// Block赋值的一般格式为 xxBlock = ^(参数列表){ Block体};

// eg: 给一个没有返回值类型,有一个int类型参数叫做block_1的block赋值

block_1 = ^(int a) {
  NSLog(@"%d", a);
};

// 这里一般会将返回值类型省略,编译器可以从block体中确定返回值类型

// 我们可以在声明一个block的时候同时给它赋值

// eg: 声明一个没有返回值类型,有一个int类型参数叫做block_1的block并给它赋值

void (^block_1) (int a) = ^(int a) {
  NSLog(@"%d", a);
};

Block的调用

// Block调用的一般格式为 xxBlock(参数列表);

// eg: 调用一个没有返回值类型,有一个int类型参数叫做block_1的block

block_1(20);

使用typedef定义Block类型

如果想要声明多个具有相同返回值类型、相同参数列表的block,按照上面的声明方式来做的话就要写很多繁琐的代码,这时我们可以使用typdef来定义block类型。

typedef void(^commonBlock)(int a);

// 通过commonBlock这个别名来声明一系列相似的block

commonBlock block_2;
commonBlock block_3;

// 相当于
void(^block_2)(int a);
void(^block_3)(int a);

Block作为函数的参数

Block作为C函数的参数

// 1.定义一个形参为Block的C函数
void useBlockForC(int(^aBlock)(int, int))
{
    NSLog(@"result = %d", aBlock(10,10));
}

// 2.声明并赋值定义一个Block变量
int(^addBlock)(int, int) = ^(int x, int y){
    return x + y;
};

// 3.以Block作为函数参数,把Block像对象一样传递
useBlockForC(addBlock);

// 集合一下2、3两点
useBlockForC(^(int x, int y) {
    return x + y;
});

// 最终结果 打印输出 result = 20

Block作为OC函数的参数

// 1.定义一个形参为Block的OC函数
- (void)useBlockForOC:(int(^aBlock)(int, int))aBlock
{
    NSLog(@"result = %d", aBlock(10,10));
}

// 2.声明并赋值定义一个Block变量
int(^addBlock)(int, int) = ^(int x, int y){
    return x + y;
}; 

// 3.以Block作为函数参数,把Block像对象一样传递
[self useBlockForOC:addBlock];

// 集合一下2、3两点
[self useBlockForOC:^(int x, int y){
    return x + y;
}];

// 最终结果 打印输出 result = 20

Block内访问外部变量

Block内访问局部变量

int tempValue = 10;
void (^block_1) (void) = ^{
  NSLog(@"in block tempValue is %d", tempValue);
};
block_1();

// 打印输出 in block tempValue is 10
        

原理解析

我们进入到main.m所在文件的目录,用Clang命令clang -rewrite-objc main.m可以将.m文件重新转成.cpp文件,转换后的main.cpp文件大概有近10W行样子,将光标移到最后往上找可以看到一个main函数

int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
        int tempValue = 10;
        void (*block_1) (void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, tempValue));

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

    }
        return 0;
}

对照原来OC代码

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        int tempValue = 10;
        void (^block_1) (void) = ^{
            NSLog(@"in block tempValue is %d", tempValue);
        };
        
        block_1();

    }
        return 0;
}

我们可以看到block的定义转换成C++代码后变成了

void (*block_1) (void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, tempValue));

可以看到Block实际上就是一个指向结构体__main_block_impl_0的指针,其中第三个元素是局部变量tempValue的值,在main.cpp文件中全局搜索__main_block_impl_0可以看到__main_block_impl_0结构如下

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

调用block_1的代码如下

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

可以看到block的调用实际上是指向结构体的指针block_1访问其FuncPtr元素,在定义block时为FuncPtr元素传进去的是__main_block_func_0方法,我们在搜索这个方法

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

可以看到在block定义的时候就将局部变量tempValue的值传了进去,所以在当tempValue在调用block之前改变并不会影响到block内部的值,并且在block内部是无法对其进行修改的。

总结

在Block定义时便将局部变量的值传给Block所指向的结构体,因此在调用Block之前对局部变量进行修改并不会影响Block内部的值,同时内部的值也是不可修改的

Block内访问__block修饰的局部变量

  • 在局部变量前使用__block修饰,在声明Block之后、调用Block之前对局部变量进行修改,此时在调用Block局部变量的值是修改后新的值
__block int tempValue = 10;
void (^block_1) (void) = ^{
  NSLog(@"in block tempValue is %d", tempValue);
};
block_1();

// 打印输出 in block tempValue is 20
        

原理解析

同样我们再次用Clang命令将main.m文件转成main.cpp文件,找到main函数

int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
        __attribute__((__blocks__(byref))) __Block_byref_tempValue_0 tempValue = {(void*)0,(__Block_byref_tempValue_0 *)&tempValue, 0, sizeof(__Block_byref_tempValue_0), 10};
        void (*block_1) (void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_tempValue_0 *)&tempValue, 570425344));

        (tempValue.__forwarding->tempValue) = 20;
        ((void (*)(__block_impl *))((__block_impl *)block_1)->FuncPtr)((__block_impl *)block_1);

    }
        return 0;
}

我们发现用__block修饰的局部变量的定义变成了__attribute__((__blocks__(byref))) __Block_byref_tempValue_0 tempValue = {(void*)0,(__Block_byref_tempValue_0 *)&tempValue, 0, sizeof(__Block_byref_tempValue_0), 10};这一串代码,全局搜索__Block_byref_tempValue_0可以看到是一个如下的结构体

struct __Block_byref_tempValue_0 {
  void *__isa;
__Block_byref_tempValue_0 *__forwarding;
 int __flags;
 int __size;
 int tempValue;
};

再来看block的定义发现,在block定义的时候我们传入的不再是tempValue这个局部变量的值,而是一个指向tempValue的一个指针,所以在block内部是可以修改这个局部变量的值并且当block外部tempValue的值改变block内部也会跟着改变。

总结

使用__block修饰局部变量,在Block定义时便将局部变量的指针传给Block所指向的结构体,因此在调用Block之前对局部变量进行修改会影响Block内部的值,同时Block内部的值也是可以修改的

Block内访问全局变量

  • 在block中可以访问全局变量
int tempValue = 10;
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        void (^block_1) (void) = ^{
            NSLog(@"in block tempValue is %d", tempValue);
        };
        
        tempValue = 20;
        block_1();

    }
        return 0;
}

// 打印输出 in block tempValue is 20

原理解析

同样我们再次用Clang命令将main.m文件转成main.cpp文件,找到main函数

int tempValue = 10;

int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
        void (*block_1) (void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));

        tempValue = 20;
        ((void (*)(__block_impl *))((__block_impl *)block_1)->FuncPtr)((__block_impl *)block_1);

    }
        return 0;
}

观察上述代码我们可以发现在block定义的时候并没有将全局变量的值或则会指针传入进去我们再来观察下__main_block_func_0方法

static void __main_block_func_0(struct __main_block_impl_0 *__cself) {

            NSLog((NSString *)&__NSConstantStringImpl__var_folders_f0_sl9z3g2x12l7pns1wx_0yyx00000gn_T_main_08df78_mi_0, tempValue);
        }

可以看到输出打印的是全局变量tempValue的值。

总结

全局变量所占用的内存只有一份,供所有函数共同调用,在Block定义时并未将全局变量的值或者指针传给Block所指向的结构体,因此在调用Block之前对局部变量进行修改会影响Block内部的值,同时内部的值也是可以修改的

Block内访静态变量

  • 在Block中可以访问静态变量
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        static int tempValue = 10;
        void (^block_1) (void) = ^{
            NSLog(@"in block tempValue is %d", tempValue);
        };
        
        tempValue = 20;
        block_1();

    }
        return 0;
}

// 打印输出 in block tempValue is 20

原理解析

同样我们再次用Clang命令将main.m文件转成main.cpp文件,找到main函数

int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
        static int tempValue = 10;
        void (*block_1) (void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, &tempValue));

        tempValue = 20;
        ((void (*)(__block_impl *))((__block_impl *)block_1)->FuncPtr)((__block_impl *)block_1);

    }
        return 0;
}

我们可以看到在block定义的时候传入的是tempValue的地址,调用block实际上是指向结构体的指针block_1访问其FuncPtr元素,我们接着看__main_block_func_0

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

            NSLog((NSString *)&__NSConstantStringImpl__var_folders_f0_sl9z3g2x12l7pns1wx_0yyx00000gn_T_main_6fa3db_mi_0, (*tempValue));
        }

可以看到NSLog的tempValue正是定义Block时为结构体传进去的静态变量tempValue的指针。

总结

在Block定义时便将静态变量的指针传给Block所指向的结构体,因此在调用Block之前对静态变量进行修改会影响Block内部的值,同时内部的值也是可以修改的

Block的内存管理

  • 由于现在工程主要都是在ARC环境下,所以主要讨论ARC环境下Block的内存管理
  • 在ARC默认情况下,Block的内存存储在堆中,ARC会自动进行内存管理,程序员只需要避免循环引用即可
  • 当Block变量出了作用域,Block的内存会被自动释放
void(^block_1)() = ^{
    NSLog(@"block called");
};
block_1();

// 当block_1执行完成后系统会自动释放其内存
  • 在Block的内存存储在堆中时,如果在Block中引用了外面的对象,会对所引用的对象进行强引用,但是在Block被释放时会自动去掉对该对象的强引用,所以不会造成内存泄漏(循环引用)
Person *p = [[Person alloc] init];
        
void(^ block_1)() = ^{
    NSLog(@"%@", p);
};
block_1();
        
// Person对象在这里可以正常被释放
  • 如果对象内部有一个Block属性,而在Block内部又访问了该对象,那么会造成循环引用
@interface Person: NSObject

@property (nonatomic, copy) void(^block_1)();

@end

@implementation Person

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

@end


Person *p = [[Person alloc] init];
        
p.block_1 = ^{
    NSLog(@"%@", p);
};
p.block_1();
        
// 因为block_1作为Person的属性,采用copy修饰符修饰(保证Block在堆里面,以免Block在栈中被系统释放),所以Block会对Person对象进行一次强引用,导致循环引用无法释放

循环引用解决方案

Person *person = [[Person alloc] init];

// 这里还可以使用__weak typeof(Person) *weakPerson = person;
__weak Person *weakPerson = person;

person.block_1 =  ^{
    NSLog(@"%@", weakPerson);
};

person.block_1();

这样Person对象就可以正常被释放

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

推荐阅读更多精彩内容

  • Block基础回顾 1.什么是Block? 带有局部变量的匿名函数(名字不重要,知道怎么用就行),差不多就与C语言...
    Bugfix阅读 6,744评论 5 61
  • 前言 Blocks是C语言的扩充功能,而Apple 在OS X Snow Leopard 和 iOS 4中引入了这...
    小人不才阅读 3,757评论 0 23
  • block.png iOS代码块Block 概述 代码块Block是苹果在iOS4开始引入的对C语言的扩展,用来实...
    全栈农民工阅读 588评论 0 1
  • 米大王是我对妈妈的特殊昵称 在我家,爸爸叫大豆(因为dad是D开头),妈妈叫大米(因为mum是M开头) 而我自己是...
    毛嘟子阅读 628评论 0 2
  • 爸,你知道吗?我是多么的想你现在是个健健康康的人,能快快乐乐的活着。我知道你对这个世界还很眷恋。现在是该轮到你享福...
    潘潘的成长之路阅读 293评论 4 1