阅读 123

分类的本质

对于分类,想必大家早已使用的炉火纯青,在开发中分类在为我们提供了很大的便利,从功能繁复的大型APP到简单的应用,catagory无处不在,下面就让我们一块看看它的本来面貌。

编译

使用xcrun --sdk iphoneos clang -arch arm64 -rewrite-objc XXObject+xx.m编译成.cpp文件

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;//属性列表
};
static struct _category_t _OBJC_$_CATEGORY_XXObject_$_xx __attribute__ ((used, section ("__DATA,__objc_const"))) = 
{
	"XXObject",
	0, // &OBJC_CLASS_$_XXObject,
	(const struct _method_list_t *)&_OBJC_$_CATEGORY_INSTANCE_METHODS_XXObject_$_xx,
	(const struct _method_list_t *)&_OBJC_$_CATEGORY_CLASS_METHODS_XXObject_$_xx,
	0,
	(const struct _prop_list_t *)&_OBJC_$_PROP_LIST_XXObject_$_xx,
};
//对象方法列表
static struct /*_method_list_t*/ {
	unsigned int entsize;  // sizeof(struct _objc_method)
	unsigned int method_count;
	struct _objc_method method_list[1];
} _OBJC_$_CATEGORY_INSTANCE_METHODS_XXObject_$_xx __attribute__ ((used, section ("__DATA,__objc_const"))) = {
	sizeof(_objc_method),
	1,
	{{(struct objc_selector *)"yty_test", "v16@0:8", (void *)_I_XXObject_xx_yty_test}}
};
//类方法列表
static struct /*_method_list_t*/ {
	unsigned int entsize;  // sizeof(struct _objc_method)
	unsigned int method_count;
	struct _objc_method method_list[1];
} _OBJC_$_CATEGORY_CLASS_METHODS_XXObject_$_xx __attribute__ ((used, section ("__DATA,__objc_const"))) = {
	sizeof(_objc_method),
	1,
	{{(struct objc_selector *)"load", "v16@0:8", (void *)_C_XXObject_xx_load}}
};
//属性
static struct /*_prop_list_t*/ {
	unsigned int entsize;  // sizeof(struct _prop_t)
	unsigned int count_of_properties;
	struct _prop_t prop_list[1];
} _OBJC_$_PROP_LIST_XXObject_$_xx __attribute__ ((used, section ("__DATA,__objc_const"))) = {
	sizeof(_prop_t),
	1,
	{{"test","T@\"NSString\",C,N"}}
};
复制代码

由编译后的代码我们可以看出,category本质上也是个结构体,它里面存储了,属性,成员方法,类方法,协议,类,分类名这些信息

源码分析

我们知道OC都是依赖runtime运行时的,我们可以在源码中查看。前面我们大致看到过分类是怎么加载进内存的,在remethodizeClass

  static void remethodizeClass(Class cls)
{
    category_list *cats;
    bool isMeta;

    runtimeLock.assertWriting();

    isMeta = cls->isMetaClass();

    // Re-methodizing: check for more categories
    if ((cats = unattachedCategoriesForClass(cls, false/*not realizing*/))) {
        if (PrintConnecting) {
            _objc_inform("CLASS: attaching categories to class '%s' %s", 
                         cls->nameForLogging(), isMeta ? "(meta)" : "");
        }
        
        attachCategories(cls, cats, true /*flush caches*/);        
        free(cats);
    }
}
复制代码

我们可以看到在这个方法中有调用attachCategories方法

static void 
attachCategories(Class cls, category_list *cats, bool flush_caches)
{
	//yty 将分类里面的所有信息填充到cls的class_rw_t结构体中
    if (!cats) return;
    if (PrintReplacedMethods) printReplacements(cls, cats);

    bool isMeta = cls->isMetaClass();

    // fixme rearrange to remove these intermediate allocations
    method_list_t **mlists = (method_list_t **)
        malloc(cats->count * sizeof(*mlists));
    property_list_t **proplists = (property_list_t **)
        malloc(cats->count * sizeof(*proplists));
    protocol_list_t **protolists = (protocol_list_t **)
        malloc(cats->count * sizeof(*protolists));

    // Count backwards through cats to get newest categories first
    int mcount = 0;
    int propcount = 0;
    int protocount = 0;
    int i = cats->count;
    bool fromBundle = NO;
    while (i--) {
        auto& entry = cats->list[i];
        method_list_t *mlist = entry.cat->methodsForMeta(isMeta);
        if (mlist) {
            mlists[mcount++] = mlist;
            fromBundle |= entry.hi->isBundle();
        }

        property_list_t *proplist = 
            entry.cat->propertiesForMeta(isMeta, entry.hi);
        if (proplist) {
            proplists[propcount++] = proplist;
        }

        protocol_list_t *protolist = entry.cat->protocols;
        if (protolist) {
            protolists[protocount++] = protolist;
        }
    }

    auto rw = cls->data();

    prepareMethodLists(cls, mlists, mcount, NO, fromBundle);
    rw->methods.attachLists(mlists, mcount);
    free(mlists);
    if (flush_caches  &&  mcount > 0) flushCaches(cls);

    rw->properties.attachLists(proplists, propcount);
    free(proplists);

    rw->protocols.attachLists(protolists, protocount);
    free(protolists);
}

