Objective-C的Runtime学习笔记

Runtime简介

Objective-C 扩展了 C 语言,并加入了面向对象特性和 Smalltalk(一种开发语言) 式的消息传递机制。而这个扩展的核心是一个用 C 和 汇编语言 写的 Runtime 库,它是 Objective-C 面向对象和动态机制的基石。

Runtime简称运行时。Objective-C语言就是运行时机制,即在运行时候的一些机制。这意味着它不仅需要一个编译器,也需要一个运行时系统来动态得创建类和对象、进行消息传递和转发。其中最主要的是消息机制。Objective-C语言作为一门动态语言,将很多静态语言在编译和链接时期做的事放到了运行时来处理。

例如对于C语言,函数的调用在编译的时候会决定调用哪个函数。而对于Objective-C语言的函数,属于动态调用过程,在编译的时候并不能决定真正调用哪个函数,只有在真正运行的时候才会根据函数的名称找到对应的函数来调用。在编译阶段,Objective-C语言可以调用任何函数,即使这个函数并未实现,只要声明过就不会报错。而对于C语言,在编译阶段调用未实现的函数就会报错。

高级编程语言想要成为可执行文件需要先编译为汇编语言再汇编为机器语言,机器语言也是计算机能够识别的唯一语言,但是Objective-C并不能直接编译为汇编语言,而是要先转写为纯C语言再进行编译和汇编的操作,从Objective-C到C语言的过渡就是由runtime来实现的。然而我们使用Objective-C进行面向对象开发,而C语言更多的是面向过程开发,这就需要将面向对象的类转变为面向过程的结构体。

Objective-C的Runtime库是开源的,可以在这里查看苹果开源代码的Runtime代码

了解 Runtime ,要先了解它的核心 - 消息传递(Messaging)。

Runtime消息传递

一个对象的方法像这样[obj foo],编译器转成消息发送objc_msgSend(obj, foo),Runtime时执行的流程是这样的:

  • 首先,通过obj的isa指针找到它的 class ;
  • 在 class 的 method list 找 foo ;
  • 如果 class 中没到 foo,继续往它的 superclass 中找 ;
  • 一旦找到 foo 这个函数,就去执行它的实现IMP 。

但这种实现有个问题,效率低。但一个class 往往只有 20% 的函数会被经常调用,可能占总调用次数的 80% 。每个消息都需要遍历一次objc_method_list 并不合理。如果把经常被调用的函数缓存下来,那可以大大提高函数查询的效率。这也就是objc_class 中另一个重要成员objc_cache 做的事情 - 再找到foo 之后,把foo 的method_name 作为key ,method_imp作为value 给存起来。当再次收到foo 消息的时候,可以直接在cache 里找到,避免去遍历objc_method_list。从下面的源代码可以看到objc_cache是存在objc_class 结构体中的。

  • 对象(object),类(class),方法(method)的结构体:
//对象(结构体定义在objc.h 中)
struct objc_object {
    Class isa  OBJC_ISA_AVAILABILITY;
};

//类(结构体定义在runtime.h 中)
struct objc_class {
    Class isa  OBJC_ISA_AVAILABILITY;
#if !__OBJC2__
    Class super_class                                        OBJC2_UNAVAILABLE;
    const char *name                                         OBJC2_UNAVAILABLE;
    long version                                             OBJC2_UNAVAILABLE;
    long info                                                OBJC2_UNAVAILABLE;
    long instance_size                                       OBJC2_UNAVAILABLE;
    struct objc_ivar_list *ivars                             OBJC2_UNAVAILABLE;
    struct objc_method_list **methodLists                    OBJC2_UNAVAILABLE;
    struct objc_cache *cache                                 OBJC2_UNAVAILABLE;
    struct objc_protocol_list *protocols                     OBJC2_UNAVAILABLE;
#endif
} OBJC2_UNAVAILABLE;

//方法列表(结构体定义在runtime.h 中)
struct objc_method_list {
    struct objc_method_list *obsolete                        OBJC2_UNAVAILABLE;
    int method_count                                         OBJC2_UNAVAILABLE;
#ifdef __LP64__
    int space                                                OBJC2_UNAVAILABLE;
#endif
    /* variable length structure */
    struct objc_method method_list[1]                        OBJC2_UNAVAILABLE;
}                                                            OBJC2_UNAVAILABLE;

//方法(结构体定义在runtime.h 中)
struct objc_method {
    SEL method_name                                          OBJC2_UNAVAILABLE;
    char *method_types                                       OBJC2_UNAVAILABLE;
    IMP method_imp                                           OBJC2_UNAVAILABLE;
}

消息传递的实现流程:

    1. 系统首先找到消息的接收对象,然后通过对象的isa找到它的类。
    1. 在它的类中查找method_list,是否有selector方法。
    1. 没有则查找父类的method_list。
    1. 找到对应的method,执行它的IMP。
    1. 转发IMP的return值。

消息传递的一些概念

  • 类对象(objc_class)
  • 实例(objc_object)
  • 元类(Meta Class)
  • Method(objc_method)
  • SEL(objc_selector)
  • IMP
  • 类缓存(objc_cache)
  • Category(objc_category)
  1. 类对象(objc_class) —— Objective-C类是由Class类型来表示的,它实际上是一个指向objc_class结构体的指针。
typedef struct objc_class *Class;

在objc/runtime.h中对objc_class结构体的定义如下:

struct objc_class {
    Class _Nonnull isa  OBJC_ISA_AVAILABILITY;

#if !__OBJC2__
    Class _Nullable super_class                              OBJC2_UNAVAILABLE;
    const char * _Nonnull name                               OBJC2_UNAVAILABLE;
    long version                                             OBJC2_UNAVAILABLE;
    long info                                                OBJC2_UNAVAILABLE;
    long instance_size                                       OBJC2_UNAVAILABLE;
    struct objc_ivar_list * _Nullable ivars                  OBJC2_UNAVAILABLE;
    struct objc_method_list * _Nullable * _Nullable methodLists                    OBJC2_UNAVAILABLE;
    struct objc_cache * _Nonnull cache                       OBJC2_UNAVAILABLE;
    struct objc_protocol_list * _Nullable protocols          OBJC2_UNAVAILABLE;
#endif

} OBJC2_UNAVAILABLE;

struct objc_class结构体定义了很多变量,结构体里保存了指向父类的指针、类的名字、版本、实例大小、实例变量列表、方法列表、缓存、遵守的协议列表等,这包含了一个类的基本信息,类对象就是一个结构体struct objc_class,这个结构体存放的数据称为元数据(metadata),该结构体的第一个成员变量也是isa指针,这就说明了Class本身其实也是一个对象,因此我们称之为类对象,类对象在编译期产生用于创建实例对象,每个类对象在内存中有且只有一个,是单例。

  1. 实例(objc_object) —— 使用类对象(objc_class)中的元数据存储的用于创建一个实例的相关信息来创建。

