【iOS Sharing #02】2019-03-30

515 阅读4分钟

iOS Sharing 系列

目录

1、类方法去哪里找?

2、isa指针有几种类型么?

3、分类的方法具体是在什么时候添加到类的方法列表中?

4、class_addMethod()都需要什么参数?

5、iOS消息转发流程


1、类方法去哪里找?

答: 见上一期《iOS Sharing #01 | 2019-03-23》第5问

5、实例方法去哪里找?


2、isa指针有几种类型么?

答: isa指针分,指针类型和非指针类型,32位只做地址保存,非嵌入式64位架构下,包含除类地址外的其他信息。

isa指针类型


3、分类的方法具体是在什么时候添加到类的方法列表中?

答: 类在编译后会以 class_ro_t 的结构把类的信息存储在 bits 里,运行时的 realizeClass 之后,会把 ro 中的所有信息拷贝到 bits 的 data 内,即以 class_rw_t 的形式存在,分类里的方法即在这个时候添加到类的方法表里,并在方法表数组的最前面

4、class_addMethod()都需要什么参数?

答:
/**
* 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)
__OSX_AVAILABLE_STARTING(__MAC_10_5, __IPHONE_2_0);

  • 给类添加一个新的方法和该方法的具体实现
  • BOOL: 返回值,YES -------方法添加成功 NO --------方法添加失败
  • Class cls: 将要给添加方法的类,传的类型 [类名 class]
  • SEL name: 将要添加的方法名,传的类型 @selector(方法名)
  • IMP imp:实现这个方法的函数 ,传的类型
    • 1、C语言写法:(IMP)方法名
    • 2、OC的写法:class_getMethodImplementation(self,@selector(方法名:))
  • const char *types:表示我们要添加的方法的返回值和参数
  • "v@:@":
    • 'v'是添加方法无返回值
    • '@'表示是id(也就是要添加的类)
    • ':'表示添加的方法类型
    • '@'表示参数类型

const char *types含义表:

CodeMeaning
cA char
iAn int
sA short
lA long l is treated as a 32-bit quantity on 64-bit programs.
qA long long
CAn unsigned char
IAn unsigned int
SAn unsigned short
LAn unsigned long
QAn unsigned long long
fA float
dA double
BA C++ bool or a C99 _Bool
vA void
*A character string (char *)
@An object (whether statically typed or typed id)
#A class object (Class)
:A method selector (SEL)
[array type]An array
{name=type...}A structure
(name=type...)A union
bnumA bit field of num bits
^typeA pointer to type
?An unknown type (among other things, this code is used for function pointers)

注意:
用这个方法添加的方法是无法直接调用的,必须用performSelector:调用。 因为performSelector是运行时系统负责去找方法的,在编译时候不做任何校验;如果直接调用编译是会自动校验。 添加方法是在运行时添加的,你在编译的时候还没有这个本类方法,所以当然不行。


5、iOS消息转发流程

(1)、答案示例

消息转发机制基本分为三个步骤:
  • 1、动态方法解析。调用动态解析方法+ (BOOL)resolveInstanceMethod:(SEL)sel,如果动态添加方法class_addMethod(self, sel, (IMP)dynamicAddMethodIMP, "@@:");并返回YES,则结束流程;
  • 2、备用接受者。如果上一步没有实现动态添加方法,会调用消息接受者重定向- (id)forwardingTargetForSelector:(SEL)selector方法,如果返回重定向接受者,则当前流程结束;
  • 3、完整转发。如果上一步返回nil,则会调用- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector获取函数的参数和返回值类型,同时会调用- (void)forwardInvocation:(NSInvocation *)anInvocation消息通知当前对象;
  • 4、App crash。如果上一步方法签名返回nil,消息无法处理,提示doesNotRecognizeSelector,App crash。

(2)、代码示例

(a)、部分代码

  • 1、动态方法解析
//实例方法
+ (BOOL)resolveInstanceMethod:(SEL)sel {
    
    printf("[1️⃣] %s 未实现. \n", NSStringFromSelector(sel).UTF8String);
    if (sel == @selector(nonExistentMethod)) {  //如果是要响应这个方法,那么动态添加一个方法进去
        class_addMethod(self, sel, (IMP)dynamicAddMethodIMP, "@@:");
        return YES;
    }
    return [super resolveInstanceMethod:sel];
}

//类方法
//+ (BOOL)resolveClassMethod:(SEL)sel {
//    printf("[1️⃣] %s 未实现. \n", NSStringFromSelector(sel).UTF8String);
//    if (sel == @selector(nonExistentMethod)) {
//        class_addMethod(self, sel, (IMP)dynamicAddMethodIMP, "@@:");
//        return YES;
//    }
//    return [super resolveInstanceMethod:sel];
//}

id dynamicAddMethodIMP(id self, SEL _cmd) {
    printf("[✅] 调用了动态添加的方法: %s. \n", __FUNCTION__);
    return @"YES!";
}
  • 2、备用接受者
- (id)forwardingTargetForSelector:(SEL)aSelector {
    printf("[2️⃣] 转发给其它对象去响应 %s方法.\n", NSStringFromSelector(aSelector).UTF8String);
    _fastFowarding = [FastFowarding new];
    if ([_fastFowarding respondsToSelector:@selector(nonExistentMethod)]) { //如果FastFowarding对象能响应这个方法,那就让该对象去处理
        return _fastFowarding;
    }
    return [super forwardingTargetForSelector:aSelector];
}
  • 3、完整转发

- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
    printf("[3️⃣] 对方法进行签名: %s.\n", NSStringFromSelector(aSelector).UTF8String);
    if (aSelector == @selector(nonExistentMethod)) {
        return [NSMethodSignature signatureWithObjCTypes:"v@:"];
    }
    return [super methodSignatureForSelector:aSelector];
}


- (void)forwardInvocation:(NSInvocation *)anInvocation {
    printf("[4️⃣] 转发此次调用: %s.\n", NSStringFromSelector(anInvocation.selector).UTF8String);
    if (anInvocation.selector == @selector(nonExistentMethod)) {
        _normalForwarding = [NormalForwarding new];
        [anInvocation invokeWithTarget:_normalForwarding];
    }
}

(b)、完整代码

完整代码


(3)、参考图片

流程

类方法: 类方法

实例方法: 实例方法

详细流程: 详细流程

感谢大佬提供的图片。


仓库

本篇相关代码


联系方式

邮箱: adrenine@163.com

邮箱: holaux@gmail.com

邮箱: ledahapple@icloud.com