Runtime最实用的总结

  • 前言

  近期项目不急,所以有时间来看看自己想学的东西,记得去面试的时候很多面试官都问到runtime的知识点,自己虽然了解一点这方面的知识,但都很零碎。所以这段时间好好研究总结一下。runtime的资料网上很多,觉得很多都讲得比较晦涩难懂(个人观点)。我通过自己的学习总结一遍,主要讲一些常用的、实用的方法。

  • 什么是runtime?

  runtime简称运行时。就是系统在运行的时候的一些机制,其中最主要的就是消息机制。OC的函数调用成为消息消息发送。属于动态过程。在编译的时候并不能决定真正的调用函数(在编译阶段,OC可以调用任何函数,即使这个 函数并未实现,只要声明就不会报错。而C语言在编译时就会报错——对于C语言,函数在编译的时候会决定调用那个函数,编译完成之后直接顺序执行),只有在真正运行时才会根据函数名称找到对应的函数来调用。

  runtime是OC底层的一套C语言的API(使用时需引入<objc/runtime.h><objc/message.h>),其为 iOS 内部的核心之一,我们平时编写的 OC 代码,底层都是基于它来实现的,编译器最终会将OC代码转换为运行时的代码。比如:

  [receiver message];

  底层运行时会被编译器转化为:

  obje_msgSend(receiver,selector)。

  如果还有其他参数,比如:

  [recevier message:(id)arg1,arg2...]

  底层运行时会被编译器转化为:

  objc_msgSend(recevier,Selector,arg1,arg2,...)

以上代码你可能看不出他的价值,但我们只需要了解OC是一门动态语言,他会将一些工作放在代码运行时才处理而并非编译时。也就是说,有很多类和成员变量在我们编译时是不知道的,而在运行时,我们所编写的代码会转换成完整的确定代码运行。所以,编译器是不够用的,我们就需要一个运行时系统(Runtime system)来处理编译后的代码。

  • runtime可实现的功能

  (1) 动态交换两个方法的实现(特别是交换系统自带的方法)

  (2)动态添加对象的成员变量和方法

  (3)获取某个类的所有成员方法和成员变量 

  • runtime的相关运用?

  (1)拦截并替换系统自带的方法(Swizzle)。如拦截viewDidLoad、alloc、imageNamed等等;

  (2)动态的添加对象的成员变量和方法;

  (3)实现字典和模型的转换(利用runtime遍历模型对象的所有属性, 根据属性名从字典中取出对应的值, 设置到模型的属性上,反之亦然如:MJExtension);

  (4)将某些OC代码转换为运行时代码,探究其底层的实现。如Block的实现、KVO(利用runtime动态产生一个类);

  (5)NSCoding(归档和解档, 利用runtime遍历模型对象的所有属性);

  (6)用于封装框架(想怎么改就怎么改);

  (7)动态交换两个方法的实现

  • runtime常见的函数

  (1)class_copyPropertyList 获取一份拷贝的成员列表数组

  (2)property_getName 获取成员名称

  (3)objc_msgSend  给对象发送消息

  (4)object_getIvar 从Ivar(成员变量)对象中取值

  (5)object_setIvar 赋值函数

  (6)class_getInstanceVariable 获取成员对象的Ivar

  (7)class_copyMethodList  遍历某个类所有的方法

  (8)class_copyIvarList 遍历某个类所有的成员变量

  (9)class_getClassMethod 获取类的方法

  (10)method_exchangeImplementations 交换方法

  (11)class_replaceMethod 修改类的方法

  (12)method_setImplementation 来直接设置某个方法的IMP

  (13)class_…..

  这些都是学习runtime必须要知道的函数!!!!(10、11、12归根结底,都是偷换了selector的IMP)

  • runtime的一些术语的数据结构

  想要全面了解Runtime机制,我们必须先了解Runtime的一些术语及他们对应的数据结构

  SEL

  SEL是selector 的简写,俗称方法选择器,实质存储的是方法的名称(Swift中是Selector类)。selector是方法选择器,其作用就和名字一样,就像日常生活中,我们通过人名去辨别谁是谁。在OC相同的类不会有两个命名相同的方法。selector 对方法名进行包装,以便找到对应的方法实现。它的数据结构是:

typedef struct objc_selector *SEL;