定义如下:

/// Represents an instance of a class.
struct objc_object {
    Class isa  OBJC_ISA_AVAILABILITY;
};

/// A pointer to an instance of a class.
typedef struct objc_object *id;
  1. 元类(Meta Class) —— 元类(Meta Class)是一个类对象的类。所有的类自身也是一个对象,我们可以向这个对象发送消息(即调用类方法)。为了调用类方法,这个类的isa指针必须指向一个包含这些类方法的一个objc_class结构体。这就引出了meta-class的概念,元类中保存了创建类对象以及类方法所需的所有信息。任何NSObject继承体系下的meta-class都使用NSObject的meta-class作为自己的所属类,而基类的meta-class的isa指针是指向它自己。元类中保存了创建类对象以及类方法所需的所有信息,因此整个结构应该如下图所示:
截屏2022-06-22 11.53.04.png

通过代码打印对象内存地址,也可以大体看到其中关系的影子:

@implementation TSChannelThreeViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    //初始化man对象
    Man *man = [[Man alloc]init];
    NSLog(@"man实例对象 - %@,man实例对象内存首位地址 - %p",man,man);
    //获取man对象的类
    Class manClass = object_getClass(man);
    NSLog(@"Man类类对象 - %@,Man类类对象内存首位地址 - %p",manClass,manClass);
    NSLog(@"Man类父类(superClass)对象 - %@,Man类父类(superClass)对象内存首位地址 - %p",[manClass superclass],[manClass superclass]);
    //获取Man类的元类
    Class manMetaClass = [self getObjectMetaClass:manClass];
    NSLog(@"Man类元类对象 - %@,Man类元类对象内存首位地址 - %p",manMetaClass,manMetaClass);
    NSLog(@"Man类元类的父类(superClass)对象 - %@,Man类元类的父类(superClass)对象内存首位地址 - %p",[manMetaClass superclass],[manMetaClass superclass]);
    //获取Man类的根元类
    Class manRootMetaClass = [self getObjectMetaClass:manMetaClass];
    NSLog(@"Man类根元类对象 - %@,Man类根元类对象内存首位地址 - %p",manRootMetaClass,manRootMetaClass);
    NSLog(@"Man类根元类的父类(superClass)对象 - %@,Man类根元类的父类(superClass)对象内存首位地址 - %p",[manRootMetaClass superclass],[manRootMetaClass superclass]);
    
    //初始化person对象
    Person *person = [[Person alloc]init];
    NSLog(@"person实例对象 - %@,person实例对象内存首位地址 - %p",person,person);
    //获取person对象的类
    Class personClass = object_getClass(person);
    NSLog(@"Person类类对象 - %@,Person类类对象内存首位地址 - %p",personClass,personClass);
    NSLog(@"Person类父类(superClass)对象 - %@,Person类父类(superClass)对象内存首位地址 - %p",[personClass superclass],[personClass superclass]);
    //获取Person类的元类
    Class personMetaClass = [self getObjectMetaClass:personClass];
    NSLog(@"Person类元类对象 - %@,Person类元类对象内存首位地址 - %p",personMetaClass,personMetaClass);
    NSLog(@"Person类元类的父类(superClass)对象 - %@,Person类元类的父类(superClass)对象内存首位地址 - %p",[personMetaClass superclass],[personMetaClass superclass]);
    //获取Person类的根元类
    Class personRootMetaClass = [self getObjectMetaClass:personMetaClass];
    NSLog(@"Person类根元类对象 - %@,Person类根元类对象内存首位地址 - %p",personRootMetaClass,personRootMetaClass);
    NSLog(@"Person类根元类的父类(superClass)对象 - %@,Person类根元类的父类(superClass)对象内存首位地址 - %p",[personRootMetaClass superclass],[personRootMetaClass superclass]);
    
    //初始化obj对象
    NSObject *obj = [[NSObject alloc]init];
    NSLog(@"obj实例对象 - %@,obj实例对象内存首位地址 - %p",obj,obj);
    //获取obj对象的类
    Class objClass = object_getClass(obj);
    NSLog(@"NSObject类类对象 - %@,NSObject类类对象内存首位地址 - %p",objClass,objClass);
    NSLog(@"NSObject类父类(superClass)对象 - %@,NSObject类父类(superClass)对象内存首位地址 - %p",[objClass superclass],[objClass superclass]);
    //获取NSObject类的元类
    Class objMetaClass = [self getObjectMetaClass:objClass];
    NSLog(@"NSObject类元类对象 - %@,NSObject类元类对象内存首位地址 - %p",objMetaClass,objMetaClass);
    NSLog(@"NSObject类元类的父类(superClass)对象 - %@,NSObject类元类的父类(superClass)对象内存首位地址 - %p",[objMetaClass superclass],[objMetaClass superclass]);
    //获取NSObject类的根元类
    Class objRootMetaClass = [self getObjectMetaClass:objMetaClass];
    NSLog(@"NSObject类根元类对象 - %@,NSObject类根元类对象内存首位地址 - %p",objRootMetaClass,objRootMetaClass);
    NSLog(@"NSObject类根元类的父类(superClass)对象 - %@,NSObject类根元类的父类(superClass)对象内存首位地址 - %p",[objRootMetaClass superclass],[objRootMetaClass superclass]);
}

/// 获取对象的元类
/// @param object 对象
- (Class)getObjectMetaClass:(id)object {
    if (object == nil) {
        return nil;
    }
    Class class = nil;
    class = object_getClass(object);
    //判断类对象是否为元类
    if (class_isMetaClass(class)) {
        return class;
    } else {
        [self getObjectMetaClass:class];
    }
    return class;
}

@end

截屏2022-06-21 16.09.05.png

也可以通过LLDB调试命令打印内存,也可以看到其中的关系:

1. 执行x/4gx obj,以16进制格式化打印4段内存情况。
2. 对象内存中的第一个成员为isa,将isa和ISA_MASK进行与运算即可得到类对象的地址,再以16进制打印运算结果。
3. (注:arm64中,ISA_MASK 宏定义的值为0x0000000ffffffff8ULL 、x86_64中,ISA_MASK 宏定义的值为0x00007ffffffffff8ULL4)
4.  po打印对象。
截屏2022-06-23 09.24.45.png
  1. Method(objc_method) —— 和我们平时理解的函数是一致的,就是表示能够独立完成一个功能的一段代码。

定义如下:

typedef struct objc_method *Method;
// SEL method_name 方法名
// char *method_types 方法类型
// IMP method_imp 方法实现
struct objc_method {
    SEL _Nonnull method_name                                 OBJC2_UNAVAILABLE;
    char * _Nullable method_types                            OBJC2_UNAVAILABLE;
    IMP _Nonnull method_imp                                  OBJC2_UNAVAILABLE;
}                                                            OBJC2_UNAVAILABLE;
  1. SEL(objc_selector) —— 它是selector在Objective-C中的表示类型(Swift中是Selector类)。selector是SEL的一个实例,selector是方法选择器,可以理解为区分方法的 ID,而这个 ID 的数据结构是SEL。

