Method
struct method_t {
static const uint32_t smallMethodListFlag = 0x80000000;
method_t(const method_t &other) = delete;
struct big {
SEL name;
const char *types;
MethodListIMP imp;
};
method_t是一个没有成员变量的结构体,只有成员函数,在C++中这种结构体占1个字节.
二级结构big,一个SEL,表示方法名,一个char*表示参数类型和返回值,相当与方法的模板;最后是一个MethodListIMP指针,它是IMP的签名包装.
struct small {
RelativePointer<const void *> name;
RelativePointer<const char *> types;
RelativePointer<IMP, /*isNullable*/false> imp;
}
还有一个small结构体,这个用来描述方法在image中的存储结构,是三个偏移地址.
big &big() const {
ASSERT(!isSmall());
return *(struct big *)this;
}
提供了一个big函数用来获取big结构体.this是形参,实际调用时传的是对象指针.所以它做了强转,返回新的指针,新的指针是struct big类型,大小也不再是一个字节.
ALWAYS_INLINE SEL name() const {
if (isSmall()) {
if (small().inSharedCache()) {
return (SEL)small().name.get(sharedCacheRelativeMethodBase());
} else {
// Outside of the shared cache, relative methods point to a selRef
return *(SEL *)small().name.get();
}
} else {
return big().name;
}
}
const char *types() const {
return isSmall() ? small().types.get() : big().types;
}
IMP imp(bool needsLock) const {
if (isSmall()) {
IMP smallIMP = ptrauth_sign_unauthenticated(small().imp.get(),
ptrauth_key_function_pointer, 0);
asm ("": : "r" (smallIMP) :);
IMP remappedIMP = remappedImp(needsLock);
if (remappedIMP)
return remappedIMP;
return smallIMP;
}
return big().imp;
}
这三个函数用于获取SEL, types和imp.实际内容也没啥,big的话直接取big结构体,small的话调用samll的成员函数取值.
观察一下内存布局
@interface MyClass : NSObject
@property(nonatomic, strong) NSNumber *number;
@property(nonatomic, assign) NSInteger integer;
@property(atomic, assign) NSInteger atomic;
@property(nonatomic, copy) NSString *Str;
@property(nonatomic, weak) NSObject *weak;
@property(nonatomic, strong, readonly) NSObject *readonly;
- (void)instanceMethod;
@end
(lldb) p my.class
(Class) $0 = 0x0000000100008450
(lldb) p (objc_class *)$0
(objc_class *) $1 = 0x0000000100008450
(lldb) p $1->safe_ro()
(const class_ro_t *) $2 = 0x0000000100008348
(lldb) p *$2
(const class_ro_t) $3 = {
flags = 388
instanceStart = 8
instanceSize = 56
reserved = 0
= {
ivarLayout = 0x0000000100003f43 "\U00000001!\U00000011"
nonMetaclass = 0x0000000100003f43
}
name = {
std::__1::atomic<const char *> = "MyClass" {
Value = 0x0000000100003f3b "MyClass"
}
}
baseMethods = {
ptr = 0x00000001000080d8
}
baseProtocols = nil
ivars = 0x0000000100008218
weakIvarLayout = 0x0000000100003f47 "A"
baseProperties = 0x00000001000082e0
_swiftMetadataInitializer_NEVER_USE = {}
}
(lldb) p $3.baseMethods.ptr
(method_list_t *const) $4 = 0x00000001000080d8
(lldb) p $4->get(0)
(method_t) $5 = {}
(lldb) p $5.big()
(method_t::big) $6 = {
name = "setWeak:"
types = 0x0000000100003f59 "v24@0:8@16"
imp = 0x0000000100003bd0 (KCObjcBuild`-[MyClass setWeak:])
}
(lldb) p sizeof($5)
(unsigned long) $7 = 1
(lldb) p &$5
(method_t *) $9 = 0x00000001000080e0
(lldb) p &$6
(method_t::big *) $10 = 0x00000001000080e0
首先获取class_ro_t,然后输出baseMethods的get(0),是一个空的结构体,然后调用big()可以获取到big结构体.
下面查看空method_t的大小是1字节,并且method_t的地址和big结构体的地址是一样的,和big函数描述的一样.
然后还可以看到SEL是setWeak,这是@property(nonatomic, weak) NSObject *weak的setter.
(lldb) p $4.count
(uint32_t) $11 = 13
(lldb) p *$4
(method_list_t) $12 = {
entsize_list_tt<method_t, method_list_t, 4294901763, method_t::pointer_modifier> = (entsizeAndFlags = 27, count = 13)
}
(lldb) p $12.get(1)->big()
(method_t::big) $13 = {
name = "instanceMethod"
types = 0x0000000100003f49 "v16@0:8"
imp = 0x0000000100003a50 (KCObjcBuild`-[MyClass instanceMethod])
}
Fix-it applied, fixed expression was:
$12.get(1).big()
(lldb) p $12.get(1).big()
(method_t::big) $14 = {
name = "instanceMethod"
types = 0x0000000100003f49 "v16@0:8"
imp = 0x0000000100003a50 (KCObjcBuild`-[MyClass instanceMethod])
}
(lldb) p $12.get(2).big()
(method_t::big) $15 = {
name = "Str"
types = 0x0000000100003f51 "@16@0:8"
imp = 0x0000000100003b50 (KCObjcBuild`-[MyClass Str])
}
(lldb) p $12.get(3).big()
(method_t::big) $16 = {
name = ".cxx_destruct"
types = 0x0000000100003f49 "v16@0:8"
imp = 0x0000000100003c20 (KCObjcBuild`-[MyClass .cxx_destruct])
}
(lldb) p $12.get(4).big()
(method_t::big) $17 = {
name = "number"
types = 0x0000000100003f51 "@16@0:8"
imp = 0x0000000100003a80 (KCObjcBuild`-[MyClass number])
}
(lldb) p $12.get(5).big()
(method_t::big) $18 = {
name = "setNumber:"
types = 0x0000000100003f59 "v24@0:8@16"
imp = 0x0000000100003aa0 (KCObjcBuild`-[MyClass setNumber:])
}
(lldb) p $12.get(6).big()
(method_t::big) $19 = {
name = "readonly"
types = 0x0000000100003f51 "@16@0:8"
imp = 0x0000000100003c00 (KCObjcBuild`-[MyClass readonly])
}
(lldb) p $12.get(7).big()
(method_t::big) $20 = {
name = "setAtomic:"
types = 0x0000000100003f6c "v24@0:8q16"
imp = 0x0000000100003b30 (KCObjcBuild`-[MyClass setAtomic:])
}
(lldb) p $12.get(8).big()
(method_t::big) $21 = {
name = "atomic"
types = 0x0000000100003f64 "q16@0:8"
imp = 0x0000000100003b10 (KCObjcBuild`-[MyClass atomic])
}
(lldb) p $12.get(9).big()
(method_t::big) $22 = {
name = "weak"
types = 0x0000000100003f51 "@16@0:8"
imp = 0x0000000100003ba0 (KCObjcBuild`-[MyClass weak])
}
(lldb) p $12.get(10).big()
(method_t::big) $23 = {
name = "integer"
types = 0x0000000100003f64 "q16@0:8"
imp = 0x0000000100003ad0 (KCObjcBuild`-[MyClass integer])
}
(lldb) p $12.get(11).big()
(method_t::big) $24 = {
name = "setInteger:"
types = 0x0000000100003f6c "v24@0:8q16"
imp = 0x0000000100003af0 (KCObjcBuild`-[MyClass setInteger:])
}
(lldb) p $12.get(12).big()
(method_t::big) $25 = {
name = "setStr:"
types = 0x0000000100003f59 "v24@0:8@16"
imp = 0x0000000100003b70 (KCObjcBuild`-[MyClass setStr:])
}
baseMethods里一共有13个方法,全部输出,6个property,其中一个是readonly,它只有getter没有setter,
还有一个实例方法instanceMethod以及一个来cxx_destruct,这个函数用于释放成员变量.
///注释Myclass的属性再运行
(lldb) p $5.count
(uint32_t) $6 = 1
(lldb) p $5.get(0).big()
(method_t::big) $7 = {
name = "instanceMethod"
types = 0x0000000100003fa4 "v16@0:8"
imp = 0x0000000100003e70 (KCObjcBuild`-[MyClass instanceMethod])
}
假如把Myclass的property都注了,只留一个实例方法,此时cxx_destruct就没了.
SEL与IMP
/// An opaque type that represents a method selector.
typedef struct objc_selector *SEL;
SEL是一个不透明的objc_selector结构体指针,但是上面观察method_t的时候可以看到它就是一个字符串,或者说可以当做一个char *来看待.
它用于表示方法的名称,从上面的例子也可以看到,name等于方法名,所以它不区分是来自那个类对象或者元类,
这些方法名被添加在一张表中,runtime可以通过sel_registerName或者NSSelectorFormString获取SEL,编译时可以通过编译器指令@selector()来创建SEL.
/// A pointer to the function of a method implementation.
#if !OBJC_OLD_DISPATCH_PROTOTYPES
typedef void (*IMP)(void /* id, SEL, ... */ );
#else
typedef id _Nullable (*IMP)(id _Nonnull, SEL _Nonnull, ...);
#endif
IMP是一个函数指针,它的类型是 id ()(id, SEL, ...)或者void ()(...)
首先回忆一下C++的函数指针.
void aMethod(int a){
printf("aMethod = %d\n", a);
}
int main(int argc, const char * argv[]) {
@autoreleasepool {
void (*methodPointer)(int) = aMethod;
methodPointer(1);
NSLog(@"Hello, World!");
}
return 0;
}
void (* methodPointer)(int) = aMethod定义了一个函数指针,
指针要有类型,比如int * char*,这个指针的类型是void ()(int)这么一种函数.
void是函数的返回值类型,int是参数列表,methodPointer是指针的名字.
然后= aMethod是让指针methodPointer指向了函数aMethod.
(lldb) p &aMethod
(void (*)(int)) $0 = 0x0000000100003d50 (KCObjcBuild`aMethod at main.m:25)
(lldb) p methodPointer
(void (*)(int)) $1 = 0x0000000100003d50 (KCObjcBuild`aMethod at main.m:25)
调用这个函数,可以是aMethod(1),也可以是methodPointer(1),他们实质是一样的,函数名就像是数组名,访问数组名就是访问数组内存的起始,函数名也代表了这个函数在内存代码区的起始地址.
typedef void (*MY_SEL)(int);
MY_SEL a_sel = aMethod;
a_sel(1);
函数指针也使用typedef来取别名,这里定义了一个函数指针类型,MY_SEL,是void (* )(int).
就类似于typedef MY_INT int;然后就可以MY_INT a = 1;
所以IMP就是void ( * )()类型的指针.
而且不管它指向什么函数,有什么返回值,什么参数,都用这个类型的指针.
int aMethod(int a){
printf("aMethod = %d\n", a);
return a;
}
int main(int argc, const char * argv[]) {
@autoreleasepool {
class_addMethod(MyClass.class, @selector(aMethod:), (IMP)aMethod, "i@:i");
NSLog(@"Hello, World!");
}
return 0;
运行断点
(objc_class *) $0 = 0x0000000100008120
(lldb) p $0->data()
(class_rw_t *) $1 = 0x0000000108e27920
(lldb) p $1->methods()
(const method_array_t) $2 = {
list_array_tt<method_t, method_list_t, method_list_t_authed_ptr> = {
= {
list = {
ptr = 0x0000000108e498b1
}
arrayAndFlag = 4444166321
}
}
}
(lldb) p $2.begin()
(list_array_tt<method_t, method_list_t, method_list_t_authed_ptr>::iterator) $3 = {
lists = 0x0000000108e498b8
listsEnd = 0x0000000108e498c8
m = (entsize = 24, index = 0, element = 0x0000000108e49898)
mEnd = (entsize = 24, index = 1, element = 0x0000000108e498b0)
}
(lldb) p $3.m
(list_array_tt<method_t, method_list_t, method_list_t_authed_ptr>::iteratorImpl<false>::ListIterator<false>::Type) $4 = (entsize = 24, index = 0, element = 0x0000000108e49898)
(lldb) p $4.element
(entsize_list_tt<method_t, method_list_t, 4294901763, method_t::pointer_modifier>::iteratorImpl<false>::ElementPtr) $5 = 0x0000000108e49898
(lldb) p $5->big()
(method_t::big) $6 = {
name = "aMethod:"
types = 0x0000000100003f60 "i@:i"
imp = 0x0000000100003de0 (KCObjcBuild`aMethod at main.m:25)
}
(lldb) p *$6.imp
(void (*)()) $7 = 0x0000000100003de0 (KCObjcBuild`aMethod at main.m:25)
可以看到IMP是void (*)()类型指针,但是我们给MyClass添加的方法指定的imp是aMethod,类型是int:int
(lldb) p $5.imp
(MethodListIMP) $6 = 0x0000000100003db0 (KCObjcBuild`aMethod at main.m:25)
(lldb) p *$6
(void (*)()) $7 = 0x0000000100003db0 (KCObjcBuild`aMethod at main.m:25)
(lldb) p (int (*)(int))$7
(int (*)(int)) $8 = 0x0000000100003db0 (KCObjcBuild`aMethod at main.m:25)
(lldb) p $8(1)
aMethod = 1
(int) $9 = 1
所以我们想要调用这个函数需要转换指针类型,把(void ( * )())转换成(int (*)(int)).然后就可以调用aMethod了.
addMethod
static IMP
addMethod(Class cls, SEL name, IMP imp, const char *types, bool replace)
{
IMP result = nil;
method_t *m;
if ((m = getMethodNoSuper_nolock(cls, name))) {
// already exists
if (!replace) {
result = m->imp(false);
} else {
result = _method_setImplementation(cls, m, imp);
}
} else {
// fixme optimize
method_list_t *newlist;
newlist = (method_list_t *)calloc(method_list_t::byteSize(method_t::bigSize, 1), 1);
newlist->entsizeAndFlags =
(uint32_t)sizeof(struct method_t::big) | fixed_up_method_list;
newlist->count = 1;
auto &first = newlist->begin()->big();
first.name = name;
first.types = strdupIfMutable(types);
first.imp = imp;
addMethods_finish(cls, newlist);
result = nil;
}
return result;
}
runtime可以动态添加方法,这个函数可以指定是否替换replace原来的方法.
getMethodNoSuper_nolock这个函数用于在类中通过SEL查找方法method_t,这部分属于方法慢速查找流程,之后在细说.
如果找到了,需要替换就替换,替换不替换都是返回原来的那个IMP,因为_method_setImplementation返回旧的IMP.
如果没找到,就创建一个新的method_list_t,给首位元素赋值,SEL,types和IMP.
static void
addMethods_finish(Class cls, method_list_t *newlist)
{
auto rwe = cls->data()->extAllocIfNeeded();
if (newlist->count > 1) {
method_t::SortBySELAddress sorter;
std::stable_sort(&newlist->begin()->big(), &newlist->end()->big(), sorter);
}
prepareMethodLists(cls, &newlist, 1, NO, NO, __func__);
rwe->methods.attachLists(&newlist, 1);
flushCaches(cls, __func__, [](Class c){
return !c->cache.isConstantOptimizedCache();
});
}
最后调用addMethods_finish,
先对newlist进行了排序,这个排序在查找中会用到.
rwe是objc_class的class_rw_t的rw_ext_t,也就是rw的内容,可以获取到methods,是list_array_tt类型,
所以后面调用了list_array_tt的attachLists来添加新的method_list_t;
然后添加了缓存,runtime添加的方法,添加的时候就已经放在缓存里了.
最后如果addMethod成功添加了一个新的,result就有没有值,如果已经存在旧的SEL,那result就有值,
所以class_addMethod最后才会这么写
BOOL
class_addMethod(Class cls, SEL name, IMP imp, const char *types)
{
if (!cls) return NO;
mutex_locker_t lock(runtimeLock);
return !addMethod(cls, name, imp, types ?: "", NO);
}
如果addMethod返回了有值,那就添加失败了.
objc_msgSend
#if !OBJC_OLD_DISPATCH_PROTOTYPES
OBJC_EXPORT void
objc_msgSend(void /* id self, SEL op, ... */ )
OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0);
这里OBJC_OLD_DISPATCH_PROTOTYPES是0,所以最终objc_msgSend是这么定义的.
但是在使用的时候会进行强制类型转换,来匹配使用场景下的真实返回值和参数.
objc_msgSend是一个函数,
- (id)performSelector:(SEL)sel withObject:(id)obj {
if (!sel) [self doesNotRecognizeSelector:sel];
return ((id(*)(id, SEL, id))objc_msgSend)(self, sel, obj);
}
比如performSelector的实现是这样的,最终是调用的objc_msgSend函数,不过需要转换类型
(lldb) p objc_msgSend
error: expression failed to parse:
error: <user expression 0>:1:1: 'objc_msgSend' has unknown type; cast it to its declared type to use it
objc_msgSend是一个不完整的类型,如果在这里断点,输出p objc_msgSend,
会报错,让你转换类型再输出
- (id)performSelector:(SEL)sel withObject:(id)obj {
if (!sel) [self doesNotRecognizeSelector:sel];
id(*my_objc_msgSend)(id, SEL, id) = (id(*)(id, SEL, id))objc_msgSend;
return my_objc_msgSend(self, sel, obj);
}
(lldb) p my_objc_msgSend
(id (*)(id, SEL, id)) $0 = 0x0000000108f9bf80 (libobjc.A.dylib`objc_msgSend)
我们可以改造一下这个方法,先转换,再调用,也是可以生效的.
objc4中使用objc_msgSend的时候,都进行了强制类型转换.
@interface Myclass : NSObject
@property(nonatomic, strong) NSNumber *myName;
@end
@implementation Myclass
@end
int main(int argc, char * argv[]) {
id obj = [Myclass alloc];
Myclass *my = [obj init];
return 0;
}
修改main.m的代码,随便什么类型的工程都行.添加一个MyClass,然后alloc ,init.
clang -rewrite-objc main.m -o main.cpp
把main.m编译成C++代码.
int main(int argc, char * argv[]) {
id obj = ((Myclass *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("Myclass"), sel_registerName("alloc"));
Myclass *my = ((id (*)(id, SEL))(void *)objc_msgSend)((id)obj, sel_registerName("init"));
return 0;
}
找到main函数,
原来的OC代码是Myclass *my = [[Myclass alloc]init];现在被转换成两个objc_msgSend函数调用.
这里(void *)是做了两次类型转换,void 可以指向任意类型的数据,
所以第一句是把objc_msgSend 转换成(Myclass ()(id, SEL)),然后调用,传了两个参数,一个Class指针,一个SEL.
第二句是把objc_msgSend转换成(id ()(id, SEL))然后调用,传了两个参数obj和一个SEL.
alloc传的第一个参数是类对象,init传的第一个参数是实例对象,这对应了alloc是+方法,init是-方法.
objc_msgSend没有C++的实现,而是直接汇编实现.具体是在source文件夹里的几个.s文件,比如objc-msg-arm64.s
并且它还是一个系列的函数,在message.h中定义了其他很多函数.
OBJC_EXPORT void
objc_msgSendSuper(void /* struct objc_super *super, SEL op, ... */ )
OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0);
objc_msgSend_stret(void /* id self, SEL op, ... */ )
OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0)
OBJC_ARM64_UNAVAILABLE;
OBJC_EXPORT void
objc_msgSendSuper_stret(void /* struct objc_super *super, SEL op, ... */ )
OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0)
OBJC_ARM64_UNAVAILABLE;
这些函数都是汇编实现,和objc_msgSend在相同的位置,
不过特别的是,除了objc_msgSend之外,其他函数没有在汇编之外的地方调用过.
但是他们声明在外面,所以也可以主动调用看看.
objc_msgSendSuper需要一个struct objc_super *,一个SEL
struct objc_super {
__unsafe_unretained _Nonnull id receiver;
__unsafe_unretained _Nonnull Class super_class;
};
objc_super结构体是这样的,指定一个实例对象receiver,还有父类.
//MyClass
- (int)instanceMethod:(NSNumber *)a{
NSLog(@"instanceMethod = %d", [a intValue]);
return [a intValue];
}
//main.m
#import <objc/message.h>
MySubClass *sub = [[MySubClass alloc]init];
struct objc_super s;
s.receiver = sub;
s.super_class = [MyClass class];
((id (*)(struct objc_super*, SEL, NSNumber *))objc_msgSendSuper)(&s, sel_registerName("instanceMethod:"), @9);
需要头文件#import <objc/message.h>
objc_msgSendSuper同样也需要强制转换类型,顺便带上参数,
指定objc_super的接受者是sub对象,父类是MyClass.class.
instanceMethod:是父类MyClass的实例方法,MySubClass没有重写.
运行可以正常输出
instanceMethod = 9
performSelector
- (int)instanceMethod:(int)a{
NSLog(@"instanceMethod = %d", a);
return a;
}
//- (id)performSelector:(SEL)sel withObject:(id)obj
假如instanceMethod是这么写的,它接收一个int型的参数,那么performSelector就不好使了,
因为performSelector的参数必须是id,也就是objc_object *类型.
并且performSelector只提供了无参,一个参数和两个参数两种版本.
MyClass *my = [[MyClass alloc]init];
((int (*)(id, SEL, int))objc_msgSend)(my, sel_registerName("instanceMethod:"), 9);
这个只能用objc_msgSend来调用
MyClass *my = [[MyClass alloc]init];
int num = 9;
SEL sel = sel_registerName("instanceMethod:");
NSMethodSignature *signature = [my methodSignatureForSelector:sel];
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature];
[invocation setArgument:&num atIndex:2];
invocation.selector = sel;
invocation.target = my;
[invocation invoke];
int result;
[invocation getReturnValue:&result];
NSLog(@"result = %d", result);
或者使用NSInvocation.关于NSInvocation的细节在之后消息转发中研究.