分类加载和关联对象分析

204 阅读6分钟

0a000581fca74098bc53b876f5e957ae.jpeg 上章我们了解:realizeClassWithoutSwift对非懒加载类进行加载,并给rw开辟内存空间,然后将ro的数据“拷⻉”到rw⾥⾯。递归调⽤realizeClassWithoutSwift,对⽗类和元类进⾏初始化。最后设置⽗类,isa指针的初始化。我们继续分析分类加载和关联对象。

一. 懒加载类的加载

在源码objc4-838.1中添加测试代码:

@interface NYPerson : NSObject

@property (nonatomic ,copy) NSString *hobby;

@end
//懒加载类  未实现load方法
@implementation NYPerson
@end

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

    @autoreleasepool {

        // insert code here...

        NSLog(@"Hello, World!");
        NYPerson *p = [NYPerson alloc];

    }
    return 0;
}

断点调试发现懒加载类也会调用realizeClassWithoutSwift方法。 image.png bt看下堆栈信息: image.png 执行顺序是,预编译->编译->汇编->连接->dyld->通知_objc_init->执行mian函数(ps:_objc_init初始化过程,前几章的知识,需要加深理解)。从堆栈信息可以看出,懒加载类是在main函数之后被调用->objc_alloc->callAlloc->快速查找未命中->_objc_msgSend_uncached->lookUpImpOrForward(到了这我们熟悉的慢速查找流程)->realizeAndInitializeIfNeeded_locked->initializeAndLeaveLocked->initializeAndMaybeRelock->realizeClassMaybeSwiftMaybeRelock->realizeClassWithoutSwift 如图所示: image.png 结论:所以在懒加载类第一次接受到消息的时候才会被加载。

二.关联对象

在代码中添加NY分类:

@interface NYPerson (NY)

@property (nonatomic ,copy) NSString *name;

@property (nonatomic ,assign) int age;

-(void)test;

-(void)category_instanceMethod;

+(void)categoty_classMethod;

@end


@implementation NYPerson (NY)


-(void)test {

    NSLog(@"%s",__func__);

}


-(void)category_instanceMethod {

    NSLog(@"%s",__func__);

}


+(void)categoty_classMethod {

    NSLog(@"%s",__func__);

}

static const char *NYNameKey = "NYNameKey";

-(void)setName:(NSString *)name {

    objc_setAssociatedObject(self, NYNameKey, name, OBJC_ASSOCIATION_COPY_NONATOMIC);

}

-(NSString *)name {
    return objc_getAssociatedObject(self, NYNameKey);
}
@end

在终端使用clang -rewrite-objc mian.mm 生成cpp文件进行分析。 image.png 找到对应分类数据结构代码:

//分类数据结构
struct _category_t {

const char *name; //NY 分类名称

struct _class_t *cls;//类 NYPerson

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; //属性列表

};

分类没有元类,所以使用类方法列表。再者分类没有ivar说明分类没有成员变量。那如果要给分类添加属性,要怎么办呢? image.png 类的扩展必须写在类的声明类的实现之间。类扩展与分类有什么区别?

分类:在原则上只能添加方法,有单独的实现。

类扩展:能添加属性,添加方法,成员变量,没有单独的实现。

继续上面的问题,如何给分类添加属性呢?

通过关联对象方法objc_setAssociatedObjectobjc_getAssociatedObject类设置方法关联。

如下有5种关联策略:

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. */

};

我们来看看打印: image.png 来看看objc_setAssociatedObject底层代码:

