经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » 移动开发 » iOS » 查看文章
iOS底层原理(五)Runtime(下)
来源:cnblogs  作者:FunkyRay  时间:2021/4/12 9:49:58  对本文有异议

动态方法解析

如果消息发送阶段不成功,那么就会进入到动态方法解析阶段

【第一步】 我们还是先从objc源码里找到函数resolveMethod_locked来看,分别对应着类对象和元类对象做了不同的调用处理

  1. static NEVER_INLINE IMP
  2. resolveMethod_locked(id inst, SEL sel, Class cls, int behavior) {
  3. runtimeLock.assertLocked();
  4. ASSERT(cls->isRealized());
  5. runtimeLock.unlock();
  6. // 不是元类对象
  7. if (! cls->isMetaClass()) {
  8. // try [cls resolveInstanceMethod:sel]
  9. resolveInstanceMethod(inst, sel, cls);
  10. }
  11. else { // 是元类对象
  12. // try [nonMetaClass resolveClassMethod:sel]
  13. // and [cls resolveInstanceMethod:sel]
  14. resolveClassMethod(inst, sel, cls);
  15. // 这句印证了在元类对象里找类方法找不到,会去类对象里找同名的对象方法
  16. if (!lookUpImpOrNilTryCache(inst, sel, cls)) {
  17. resolveInstanceMethod(inst, sel, cls);
  18. }
  19. }
  20. // chances are that calling the resolver have populated the cache
  21. // so attempt using it
  22. return lookUpImpOrForwardTryCache(inst, sel, cls, behavior);
  23. }

注意: 能调用到这里,说明已经找到基类的元类对象了,如果还是没有,那么就会去基类的类对象里找同名的对象方法,正好印证了之前分析的元类对象的superclass指针指向类对象的原理

【第二步】 如果是类对象则会进入resolveInstanceMethod函数中,会走消息发送的流程去查找是否有实现resolveInstanceMethod方法,如果没有实现则返回;如果有实现就发送消息调用resolveInstanceMethod方法,并且再次走消息发送流程查找是否有实现对应的实例方法

  1. static void resolveInstanceMethod(id inst, SEL sel, Class cls) {
  2. runtimeLock.assertUnlocked();
  3. ASSERT(cls->isRealized());
  4. SEL resolve_sel = @selector(resolveInstanceMethod:);
  5. // 发送消息的容错处理
  6. // 会走消息发送的流程去找是否有实现resolveInstanceMethod方法,如果有实现才会往下执行
  7. if (!lookUpImpOrNilTryCache(cls, resolve_sel, cls->ISA(/*authenticated*/true))) {
  8. // Resolver not implemented.
  9. return;
  10. }
  11. // 因为已经有实现resolveInstanceMethod,所以发送消息调用resolveInstanceMethod
  12. BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend;
  13. bool resolved = msg(cls, resolve_sel, sel);
  14. // Cache the result (good or bad) so the resolver doesn't fire next time.
  15. // +resolveInstanceMethod adds to self a.k.a. cls
  16. // 再次查找是否有实现对应的实例方法
  17. IMP imp = lookUpImpOrNilTryCache(inst, sel, cls);
  18. // 拿到resolveInstanceMethod的调用返回值,只是为了是否打印,无其他意义
  19. if (resolved && PrintResolving) {
  20. if (imp) {
  21. _objc_inform("RESOLVE: method %c[%s %s] "
  22. "dynamically resolved to %p",
  23. cls->isMetaClass() ? '+' : '-',
  24. cls->nameForLogging(), sel_getName(sel), imp);
  25. }
  26. else {
  27. // Method resolver didn't add anything?
  28. _objc_inform("RESOLVE: +[%s resolveInstanceMethod:%s] returned YES"
  29. ", but no new implementation of %c[%s %s] was found",
  30. cls->nameForLogging(), sel_getName(sel),
  31. cls->isMetaClass() ? '+' : '-',
  32. cls->nameForLogging(), sel_getName(sel));
  33. }
  34. }
  35. }

