OC语言基础特性什么是分类分类的原理和机制是怎么样的关联对象扩展代理通知实现机制和原理KVO实现机制KVC是什么原理属性和关键字分类分类做了哪些事情声明私有方法分解体积庞大的类文件把FrameWork的私有方法公开化....分类的特点运行时决议在编译分类文件的时候并没有将其添加到宿主类当中而是在运行的时候通过runTime进行添加可以为系统类添加分类分类中可以添加哪些内容实例方法类方法协议属性实际上只是声明了get和set方法并没有添加实例变量 -》关联对象分类结构体// OCRunTime 680 struct category_t { const char *name; classref_t cls; struct method_list_t *instanceMethods; struct method_list_t *classMethods; struct protocol_list_t *protocols; struct property_list_t *instanceProperties; method_list_t *methodsForMeta(bool isMeta) { if (isMeta) return classMethods; else return instanceMethods; } property_list_t *propertiesForMeta(bool isMeta) { if (isMeta) return nil; // classProperties; else return instanceProperties; } };name 代表分类名称 cls 表示分类所属宿主类 instanceMethods 实例方法 classMethods 类方法 protocols 协议 instanceProperties 实例属性分类加载调用栈// 1. 动态链接器 dyld 调用 Runtime 初始化 _objc_init ├── _dyld_objc_notify_register // 注册 dyld 镜像加载回调 └── map_images // 2. 镜像加载入口加锁 └── map_images_nolock // 3. 无锁处理镜像 └── _read_images // 4. 读取镜像类/分类/协议 ├── 收集所有 category_t 结构体 ├── 区分懒加载/非懒加载类 └── load_categories_nolock // 5. 加载非懒加载类的分类 └── attachCategories // 6. 底层核心合并分类attachCategories 合并分类的主要思路是遍历宿主类分类倒序遍历最先访问最后编译的类每次获取一个分类然后获取其方法列表然后将其添加到一个二维数组中数组形式类似于[method_t,method_t],[method_t],[...],...(这样就会导致如果多个分类中定义了同名方法那么最后被编译的类生效属性和协议也是类似的)后续会将这个methods拼接为一维形式拼接到类的方法列表前面会做memmove和memcopy操作这也是分类的方法覆盖类方法的原因。关联对象解决如何为分类添加成员变量的问题Runtime 提供动态绑定机制允许在运行时给任意对象绑定额外数据不修改类的结构体、不改变对象内存布局。关联对象相关函数objc_setAssociatedObject 设置 / 添加 / 修改 / 移除关联对象传递nil就可以擦出对应的值objc_getAssociatedObject 获取关联对象objc_removeAssociatedObjects 移除对象的所有关联对象不推荐用关联对象底层原理首先关联对象不存储在对象自身的内存里而是存在 Runtime 全局哈希表[全局容器]全局 AssociationsManager管理者 ↓ 顶层哈希表AssociationsHashMap Key对象的内存地址 Value二级哈希表 ObjectAssociationMap ↓ 二级哈希表ObjectAssociationMap Key我们传入的唯一键(一般情况下用selector作为key) ValueObjcAssociation存储 值 内存策略实践代码// setter存储值 - (void)setName:(NSString *)name { // 绑定对象 self键 selector(name)值 name策略 COPY_NONATOMIC objc_setAssociatedObject(self, selector(name), name, OBJC_ASSOCIATION_COPY_NONATOMIC); } // getter获取值 - (NSString *)name { return objc_getAssociatedObject(self, selector(name)); }扩展a. 扩展的作用声明私有属性声明私有方法声明私有成员变量b. 分类和扩展的区别编译时决议以声明的形式存在多数情况下寄生在.m文件中无法为系统类添加扩展代理可以说代理是一种软件设计模式代理模式在ios中以protocol形式体验传递方式为1V1note一般委托方使用weak避免循环引用通知代理和通知的区别是什么通知使用观察者模式实现的跨层消息传递的机制传递为1V多如何实现通知的机制 目前没有公开的源码下述仅作为参考KVO OC对于观察者设计模式的实现Apple使用isa混写技术实现KVOisa混写技术KVO是Runtime动态创建原类的子类NSKVONotifying将原类的isa指针指向子类同时子类重写属性的setter方法在赋值前后插入监听逻辑实现属性变化通知的机制。 当调用如下代码后[person addObserver:self forKeyPath:age options:NSKeyValueObservingOptionNew context:nil];runtime执行的操作为动态创建子类生成一个原类的私有子类命名固定为NSKVONotifying_原类名。修改isa指针把被监听对象的isa指针从指向原类改为指向动态子类。重写核心方法动态子类重写4个方法被监听属性的 setter 方法class 方法伪装自己还是原类对上层隐藏动态子类dealloc 方法释放时清理监听_isKVOA 私有方法标记这是 KVO 子类重写setter方法的具体实现伪代码 - (void)setAge:(NSInteger)age { // 1. 私有方法通知即将修改属性 [self willChangeValueForKey:age]; // 2. 调用原类的 setter真正修改属性值 [super setAge:age]; // 3. 私有方法通知属性修改完成 [self didChangeValueForKey:age]; //在系统内部实现中会触发观察者回掉方法observeValueForKeyPath }小问题通过KVC设置value能否使KVO生效YES通过成员变量赋值能否使KVO生效NO但是我们可以在赋值前后加上willChangeValueForKey和didChangeValueForKey去手动触发为什么我们看不到动态子类动态子类重写了class方法使其返回父类的classKVC键值编码技术核心API- (void)setValue:(id)value forKey:(NSString *)key; - (id)valueForKey:(NSString *)key;逻辑实现KVC有固定优先级查找顺序取值流程优先查找getter方法如果找到直接结束如果找不到方法直接查找成员变量这里使用accessInstanceVariablesDirectly来确定我们可以通过重写这个方法来避免某些私有成员变量被KVC读取这样来保证面向对象的原则如果还找不到调用 valueForUndefinedKey: 抛出异常崩溃查找getter方法的流程为依次寻找key/getKey如果是Bool类型还会查找isKey成员变量查找顺序_key - _isKey - key - isKey赋值流程类似于取值流程属性关键字atomic这个原子性是用来保证线程安全的但是仅针对赋值和获取如果对于一个数组的添加和删除元素是无法保证线程安全的。引用计数retain\strong:retain一般在MRC中使用strong在ARC使用assign修饰基本数据类型修饰对象类型时不改变其引用计数assign修饰的对象被释放后依旧会指向原来的地址会产生悬垂指针weak不改变被修饰对象引用计数修饰的对象被释放后对应的指针会置为nilcopy