Objective-C 关联对象与 Method Swizzling

栏目: Objective-C · 发布时间: 5年前

内容简介:关联对象,顾名思义,即通过唯一键(那么什么时候会用到关联对象呢?比如,我们需要对内置类

关联对象,顾名思义,即通过唯一键( key )连接(关联)至某个类的实例上的对象。

那么什么时候会用到关联对象呢?

比如,我们需要对内置类 NSArray 添加一个属性(不使用继承)。如何解决?分类似乎只能添加方法。当我们了解关联对象后,就可以轻松实现。

关联对象基础

设置关联对象

void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy)

参数说明:

  • object : 与谁关联,通常是 self
  • key : 唯一键,在获取值时通过该键获取,通常是使用 static const void * 来声明
  • value : 关联所设置的值
  • policy : 内存管理策略

内存管理策略

typedef OBJC_ENUM(uintptr_t, objc_AssociationPolicy){
    OBJC_ASSOCIATION_ASSIGN = 0,             // 表示弱引用关联,通常是基本数据类型
    OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1,   // 表示强引用关联对象,是线程安全的
    OBJC_ASSOCIATION_COPY_NONATOMIC = 3,     // 表示关联对象copy,是线程安全的
    OBJC_ASSOCIATION_RETAIN = 01401,         // 表示强引用关联对象,不是线程安全的
    OBJC_ASSOCIATION_COPY = 01403            // 表示关联对象copy,不是线程安全的
};

当对象释放时,会根据设置关联对象时采用的策略来决定是否释放关联对象。当策略为 RETAIN/COPY 时,释放关联对象。当策略为 ASSIGN 时,不释放关联对象。

获取关联对象

id objc_getAssociatedObject(id object, const void *key)

参数说明:

  • object : 与谁关联,通常是传 self ,在设置关联时所指定的与哪个对象关联的那个对象
  • key : 唯一键,在设置关联值所指定的键

取消关联对象

void objc_removeAssociatedObjects(id object)

取消对象的所有关联对象。如果要取消指定的关联对象,可使用 setAssociatedObject 设置为 nil 来实现。

关联对象应用

UIViewController 添加一个是否需要登录的属性。

@interface UIViewController (Extension)

@property (nonatomic, assign) BOOL needToLogin;

@end
static const char *ViewControllerNeedToLoginKey = "ViewControllerNeedToLoginKey";

- (void)setNeedToLogin:(BOOL)needToLogin {
    objc_setAssociatedObject(self, ViewControllerNeedToLoginKey, @(needToLogin), OBJC_ASSOCIATION_ASSIGN);
}

- (BOOL)needToLogin {
    return [objc_getAssociatedObject(self, ViewControllerNeedToLoginKey) boolValue];
}

Method Swizzling

Method Swizzling,顾名思义,就是将两个方法的实现交换。

那么什么时候会用到 Method Swizzling 呢?

比如,在开发中,我们可能会遇到系统提供的 API 不能满足实际需求。我们希望能够修改它以达到期望的效果。

Method Swizzling 原理

Method Swizzling 的实现充分利用了 Objective-C runtime 动态绑定机制

在 Objective-C 中调用方法,其实是向一个对象发送消息,而查找消息的唯一依据是方法名 selector 。每个类都有一个方法列表 objc_method_list ,存放着其所有的方法 objc_method

typedef struct objc_method *Method

struct objc_method{
    SEL method_name      OBJC2_UNAVAILABLE; // 方法名
    char *method_types   OBJC2_UNAVAILABLE;
    IMP method_imp       OBJC2_UNAVAILABLE; // 方法实现
}

每个方法 objc_method 保存了方法名( SEL )和方法实现( IMP )的映射关系。Method Swizzling 其实就是重置了 SELIMP 的映射关系。如下图所示:

Objective-C 关联对象与 Method Swizzling

Method Swizzling 基础

获取方法

Method _Nullable class_getInstanceMethod(Class _Nullable cls, SEL _Nonnull name)

参数说明:

cls
name

获取方法实现

IMP _Nonnull class_getMethodImplementation(Class _Nullable cls, SEL _Nonnull name)

添加方法

BOOL class_addMethod(Class _Nullable cls, SEL _Nonnull name, IMP _Nonnull imp, const char * _Nullable types)

参数说明:

cls
name
imp
types

替换方法

IMP _Nullable class_replaceMethod(Class _Nullable cls, SEL _Nonnull name, IMP _Nonnull imp, const char * _Nullable types)

参数说明:

cls
name
imp
types

交换方法实现

void method_exchangeImplementations(Method _Nonnull m1, Method _Nonnull m2)

获取方法的编码类型

const char * _Nullable method_getTypeEncoding(Method _Nonnull m)

Method Swizzling 应用

通过分类允许 NSObject 对任意两个方法进行 Method Swizzling。

@interface NSObject (Swizzle)

+ (BOOL)swizzleMethod:(SEL)originalSEL withMethod:(SEL)targetSEL error:(NSError **)error;
+ (BOOL)swizzleClassMethod:(SEL)originalSEL withMethod:(SEL)targetSEL error:(NSError **)error;

@end
@implementation NSObject (Swizzle)

+ (BOOL)swizzleMethod:(SEL)originalSEL withMethod:(SEL)targetSEL error:(NSError *__autoreleasing *)error {
    Method originalMethod = class_getInstanceMethod(self, originalSEL);
    if (originalMethod == nil) {
        return NO;
    }

    Method targetMethod = class_getInstanceMethod(self, targetSEL);
    if (targetMethod == nil) {
        return NO;
    }

    class_addMethod(self, originalSEL, class_getMethodImplementation(self, originalSEL), method_getTypeEncoding(originalMethod));
    class_addMethod(self, targetSEL, class_getMethodImplementation(self, targetSEL), method_getTypeEncoding(targetMethod));
    method_exchangeImplementations(class_getInstanceMethod(self, originalSEL), class_getInstanceMethod(self, targetSEL));

    return YES;
}

+ (BOOL)swizzleClassMethod:(SEL)originalSEL withMethod:(SEL)targetSEL error:(NSError *__autoreleasing *)error {
    Class metaClass = object_getClass((id)self);
    return [metaClass swizzleMethod:originalSEL withMethod:targetSEL error:error];
}

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

小米生态链战地笔记

小米生态链战地笔记

小米生态链谷仓学院 / 中信出版集团 / 2017-5 / 56.00

2013年下半年,小米开始做一件事,就是打造一个生态链布局IoT(物联网);2016年年底,小米生态链上已经拥有了77家企业,生态链企业整体销售额突破100亿元。这3年,是小米生态链快速奔跑的3年,也是小米在商场中不断厮杀着成长的3年。 3年,77家生态链企业,16家年销售额破亿,4家独角兽公司,边实战,边积累经验。 小米生态链是一个基于企业生态的智能硬件孵化器。过去的3年中,在毫无先......一起来看看 《小米生态链战地笔记》 这本书的介绍吧!

在线进制转换器
在线进制转换器

各进制数互转换器

Base64 编码/解码
Base64 编码/解码

Base64 编码/解码

UNIX 时间戳转换
UNIX 时间戳转换

UNIX 时间戳转换