zoukankan      html  css  js  c++  java
  • 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");
        };
  • 相关阅读:
    分糖果
    数字游戏
    错误票据
    包子凑数
    带分数
    翻硬币
    核桃的数量
    快速幂
    公倍数与素数筛选
    mysql 查询当天当周当月的数据
  • 原文地址:https://www.cnblogs.com/liYongJun0526/p/7509439.html
Copyright © 2011-2022 走看看