美文网首页
Category 看了又看 😭

Category 看了又看 😭

作者: biubiu15 | 来源:发表于2019-08-06 16:42 被阅读0次
    编译时的 category

    category 在编译后会得到 _category_t 的结构体. 多个 category 编译后得到多个 _category_t, 即使是同一个类不同的 category, 也会得到不同的 _category_t .

    创建一个 DemoClass 类的分类

    @interface DemoClass (Cate) <NSObject>
    @property (nonatomic, strong) NSDictionary *dic;
    @end
    
    @implementation DemoClass (Cate)
    - (void)demoCalssInstanceFuc {
        NSLog(@"%#", __func__);
    }
    
    + (void)demoCalssClassFuc {
        NSLog(@"%#", __func__);
    }
    

    转成 C++ 文件, 挑了部分代码放出来...

    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_DemoClass_$_Cate __attribute__ ((used, section ("__DATA,__objc_const"))) = 
    {
        // 赋值顺序与上面结构体顺序一一对应滴
        "DemoClass", // 类名赋值
        0, // &OBJC_CLASS_$_DemoClass,
        (const struct _method_list_t *)&_OBJC_$_CATEGORY_INSTANCE_METHODS_DemoClass_$_Cate, //实例方法数组赋值
        (const struct _method_list_t *)&_OBJC_$_CATEGORY_CLASS_METHODS_DemoClass_$_Cate, //类方法数组赋值
        (const struct _protocol_list_t *)&_OBJC_CATEGORY_PROTOCOLS_$_DemoClass_$_Cate, //协议数组赋值
        (const struct _prop_list_t *)&_OBJC_$_PROP_LIST_DemoClass_$_Cate, //属性数组赋值
    };
    
    运行时的 category

    运行时, 会将分类里的实例方法合并到类的方法列表中, 将类方法合并到元类对象的方法列表中, 可读 runtime 的源码
    阅读顺序... 入口为 objc-os.mm 文件

    1. 找到void _objc_init(void) 方法, 找到&map_images
    2. 进入 &map_images 方法
    3. 进入 map_images_nolock 方法, 找到 _read_images 方法
    4. 进入 _read_images, 找到 category_t ... 在比较下面的位置, 2555行... 找到 remethodizeClass(cls)
    5. remethodizeClass(cls) 中再进入 attachCategories 方法 🤣
    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--) { // 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 的方法列表中
        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);
    }
    
    // 附加的方法
        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]));
                memcpy(array()->lists, addedLists, 
                       addedCount * sizeof(array()->lists[0]));
            }
            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]));
            }
        }
    
    注意:

    遍历分类方法列表时, 是采用倒序, while (i--), 所以编译在后的分类, 该分类方法会在前面.

    • 分类的方法名和类的方法名一样的话, 会调用分类的方法.
    • 有两个分类的方法名一样, 看编译顺序, 会调用后编译的

    ps: 在 Build Phases -> Compile Sources 中排前面的文件先编译
    ps: 同方法名这个不属于分类方法覆盖了类的方法, 只能说是先调用了分类的方法. 调用方法, 其实就是在类的方法列表里面遍历查找, 因为在附加方法里, 把分类的方法放前面, 类的方法放在了最后, 所以会先找到分类的方法, 返回方法之后就不会再放下查找了. 因而调用了分类的方法, 但是类方法还是在的, 只是走不到类方法那里而已

    关联对象

    在分类里面写属性, 不会生成成员变量和生成 setter & getter 方法实现, 只会声明 setter & getter. 也不能直接给分类添加成员变量, 但是可以通过关联对象来实现同样效果.

    为什么不能给分类添加成员变量...
    • 因为 category_t 结构里面没有存储成员变量的地方
    关联对象最终存在哪, 存在类对象里面吗?
    • 不不不, 另外有个关联对象的 manager 来存储管理.

    通过 objc_getAssociatedObject(id _Nonnull object, const void * _Nonnull key)objc_setAssociatedObject(id _Nonnull object, const void * _Nonnull key, id _Nullable value, objc_AssociationPolicy 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());
            // object 经过 DISGUISE() 后作为 AssociationsHashMap 的 key
            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;
                    // AssociationsHashMap 以 object 作为 key, ObjectAssociationMap 对象作为 value
                    associations[disguised_object] = refs;
                    // ObjectAssociationMap 以传入的 key 作为 key, ObjcAssociation 对象作为 value
                    // ObjcAssociation 保存了传入的 policy 和 value
                    (*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 manager
    • AssociationsHashMap associations //类似manager的一个属性
    • ObjectAssociationMap refs //associations里, 以 object 为 key 对应的一个 value
    • ObjcAssociation //refs 里, 以传入的 key 为 key 对应的一个value, 存储着传入的policy 和 value

    稍微捋捋几个类之间的关系:

    • 关联对象会保存在一个叫 AssociationsManager 的类里面, 这个对象是全局的, 只有仅此的一份, 保管所有类的关联对象
    • 不同的类会以类名 objectKey(这里并不是那么单纯的以类名为 key, 而是经过一个函数的转换啥的...) 作为 key, ObjectAssociationMap 作为 value, 存在 AssociationsHashMap
    • ObjectAssociationMap 中存着以关联属性的 key 为 key, ObjcAssociation 作为 value 的键值对, 其中 ObjcAssociation 包含了关联属性的 policy 和 value

    清晰明了了吗! 没看懂? 看下面! 尽力了... 画图水平就这样了...


    以上关系图.png
    + load 和 + initiallze
    +load (阅读源码还是在 objc-os.mm 里面)
    • +load 在 runtime 加载类/分类时调用, 直接通过指针调用, 所以每个类/分类的 +load 都会调用,
    • 每个 +load 只调用一次
    • 调用顺序
      1. 类的 + load (先调用父类再调子类)
      2. 分类的 + load
      ps: 类的和分类都看编译顺序, 先编译先调用
    + initialize (额.. 这里只说创建方法的源码吧... objc-initialize.mm)
    • +initialize 方法在类第一次使用的时候调用, 通过 objc_msgSend 进行调用
    • 先父类再子类
    • 每个类只会初始化一次
      ps: 父类的 +initialize 可能会被调用多次, 如果子类没有实现 +initialize, objc_msgSend 会找到父类的 +initialize 去调用

    对于是用 +load 还是用 +initialize, 说说自己的看法...
    +load 是在启动前跑的, +initialize 在启动后跑的. 很多优化 App 启动时间的文章里有说 不要把太多处理写在 +load 里面, 会造成启动慢!!! 嗯, 但是貌似很多第三方库还是写 +load, 这是为什么呢 🧐
    可能是怕自己的方法被别人 hook 了, 导致一些数据没被初始化成功... 吧...
    所以个人来说吧, hook 方法会放在 +load 里, 一些无关紧要的初始化就放在 +initialize 里

    // 调用 load 方法  有些代码给删了... 专注重点... 也是不想篇幅过长 🤣
    void call_load_methods(void)
    {
        do {
            // 1. Repeatedly call class +loads until there aren't any more
            // 先调用类的 load
            while (loadable_classes_used > 0) {
                call_class_loads();
            }
    
            // 2. Call category +loads ONCE
            // 再调用分类的 load
            more_categories = call_category_loads();
    
            // 3. Run more +loads if there are classes OR more untried categories
        } while (loadable_classes_used > 0  ||  more_categories);
    }
    
    // 类调用 load 方法源码
    static void call_class_loads(void)
    {
        int i;
        
        // Detach current loadable list.
        struct loadable_class *classes = loadable_classes;
        int used = loadable_classes_used;
        loadable_classes = nil;
        loadable_classes_allocated = 0;
        loadable_classes_used = 0;
        
        // Call all +loads for the detached list.
        for (i = 0; i < used; i++) {
            Class cls = classes[i].cls;
            load_method_t load_method = (load_method_t)classes[i].method;
            if (!cls) continue; 
    
            if (PrintLoading) {
                _objc_inform("LOAD: +[%s load]\n", cls->nameForLogging());
            }
            // 直接调用, 没有通过消息机制来发送
            // 分类也是这样调用 load 的
            // 所以虽然类的方法名和分类的方法名都叫 load, 但是不会只调用分类的而不调用类的
            (*load_method)(cls, SEL_load);
        }
        
        // Destroy the detached list.
        if (classes) free(classes);
    }
    
    // objc-initialize.mm
    // 进入这个方法之前, 会先查看是否已经初始化, 如果没有初始化才会走这里 
    // initialize 的方法  又开始毫无人性的删掉代码了 🤣 请自行看源码
    void _class_initialize(Class cls)
    {
        assert(!cls->isMetaClass());
        
        Class supercls;
        bool reallyInitialize = NO;
        // 先调父类
        supercls = cls->superclass;
        // 判断是否有初始化, 没有初始化才会调 _class_initialize, 所以只有第一次使用的时候才会调用
        if (supercls  &&  !supercls->isInitialized()) {
            _class_initialize(supercls);
        }
    
        // Try to atomically set CLS_INITIALIZING.
        {
            monitor_locker_t lock(classInitLock);
            if (!cls->isInitialized() && !cls->isInitializing()) {
                cls->setInitializing();
                reallyInitialize = YES;
            }
        }
       
        if (reallyInitialize) {
            
    #if __OBJC2__
            @try
    #endif
            {
                // 调用 initialize
                callInitialize(cls);
                
                if (PrintInitializing) {
                    _objc_inform("INITIALIZE: thread %p: finished +[%s initialize]",
                                 pthread_self(), cls->nameForLogging());
                }
            }
    #if __OBJC2__
            @catch (...) {
                if (PrintInitializing) {
                    _objc_inform("INITIALIZE: thread %p: +[%s initialize] "
                                 "threw an exception",
                                 pthread_self(), cls->nameForLogging());
                }
                @throw;
            }
            @finally
    #endif
            {
                // Done initializing.
                lockAndFinishInitializing(cls, supercls);
            }
            return;
        }
        
        else {
            // We shouldn't be here.
            _objc_fatal("thread-safe class init in objc runtime is buggy!");
        }
    }
    
    void callInitialize(Class cls)
    {
        // 消息机制调用 initialize 
        ((void(*)(Class, SEL))objc_msgSend)(cls, SEL_initialize);
        asm("");
    }
    

    相关文章

      网友评论

          本文标题:Category 看了又看 😭

          本文链接:https://www.haomeiwen.com/subject/yiamdctx.html