iOS Objective-C底层 part3:live^category

在iOS Objective-C底层 part1:start内已经大概讲了分类是如何加载到内存中的,本篇文章展开说说分类的结构与内部功能实现.

1.分类原本面目

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;//分类添加的实例的属性
    struct property_list_t *_classProperties;//分类添加的类的属性(结构中有但未真实用到)
};

来看调用流程

// Discover categories.
for (EACH_HEADER) {
    category_t **catlist =
    _getObjc2CategoryList(hi, &count);
    bool hasClassProperties = hi->info()->hasCategoryClassProperties();
    
    for (i = 0; i < count; i++) {
        category_t *cat = catlist[i];
        Class cls = remapClass(cat->cls);
        
        if (!cls) {
            // Category's target class is missing (probably weak-linked).
            // Disavow any knowledge of this category.
            catlist[i] = nil;
            if (PrintConnecting) {
                _objc_inform("CLASS: IGNORING category \?\?\?(%s) %p with "
                             "missing weak-linked target class",
                             cat->name, cat);
            }
            continue;
        }
        
        // Process this category.
        // First, register the category with its target class.
        // Then, rebuild the class's method lists (etc) if
        // the class is realized.
        bool classExists = NO;
        if (cat->instanceMethods ||  cat->protocols
            ||  cat->instanceProperties)
        {
            addUnattachedCategoryForClass(cat, cls, hi);
            if (cls->isRealized()) {
                remethodizeClass(cls);
                classExists = YES;
            }
            if (PrintConnecting) {
                _objc_inform("CLASS: found category -%s(%s) %s",
                             cls->nameForLogging(), cat->name,
                             classExists ? "on existing class" : "");
            }
        }
        
        if (cat->classMethods  ||  cat->protocols
            ||  (hasClassProperties && cat->_classProperties))
        {
            addUnattachedCategoryForClass(cat, cls->ISA(), hi);
            if (cls->ISA()->isRealized()) {
                remethodizeClass(cls->ISA());
            }
            if (PrintConnecting) {
                _objc_inform("CLASS: found category +%s(%s)",
                             cls->nameForLogging(), cat->name);
            }
        }
    }
}

ts.log("IMAGE TIMES: discover categories");

由于类加方法,属性,协议元类加方法,属性,协议是一样的,我们这只看类加方法,属性,协议.

static void addUnattachedCategoryForClass(category_t *cat, Class cls, 
                                          header_info *catHeader)
{
    runtimeLock.assertWriting();

    // DO NOT use cat->cls! cls may be cat->cls->isa instead
    NXMapTable *cats = unattachedCategories();
    category_list *list;

    list = (category_list *)NXMapGet(cats, cls);
    if (!list) {
        list = (category_list *)
            calloc(sizeof(*list) + sizeof(list->list[0]), 1);
    } else {
        list = (category_list *)
            realloc(list, sizeof(*list) + sizeof(list->list[0]) * (list->count + 1));
    }
    list->list[list->count++] = (locstamped_category_t){cat, catHeader};
    NXMapInsert(cats, cls, list);
}
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);
    }
}
static void 
attachCategories(Class cls, category_list *cats, bool flush_caches)
{
    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);
}
addUnattachedCategoryForClass //绑定分类与类
   remethodizeClass //类的三个列表加入分类的三个列表
      attachCategories

到目前为止,加方法,加属性,加协议所走流程是一模一样,代码也很容易理解.但我们知道,分类加属性在类的.h内加一个@property(nonatomic,copy) NSString *name;是不够的,还需要我们做关联属性.

1.1 提问

那么为什么加属性不能和加方法,加协议一样只是走完上述流程就好了呢?(因为加方法,加协议道理是一样的,所以下面的只针对加方法进行说明)

1.2 回答
  • reason1==>属性是个性,方法是共性

这是一个所有权的问题!实例方法的所有权归属于类.而属性的在类内只是一个样板,属性真实的所有权归属于类的对象实例,如图.