我们可以看出它是个映射到方法的 C 字符串,我们可以通过 Objc 编译器器命令@selector()或者Runtime系统的sel_registerName函数来获取一个SEL类型的方法选择器。注意:不同类中相同名字的方法所对应的 selector 是相同的,由于变量的类型不同,所以不会导致它们调用方法实现混乱。

  Class

  Class是指向objc_class结构体的指针。它的数据结构是:typedef struct objc_class *Class;

  objc_class 的数据结构如下:

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;

 

从objc_class 可以看到,一个运行时类中关联了它的父类指针、类名、成员变量、方法、缓存以及附属的协议。其中 objc_ivar_list 和 objc_method_list 分别是成员变量列表和方法列表:

// 成员变量列表

struct objc_ivar_list {

    int ivar_count                                           OBJC2_UNAVAILABLE;

#ifdef __LP64__

    int space                                                OBJC2_UNAVAILABLE;

#endif

    /* variable length structure */

    struct objc_ivar ivar_list[1]                            OBJC2_UNAVAILABLE;

}                                                            OBJC2_UNAVAILABLE;

 

// 方法列表

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;

}

由此可见,我们可以动态修改 *methodList 的值来添加成员方法,这也是 Category 实现的原理,同样解释了 Category 不能添加属性的原因。这里可以参考下美团技术团队的文章:深入理解 Objective-C: Category。objc_ivar_list 结构体用来存储成员变量的列表,而 objc_ivar 则是存储了单个成员变量的信息;同理,objc_method_list 结构体存储着方法数组的列表,而单个方法的信息则由 objc_method 结构体存储。值得注意的时,objc_class 中也有一个 isa 指针,这说明 Objc 类本身也是一个对象。为了处理类和对象的关系,Runtime 库创建了一种叫做 Meta Class(元类) 的东西,类对象所属的类就叫做元类。Meta Class 表述了类对象本身所具备的元数据。

  Method

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
      • 方法类型 method_types 是个 char 指针,存储方法的参数类型和返回值类型
      • method_imp 指向了方法的实现,本质是一个函数指针

 

Ivar

 Ivar 是表示成员变量的类型。

 typedef struct objc_ivar *Ivar;

 struct objc_ivar {

    char *ivar_name                                          OBJC2_UNAVAILABLE;

    char *ivar_type                                          OBJC2_UNAVAILABLE;

    int ivar_offset                                          OBJC2_UNAVAILABLE;

#ifdef __LP64__

    int space                                                OBJC2_UNAVAILABLE;

#endif

}

其中 ivar_offset 是基地址偏移字节

 

IMP

IMP implement 的简写,俗称方法实现,看源码得知它就是一个函数指针,在objc.h中的定义是:

typedef id (*IMP)(id, SEL, ...);

它就是一个函数指针,这是由编译器生成的。当你发起一个 ObjC 消息之后,最终它会执行的那段代码,就是由这个函数指针指定的。而 IMP 这个函数指针就指向了这个方法的实现。

如果得到了执行某个实例某个方法的入口,我们就可以绕开消息传递阶段,直接执行方法,这在后面 Cache 中会提到。

你会发现 IMP 指向的方法与 objc_msgSend 函数类型相同,参数都包含 id 和 SEL 类型。每个方法名都对应一个 SEL 类型的方法选择器,而每个实例对象中的 SEL 对应的方法实现肯定是唯一的,通过一组 id和 SEL 参数就能确定唯一的方法实现地址。

而一个确定的方法也只有唯一的一组 id 和 SEL 参数。

 

Cache

 Cache 定义如下:

typedef struct objc_cache *Cache

 struct objc_cache {

    unsigned int mask /* total = mask + 1 */                 OBJC2_UNAVAILABLE;

    unsigned int occupied                                    OBJC2_UNAVAILABLE;

    Method buckets[1]                                        OBJC2_UNAVAILABLE;

};

Cache 为方法调用的性能进行优化,每当实例对象接收到一个消息时,它不会直接在 isa 指针指向的类的方法列表中遍历查找能够响应的方法,因为每次都要查找效率太低了,而是优先在 Cache 中查找。

Runtime 系统会把被调用的方法存到 Cache 中,如果一个方法被调用,那么它有可能今后还会被调用,下次查找的时候就会效率更高。就像计算机组成原理中 CPU 绕过主存先访问 Cache 一样。

 