下面是resolveInstanceMethod的一些详细调用解析

1.多次调用lookUpImpOrNilTryCache的方法实现,内部会再次调用_lookUpImpTryCache函数

  1. IMP lookUpImpOrNilTryCache(id inst, SEL sel, Class cls, int behavior)
  2. {
  3. return _lookUpImpTryCache(inst, sel, cls, behavior | LOOKUP_NIL);
  4. }

2.在_lookUpImpTryCache中,会先去缓存中查找,如果没有还是会走消息发送流程的调用函数lookUpImpOrForward,详细分析请查看上篇文章

  1. static IMP _lookUpImpTryCache(id inst, SEL sel, Class cls, int behavior)
  2. {
  3. runtimeLock.assertUnlocked();
  4. if (slowpath(!cls->isInitialized())) {
  5. // see comment in lookUpImpOrForward
  6. return lookUpImpOrForward(inst, sel, cls, behavior);
  7. }
  8. // 查看是否有缓存
  9. IMP imp = cache_getImp(cls, sel);
  10. if (imp != NULL) goto done;
  11. #if CONFIG_USE_PREOPT_CACHES
  12. if (fastpath(cls->cache.isConstantOptimizedCache(/* strict */true))) {
  13. imp = cache_getImp(cls->cache.preoptFallbackClass(), sel);
  14. }
  15. #endif
  16. if (slowpath(imp == NULL)) {
  17. return lookUpImpOrForward(inst, sel, cls, behavior);
  18. }
  19. done:
  20. if ((behavior & LOOKUP_NIL) && imp == (IMP)_objc_msgForward_impcache) {
  21. return nil;
  22. }
  23. return imp;
  24. }

【第二步】 如果是元类对象则会进入resolveClassMethod函数中,同类对象的动态方法解析大体相似;先会走消息发送的流程去查找是否有实现resolveClassMethod方法,如果没有实现则返回;如果有实现就发送消息调用

  1. static void resolveClassMethod(id inst, SEL sel, Class cls)
  2. {
  3. runtimeLock.assertUnlocked();
  4. ASSERT(cls->isRealized());
  5. ASSERT(cls->isMetaClass());
  6. if (!lookUpImpOrNilTryCache(inst, @selector(resolveClassMethod:), cls)) {
  7. // Resolver not implemented.
  8. return;
  9. }
  10. Class nonmeta;
  11. {
  12. mutex_locker_t lock(runtimeLock);
  13. nonmeta = getMaybeUnrealizedNonMetaClass(cls, inst);
  14. // +initialize path should have realized nonmeta already
  15. if (!nonmeta->isRealized()) {
  16. _objc_fatal("nonmeta class %s (%p) unexpectedly not realized",
  17. nonmeta->nameForLogging(), nonmeta);
  18. }
  19. }
  20. BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend;
  21. bool resolved = msg(nonmeta, @selector(resolveClassMethod:), sel);
  22. // Cache the result (good or bad) so the resolver doesn't fire next time.
  23. // +resolveClassMethod adds to self->ISA() a.k.a. cls
  24. IMP imp = lookUpImpOrNilTryCache(inst, sel, cls);
  25. if (resolved && PrintResolving) {
  26. if (imp) {
  27. _objc_inform("RESOLVE: method %c[%s %s] "
  28. "dynamically resolved to %p",
  29. cls->isMetaClass() ? '+' : '-',
  30. cls->nameForLogging(), sel_getName(sel), imp);
  31. }
  32. else {
  33. // Method resolver didn't add anything?
  34. _objc_inform("RESOLVE: +[%s resolveClassMethod:%s] returned YES"
  35. ", but no new implementation of %c[%s %s] was found",
  36. cls->nameForLogging(), sel_getName(sel),
  37. cls->isMetaClass() ? '+' : '-',
  38. cls->nameForLogging(), sel_getName(sel));
  39. }
  40. }
  41. }

为了验证源码实现,我们还是先创建示例代码来看