instanceSize.png

如果以上的描述你没明白,举个例子:

@interface User : NSObject
-(void)buy;
@end
@implementation User
-(void)buy{
    NSLog(@"%@",@"买东西");
}
@end

User * user1 = [[User alloc]init];
user1.name = @"lilei";
[user1 buy];
User * user2 = [[User alloc]init];
user1.name = @"hanmeimei";
[user2 buy];

两个对象调用buy方法会去User类的方法列表内找,所以他们的实现肯定一模一样的.(如果你知道OC方法的调用流程,就更不言自明了)==>共性
而属性两者却截然不同.==>个性

所以类中@property(nonatomic,copy) NSString *name;只是个性的样板.

这也是为什么OC方法在运行时任何时候都可以加的原因,但属性就哈哈了.

  • reason2==>类本身属性内存类帮忙申请,分类属性内存类不管

OC内类生成对象的时候会根据类本身的所带属性的个数为对象申请相应的内存空间.
而分类可以加属性是后期版本的OC才实现的功能.
在不影响类生成对象的流程的基础上,
属性样板会加入类本身;(分类.h加入属性,.m不做关联属性.可以'使用',你懂的,)
但内存类不管,那怎么办,当然是第三方托管;

第三方托管==>关联属性

2.关联属性的具体实现

- (void)setName:(NSString *)name
{
    objc_setAssociatedObject(self,"name",name,OBJC_ASSOCIATION_COPY);
}

- (NSString*)name
{
    NSString *nameObject = objc_getAssociatedObject(self, "name");
    return nameObject;
}

先看保存==>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);
    id new_value = value ? acquireValue(value, policy) : nil;
    {
        AssociationsManager manager;
        AssociationsHashMap &associations(manager.associations());
        disguised_ptr_t disguised_object = DISGUISE(object);
        if (new_value) {
            // break any existing association.
            AssociationsHashMap::iterator i = associations.find(disguised_object);
            if (i != associations.end()) {
                // secondary table exists
                ObjectAssociationMap *refs = i->second;
                ObjectAssociationMap::iterator j = refs->find(key);
                if (j != refs->end()) {
                    old_association = j->second;
                    j->second = ObjcAssociation(policy, new_value);
                } else {
                    (*refs)[key] = ObjcAssociation(policy, new_value);
                }
            } else {
                // 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.
            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).
    if (old_association.hasValue()) ReleaseValue()(old_association);
}

最终梳理结果:


全局有一个 AssociationsManager持有一张哈希表 (AssociationsHashMap).哈希表内:
key==>对象地址
value==>对象绑定的属性表 (ObjectAssociationMap)

对象绑定的属性表内:
key==>调用objc_setAssociatedObject方法传入的key
value==>调用objc_setAssociatedObject方法传入的value+传入的policy(存储策略).

关于policy(存储策略),多说两句:

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. */
};
Behavior @property Equivalent Description
OBJC_ASSOCIATION_ASSIGN @property (assign) / @property (unsafe_unretained) 弱引用关联对象
OBJC_ASSOCIATION_RETAIN_NONATOMIC @property (nonatomic, strong) 强引用关联对象,且为非原子操
OBJC_ASSOCIATION_COPY_NONATOMIC @property (nonatomic, copy) 复制关联对象,且为非原子操作
OBJC_ASSOCIATION_RETAIN @property (atomic, strong) 强引用关联对象,且为原子操作
OBJC_ASSOCIATION_COPY @property (atomic, copy) 复制关联对象,且为原子操作

objc_setAssociatedObject分析完毕.
objc_getAssociatedObject上面的get方法也看到了.

还有一个方法

void objc_removeAssociatedObjects(id object);//解除对象的所有绑定的属性

在理解了objc_setAssociatedObject的基础上,这两个方法也不难懂,就不展开说.


文章参考:
关联对象 AssociatedObject 完全解析
可以跑起来的objc源码

你可能感兴趣的:(iOS Objective-C底层 part3:live^category)