复制代码

这个方法的实现也是比较简单,遍历分类列表(可能存在多个分类),将分类中的内容(编译后生成的那个结构体)给添加到各个数组中,然后再将各个结构体中的内容存储到类中的class_rw_t结构体中,我们重点看下rw->methods.attachLists(mlists, mcount);

void attachLists(List* const * addedLists, uint32_t addedCount) {
        if (addedCount == 0) return;

        if (hasArray()) {
            // many lists -> many lists
            uint32_t oldCount = array()->count;
            uint32_t newCount = oldCount + addedCount;
            setArray((array_t *)realloc(array(), array_t::byteSize(newCount)));
            array()->count = newCount;
            memmove(array()->lists + addedCount, array()->lists, 
                    oldCount * sizeof(array()->lists[0]));//1,将原始数组扩容并向后移动addedCount长度
            memcpy(array()->lists, addedLists, 
                   addedCount * sizeof(array()->lists[0]));//2,将新的数组添加到数组前面
        }
        else if (!list  &&  addedCount == 1) {
            // 0 lists -> 1 list
            list = addedLists[0];
        } 
        else {
            // 1 list -> many lists
            List* oldList = list;
            uint32_t oldCount = oldList ? 1 : 0;
            uint32_t newCount = oldCount + addedCount;
            setArray((array_t *)malloc(array_t::byteSize(newCount)));
            array()->count = newCount;
            if (oldList) array()->lists[addedCount] = oldList;
            memcpy(array()->lists, addedLists, 
                   addedCount * sizeof(array()->lists[0]));
        }
    }
复制代码

根据1,2两步骤我们可以知道了为什么类与分类有同名方法调用此同名方法会调用分类的原因了。

关联对象

从上面分类的结构体我们可以看到,分类并没有一个成员变量的数组,这也是为什么分类不能添加成员变量的原因;那我们想在分类中添加变量怎么办呢?没错,这就要使用到我们的关联对象了,我们想要使用关联对象

- (void)setTest:(NSString *)test{
	objc_setAssociatedObject(self,"test",test,OBJC_ASSOCIATION_COPY);
}
- (NSString *)test{
	NSString *object = objc_getAssociatedObject(self, "test");
	return object;
}
复制代码

我们根据objc_setAssociatedObject方法可以去查找

void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy) {
    _object_set_associative_reference(object, (void *)key, value, policy);
}
void _object_set_associative_reference(id object, void *key, id value, uintptr_t policy) {
    // retain the new value (if any) outside the lock.
    ObjcAssociation old_association(0, nil);//创建个ObjcAssociation(用于持有原有的关联对象,方便在方法的调用最后释放)
    id new_value = value ? acquireValue(value, policy) : nil;//调用 acquireValue 对 new_value 进行 retain 或者 copy
    {
        AssociationsManager manager;
        AssociationsHashMap &associations(manager.associations());//获取唯一一个保存关联对象的一个hash表AssociationsHashMap
        disguised_ptr_t disguised_object = DISGUISE(object);//使用DISGUISE(object)作为key寻找对应的ObjectAssociationMap
        if (new_value) {
            // break any existing association.
            AssociationsHashMap::iterator i = associations.find(disguised_object);
            if (i != associations.end()) {
                // secondary table exists
				//找到对应的ObjectAssociationMap
                ObjectAssociationMap *refs = i->second;
                ObjectAssociationMap::iterator j = refs->find(key);
                if (j != refs->end()) {
					//key存在 更新原有关联对象
                    old_association = j->second;
                    j->second = ObjcAssociation(policy, new_value);
                } else {
					//key不存在 增加一个关联对象
                    (*refs)[key] = ObjcAssociation(policy, new_value);
                }
            } else {
				//没有找到对应的ObjectAssociationMap,初始化一个ObjectAssociationMap,在实例化一个ObjcAssociation添加到Map中,并调用setHasAssociatedObjects方法,表明当前对象含有关联对象
                // create the new association (first time).
                ObjectAssociationMap *refs = new ObjectAssociationMap;
                associations[disguised_object] = refs;
                (*refs)[key] = ObjcAssociation(policy, new_value);
                object->setHasAssociatedObjects();
            }
        } else {
            // setting the association to nil breaks the association.
			//如果 new_value == nil,就说明我们要删除对应 key 的关联对象
			//这种情况下方法的实现与前面的唯一区别就是,我们会调用 erase 方法,擦除 ObjectAssociationMap 中 key 对应的节点。
            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;
                    refs->erase(j);
                }
            }
        }
    }
    // release the old value (outside of the lock).
	//如果原来的关联对象有值的话,会调用 ReleaseValue() 释放关联对象的值
    if (old_association.hasValue()) ReleaseValue()(old_association);
}
复制代码
  • AssociationsManager是一个单例,维护了一个全局的hash表