定义如下:

Objc.h
/// An opaque type that represents a method selector.代表一个方法的不透明类型
typedef struct objc_selector *SEL;

selector就是个映射到方法的C字符串,可以用 Objective-C 编译器命令@selector()或者 Runtime 系统的sel_registerName函数来获得一个 SEL 类型的方法选择器。其命名规则:不同的类,selector可以重复, 但同一个类,selector不能重复。这也导致Objective-C无法像C语言一样进行函数的重载,因为selector只记了method的name,没有参数,所以没办法依靠参数区分不同的method。

  1. IMP —— 是指向最终实现程序的内存地址的指针。在iOS的Runtime中,Method通过selector和IMP两个属性,实现了快速查询方法及实现,相对提高了性能,又保持了灵活性。

定义如下:

/// A pointer to the function of a method implementation.  指向一个方法实现的指针
typedef id (*IMP)(id, SEL, ...); 
#endif
  1. 类缓存(objc_cache)

当Objective-C运行时通过跟踪它的isa指针检查对象时,它可以找到一个实现许多方法的对象。然而,你可能只调用它们的一小部分,并且每次查找时,搜索所有选择器的类分派表没有意义。所以类实现一个缓存,每当你搜索一个类分派表,并找到相应的选择器,它把它放入它的缓存。所以当objc_msgSend查找一个类的选择器,它首先搜索类缓存。这是基于这样的理论:如果你在类上调用一个消息,你可能以后再次调用该消息。

为了加速消息分发, 系统会对方法和对应的地址进行缓存,就放在上述的objc_cache,所以在实际运行中,大部分常用的方法都是会被缓存起来的,Runtime系统实际上非常快,接近直接执行内存地址的程序速度。

  1. Category(objc_category)

Category是表示一个指向分类的结构体的指针,在category_t的结构体中可以看出,分类中可以添加实例方法,类方法,甚至可以实现协议,添加属性,但不可以添加成员变量,但可以通过objc_setAssociatedObject和objc_getAssociatedObject实现增加实例变量的效果。其定义如下:

// name:是指 class_name 而不是 category_name。
// cls:要扩展的类对象,编译期间是不会定义的,而是在Runtime阶段通过name对应到对应的类对象。
// instanceMethods:category中所有给类添加的实例方法的列表。
// classMethods:category中所有添加的类方法的列表。
// protocols:category实现的所有协议的列表。
// instanceProperties:表示Category里所有的properties,这就是我们可以通过objc_setAssociatedObject和objc_getAssociatedObject增加实例变量的原因,不过这个和一般的实例变量是不一样的。

struct category_t { 
    const char *name; 
    classref_t cls; 
    struct method_list_t *instanceMethods; 
    struct method_list_t *classMethods;
    struct protocol_list_t *protocols;
    struct property_list_t *instanceProperties;
};

Runtime消息转发

Runtime进行一次发送消息会在相关的类对象中搜索方法列表,如果找不到则会沿着继承树向上一直搜索知道继承树根部(通常为NSObject),如果还是找不到并且消息转发都失败了就回执行doesNotRecognizeSelector:方法报unrecognized selector错。但在错误抛出前,还有最后的三次机会进行消息转发处理。分别为:

  • 动态方法解析
  • 备用接收者
  • 完整消息转发
301129-a1159ef51f453da8.png
  1. 动态方法解析

首先,Objective-C运行时会调用 +resolveInstanceMethod:或者 +resolveClassMethod:,让你有机会提供一个函数实现。如果你添加了函数并返回YES, 那运行时系统就会重新启动一次消息发送的过程。

\color{red}{练习的代码片段(例如:实现一个动态方法解析的例子)}

- (void)viewDidLoad {

    [super viewDidLoad];
    self.navigationItem.title = @"测试代码控制器";
    
    //执行foo函数
    [self performSelector:@selector(foo:)];
}

+ (BOOL)resolveInstanceMethod:(SEL)sel {
    if (sel == @selector(foo:)) {//如果是执行foo函数,就动态解析,指定新的IMP
        class_addMethod([self class], sel, (IMP)fooMethod, "v@:");
        return YES;
    }
    return [super resolveInstanceMethod:sel];
}

void fooMethod(id obj, SEL _cmd) {
    NSLog(@"foo即使不存在也不会报错");//新的foo函数
}

虽然没有实现foo:这个函数,但是通过class_addMethod动态添加fooMethod函数,并执行fooMethod这个函数的IMP。输出如下:

截屏2020-08-01下午2.54.42.png
  1. 备用接收者

如果目标对象没有在+resolveInstanceMethod:函数或者 +resolveClassMethod:函数中成功处理消息,但实现了-forwardingTargetForSelector:函数,Runtime 这时就会调用这个函数,把这个消息转发给其他对象的机会(selector传递的函数不能带有参数,例如@selector(foo:),否则仍旧会报错)。

\color{red}{练习的代码片段(实现一个备用接收者的例子如下:)}

@interface Person: NSObject

@end

@implementation Person

- (void)foo{
    NSLog(@"foo即使不存在也不会报错");//Person的foo函数
}

@end


@interface TestCodeController ()


@end

@implementation TestCodeController



- (void)viewDidLoad {

    [super viewDidLoad];
    self.navigationItem.title = @"测试代码控制器";
    
    //执行foo函数
    [self performSelector:@selector(foo)];
}


- (id)forwardingTargetForSelector:(SEL)aSelector {
    if (aSelector == @selector(foo)) {
        return [Person new];//返回Person对象,让Person对象接收这个消息
    }
    
    return [super forwardingTargetForSelector:aSelector];
}

@end

通过forwardingTargetForSelector把当前ViewController的方法转发给了Person去执行了,输出如下:

截屏2020-08-01下午3.30.44.png
  1. 完整消息转发

如果前面两步都未能完成消息的处理,则唯一能做的就是启用完整的消息转发机制了。
首先它会发送-methodSignatureForSelector:消息获得函数的参数和返回值类型。如果-methodSignatureForSelector:返回nil ,Runtime则会发出 -doesNotRecognizeSelector: 消息,程序这时也就挂掉了。如果返回了一个函数签名,Runtime就会创建一个NSInvocation 对象并发送 -forwardInvocation:消息给目标对象。

\color{red}{练习的代码片段(实现一个完整转发的例子如下:)}

@interface Person: NSObject

@end

@implementation Person

- (void) foo {
    NSLog(@"foo即使不存在也不会报错");//Person的foo函数
}

@end


@interface TestCodeController ()<UIScrollViewDelegate>

