Object-C高级编程Blocks

  • 什么是Blocks
  • 代码块高亮
  • 图片链接和图片上传
  • LaTex数学公式
  • UML序列图和流程图
  • 离线写博客
  • 导入导出Markdown文件
  • 丰富的快捷键

什么是Blocks

可以用一句话来表示Blocks的扩充功能:带有局部变量的匿名函数。

###Blocks语法
Blocks完整表达式语法:
^返回值类型(参数列表) {
}
如果无返回值类型或者无参数可以缩写:
^ {
}

###Block类型变量
先看C语言中函数func地址赋值给函数指针类型变量funcptr:

int func(int count) {
	return count + 1;
}
int (*funcptr)(int) = &func;

在Block语法下,可将Block赋值给声明为Block类型的变量:

int (^funcptr)(int) = ^int(int count) {};

Block类型变量用途:

  • 自动变量
  • 函数参数
  • 静态变量
  • 静态全局变量
  • 全局变量

Block作为函数参数

void func(int (^blk) (int)) {
}

Block作为返回类型

int (^ func() (int)) {
	return ^(int count) {return count + 1;};
}

用typedef可以简写

typedef int (^ blk_t) (int);

通过typedef可声明"blk_t"类型变量;void func(int (^blk) (int)) {};改写为

void func(blk_t blk) {};
int (^ func() (int)) {};

改写为

blk_t func() {};

##截获自动变量

- (void)func {
	int val = 10;
	void (^blk)() = ^{NSLog(@"%d", val);};
	val = 2;
	blk();
}

函数输出为10,而是执行Block语法的自动变量的瞬间值;
##__block说明符
自动变量值截获只能保存执行block语法执行的瞬间值。保存后就不能修改该值。弱想在上面的block语法中实现block内赋值,需要用到__block说明符

__block int val = 10;
void(^blk)() = ^{val = 2; NSLog(@"%d", val);};

##截获的自动变量

#Block的实现
##Block实质
Block是“带有自动变量的匿名函数”,但Block究竟是什么呢?通过clang(LLVM编译器)具有转换为我们可读源代码的功能。通过“-rewrite-objc”选项就能将含有Block语法的源代码变换为C++源代码
clang -rewrite-objc 源代码名称

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

通过clang可变换为以下形式:

