iOS Runtime Method IMP指针详解

Method IMP 概念介绍 


OC是消息转发机制,代码在编译的时候会生产Runtime中间代码,运行的时候执行Runtime代码,我们也可以动态的添加Runtime代码。
这篇之前讲过了如何创建类和Runtime中的属性,今天主要说一下关于Runtime的方法。

首先还要说一下Runtime类的结构体:

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_method_list * _Nullable * _Nullable methodLists                    OBJC2_UNAVAILABLE;




继续分析下 objc_method_list:

struct objc_method_list {

    struct objc_method_list * _Nullable 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;

}          


我们可以从上面的结构体上面看出  objc_method_list是 objc_method 结构体。


那么继续找 objc_method结构体 看看是否有IMP

struct objc_method {

    SEL _Nonnull method_name                                 OBJC2_UNAVAILABLE;

    char * _Nullable method_types                            OBJC2_UNAVAILABLE;

    IMP _Nonnull method_imp                                  OBJC2_UNAVAILABLE;

}      



分析:

看了根据上面的关系网,我们知道在OC的类中,所有的方法在Runtime的时候都会变成 objc_method,IMP是一个函数指针,指向实现这个函数的方法。然后这些方法都被放到objc_method_list这个结构体里。


Runtime 关于Method IMP 的API介绍


/* Working with Methods */


/** 

 * Returns the name of a method.

 * 

 * @param m The method to inspect.

 * 

 * @return A pointer of type SEL.

 * 

 * @note To get the method name as a C string, call \c sel_getName(method_getName(method)).

 */

OBJC_EXPORT SEL_Nonnull

method_getName(Method _Nonnull m) 

    OBJC_AVAILABLE(10.5,2.0,9.0,1.0,2.0);


/** 

 * Returns the implementation of a method.

 * 

 * @param m The method to inspect.

 * 

 * @return A function pointer of type IMP.

 */

OBJC_EXPORT IMP_Nonnull

method_getImplementation(Method _Nonnull m) 

    OBJC_AVAILABLE(10.5,2.0,9.0,1.0,2.0);


/** 

 * Returns a string describing a method's parameter and return types.

 * 

 * @param m The method to inspect.

 * 

 * @return A C string. The string may be \c NULL.

 */

OBJC_EXPORT const char * _Nullable

method_getTypeEncoding(Method _Nonnull m) 

    OBJC_AVAILABLE(10.5,2.0,9.0,1.0,2.0);


/** 

 * Returns the number of arguments accepted by a method.

 * 

 * @param m A pointer to a \c Method data structure. Pass the method in question.

 * 

 * @return An integer containing the number of arguments accepted by the given method.

 */

OBJC_EXPORT unsignedint

method_getNumberOfArguments(Method _Nonnull m)

    OBJC_AVAILABLE(10.0,2.0,9.0,1.0,2.0);


/** 

 * Returns a string describing a method's return type.

 * 

 * @param m The method to inspect.

 * 

 * @return A C string describing the return type. You must free the string with \c free().

 */

OBJC_EXPORT char * _Nonnull

method_copyReturnType(Method _Nonnull m) 

    OBJC_AVAILABLE(10.5,2.0,9.0,1.0,2.0);


/** 

 * Returns a string describing a single parameter type of a method.

 * 

 * @param m The method to inspect.

 * @param index The index of the parameter to inspect.

 * 

 * @return A C string describing the type of the parameter at index \e index, or \c NULL

 *  if method has no parameter index \e index. You must free the string with \c free().

 */

OBJC_EXPORT char * _Nullable

method_copyArgumentType(Method _Nonnull m, unsigned int index) 

    OBJC_AVAILABLE(10.5,2.0,9.0,1.0,2.0);


/** 

 * Returns by reference a string describing a method's return type.

 * 

 * @param m The method you want to inquire about. 

 * @param dst The reference string to store the description.

 * @param dst_len The maximum number of characters that can be stored in \e dst.

 *

 * @note The method's return type string is copied to \e dst.

 *  \e dst is filled as if \c strncpy(dst, parameter_type, dst_len) were called.

 */

OBJC_EXPORT void