@property (nonatomic, strong) UIView *view1;
@property (nonatomic, strong) UIView *view2;
@property (nonatomic, strong) UIView *view3;

@end

@implementation TestCodeController



- (void)viewDidLoad {

    [super viewDidLoad];
    self.navigationItem.title = @"测试代码控制器";
    
    //执行foo函数
    [self performSelector:@selector(foo)];
}


- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
    if ([NSStringFromSelector(aSelector) isEqualToString:@"foo"]) {
        return [NSMethodSignature signatureWithObjCTypes:"v@:"];//签名,进入forwardInvocation
    }
    
    return [super methodSignatureForSelector:aSelector];
}

- (void)forwardInvocation:(NSInvocation *)anInvocation {
    SEL sel = anInvocation.selector;

    Person *p = [Person new];
    if([p respondsToSelector:sel]) {
        [anInvocation invokeWithTarget:p];
    }
    else {
        [self doesNotRecognizeSelector:sel];
    }

}

@end

实现了完整的转发。通过签名,Runtime生成了一个对象anInvocation,发送给了forwardInvocation,我们在forwardInvocation方法里面让Person对象去执行了foo函数。输出如下:

截屏2020-08-01下午3.55.50.png

Runtime应用

Runtime简直就是做大型框架的利器。它的应用场景非常多,下面就介绍一些常见的应用场景。

  • 关联对象(Objective-C Associated Objects)给分类增加属性
  • 方法魔法(Method Swizzling)方法添加和替换和KVO实现
  • 消息转发(热更新)解决Bug(JSPatch)
  • 实现NSCoding的自动归档和自动解档
  • 实现字典和模型的自动转换(MJExtension)

Runtime常用属性

typedef struct objc_property *objc_property_t;

属性描述 : 表示Objective-C声明的属性的模糊类型

typedef struct objc_method *Method;

属性描述 :表示类定义中的方法的模糊类型

Runtime常用函数

OBJC_EXPORT Class _Nullable
object_getClass(id _Nullable obj) 
    OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);

函数描述 : 返回对象的类

参数 :

obj : 要检查的对象。

返回值 : 返回其实例对象的类,如果对象为空,则为Nil。

FOUNDATION_EXPORT Class _Nullable NSClassFromString(NSString *aClassName);

函数描述 : 这个方法判断类是否存在,如果存在就动态加载的,不存为就返回一个空对象。
例如:
id myObj = [[NSClassFromString(@"MySpecialClass") alloc] init];
正常情况下等价于:
id myObj = [[MySpecialClass alloc] init];
但是,如果程序中并不存在MySpecialClass这个类,[[MySpecialClass alloc] init]写法会出错,而[[NSClassFromString(@"MySpecialClass") alloc] init]只是返回一个空对象而已。

FOUNDATION_EXPORT SEL NSSelectorFromString(NSString *aSelectorName);

函数描述返回带有给定名称的选择器。要创建一个选择器,NSSelectorFromString将一个UTF-8编码的aSelectorName字符表示传递给sel_registerName,并返回该函数返回的值。因此,请注意,如果选择器不存在,则注册该选择器并返回新注册的选择器。

注 : 冒号(“:”)是方法名的一部分;setHeight与setHeight:是不同的。

参数 :

aSelectorName : 任意长度的字符串,包含任意字符,表示选择器的名称。

返回值 :

由一个aSelectorName命名的选择器。如果aSelectorName为nil,或者无法转换为UTF-8(这应该只是由于内存不足),则返回(SEL)0。

\color{red}{例如:}

- (void)viewDidLoad {
    
    [super viewDidLoad];

    SEL sel = NSSelectorFromString(@"dynamicLoadingL");
    if([self respondsToSelector:sel]){
        [self performSelector:sel];
    }
    
}

- (void)dynamicLoadingL{
    NSLog(@"函数调用了");
}

OBJC_EXPORT objc_property_t _Nonnull * _Nullable
protocol_copyPropertyList(Protocol * _Nonnull proto,
                          unsigned int * _Nullable outCount)
    OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);

函数描述 :返回由协议声明的属性数组

参数 :

proto : 一个协议。

outCount : 在返回时,包含返回数组中的元素数。

返回值 : 类型为objc_property_t的指针的C数组,用于描述proto声明的属性。不包括本协议所采用的其他协议声明的任何属性。数组包含outCount指针,后跟一个空终止符。必须使用free()释放数组。如果协议没有声明属性,则返回NULL并outCount为0。

OBJC_EXPORT const char * _Nonnull
property_getName(objc_property_t _Nonnull property) 
    OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);

函数描述 :返回属性的名称

返回值 :包含属性名称的C字符串。

OBJC_EXPORT const char * _Nullable
property_getAttributes(objc_property_t _Nonnull property) 
    OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);

函数描述 :返回属性的属性字符串

返回值 :包含属性属性的C字符串。类似这样:@"T@"NSString",C,N,V_name"

//关联对象
OBJC_EXPORT void
objc_setAssociatedObject(id _Nonnull object, const void * _Nonnull key,
                         id _Nullable value, objc_AssociationPolicy policy)
    OBJC_AVAILABLE(10.6, 3.1, 9.0, 1.0, 2.0);

函数描述使用给定的键和关联策略设置给定对象的关联值

参数 :

id object : 表示关联者。

const void *key : 获取被关联者的key

id value : 被关联者,对象。通过NIL来清除现有的关联。

policy : 关联策略,有assign,retain,copy.一般使用”“OBJC_ASSOCIATION_RETAIN_NONATOMIC”

policy关联策略有五种:
OBJC_ASSOCIATION_ASSIGN 等价于 @property(assign)。
OBJC_ASSOCIATION_RETAIN_NONATOMIC等价于 @property(strong, nonatomic)。
OBJC_ASSOCIATION_COPY_NONATOMIC等价于@property(copy, nonatomic)。
OBJC_ASSOCIATION_RETAIN等价于@property(strong,atomic)。
OBJC_ASSOCIATION_COPY等价于@property(copy, atomic)。
//获取关联对象
OBJC_EXPORT id _Nullable
objc_getAssociatedObject(id _Nonnull object, const void * _Nonnull key)
    OBJC_AVAILABLE(10.6, 3.1, 9.0, 1.0, 2.0);

函数描述返回与给定键的给定对象关联的值

参数 :

id object : 关联者对象。

key : 被关联对象索引key

返回值 : 与对象的键关联的值。

\color{red}{练习的代码片段(例如创建一个伪属性示例):}

//
//  play.h

#import <Foundation/Foundation.h>


@interface play : NSObject

@end

//
//  play.m

#import "play.h"

@implementation play

@end

//
//  play+love.h


#import "play.h"

@interface play (love)

@property (nonatomic, copy) NSString *lover;

@end

//
//  play+love.m


#import "play+love.h"
#import <objc/runtime.h>

