文章目录

  • 分类
  • 分类的结构
  • load方法的调用顺序
  • initialize
  • load和initialize区别
  • 分类在运行期做了什么
  • 为什么category不能添加成员变量
  • 分类能否添加属性
  • 使用关联对象
  • 类扩展
    • category和extention的区别

写的有些乱…

分类

Category分类是Objective-C 2.0添加的语言特性,主要作用是为已经存在的类添加方法。Category可以做到在既不子类化,也不侵入一个类的源码的情况下,为原有的类添加新的方法,从而实现扩展一个类或者分离一个类的目的。

通常Category有以下几种使用场景

  • 把类的不同实现方法分开到不同的文件里
  • 可以由多个开发者共同完成一个类
  • 声明私有方法
  • 模拟多继承
  • 将framework私有方法公开化

分类的结构

struct _category_t {const char *name;            //分类所属的类名struct _class_t *cls;const struct _method_list_t *instance_methods;   //实例方法const struct _method_list_t *class_methods;   //类方法const struct _protocol_list_t *protocols;    //协议const struct _prop_list_t *properties;       //属性
};

将新建的分类的.m文件,转为c++,就可以看到 上面分类的底层结构,_category_t结构体

_category_t结构体定义了一个结构体变量
变量名有些长 _OBJC_$_CATEGORY_Person_$_Test

static struct _category_t _OBJC_$_CATEGORY_Person_$_Test __attribute__ ((used, section ("__DATA,__objc_const"))) =
{"Person",0, // &OBJC_CLASS_$_Person,(const struct _method_list_t *)&_OBJC_$_CATEGORY_INSTANCE_METHODS_Person_$_Test,(const struct _method_list_t *)&_OBJC_$_CATEGORY_CLASS_METHODS_Person_$_Test,(const struct _protocol_list_t *)&_OBJC_CATEGORY_PROTOCOLS_$_Person_$_Test,(const struct _prop_list_t *)&_OBJC_$_PROP_LIST_Person_$_Test,
};

后面是对_category_t结构体类型变量_category_t OBJCKaTeX parse error: Expected group after '_' at position 17: …CATEGORY_Person_̲_Test的赋值。
对比分类结构体的定义

  • 第一个成员,“Person”,分类所属的类名
  • 第二个,都设为0
  • 第三个成员,(const struct _method_list_t *)&_OBJC_$_ CATEGORY_INSTANCE_METHODS_Person_$_Test
    这个也是一个结构体变量,存储着实例方法,如图

    实例方法test的实现
  • 第四个成员,(const struct _method_list_t *)&_OBJC_$_CATEGORY_CLASS_METHODS_Person_$_Test
    也是一个结构体变量,存储着类方法,如图

    类方法test1的实现

    在分类中,如果实现了对象方法、类方法,都会添加到对应的方法列表结构体中。
    但如果仅仅定义、没有实现,就不会加进来
  • 第五个成员,(const struct _protocol_list_t *)&_OBJC_CATEGORY_PROTOCOLS_$_Person_$_Test
    协议相关的结构体变量,Test分类中我遵循了NSCopying协议

    这个结构体中包含了一个_protocol_t结构体数组
    _protocol_t的定义如下

    _OBJC_PROTOCOL_NSCopying的赋值如下
  • 第六个成员,(const struct _prop_list_t *)&_OBJC_$_PROP_LIST_Person_$_Test
    属性相关的结构体变量,Test分类中,写了一个NSString类型的变量str

当类中有一个方法,多个分类中有相同的方法,调用这个方法一定执行的是分类中的方法,具体是哪一个分类就要看编译时的顺序,在attachCategories方法中 对分类列表进行倒序遍历 将分类包含的方法列表、协议列表、属性列表分别合并到对应的大数组中,将合并好的分类数据插入到类原来的数据前面。
对分类列表的遍历是倒序,即最后参与编译的分类先被添加到方法列表的开始位置。
attachCategories(Class cls, category_list *cats, bool flush_caches)方法实现中

日志中eat先编译,打印是eat的方法????

上面那个应该不准 哈哈哈哈

控制编译顺序

调用优先级
分类中有和原有类同名的方法,会先调用分类中的方法。
分类(category)-> 本类->父类

category是在运行时加载的,不是在编译期

load方法的调用顺序

