保山网站建设,如果自己建立网站,响应式网站管理,软件开发专业是做什么的目录 Blocks概要什么是BlocksOC转C方法关于几种变量的特点 Blocks模式Block语法Block类型 变量截获局部变量值__block说明符截获的局部变量 Blocks的实现Block的实质 Blocks概要
什么是Blocks Blocks是C语言的扩充功能#xff0c;即带有局部变量的匿名函数。 顾名思义#x… 目录 Blocks概要什么是BlocksOC转C方法关于几种变量的特点 Blocks模式Block语法Block类型 变量截获局部变量值__block说明符截获的局部变量 Blocks的实现Block的实质 Blocks概要
什么是Blocks Blocks是C语言的扩充功能即带有局部变量的匿名函数。 顾名思义不带有名称的函数C语言的标准可不允许存在这样的函数。就算是使用函数指针调用函数也需要知道函数名称。
OC转C方法
因为需要看Block操作的C源码所以要知道转换的方法 打开终端cd到OC源文件.m所在的文件夹输入clang -rewrite-objc 文件名称.m就会在当前文件夹内自动生成对应的.cpp文件. 关于几种变量的特点
C语言函数中可能使用的变量
函数参数自动变量局部变量静态变量静态局部变量静态全局变量全局变量
而且由于存储区域特殊这其中有三种变量是可以在任何时候以任何状态调用的在函数的多次调用之间能够传递值的变量
静态变量静态局部变量全局变量
虽然这些变量的作用域不同但在整个程序中一个变量总保持在一个内存区域。而其他两种虽然有各自相应的作用域超过作用域后会被销毁。
Blocks模式
Block语法
完整形式的Block语法与一般的C语言函数定义相比仅有两点不同
没有函数名即匿名函数带有^因为iOS、Mac OS应用程序的源代码中将大量使用Block所以插入该记号便于查找。
Block语法的BN范式^ 返回值类型 参数列表 表达式。例如
^int (int value, int count) {return count * value;}可省略返回值类型
^(int value, int count) {return count * value;}省略返回值类型的情况下
表达式中return的类型就是返回类型表达式中无return语句说明是void类型表达式中含有多个return语句时所有return的返回值类型必须相同。
可省略参数列表如果不使用参数
^void (void) {printf(Blocks\n);}
//省略形式
^{printf(Blocks\n);}Block类型 变量
定义C语言函数时可以将所定义的函数的地址赋值给函数指针类型的变量
int func(int count) {return count 1;
}
int (*funcptr) (int) func;同样的Block是一种数据类型可将Block语法赋值给声明为Block类型的变量
//声明Block类型变量仅仅是将声明函数指针类型变量的*变为^
int (^blockName) (int);//赋值Block内容的实现
int (^blockName) (int) ^(int count) {return count 1;
};如果我们在项目中经常使用某种相同类型的block可以用typedef抽象出这种类型的Block
typedef int (^AddOneBlock) (int count);
AddOneBlock blockName ^(int count) {return count 1;};用typedef给Block起别名使得block的赋值和传递变得相对方便因为block一经抽象出来了
typedef int (^block_t) (int);//block作为参数
void func(int (^blockName) (int));
//简化后
void func(block_t blockName);//block作为返回值
int (^func() (int)) {return ^(int count) {return count 1;};
}
//简化后
block_t func() { ... }Block类型变量可完全像通常的C语言变量一样使用因此也可以使用指向Block类型变量的指针即Block指针类型变量
typedef int (^block_t) (int);
block_t blockName ^(int count) {return count 1;};
block_t* blockptr blockName;//int result blockName(10);
int result (*blockptr)(10);截获局部变量值
int a 20;
int b 10;void (^blockName)(void) ^{printf(%d, %d\n, a, b);
};blockName();a;
b;printf(%d, %d\n, a, b); //21, 11
blockName(); //20, 10可以看到使用Block时还可以使用Block外部的局部变量。而一旦在Block内部使用了其外部变量这些变量就会被Block保存即被截获从而在执行块时使用。
__block说明符
实际上局部变量值截获只能保存执行Block语法瞬间的值保存后就不能改写改值 可以看到当修改截获的局部变量值时会产生编译错误。
若想实现在Block内部将值赋给外部的局部变量需要在该局部变量上附加__block说明符
__block int a 20;
void (^blockName)(void) ^{a 27;printf(%d\n, a);
};
blockName(); //27
a;
printf(%d\n, a); //28
blockName(); //27小结
修改Block外部的局部变量Block内部被截获的局部变量不受影响修改Block内部的局部变量编译不通过附有 __block说明符的局部变量可在Block中赋值该变量也称__block变量。
截获的局部变量
截获变量为OC对象
从前面一部分可以得知将值赋给Block中截获的局部变量会产生编译错误。 那么截获OC对象调用变更该对象的方法也会产生编译错误吗
id array [[NSMutableArray alloc] init];void (^blockName) (void) ^{id object [[NSObject alloc] init];[array addObject: object];
};blockName();截获的变量值array为NSMutableArray类的对象用C语言描述即是截获NSMutableArray类对象用的结构体实例指针。
使用截获的值这是没有问题的而向截获的变量array赋值则会产生编译错误 这种情况下需要给截获的局部变量附加__block说明符
__block id array [[NSMutableArray alloc] init];截获对象为C语言数组 看似没有任何问题只是使用了C语言的字符串字面量数组而并没有截获的局部变量赋值。但由于在目前的Blocks中截获自动变量的方法并没有实现对C语言数组的截获所以无法通过编译。
使用指针就可以解决该问题
const char* text hello;void (^blockName) (void) ^{printf(%c\n, text[2]);
};blockName(); //lBlocks的实现
Block的实质
Block语法实际上是作为极普通的C语言源代码来处理的。 通过支持Block的编译器含有Block语法的源代码转换为一般C语言编译器能够处理的源代码并作为极为普通的C语言源代码被编译。
Block其实就是Objective-C对象因为它的结构体中含有isa指针。 下面在终端通过clang将OC中Block语法转换为C代码clang -rewrite-objc main.m
main.m
int main(void) {void (^blockName) (void) ^{printf(Block\n);};blockName();return 0;
}main.cpp
下面我们将源代码分成几个部分逐步理解 源代码中的Block语法 //void (^blockName) (void) ^{printf(Block\n);};
//通过Blocks使用的匿名函数被作为简单的C语言函数来处理
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {printf(Block\n);}根据Block所属的函数名此处为main和该Block语法在该函数出现的顺序值此处为0来给经clang变换的函数命名。 该函数的参数_cself相当于C实例方法中指向实例自身的变量this或是OC实例方法中指向对象自身的变量self即参数__cself为指向。 C的thisObjective-C的self 定义类的实例方法 //C
void MyClass::method(int arg) {printf(%p %d, this, arg);}
MyClass cls;
cls.method(10);
//OC
- (void)method: (int)arg {printf(%p %d, self, arg);}
MyObject* obj [[MyObject alloc] init];
[obj method: 10];C、Objective-C编译器将该方法作为C语言函数来处理 //C转成C
void __ZN7MyClass6methodEi(MyClass* this, int arg) {printf(%p %d, this, arg);
}
struct MyClass cls;
__ZN7MyClass6methodEi(cls, 10);
//OC转成C
void _I_MyObject_method_(struct MyObject* self, SEL _cmd, int arg) {printf(%p %d, self, arg);
}
MyObject* obj objc_msgSend(objc_getClass(MyObject), sel_registerName(alloc));
obj objc_msgSend(obj, sel_registerName(init));
objc_Send(obj, sel_registerName(method:), 10);objc_msgSend函数根据指定的对象和函数名从对象持有类的结构体中检索_I_MyObject_method_函数的指针并调用。 objc_msgSend函数的第一个参数objc作为_I_MyObject_method_的第一个参数self进行传递。 来看看参数的声明struct __main_block_impl_0* __cself该结构体的声明如下 struct __main_block_impl {void* isa;int Flags; //标志int Reserved; //今后版本升级所需的区域void* FuncPtr; //指针函数
}
struct __main_block_impl_desc_0 {unsigned long reserved; //今后版本升级所需的区域unsigned long Block_size; //Block大小
}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 flags0) {impl.isa _NSConcreteStackBlock;impl.Flags flags;impl.FuncPtr fp;Desc desc;}
}来看看构造函数的调用因为转换较多看起来比较复杂以下去掉转换的部分 //void (*blockName) (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* blockName tmp;该源代码将__main_block_impl_0结构体类型的局部变量即栈上生成的__main_block_impl_0结构体实例的指针赋值给__main_block_impl_0结构体指针类型的变量blockName。 这部分代码对应的最初源代码void (^blockName) (void) ^{printf(Block\n);};将Block语法生成的Block赋给Block类型变量blockName它等同于将__main_block_impl_0结构体实例的指针赋给变量blockName。 构造函数是C中一种特殊的成员函数用于在创建结构体对象时对其进行初始化操作避免对象处于未定义状态。构造函数名称必须和类包括结构体的名称完全相同无返回类型包括void若构造函数名称和结构体名不一致编译器将不认为这是一个有效的构造函数而是一个普通的成员函数。 下面来分析一下该构造函数__main_block_impl_0(__main_block_func_0, __main_block_desc_0_DATA)中的参数 第一个参数是由Block语法转换的C语言函数指针。第二个参数是作为静态全局变量初始化的__main_block_desc_0结构体实例指针 static struct __main_block_desc_0 __main_block_desc_0_DATA {0,sizeof(struct __main_block_impl_0) //Block大小
};接下来来看看调用Block的部分blockName(); 这部分源代码 ((void (*)(__block_impl *))((__block_impl *)blockName)-FuncPtr)((__block_impl *)blockName);去掉转换部分 (*blockName-impl.FuncPtr)(blockName);可以看出这是简单的函数指针调用函数。 最后探究一下上面没有提到的_NSConcreteStackBlock isa _NSConcreteStackBlock;首先要理解OC类和对象的实质所谓Block就是Objective-C对象。 “id”这一变量类型用于存储OC对象在usr/include/objc/runtime.h中是如下进行声明的 typedef struct objc_object {Class isa;
}* id;typedef struct objc_class {Class isa;
}* Class;这两种结构体归根结底是在各个对象和类的实现中使用的最基本的结构体。 下面通过编写OC类来确认一下 interface MyObject : NSObject {int val0;int val1;
}//基于objc_object结构体该类的对象的结构体如下
struct MyObject {Class isa;int val0;int val1;
}MyObject类的实例变量val0和val1被直接声明为对象的结构体成员。生成的各个对象即由该类生成的对象的各个结构体实例通过成员变量isa保持该类的结构体实例指针。 各类的结构体就是基于objc_class结构体的class_t结构体。class_t结构体在objc4运行时库的runtime/objc-runtime-new.h中声明如下 struct class_t {struct class_t* isa;struct class_t* superclass;Cache cache;IMP* vtable;unitptr_t data_NEVER_USE;
};