static void * LOVERKEY = &LOVERKEY;

@implementation play (love)

- (void)setLover:(NSString *)lover{
    objc_setAssociatedObject(self, LOVERKEY, lover, OBJC_ASSOCIATION_COPY_NONATOMIC);
}

- (NSString *)lover{
    return objc_getAssociatedObject(self, LOVERKEY);
}

@end
//
//  ViewController.h

#import <UIKit/UIKit.h>

@interface ViewController : UIViewController


@end

//
//  ViewController.m


#import "ViewController.h"
#import "play.h"
#import "play+love.h"

@interface ViewController ()

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    play *p = [[play alloc]init];
    p.lover = @"可以赋值";
    NSLog(@"%@",p.lover);
    
}

@end

运行结果:

屏幕快照 2019-06-26 下午11.23.46.png
//获取当前类中实例方法
OBJC_EXPORT Method _Nullable
class_getInstanceMethod(Class _Nullable cls, SEL _Nonnull name)
    OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0);

函数描述返回给定类的指定实例方法。注意,这个函数搜索实现的超类,而class_copyMethodList不搜索。

参数 :

aClass : 要检查的类。

aSelector : 要检索的方法的选择器。

返回值 :对应于由aSelector为aClass指定的类指定的选择器实现的方法,如果指定的类或其超类不包含具有指定选择器的实例方法,则为NULL。

//获取当前类中对象类方法
OBJC_EXPORT Method _Nullable
class_getClassMethod(Class _Nullable cls, SEL _Nonnull name)
    OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0);

函数描述返回指向数据结构的指针,该数据结构描述给定类的给定类方法。注意,这个函数搜索实现的超类,而class_copyMethodList不搜索。

参数 :

aClass : 指向类定义的指针。传递包含要检索的方法的类。

aSelector : SEL类型的指针。传递要检索的方法的选择器。

返回值 : 指向方法数据结构的指针,该方法数据结构对应于由aSelector为aClass指定的类指定的选择器的实现,如果指定的类或其超类不包含具有指定选择器的类方法,则为NULL。

OBJC_EXPORT BOOL
class_addMethod(Class _Nullable cls, SEL _Nonnull name, IMP _Nonnull imp, 
                const char * _Nullable types) 
    OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);

函数描述向具有给定名称和实现的类添加新方法。class_addMethod将添加超类实现的重写,但不会替换该类中的现有实现。要更改现有实现,请使用method_setImplementation。

参数 :

cls :要向其添加方法的类。

name :指定要添加的方法的名称的selector。

imp :一个函数,它是新方法的实现。该函数必须包含至少两个参数- self和_cmd。

types :描述方法的参数类型的字符数组。有关可能的值,请参阅Objective-C运行时编程指南>类型编码。因为函数必须包含至少两个参数—self和_cmd,所以第二个和第三个字符必须是“@:”(第一个字符是返回类型)。

返回值 : 如果方法被成功添加,则为YES,否则为NO(例如,该类已经包含具有该名称的方法实现)。

OBJC_EXPORT const char * _Nullable
method_getTypeEncoding(Method _Nonnull m) 
    OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);

函数描述返回一个字符串,该字符串描述方法的参数和返回类型

参数 :

method :检查的方法。

返回值 :一个C字符串。字符串可以为空。

//方法交换(黑魔法)
OBJC_EXPORT void
method_exchangeImplementations(Method _Nonnull m1, Method _Nonnull m2) 
    OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);

函数描述在运行时将两个method进行交换,这段代码可以写在任何地方,只有当这段代码执行完成后才能起到交换的作用。一般在load方法中执行方法交换,因为load方法在APP运行时就会调用,且只调用一次。可以用于对某个系统的方法保持原功能的情况下拓展功能。

参数 :

m1 : m1方法与第二种方法交换。

m2 : m2方法与第一种方法交换。

\color{red}{例如:Objective-C中经典的运行时函数交换的代码片段:}

+ (void)load
{
    //保证方法替换只执行一次
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        //获取类对象
        Class class = [self class];
        //获取viewWillAppear的函数编号
        SEL originalSelector = @selector(viewWillAppear:);
        //获取fd_viewWillAppear的函数编号
        SEL swizzledSelector = @selector(fd_viewWillAppear:);
        //获取当前类对象的实例函数viewWillAppear:
        Method originalMethod = class_getInstanceMethod(class, originalSelector);
        //获取当前类对象的实例函数fd_viewWillAppear:
        Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);
        //class_addMethod:如果发现方法已经存在,会返回失败,也可以用来做检查用,这里是为了避免源方法没有实现的情况;如果方法没有存在,我们则先尝试添加被替换的方法的实现
        //向当前类的viewWillAppear:函数添加新的fd_viewWillAppear函数的实现,并添加描述方法参数和返回类型的字符串
        BOOL success = class_addMethod(class, originalSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod));
        //判断是否添加成功
        if (success) {
            //如果返回成功:则说明被替换方法没有存在,也就是被替换的方法没有被实现,需要先把这个方法实现,class_replaceMethod本身会尝试调用class_addMethod和method_setImplementation
            //替换当前类的fd_viewWillAppear:方法实现为viewWillAppear:方法的实现
            class_replaceMethod(class, swizzledSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod));
        } else {
            //如果返回失败:则说明被替换方法已经存在.直接将两个方法的实现交换
            method_exchangeImplementations(originalMethod, swizzledMethod);
        }
    });
}

- (void)fd_viewWillAppear:(BOOL)animated
{

    [self fd_viewWillAppear:animated];
    
    if (self.fd_willAppearInjectBlock) {
        self.fd_willAppearInjectBlock(self, animated);
    }
}

OBJC_EXPORT struct objc_method_description
protocol_getMethodDescription(Protocol * _Nonnull proto, SEL _Nonnull aSel,
                              BOOL isRequiredMethod, BOOL isInstanceMethod)
    OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);

函数描述 : 返回给定协议的指定方法的方法描述结构

参数 :

proto : 协议。

aSel : 选择器。

isRequiredMethod : 一个布尔值,指示aSel是否是必需的方法。

isInstanceMethod : 一个布尔值,指示aSel是否为实例方法。

返回值:一种描述协议proto的aSel、isRequiredMethod和isInstanceMethod指定的方法的objc_method_description结构。如果协议不包含指定的方法,则返回值为{NULL,NULL}的objc_method_description结构。

  • objc_method_description 定义一个Objective-C方法的结构
struct objc_method_description {
    SEL _Nullable name;               /**< 方法的名称 */
    char * _Nullable types;           /**< 方法参数的类型 */
};

一些使用的示例

\color{red}{例如替换UIImage类的imageNamed:函数,全局对某一图像进行替换:}

//
//  UIImage+ExchangeImage.h


#import <UIKit/UIKit.h>

@interface UIImage (ExchangeImage)