method_getReturnType(Method _Nonnull m, char * _Nonnull dst, size_t dst_len) 

    OBJC_AVAILABLE(10.5,2.0,9.0,1.0,2.0);


/** 

 * Returns by reference a string describing a single parameter type of a method.

 * 

 * @param m The method you want to inquire about. 

 * @param index The index of the parameter you want to inquire about.

 * @param dst The reference string to store the description.

 * @param dst_len The maximum number of characters that can be stored in \e dst.

 * 

 * @note The parameter type string is copied to \e dst. \e dst is filled as if \c strncpy(dst, parameter_type, dst_len) 

 *  were called. If the method contains no parameter with that index, \e dst is filled as

 *  if \c strncpy(dst, "", dst_len) were called.

 */

OBJC_EXPORT void

method_getArgumentType(Method _Nonnull m, unsigned int index, 

                       char * _Nullable dst, size_t dst_len) 

    OBJC_AVAILABLE(10.5,2.0,9.0,1.0,2.0);


OBJC_EXPORT struct objc_method_description *_Nonnull

method_getDescription(Method _Nonnull m) 

    OBJC_AVAILABLE(10.5,2.0,9.0,1.0,2.0);


/** 

 * Sets the implementation of a method.

 * 

 * @param m The method for which to set an implementation.

 * @param imp The implemention to set to this method.

 * 

 * @return The previous implementation of the method.

 */

OBJC_EXPORT IMP_Nonnull

method_setImplementation(Method _Nonnull m, IMP _Nonnull imp) 

    OBJC_AVAILABLE(10.5,2.0,9.0,1.0,2.0);


/** 

 * Exchanges the implementations of two methods.

 * 

 * @param m1 Method to exchange with second method.

 * @param m2 Method to exchange with first method.

 * 

 * @note This is an atomic version of the following:

 *  \code 

 *  IMP imp1 = method_getImplementation(m1);

 *  IMP imp2 = method_getImplementation(m2);

 *  method_setImplementation(m1, imp2);

 *  method_setImplementation(m2, imp1);

 *  \endcode

 */

OBJC_EXPORT void

method_exchangeImplementations(Method _Null_unspecified /* _Nonnull */ m1, Method _Null_unspecified /* _Nonnull */ m2) 

    OBJC_AVAILABLE(10.5,2.0,9.0,1.0,2.0);



Runtime 关于Method IMP 的应用示例


Demo1:
     获取方法名字和获取方法的IMP 。 从objc_method结构体可知,method里有SEL 方法名 和IMP 实现方法的函数指针组成。所以我们可以从方法里拿到他的属性,然后玩一玩,下面就是针对这两个方法的小应用。

通过下面示例我们可以明白:Method SEL IMP 外在联系

method_getName(Method _Nonnull m) 

method_getImplementation(Method _Nonnull m) 


- (void)viewDidLoad {
    [super viewDidLoad];
//首先我们早类里面找到该方法
    Method ori_Method = class_getInstanceMethod([self class], @selector(myMethod:));
#if 0
    //获取方法名 SEL
    SEL oriMethodName = method_getName(ori_Method);
    //根据方法名获取函数指针
    IMP myMethodImp  =  [self methodForSelector:oriMethodName];
#else
    //直接根据方法名获取函数指针  等同于IF 0
    IMP myMethodImp  =  method_getImplementation(ori_Method);
#endif
   
 //在该类中添加方法。
#if 0
    class_addMethod([self class], @selector(testMethod:), myMethodImp, method_getTypeEncoding(ori_Method));
#else
    class_addMethod([self class], @selector(testMethod:), myMethodImp, "V@:@");
#endif
    //在执行方法。
    [self performSelector:@selector(testMethod:) withObject:@"7777"];
}

-(void)myMethod:(NSString *)myValue{
    NSLog(@"myMethod:%@",myValue);
}

打印结果:

2017-07-20 10:52:26.581146+0800 zyTest[8351:416437] myMethod:7777



Demo2:


中间关于获得方法参数,参数个数,参数返回类型就不赘述了,示例中有的会用到。主要介绍下面几个方法:


上面是GET IMP,那肯定可以设置方法的IMP

