Block简介

      Block是C语言的扩充功能。一言以蔽之,带有局部变量的匿名函数,简单说它就是一个函数指针。

一、语法:

格式:Block变量 = Block值

具体形式如下:

声明并赋值:返回值类型(^变量名)(参数列表) = ^返回值类型 参数列表 表达式;

调用:变量名(参数列表);

使用实例1:

// 声明Block变量,并赋值

void(^name)(int num) = ^void(int num){ NSLog(@"%d", ++num); };

// 使用Block变量

name(6);

使用实例2:

// 定义Block类型

typedef void(^BlockType)(int num);

// 用定义的类型声明变量,并赋值

BlockType name = ^void(int num){ NSLog(@"%d", ++num); };

// 调用

name(8);

二、Block的种类

2.1、NSGlobalBlock

a.全局区域创建的Block,永远是 NSGlobalBlock

b.局部区域创建的Block,有指针变量引用,不访问局部变量/成员变量,是 NSGlobalBlock

d.局部区域创建的Block,没有指针变量引用,不访问局部变量/成员变量,是 NSGlobalBlock

2.2、NSMallocBlock

c.局部区域创建的Block,有指针变量引用,访问局部变量/成员变量,是 NSMallocBlock

2.3、NSStackBlock

e.局部区域创建的Block,没有指针变量引用,访问局部变量/成员变量,是 NSStackBlock

对于d\e两种情况的一般用法,如下:

// 1> 作为方法参数,如:[self methodBlock:^{ NSLog(@">>>"); }];

// 2> 创建后直接调用(一般不这么用),如:^{ NSLog(@">>>"); }();

三、实质:

获取源码的方法:

1> 创建Command Line Tool工程。

2> 添加Block相关代码。

3> 打开终端并CD到main.m文件所在的文件夹。

4> 执行命令:clang -rewrite-objc main.m,该文件夹下会多出main.cpp文件,该文件的末尾就是相关源码。

以下是5种比较典型的使用情况的源码:

情况1:局部Block不访问Block外部的变量

// OC代码

// main函数中,声明局部Block变量,并赋值,Block的实现只是打印一个字符串,然后调用该Block。

// clang编译后的相关代码

// 该结构体和Block相关,我们只关注两个属性:isa && FuncPtr

// isa:表示Block的类型

// FuncPtr:表示Block的实现(其实是个函数指针)

// 该结构体有两个属性和一个构造函数

// impl:和Block相关的结构体,其结构体指针和__main_block_impl_0结构体指针相同(因为它是第一个成员变量,固它们的首地址相同)

// Desc:是个结构体,对结构体__main_block_impl_0的描述,即开辟内存空间时的依据

// __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0):构造函数,该函数需要两个参数,分别是函数指针和描述性的结构体指针

// 静态函数,其实就是Block的对应实现

// 结构体实例,是__main_block_impl_0结构体的描述

// 主函数,函数内部

// 1、声明了结构体指针变量并赋值

// 2、根据该指针找到对应的函数,调用了该函数

// 大概流程就是:

1、从 main 函数开始

1.1、声明结构体指针block,然后调用__main_block_impl_0结构体的构造函数,传入参数__main_block_func_0函数指针(函数首地址,函数名即函数首地址)和__main_block_desc_0_DATA指针,得到__main_block_impl_0结构体,并将得到的结构体的地址赋值给block。

1.2、block就是__main_block_impl_0结构体的首地址,也是__block_impl结构体的首地址,所以根据block->FuncPtr,可以拿到对应函数指针,然后调用该函数,该函数打印一个字符串。

情况2:局部Block读取其外部的局部变量

// OC代码

// main函数中,声明局部变量 num 并赋值,声明局部Block变量,并赋值,Block的实现打印一个字符串和 num 值,然后调用该Block。

// clang编译后的相关代码

// 和情况1一样

// 比情况1多了一个 num 变量,构造函数也多了一个对应该变量的参数

// 该函数内部声明一个局部变量,并根据__cself获取对应的值赋给该变量,然后打印相应的东西

// 注意:根据OC对象的定义,对象其实就是结构体指针,所以这里__cself是一个Block对象

// 和情况1一样

// 主函数,函数内部

// 1、声明变量 num 并赋值 666

// 2、声明了结构体指针变量并赋值

// 3、根据该指针找到对应的函数,调用了该函数

// 大概流程就是:

1、从 main 函数开始

1.1、声明变量 num 并赋值 666

1.2、声明结构体指针block,然后调用__main_block_impl_0结构体的构造函数,传入参数__main_block_func_0函数指针(函数首地址,函数名即函数首地址)和__main_block_desc_0_DATA指针和num,得到__main_block_impl_0结构体,并将得到的结构体的地址赋值给block。

1.3、block就是__main_block_impl_0结构体的首地址,也是__block_impl结构体的首地址,所以根据block->FuncPtr,可以拿到对应函数指针,然后调用该函数,该函数打印一个字符串。

情况3:局部Block修改其外部的局部变量

// OC代码

//main函数中,声明__block修饰的局部变量 num 并赋值,声明局部Block变量,并赋值,Block的实现打印一个字符串和 ++num 值,然后调用该Block。

// clang编译后的相关代码

// 和情况1一样

// num 变量转变的结构体

// 对比情况2,这里把int类型的num变量,换成了__Block_byref_num_0类型的结构体指针(即对象),这里__block修饰后的num变量被编译成了对象

// __cself对象中获取num对象,又从num对象中获取__forwarding对象,然后从__forwarding对象中获取num变量,执行++num之后打印输出最后的值