id

id 是一个参数类型,它是指向某个类的实例的指针。定义如下:

typedef struct objc_object *id;

struct objc_object { Class isa; };

以上定义,看到 objc_object 结构体包含一个 isa 指针,根据 isa 指针就可以找到对象所属的类。注意:isa 指针在代码运行时并不总指向实例对象所属的类型,所以不能依靠它来确定类型,要想确定类型还是需要用对象的 -class 方法。(KVO 的实现机理就是将被观察对象的 isa 指针指向一个中间类而不是真实类型,详见:KVO章节。)

 

  Property

typedef struct objc_property *Property;

typedef struct objc_property *objc_property_t;//这个更常用

可以通过class_copyPropertyList 和 protocol_copyPropertyList 方法获取类和协议中的属性:

objc_property_t *class_copyPropertyList(Class cls, unsigned int *outCount)

objc_property_t *protocol_copyPropertyList(Protocol *proto, unsigned int *outCount)

注意:

返回的是属性列表,列表中每个元素都是一个 objc_property_t 指针

 

  • 下面我通过demo 我一个个来讲解

在使用runtime时必须导入<objc/runtime.h> ,这里就以Student类为例,现在创建的xiaoming对象,有+(void)study+(void)run两个类方法和-(void)study1-(void)run1实例方法,有name和age两个属性。

一、获取属性列表

使用objc_property_t *class_copyPropertyList(Class cls, unsigned int *outCount)

    OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0);

其中cls 参数表示需要获取属性的类。outCount表示返回包含返回的属性数组的长度

首先从文档里看他的结构

/** 
 * Describes the properties declared by a class.
 * 
 * @param cls The class you want to inspect.
 * @param outCount On return, contains the length of the returned array. 
 *  If \e outCount is \c NULL, the length is not returned.        
 * 
 * @return An array of pointers of type \c objc_property_t describing the properties 
 *  declared by the class. Any properties declared by superclasses are not included. 
 *  The array contains \c *outCount pointers followed by a \c NULL terminator. You must free the array with \c free().
 * 
 *  If \e cls declares no properties, or \e cls is \c Nil, returns \c NULL and \c *outCount is \c 0.
 */
OBJC_EXPORT objc_property_t *class_copyPropertyList(Class cls, unsigned int *outCount)
    OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0);

示例

unsigned int count;
    objc_property_t *propertyList = class_copyPropertyList([Student class], &count);
    for (unsigned int i=0; i<count; i++) {
        const char *propertyName = property_getName(propertyList[i]);
        NSLog(@"property---->%@", [NSString stringWithUTF8String:propertyName]);
    }

打印结果为:

2017-09-12 14:35:32.131 RunTimeProduct[4680:110834] property---->name

2017-09-12 14:35:32.131 RunTimeProduct[4680:110834] property---->age

二、获得一个类的所有成员变量

使用方法:Ivar *class_copyIvarList(Class cls, unsigned int *outCount) 

        OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0);

其中cls 参数表示需要获取成员变量的类。outCount表示返回包含返回的属性数组的长度,用来存放属性的个数

还要用到方法:const char *ivar_getName(Ivar v)(获得成员变量的名字)和

          const char *ivar_getTypeEndcoding(Ivar v)(获得成员变量的类型)
 
 
/** 
 * Describes the instance variables declared by a class.
 * 
 * @param cls The class to inspect.
 * @param outCount On return, contains the length of the returned array. 
 *  If outCount is NULL, the length is not returned.
 * 
 * @return An array of pointers of type Ivar describing the instance variables declared by the class. 
 *  Any instance variables declared by superclasses are not included. The array contains *outCount 
 *  pointers followed by a NULL terminator. You must free the array with free().
 * 
 *  If the class declares no instance variables, or cls is Nil, NULL is returned and *outCount is 0.
 */
OBJC_EXPORT Ivar *class_copyIvarList(Class cls, unsigned int *outCount) 
    OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0);

示例

unsigned int count;
    Ivar *ivarList = class_copyIvarList([Student class], &count);
    for (unsigned int i = 0; i < count; i++) {
        // 取出i位置对应的成员变量
        Ivar myIvar = ivarList[i];
        const char *ivarName = ivar_getName(myIvar);
        const char *type = ivar_getTypeEncoding(myIvar);
        NSLog(@"成员变量名:%s 成员变量类型:%s",ivarName,type);
    }

