iOS-底层探索15:类的扩展+关联对象

iOS 底层探索 文章汇总

目录

  • 一、前言
  • 二、类的扩展
  • 三、关联对象


一、前言

前面的文章我们分析了类的加载流程,知道了类在有分类,动态添加方法、协议、属性的情况下才会生成rwe。那么这篇文章我们探索类的扩展和方法的动态绑定

二、类的扩展

代码如下:

@interface NATeacher : NSObject
@end

@interface NATeacher ()// 类的扩展

@end

@implementation NATeacher
@end

注意:类的扩展只能放在类的声明之后,类的实现之前

最常见的类的扩展:ViewController类的扩展

#import "ViewController.h"

@interface ViewController () // ViewController类的扩展

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view.
}

@end

分类 VS 类扩展

iOS-底层探索15:类的扩展+关联对象_第1张图片

类扩展的本质

在类扩展中添加属性和方法


@interface NATeacher : NSObject
@end

@interface NATeacher ()// 类的扩展

@property (nonatomic, copy) NSString *ext_name;

- (void)ext_instanceMethod;
+ (void)ext_classMethod;

@end

@implementation NATeacher

- (void)ext_instanceMethod {
}

+ (void)ext_classMethod {
}

@end

生成cpp文件:

  • cd 当前代码所在的目录
  • clang -rewrite-objc main.m -o main2.cpp

类、类扩展、属性如下:

#ifndef _REWRITER_typedef_NATeacher
#define _REWRITER_typedef_NATeacher
typedef struct objc_object NATeacher;
typedef struct {} _objc_exc_NATeacher;
#endif

extern "C" unsigned long OBJC_IVAR_$_NATeacher$_ext_name;
struct NATeacher_IMPL {
    struct NSObject_IMPL NSObject_IVARS;
    NSString *_ext_name;
};

对应的方法实现如下:

static void _I_NATeacher_ext_instanceMethod(NATeacher * self, SEL _cmd) {
}


static void _C_NATeacher_ext_classMethod(Class self, SEL _cmd) {
}


static NSString * _I_NATeacher_ext_name(NATeacher * self, SEL _cmd) { return (*(NSString **)((char *)self + OBJC_IVAR_$_NATeacher$_ext_name)); }
extern "C" __declspec(dllimport) void objc_setProperty (id, SEL, long, id, bool, bool);

static void _I_NATeacher_setExt_name_(NATeacher * self, SEL _cmd, NSString *ext_name) { objc_setProperty (self, _cmd, __OFFSETOFIVAR__(struct NATeacher, _ext_name), (id)ext_name, 0, 1); }
  • 从上面的代码中可以看到为属性自动生成的set、get方法
  • 因此写到类扩展中的属性和方法和写到类声明中表现完全相同
  • cpp_method_list_t里面属性的set、get都有两个:

查看macho中类扩展的数据加载情况

代码准备如下:

NATeacher.h

@interface NATeacher : NSObject
- (void)na_instanceMethod;
@end
NATeacher.m

#import "NATeacher.h"

@implementation NATeacher

+ (void)load {
}

- (void)na_instanceMethod {
}
- (void)ext_instanceMethod {
}
+ (void)ext_classMethod {
}

@end
NATeacher+NAEXT.h

#import "NATeacher.h"

@interface NATeacher ()

- (void)ext_instanceMethod;
+ (void)ext_classMethod;

@end
  • 实现+load方法将NATeacher设置为非懒加载类方便研究
  • 新建NATeacher+NAEXT.h头文件用于放分类的方法

readClass方法打印如下:

iOS-底层探索15:类的扩展+关联对象_第2张图片
  • 可见扩展中的方法和主类中的方法一样在编译期作为主类的一部分一起编译进类中


三、关联对象

代码如下:

@interface LGPerson (LG)

@property (nonatomic, copy) NSString *cate_name;

@end
LGPerson *person = [LGPerson alloc];
person.cate_name = @"KC";
NSLog(@"cate_name:%@",person.cate_name);

上面的代码编译不会报错,运行会Crash,因为系统只会生成属性set、get方法的声明,不会生成方法的实现

在分类中添加如下方法以实现属性的set、get方法:

@implementation LGPerson (LG)

- (void)setCate_name:(NSString *)cate_name {
    //1: 对象,2: 标识符,3: value,4: 属性关联策略
    objc_setAssociatedObject(self, "cate_name", cate_name, OBJC_ASSOCIATION_COPY_NONATOMIC);
}

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

@end
  • 现在就能正常运行代码进行属性set、get方法的调用了。

1. 关联对象底层方法调用情况

