iOS Category底层原理分析(二)

452 阅读6分钟

目录

1. load方法分析

2. +load和+initialize区别

3. 给Category“添加成员变量”

4. 关联对象实现原理


+load方法分析:

  • 调用时机:load方法会在runtime加载类、分类时调用,因为所有的类、分类都会在程序启动时被载入内存,所以所有的类、分类不管有没有被用到都会调用一次load。

问题1:

  • 没有继承关系时,load方法按照类的编译顺序执行;
  • 有继承关系时,父类和子类的load方法,无论怎么调整编译顺序,父类的load方法总是先执行? 下面时person是student的父类,student的编译顺序在person前面,person却先调用load 截屏2021-03-22 下午5.45.31.png

问题2:
上篇文章有分析过,在runtime加载时会把所有方法合并到数组,分类的方法总是会在类方法前面,如果有相同的方法,只会调用最后编译的分类方法,然而对于+load方法,测试发现所有的分类和类调用到了load方法,这是为什么呢?

截屏2021-03-22 下午4.51.37.png

这里我们还是源码分析:Objc源码下载地址 主要查看源码的文件和方法

objc-os.mm 文件 (主要是初始化)
  _objc_init
  
objc-runtime-new.mm文件
  load_images  //加载方法
  
  ///加载load方法前的一些处理
  prepare_load_methods //按照编译顺序取出类放在一个list中
  schedule_class_load, //递归方法,先找到父类,最后再到子类
  add_class_to_loadable_list //添加进load数组中
  
  ///加载load方法
  call_load_methods //加载所有load方法
  call_class_loads //加载所有类的load方法
  call_category_loads //加载所有分类的load方法

问题一的分析:
方法查看顺序 load_images ->prepare_load_methods ->schedule_class_load ->add_class_to_loadable_list

截屏2021-03-22 下午5.57.13.png

截屏2021-03-22 下午5.53.48.png

截屏2021-03-22 下午5.54.01.png

通过上面的源码分析,已经知道: 类按照编译顺序将类依次加入到list,然后遍历list,取出每个类,再通过递归方法找到父类,然后全部放入loadable_classes中,
所以loadable_classes里面的类排序就是: 没有继承关系的类,按编译顺序;有继承关系的,父类在前,子类在后。

接着我们先来

问题二的分析:
方法查看顺序
load_images ->call_load_methods ->call_class_loads ->call_category_loads 截屏2021-03-22 下午6.25.01.png 截屏2021-03-22 下午7.27.40.png

从上面的源码分析我们可以知道:

  1. 所有类的load方法先调用完,才会调用分类的load方法
  2. 每个类和分类都有一个load方法函数指针,是直接通过函数指针调用,并不会像其他方法会合并到一个方法列表里,所以所有的类和分类都会执行load方法,不存在被“覆盖”。
  3. loadable_classes按顺序遍历调用类的load方法:先编译的类, 优先调用load, 调用子类的load之前, 会先调用父类的load 当包含的程序库载入系统时,就会执行此方法,并且此过程通常是在程序启动的时候执行。

+load和+initialize区别

  1. 在程序启动的时候就会执行一次所有类和分类的+load方法,而+initialize方法只会在类第一次接收到消息时调用一次。

  2. +load是通过调用函数地址直接调用,所以不存在被“覆盖”问题,每个类/分类都会调用到;
    +initialize是通过objc_msgSend进行调用的所以和之前讲的类方法一样,通过isa指针查找方法实现,方法都最终会合并到一个方法列表中,会存在“覆盖”的情况:

a. 如果子类没有实现+initialize,会调用父类的+initialize(所以父类的+initialize可能会被调用多次);
b. 如果分类实现了+initialize,就覆盖类本身的+initialize调用.


给Category“添加成员变量”

  • 一般情况,因为分类底层结构的限制,不能添加成员变量到分类中,但是可以通过Runtime API 关联对象实现;

主要是三个API

//添加关联对象

/** 
参数含义:
id object                     :表示关联者,是一个对象,一般是传self
const void *key               :获取被关联者的索引key,
id value                      :被关联者
objc_AssociationPolicy policy: 关联策略
OBJC_ASSOCIATION_ASSIGN ,           =	assign
OBJC_ASSOCIATION_RETAIN_NONATOMIC,  =	nonatomic, strong
OBJC_ASSOCIATION_COPY_NONATOMIC,    =	nonatomic, copy
OBJC_ASSOCIATION_RETAIN	atomic,     =   strong
OBJC_ASSOCIATION_COPY	atomic,     =   copy
**/
void objc_setAssociatedObject(id object, const void * key,
                                id value, objc_AssociationPolicy policy)
                                
