在iOS中,Block
是一种特殊的对象。封装了函数调用以及调用环境的OC对象。用于封装代码块。它可以作为参数传递给方法或函数,并且可以在稍后的时间点执行。
Block
的本质是一个封装了一段代码以及其访问的变量的结构体。当定义一个Block
时,它会捕获其所在作用域中的变量,并将这些变量的值复制到自己的内部结构中。这样,在Block
执行时,即使变量已经超出了其作用域,仍然可以访问并使用这些变量的值。
block
转成C++的源码:
//经过clang转换后的C++代码
struct __block_impl {
void *isa;//指向所属类的指针
int Flags;//标志性参数,暂时没用到所以默认为0
int Reserved;//今后版本升级所需的区域大小。
void *FuncPtr;//函数指针,指向实际执行的函数,也就是block中花括号里面的代码内容。
};
struct __main_block_impl_0 {
struct __block_impl impl;//上面点1中的结构体的变量
struct __main_block_desc_0* Desc;//上面点2中的结构体的指针
__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 {
printf("Block\n");
}
static struct __main_block_desc_0 {
size_t reserved; //今后版本升级所需区域的大小(一般填0)
size_t Block_size; //Block的大小
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};
int main(int argc, const char * argv[]) {
void (*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
return 0;
}
关于block的具体细节可以看看之前的博客:【iOS】—— 浅看block源码
【iOS】—— 初识block
在iOS中,__block
是一个修饰符,用于在Block内部修改外部变量的值。它的作用是将外部变量在Block内部转为可修改的变量。
使用__block
修符可以解决Block内部无法修改外部变的问题。默认情况下,Block内部能访问外部变量的,而不能修改它们。是,当你在外部量前面加上__block
修饰符时,Block就可以修改这变量的值了。
以下是一些使用注意点:
__block
修饰符只能于局部变量,不能用全局变量或静态变量。__block
修的变量时,需要注意循环引用的问题如果Block被强引用并且同时引用了__block
修饰的变,可能会导致环引用,造成内存漏。为了避免这种情况,可以在Block内部使用weakSelf
来弱引用self,或者使用__weak
饰符来修饰__block
变量。__block
修饰的变量Block内部被修改,外部变量的值会被修改。这意味在Block执行完后,外部变量的值将保持被修改后的状态。__block
饰符会自动处理内存管理。但是,在非ARC环境下,你需要手动处理__block
变量的内存管理,确保Block执行完毕后释放它们。循环引用及强弱共舞可以看看之前的博客,上面有链接
不需要,给NSMutableArray
添加元素时。__block修饰符主要用于在Block内部问和修改外部变量,以决Block内部对外部变量的捕获问题。而在修改NSMutableArray
这样可变对象时,并不需要使用__block修饰符。因为NSMutableArray
是一个指针类型的对象,当你在Block内部修改它,实际上是修改了指向该对象的指针,而是直接修改指针所指的对象本身。因此,无需使用__block修饰符来解决捕获问题。
先说结论:
int dmy = 256;
int val = 10;
const char *fmt = "val = %d\n";
void (^blk)(void) = ^{
printf(fmt, val);
};
val = 2;
fmt = "These values were changed. val = %d\n";
blk();
void (^blk)(void) = ^{
printf("%d\n", quanju);
};
quanju = 60;
blk();
void (^blk)(void) = ^{
printf("%d\n", jingquanju);
};
jingquanju = 60;
blk();
int jingjubu = 10;
void (^blk)(void) = ^{
printf("%d\n", jingjubu);
};
jingjubu = 60;
blk();
__Block_byref_id_object_copy
和__Block_byref_id_object_dispose
两个函数,用来对对象类型的变量进行内存管理的操作。__main_block_copy_0
函数中会根据变量是强弱指针及有没有被__block修饰做出不同的处理,强指针在block内部产生强引用,弱指针在block内部产生弱引用。__Block_byref_age_0
找到其中__forwarding
指针,__forwarding
指针指向的是结构体自己因此可以找到变量进行修改。__Block_byref_age_0
结构体内的__forwarding
指针指向结构体自己。__Block_byref_age_0
结构体也会被复制到堆中一份,而此时栈中的__Block_byref_age_0
结构体中的__forwarding
指针指向的就是堆中的__Block_byref_age_0
结构体,堆中__Block_byref_age_0
结构体内的__forwarding
指针依然指向自己。详解KVO的博客:【iOS】—— KVO再学习
RuntimeAPI
动态生成一个子类,并且让instance对象
的isa指向这个全新的子类_NSSetXXXValueAndNotify
函数didChangeValueForKey
,其内部会触发监听器(Oberser)的监听方法(observerValueForKeyPath:ofObject:change:context:
);isa-swizzling
,修改了观察者的类信息,并且hooksetter
方法,当setter
方法调用时发送消息给所有观察者[self willChangeValueForKey: key]
, [self didChangeValueForKey: key]
触发,并且必须成对出现,automaticallyNotifiesObserversForKey
方法用来控制,是否要主要添加上述的两个方法,默认返回值为YES,如果返回NO则不会自动添加,也就是说setter的调用以及KVC修改都不会触发通知+ (NSSet *)keyPathsForValuesAffectingValueForKey:(NSString *)key
dependentKeyTable
中,然后hook了所有依赖的key
的setter
方法,当[self willChangeValueForKey: key]
, [self didChangeValueForKey: key]
调用时会查找所有的依赖关系,然后发送消息setValue:forKey
valueForKey --- valueForKeyPath
获取属性的值,尤其是在使用点语法的时候,只有valueForKeyPath
可以获得深层次的属性值。willChangeValueForKey:
和didChangeValueForKey:
详解KVC的博客:【iOS】—— KVC再学习
willChangeValueForKey:
和 didChangeValueForKey:
方法;setKey
、_setKey
的顺序查找方法,若找到方法,则直接调用方法并赋值;+ (BOOL)accessInstanceVariablesDirectly;
accessInstanceVariablesDirectly
方法返回YES,则按照_key
、_isKey
、key
、isKey
的顺序查找成员变量,找到直接赋值,找不到则抛出异常;accessInstanceVariablesDirectly
方法返回NO,则直接抛出异常;getKey
、key
、isKey
、_key
的顺序查找方法,找到直接调用取值+ (BOOL)accessInstanceVariablesDirectly
的返回值,若返回NO,则直接抛出异常;_key
、_isKey
、key
、isKey
的顺序查找成员变量,找到则取值;Category
除了用来给类进行扩展外,还有一种比较高级的用法,就是用来拆分模块,将一个大的模块拆分成多个小的模块,方便进行维护和管理。什么意思呢?我就举一个很多开发人员都会存在的问题,就是AppDelegate
这个类。这个类是刚创建项目时自动生成的,用来管理程序生命周期的。在刚创建项目时,这个类中是没有多少代码的,但是随着项目的进行,越来越多的代码会被放在这个类里面。比如说集成极光推送、友盟、百度地图、微信SDK等各种第三方框架时,这些第三方框架的初始化工作,甚至是相关的业务逻辑代码都会放在这个类里面,这就导致随着APP的功能越来越复杂,AppDelegate
中的代码就会越来越多,有的甚至有几千行,看着就让人头皮发麻。
这时我们就可以利用Category
来对AppDelegate
进行拆分,首先我们就需要对AppDelegate
中的代码进行划分,把同一种功能的代码抽取出来放在一个分类里面。比如说我可以新建一个极光推送的分类,然后把所有和极光推送有关的代码都抽出来放入这个分类,把所有和微信相关的代码抽出来放进微信的分类中,后面又有新的功要添加的话我只需要新建分类就好了。维护的时候要改什么功能的代码就直接找相应的分类就好了。
// 定义在objc-runtime-new.h文件中
struct category_t {
const char *name; // 比如给Student添加分类,name就是Student的类名
classref_t cls;
struct method_list_t *instanceMethods; // 分类的实例方法列表
struct method_list_t *classMethods; // 分类的类方法列表
struct protocol_list_t *protocols; // 分类的协议列表
struct property_list_t *instanceProperties; // 分类的实例属性列表
struct property_list_t *_classProperties; // 分类的类属性列表
};
struct category_t
,里面存储着分类的对象方法、类方法、属性、协议信息。Runtime
会将Category
的信息合并到类信息中(class
类对象、mate-class
元类对象),后合并的分类数据会插入到原来数据的前面;class
对象、和mate-class
对象,因为一个类只有一个class
对象、mate-class
对象。Class Extension
是在编译的时候,它的数据都已经包含在类信息中了;Category
是在运行的时候,才将数据合并到类信息中。+load
方法。+load
方法会在Runtime
加载类、分类的时候调用;+load
方法只会调用一次;+load
方法,(按照编译顺序,先编译,先调用),调用子类的+load
方法之前,会先调用父类的+load方法;+load
方法,(按照编译顺序,先编译,先调用)。+load
方法调用的顺序比较特别,没有先调用分类的+load
方法,因为+load
方法的调用机制不是objec_msgSend
的方式,+load
是直接找到方法地址进行调用的。load
方法能继承,不过一般不会手动调用load
方法,都是系统自动调用。load
,当runtime加载类、分类时会调用。load方法总是在main函数之前调用,每个类、分类的load在运行时只调用一次initialize
,在类第一次接收到消息时调用(先初始化父类,再初始化子类,每一个类只会被初始化一次)load方法
:先调用类的load,子类调用load方法之前会先调用父类的load,先编译的先调用;再调用分类的load方法,先编译的先调用initialize方法
:先调用父类的initialize再调用当前类的initialize,如果子类没有实现initialize,则会调用父类的initialize;如果有分类,则调用最后编译的分类的initialize,就不调用本类的initialize了load
,根据IMP地址直接调用(*load_method)(cls, SEL_load)initialize
,通过objc_msgSend进行调用load
是通过直接函数地址调用,只会调用一次initialize
是通过msgSend调用initialize
,会调用父类的initialzie
(所以父类的initialize会被调用很多次)initialize
,就会覆盖类本身的initailize
不能直接给Category添加成员变量,但是可以间接实现Catecory有成员变量的效果。