1.创建一个Person类,添加test对象方法,并实现resolveInstanceMethod方法,然后动态添加一个other函数

  1. @interface Person : NSObject
  2. - (void)test;
  3. @end
  4. @implementation Person
  5. - (void)other
  6. {
  7. NSLog(@"%s", __func__);
  8. }
  9. + (BOOL)resolveInstanceMethod:(SEL)sel
  10. {
  11. if (sel == @selector(test)) {
  12. // 获取其他方法
  13. Method method = class_getInstanceMethod(self, @selector(other));
  14. // 动态添加test方法的实现
  15. class_addMethod(self, sel,
  16. method_getImplementation(method),
  17. method_getTypeEncoding(method));
  18. // 返回YES代表有动态添加方法
  19. return YES;
  20. }
  21. return [super resolveInstanceMethod:sel];
  22. }
  23. @end

知识点: Method的底层实现struct method_t类型,所以可以理解为等价于struct method_t *

2.在main函数中调用[person test],运行程序可以发现,控制台会打印other函数已经被调用

  1. int main(int argc, const char * argv[]) {
  2. @autoreleasepool {
  3. Person *person = [[Person alloc] init];
  4. [person test];
  5. }
  6. return 0;
  7. }
总结

从示例代码我们可以知道,利用Runtime的消息发送机制来动态增加一些方法的实现和调用

整个动态方法分析的流程可以用下图表述

-w796

消息转发

如果没有进行动态分析的代码实现,就会进入到消息转发阶段

消息转发的源码由于是不开源的,所以我们只能通过一些其他的方法来分析其内部的实现

实现步骤

1.首先我们先通过方法崩溃的日志打印来查看消息转发都对应调用了哪些方法

我们注释掉Person.m文件里的resolveInstanceMethod实现,再次运行程序发现,程序崩溃并打印经典错误信息

  1. -[Person test]: unrecognized selector sent to instance 0x1018331d0
  2. *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[Person test]: unrecognized selector sent to instance 0x1018331d0'
  3. *** First throw call stack:
  4. (
  5. 0 CoreFoundation 0x00007fff204a16af __exceptionPreprocess + 242
  6. 1 libobjc.A.dylib 0x00007fff201d93c9 objc_exception_throw + 48
  7. 2 CoreFoundation 0x00007fff20523c85 -[NSObject(NSObject) __retain_OA] + 0
  8. 3 CoreFoundation 0x00007fff2040906d ___forwarding___ + 1467
  9. 4 CoreFoundation 0x00007fff20408a28 _CF_forwarding_prep_0 + 120
  10. 6 libdyld.dylib 0x00007fff2034a631 start + 1
  11. 7 ??? 0x0000000000000001 0x0 + 1
  12. )

从上面的调用栈打印信息我们可以看出,系统会先去调用CoreFoundation框架中的___forwarding___

2.我们可以通过逆向工具HopperCoreFoundation框架进行反汇编,通过一系列操作,可以得到__forwarding_prep_0___的伪代码

源码分析