struct __block_impl {
	void *isa;
	int Flags;
	int Reserved;
	void *FuncPtr;
};

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;
	}
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
	print("Block\n");
}
static struct __main_block_desc_0 {
	unsigned long reserved;
	unsigned long Block_size;
`} __main_block_desc_0_Data {
	0,
	sizeof(struct __main_block_impl_0)
}
int main() {
	void (*block)(void) = (void * (void))&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA);
	((void (*) struct __block_impl *)) ((struct __block_impl *)blk) ->FuncPtr)((struct __block_impl *)blk);
	return 0;
}

先将源代码分成几部分逐步理解,首先看源代码中的Block语法:

^{printf("Block\n");};

可以看到变换后的源代码

static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
	printf("Block\n");
}

通过Block使用的匿名函数实际上被作为简单的c语言来处理。另外,根据Block语法所属的函数名(此处为main)和该Block语法在函数出现的顺序值(此处为0)来给经clang变换的函数命名。
该函数的参数__cself,为指向Block值得变量。我们先看看该参数的声明。

struct __main_block_impl_0 *__cself

与C++的this和OC的self相同,参数__cself是__main_block_impl_0结构体的指针。
该结构体声明如下:

struct __main_block_impl_0 {
	struct __block_impl impl;
	struct __main_block_impl_desc_0* Desc;
}

由于转换后的源代码中,也一并写入了其构造函数,所以看起来复杂,如果去除该构造函数,__main_block_impl_0结构体将变得非常简单:

struct __block_impl {
	void *isa;
	int Flags;
	int Reserved;
	void *FuncPtr;
};

第二个成员变量为Desc指针,__main_block_impl_desc_0结构体声明:

struct __main_blok_impl_desc_0 {
	unsigned long reserved;
	unsigned long Block_size;
};

其成员名称为今后版本升级所需要的区域和Block大小。
初始化含有这些结构体的__main_block_impl_0结构体的构造函数。

__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) {
	impl.isa = NSConcreteStackBlock;
	impl.Flags = flag;
	impl.FuncPtr = fp;
	Desc = desc;
}

NSConcreteStackBlock表明该Block在栈上,我们先看看该构造函数的调用。

void(*blk)(void) = (void(*)(void)&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA);

因为转换较多,看起来不是很清楚,去掉转换部分如下:

struct __main_block_impl_0 tmp = __main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA);
struct __main_block_impl_0 *blk = &tmp;

这样就容易理解了。该源代码将__main_block_impl_0结构体类型的自动变量,即栈上生成的__main_block_impl_0结构体实例的指针,赋值给__main_block_impl_0结构体指针类型的变量blk,以下为对应的源代码。

void (^blk)(void) = ^{print("Block\n");};

栈上生成的__main_block_impl_0结构体实例指针赋值给__main_block_impl_0结构体指针类型的变量blk。该源码的Block就是__main_block_impl_0结构体类型的自动变量。
下面看看__main_block_impl_0结构体实例构造参数。

__main_block_impl_0(__main_block_func_0, &__main_block_desc_0_Data);

第一个参数是C语言函数指针。为Block匿名函数在C语言中的体现。第二个参数为静态全局变量初始化的__main_bloock_desc_0结构体实例指针。以下为初始化代码。

static struct __main_block_desc_0 __main_block_desc_0_DATA = {
	0,
	sizeof(struct __main_block_impl_0)
};

由此可知,该源代码使用Block,即__main_block_impl_0结构体的实例大小,进行初始化。
下面看看栈上的__main_block_impl_0结构体(即Block)是如何根据这些参数进行初始化的,如果展开__main_block_impl_0结构体的__block_impl结构体,可记为如下形式:

struct __main_block_impl_0 {
	void *isa;
	int Flags;
	int Reserved;
	void *FuncPtr;
	struct __main_block_desc_0* Desc;
}

该结构体根据构造函数像下面这样进行初始化。

isa = &_NSConcreteStackBlock;
Flags = 0;
Reserved = 0;
FuncPtr = __main_block_func_0;
Desc = &__main_block_desc_0_DATA;

再来看看使用该Block部分。

blk();

这部分变换为以下源码:

((void (*))(struct __block_impl *))((struct __block _block_impl *)blk)->FuncPtr)((struct __block_impl *)blk);

去掉转换部分。

(*blk->impl.FuncPtr)(blk);

这就是简单的函数指针调用函数,由Block语法转换的__main_block_func_0函数的指针被赋值给成员变量FuncPtr中,另外也说明了__main_block_func_0函数参数__cself指向Block值
##截获自动变量

- (void)func {
	int val = 10;
	void (^blk)() = ^{NSLog(@"%d", val);};
	val = 2;
	blk();
}

代码通过clang进行转换。

struct __main_block_impl_0 {
	struct __block_impl impl;
	struct __main_block_desc_0 *Desc;
	int val;

	__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _val, int flags=0) : val(_val) {
		impl.isa = &_NSConcreteStackBlock;
		impl.Flags = flags;
		impl.FuncPtr = fp;
		Desc = desc;
	}
};
static void __main_func_0(struct __main_block_impl_0 *__cself) {
	int val = __cself->val;
	printf(val);
}
static struct __main_block_desc_0 {
	unsigned long reserved;
	unsigned long Block_size;
} __main_block_desc_0_Data = {
	0,
	sizeof(struct __main_block_impl_0)
};
int main() {
	int val = 10;
	void(*blk)(void) = &__main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA, val);
	return 0;
}

Block语法表达式中使用的自动变量被作为成员变量追加到__main_block_impl_0结构体中。

struct __main_block_impl_0 {
	struct __block_impl impl;
	struct __main_block_desc_0 *Desc;
	int val;
	}

Block语法表达式中没有使用的自动变量不会被追加,Block的自动变量截获只针对Block中使用的自动变量。下面看看初始化该结构体的构造函数的差异。

__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _val, int flags=0) : val(_val) {}

使用执行Block语法时的自动变量val和初始化__main_block_impl_0结构体实例。

	impl.isa = &_NSConcreteStackBlock;
	impl.Flags = 0;
	impl.FuncPtr = __main_block_func_0;
	Desc = &__main_block_desc_0_DATA;
	val = 10;

由此可知,在__main_block_impl_0结构体实例(Block)中,自动变量被截获。下面再来看Block匿名函数实现

^{printf(val);}

该源代码转化为以下函数:

static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
	int val = __cself->val;
	printf(val);
}

在转换后的源码中,截获的__main_block_impl_0结构体实例的成员变量上的自动变量,这些变量在Block语法表达式之前被声明定义。因此,原来的源代码表达式无需改动便可以使用截获的自动变量执行。
##__block说明符
我们再来回顾截取自动变量的例子

^{printf(val);}

该源码转换结果如下:

static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
	int val = __cself->val;
	printf(val);
}

Block中使用的被截获的自动变量就如“带有自动变量的匿名函数”,在Block结构体实例中重写该自动变量也不会改变原先截获的自动变量。自动变量val虽然被捕获进来了,但是是用 __cself->val来访问的。Block仅仅捕获了val的值,并没有捕获val的内存地址。所以在__main_block_func_0这个函数中即使我们重写这个自动变量val的值,依旧没法去改变Block外面自动变量val的值。
OC可能是基于这一点,在编译的层面就防止开发者可能犯的错误,因为自动变量没法在Block中改变外部变量的值,所以编译过程中就报编译错误

int val = 0;
void (^blk)(void) = ^{val = 1;};
#import <Foundation/Foundation.h>
 
int global_i = 1;
 
static int static_global_j = 2;
 
int main(int argc, const char * argv[]) {
 
    static int static_k = 3;
    int val = 4;
 
    void (^myBlock)(void) = ^{
        global_i ++;
        static_global_j ++;
        static_k ++;
        NSLog(@"Block中 global_i = %d,static_global_j = %d,static_k = %d,val = %d",global_i,static_global_j,static_k,val);
    };
 
    global_i ++;
    static_global_j ++;
    static_k ++;
    val ++;
    NSLog(@"Block外 global_i = %d,static_global_j = %d,static_k = %d,val = %d",global_i,static_global_j,static_k,val);
 
    myBlock();
 
    return 0;
}

运行结果

Block 外  global_i = 2,static_global_j = 3,static_k = 4,val = 5
Block 中  global_i = 3,static_global_j = 4,static_k = 5,val = 4

这里就有2点需要弄清楚了
1.为什么在Block里面不加__bolck不允许更改变量?
2.为什么自动变量的值没有增加,而其他几个变量的值是增加的?自动变量是什么状态下被block捕获进去的?
为了弄清楚这2点,我们用clang转换一下源码出来分析分析。

int global_i = 1;
 
static int static_global_j = 2;
 
struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  int *static_k;
  int val;
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int *_static_k, int _val, int flags=0) : static_k(_static_k), val(_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_k = __cself->static_k; // bound by copy
  int val = __cself->val; // bound by copy
 
        global_i ++;
        static_global_j ++;
        (*static_k) ++;
        NSLog((NSString *)&__NSConstantStringImpl__var_folders_45_k1d9q7c52vz50wz1683_hk9r0000gn_T_main_6fe658_mi_0,global_i,static_global_j,(*static_k),val);
    }
 
static struct __main_block_desc_0 {
  size_t reserved;
  size_t Block_size;
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};
 
 
int main(int argc, const char * argv[]) {
 
    static int static_k = 3;
    int val = 4;
 
    void (*myBlock)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, &static_k, val));
 
    global_i ++;
    static_global_j ++;
    static_k ++;
    val ++;
    NSLog((NSString *)&__NSConstantStringImpl__var_folders_45_k1d9q7c52vz50wz1683_hk9r0000gn_T_main_6fe658_mi_1,global_i,static_global_j,static_k,val);
 
    ((void (*)(__block_impl *))((__block_impl *)myBlock)->FuncPtr)((__block_impl *)myBlock);
 
    return 0;
}

首先全局变量global_i和静态全局变量static_global_j的值增加,以及它们被Block捕获进去,这一点很好理解,因为是全局的,作用域很广,所以Block捕获了它们进去之后,在Block里面进行++操作,Block结束之后,它们的值依旧可以得以保存下来。

接下来仔细看看自动变量和静态变量的问题。
在__main_block_impl_0中,可以看到静态变量static_k和自动变量val,被Block从外面捕获进来,成为__main_block_impl_0这个结构体的成员变量了。
接着看构造函数,

__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int *_static_k, int _val, int flags=0) : static_k(_static_k), val(_val)

这个构造函数中,自动变量和静态变量被捕获为成员变量追加到了构造函数中。main里面的myBlock闭包中的__main_block_impl_0结构体,初始化如下

void (*myBlock)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, &static_k, val));
impl.isa = &_NSConcreteStackBlock;
impl.Flags = 0;
impl.FuncPtr = __main_block_impl_0; 
Desc = &__main_block_desc_0_DATA;
*_static_k = 4;
val = 4;

再研究一下源码,我们注意到__main_block_func_0这个函数的实现

static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  int *static_k = __cself->static_k; // bound by copy
  int val = __cself->val; // bound by copy
	  global_i ++;
      static_global_j ++;
      (*static_k) ++;
      NSLog((NSString *)&__NSConstantStringImpl__var_folders_45_k1d9q7c52vz50wz1683_hk9r0000gn_T_main_6fe658_mi_0,global_i,static_global_j,(*static_k),val);
    }

回到上面的例子上面来,4种变量里面只有静态变量,静态全局变量,全局变量这3种是可以在Block里面被改变值的。仔细观看源码,我们能看出这3个变量可以改变值的原因。
1、静态全局变量,全局变量由于作用域的原因,于是可以直接在Block里面被改变。他们也都存储在全局区。
2、静态变量传递给Block是内存地址值,所以能在Block里面直接改变值。
根据官方文档我们可以了解到,苹果要求我们在自动变量前加入 __block关键字(__block storage-class-specifier存储域类说明符),就可以在Block里面改变外部自动变量的值了。
总结一下在Block中改变变量值有2种方式,一是传递内存地址指针到Block中,二是改变存储区方式(__block)。静态变量的这种方法似乎也适用于自动变量的访问。但我们为什么没有这么做呢?
实际上在由Block语法生成的值Block上,可以存储超过其变量作用域的被截获对象的自动变量。变量作用域结束的同时,原来·的自动变量被废弃,因此静态变量的地址将不能访问。
下面我们来实际用__block说明符,来变更值

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

该代码变换后如下:

struct __Blcok_byref_val_0 {
	void *isa;
	__Blcok_byref_val_0 *__forwarding;
	int __flags;
	int __size;
	int val;
};
struct __main_block_impl_0 {
	struct __block_impl impl;
	struct __block_impl_desc_0 *Desc;
	__Block_byref_val_0 *val;
	__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_blok_func_0(struct __main_block_impl_0 *__cself) {
	__Blcok_byref_val_0 *val = __cself->val;
	(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(&dst->val, src->val, BLOCK_FIELD_IS_BYREF);
}
static void __main_block_dispose_0(struct __main_block_impl_0 *src) {
	_Block_object_dispose(src->val, BLOCK_FIELD_IS_BYREF);
}
static struct __main_block_desc_0 {
	unsigned long reserved;
	unsigned long Block_size;
	void (*copy)(struct __main_blok_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() {
	__Block_byref_val_0 val = {
		0,
		&val,
		0,
		sizeof(__Block_byref_val_0),
		10
	};
	blk = &__main_block_impl_0(__main_block_func_0, &__mian_block_desc_0_DATA, &val, Ox22000000);
	return 0;
}
 __block int val = 10;

这个__block变量val是怎么转换过来的呢?

__Block_byref_val_0 val = {
		0,
		&val,
		0,
		sizeof(__Block_byref_val_0),
		10
	};

我们发现,他竟然变成为了结构体实例,__block变量也同Block一样变成__Block_byref_val_0结构体类型的自动变量,既栈上生成的__Block_byref_val_0结构体实例。该变量初始化为10,且这个值也出现在结构体实例的初始化中,这意味着该结构体持有相当于原自动变量的成员变量。
该结构体声明如下:

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

如同初始化时的源代码一样,该结构体中最后的成员变量val相当于原自动比变量的成员变量

^{val = 1;}

该源代码转换如下:

static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
	__Block_byref_val_0 *val = __cself->val;
	(val->__forwarding->val) = 1;
}

刚刚在Block中向静态变量赋值时,使用了指向该静态变量的指针。而向__block变量赋值要比这个更为复杂。Block的__main_block_impl_0结构体实例持有指向__block变量的__Block_byref_val_0结构体实例的指针。
__Block_byref_val_0结构体实例的成员变量__forwarding持有指向该实例自身的指针。通过成员变量__forwarding访问成员变量val。(成员变量val是该实例自身持有的变量,它相当于原自动变量)
这里写图片描述
__block变量的__Block_byref_val_0结构体并不在Block用__main_block_impl_0结构体中,这样做是为了在多个Block中使用__block变量。

 __block int val = 10;
 void (^blk0)(void) = ^{val = 0;};
 void (blk1)(void) = ^{val = 1;};

Block类型变量blk0和blk1访问__blkvk变量val。

__Block_byref_val_0 val = {0, &val, 0, sizeof(__Block_byref_val_0), 10};
blk0 = &__main_block_impl_0(__main_block_func_), &__main_block_desc_0_DATA, &val, 0x22000000);
blk0 = &__main_block_impl_1(__main_block_func_), &__main_block_desc_1_DATA, &val, 0x22000000);

两个都使用了__Blcok_buref_val_0结构体实例val的指针。这样一来就可以从多个Blcok中使用同一个__block变量。当然,反过来从一个Block中使用多个__block变量也是可以的。只是要增加Block的结构体成员变量与构造函数的参数,便可对应使用多个__block变量。
##Block存储域
通过前面说明可知,Block转换为Block的结构体类型的自动变量,__block变量转换为__block变量的结构体类型的自动变量。所谓结构体类型的自动变量,即栈上生成的该结构体的实例。
Block与__block变量的实质

名称实质
Block栈上Block的结构体实例
__block变量栈上__block变量的结构体实例

另外,通过之前的说明可知Block也是OC对象。将Block当做OC对象来看,该Block的类为_NSConcreteStackBlock。虽然该类并没有出现源代码中,但有很多相似的类,例如:

  • _NSConcreteStackBlock
  • _NSConcreteGlobalBlock
  • _NSConcreteMallocBlock
    _NSConcreteStackBlock的对象Block设置在栈上。_NSConcreteGlobalBlock与全局变量一样,设置在数据区。_NSConcreteMallocBlock类的对象设置在由malloc函数分配的内存块(即堆)中。到现在为止出现的Block例子使用的都是_NSConcreteStackBlock类。且都设置在栈上,但实际上也非全是这样,在记述全局变量的地方使用Block语法时生成的Block为_NSConcreteGlobalBlock类对象。例如:
void (^blk)(void) = ^{printf("Global Block\n");};
int main() {
}

该Block结构体成员isa初始化如下:

 impl.isa = &_NSConcreteStackBlock;

该Block的类为_NSConcreteStackBlock类。此Block即该Block用结构体实例设置在程序的数据区域。因为在使用全局变量的地方不能使用自动变量,所以不存在对自动变量的截获。由此Block用结构体实例的内容不依赖于执行的状态,所以整个程序中只需要一个实例,因此将Block用结构体实例设置在与全局变量相同的数据区域中即可。

typedef int (^blk_t)(int);
for (int rate = 0; rate < 0; ++rate) {
	blk_t blk = ^(int count){return rate * count;};
}

上面Block语法生成的Block用结构体实例在每次for循环中截获的值都不同。但是以下代码中在不截获自动变量时,Block用结构体实例每次截获的值都完全相同。

typedef int (^blk_t)(int);
for (int rate = 0; rate < 0; ++rate) {
	blk_t blk = ^(int count){return count;};
}

也就是说,即使在函数内而不在记述广域变量的地方使用Block语法时,只要Block不截获自动变量,就可以将Block用结构体实例设置在程序的数据区域。
虽然通过clang转换的源代码通常是_NSConcreteStackBlock类对象,但实现上却有不同,总结如下:

-记述全局变量的地方有Bock语法时
-Block语法的表达式中不使用应截获的自动变量时
在以上这些情况下,Block为_NSConcreteGlobalBlock类对象。即Block配置在程序的数据区域中。除此之外的Block语法生成的Block为_NSConcreteStackBlock类对象,且设置在栈上。
配置在全局变量的Block,从变量作用域外也可以通过指针安全地使用。但设置在栈上的Block。但设置在栈上的Block,如果其所属变量作用域结束,该Block就被废弃。由于__block变量也配置在栈上,同样道理。
Block提供了将Block和__block变量从栈上复制到堆上的方法来解决这个问题。将配置在栈上的Block复制到堆上,这样即使Block语法记述的变量作用域结束,堆上Block还可以继续存在。
复制到堆上的Block将_NSConcreteMallocBlock类对象写入Block结构体实例的成员变量isa。

impl.isa = &_NSConcreteMallocBlock;

而__block变量用结构体成员变量__forwarding可以实现无论__block变量配置在栈上还是堆上都能够正确的访问__block变量。
有时在__blokc变量配置在堆上的状态下,也可以访问栈上的__block变量。在此情况下,只要栈上的结构体实例成员变量__forwarding指向堆上的结构体实例,那么不管是从栈上的__block变量还是堆上的__block变量都能够正确访问。
那么Block提供的复制方法究竟是什么呢?实际上当ARC有效时,大多情况编译器会恰当进行判断,自动生成将Block从栈上复制到堆上的代码。

typedef int (^blk_t)(int);
blk_t func(int rate)
{
		return ^(int count){return rate * count;};	
}

该源代码为返回配置在栈上的Block的函数。即程序执行中从该函数返回函数调用方时变量作用域才结束,因此栈上的Block也被废弃

blk_t func(int rate) {
	blk_t temp = &__func_block_impl_0(__func_bock_func_0, &__func_block_desc_0_DATA, rate);
	temp = objc_retainBlock(temp);
	return objc_autoreleaseReturnValue(temp);
}

另外,因为ARC处于有效的状态,所以blk_t temp实际上与附有__string修饰的blk_t __strong temp相同。
objc_retainBlock函数实际上就是Block_copy函数。即:

temp = _Block_copy(temp);
return objc_autoreleaseReturnValue(temp);
/*
 将通过Blcok语法生成的Block,
 即配置在栈上的Block用结构体实例
 赋值给相当于Blcok类型的变量temp中。
 */
 temp = _Block_copy(temp);
 /*
 _Block_copy函数
 将栈上的Block复制到堆上
 复制后,将堆上的地址作为指针赋值给变量temp
 */
 return objc_autoreleaseReturnValue(temp);
 /*
将堆上的Block作为OC对象
注册到autoreleasepool中,然后返回该对象
 */

将Block作为函数返回值时,编译器会自动生成复制到堆上的代码。

  • 加粗 Ctrl + B
  • 斜体 Ctrl + I
  • 引用 Ctrl + Q
  • 插入链接 Ctrl + L
  • 插入代码 Ctrl + K
  • 插入图片 Ctrl + G
  • 提升标题 Ctrl + H
  • 有序列表 Ctrl + O
  • 无序列表 Ctrl + U
  • 横线 Ctrl + R
  • 撤销 Ctrl + Z
  • 重做 Ctrl + Y

Markdown及扩展

Markdown 是一种轻量级标记语言,它允许人们使用易读易写的纯文本格式编写文档,然后转换成格式丰富的HTML页面。 —— [ 维基百科 ]

使用简单的符号标识不同的标题,将某些文字标记为粗体或者斜体,创建一个链接等,详细语法参考帮助?。

本编辑器支持 Markdown Extra ,  扩展了很多好用的功能。具体请参考Github.

表格

Markdown Extra 表格语法:

项目价格
Computer$1600
Phone$12
Pipe$1

可以使用冒号来定义对齐方式:

项目价格数量
Computer1600 元5
Phone12 元12
Pipe1 元234

###定义列表

Markdown Extra 定义列表语法:
项目1
项目2
: 定义 A
: 定义 B

项目3

定义 C

定义 D

定义D内容

代码块

代码块语法遵循标准markdown代码,例如:

@requires_authorization
def somefunc(param1='', param2=0):
    '''A docstring'''
    if param1 > param2: # interesting
        print 'Greater'
    return (param2 - param1 + 1) or None
class SomeClass:
    pass
>>> message = '''interpreter
... prompt'''


###脚注
生成一个脚注[^footnote].
  [^footnote]: 这里是 **脚注***内容*.
  
### 目录
用 `[TOC]`来生成目录:

@[toc]
### 数学公式
使用MathJax渲染*LaTex* 数学公式,详见[math.stackexchange.com][1].

 - 行内公式,数学公式为:$\Gamma(n) = (n-1)!\quad\forall n\in\mathbb N$。
 - 块级公式:

$$	x = \dfrac{-b \pm \sqrt{b^2 - 4ac}}{2a} $$

更多LaTex语法请参考 [这儿][3].

### UML 图:

可以渲染序列图:

```mermaid
sequenceDiagram
张三->>李四: 嘿,小四儿, 写博客了没?
Note right of 李四: 李四愣了一下,说:
李四-->>张三: 忙得吐血,哪有时间写。

或者流程图:

Created with Raphaël 2.2.0 开始 我的操作 确认? 结束 yes no
  • 关于 序列图 语法,参考 这儿,
  • 关于 流程图 语法,参考 这儿.

离线写博客

即使用户在没有网络的情况下,也可以通过本编辑器离线写博客(直接在曾经使用过的浏览器中输入write.blog.csdn.net/mdeditor即可。Markdown编辑器使用浏览器离线存储将内容保存在本地。

用户写博客的过程中,内容实时保存在浏览器缓存中,在用户关闭浏览器或者其它异常情况下,内容不会丢失。用户再次打开浏览器时,会显示上次用户正在编辑的没有发表的内容。

博客发表后,本地缓存将被删除。

用户可以选择 把正在写的博客保存到服务器草稿箱,即使换浏览器或者清除缓存,内容也不会丢失。

**注意:**虽然浏览器存储大部分时候都比较可靠,但为了您的数据安全,在联网后,请务必及时发表或者保存到服务器草稿箱

##浏览器兼容

  1. 目前,本编辑器对Chrome浏览器支持最为完整。建议大家使用较新版本的Chrome。
  2. IE9以下不支持
  3. IE9,10,11存在以下问题
    1. 不支持离线功能
    2. IE9不支持文件导入导出
    3. IE10不支持拖拽文件导入

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值