load方法的调用时机,在runtime加载类、分类时由系统调用的
每个类、分类的load在程序运行过程中只调用一次load

  • 先调用类的load方法

    • 按照编译的先后顺序调用(先编译先调用)
    • 调用子类的load方法之前会先调用父类的load方法
  • 再调用分类的load方法
    • 按照编译的先后顺序调用(先编译先调用)

load方法的调用(载入内存系统自动调用时)并不符合上面说的调用顺序(先调用分类在调用类),
这是因为 load方法的调用并不通过msgSend,它是直接找到类的load方法的地址,然后调用load方法,然后再找到分类的load方法,再去调用

而上述例子中Test方法的调用是通过msgSend,先找到类对象,如果是实例方法就在类对象中寻找test方法(如果是类方法,通过类对象的isa找到元类对象,再找test方法)由于分类也实现了test方法,并且放在了类的test方法的前面,所以调用的是分类的test方法

而对于手动调用load,则通过msgSend方式,找到所属的类的元类对象,由于分类也实现了load,则调用分类的load。

initialize

类第一次接收消息时,会调用initialize(初始化)

通过msgSend调用

  • 当父类没有initialize,先调用父类的initialize,再调用当前类的initialize
    如果当前类没有实现initialize,则调用的是父类的initialize
    若多个子类未实现,则父类的initialize会被调用多次
  • 当分类实现了initialize,会覆盖本类的initialize

load和initialize区别

  • 调用时机
    load,当runtime加载类、分类时会调用。load方法总是在main函数之前调用,每个类、分类的load在运行时只调用一次
    initialize,在类第一次接收到消息时调用(先初始化父类,再初始化子类,每一个类只会被初始化一次)
  • 调用顺序
    load方法:先调用类的load,子类调用load方法之前会先调用父类的load,先编译的先调用。再调用分类的load方法,先编译的先调用
    initialize方法:先调用父类的initialize再调用当前类的initialize,如果子类没有实现initialize,则会调用父类的initialize。如果有分类,则调用最后编译的分类的initialize
  • 调用本质
    load,根据IMP地址直接调用(*load_method)(cls, SEL_load)
    initialize,通过objc_msgSend进行调用
  • 使用场景
    在load方法中实现方法交换(Method Swizzle)
    一般用于初始化全局变量或静态变量
  • 相同点
    两个方法会被自动调用,不能手动调用他们
  • 区别
    load是通过直接函数地址调用,只会调用一次
    initialize是通过msgSend调用

    • 如果子类没有实现initialize,会调用父类的initialzie(所以父类的initialize会被调用很多次)
    • 分类如果实现了initialize,就会覆盖类本身的initailize,

分类在运行期做了什么

  • 通过runtime加载某个类的所有category数据
  • 遍历所有category,把category的方法、属性、协议数据,合并到对应的大数组中,逆序遍历,即最后参与编译的category数据会在数组首位
  • 将合并后的分类数据(方法、属性、协议),插入到类原来数据的前面

为什么category不能添加成员变量

在分类的底层结构中,存并没有成员变量相关列表,所以不能声明成员变量

但是可以通过关联对象,来添加属性。

分类能否添加属性

可以添加属性,但是在分类中添加属性,只有对应的setter、getter的声明,并不会生成成员变量、setter和getter方法的实现

使用关联对象

- (void)setName:(NSString *)name {objc_setAssociatedObject(self, @"name", name, OBJC_ASSOCIATION_COPY_NONATOMIC);
}- (NSString *)name {return objc_getAssociatedObject(self, @"name");
}

runtime源码中