打印结果为:

2017-09-12 16:45:20.492 RunTimeProduct[6085:173368] 成员变量名:_name 成员变量类型:@"NSString"

2017-09-12 16:45:20.493 RunTimeProduct[6085:173368] 成员变量名:_age 成员变量类型:Q

 

      

三、获取协议列表

使用方法:Protocol * __unsafe_unretained *class_copyProtocolList(Class cls, unsigned int *outCount)

      OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0);

    其中cls 参数表示需要获取协议列表的类。outCount表示返回包含返回的属性数组的长度
/** 
 * Describes the protocols adopted by a class.
 * 
 * @param cls The class you want to inspect.
 * @param outCount On return, contains the length of the returned array. 
 *  If outCount is NULL, the length is not returned.
 * 
 * @return An array of pointers of type Protocol* describing the protocols adopted 
 *  by the class. Any protocols adopted by superclasses or other protocols are not included. 
 *  The array contains *outCount pointers followed by a NULL terminator. You must free the array with free().
 * 
 *  If cls adopts no protocols, or cls is Nil, returns NULL and *outCount is 0.
 */
OBJC_EXPORT Protocol * __unsafe_unretained *class_copyProtocolList(Class cls, unsigned int *outCount)
    OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0);

示例

unsigned int count;
    __unsafe_unretained Protocol **protocolList = class_copyProtocolList([Student class], &count);
    for (unsigned int i = 0; i < count; i++) {
        Protocol *myProtocal = protocolList[i];
        const char *protocolName = protocol_getName(myProtocal);
        NSLog(@"protocol---->%@", [NSString stringWithUTF8String:protocolName]);
    }

四、获取方法列表

使用的方法:

Method *class_copyMethodList(Class cls, unsigned int *outCount)

其中cls 参数表示需要获取方法列表的类。outCount表示返回包含返回的属性数组的长度

/** 
 * Describes the instance methods implemented by a class.
 * 
 * @param cls The class you want to inspect.
 * @param outCount On return, contains the length of the returned array. 
 *  If outCount is NULL, the length is not returned.
 * 
 * @return An array of pointers of type Method describing the instance methods 
 *  implemented by the class—any instance methods implemented by superclasses are not included. 
 *  The array contains *outCount pointers followed by a NULL terminator. You must free the array with free().
 * 
 *  If cls implements no instance methods, or cls is Nil, returns NULL and *outCount is 0.
 * 
 * @note To get the class methods of a class, use \c class_copyMethodList(object_getClass(cls), &count).
 * @note To get the implementations of methods that may be implemented by superclasses, 
 *  use \c class_getInstanceMethod or \c class_getClassMethod.
 */
OBJC_EXPORT Method *class_copyMethodList(Class cls, unsigned int *outCount) 
    OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0);

示例

unsigned int count;
    Method *methodList = class_copyMethodList([Student class], &count);
    for (unsigned int i = 0; i < count; i++) {
        Method method = methodList[i];
        NSLog(@"method---->%@", NSStringFromSelector(method_getName(method)));
    }

打印结果为:

2017-09-12 14:52:07.997 RunTimeProduct[4929:120846] method---->run1

2017-09-12 14:52:07.997 RunTimeProduct[4929:120846] method---->study1

2017-09-12 14:52:07.997 RunTimeProduct[4929:120846] method---->age

2017-09-12 14:52:07.997 RunTimeProduct[4929:120846] method---->setAge:

2017-09-12 14:52:07.997 RunTimeProduct[4929:120846] method---->.cxx_destruct

2017-09-12 14:52:07.997 RunTimeProduct[4929:120846] method---->name

2017-09-12 14:52:07.998 RunTimeProduct[4929:120846] method---->setName:

五、获得某个类的类方法

使用方法:

    Method class_getClassMethod(Class cls, SEL name)
      OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0);
    其中cls 参数表示需要获取类方法的类。SEL name 要获取的方法的名字
