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是一個(gè)沒有成員變量的結(jié)構(gòu)體,只有成員函數(shù),在C++中這種結(jié)構(gòu)體占1個(gè)字節(jié).
二級結(jié)構(gòu)big,一個(gè)SEL,表示方法名,一個(gè)char*表示參數(shù)類型和返回值,相當(dāng)與方法的模板;最后是一個(gè)MethodListIMP指針,它是IMP的簽名包裝.
struct small {
RelativePointer<const void *> name;
RelativePointer<const char *> types;
RelativePointer<IMP, /*isNullable*/false> imp;
}
還有一個(gè)small結(jié)構(gòu)體,這個(gè)用來描述方法在image中的存儲結(jié)構(gòu),是三個(gè)偏移地址.
big &big() const {
ASSERT(!isSmall());
return *(struct big *)this;
}
提供了一個(gè)big函數(shù)用來獲取big結(jié)構(gòu)體.this是形參,實(shí)際調(diào)用時(shí)傳的是對象指針.所以它做了強(qiáng)轉(zhuǎn),返回新的指針,新的指針是struct big類型,大小也不再是一個(gè)字節(jié).
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;
}
這三個(gè)函數(shù)用于獲取SEL, types和imp.實(shí)際內(nèi)容也沒啥,big的話直接取big結(jié)構(gòu)體,small的話調(diào)用samll的成員函數(shù)取值.
觀察一下內(nèi)存布局
@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),是一個(gè)空的結(jié)構(gòu)體,然后調(diào)用big()可以獲取到big結(jié)構(gòu)體.
下面查看空method_t的大小是1字節(jié),并且method_t的地址和big結(jié)構(gòu)體的地址是一樣的,和big函數(shù)描述的一樣.
然后還可以看到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個(gè)方法,全部輸出,6個(gè)property,其中一個(gè)是readonly,它只有g(shù)etter沒有setter,
還有一個(gè)實(shí)例方法instanceMethod以及一個(gè)來cxx_destruct,這個(gè)函數(shù)用于釋放成員變量.
///注釋Myclass的屬性再運(yùn)行
(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都注了,只留一個(gè)實(shí)例方法,此時(shí)cxx_destruct就沒了.
SEL與IMP
/// An opaque type that represents a method selector.
typedef struct objc_selector *SEL;
SEL是一個(gè)不透明的objc_selector結(jié)構(gòu)體指針,但是上面觀察method_t的時(shí)候可以看到它就是一個(gè)字符串,或者說可以當(dāng)做一個(gè)char *來看待.
它用于表示方法的名稱,從上面的例子也可以看到,name等于方法名,所以它不區(qū)分是來自那個(gè)類對象或者元類,
這些方法名被添加在一張表中,runtime可以通過sel_registerName或者NSSelectorFormString獲取SEL,編譯時(shí)可以通過編譯器指令@selector()來創(chuàng)建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是一個(gè)函數(shù)指針,它的類型是 id ()(id, SEL, ...)或者void ()(...)
首先回憶一下C++的函數(shù)指針.
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定義了一個(gè)函數(shù)指針,
指針要有類型,比如int * char*,這個(gè)指針的類型是void ()(int)這么一種函數(shù).
void是函數(shù)的返回值類型,int是參數(shù)列表,methodPointer是指針的名字.
然后= aMethod是讓指針methodPointer指向了函數(shù)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)
調(diào)用這個(gè)函數(shù),可以是aMethod(1),也可以是methodPointer(1),他們實(shí)質(zhì)是一樣的,函數(shù)名就像是數(shù)組名,訪問數(shù)組名就是訪問數(shù)組內(nèi)存的起始,函數(shù)名也代表了這個(gè)函數(shù)在內(nèi)存代碼區(qū)的起始地址.
typedef void (*MY_SEL)(int);
MY_SEL a_sel = aMethod;
a_sel(1);
函數(shù)指針也使用typedef來取別名,這里定義了一個(gè)函數(shù)指針類型,MY_SEL,是void (* )(int).
就類似于typedef MY_INT int;然后就可以MY_INT a = 1;
所以IMP就是void ( * )()類型的指針.
而且不管它指向什么函數(shù),有什么返回值,什么參數(shù),都用這個(gè)類型的指針.
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;
運(yùn)行斷點(diǎn)
(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
所以我們想要調(diào)用這個(gè)函數(shù)需要轉(zhuǎn)換指針類型,把(void ( * )())轉(zhuǎn)換成(int (*)(int)).然后就可以調(diào)用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可以動態(tài)添加方法,這個(gè)函數(shù)可以指定是否替換replace原來的方法.
getMethodNoSuper_nolock這個(gè)函數(shù)用于在類中通過SEL查找方法method_t,這部分屬于方法慢速查找流程,之后在細(xì)說.
如果找到了,需要替換就替換,替換不替換都是返回原來的那個(gè)IMP,因?yàn)開method_setImplementation返回舊的IMP.
如果沒找到,就創(chuàng)建一個(gè)新的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();
});
}
最后調(diào)用addMethods_finish,
先對newlist進(jìn)行了排序,這個(gè)排序在查找中會用到.
rwe是objc_class的class_rw_t的rw_ext_t,也就是rw的內(nèi)容,可以獲取到methods,是list_array_tt類型,
所以后面調(diào)用了list_array_tt的attachLists來添加新的method_list_t;
然后添加了緩存,runtime添加的方法,添加的時(shí)候就已經(jīng)放在緩存里了.
最后如果addMethod成功添加了一個(gè)新的,result就有沒有值,如果已經(jīng)存在舊的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是這么定義的.
但是在使用的時(shí)候會進(jìn)行強(qiáng)制類型轉(zhuǎn)換,來匹配使用場景下的真實(shí)返回值和參數(shù).
objc_msgSend是一個(gè)函數(shù),
- (id)performSelector:(SEL)sel withObject:(id)obj {
if (!sel) [self doesNotRecognizeSelector:sel];
return ((id(*)(id, SEL, id))objc_msgSend)(self, sel, obj);
}
比如performSelector的實(shí)現(xiàn)是這樣的,最終是調(diào)用的objc_msgSend函數(shù),不過需要轉(zhuǎn)換類型
(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是一個(gè)不完整的類型,如果在這里斷點(diǎn),輸出p objc_msgSend,
會報(bào)錯(cuò),讓你轉(zhuǎn)換類型再輸出
- (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)
我們可以改造一下這個(gè)方法,先轉(zhuǎn)換,再調(diào)用,也是可以生效的.
objc4中使用objc_msgSend的時(shí)候,都進(jìn)行了強(qiáng)制類型轉(zhuǎn)換.
@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的代碼,隨便什么類型的工程都行.添加一個(gè)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函數(shù),
原來的OC代碼是Myclass *my = [[Myclass alloc]init];現(xiàn)在被轉(zhuǎn)換成兩個(gè)objc_msgSend函數(shù)調(diào)用.
這里(void *)是做了兩次類型轉(zhuǎn)換,void 可以指向任意類型的數(shù)據(jù),
所以第一句是把objc_msgSend 轉(zhuǎn)換成(Myclass ()(id, SEL)),然后調(diào)用,傳了兩個(gè)參數(shù),一個(gè)Class指針,一個(gè)SEL.
第二句是把objc_msgSend轉(zhuǎn)換成(id ()(id, SEL))然后調(diào)用,傳了兩個(gè)參數(shù)obj和一個(gè)SEL.
alloc傳的第一個(gè)參數(shù)是類對象,init傳的第一個(gè)參數(shù)是實(shí)例對象,這對應(yīng)了alloc是+方法,init是-方法.
objc_msgSend沒有C++的實(shí)現(xiàn),而是直接匯編實(shí)現(xiàn).具體是在source文件夾里的幾個(gè).s文件,比如objc-msg-arm64.s
并且它還是一個(gè)系列的函數(shù),在message.h中定義了其他很多函數(shù).
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;
這些函數(shù)都是匯編實(shí)現(xiàn),和objc_msgSend在相同的位置,
不過特別的是,除了objc_msgSend之外,其他函數(shù)沒有在匯編之外的地方調(diào)用過.
但是他們聲明在外面,所以也可以主動調(diào)用看看.
objc_msgSendSuper需要一個(gè)struct objc_super *,一個(gè)SEL
struct objc_super {
__unsafe_unretained _Nonnull id receiver;
__unsafe_unretained _Nonnull Class super_class;
};
objc_super結(jié)構(gòu)體是這樣的,指定一個(gè)實(shí)例對象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同樣也需要強(qiáng)制轉(zhuǎn)換類型,順便帶上參數(shù),
指定objc_super的接受者是sub對象,父類是MyClass.class.
instanceMethod:是父類MyClass的實(shí)例方法,MySubClass沒有重寫.
運(yùn)行可以正常輸出
instanceMethod = 9
performSelector
- (int)instanceMethod:(int)a{
NSLog(@"instanceMethod = %d", a);
return a;
}
//- (id)performSelector:(SEL)sel withObject:(id)obj
假如instanceMethod是這么寫的,它接收一個(gè)int型的參數(shù),那么performSelector就不好使了,
因?yàn)閜erformSelector的參數(shù)必須是id,也就是objc_object *類型.
并且performSelector只提供了無參,一個(gè)參數(shù)和兩個(gè)參數(shù)兩種版本.
MyClass *my = [[MyClass alloc]init];
((int (*)(id, SEL, int))objc_msgSend)(my, sel_registerName("instanceMethod:"), 9);
這個(gè)只能用objc_msgSend來調(diào)用
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.關(guān)于NSInvocation的細(xì)節(jié)在之后消息轉(zhuǎn)發(fā)中研究.