@end
//
//  UIImage+ExchangeImage.m


#import "UIImage+ExchangeImage.h"
#import <objc/runtime.h>

@implementation UIImage (ExchangeImage)

+ (void)load
{
    //保证方法替换只执行一次
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        //获取对象类
        Class class = object_getClass((id)self);
        //获取imageNamed:的函数编号
        SEL originalSelector = @selector(imageNamed:);
        //获取bk_imageNamed:的函数编号
        SEL swizzledSelector = @selector(exchange_imageNamed:);
        //获取当前类对象的类函数imageNamed:
        Method originalMethod = class_getClassMethod(class, originalSelector);
        //获取当前类对象的类函数bk_imageNamed:
        Method swizzledMethod = class_getClassMethod(class, swizzledSelector);
        //class_addMethod:如果发现方法已经存在,会返回失败,也可以用来做检查用,这里是为了避免源方法没有实现的情况;如果方法没有存在,我们则先尝试添加被替换的方法的实现
        //向当前类的imageNamed:函数添加新的bk_imageNamed:函数的实现,并添加描述方法参数和返回类型的字符串
        BOOL success = class_addMethod(class, originalSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod));
        //判断是否添加成功
        if (success) {
            //如果返回成功:则说明被替换方法没有存在,也就是被替换的方法没有被实现,需要先把这个方法实现,class_replaceMethod本身会尝试调用class_addMethod和method_setImplementation
            //替换当前类的bk_imageNamed:方法实现为imageNamed:方法的实现
            class_replaceMethod(class, swizzledSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod));
        } else {
            //如果返回失败:则说明被替换方法已经存在.直接将两个方法的实现交换
            method_exchangeImplementations(originalMethod, swizzledMethod);
        }
    });
}

+ (instancetype)exchange_imageNamed:(NSString *)name {
    [self exchange_imageNamed:name];
    if ([name isEqualToString:@"btn_plus_normal"] || [name isEqualToString:@"btn_plus_disabled"]) {
       return [self exchange_imageNamed:@"ic_success_red"];
    }else {
       return [self exchange_imageNamed:name];
    }
}

@end

替换前 :

截屏2020-12-16上午10.31.54.png

替换后 :

截屏2020-12-16上午10.33.18.png

\color{red}{例如全局定义控制器返回按钮的样式:}

//
//  UIViewController+TSSwizzle.h

#import <UIKit/UIKit.h>

@interface UIViewController(TSSwizzle)

@end

//
//  UIViewController+TSSwizzle.m


#import "UIViewController+TSSwizzle.h"

static char *viewLoadStartTimeKey = "viewLoadStartTimeKey";

@implementation UIViewController(TSSwizzle)

+ (void)load {
    
    [UIViewController swizzleMethods:[self class] originalSelector:@selector(viewDidLoad) swizzledSelector:@selector(ysc_viewDidLoad)];
    
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        SEL origSel = @selector(viewDidAppear:);
        SEL swizSel = @selector(swiz_viewDidAppear:);
        [UIViewController swizzleMethods:[self class] originalSelector:origSel swizzledSelector:swizSel];
        
        SEL vcWillAppearSel=@selector(viewWillAppear:);
        SEL swizWillAppearSel=@selector(swiz_viewWillAppear:);
        [UIViewController swizzleMethods:[self class] originalSelector:vcWillAppearSel swizzledSelector:swizWillAppearSel];
        
        SEL vcDidLoadSel=@selector(viewDidLoad);
        SEL swizDidLoadSel=@selector(swiz_viewDidLoad);
        [UIViewController swizzleMethods:[self class] originalSelector:vcDidLoadSel swizzledSelector:swizDidLoadSel];
        
        SEL vcDidDisappearSel=@selector(viewDidDisappear:);
        SEL swizDidDisappearSel=@selector(swiz_viewDidDisappear:);
        [UIViewController swizzleMethods:[self class] originalSelector:vcDidDisappearSel swizzledSelector:swizDidDisappearSel];
        
        SEL vcWillDisappearSel=@selector(viewWillDisappear:);
        SEL swizWillDisappearSel=@selector(swiz_viewWillDisappear:);
        [UIViewController swizzleMethods:[self class] originalSelector:vcWillDisappearSel swizzledSelector:swizWillDisappearSel];
    });
}

- (void)ysc_viewDidLoad {
    
    [self ysc_viewDidLoad];
    [self setUpCommonHeader];
    
}

-(void)setViewLoadStartTime:(CFAbsoluteTime)viewLoadStartTime{
    
    objc_setAssociatedObject(self, &viewLoadStartTimeKey, @(viewLoadStartTime), OBJC_ASSOCIATION_COPY);
    
}

-(CFAbsoluteTime)viewLoadStartTime{
    
    return [objc_getAssociatedObject(self, &viewLoadStartTimeKey) doubleValue];
}

+ (void)swizzleMethods:(Class)class originalSelector:(SEL)origSel swizzledSelector:(SEL)swizSel{
    
    Method origMethod = class_getInstanceMethod(class, origSel);
    Method swizMethod = class_getInstanceMethod(class, swizSel);
    
    //如果原始方法已经存在,则class_addMethod将失败
    BOOL didAddMethod = class_addMethod(class, origSel, method_getImplementation(swizMethod), method_getTypeEncoding(swizMethod));
    if (didAddMethod) {
        class_replaceMethod(class, swizSel, method_getImplementation(origMethod), method_getTypeEncoding(origMethod));
    } else {
        //origMethod和swizMethod已经存在,交换函数
        method_exchangeImplementations(origMethod, swizMethod);
    }
}

- (void)swiz_viewDidAppear:(BOOL)animated{
    
    [self swiz_viewDidAppear:animated];
    if (self.viewLoadStartTime) {
        CFAbsoluteTime linkTime = (CACurrentMediaTime() - self.viewLoadStartTime);
      YSCLog(@" %f s--------------------ssssss   %@:速度:         %f s",self.viewLoadStartTime, self.class,linkTime  );
      #if DEBUG
      //在窗口输出打开控制器的耗时
      [WINDOW makeToast:[NSString stringWithFormat:@"速度:\n%f s",linkTime]];
      #endif

        self.viewLoadStartTime = 0;
    }
}

-(void)swiz_viewWillAppear:(BOOL)animated{
    
    [self swiz_viewWillAppear:animated];
    
}

-(void)swiz_viewDidDisappear:(BOOL)animated{
    
    [self swiz_viewDidDisappear:animated];
    
}

-(void)swiz_viewWillDisappear:(BOOL)animated{
    
    [self swiz_viewWillDisappear:animated];
    
}

-(void)swiz_viewDidLoad{
    
    self.viewLoadStartTime =CACurrentMediaTime();
    [self swiz_viewDidLoad];
    
}