/** 
 * Returns a pointer to the data structure describing a given class method for a given class.
 * 
 * @param cls A pointer to a class definition. Pass the class that contains the method you want to retrieve.
 * @param name A pointer of type \c SEL. Pass the selector of the method you want to retrieve.
 * 
 * @return A pointer to the \c Method data structure that corresponds to the implementation of the 
 *  selector specified by aSelector for the class specified by aClass, or NULL if the specified 
 *  class or its superclasses do not contain an instance method with the specified selector.
 *
 * @note Note that this function searches superclasses for implementations, 
 *  whereas \c class_copyMethodList does not.
 */
OBJC_EXPORT Method class_getClassMethod(Class cls, SEL name)
    OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0);

示例

    Class stuClass = object_getClass([Student class]);
    SEL oriSEL = @selector(run);
    Method oriMethod = class_getClassMethod(stuClass, oriSEL);
   NSLog(@"method---->%@", NSStringFromSelector(method_getName(oriMethod)));

打印结果为:

  2017-09-12 14:56:47.605 RunTimeProduct[4989:123342] method---->run

六、获得实例方法

使用方法:

    Method class_getInstanceMethod(Class cls, SEL name)
      OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0);
    其中cls 参数表示需要获取的实例的类。SEL name 要获取的方法的名字
/** 
 * Returns a specified instance method for a given class.
 * 
 * @param cls The class you want to inspect.
 * @param name The selector of the method you want to retrieve.
 * 
 * @return The method that corresponds to the implementation of the selector specified by 
 *  \e name for the class specified by \e cls, or \c NULL if the specified class or its 
 *  superclasses do not contain an instance method with the specified selector.
 *
 * @note This function searches superclasses for implementations, whereas \c class_copyMethodList does not.
 */
OBJC_EXPORT Method class_getInstanceMethod(Class cls, SEL name)
    OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0);

示例

Method cusMethod = class_getInstanceMethod([Student class], @selector(run1));
    NSLog(@"method---->%@", NSStringFromSelector(method_getName(cusMethod)));

打印结果为:

  2017-09-12 15:02:46.873 RunTimeProduct[5132:127840] method---->run1 由此可知Student里有这个实例方法

七、给实例动态添加方法

使用方法:BOOL class_addMethod(Class cls, SEL name, IMP imp, const char *types) 

参数详解:

Class cls 需要添加方法的类 SEL name 在添加方法的类里,这个添加方法的名字(可以根据爱好写)即就是添加的方法在本类里面叫做的名字,但是方法的格式一定要和你需要添加的方法的格式一样,比如有无参数。(有几个小伙伴问我Demo里面的findInSelf这个方法没有找到,请看这里呀。这个就是里面为啥没有findInSelf方法而可以直接调用的原因) IMP imp 就是Implementation的缩写,它是指向一个方法实现的指针,每一个方法都有一个对应的IMP。这里需要的是IMP,所以你不能直接写方法,需要用到一个方法:
const char *types:这一个也很有意思,我刚开始也很费解,结果看了好多人的解释我释然了,知道吗,我释然啦。这个东西其实也很好理解: 比如:”v@:”意思就是这已是一个void类型的方法,没有参数传入。 再比如 “i@:”就是说这是一个int类型的方法,没有参数传入。 再再比如”i@:@”就是说这是一个int类型的方法,又一个参数传入。

/** 
 * Adds a new method to a class with a given name and implementation.
 * 
 * @param cls The class to which to add a method.
 * @param name A selector that specifies the name of the method being added.
 * @param imp A function which is the implementation of the new method. The function must take at least two arguments—self and _cmd.
 * @param types An array of characters that describe the types of the arguments to the method. 
 * 
 * @return YES if the method was added successfully, otherwise NO 
 *  (for example, the class already contains a method implementation with that name).
 *
 * @note class_addMethod will add an override of a superclass's implementation, 
 *  but will not replace an existing implementation in this class. 
 *  To change an existing implementation, use method_setImplementation.
 */
OBJC_EXPORT BOOL class_addMethod(Class cls, SEL name, IMP imp, 
                                 const char *types) 
    OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0);

示例代码