method_setImplementation(Method _Nonnull m, IMP _Nonnull imp) 

方法交换

method_exchangeImplementations(Method _Null_unspecified /* _Nonnull */ m1, Method _Null_unspecified /* _Nonnull */ m2) 


//方法交换
-(void)demo2{
    Method method1 = class_getInstanceMethod([self class], @selector(exchangeMethod1:));
    Method method2 = class_getInstanceMethod([self class], @selector(exchangeMethod2:));
    Method method3 = class_getInstanceMethod([self class], @selector(exchangeMethod3:));
    Method method4 = class_getInstanceMethod([self class], @selector(exchangeMethod4:));
    //方法替换   替换SEL 的IMP实现
    class_replaceMethod([self class], @selector(exchangeMethod1:), method_getImplementation(method3), method_getTypeEncoding(method3));
    //和class_replaceMethod 类似,替换method 的结构题IMP指针
    method_setImplementation(method4, method_getImplementation(method2));
    //方法交换
    method_exchangeImplementations(method1, method2);
    //猜一猜打印的什么
    [self performSelector:method_getName(method1) withObject:@"Runtime Method Demo1" afterDelay:0.0];
    [self exchangeMethod2:@"Runtime Method Demo2"];
    [self exchangeMethod3:@"Runtime Method Demo3"];
    [self exchangeMethod4:@"Runtime Method Demo4"];
}

-(void)exchangeMethod1:(id)str{
    NSLog(@"exchangeMethod1:%@",str);
}

-(void)exchangeMethod2:(id)num{
    NSLog(@"exchangeMethod2:%@",num);
}

-(void)exchangeMethod3:(id)f{
    NSLog(@"exchangeMethod3:%@",f);
}

-(void)exchangeMethod4:(id)w{
    NSLog(@"exchangeMethod4:%@",w);
}


最后的打印结果是你想的吗?

2017-07-20 13:23:14.804342+0800 zyTest[16764:910790] exchangeMethod3:Runtime Method Demo2

2017-07-20 13:23:14.804586+0800 zyTest[16764:910790] exchangeMethod3:Runtime Method Demo3

2017-07-20 13:23:14.804738+0800 zyTest[16764:910790] exchangeMethod2:Runtime Method Demo4

2017-07-20 13:23:14.817341+0800 zyTest[16764:910790] exchangeMethod2:Runtime Method Demo1



Runtime  IMP 块操作


参考地址:http://www.cocoachina.com/ios/20141111/10186.html

块操作

我们都知道block给我们带到极大的方便,苹果也不断提供一些使用block的新的API。同时,苹果在runtime中也提供了一些函数来支持针对block的操作,这些函数包括:

1
2
3
4
5
6
7
8
// 创建一个指针函数的指针,该函数调用时会调用特定的block
IMP imp_implementationWithBlock ( id block );
 
// 返回与IMP(使用imp_implementationWithBlock创建的)相关的block
id imp_getBlock ( IMP anImp );
 
// 解除block与IMP(使用imp_implementationWithBlock创建的)的关联关系,并释放block的拷贝
BOOL imp_removeBlock ( IMP anImp );

● imp_implementationWithBlock函数:参数block的签名必须是method_return_type ^(id self, method_args …)形式的。该方法能让我们使用block作为IMP。如下代码所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@interface MyRuntimeBlock : NSObject
 
@end
 
@implementation MyRuntimeBlock
 
@end
 
// 测试代码
IMP imp = imp_implementationWithBlock(^(id obj, NSString *str) {
     NSLog(@ "%@" , str);
});
 
class_addMethod(MyRuntimeBlock.class, @selector(testBlock:), imp,  "v@:@" );
 
MyRuntimeBlock *runtime = [[MyRuntimeBlock alloc] init];
[runtime performSelector:@selector(testBlock:) withObject:@ "hello world!" ];

输出结果是

1
2014-11-09 14:03:19.779 [1172:395446] hello world!

Demo地址: https://github.com/RainManGO/RuntimeMethodIMPDemo

你可能感兴趣的:(iOS,Runtime,iOS,Runloop,Runtime实战)