class AssociationsManager {
    static spinlock_t _lock;
    static AssociationsHashMap *_map;               // associative references:  object pointer -> PtrPtrHashMap.
public:
    AssociationsManager()   { _lock.lock(); }
    ~AssociationsManager()  { _lock.unlock(); }
    
    AssociationsHashMap &associations() {
        if (_map == NULL)
            _map = new AssociationsHashMap();
        return *_map;
    }
};
复制代码
  • disguised_ptr_t

是以当前对象的地址为键

  • AssociationsHashMap

AssociationsHashMap 用与保存从对象的 disguised_ptr_tObjectAssociationMap 的映射

    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); }
    };
复制代码
  • ObjectAssociationMap

ObjectAssociationMap 则保存了从 key 到关联对象 ObjcAssociation 的映射,这个数据结构保存了当前对象对应的所有关联对象:

    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); }
    };
复制代码
  • ObjcAssociation

ObjcAssociation是真正意义上的关联对象的类,包含了 policy 以及 value

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; }
    };
复制代码

借用Draveness大神的一张图

int main(int argc, const char * argv[]) {
    @autoreleasepool {

        NSObject *obj = [NSObject new];
        objc_setAssociatedObject(obj, @selector(hello), @"Hello", OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    }
    return 0;
}
复制代码

首先获取全局的hash表

 AssociationsManager manager;
 AssociationsHashMap &associations(manager.associations());
复制代码

根据对象地址为key找到对应的ObjectAssociationMap

disguised_ptr_t disguised_object = DISGUISE(object);//使用DISGUISE(object)作为key寻找对应的ObjectAssociationMap
AssociationsHashMap::iterator i = associations.find(disguised_object);
if (i != associations.end()) {
   ObjectAssociationMap *refs = i->second;
}
复制代码

在根据key去查找关联对象,key存在则更新原有关联对象;key不存在增加一个关联对象

     ObjectAssociationMap::iterator j = refs->find(key);
    if (j != refs->end()) {
	   //key存在 更新原有关联对象
      old_association = j->second;
      j->second = ObjcAssociation(policy, new_value);
    } else {
		//key不存在 增加一个关联对象
         (*refs)[key] = ObjcAssociation(policy, new_value);
        }
复制代码

没有找到对应的ObjectAssociationMap,初始化一个ObjectAssociationMap,在实例化一个ObjcAssociation添加到Map中,并调用setHasAssociatedObjects方法,设置当前对象含有关联对象标记

    ObjectAssociationMap *refs = new ObjectAssociationMap;
    associations[disguised_object] = refs;
    (*refs)[key] = ObjcAssociation(policy, new_value);
    object->setHasAssociatedObjects();
复制代码

如果 new_value == nil,就说明我们要删除对应 key 的关联对象

    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;
                    refs->erase(j);//删除`ObjectAssociationMap`中`key`对应的节点
                }
            }
复制代码

如果原来的关联对象有值的话,会调用 ReleaseValue() 释放关联对象的值

if (old_association.hasValue()) ReleaseValue()(old_association);
复制代码

上面分析完了设置关联对象,下面我们看下读取关联对象objc_getAssociatedObject

