Objective-C的runtime

2014-04-27 Xiaosong Gao 更多博文 » 博客 » GitHub »

iOS Mac

原文链接 https://gaoxiaosong.github.io/2014/04/27/objective-c-runtime.html
注:以下为加速网络访问所做的原文缓存,经过重新格式化,可能存在格式方面的问题,或偶有遗漏信息,请以原文为准。


Objective-C是一门简单的语言,95%是C语言。只是在语言层面上加了些关键字和语法。真正让Objective-C如此强大的是它的运行时。它很小但却很强大。它的核心是消息分发。

归纳了下,Objective-C的核心思想是消息机制,先在这里讲下Objective-C里类的原理。首先我们看他对类的定义:

typedef struct objc_class *Class;
typedef struct objc_object {
  Class isa;
} *id;

大多数面向对象的语言里有classes和objects的概念。Objects通过Classes生成。但是在Objective-C中,classes本身也是objects,这点跟python很像,也可以处理消息,这也是为什么会有类方法和实例方法。具体来说,Objective-C中的Object是一个结构体(struct),第一个成员是isa,指向自己的class。这是在objc/objc.h中定义的。

object的class保存了方法列表,还有指向父类的指针。但classes也是objects,也会有isa变量,那么它又指向哪儿呢?这里就引出了第三个类型:metaclasses。一个metaclass被指向class,class被指向object。它保存了所有实现的方法列表,以及父类的metaclass这个的定义在runtime.h里:

Objective-C is a class-based object system. Each object is an instance of some class; the object's isa pointer points to its class. That class describes the object's data: allocation size and ivar types and layout. The class also describes the object's behavior: the selectors it responds to and instance methods it implements.

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;

从中可以清楚的看到一个Class到底里面是些什么东西,父类,名字,还有一堆我们关心的方法,协议,缓存数据等。可以看到方法协议,什么的都用的还是struct。

当然我们肯定关心的是方法,首先看下方法列表的定义:

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;
} OBJC2_UNAVAILABLE;

继续看下方法的具体定义:

struct objc_method {
  SEL method_name OBJC2_UNAVAILABLE;
  char *method_types OBJC2_UNAVAILABLE;
  IMP method_imp OBJC2_UNAVAILABLE;
} OBJC2_UNAVAILABLE;

这下差不多了,这是具体方法的定义。这个内部的剖析,等下在说,先绕开往下说下objc_cache。看看objc_cache的定义:

struct objc_cache {
  unsigned int mask /* total = mask + 1 */ OBJC2_UNAVAILABLE;
  unsigned int occupied OBJC2_UNAVAILABLE;
  Method buckets[1] OBJC2_UNAVAILABLE;
} /* GrP fixme should be OBJC2_UNAVAILABLE, but isn't because of spurious warnings in [super ...] calls */;

等下,这有个Method有点类似objc_method哈,于是找到这个定义:

typedef struct objc_method *Method;

讲讲方法吧,这里也就是上面要涉及但憋回去的消息机制。说起方法就不得不说他的机制,Objective-C里的方法就是消息:

NSMutableArray *arr = [NSMutableArray arrayWithObjects:@"dd", nil];
[arr addObject:@"k"];
objc_msgSend(arr, @selector(addObject:),@"pp");
NSLog(@"arr:%@",arr);

他们可以等价,调用array的insertObject的方法,参数分别是foo, 5。上面就是典型的Objective-C模式,下面就是等价的消息模式,前提是你得引入消息模式的文件。

#if TARGET_IPHONE_SIMULATOR
#import <objc/objc-runtime.h>
#else
#import <objc/runtime.h>
#import <objc/message.h>
#endif

继续说这个objc_method,逐个说下SEL类成员方法的指针。可以理解@selector()就是取类方法的编号,他的行为基本可以等同C语言的中函数指针,只不过C语言中,可以把函数名直接赋给一个函数指针,而Objective-C的类不能直接应用函数指针,这样只能做一个@selector语法来取。可以用这个NSSelectorFromString直接取,NSStringFromSelector反转。

简单说就是函数的地址,只不过函数的地址和函数实现的地址不在同一个地方。

method_types描述方法的参数列表. 在运行时注册选择器时使用。那时候方法名就会包含方法的参数列表。

method_imp之IMP是"implementation"的缩写,它是Objetive-C方法实现代码块的地址,可像C函数一样直接调用。通常情况下我们是通过[object method:parameter]或objc_msgSend()的方式向对象发送消息,然后Objective-C运行时寻找匹配此消息的IMP,然后调用它。但有些时候我们希望获取到IMP进行直接调用。

简单来说method_imp就是函数实现的代码块地址。

方法大概就这些,接下来就说下,对象是怎么找到方法的,这里涉及到一些原理,我们可以假装不知道的。

当一个对象去执行某一个方法的时候,首先是去cache里找method,因为这个最快(他们都是存到hash里的),当找不到的时候才去那个objc_method_list里找,如果还没有就去super_class里找,这样循环到根上也就是NSObject,还是没有就不好意思crash了。

接下来说下平常coding有用的东西了。

class开头的方法是用来修改classes。方法如class_addIvar、class_addMethod、class_addProperty和class_addProtocol允许重建classes。class_copyIvarList、class_copyMethodList、class_copyProtocolList和class_copyPropertyList能拿到一个class的所有内容。而class_getClassMethod、class_getClassVariable、class_getInstanceMethod、class_getInstanceVariable、class_getMethodImplementation和class_getProperty返回单个内容。

假设我要取回某个对象的属性或者方法:

uint attrCount = 0, selfCount = 0;
objc_property_t *attrs = class_copyPropertyList([self class], &attrCount);
Method *methods = class_copyMethodList([self class], &selfCount);
for (int i = 0; i <= attrCount; ++i) {
  objc_property_t property = attrs[i];
  NSLog(@"key:%s,value:%@",property_getName(property),[self valueForKey:[NSString stringWithFormat:@"%s",property_getName(property)]]);
}
for (int i = 0; i <= selfCount; ++i) {
  Method *method = methods[i];
  SEL sel = method_getName(method);
  IMP imp = method_getImplementation(method);
  NSLog(@"sel:%s",sel_getName(sel));
}

这些都不算啥,现在说点动态的东西,比如动态添加属性,动态添加方法。这里其实我们需要用class_addMethod就可以了。

这里有必要说下+(BOOL)resolveInstanceMethod:(SEL)sel;和+(BOOL)resolveClassMethod:(SEL)sel;字面上来说,一个是对象方法,一个是类方法,都是消息转发机制前调用的。

意思是如果我们劫持重写这个,就可以添加一切我们想要的方法。下面拿一个世界上最干净的类之一来说事:

@interface DB : NSObject
@end
@implementation DB
@end

如果我现在

DB *d = [DB new];
[d alert];

对,我们上面说到了劫持,所以我就继承了这个方法,并重写了下。第一个参数是消息的发起方,就对象,self就是方法的指针。必须的,后面的参数就跟着依次。

// 这个就是alert函数实现的地方。
void defineIMPForAlert(id self,SEL _cmd) {
  NSLog(@"xxxx");
}

这里可以通过重写这个,当然也可以在外面直接用class_addMethod。

+ (BOOL)resolveInstanceMethod:(SEL)sel {
  // 当在调用alert的时候,会自动添加这个方法进去。
  if (sel == @selector(alert)) {
  // 这里就劫持了并添加并实现了我想要的方法。最后那个参数是个奇葩见说明。
    class_addMethod([self class],sel,(IMP)defineIMPForAlert,[@"v@:" UTF8String]);
    return YES;
  }
  return [super resolveClassMethod:sel];
}

我的对象就具备了alert的方法。也就是说我的对象就可以来发送alert的消息。好吧,就是说我[d performSelector:@selector(alert)];就合法了(注意这里不能直接写[d alert],因为我那个是动态添加的应该是说要在运行的时候才有alert方法,所以IDE并不知道,编译也不会让你过)。尽管我什么alert都没定义。

记得上面说过属性也可以,是的,属性也可以。但网上的例子都不成熟,所以,我就先放一个我测试过了的。

NSString* name(id self,SEL _cmd) {
  return objc_getAssociatedObject(self, @"name");
}
void setName(id self,SEL _cmd,NSString *name) {
  objc_setAssociatedObject(self, @"name", name, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
class_addMethod([DB class],@selector(name),(IMP)name,[@"@@:" UTF8String]);
class_addMethod([DB class],@selector(setName:),(IMP)setName,[@"v@:@" UTF8String]);

这样我就可以:

[d performSelector:@selector(setName:) withObject:@"ddd"];
NSLog(@"self.name:%@",[d performSelector:@selector(name)]);

下面是些扩展用法。

@interface CC : NSObject
- (void)say;
- (void)sing;
@end

@implementation CC
- (void)say {
  NSLog(@"os");
}
- (void)sing {
  NSLog(@"sn");
}
@end

BB *b = [BB new];
// 我让b去继承cc的
object_setClass(b, [CC class]); //实现方法。
// 于是b就有了say的方法。
[b performSelector:@selector(say)];
CC *c = [CC new];
// 获取say的方法
Method methodSay = class_getInstanceMethod([c class], @selector(say));
Method methodSing = class_getInstanceMethod([c class], @selector(sing));
// 交换2个方法
method_exchangeImplementations(methodSay, methodSing); //实现方法。
// 于是有了结果的变化
[c say];
[c sing];
// b也跟着变了
[b performSelector:@selector(say)];

说明:

1、方法缩略写法的字符含义:

  • c char
  • i int
  • s short
  • l long
  • l is treated as a 32-bit quantity on 64-bit programs.
  • q long long
  • C unsigned char
  • I unsigned int
  • S unsigned short
  • L unsigned long
  • Q unsigned long long
  • f float
  • d double
  • B C++ bool or a C99 _Bool
  • v void
  • * character string (char *)
  • @ object (whether statically typed or typed id)
  • # class object (Class)
  • : method selector (SEL)
  • [array type] array
  • {name=type...} structure
  • (name=type...) A union
  • bnum bit field of num bits
  • ^ type pointer to type
  • ? An unknown type (among other things, this code is used for function pointers)

貌似@:组成了返回类型和形参的分割线。

2、设置子类继承关系:

Class object_setClass(id obj, Class cls)
{
  if (obj) {
    Class old;
    do {
      old = obj->isa;
    } while (! OSAtomicCompareAndSwapPtrBarrier(old, cls, (void*)&obj->isa));
    return old;
  }
  else return Nil;
}

3、交换两个方法名称对应的实现:

void method_exchangeImplementations(Method m1_gen, Method m2_gen)
{
  IMP m1_imp;
  struct old_method *m1 = _method_asOld(m1_gen);
  struct old_method *m2 = _method_asOld(m2_gen);
  if (!m1 || !m2) return;
  if (m1->method_name == (SEL)kIgnore  ||  m2->method_name == (SEL)kIgnore) {
    // Ignored methods stay ignored. Now they're both ignored.
    m1->method_imp = (IMP)&_objc_ignored_method;
    m2->method_imp = (IMP)&_objc_ignored_method;
    return;
  }
  OSSpinLockLock(&impLock);
  m1_imp = m1->method_imp;
  m1->method_imp = m2->method_imp;
  m2->method_imp = m1_imp;
  OSSpinLockUnlock(&impLock);
}