- (void)setUpCommonHeader {
    
    if (self.navigationController.viewControllers.count > 1 ||
        self.presentingViewController) {
        //设置按钮样式与点击事件
        __weak typeof(self) weakSelf = self;
        UIButton *backButton = [UIButton buttonWithType:UIButtonTypeCustom];
        [backButton addTarget:weakSelf action:@selector(backToPreviousViewController) forControlEvents:UIControlEventTouchUpInside];
        backButton.frame = CGRectMake(0, 0, 25.0, 40.0);//调整返回按钮点击区域大小 2019.9.2
        backButton.imageEdgeInsets = UIEdgeInsetsMake(0, -10, 0, 0);
        [backButton setImage:[UIImage imageNamed:@"btn_back_dark"] forState:UIControlStateNormal];
        [backButton setImage:[UIImage imageNamed:@"btn_back_dark"] forState:UIControlStateHighlighted];
        UIBarButtonItem *backBarButtonItem = [[UIBarButtonItem alloc] initWithCustomView:backButton];
        self.navigationItem.leftBarButtonItem = backBarButtonItem;
    }
}

- (void)backToPreviousViewController {
    //控制返回的函数
    [self cancel];
}

@end

替换前:


截屏2020-01-11下午3.57.38.png

替换后:


截屏2020-01-11下午3.59.24.png

\color{red}{例如为视图添加点击事件示例 }(多用于表视图中为cell的某个控件添加点击事件,方便在控制器中获取数据源中的模型进行对应的操作):

//
//  UIView+Action.h

#import <UIKit/UIKit.h>

@interface UIView(Action)

///返回与&KEY_ITEM_ACTION关联的选择器
- (SEL __nullable)itemAction;

///返回与&KEY_ITEM_TARGET关联的事件处理者
- (id __nullable)itemTarget;

///返回与&KEY_ITEM_POSITION关联的view所在列表中的row number
- (NSInteger)itemPosition;

///返回与&KEY_ITEM_SECTION关联的view所在列表中的section number
- (NSInteger) itemSection;

///为视图添加事件
- (void)addTarget:(id _Nonnull)target
           action:(SEL _Nonnull)action
          section:(NSInteger)section;

///为视图添加事件
- (void)addTarget:(id _Nonnull)target
           action:(SEL _Nonnull)action
         position:(NSInteger)position
          section:(NSInteger)section;

///为添加事件视图的子视图添加事件
- (void)addEvent:(UIView * _Nonnull)view;
@end
//
//  UIView+Action.m

#import "UIView+Action.h"
#import "UIView+ExtraTag.h"
#import <objc/runtime.h>

static void * KEY_ITEM_TARGET = &KEY_ITEM_TARGET;//事件处理者
static void * KEY_ITEM_ACTION = &KEY_ITEM_ACTION;//事件
static void * KEY_ITEM_POSITION = &KEY_ITEM_POSITION;//view在列表中所在的row
static void * KEY_ITEM_SECTION = &KEY_ITEM_SECTION;//view在列表中所在的section

@implementation UIView(Action)

- (SEL)itemAction {
    //返回与&KEY_ITEM_ACTION关联的选择器
    return NSSelectorFromString(objc_getAssociatedObject(self, KEY_ITEM_ACTION));
}

- (id)itemTarget {
    //返回与&KEY_ITEM_TARGET关联的事件处理者
    return objc_getAssociatedObject(self, KEY_ITEM_TARGET);
}

- (NSInteger)itemPosition {
    //返回与&KEY_ITEM_POSITION关联的view所在列表中的row number
    return [objc_getAssociatedObject(self, KEY_ITEM_POSITION) integerValue];
}

- (NSInteger)itemSection {
    //返回与&KEY_ITEM_SECTION关联的view所在列表中的section number
    return [objc_getAssociatedObject(self, KEY_ITEM_SECTION) integerValue];
}

/**
 为视图添加事件
 参数:
 target:事件的处理者对象,通常为self
 action:添加的事件
 section:view在列表中所在的section,如未在列表中,传递0
 */
- (void)addTarget:(id _Nonnull)target
           action:(SEL _Nonnull)action
          section:(NSInteger)section{
    [self addTarget:target action:action position:0 section:section];
}