下面是伪代码的实现

  1. // 伪代码的实现
  2. int __forwarding__(void *frameStackPointer, int isStret) {
  3. id receiver = *(id *)frameStackPointer;
  4. SEL sel = *(SEL *)(frameStackPointer + 8);
  5. const char *selName = sel_getName(sel);
  6. Class receiverClass = object_getClass(receiver);
  7. // 调用 forwardingTargetForSelector:
  8. if (class_respondsToSelector(receiverClass, @selector(forwardingTargetForSelector:))) {
  9. id forwardingTarget = [receiver forwardingTargetForSelector:sel];
  10. if (forwardingTarget && forwardingTarget != receiver) {
  11. if (isStret == 1) {
  12. int ret;
  13. objc_msgSend_stret(&ret,forwardingTarget, sel, ...);
  14. return ret;
  15. }
  16. return objc_msgSend(forwardingTarget, sel, ...);
  17. }
  18. }
  19. // 僵尸对象
  20. const char *className = class_getName(receiverClass);
  21. const char *zombiePrefix = "_NSZombie_";
  22. size_t prefixLen = strlen(zombiePrefix); // 0xa
  23. if (strncmp(className, zombiePrefix, prefixLen) == 0) {
  24. CFLog(kCFLogLevelError,
  25. @"*** -[%s %s]: message sent to deallocated instance %p",
  26. className + prefixLen,
  27. selName,
  28. receiver);
  29. <breakpoint-interrupt>
  30. }
  31. // 调用 methodSignatureForSelector 获取方法签名后再调用 forwardInvocation
  32. if (class_respondsToSelector(receiverClass, @selector(methodSignatureForSelector:))) {
  33. NSMethodSignature *methodSignature = [receiver methodSignatureForSelector:sel];
  34. if (methodSignature) {
  35. BOOL signatureIsStret = [methodSignature _frameDescriptor]->returnArgInfo.flags.isStruct;
  36. if (signatureIsStret != isStret) {
  37. CFLog(kCFLogLevelWarning ,
  38. @"*** NSForwarding: warning: method signature and compiler disagree on struct-return-edness of '%s'. Signature thinks it does%s return a struct, and compiler thinks it does%s.",
  39. selName,
  40. signatureIsStret ? "" : not,
  41. isStret ? "" : not);
  42. }
  43. if (class_respondsToSelector(receiverClass, @selector(forwardInvocation:))) {
  44. NSInvocation *invocation = [NSInvocation _invocationWithMethodSignature:methodSignature frame:frameStackPointer];
  45. [receiver forwardInvocation:invocation];
  46. void *returnValue = NULL;
  47. [invocation getReturnValue:&value];
  48. return returnValue;
  49. } else {
  50. CFLog(kCFLogLevelWarning ,
  51. @"*** NSForwarding: warning: object %p of class '%s' does not implement forwardInvocation: -- dropping message",
  52. receiver,
  53. className);
  54. return 0;
  55. }
  56. }
  57. }
  58. SEL *registeredSel = sel_getUid(selName);
  59. // selector 是否已经在 Runtime 注册过
  60. if (sel != registeredSel) {
  61. CFLog(kCFLogLevelWarning ,
  62. @"*** NSForwarding: warning: selector (%p) for message '%s' does not match selector known to Objective C runtime (%p)-- abort",
  63. sel,
  64. selName,
  65. registeredSel);
  66. } // doesNotRecognizeSelector
  67. else if (class_respondsToSelector(receiverClass,@selector(doesNotRecognizeSelector:))) {
  68. [receiver doesNotRecognizeSelector:sel];
  69. }
  70. else {
  71. CFLog(kCFLogLevelWarning ,
  72. @"*** NSForwarding: warning: object %p of class '%s' does not implement doesNotRecognizeSelector: -- abort",
  73. receiver,
  74. className);
  75. }
  76. // The point of no return.
  77. kill(getpid(), 9);
  78. }

1.首先会查看是否实现forwardingTargetForSelector方法,如果实现了该方法并且返回值不为nil,那么会调用objc_msgSend进行消息发送流程;如果该方法未实现或者返回值为nil,就会调用方法签名methodSignatureForSelector

2.如果methodSignatureForSelector的返回值不为nil,那么就会调用forwardInvocation,如果该方法未实现或者返回值为nil,那么就会调用doesNotRecognizeSelectordoesNotRecognizeSelector里就会进行崩溃报错

3.如果forwardInvocation未实现,也会进行崩溃报错

4.为了验证上述源码分析,在增加一个Cat类,并实现test方法

  1. @interface Cat: NSObject
  2. - (void)test;
  3. @end
  4. @implementation Cat
  5. - (void)test {
  6. NSLog(@"%s", __func__);
  7. }
  8. @end