// __forwarding就是一个指向自身结构体的首地址的结构体指针,这样做的目的就是在把变量从栈区复制到堆区的时候,不论变量在哪一个存储区域,都可以正确访问其对应的变量值

// 复制对象(引用计数+1)

// 处理对象(引用计数-1)

// 结构体实例,是__main_block_impl_0结构体的描述,对比情况2,成员变量中多了两个函数指针,这两个函数指针就是结构体指针(即对象)的内存管理函数,系统会在适当的时候自动调用它们

// 主函数,函数内部

// 1、声明结构体变量 num 并赋了相关的值

// 2、声明了结构体指针变量block并赋值,这里调用构造函数创建该结构体的时候,第三个参数是num的结构体指针,即num对象

// 3、根据该指针找到对应的函数,调用了该函数

// 大概流程就是:

1、从 main 函数开始

1.1、声明结构体变量 num 并赋了相关的值

1.2、声明结构体指针block,然后调用__main_block_impl_0结构体的构造函数,传入参数__main_block_func_0函数指针(函数首地址,函数名即函数首地址)和__main_block_desc_0_DATA指针和num对象,得到__main_block_impl_0结构体,并将得到的结构体的地址赋值给block。

1.3、block就是__main_block_impl_0结构体的首地址,也是__block_impl结构体的首地址,所以根据block->FuncPtr,可以拿到对应函数指针,然后调用该函数,该函数打印相关值。

情况4:局部Block访问(读取&修改)全局变量

// OC代码

// 声明全局int类型的变量num,并赋值6

// main函数中,声明局部Block变量,并赋值,Block的实现打印一个字符串和 num 值,然后调用该Block

// clang编译后的相关代码

// 和情况1一样

// 声明全局变量num并赋初始值6。

// 和情况1一样

// 和情况1一样,只是多了num变量的打印,num值来自全局变量

// 和情况1一样

// 和情况1一样

// 大概流程就是:

1、从 main 函数开始

1.1、声明结构体指针block,然后调用__main_block_impl_0结构体的构造函数,传入参数__main_block_func_0函数指针(函数首地址,函数名即函数首地址)和__main_block_desc_0_DATA指针,得到__main_block_impl_0结构体,并将得到的结构体的地址赋值给block。

1.2、block就是__main_block_impl_0结构体的首地址,也是__block_impl结构体的首地址,所以根据block->FuncPtr,可以拿到对应函数指针,然后调用该函数,该函数打印一个字符串。

情况5:全局Block访问(读取&修改)全局变量

// OC代码

//全局Block读取全局变量

//全局Block修改全局变量

// main函数调用Block

// clang编译后的相关代码

// 和情况4一样

// 和情况4一样

// 和情况4一样

// 和情况4一样

// 和情况4一样

// 调用对应的函数构造__blk1_block_impl_0静态结构体

// 把上面的结构体指针(对象)赋值给blk1变量

// main函数,调用全局blk1

// 大概流程就是:

1、从 main 函数开始

1.1、直接调用代表Block实例的全局的结构体指针

综上可知:

1、Block其实就是OC所谓的实例,每一个Block,都会被编译为对应的结构体指针,通过这个结构体指针,可以找到对应的函数实现以及Block内部访问的外部的局部变量或成员变量。

2、Block有3种类型:_NSConcreteGlobalBlock(全局)、_NSConcreteStackBlock(栈)、__NSConcreteMallocBlock(堆)

3、访问全局变量或者不访问任何外部变量的Block变量(注意是变量,不是实现),永远都是全局Block

4、参数形式出现的栈Block,如果在方法内部被一个新Block指针变量引用,那么该栈Block被会复制一份到堆区,新Block指针指向这个堆Block

5、__block修饰后的变量,该变量会被编译为一个结构体,且该结构体包含这个变量,并以结构体指针(对象)的形式被访问,因此当一个变量没有被__block修饰时,Block内部捕获的是该变量的值,被__block修饰时,Block内部捕获的是包含该变量的对象

四、总结

      归根结底,Block就是一个包含函数实现和所使用的变量(如果需要的话)的结构体指针,即OC对象,所以Block可以被当做全局变量、局部变量、成员变量、参数、返回值,来回传递使用,更重要的是,它能携带函数的实现(函数指针),这意味着它能封装一段代码块,被来回传递,在适当的时候调用执行,执行的同时也可以修改它携带的变量值,起到异地异步传值的的目的。

      因为Block是个OC对象,当它本身作为一个对象的成员变量时,而其内部又引用这个对象或者这个对象的成员变量和方法的时候(或者多个对象依次引用构成了循环链),就会导致循环引用,从而导致内存问题。这种情况,一般用 __weak 弱化 self 指针就可以了。这里不细说各种循环引用的问题。

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

推荐阅读更多精彩内容

  • Blocks Blocks Blocks 是带有局部变量的匿名函数 截取自动变量值 int main(){ ...
    南京小伙阅读 907评论 1 3
  • 前言 Blocks是C语言的扩充功能,而Apple 在OS X Snow Leopard 和 iOS 4中引入了这...
    小人不才阅读 3,752评论 0 23
  • Block基础回顾 1.什么是Block? 带有局部变量的匿名函数(名字不重要,知道怎么用就行),差不多就与C语言...
    Bugfix阅读 6,744评论 5 61
  • block.png iOS代码块Block 概述 代码块Block是苹果在iOS4开始引入的对C语言的扩展,用来实...
    全栈农民工阅读 588评论 0 1
  • Block使用场景,可以在两个界面的传值,也可以对代码封装作为参数的传递等。用过GCD就知道Block的精妙之处。...
    Coder_JMicheal阅读 716评论 2 1