类的扩展和关联对象

593 阅读4分钟

分类和类扩展简介

分类:categoty

  • 专门用来给类添加新方法。
  • 不能给类添加成员属性,添加了成员变量也无法取到,但是可以通过runtime给分类添加属性。
  • 分类中用@property定义变量,只会生成变量的getter,setter方法的声明,不能生成方法实现和带下划线的成员变量。

类扩展:extension

  • 特殊的分类,也叫做匿名分类。
  • 可以给类添加成员属性,但是是私有变量。
  • 可以给类添加方法,也是私有方法。

类扩展extension

新建一个类扩展,扩展必须定义在@interface 和 @implementation 之间

@interface LKPerson : NSObject
@property (nonatomic, copy) NSString *name;
@property (nonatomic, assign) int age;

- (void)instanceMethod;
+ (void)classMethod;

@end

@interface LKPerson ()

@property (nonatomic, copy) NSString *ext_name;
@property (nonatomic, assign) int ext_age;
- (void)ext_instanceMethod;
+ (void)ext_classMethod;

@end

@implementation LKPerson
- (void)instanceMethod{
    NSLog(@"%s",__func__);
}

+ (void)classMethod{
    NSLog(@"%s",__func__);
}

- (void)ext_instanceMethod{
    NSLog(@"%s",__func__);
}

+ (void)ext_classMethod{
    NSLog(@"%s",__func__);
}
@end


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

    }
    return 0;
}

利用clang -rewrite-objc main.m -o main.cpp转化cpp文件,

extern "C" unsigned long OBJC_IVAR_$_LKPerson$_name;
extern "C" unsigned long OBJC_IVAR_$_LKPerson$_age;
extern "C" unsigned long OBJC_IVAR_$_LKPerson$_ext_name;
extern "C" unsigned long OBJC_IVAR_$_LKPerson$_ext_age;
struct LKPerson_IMPL {
	struct NSObject_IMPL NSObject_IVARS;
	int _age;
	int _ext_age;
	NSString *_name;
	NSString *_ext_name;
};

static struct /*_method_list_t*/ {
	unsigned int entsize;  // sizeof(struct _objc_method)
	unsigned int method_count;
	struct _objc_method method_list[18];
} _OBJC_$_INSTANCE_METHODS_LKPerson __attribute__ ((used, section ("__DATA,__objc_const"))) = {
	sizeof(_objc_method),
	18,
	{{(struct objc_selector *)"instanceMethod", "v16@0:8", (void *)_I_LKPerson_instanceMethod},
	{(struct objc_selector *)"ext_instanceMethod", "v16@0:8", (void *)_I_LKPerson_ext_instanceMethod},
	{(struct objc_selector *)"name", "@16@0:8", (void *)_I_LKPerson_name},
	{(struct objc_selector *)"setName:", "v24@0:8@16", (void *)_I_LKPerson_setName_},
	{(struct objc_selector *)"age", "i16@0:8", (void *)_I_LKPerson_age},
	{(struct objc_selector *)"setAge:", "v20@0:8i16", (void *)_I_LKPerson_setAge_},
	{(struct objc_selector *)"ext_name", "@16@0:8", (void *)_I_LKPerson_ext_name},
	{(struct objc_selector *)"setExt_name:", "v24@0:8@16", (void *)_I_LKPerson_setExt_name_},
	{(struct objc_selector *)"ext_age", "i16@0:8", (void *)_I_LKPerson_ext_age},
	{(struct objc_selector *)"setExt_age:", "v20@0:8i16", (void *)_I_LKPerson_setExt_age_},
	{(struct objc_selector *)"name", "@16@0:8", (void *)_I_LKPerson_name},
	{(struct objc_selector *)"setName:", "v24@0:8@16", (void *)_I_LKPerson_setName_},
	{(struct objc_selector *)"age", "i16@0:8", (void *)_I_LKPerson_age},
	{(struct objc_selector *)"setAge:", "v20@0:8i16", (void *)_I_LKPerson_setAge_},
	{(struct objc_selector *)"ext_name", "@16@0:8", (void *)_I_LKPerson_ext_name},
	{(struct objc_selector *)"setExt_name:", "v24@0:8@16", (void *)_I_LKPerson_setExt_name_},
	{(struct objc_selector *)"ext_age", "i16@0:8", (void *)_I_LKPerson_ext_age},
	{(struct objc_selector *)"setExt_age:", "v20@0:8i16", (void *)_I_LKPerson_setExt_age_}}
};