//获取关联对象
id objc_getAssociatedObject(id object, const void * key)

//移除所有关联对象
void objc_removeAssociatedObjects(id object)

如下在一个分类添加name属性,在分类中property不会实现set和get方法,关联对象主要也是帮分类声明的属性手动生成set和get方法。

.h
@interface Person (Test)
@property(nonatomic,strong)NSString * name;
@end

.m
#import <objc/runtime.h>
@implementation Person (Test)
-(void)setName:(NSString *)name{
    //const void * key直接传@selector(name)确保唯一性
    objc_setAssociatedObject(self, @selector(name), name, OBJC_ASSOCIATION_COPY_NONATOMIC);
}
-(NSString *)name{
     //_cmd代表当前方法的selector,即@selector(name)
    return objc_getAssociatedObject(self,_cmd);
}
@end

关联对象实现原理

实现关联对象技术的核心对象有

  • AssociationsManager
  • AssociationsHashMap
  • ObjectAssociationMap
  • ObjcAssociation

1.AssociationsManager的结构源码如下:

spinlock_t AssociationsManagerLock;

class AssociationsManager {
    using Storage = ExplicitInitDenseMap<DisguisedPtr<objc_object>, ObjectAssociationMap>;
    static Storage _mapStorage;

public:
    AssociationsManager()   { AssociationsManagerLock.lock(); }
    ~AssociationsManager()  { AssociationsManagerLock.unlock(); }

    AssociationsHashMap &get() {
        return _mapStorage.get();
    }

    static void init() {
        _mapStorage.init();
    }
};

主要作用:

  • 它维护了 spinlock_t 和 AssociationsHashMap 的单例,初始化它的时候会调用 lock() 方法,在析构时会调用unlock(),而 get 方法用于取得一个全局的 AssociationsHashMap单例。
  • 简单来说:AssociationsManager 通过持有一个自旋锁 spinlock_t 保证对 AssociationsHashMap 的操作是线程安全的,即每次只会有一个线程对 AssociationsHashMap 进行操作。

2.AssociationsHashMap的结构:
HashMap相当于OC中的NSDictionary。AssociationsHashMap的定义如下:

class AssociationsHashMap : public unordered_map<disguised_ptr_t, ObjectAssociationMap *, DisguisedPointerHash, DisguisedPointerEqual, AssociationsHashMapAllocator> {
public:
    void *operator new(size_t n) { return ::malloc(n); }
    void operator delete(void *ptr) { ::free(ptr); }
};

AssociationsHashMap继承自unordered_map,使用的是C++语法。它的作用就是保存从对象的disguised_ptr_t到ObjectAssociationMap的映射。 我们可以理解为AssociationsHashMap以key-value的形式存着若干个ObjectAssociationMap。

2.ObjectAssociationMap:

class ObjectAssociationMap : public std::map<void *, ObjcAssociation, ObjectPointerLess, ObjectAssociationMapAllocator> {
public:
    void *operator new(size_t n) { return ::malloc(n); }
    void operator delete(void *ptr) { ::free(ptr); }
};

ObjectAssociationMap保存了从key到ObjcAssociation的映射,我们可以理解为ObjectAssociationMap以key-value的形式存着若干个ObjcAssociation对象。这个数据结构保存了当前对象对应的所有关联对象;

3.ObjcAssociation: ObjcAssociation的定义如下:

class ObjcAssociation {
    uintptr_t _policy;
    id _value;
public:
    ObjcAssociation(uintptr_t policy, id value) : _policy(policy), _value(value) {}
    ObjcAssociation() : _policy(0), _value(nil) {}
    
    uintptr_t policy() const { return _policy; }
    id value() const { return _value; }
    
    bool hasValue() { return _value != nil; }
};

ObjcAssociation对象保存了对象对应的关联对象,其中的_policy和_value字段存的便是我们使用objc_setAssociatedObject方法时传入的policy和value。

整体关系如下:

截屏2021-03-23 下午3.03.10.png

总结:

  1. 关联对象并不是存储在被关联对象本身内存中,而是存储在全局的统一的一个AssociationsManager中
  2. AssociationsHashMap是全局唯一的,有AssociationsManager管理。
  3. ObjectAssociationMap以key为健存储关联对象的数据结构(ObjcAssociation对象),每一个对象都对应着一个ObjectAssociationMap,而ObjectAssociationMap则装着添加的关联对象,通过key-value一一对应。
  4. 分类通过关联对象添加属性实际只是帮它实现了set和get方法,并不是增加了一个属性。