/**
 为视图添加事件
 参数:
 target:事件的处理者对象,通常为self
 action:添加的事件
 position:view所在列表中的row number
 section:view在列表中所在的section number
*/
- (void)addTarget:(id _Nonnull)target
           action:(SEL _Nonnull)action
         position:(NSInteger)position
          section:(NSInteger)section {
    //为&KEY_ITEM_TARGET与事件处理者设置关联
    objc_setAssociatedObject(self, KEY_ITEM_TARGET, target, OBJC_ASSOCIATION_ASSIGN);
    //为&KEY_ITEM_ACTION与事件设置关联
    objc_setAssociatedObject(self, KEY_ITEM_ACTION, NSStringFromSelector(action), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    //&KEY_ITEM_POSITION与view所在列表中的row number设置关联
    objc_setAssociatedObject(self, KEY_ITEM_POSITION, @(position), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    //&KEY_ITEM_SECTION与view在列表中所在的section number设置关联
    objc_setAssociatedObject(self, KEY_ITEM_SECTION, @(section), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

///添加事件的视图为UIButton时执行的函数
- (void)onButtonClicked:(UIView *)sender {
    [self callTarget:sender];
}

///添加事件的视图为UITextField时执行的函数
- (void)textFieldDidEndEditing:(UITextField *)textField {
    [self callTarget:textField];
}

///添加事件的视图为UIView时执行的函数
- (void)onTap:(UIGestureRecognizer *)gesture {
    [self callTarget:gesture.view];
}

///执行与&KEY_ITEM_ACTION关联的函数
- (void)callTarget:(UIView *)sender {
    //与&KEY_ITEM_TARGET关联的事件处理者如果没有响应与&KEY_ITEM_ACTION关联的选择器
    if (![self.itemTarget respondsToSelector:self.itemAction]) {
        //结束函数
        return;
    }
    //为添加事件的视图设置与&KEY_POSITION的关联,参数为view所在列表中的row number
    [sender setPositionForTag:self.itemPosition];
    //为添加事件的视图设置与&KEY_SECTION的关联,参数为view所在列表中的section number
    [sender setSectionForTag:self.itemSection];
    //获取与&KEY_ITEM_TARGET关联的事件处理者中与&KEY_ITEM_ACTION关联的选择器的IMP(函数指针)
    IMP imp = [self.itemTarget methodForSelector:self.itemAction];
    //通过IMP(函数指针)获取函数
    void (*function) (id, SEL, UIView *) = (void *)imp;
    //执行函数
    function (self.itemTarget, self.itemAction, sender);
}

/**
 为添加事件视图的子视图添加事件
 参数:
 view :通过addTarget:(id _Nonnull)target action:(SEL _Nonnull)action position:(NSInteger)position section:(NSInteger)section函数添加事件的视图的子视图
 */
- (void)addEvent:(UIView *)view {
    if ([view isKindOfClass:[UIButton class]]) {
        //如果添加事件的视图是UIButton
        UIButton *button = (UIButton*)view;
        //添加点击事件
        [button addTarget:self
                   action:@selector (onButtonClicked:)
         forControlEvents:UIControlEventTouchUpInside];
    } else if ([view isKindOfClass:[UITextField class]]) {
        //如果添加事件的视图是UITextField
        UITextField *textFiled = (UITextField *)view;
        //添加UITextField结束编辑事件
        [textFiled addTarget:self
                      action:@selector (textFieldDidEndEditing:)
            forControlEvents:UIControlEventEditingDidEnd];
    } else if ([view isKindOfClass:[UIView class]]) {
        //如果添加事件的视图是UIView
        view.userInteractionEnabled = YES;
        //初始化点击手势识别
        UITapGestureRecognizer *gesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector (onTap:)];
        //遍历清除view已经添加的点击手势识别
        [view.gestureRecognizers enumerateObjectsUsingBlock:^(__kindof UIGestureRecognizer * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
            [view removeGestureRecognizer:obj];
        }];
        //添加点击手势识别到view
        [view addGestureRecognizer:gesture];
    }
}
@end

//UIView+ExtraTag.h

#import <UIKit/UIKit.h>
@interface UIView (ExtraTag)
/**
 * viewType为view的类别
 */
- (void)setViewTypeForTag:(NSInteger)viewType;
/**
 * position一般为view在列表中的row number
 */
- (void)setPositionForTag:(NSInteger)position;
/**
 * section一般为view在列表中section number
 */
- (void)setSectionForTag:(NSInteger)section;
/**
 * extraInfo一般用view在嵌套列表中的row number
 */
- (void)setExtraInfoForTag:(NSInteger)extraInfo;

- (NSInteger)getViewTypeOfTag;
- (NSInteger)getPositionOfTag;
- (NSInteger)getSectionOfTag;
- (NSInteger)getExtraInfoOfTag;
@end

//UIView+ExtraTag.m

#import "UIView+ExtraTag.h"
#import <objc/runtime.h>

static void * KEY_VIEW_TYPE = &KEY_VIEW_TYPE;
static void * KEY_POSITION = &KEY_POSITION;
static void * KEY_SECTION = &KEY_SECTION;
static void * KEY_EXTRA_INFO = &KEY_EXTRA_INFO;

@implementation UIView (ExtraTag)

- (void)setViewTypeForTag:(NSInteger)viewType {
    objc_setAssociatedObject(self, KEY_VIEW_TYPE, [NSNumber numberWithInteger:viewType], OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

- (void)setPositionForTag:(NSInteger)position {
    objc_setAssociatedObject(self, KEY_POSITION, [NSNumber numberWithInteger:position], OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

- (void)setSectionForTag:(NSInteger)section {
    objc_setAssociatedObject(self, KEY_SECTION, [NSNumber numberWithInteger:section], OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

- (void)setExtraInfoForTag:(NSInteger)extraInfo {
    objc_setAssociatedObject(self, KEY_EXTRA_INFO, [NSNumber numberWithInteger:extraInfo], OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

- (NSInteger)getViewTypeOfTag {
    return [objc_getAssociatedObject(self, KEY_VIEW_TYPE) integerValue];
}

- (NSInteger)getPositionOfTag {
    return [objc_getAssociatedObject(self, KEY_POSITION) integerValue];
}

- (NSInteger)getSectionOfTag {
    return [objc_getAssociatedObject(self, KEY_SECTION) integerValue];
}

- (NSInteger)getExtraInfoOfTag {
    return [objc_getAssociatedObject(self, KEY_EXTRA_INFO) integerValue];
}
@end
//  addEvent.pch

#ifndef addEvent_pch
#define addEvent_pch

#import "UIView+Action.h"
#import "UIView+ExtraTag.h"
#import "ViewType.h"

#endif
//  ViewType.h

#ifndef ViewType_h
#define ViewType_h

typedef NS_ENUM (NSUInteger, ViewType) {
    ViewTypeNone,
    ViewTypeOne,
};

#endif
//  labelView.h

#import <UIKit/UIKit.h>

@interface labelView : UIView

@end
//  labelView.m

#import "labelView.h"

@implementation labelView

- (instancetype)initWithFrame:(CGRect)frame
{
    self = [super initWithFrame:frame];
    if (self) {
        CGFloat labelWidth = 180;
        CGFloat labelheigth = 20;
        UILabel *label = [[UILabel alloc]initWithFrame:CGRectMake(0, 0, labelWidth, labelheigth)];
        //设置文本
        label.text = @"可以点击的标签";
        //设置文本左右居中
        label.textAlignment = NSTextAlignmentCenter;
        //设置文本颜色
        label.textColor = [UIColor redColor];
        //视图中添加标签
        [self addSubview:label];
        
        [label setViewTypeForTag:ViewTypeOne];
        
        [self addEvent:label];
    }
    return self;
}

@end
//  ViewController.h

#import <UIKit/UIKit.h>

@interface ViewController : UIViewController

@end
//  ViewController.m

#import "ViewController.h"
#import "labelView.h"

@interface ViewController ()

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    CGRect screen = [[UIScreen mainScreen] bounds];
    CGFloat labelViewWidth = 180;
    CGFloat labelViewHeigth = 20;
    labelView *label = [[labelView alloc]initWithFrame:CGRectMake((screen.size.width - labelViewWidth)/2, (screen.size.height - labelViewHeigth)/2, labelViewWidth, labelViewHeigth)];
    [label addTarget:self action:@selector(onClick:) section:0];
    [self.view addSubview:label];
}

- (void)onClick:(UIView *)sender{
    if([sender getViewTypeOfTag] == ViewTypeOne){
        UIAlertController *alertController = [UIAlertController alertControllerWithTitle:@"Alert" message:@"响应了点击" preferredStyle:UIAlertControllerStyleAlert];
        UIAlertAction *yesAction = [UIAlertAction actionWithTitle:@"YES" style:UIAlertActionStyleCancel handler:^(UIAlertAction *action){
            NSLog(@"Top YES Button");
        }];
        [alertController addAction:yesAction];
        [self presentViewController:alertController animated:true completion:nil];
    }
}

@end

运行结果:

Untitled.gif

笔记参考摘抄:

链接:https://www.jianshu.com/p/6ebda3cd8052

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,911评论 5 460
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 82,014评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 142,129评论 0 320
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,283评论 1 264
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,159评论 4 357
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,161评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,565评论 3 382
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,251评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,531评论 1 292
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,619评论 2 310
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,383评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,255评论 3 313
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,624评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,916评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,199评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,553评论 2 342
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,756评论 2 335

推荐阅读更多精彩内容