/**
     OBJC_EXPORT BOOL class_addMethod(Class cls, SEL name, IMP imp,
     
     const char *types)
     Class cls 需要添加方法的类
     SEL name 在添加方法的类里,这个添加方法的名字(可以根据爱好写)即就是添加的方法在本类里面叫做的名字,但是方法的格式一定要和你需要添加的方法的格式一样,比如有无参数。(有几个小伙伴问我Demo里面的findInSelf这个方法没有找到,请看这里呀。这个就是里面为啥没有findInSelf方法而可以直接调用的原因)
     IMP imp 就是Implementation的缩写,它是指向一个方法实现的指针,每一个方法都有一个对应的IMP。这里需要的是IMP,所以你不能直接写方法,需要用到一个方法:
     
     const char *types:这一个也很有意思,我刚开始也很费解,结果看了好多人的解释我释然了,知道吗,我释然啦。这个东西其实也很好理解:
     
     比如:”v@:”意思就是这已是一个void类型的方法,没有参数传入。
     
     再比如 “i@:”就是说这是一个int类型的方法,没有参数传入。
     
     再再比如”i@:@”就是说这是一个int类型的方法,又一个参数传入。
     
     
     OBJC_EXPORT IMP class_getMethodImplementation(Class cls, SEL name)
     
     __OSX_AVAILABLE_STARTING(__MAC_10_5, __IPHONE_2_0);
     这个方法也是runtime的方法,就是获得对应的方法的指针,也就是IMP。
     
     
     用这个方法添加的方法是无法直接调用的,必须用performSelector:调用。为甚么呢???
     知道为甚么了吧,你添加方法是在运行时添加的,你在编译的时候还没有这个本类方法,所以当然不行啦。
     */
    
    Student *xiaoming = [[Student alloc]init];
    class_addMethod([Student class], @selector(addMothodName), class_getMethodImplementation([self class], @selector(addMothod:)), "v@:@:");
    [xiaoming performSelector:@selector(addMothodName) withObject:@"lyj"];
- (void)addMothod:(NSString *)name{
    NSLog(@"动态添加的方法 %@",name);
}

打印结果为:

  2017-09-12 15:13:17.380 RunTimeProduct[5357:134692] 动态添加的方法 lyj

 

八、交换两个方法的实现,拦截系统自带的方法调用功能

  使用的方法:      

    void method_exchangeImplementations(Method m1 , Method m2)

  其中Method m1 ,Method m2就是我们要交换的两个方法

  案例1:方法简单的交换 

  创建一个Student类,其中实现两个方法,并在.h中声明

+ (void)run {
    NSLog(@"跑步");
}

+ (void)study {
    NSLog(@"学习");
}

在控制器中调用以下代码 

  [Student run];
  [Student study];

 打印结果为:

2017-09-11 17:15:58.513 RunTimeProduct[18066:171816] 跑步

2017-09-11 17:15:58.514 RunTimeProduct[18066:171816] 学习

  下面通过runtime 实现方法交换,类方法的获取class_getClassMethod,对象方法用class_getInstanceMethod

     // 获取两个类的类方法
    Method m1 = class_getClassMethod([Student class], @selector(run));
    Method m2 = class_getClassMethod([Student class], @selector(study));
    // 开始交换方法实现
    method_exchangeImplementations(m1, m2);
    [Student run];
    [Student study];

打印结果为:

2017-09-11 17:30:22.138 RunTimeProduct[18247:179351] 学习

2017-09-11 17:30:22.138 RunTimeProduct[18247:179351] 跑步

这里就实现了一个简单的方法替换

 

  案例2:拦截系统方法

  比如:比如iOS8 升级 iOS9 后需要版本适配,根据不同系统使用不同样式图片(拟物化和扁平化),如何通过不去手动一个个修改每个UIImage的imageNamed:方法就可以实现为该方法中加入版本判断语句?

  实现步骤:

    1、为UIImage创建一个分类(UIImage+Category)

2、在分类中实现一个自定义的方法,方法中写要在系统方法中加入的语句,实现版本判断

 

+ (UIImage *)lyj_imageName:(NSString *)imageName {
    double version = [[UIDevice currentDevice].systemVersion doubleValue];
    if (version >= 9.0) {
        //如果系统版本是9.0以上,使用另外一套文件名结尾是‘_os7’的扁平化图片
        imageName = [imageName stringByAppendingString:@"_ios9"];
        NSLog(@"changName = %@",imageName);
    }
    return [UIImage lyj_imageName:imageName];
}

 