Person.mm文件里对应实现这三个函数,并分别返回nil或者注释掉函数实现,然后运行程序发现,和上述分析相同

  1. @implementation Person
  2. //+ (BOOL)resolveInstanceMethod:(SEL)sel
  3. //{
  4. // class_addMethod(<#Class _Nullable __unsafe_unretained cls#>, <#SEL _Nonnull name#>, <#IMP _Nonnull imp#>, <#const char * _Nullable types#>)
  5. //}
  6. - (id)forwardingTargetForSelector:(SEL)aSelector
  7. {
  8. NSLog(@"%s", __func__);
  9. if (aSelector == @selector(test)) {
  10. // objc_msgSend([[Cat alloc] init], aSelector)
  11. return nil;
  12. //[[Cat alloc] init];
  13. //[[NSObject alloc] init];
  14. }
  15. return [super forwardingTargetForSelector:aSelector];
  16. }
  17. // 方法签名:返回值类型、参数类型
  18. - (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
  19. {
  20. NSLog(@"%s", __func__);
  21. if (aSelector == @selector(test)) {
  22. return [NSMethodSignature signatureWithObjCTypes:"v16@0:8"];
  23. }
  24. return [super methodSignatureForSelector:aSelector];
  25. }
  26. // NSInvocation封装了一个方法调用,包括:方法调用者、方法名、方法参数
  27. // anInvocation.target 方法调用者
  28. // anInvocation.selector 方法名
  29. // [anInvocation getArgument:NULL atIndex:0]
  30. - (void)forwardInvocation:(NSInvocation *)anInvocation
  31. {
  32. NSLog(@"%s", __func__);
  33. // anInvocation.target = [[Cat alloc] init];
  34. // [anInvocation invoke]; 执行方法
  35. [anInvocation invokeWithTarget:[[Cat alloc] init]];
  36. // [anInvocation invokeWithTarget:[[NSObject alloc] init]];
  37. }
  38. @end

方法签名methodSignatureForSelector返回值的多种写法

  1. // 省略types类型字节大小
  2. [NSMethodSignature signatureWithObjCTypes:"i@:i"]
  3. // 用默认的aSelector传递
  4. [[[Cat alloc] init] methodSignatureForSelector:aSelector];

forwardInvocation会通过NSInvocation类型的参数拿到整个方法的调用者、方法名以及方法参数

  1. // 更改调用者对象
  2. [anInvocation invokeWithTarget:[[Cat alloc] init]];
  3. // 拿到参数信息,传递的是地址值
  4. int age;
  5. [anInvocation getArgument:&age atIndex:2];
  6. // 拿到返回值信息
  7. int ret;
  8. [anInvocation getReturnValue:&ret];

注意:

  • forwardingTargetForSelector的返回值改为NSObject对象,发现也一样会崩溃报错;说明了如果返回值的类型也找不到对应的函数实现,会重新走消息发送的流程然后最后崩溃报错
  • forwardInvocation的实现里可以做任何事,只要实现了该函数,就不会崩溃报错;前提是不会进行未实现方法的invokeWithTarget对象调用
  • 以上方法都有对象方法、类方法2个版本
总结

整个消息转发分析的流程可以用下图表述

面试题

1.下面这段代码的self和super分别对应着是谁,说下原理

  1. @interface Person : NSObject
  2. - (void)run;
  3. @end
  4. @implementation Person
  5. - (void)run {
  6. NSLog(@"%s", __func__);
  7. }
  8. @end
  9. @interface Student: Person
  10. @end
  11. @implementation Student
  12. - (instancetype)init {
  13. if (self = [super init]) {
  14. NSLog(@"[self class] = %@", [self class]); // Student
  15. NSLog(@"[self superclass] = %@", [self superclass]); // Person
  16. NSLog(@"[super class] = %@", [super class]); // Student
  17. NSLog(@"[super superclass] = %@", [super superclass]); // Person
  18. }
  19. return self;
  20. }
  21. - (void)run {
  22. [super run];
  23. }
  24. @end

将这段代码转成C++文件后可以发现,run方法的底层会调用objc_msgSendSuper函数

  1. static void _I_MJStudent_run(MJStudent * self, SEL _cmd) {
  2. // objc_msgSendSuper(__rw_objc_super { self, [Person class]) },
  3. // sel_registerName("run")
  4. // );
  5. ((void (*)(__rw_objc_super *, SEL))(void *)objc_msgSendSuper)((__rw_objc_super){(id)self, (id)class_getSuperclass(objc_getClass("Student"))}, sel_registerName("run"));
  6. NSLog((NSString *)&__NSConstantStringImpl__var_folders_2r__m13fp2x2n9dvlr8d68yry500000gn_T_MJStudent_69ea3c_mi_0);
  7. }

我们还发现其中一个参数是__rw_objc_super的结构体类型,里面的两个成员正好对着selfclass_getSuperclass(objc_getClass("Student")),也就是Student对象父类Person对象

  1. struct __rw_objc_super {
  2. struct objc_object *object;
  3. struct objc_object *superClass;
  4. __rw_objc_super(struct objc_object *o, struct objc_object *s) : object(o), superClass(s) {}
  5. };

我们在objc源码objc-msg-arm64.s文件里可以查找到关于objc_msgSendSuper函数的实现

  1. ENTRY _objc_msgSendSuper
  2. UNWIND _objc_msgSendSuper, NoFrame
  3. ldp p0, p16, [x0] // p0 = real receiver, p16 = class
  4. b L_objc_msgSendSuper2_body
  5. END_ENTRY _objc_msgSendSuper
  6. // no _objc_msgLookupSuper
  7. ENTRY _objc_msgSendSuper2
  8. UNWIND _objc_msgSendSuper2, NoFrame
  9. #if __has_feature(ptrauth_calls)
  10. ldp x0, x17, [x0] // x0 = real receiver, x17 = class
  11. add x17, x17, #SUPERCLASS // x17 = &class->superclass
  12. ldr x16, [x17] // x16 = class->superclass
  13. AuthISASuper x16, x17, ISA_SIGNING_DISCRIMINATOR_CLASS_SUPERCLASS
  14. LMsgSendSuperResume:
  15. #else
  16. ldp p0, p16, [x0] // p0 = real receiver, p16 = class
  17. ldr p16, [x16, #SUPERCLASS] // p16 = class->superclass
  18. #endif
  19. L_objc_msgSendSuper2_body:
  20. CacheLookup NORMAL, _objc_msgSendSuper2, __objc_msgSend_uncached
  21. END_ENTRY _objc_msgSendSuper2

发现最终会调用_objc_msgSendSuper2,会根据当前类型的superclass指针去查找父类方法来调用;而传进来的第一个结构体变量的真实类型是objc_super2

  1. struct objc_super2 {
  2. id receiver;
  3. Class current_class;
  4. };

我们可以在objc源码NSObject.mm中查看class方法的实现,就是获取当前调用者的类型,那传进去的self就是当前调用者,所以不论是[self class]还是[super class]得到的都是当前调用者的类型,也就是Student类型

  1. + (Class)class {
  2. return self;
  3. }
  4. - (Class)class {
  5. return object_getClass(self);
  6. }

superclass方法的实现就是获取当前调用者类型的父类类型,所以[self superclass]还是[super superclass]得到的都是Person类型

  1. + (Class)superclass {
  2. return self->getSuperclass();
  3. }
  4. - (Class)superclass {
  5. return [self class]->getSuperclass();
  6. }

2.以下代码能不能执行成功?如果可以,打印结果是什么?

  1. @interface Person : NSObject
  2. @property (copy, nonatomic) NSString *name;
  3. - (void)print;
  4. @end
  5. @implementation Person
  6. - (void)print
  7. {
  8. NSLog(@"my name is %@", self->_name);
  9. // 能调用成功,输入结果为 my name is <ViewController: 0x7fcfc2a04720>
  10. }
  11. @end
  12. @interface ViewController ()
  13. @end
  14. @implementation ViewController
  15. - (void)viewDidLoad {
  16. [super viewDidLoad];
  17. id cls = [Person class];
  18. void *obj = &cls;
  19. [(__bridge id)obj print];
  20. }
  21. @end

将上述代码用下图来表示关系可以看出,创建一个Person对象Person *person = [[Person alloc] init]等同于obj指向的[Person class],所以obj可以直接调用print函数

然后我们知道viewDidLoad中的几个局部变量的内存地址都是从大到小的排列,所以可以用下图来表示

[super viewDidLoad]的本质就是调用objc_msgSendSuper2的函数,函数的第一个参数就是一个类似于下面代码的结构体变量

  1. struct objc_super2 { self, [ViewController class]};

所以在内存中的分布也就是如下图所示

Person结构体的本质如下面代码所示,里面的成员变量也是由低到高来排列;所以self->_name就是在结构体里跳过isa指针找到_name成员变量,也就相等于obj跳过cls找到结构体变量里的self,那么取得值就是ViewController的内存地址

  1. struct Person_IMPL
  2. {
  3. Class isa;
  4. NSString *_name;
  5. };

3.Runtime的具体应用

利用关联对象(AssociatedObject)给分类添加属性遍历类的所有成员变量(修改textfield的占位文字颜色、字典转模型、自动归档解档)交换方法实现(交换系统的方法,监听按钮多次点击事件)利用消息转发机制解决方法找不到的异常问题
1.替换类

  1. @interface Person: NSObject
  2. - (void)run;
  3. @end
  4. @implementation Person
  5. - (void)run
  6. {
  7. NSLog(@"%s", __func__);
  8. }
  9. @end
  10. @interface Car: NSObject
  11. - (void)run;
  12. @end
  13. @implementation Car
  14. - (void)run
  15. {
  16. NSLog(@"%s", __func__);
  17. }
  18. @end
  19. Person *person = [[Person alloc] init];
  20. [person run];
  21. object_setClass(person, [Car class]);
  22. [person run];
  23. // 分别输出 [Person run],[Car run]

2.判断是否为类对象

  1. NSLog(@"%d %d %d",
  2. object_isClass(person),
  3. object_isClass([Person class]),
  4. object_isClass(object_getClass([Person class]))
  5. );

3.动态创建类

  1. void run(id self, SEL _cmd)
  2. {
  3. NSLog(@"_____ %@ - %@", self, NSStringFromSelector(_cmd));
  4. }
  5. // 创建类
  6. Class newClass = objc_allocateClassPair([NSObject class], "Dog", 0);
  7. class_addIvar(newClass, "_age", 4, 1, @encode(int));
  8. class_addIvar(newClass, "_weight", 4, 1, @encode(int));
  9. class_addMethod(newClass, @selector(run), (IMP)run, "v@:");
  10. // 注册类
  11. objc_registerClassPair(newClass);
  12. id dog = [[newClass alloc] init];
  13. [dog setValue:@10 forKey:@"_age"];
  14. [dog setValue:@20 forKey:@"_weight"];
  15. [dog run];
  16. // 在不需要这个类时释放
  17. // objc_disposeClassPair(newClass);

注意: 由于成员变量是只读属性,所以必须在注册类之前添加;为了规范,属性、成员变量、方法都最好在注册类之前添加好

4.成员变量相关的用法

  1. // 获取成员变量信息
  2. Ivar ageIvar = class_getInstanceVariable([Person class], "_age");
  3. NSLog(@"%s %s", ivar_getName(ageIvar), ivar_getTypeEncoding(ageIvar));
  4. // 设置和获取成员变量的值
  5. Ivar nameIvar = class_getInstanceVariable([Person class], "_name");
  6. Person *person = [[Person alloc] init];
  7. object_setIvar(person, nameIvar, @"123");
  8. // 不能直接传基本数据类型,所以先转成指针类型,再变为id类型
  9. object_setIvar(person, ageIvar, (__bridge id)(void *)10);
  10. NSLog(@"%@ %d", person.name, person.age);
  11. // 获取所有成员变量信息
  12. // 成员变量的数量
  13. unsigned int count;
  14. Ivar *ivars = class_copyIvarList([Person class], &count);
  15. for (int i = 0; i < count; i++) {
  16. // 取出i位置的成员变量
  17. Ivar ivar = ivars[i];
  18. NSLog(@"%s %s", ivar_getName(ivar), ivar_getTypeEncoding(ivar));
  19. }
  20. free(ivars);

5.方法实现的置换

  1. void myrun()
  2. {
  3. NSLog(@"---myrun");
  4. }
  5. Person *person = [[Person alloc] init];
  6. class_replaceMethod([Person class], @selector(run), (IMP)myrun, "v");
  7. // class_replaceMethod([Person class], @selector(run), imp_implementationWithBlock(^{
  8. // NSLog(@"123123");
  9. // }), "v");
  10. [person run];

将类里的两个方法实现交换

  1. @implementation Person
  2. - (void)run
  3. {
  4. NSLog(@"%s", __func__);
  5. }
  6. - (void)test
  7. {
  8. NSLog(@"%s", __func__);
  9. }
  10. @end
  11. Person *person = [[Person alloc] init];
  12. Method runMethod = class_getInstanceMethod([Person class], @selector(run));
  13. Method testMethod = class_getInstanceMethod([Person class], @selector(test));
  14. method_exchangeImplementations(runMethod, testMethod);
  15. [person run];

利用方法交换进行容错

  1. @interface NSMutableArray (Extension)
  2. @end
  3. @implementation NSMutableArray (Extension)
  4. + (void)load
  5. {
  6. static dispatch_once_t onceToken;
  7. dispatch_once(&onceToken, ^{
  8. // 类簇:NSString、NSArray、NSDictionary,真实类型是其他类型
  9. Class cls = NSClassFromString(@"__NSArrayM");
  10. Method method1 = class_getInstanceMethod(cls, @selector(insertObject:atIndex:));
  11. Method method2 = class_getInstanceMethod(cls, @selector(ll_insertObject:atIndex:));
  12. method_exchangeImplementations(method1, method2);
  13. });
  14. }
  15. - (void)ll_insertObject:(id)anObject atIndex:(NSUInteger)index
  16. {
  17. if (anObject == nil) return;
  18. [self ll_insertObject:anObject atIndex:index];
  19. }
  20. @end

我们在objc4源码里可以看到该方法的实现,就是将两个函数的IMP交换,并且清空缓存

  1. void method_exchangeImplementations(Method m1, Method m2)
  2. {
  3. if (!m1 || !m2) return;
  4. mutex_locker_t lock(runtimeLock);
  5. IMP imp1 = m1->imp(false);
  6. IMP imp2 = m2->imp(false);
  7. SEL sel1 = m1->name();
  8. SEL sel2 = m2->name();
  9. m1->setImp(imp2);
  10. m2->setImp(imp1);
  11. // RR/AWZ updates are slow because class is unknown
  12. // Cache updates are slow because class is unknown
  13. // fixme build list of classes whose Methods are known externally?
  14. flushCaches(nil, __func__, [sel1, sel2, imp1, imp2](Class c){
  15. return c->cache.shouldFlush(sel1, imp1) || c->cache.shouldFlush(sel2, imp2);
  16. });
  17. adjustCustomFlagsForMethodChange(nil, m1);
  18. adjustCustomFlagsForMethodChange(nil, m2);
  19. }

在这个函数里清空缓存数据

  1. static void flushCaches(Class cls, const char *func, bool (^predicate)(Class))
  2. {
  3. runtimeLock.assertLocked();
  4. #if CONFIG_USE_CACHE_LOCK
  5. mutex_locker_t lock(cacheUpdateLock);
  6. #endif
  7. const auto handler = ^(Class c) {
  8. if (predicate(c)) {
  9. // 清空数据
  10. c->cache.eraseNolock(func);
  11. }
  12. return true;
  13. };
  14. if (cls) {
  15. foreach_realized_class_and_subclass(cls, handler);
  16. } else {
  17. foreach_realized_class_and_metaclass(handler);
  18. }
  19. }

原文链接:http://www.cnblogs.com/funkyRay/p/ios-di-ceng-yuan-li-wuruntime-xia.html

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号