id objc_getAssociatedObject(id object, const void *key) {
    return _object_get_associative_reference(object, (void *)key);
}
id _object_get_associative_reference(id object, void *key) {
    id value = nil;
    uintptr_t policy = OBJC_ASSOCIATION_ASSIGN;
    {
        AssociationsManager manager;
        AssociationsHashMap &associations(manager.associations());//获取唯一的一个关联对象的hash表
        disguised_ptr_t disguised_object = DISGUISE(object);
        AssociationsHashMap::iterator i = associations.find(disguised_object);//以 DISGUISE(object) 为 key 查找 AssociationsHashMap
        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) ((id(*)(id, SEL))objc_msgSend)(value, SEL_retain);
            }
        }
    }
    if (value && (policy & OBJC_ASSOCIATION_GETTER_AUTORELEASE)) {
        ((id(*)(id, SEL))objc_msgSend)(value, SEL_autorelease);
    }
    return value;
}
复制代码

读取就相对简单很多了,和设置操作一样,首先根据AssociationsManager单例读取全局的AssociationsHashMap,在根据对象地址获取到对应的ObjectAssociationMap,根据key查找对应的关联对象,如果存在则读取出来,判断当前设置的类型policy & OBJC_ASSOCIATION_GETTER_RETAIN做对应的retain操作,然后判断value是否有值,以及policy & OBJC_ASSOCIATION_GETTER_AUTORELEASE做对应的autorelease操作

typedef OBJC_ENUM(uintptr_t, objc_AssociationPolicy) {
    OBJC_ASSOCIATION_ASSIGN = 0,           /**< Specifies a weak reference to the associated object. */
    OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1, /**< Specifies a strong reference to the associated object. 
                                            *   The association is not made atomically. */
    OBJC_ASSOCIATION_COPY_NONATOMIC = 3,   /**< Specifies that the associated object is copied. 
                                            *   The association is not made atomically. */
    OBJC_ASSOCIATION_RETAIN = 01401,       /**< Specifies a strong reference to the associated object.
                                            *   The association is made atomically. */
    OBJC_ASSOCIATION_COPY = 01403          /**< Specifies that the associated object is copied.
                                            *   The association is made atomically. */
};
enum { 
    OBJC_ASSOCIATION_SETTER_ASSIGN      = 0,
    OBJC_ASSOCIATION_SETTER_RETAIN      = 1,
    OBJC_ASSOCIATION_SETTER_COPY        = 3,            // NOTE:  both bits are set, so we can simply test 1 bit in releaseValue below.
    OBJC_ASSOCIATION_GETTER_READ        = (0 << 8), 
    OBJC_ASSOCIATION_GETTER_RETAIN      = (1 << 8), 
    OBJC_ASSOCIATION_GETTER_AUTORELEASE = (2 << 8)
}; 
复制代码

由上可以得出OBJC_ASSOCIATION_RETAIN以及OBJC_ASSOCIATION_COPY会做对应的retain,autorelease操作

Behavior @property Equivalent Description
OBJC_ASSOCIATION_SETTER_ASSIGN assign 弱引用
OBJC_ASSOCIATION_RETAIN_NONATOMIC nonatomic, strong 强引用关联对象,且为非原子操
OBJC_ASSOCIATION_COPY_NONATOMIC nonatomic, copy 复制关联对象,且为非原子操作
OBJC_ASSOCIATION_RETAIN atomic, strong 强引用关联对象,且为原子操作
OBJC_ASSOCIATION_COPY atomic, copy 复制关联对象,且为原子操作

最后,在dealloc中runtime会给我们自动释放掉关联对象,不需要我们手动释放

void *objc_destructInstance(id obj) 
{
    if (obj) {
        // Read all of the flags at once for performance.
        bool cxx = obj->hasCxxDtor();
        bool assoc = obj->hasAssociatedObjects();

        // This order is important.
        if (cxx) object_cxxDestruct(obj);
        if (assoc) _object_remove_assocations(obj);
        obj->clearDeallocating();
    }

    return obj;
}
复制代码

总结:
1,分类也是个结构体,分类中的对象方法,属性,协议最终会添加到类中,类方法会添加到元类中;
2,关联对象并没有存储在类中,而是存储在一个全局的hash表中并以对象的地址为key;
3,我们不需要手动的去释放关联对象,runtime已经为我们做好了

在整理阶段发现个不太明白的地方iOS Objective-C底层 part1:start,我简单的梳理了下,不知道这么理解对不对,先贴上来,恳请各位大牛帮忙指点链接

很诡异 链接里面的问题不知道为什么显示不出来,直接贴出来


文章参考:
深入理解Objective-C:Category
关联对象 AssociatedObject 完全解析

文章分类
iOS
文章标签