void
_object_set_associative_reference(id object, const void *key, id value, uintptr_t policy)
{


    if (!object && !value) return;

    if (object->getIsa()->forbidsAssociatedObjects())

        _objc_fatal("objc_setAssociatedObject called on instance (%p) of class %s which does not allow associated objects", object, object_getClassName(object));


    DisguisedPtr<objc_object> disguised{(objc_object *)object};//关联对象

    ObjcAssociation association{policy, value};//关联策略和值

    // retain the new value (if any) outside the lock.

    association.acquireValue();

    bool isFirstAssociation = false;

    {

        AssociationsManager manager;//C++ 构造函数 初始化一个对象

        AssociationsHashMap &associations(manager.get());//获取全局的HasMap

        if (value) {

            //去关联对象表中找对象对应的HashMap表,如果没有内部会重新生成一个

            auto refs_result = associations.try_emplace(disguised, ObjectAssociationMap{});

            if (refs_result.second) {

                /* it's the first association we make */

                //说明是第一次设置关联对象,把是否关联对象设置为YES

                isFirstAssociation = true;

            }

            /* establish or replace the association */

            //更新对应关联对象

            auto &refs = refs_result.first->second;

            auto result = refs.try_emplace(key, std::move(association));

            if (!result.second) {

                association.swap(result.first->second);

            }

        } else {//如果value为空

            auto refs_it = associations.find(disguised);//通过object找对应的HashMap表

            if (refs_it != associations.end()) {// 存在

                auto &refs = refs_it->second;

                auto it = refs.find(key);//通过key再在二级表里面找对应的内容

                if (it != refs.end()) {

                    //删除掉

                    association.swap(it->second);

                    refs.erase(it);

                    if (refs.size() == 0) {

                        associations.erase(refs_it);

                    }

                }

            }

        }

    }


    // Call setHasAssociatedObjects outside the lock, since this

    // will call the object's _noteAssociatedObjects method if it

    // has one, and this may trigger +initialize which might do

    // arbitrary stuff, including setting more associated objects.

    if (isFirstAssociation)

        object->setHasAssociatedObjects();

    // release the old value (outside of the lock).

    association.releaseHeldValue();

}

我们来看看try_emplace做了什么:

template <typename... Ts>

  std::pair<iterator, bool> try_emplace(const KeyT &Key, Ts &&... Args) {

    BucketT *TheBucket;

    //在TheBucket找关联的key值 已存则返回

    if (LookupBucketFor(Key, TheBucket))

      return std::make_pair(

               makeIterator(TheBucket, getBucketsEnd(), true),

               false); // Already in map.

    // Otherwise, insert the new element.

    //不存在就插入一个新的对象

    TheBucket = InsertIntoBucket(TheBucket, Key, std::forward<Ts>(Args)...);

    return std::make_pair(

             makeIterator(TheBucket, getBucketsEnd(), true),

             true);
  }

分类和类扩展的区别

  • 分类原则上只能增加⽅法(可以通过rutime关联对象实现添加属性)。

  • 类扩展不仅可以增加⽅法,还可以增加实例变量(或者属性)。

  • 类扩展是在编译阶段被添加到类中,⽽分类是在运⾏时添加到类中。

  • 类扩展不能像分类那样拥有独⽴的实现部分(@implementation部分),也就是说,类扩展所

  • 声明的⽅法必须依托对应类的实现部分来实现。

  • 定义在 .m ⽂件中的类扩展⽅法为私有的,定义在 .h ⽂件(头⽂件)中的类扩展⽅法为公有的。类扩展是在 .m ⽂件中声明私有⽅法的⾮常好的⽅式。

三.分类加载

我们来看看分类的加载是在objc中实现的。 在源码attachCategories的实现中:

static void
attachCategories(Class cls, const locstamped_category_t *cats_list, uint32_t cats_count,int flags)
{
    ......忽略......
    bool fromBundle = NO;

    bool isMeta = (flags & ATTACH_METACLASS);

    auto rwe = cls->data()->extAllocIfNeeded();//新建rwe
   
    const char *mangledName = cls->nonlazyMangledName();

    const char *personName = "NYPerson";

    auto lg_ro = (const class_ro_t *)cls->data();

    auto lg_isMeta = lg_ro->flags & RO_META;


    if (strcmp(mangledName, personName) == 0 && !lg_isMeta) {


    }


    for (uint32_t i = 0; i < cats_count; i++) {//遍历每个分类

        auto& entry = cats_list[i];
        //获取分类里面的方法
        method_list_t *mlist = entry.cat->methodsForMeta(isMeta);

        if (mlist) {

            if (mcount == ATTACH_BUFSIZ) {

                prepareMethodLists(cls, mlists, mcount, NO, fromBundle, __func__);

                rwe->methods.attachLists(mlists, mcount);

                mcount = 0;

            }

            mlists[ATTACH_BUFSIZ - ++mcount] = mlist;

            fromBundle |= entry.hi->isBundle();

        }
        //获取属性列表
        property_list_t *proplist =

            entry.cat->propertiesForMeta(isMeta, entry.hi);

        if (proplist) {

            if (propcount == ATTACH_BUFSIZ) {

                rwe->properties.attachLists(proplists, propcount);

                propcount = 0;

            }

            proplists[ATTACH_BUFSIZ - ++propcount] = proplist;

        }
        //获取协议列表
        protocol_list_t *protolist = entry.cat->protocolsForMeta(isMeta);

        if (protolist) {

            if (protocount == ATTACH_BUFSIZ) {

                rwe->protocols.attachLists(protolists, protocount);

                protocount = 0;

            }

            protolists[ATTACH_BUFSIZ - ++protocount] = protolist;

        }

    }


    if (mcount > 0) {
        /添加分类的方法到rwe中
        prepareMethodLists(cls, mlists + ATTACH_BUFSIZ - mcount, mcount,

                           NO, fromBundle, __func__);

        rwe->methods.attachLists(mlists + ATTACH_BUFSIZ - mcount, mcount);

        if (flags & ATTACH_EXISTING) {

            flushCaches(cls, __func__, [](Class c){

                // constant caches have been dealt with in prepareMethodLists

                // if the class still is constant here, it's fine to keep

                return !c->cache.isConstantOptimizedCache();

            });

        }

    }

    rwe->properties.attachLists(proplists + ATTACH_BUFSIZ - propcount, propcount);

    rwe->protocols.attachLists(protolists + ATTACH_BUFSIZ - protocount, protocount);

}

我们进行调试打印: 注意:这里面,分类和本类都需要实现+load方法才可以。

1.类(非懒),分类(非懒) image.png 默认一个.cxx_destruct析构函数 image.png 2.类(非懒),分类(懒) image.png image.png 3.类(懒),分类(非懒) image.png 4.类(懒),分类(懒) image.png image.png 小结:

  1. 类为⾮懒加载类/分类为⾮懒加载类

编译时ro⾥⾯只有类的数据没有分类的数据,分类的数据在运⾏是被加载到rwe⾥⾯。

  1. 类为⾮懒加载类/分类为懒加载类

编译时类和分类的数据都被加载ro⾥⾯了。

  1. 类为懒加载类/分类为⾮懒加载类

编译时类和分类的数据都被加载ro⾥⾯了。

  1. 类为懒加载类/分类懒加载类

在类第⼀次接收到消息时加载数据,类和分类的数据都被加载在ro⾥⾯。

四.总结

  1. 懒加载类第一次接受到消息的时候才会被加载。

  2. 分类:在原则上只能添加方法,有单独的实现。类扩展:能添加属性,添加方法,成员变量,没有单独的实现。通过关联对象方法objc_setAssociatedObjectobjc_getAssociatedObject类设置方法关联对象。

  3. 类与分类的加载:

  • 类(非懒),分类(非懒):编译时ro⾥⾯只有类的数据没有分类的数据,分类的数据在运⾏是被加载到rwe⾥⾯。
  • 类(非懒),分类(懒):编译时类和分类的数据都被加载ro⾥⾯了。
  • 类(懒),分类(非懒):编译时类和分类的数据都被加载ro⾥⾯了。
  • 类(懒),分类(懒):在类第⼀次接收到消息时加载数据,类和分类的数据都被加载在ro⾥⾯。