3.在分类中重新UIImage的load方法,实现方法交换(只要能让其执行一次方法交换语句,load就再适合不过了)

 

  //dispatch_once这里不是“单例”,是保证方法替换只执行一次.
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        // 获取两个类的类方法
        Method m1 = class_getClassMethod([self class], @selector(imageNamed:));
        Method m2 = class_getClassMethod([self class], @selector(lyj_imageName:));
        // 开始交换方法实现
        method_exchangeImplementations(m1, m2);
    });

注意:在自定义的方法中,最后一定要在调用一下系统的方法,让其有系统方法的功能,但由于方法交换,系统的方法名已经变成了我们自定义的方法名,这就实现了系统方法的拦截。

  

九、在分类中设置属性,给任何一个对象设置属性

  众所周知,分类中是无法设置属性的,如果在分类的声明中写属性只能为其生成get和set方法的声明,但无法生成成员变量。虽然点语法能调用出来,但程序执行就会Crash。那有人会想为什么不使用全局变量?但全局变量在整个内存中只有一份,我们创建多个对象修改其属性值都会修改同一个变量,这样就无法保证像属性一样每个对象都拥有其自己的属性值。这时我们就可以借助runtime来为分类增加属性了。

这里需要用到两个方法:

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

这里set方法,将值value 跟对象object 关联起来(将值value 存储到对象object 中)

参数 object:给哪个对象设置属性
参数 key:一个属性对应一个Key,将来可以通过key取出这个存储的值,key 可以是任何类型:double、int 等,建议用char 可以节省字节
参数 value:给属性设置的值
参数policy:存储策略 (assign 、copy 、 retain就是strong)

2、
id objc_getAssociatedObject(id object , const void *key)

     利用参数key 将对象object中存储的对应值取出来

步骤:

  1、创建一个分类,比如给UIbutton添加一个点击事件的block,设置Title字体颜色的属性lyj_textColor和倒圆角的属性lyj_Radius。

  2、先在.h 中@property 声明出get 和 set 方法,方便点语法调用

//先说明一个block
typedef void (^clickBlock)(void);

/**设置点击事件*/
@property (nonatomic,copy) clickBlock click;

@property (nonatomic, retain)UIColor *lyj_textColor;

@property (nonatomic, assign) double lyj_Radius;

3、在.m 中重写set 和 get 方法,内部利用runtime 给属性赋值和取值

static const void *associatedKey = "associatedKey";
//Category中的属性,只会生成setter和getter方法,不会生成成员变量
- (void)setClick:(clickBlock)click {
    objc_setAssociatedObject(self, associatedKey, click, OBJC_ASSOCIATION_COPY_NONATOMIC);
    [self removeTarget:self action:@selector(buttonClick) forControlEvents:UIControlEventTouchUpInside];
    if (click) {
        [self addTarget:self action:@selector(buttonClick) forControlEvents:UIControlEventTouchUpInside];
    }
}

- (clickBlock)click {
    return objc_getAssociatedObject(self, associatedKey);
}

-(void)buttonClick{
    if (self.click) {
        self.click();
    }
}

- (UIColor *)lyj_textColor {
    return objc_getAssociatedObject(self, @selector(lyj_textColor));
}

- (void)setLyj_textColor:(UIColor *)lyj_textColor {
    objc_setAssociatedObject(self, @selector(lyj_textColor), lyj_textColor, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    [self setTitleColor:lyj_textColor forState:UIControlStateNormal];
}

- (double)lyj_Radius {
    return [objc_getAssociatedObject(self, "lyj_Radius") doubleValue];
}

- (void)setLyj_Radius:(double)lyj_Radius {
    objc_setAssociatedObject(self, "lyj_Radius", @(lyj_Radius), OBJC_ASSOCIATION_ASSIGN);
    self.layer.cornerRadius = lyj_Radius;
    self.layer.masksToBounds = YES;
}

 4、在要使用的地方直接点语法就可以设置相关的属性了

UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom];
    button.frame = self.view.bounds;
    button.lyj_textColor = [UIColor redColor];
    button.backgroundColor = [UIColor yellowColor];
    button.lyj_Radius = self.view.bounds.size.width/2;
    [button setTitle:@"测试" forState:UIControlStateNormal];
    [self.view addSubview:button];
    button.click = ^{
        NSLog(@"buttonClicked");
    };

 

转载于:https://www.cnblogs.com/liYongJun0526/p/7509439.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值