static struct /*_method_list_t*/ {
	unsigned int entsize;  // sizeof(struct _objc_method)
	unsigned int method_count;
	struct _objc_method method_list[2];
} _OBJC_$_CLASS_METHODS_LKPerson __attribute__ ((used, section ("__DATA,__objc_const"))) = {
	sizeof(_objc_method),
	2,
	{{(struct objc_selector *)"classMethod", "v16@0:8", (void *)_C_LKPerson_classMethod},
	{(struct objc_selector *)"ext_classMethod", "v16@0:8", (void *)_C_LKPerson_ext_classMethod}}
};

发现属性和方法都已经存在了。

添加扩展之后加载流程

realizeClassWithoutSwift添加断点,打印ro, image.png 发现方法都已经有了,而扩展的属性setget方法没有,原因是并未引入#import "LKTeacher+LK.h"

关联对象

不能给类添加成员属性,添加了成员变量也无法取到,但是可以通过runtime给分类添加属性。

@property (nonatomic, copy) NSString *categoty_name;

- (void)setCategoty_name:(NSString *)categoty_name{
    objc_setAssociatedObject(self, "categoty_name", categoty_name, OBJC_ASSOCIATION_COPY_NONATOMIC);
}

-(NSString *)categoty_name{
    objc_getAssociatedObject(self, "categoty_name");
}

查看objc_setAssociatedObject,这种设计模式属于是接口模式,这种模式下对外的接口不变,也就是外部调用永远是调用objc_setAssociatedObject,内部的逻辑变化不影响外部的调用。

void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy)
{
    _object_set_associative_reference(object, key, value, policy);
}

void _object_set_associative_reference(id object, const void *key, id value, uintptr_t policy)
{
    // This code used to work when nil was passed for object and key. Some code
    // probably relies on that to not crash. Check and handle it explicitly.
    // rdar://problem/44094390
    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;
        AssociationsHashMap &associations(manager.get());

        if (value) {
            auto refs_result = associations.try_emplace(disguised, ObjectAssociationMap{});
            if (refs_result.second) {
                /* it's the first association we make */
                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 {
            auto refs_it = associations.find(disguised);
            if (refs_it != associations.end()) {
                auto &refs = refs_it->second;
                auto it = refs.find(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();
}

DisguisedPtr<objc_object> disguised{(objc_object *)object};,将object包装成统一的数据格式DisguisedPtr

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();
    }
};

AssociationsManager析构函数,开始调用执行AssociationsManager() { AssociationsManagerLock.lock(); }加锁,作用域结束调用~AssociationsManager() { AssociationsManagerLock.unlock(); }解锁。
static Storage _mapStorage;静态变量,所以AssociationsHashMap &associations(manager.get());是单例模式,只有一张AssociationsHashMap表。

template <typename... Ts>
  std::pair<iterator, bool> try_emplace(const KeyT &Key, Ts &&... Args) {
    BucketT *TheBucket;
    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);
  }

image.png

(objc::AssociationsHashMap &) associations = 0x000000010037f088: {
  Buckets = nil
  NumEntries = 0
  NumTombstones = 0
  NumBuckets = 0
}

可以看到associations里面为空
auto refs_result = associations.try_emplace(disguised, ObjectAssociationMap{});创建一个空的ObjectAssociationMap,去取查询的键值对,如果发现没有这个 key 就插入一个 空的 BucketT进去并返回true
auto result = refs.try_emplace(key, std::move(association));association里面的policyvalue存进去。

以下内容引用自KC课件

关联对象: 设值流程

  • 1: 创建一个 AssociationsManager 管理类
  • 2: 获取唯一的全局静态哈希Map
  • 3: 判断是否插入的关联值是否存在:
  • 3.1: 存在走第4步
  • 3.2: 不存在就走 : 关联对象插入空流程
  • 4: 创建一个空的 ObjectAssociationMap 去取查询的键值对
  • 5: 如果发现没有这个 key 就插入一个 空的 BucketT进去 返回
  • 6: 标记对象存在关联对象
  • 7: 用当前 修饰策略 和 值 组成了一个 ObjcAssociation 替换原来 BucketT 中的空
  • 8: 标记一下 ObjectAssociationMap 的第一次为 false

关联对象插入空流程

  • 1: 根据 DisguisedPtr 找到 AssociationsHashMap 中的 iterator 迭代查询器
  • 2: 清理迭代器
  • 3: 其实如果插入空置 相当于清除

关联对象: 取值流程

  • 1: 创建一个 AssociationsManager 管理类
  • 2: 获取唯一的全局静态哈希Map
  • 3: 根据 DisguisedPtr 找到 AssociationsHashMap 中的 iterator 迭代查询器
  • 4: 如果这个迭代查询器不是最后一个 获取 : ObjectAssociationMap (这里有策略和value)
  • 5: 找到ObjectAssociationMap的迭代查询器获取一个经过属性修饰符修饰的value
  • 6: 返回_value

image.png