进入objc_setAssociatedObject方法

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

static ChainedHookFunction SetAssocHook{_base_objc_setAssociatedObject};

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

所以关联对象底层调用的是_object_set_associative_reference方法。


现在分析关联对象方法将属性的value存到了哪里??

2. 主类属性value存储情况

在前面clang生成的cpp文件中属性的设置方法调用的是objc_setProperty方法

static void _I_NATeacher_setExt_name_(NATeacher * self, SEL _cmd, NSString *ext_name) { 
    objc_setProperty (self, _cmd, __OFFSETOFIVAR__(struct NATeacher, _ext_name), (id)ext_name, 0, 1);
 }
void objc_setProperty(id self, SEL _cmd, ptrdiff_t offset, id newValue, BOOL atomic, signed char shouldCopy) 
{
    bool copy = (shouldCopy && shouldCopy != MUTABLE_COPY);
    bool mutableCopy = (shouldCopy == MUTABLE_COPY);
    reallySetProperty(self, _cmd, newValue, offset, atomic, copy, mutableCopy);
}
static inline void reallySetProperty(id self, SEL _cmd, id newValue, ptrdiff_t offset, bool atomic, bool copy, bool mutableCopy)
{
    if (offset == 0) {
        object_setClass(self, newValue);
        return;
    }

    id oldValue;
    id *slot = (id*) ((char*)self + offset);

    if (copy) {
        newValue = [newValue copyWithZone:nil];
    } else if (mutableCopy) {
        newValue = [newValue mutableCopyWithZone:nil];
    } else {
        if (*slot == newValue) return;
        newValue = objc_retain(newValue);
    }

    if (!atomic) {
        oldValue = *slot;
        *slot = newValue;
    } else {
        spinlock_t& slotlock = PropertyLocks[slot];
        slotlock.lock();
        oldValue = *slot;
        *slot = newValue;        
        slotlock.unlock();
    }

    objc_release(oldValue);
}

reallySetProperty方法中属性的值是放在了slot地址,其中slot = (char*)self + offsetoffset = __OFFSETOFIVAR__(struct NATeacher, _ext_name)

#define __OFFSETOFIVAR__(TYPE, MEMBER) ((long long) &((TYPE *)0)->MEMBER)

3. 关联对象value存储情况

进入到_object_set_associative_reference方法源码

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 disguised{(objc_object *)object};
    // 包装一下 policy - value
    ObjcAssociation association{policy, value};

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

    {
        AssociationsManager manager;//不唯一,只是加锁避免多线程影响
    
        AssociationsHashMap &associations(manager.get());//唯一

        if (value) {// try_emplace:如果map中没有键,则将键、值对插入到映射中。
            auto refs_result = associations.try_emplace(disguised, ObjectAssociationMap{});
            if (refs_result.second) {
                /* it's the first association we make */
                object->setHasAssociatedObjects();
            }

            /* 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);
            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);
                    }
                }
            }
        }
    }

    // release the old value (outside of the lock).
    association.releaseHeldValue();
}



关联对象流程如下:

iOS-底层探索15:类的扩展+关联对象_第3张图片
iOS-底层探索15:类的扩展+关联对象_第4张图片
3.1 关联对象:设值流程
  1. 创建一个AssociationsManager管理类
  2. 获取唯一的全局静态哈希Map
  3. 判断是否插入的关联value是否为空:
    3.1:存在走第4步
    3.2:不存在就走:关联对象插入空值流程
  4. 创建一个空的ObjcAssociationMap去取查询的键值对
  5. 如果发现这个key就插入一个空的BucketT进去并返回
  6. 标记对了存在关联对象
  7. 用当前的 修饰策略value 组成一个 ObjcAssociation 替换原来的 BucketT 的空
  8. 标记一下ObjcAssociationMap的第一次为false
3.2 关联对象:插入空值流程
  1. 根据 DisguisedPtr找到 AssociationsHashMap中的 iterator 迭代查询器
  2. 清理迭代器
  3. 插入空值就相当于清除关联对象
3.3 关联对象:取值流程
  1. 创建一个AssociationsManager管理类
  2. 获取唯一的全局静态哈希Map
  3. 根据 DisguisedPtr找到 AssociationsHashMap中的 iterator 迭代查询器
  4. 如果这个迭代器不是最后一个:获取ObjcAssociationMap(这里有策略和value
  5. 找到ObjectAssociationsMap的迭代查询器获取一个经过属性修饰符修饰的value
  6. 返回_value


总结:其实就是两层哈希Map,存取的时候两层处理(类似二维数组)

你可能感兴趣的:(iOS-底层探索15:类的扩展+关联对象)