void _object_set_associative_reference(id object, void *key, id value, uintptr_t policy) {// retain the new value (if any) outside the lock.//初始化空的ObjcAssociation(关联对象)ObjcAssociation old_association(0, nil);id new_value = value ? acquireValue(value, policy) : nil;{//全局的managerAssociationsManager manager;//获取manager中的hashMapAssociationsHashMap &associations(manager.associations());//获取对象的ISGUISE值,作为AssociationHashMap的keydisguised_ptr_t disguised_object = DISGUISE(object);//value有值if (new_value) {// break any existing association.//AssociationsHashMap::iterator 类型的迭代器//查找AssociationsHashMap中是否有key为disguised_objectAssociationsHashMap::iterator i = associations.find(disguised_object);if (i != associations.end()) {// secondary table exists//获取key对应的ObjectAssociationMapObjectAssociationMap *refs = i->second;//查找ObjectAssociationMap是否有key为 参数keyObjectAssociationMap::iterator j = refs->find(key);if (j != refs->end()) {//获取参数key对应的ObjcAssociation(关联对象)old_association = j->second;//关联对象 赋新值j->second = ObjcAssociation(policy, new_value);} else {(*refs)[key] = ObjcAssociation(policy, new_value);}} else {// create the new association (first time).//执行到这里,说明该对象是第一次添加关联对象//初始化ObjectAssociationMapObjectAssociationMap *refs = new ObjectAssociationMap;associations[disguised_object] = refs;//赋值(*refs)[key] = ObjcAssociation(policy, new_value);object->setHasAssociatedObjects();}} else {  //value无值 为nil,释放一个key对应的关联对象// setting the association to nil breaks the association.//AssociationsHashMap::iterator i = associations.find(disguised_object);if (i !=  associations.end()) {ObjectAssociationMap *refs = i->second;ObjectAssociationMap::iterator j = refs->find(key);if (j != refs->end()) {old_association = j->second;//调用erase 删除对应的关联对象refs->erase(j);}}}}// release the old value (outside of the lock).//释放旧的关联对象if (old_association.hasValue()) ReleaseValue()(old_association);
}
id _object_get_associative_reference(id object, void *key) {id value = nil;uintptr_t policy = OBJC_ASSOCIATION_ASSIGN;{AssociationsManager manager;AssociationsHashMap &associations(manager.associations());disguised_ptr_t disguised_object = DISGUISE(object);AssociationsHashMap::iterator i = associations.find(disguised_object);if (i != associations.end()) {ObjectAssociationMap *refs = i->second;ObjectAssociationMap::iterator j = refs->find(key);if (j != refs->end()) {ObjcAssociation &entry = j->second;value = entry.value();policy = entry.policy();if (policy & OBJC_ASSOCIATION_GETTER_RETAIN) {objc_retain(value);}}}}if (value && (policy & OBJC_ASSOCIATION_GETTER_AUTORELEASE)) {objc_autorelease(value);}return value;
}
//将object对象对应的所有关联对象删除
void _object_remove_assocations(id object) {vector< ObjcAssociation,ObjcAllocator<ObjcAssociation> > elements;{AssociationsManager manager;AssociationsHashMap &associations(manager.associations());if (associations.size() == 0) return;disguised_ptr_t disguised_object = DISGUISE(object);AssociationsHashMap::iterator i = associations.find(disguised_object);if (i != associations.end()) {// copy all of the associations that need to be removed.ObjectAssociationMap *refs = i->second;for (ObjectAssociationMap::iterator j = refs->begin(), end = refs->end(); j != end; ++j) {elements.push_back(j->second);}// remove the secondary table.delete refs;associations.erase(i);}}// the calls to releaseValue() happen outside of the lock.for_each(elements.begin(), elements.end(), ReleaseValue());
}

  • 关联对象并不是存储在被关联对象本身的内存中
  • 关联对象存储在全局的统一的一个AssociationsManager中
  • 设置关联对象为nil,就相当于是移除关联对象

类扩展

在编译的时候就合并到了类里面了

Extension
extension被开发者称之为扩展、延展、匿名分类。extension看起来很像一个匿名的category,但它们几乎完全是两个东西。
和category不同的是extension不但可以声明方法,还可以声明属性、成员变量。
extension一般用于声明私有方法、私有属性、私有成员变量

extension存在形式
extension只存在于一个.h文件中,或寄生于一个类的.m文件中

category和extention的区别

  • extension可以添加实例变量,而Category无法添加实例变量
  • extension在编译期决议,是类的一部分。
    category则是在运行期决议。
    extension在编译期和头文件里的@interface以及实现文件里的@implement一起形成一个问完整的类,extension伴随类的产生而产生,也会一起消亡
  • extension一般用于隐藏类的私有信息,必须有一个类的源码才能为一个类添加extension,所以我们无法为系统的类添加extension,除非创建子类在添加extension。
    而category不需要有类的源码,我们可以为系统提供的类添加category分类
  • extension和category都可以添加属性,但是category的属性不能生成成员变量和getter、setter方法的实现

iOS—分类、load 、initialize相关推荐

  1. iOS 中 load 和 initialize的实现顺序

    1 load 函数 调用时机,当类引用进项目的时候执行load函数,在main函数开始之前,与 这个类是否被用到是无关的,每个类的load函数都会自动调用一次. 1 父类和子类都实现load函数的时候 ...

  2. +load +initialize

    +load方法 在app启动的时候各个类的+load方法都会被调用,+load方法不是通过消息机制调用的,它是直接调用的,因此无论是在子类或者category中复写此方法,复写的+load方法都会被调 ...

  3. 如何给iOS 分类添加 属性

    2019独角兽企业重金招聘Python工程师标准>>> 比如我们封装了一个类,不想再动它了,但是我们又需要在那个类中增加一些方法,这时候我们就不必在那个类中做修改或者再定义一个它的子 ...

  4. ios 分类(Category)

      今天研究了类别,都是网上找的资料,类别的作用 类别主要有3个作用:        (1)将类的实现分散到多个不同文件或多个不同框架中.        (2)创建对私有方法的前向引用.        ...

  5. iOS 分类添加属性

    1.首先在分类文件中导入头文件 #import <objc/runtime.h> 2.实现代码如下: (1).h定义文件 #import <UIKit/UIKit.h>@int ...

  6. iOS 分类思想(2)

    /******************* NSString+NumCount.h文件 ******************************/#import <Foundation/Fou ...

  7. iOS load方法和initialize方法的异同

    对于OC中的类来说,在runtime中会有两个方法被调用: +load +initialize 这两个方法看起来都是在类初始的时候调用的,但其实还是有一些异同,从而可以用来做一些行为. +load 首 ...

  8. 【iOS】——分类、扩展和关联对象

    目录 一.分类Category和扩展Extension 1.分类Category(运行期) 2.扩展Extension(编译期) 3.分类和扩展的区别 二.分类Category的实质 1.分类的结构 ...

  9. iOS---Objective-C: +load vs +initialize

    在 NSObject 类中有两个非常特殊的类方法 +load 和 +initialize ,用于类的初始化.这两个看似非常简单的类方法在许多方面会让人感到困惑,比如: 子类.父类.分类中的相应方法什么 ...

最新文章

  1. h5在手机端实现简单复制
  2. 华中科技大学计算机学院的班级,华中科技大学计算机学院数据结构(计算机专业)...
  3. Android IllegalStateException: The specified child already has a parent问题解决办法
  4. [X11forword]Display remote application with X11 forword / 远程显示linux服务器GUI
  5. 变量dwNKDrWatsonSize
  6. c++面向对象三大特征封装、继承和多态知识总结
  7. 一个使用:focus-within伪类实现的button选中动画效果
  8. mysql 5.7 pxc_mysql5.7 PXC安装记录
  9. 关于计算机软件系统分类能够匹配的有,以下关于计算机软件系统分类能够匹配的有:...
  10. python画图代码的输入数据可以取出来_用Python写了个小程序:最小二乘法、读取文件、作图以及数据输出到文件...
  11. 浏览器的全屏功能小结
  12. 基础01类与对象、封装、构造方法
  13. hdu 3829 最大独立集
  14. 桌面计算机图标带虚线框,桌面图标出现虚线框,win10桌面图标带有虚线方框
  15. LaTeX数学公式环境
  16. 华为云服务器配置过程
  17. C语言求某天是一年里的第几天(详细)
  18. 安科瑞预付费系统在电力系统中的应用
  19. 基于java学生签到考勤系统
  20. 实验7 Wireshark网络安全检测

热门文章

  1. 【ES6】阮一峰ES6学习之Generator 函数(一)
  2. 明解C语言入门篇_第13章_文件处理
  3. Redis源码分析(十一)--- memtest内存检测
  4. 数据结构复习——串、数组和广义表
  5. win10电脑人脸识别库安装及使用
  6. 如何对ccd视觉检测设备进行保养
  7. 深度分析|一文读懂银行数据架构体系
  8. 陈曦北理工电子工程系_全国大学生电子设计竞赛获奖作品汇编(第一届~第五届).pdf...
  9. 9—css的引入方式(行内样式表、内部样式表、外部样式表)
  10. python实现二叉树非递归前中后序遍历