一 runtime簡介
二 Class的結(jié)構(gòu)
三 isa指針詳解
四 method詳解
五 方法調(diào)用及消息轉(zhuǎn)發(fā)流程
六 runtime常用api
七 runtime開發(fā)中具體應(yīng)用
一 runtime簡介
定義:所謂運(yùn)行時, 就是盡可能地把決定從編譯器推遲到運(yùn)行期, 就是盡可能地做到動態(tài). 只是在運(yùn)行的時候才會去確定對象的類型和方法的. 因此利用Runtime機(jī)制可以在程序運(yùn)行時動態(tài)地修改類和對象中的所有屬性和方法.
Objective-C中調(diào)用對象的方法時, 會向該對象發(fā)送一條消息, runtime根據(jù)該消息做出反應(yīng).
Runtime是一套比較底層的純C語言的API, Objective-C是運(yùn)行在Runtime上的, 因此在Runtime中動態(tài)添加和實(shí)現(xiàn)一些非常強(qiáng)大的功能也就不足為奇了.
在Objective-C代碼中使用Runtime, 需要引入<#import <objc/runtime.h>
總結(jié)說明:
runtime是什么?
runtime是一組APi,我們平時開發(fā)也用到很多API,所以runtime并沒啥稀奇的,只不過使用c語言寫的,GCD也是C語言寫的API庫。
runtime API干啥用的?
程序運(yùn)行時動態(tài)地修改類和對象中的所有屬性和方法,就是用來在程序運(yùn)行是改變類的屬性和行為的,就像GCD就是用來操作線程的。
二 Class的結(jié)構(gòu)
既然是一組API來操作類的,首先我們就先來了解下類的本質(zhì)是什么,然后再看它是怎么操作它的。
2.1我們看下類的本質(zhì)定義
objc 源碼地址 objc-runtime-new.h
我們看出來,它實(shí)際上就是用一個結(jié)構(gòu)體來定義的,我們想想一個類它有什么,它首先有,屬性,方法,然后有父類,他自己的標(biāo)示等等。這個結(jié)構(gòu)體里面基本都包含了。
接下來讓我們具體解釋一下這個結(jié)構(gòu)體里面的每一項(xiàng)內(nèi)容
2.1.1 ISA
Class ISA;
objc_class 中也有一個 isa 指針,這說明 Objc 類本身也是一個對象。為了處理類和對象的關(guān)系,Runtime 庫創(chuàng)建了一種叫做 Meta Class(元類) 的東西,類對象所屬的類就叫做元類。Meta Class 表述了類對象本身所具備的元數(shù)據(jù)。
我們所熟悉的類方法,就源自于 Meta Class。我們可以理解為類方法就是類對象的實(shí)例方法。每個類僅有一個類對象,而每個類對象僅有一個與之相關(guān)的元類。
當(dāng)你發(fā)出一個類似 NSObject alloc 的消息時,實(shí)際上,這個消息被發(fā)送給了一個類對象(Class Object),這個類對象必須是一個元類的實(shí)例,而這個元類同時也是一個根元類(Root Meta Class)的實(shí)例。所有元類的 isa 指針最終都指向根元類。 下一節(jié)我們再詳細(xì)介紹。
2.1.2 cache_t
struct cache_t {
struct bucket_t *_buckets;
mask_t _mask;
mask_t _occupied;
Cache 為方法調(diào)用的性能進(jìn)行優(yōu)化,每當(dāng)實(shí)例對象接收到一個消息時,它不會直接在 isa 指針指向的類的方法列表中遍歷查找能夠響應(yīng)的方法,因?yàn)槊看味家檎倚侍土?,而是?yōu)先在 Cache 中查找。
Runtime 系統(tǒng)會把被調(diào)用的方法存到 Cache 中,如果一個方法被調(diào)用,那么它有可能今后還會被調(diào)用,下次查找的時候就會效率更高。就像計(jì)算機(jī)組成原理中 CPU 繞過主存先訪問 Cache 一樣。
這個cache主要是方法調(diào)用的優(yōu)化,別的沒啥。
2.1.3 class_rw_t
struct class_rw_t {
// Be warned that Symbolication knows the layout of this structure.
uint32_t flags;
uint32_t version;
const class_ro_t *ro;
method_array_t methods; // 方法列表
property_array_t properties;// 屬性列表
protocol_array_t protocols; // 協(xié)議列表
Class firstSubclass;
Class nextSiblingClass;
char *demangledName;
}
class_rw_t里面的methods、properties、protocols是二維數(shù)組,是可讀可寫的,包含了類的初始內(nèi)容、分類的內(nèi)容,二位數(shù)組存儲是為的更好的擴(kuò)展新加的方法屬性或協(xié)議。類和分類的信息都存儲在這個結(jié)構(gòu)體里面
2.1.3 class_ro_t
struct class_ro_t {
uint32_t flags;
uint32_t instanceStart;
uint32_t instanceSize; //實(shí)例對象占用內(nèi)存空間大小
#ifdef __LP64__
uint32_t reserved;
#endif
const uint8_t * ivarLayout;
const char * name; //類名
method_list_t * baseMethodList;
protocol_list_t * baseProtocols;
const ivar_list_t * ivars; //成員變量列表
const uint8_t * weakIvarLayout;
property_list_t *baseProperties;
}
class_ro_t里面的baseMethodList、baseProtocols、ivars、baseProperties是一維數(shù)組,是只讀的,類的成員變量是存放在這里。
類的基本結(jié)構(gòu)大致就這些,接下來我們要詳細(xì)了解一下ISA指針,很多方面的東西都牽扯到它。
三 isa指針詳解
3.1
在arm64架構(gòu)之前,isa就是一個普通的指針,存儲著Class、Meta-Class對象的內(nèi)存地址
從arm64架構(gòu)開始,對isa進(jìn)行了優(yōu)化,變成了一個共用體(union)結(jié)構(gòu),還使用位域來存儲更多的信息
objc-private.h
union isa_t {
isa_t() { }
isa_t(uintptr_t value) : bits(value) { }
Class cls;
uintptr_t bits;
#if defined(ISA_BITFIELD)
struct {
ISA_BITFIELD; // defined in isa.h
};
#endif
};
isa.h
# if __arm64__
# define ISA_MASK 0x0000000ffffffff8ULL
# define ISA_MAGIC_MASK 0x000003f000000001ULL
# define ISA_MAGIC_VALUE 0x000001a000000001ULL
# define ISA_BITFIELD \
uintptr_t nonpointer : 1; \
uintptr_t has_assoc : 1; \
uintptr_t has_cxx_dtor : 1; \
uintptr_t shiftcls : 33; /*MACH_VM_MAX_ADDRESS 0x1000000000*/ \
uintptr_t magic : 6; \
uintptr_t weakly_referenced : 1; \
uintptr_t deallocating : 1; \
uintptr_t has_sidetable_rc : 1; \
uintptr_t extra_rc : 19
# define RC_ONE (1ULL<<45)
# define RC_HALF (1ULL<<18)
ISA_BITFIELD ISA位域信息解釋
nonpointer
0,代表普通的指針,存儲著Class、Meta-Class對象的內(nèi)存地址
1,代表優(yōu)化過,使用位域存儲更多的信息
has_assoc
是否有設(shè)置過關(guān)聯(lián)對象,如果沒有,釋放時會更快
has_cxx_dtor
是否有C++的析構(gòu)函數(shù)(.cxx_destruct),如果沒有,釋放時會更快
shiftcls
存儲著Class、Meta-Class對象的內(nèi)存地址信息
magic
用于在調(diào)試時分辨對象是否未完成初始化
weakly_referenced
是否有被弱引用指向過,如果沒有,釋放時會更快
deallocating
對象是否正在釋放
extra_rc
里面存儲的值是引用計(jì)數(shù)器減1
has_sidetable_rc
引用計(jì)數(shù)器是否過大無法存儲在isa中
如果為1,那么引用計(jì)數(shù)會存儲在一個叫SideTable的類的屬性中
我們可以看到arm64架構(gòu)之后isa 不僅存儲著Class、Meta-Class對象的內(nèi)存地址還存儲著其他更多信息, Class、Meta-Class對象的內(nèi)存地址被存儲在shiftcls 中占用33位
uintptr_t shiftcls : 33
所以我們從一個對象isa指針看到的并不是對象真實(shí)的內(nèi)存地址,我們要做一下位運(yùn)算才能得到真實(shí)的地址要 & MASK.
既然isa存儲這幾種對象的內(nèi)存地址,那我們就來了解下oc的幾種對象。
3.2 Objective-C中的對象
Objective-C中的對象,簡稱OC對象,主要可以分為3種
instance對象(實(shí)例對象)
class對象(類對象)
meta-class對象(元類對象)
3.2.1 實(shí)例對象 instance
instance對象就是通過類alloc出來的對象,每次調(diào)用alloc都會產(chǎn)生新的instance對象
Person *person1 = [[Person alloc] init];
Person *person2 = [[Person alloc] init];
Person *person3 = [[Person alloc] init];
Person *person4 = [[Person alloc] init];
person1、person2,person3,person4是Person類的instance對象(實(shí)例對象)
它們是不同的四個對象,分別占據(jù)著四塊不同的內(nèi)存
(lldb) po person1
<Person: 0x102805b70>
(lldb) po person2
<Person: 0x102805bc0>
(lldb) po person3
<Person: 0x102805bd0>
(lldb) po person4
<Person: 0x102805be0>
instance對象在內(nèi)存中存儲的信息包括
isa指針,及它自己的成員變量
3.2.2 class對象(類對象)
Person *person1 = [[Person alloc] init];
Person *person2 = [[Person alloc] init];
//傳入對象的類對象
Class p1class = [person1 class];
Class p2class = [person2 class];
NSLog(@"p1class:%p\n p2class:%p",p1class,p2class);
22:17:12.098954+0800 test[4172:228782]
p1class:0x100002180
p2class:0x100002180
p1class ,p2class都是Person的class對象(類對象)
它們是同一個對象。每個類在內(nèi)存中有且只有一個class對象
class對象在內(nèi)存中存儲的信息主要包括
isa指針
superclass指針
類的屬性信息(@property)、類的對象方法信息(instance method)//存的是動態(tài)方法,為空
類的協(xié)議信息(protocol)、類的成員變量信息(ivar) //存儲的是空值
......
3.2.3 meta-class對象(元類對象)
Person *person1 = [[Person alloc] init];
Person *person2 = [[Person alloc] init];
//傳入對象的類對象獲取元類對象
Class p1metaclass = object_getClass([person1 class]);
Class p2metaclass = object_getClass([person2 class]);
Boolean ismetaclass = class_isMetaClass(p1metaclass);//判斷是否是元類對象
NSLog(@"p1class:%p\n p2class:%p",p1metaclass,p2metaclass);
22:10:13.933354+0800 test[4129:225264] p1class:0x1000021a8
p2class:0x1000021a8
p1metaclass是Person的meta-class對象(元類對象)
每個類在內(nèi)存中有且只有一個meta-class對象
meta-class對象和class對象的內(nèi)存結(jié)構(gòu)是一樣的,但是用途不一樣,在內(nèi)存中存儲的信息主要包括
isa指針
superclass指針
類的類方法信息(class method)存的是靜態(tài)方法
......
3.3 isa的與三種對象之間的關(guān)系
- 實(shí)例對象(instance)的isa指向類對象(class)
- 類對象(class)的isa指向元類對象meta-class)
他們的關(guān)系圖如下所示:
當(dāng)調(diào)用對象方法時,通過instance的isa找到class,最后找到對象方法的實(shí)現(xiàn)進(jìn)行調(diào)用
當(dāng)調(diào)用類方法時,通過class的isa找到meta-class,最后找到類方法的實(shí)現(xiàn)進(jìn)行調(diào)用
3.4 supperclass講解
supperclass只存在于類對象與元類對象里,它的作用想必大家也能猜到,遇到繼承關(guān)系的時候supperclass就派上用場了,下面我們就分別講解一下,類對象里的supperclass以及元類對象里的supperclass
3.4.1 類對象里的supperclass
@interface Student : Person
@interface Person: NSObject
我們定義了兩個對象的繼承關(guān)系Student繼承Person,Person繼承NSObject,那么他們的類對象關(guān)系圖如下:
當(dāng)Student的instance對象要調(diào)用Person的對象方法時,會先通過isa找到Student的class,然后通過superclass找到Person的class,最后找到對象方法的實(shí)現(xiàn)進(jìn)行調(diào)用
3.4.2 元類對象里的supperclass
@interface Student : Person
@interface Person: NSObject
我們定義了兩個對象的繼承關(guān)系Student繼承Person,Person繼承
NSObject,那么他們的類對象關(guān)系圖如下:
當(dāng)Student的class要調(diào)用Person的類方法時,會先通過isa找到Student的meta-class,然后通過superclass找到Person的meta-class,最后找到類方法的實(shí)現(xiàn)進(jìn)行調(diào)用
3.5 isa、superclass總結(jié)
1 instance的isa指向class
2 class的isa指向meta-class
3 meta-class的isa指向基類的meta-class
4 class的superclass指向父類的class
如果沒有父類,superclass指針為nil
5 meta-class的superclass指向父類的meta-class
基類的meta-class的superclass指向基類的class
6 instance調(diào)用對象方法的軌跡
isa找到class,方法不存在,就通過superclass找父類
7 class調(diào)用類方法的軌跡
isa找meta-class,方法不存在,就通過superclass找父類
這個圖已經(jīng)很經(jīng)典了需要大家細(xì)細(xì)品味。
四 method詳解
4.1.1 method_t
上面我們講過的類的結(jié)構(gòu)中,我們可以看到,一個類的一些方法,協(xié)議,屬性等是存在class_rw_t這個結(jié)構(gòu)體中的。
struct class_rw_t {
// Be warned that Symbolication knows the layout of this structure.
uint32_t flags;
uint32_t version;
const class_ro_t *ro;
method_array_t methods; // 方法列表
property_array_t properties;// 屬性列表
protocol_array_t protocols; // 協(xié)議列表
Class firstSubclass;
Class nextSiblingClass;
char *demangledName;
}
平時用的最多的就是方法調(diào)用了,下面讓我們來深入了解一下method_array_t methods; // 方法列表
先看一下method_array_t是什么
objc-runtime-new.h
class method_array_t :
public list_array_tt<method_t, method_list_t>
{
typedef list_array_tt<method_t, method_list_t> Super;
public:
method_list_t **beginCategoryMethodLists() {
return beginLists();
}
method_list_t **endCategoryMethodLists(Class cls);
method_array_t duplicate() {
return Super::duplicate<method_array_t>();
}
};
我們可以看出這個類結(jié)構(gòu)大概是下面這種結(jié)構(gòu)的
class_rw_t里面的methods是二維數(shù)組,是可讀可寫的,包含了類的初始內(nèi)容、分類的內(nèi)容,那么mathod_t內(nèi)部結(jié)構(gòu)是什么樣的,我們來看一下
struct method_t {
SEL name; // 函數(shù)名
const char *types; //編碼(返回值,參數(shù))
MethodListIMP imp; //函數(shù)地址
struct SortBySELAddress :
public std::binary_function<const method_t&,
const method_t&, bool>
{
bool operator() (const method_t& lhs,
const method_t& rhs)
{ return lhs.name < rhs.name; }
};
};
由此結(jié)構(gòu)我們可以看出method_t就是對一個函數(shù)的具體描述,包含函數(shù)的名稱,函數(shù)的出參,入?yún)?,以及函?shù)具體地址,那么我們就具體來說一下它這幾個變量
MethodListIMP
// Method lists use process-independent signature for compatibility.
using MethodListIMP = IMP __ptrauth_objc_method_list_imp;
#else
using MethodListIMP = IMP;
#endif
objc.h
#if !OBJC_OLD_DISPATCH_PROTOTYPES
typedef void (*IMP)(void /* id, SEL, ... */ );
#else
typedef id _Nullable (*IMP)(id _Nonnull, SEL _Nonnull, ...);
#endif
IMP 代表函數(shù)的具體實(shí)現(xiàn)
SEL
objc.h
typedef struct objc_selector *SEL;
1 SEL代表方法\函數(shù)名,一般叫做選擇器,底層結(jié)構(gòu)跟char *類似
2 可以通過@selector()和sel_registerName()獲得
可以通過sel_getName()和NSStringFromSelector()轉(zhuǎn)成字符串
SEL speaksel = sel_registerName(@"speak");
SEL speaksel2 = @selector(speak);
3 不同類中相同名字的方法,所對應(yīng)的方法選擇器是相同的
這個其實(shí)很好理解,同一個類的不同對象,它的類結(jié)構(gòu)都是一樣的,類結(jié)構(gòu)存儲一份就好了,所以它們方法描述也只有一份,所有這個類的對象共用一份方法描述,只是每個對象傳的出參,入?yún)?,不一樣罷了。
types
const char *types
描述方法參數(shù)類型的字符數(shù)組,types包含了函數(shù)返回值、參數(shù)編碼的字符串
描述方法參數(shù)類型的字符數(shù)組的第一個字符是代表返回值的類型,后面的字符依次代表參數(shù)的類型,因?yàn)镺bjective-C中的函數(shù)會包含兩個隱式參數(shù),也就是方法調(diào)用者和方法名,例如
+(void)method
實(shí)際應(yīng)該是
void method(id self, SEL _cmd)
如果返回值為空,那么函數(shù)的類型編碼的第一個字符是v,如果不為空,則為返回值類型對應(yīng)的編碼,詳細(xì)的可以看下面的編碼對應(yīng)表
因?yàn)榈谝粋€參數(shù)是方法調(diào)用者,它的類型肯定是對象類型,所以類型編碼的第二個字符一定是@
因?yàn)榈诙€參數(shù)是方法名的類型,第三個字符一定是 :
所以這個函數(shù)
void method(id self, SEL _cmd)
的類型編碼為 "v@:"
那么如果要添加的函數(shù)是一個set函數(shù),類型編碼是怎么樣的呢?
-(void)setA:(NSString *)a
同理,set方法實(shí)際的函數(shù)是這樣的:
void setA(id self, SEL _cmd, id a)
與上面無參數(shù)的方法相比,只是多了一個參數(shù),
所以類型編碼為"v@:@",代碼為
class_addMethod(self, @selector(setA:), (IMP)setA,"v@:@");
iOS中提供了一個叫做@encode的指令,可以將具體的類型表示成字符串編碼
由此方法描述的幾個字段我們就講解完了,接下來我們來講解一下方法緩存
4.2方法緩存
在本文第一個圖例,類結(jié)構(gòu)中有一個cache字段
struct objc_class : objc_object {
Class ISA;
Class superclass;
cache_t cache; //方法緩存 formerly cache pointer and vtable
class_data_bits_t bits; //用于獲取具體類的信息 class_rw_t * plus custom rr/alloc flags
}
cache_t cache 就是用來緩存方法的,下面我們來看一下cache_t的內(nèi)部結(jié)構(gòu)
objc-runtime-new.h
struct cache_t {
struct bucket_t *_buckets; // 散列表
mask_t _mask; //散列表的長度-1
mask_t _occupied; //已經(jīng)緩存的方法數(shù)量
}
struct bucket_t {
private:
// IMP-first is better for arm64e ptrauth and no worse for arm64.
// SEL-first is better for armv7* and i386 and x86_64.
#if __arm64__
uintptr_t _imp; //方法實(shí)現(xiàn)地址
SEL _sel; //sel 作為key
#else
SEL _sel;
uintptr_t _imp;
#endif
cache以方法名為 key方法地址為value 緩存。
4.2.1緩存查找實(shí)現(xiàn)
bucket_t * cache_t::find(SEL s, id receiver)
{
assert(s != 0);
bucket_t *b = buckets();
mask_t m = mask();
mask_t begin = cache_hash(s, m);
mask_t i = begin;
do {
if (b[i].sel() == 0 || b[i].sel() == s) {
return &b[i];
}
} while ((i = cache_next(i, m)) != begin);
// hack
Class cls = (Class)((uintptr_t)this - offsetof(objc_class, cache));
cache_t::bad_cache(receiver, (SEL)s, cls);
}
static inline mask_t cache_next(mask_t i, mask_t mask) {
return (i+1) & mask; //找不到就+1再找 這是解決散列沖突最簡單的一種實(shí)現(xiàn)方式
}
當(dāng)去查找一個方法是否在緩存中時,就會那這個方法名通過一個散列函數(shù)計(jì)算出他在buckets數(shù)組中的位置然后在&mask就能拿到一個函數(shù)的實(shí)現(xiàn)地址,
4.2.2緩存添加實(shí)現(xiàn)
static void cache_fill_nolock(Class cls, SEL sel, IMP imp, id receiver)
{
cacheUpdateLock.assertLocked();
// Never cache before +initialize is done
if (!cls->isInitialized()) return;
// Make sure the entry wasn't added to the cache by some other thread
// before we grabbed the cacheUpdateLock.
if (cache_getImp(cls, sel)) return;
cache_t *cache = getCache(cls);
// Use the cache as-is if it is less than 3/4 full
mask_t newOccupied = cache->occupied() + 1;
mask_t capacity = cache->capacity();
if (cache->isConstantEmptyCache()) {
// Cache is read-only. Replace it.
cache->reallocate(capacity, capacity ?: INIT_CACHE_SIZE);
}
else if (newOccupied <= capacity / 4 * 3) {
// Cache is less than 3/4 full. Use it as-is.
}
else {
// Cache is too full. Expand it.
cache->expand(); // 緩存擴(kuò)容,生成一個新的數(shù)組大小是現(xiàn)有數(shù)組2倍,清空當(dāng)前方法緩存
}
// Scan for the first unused slot and insert there.
// There is guaranteed to be an empty slot because the
// minimum size is 4 and we resized at 3/4 full.
bucket_t *bucket = cache->find(sel, receiver);
if (bucket->sel() == 0) cache->incrementOccupied();
bucket->set<Atomic>(sel, imp);
}
void cache_fill(Class cls, SEL sel, IMP imp, id receiver)
{
#if !DEBUG_TASK_THREADS
mutex_locker_t lock(cacheUpdateLock);
cache_fill_nolock(cls, sel, imp, receiver);
#else
_collecting_in_critical();
return;
#endif
}
添加緩存首先要查找緩存是否存在,存在則返回,無則添加,當(dāng)緩存滿了的時候就會擴(kuò)容,擴(kuò)容后并不會把現(xiàn)有緩存copy到新數(shù)組中,而是把現(xiàn)有數(shù)組清空。所以每當(dāng)緩存滿了的時候就會,擴(kuò)容,以前調(diào)用的方法都需要重新調(diào)用時才會緩存。
下面我們總結(jié)一下方法調(diào)用的一個整體過程:
1.首先通過isa指針找到類對象然后去類對象的緩存cache中去查找方法,如果查找到該方法則直接調(diào)用
2.如果在類對象中未找到方法,則去類對象的方法列表尋找方法,如果找到方法,則調(diào)用該方法,同時緩存一份到cache中
3.如果在類對象的cache和方法列表中都沒有找到該方法,則通過類對象的superClass指針到父類的類對象的cache中查找,如果找到,則調(diào)用該方法,同時緩存一份到自身的類對象的cache中
4.如果在自身的類對象的cache中,方法列表中,父類的cache中都沒找到,則到父類的方法列表中查找,如果找到,則調(diào)用該方法,同時緩存一 份到父類類對象的cache中,也緩存一份到自己類對象的cache中.
5.如果在父類的方法列表里也找不到該方法,則重復(fù)執(zhí)行4,層層向上查找,直到找到NSObject,如果NSObject都沒有,那就會結(jié)束 報(bào)錯,其實(shí)當(dāng)一個方法找不到時并不會立即報(bào)錯,它會進(jìn)入消息轉(zhuǎn)發(fā)流程,給你三次機(jī)會去處理,這下來我們就講一下這個流程。
五 方法調(diào)用及消息轉(zhuǎn)發(fā)流程
首先我們先看一段代碼:
#include<stdio.h>
#include "Person.h"
#import <Foundation/Foundation.h>
#import <objc/runtime.h>
int main()
{
Person *person = [[Person alloc] init];
[person speak];
}
把它轉(zhuǎn)成c++代碼:xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m
int main()
{
Person *person = ((Person *(*)(id, SEL))(void *)objc_msgSend)((id)((Person *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("Person"), sel_registerName("alloc")), sel_registerName("init"));
((void (*)(id, SEL))(void *)objc_msgSend)((id)person, sel_registerName("speak"));
}
我們發(fā)現(xiàn)objc_msgSend參與了對象創(chuàng)建以及方法調(diào)用,OC中的方法調(diào)用,其實(shí)都是轉(zhuǎn)換為objc_msgSend函數(shù)的調(diào)用。OC的方法調(diào)用就是利用objc_msgSend這種消息機(jī)制,給方法調(diào)用者發(fā)送消息,它有兩個參與內(nèi)容:
1 消息接受者 (receiver):person
2 消息名稱 :init ,speak
objc_msgSend的執(zhí)行流程可以分為3大階段
1 消息發(fā)送
2 動態(tài)方法解析
3 消息轉(zhuǎn)發(fā)
下面我們從源碼來分析objc_msgSend執(zhí)行流程,先看一下大致的執(zhí)行流程,然后我們在分析每個階段的流程
objc-msg-arm64.s
ENTRY _objc_msgSend // 1 進(jìn)入消息流程
b.le LNilOrTagged
CacheLookup NORMAL //查看方法緩存
.macro CacheLookup
.macro CheckMiss
STATIC_ENTRY __objc_msgSend_uncached
.macro MethodTableLookup。 //查找方法
__class_lookupMethodAndLoadCache3
objc-runtime-new.mm
_class_lookupMethodAndLoadCache3
lookUpImpOrForward
getMethodNoSuper_nolock、search_method_list、log_and_fill_cache
cache_getImp、log_and_fill_cache、getMethodNoSuper_nolock、log_and_fill_cache
_class_resolveInstanceMethod。 // 2 動態(tài)方法解析
_objc_msgForward_impcache
objc-msg-arm64.s
STATIC_ENTRY __objc_msgForward_impcache
ENTRY __objc_msgForward //3 進(jìn)入消息轉(zhuǎn)發(fā)
Core Foundation
__forwarding__(不開源)
5.1.1 消息發(fā)送
objc_msgSend 的源碼實(shí)現(xiàn)
ENTRY _objc_msgSend
UNWIND _objc_msgSend, NoFrame
cmp p0, #0 //1 消息接收者,receiver nil check and tagged pointer check
#if SUPPORT_TAGGED_POINTERS
b.le LNilOrTagged // 小于等于零跳轉(zhuǎn)到 LNilOrTagged (MSB tagged pointer looks negative)
#else
b.eq LReturnZero
#endif
ldr p13, [x0] // p13 = isa
GetClassFromIsa_p16 p13 // p16 = class
LGetIsaDone:
CacheLookup NORMAL // 2 查找緩存,具體實(shí)現(xiàn)看 CacheLookup calls imp or objc_msgSend_uncached
#if SUPPORT_TAGGED_POINTERS
LNilOrTagged:
b.eq LReturnZero // 為空跳轉(zhuǎn)到 LReturnZero nil check
// tagged
adrp x10, _objc_debug_taggedpointer_classes@PAGE
add x10, x10, _objc_debug_taggedpointer_classes@PAGEOFF
ubfx x11, x0, #60, #4
ldr x16, [x10, x11, LSL #3]
adrp x10, _OBJC_CLASS_$___NSUnrecognizedTaggedPointer@PAGE
add x10, x10, _OBJC_CLASS_$___NSUnrecognizedTaggedPointer@PAGEOFF
cmp x10, x16
b.ne LGetIsaDone
// ext tagged
adrp x10, _objc_debug_taggedpointer_ext_classes@PAGE
add x10, x10, _objc_debug_taggedpointer_ext_classes@PAGEOFF
ubfx x11, x0, #52, #8
ldr x16, [x10, x11, LSL #3]
b LGetIsaDone
// SUPPORT_TAGGED_POINTERS
#endif
LReturnZero:
// x0 is already zero
mov x1, #0
movi d0, #0
movi d1, #0
movi d2, #0
movi d3, #0
ret //return 退出程序
END_ENTRY _objc_msgSend
.macro CacheLookup
// p1 = SEL, p16 = isa
ldp p10, p11, [x16, #CACHE] // 3 查找方法緩存 p10 = buckets, p11 = occupied|mask
#if !__LP64__
and w11, w11, 0xffff // p11 = mask
#endif
and w12, w1, w11 // x12 = _cmd & mask
add p12, p10, p12, LSL #(1+PTRSHIFT)
// p12 = buckets + ((_cmd & mask) << (1+PTRSHIFT))
ldp p17, p9, [x12] // {imp, sel} = *bucket
1: cmp p9, p1 // if (bucket->sel != _cmd)
b.ne 2f // scan more
CacheHit $0 // 4 緩存查找到就返回函數(shù)地址 call or return imp
2: // not hit: p12 = not-hit bucket // 沒有查找到緩存
CheckMiss $0 // 5 沒找到緩存CheckMiss miss if bucket->sel == 0
cmp p12, p10 // wrap if bucket == buckets
b.eq 3f
ldp p17, p9, [x12, #-BUCKET_SIZE]! // {imp, sel} = *--bucket
b 1b // loop
3: // wrap: p12 = first bucket, w11 = mask
add p12, p12, w11, UXTW #(1+PTRSHIFT)
// p12 = buckets + (mask << 1+PTRSHIFT)
// Clone scanning loop to miss instead of hang when cache is corrupt.
// The slow path may detect any corruption and halt later.
ldp p17, p9, [x12] // {imp, sel} = *bucket
1: cmp p9, p1 // if (bucket->sel != _cmd)
b.ne 2f // scan more
CacheHit $0 // call or return imp
2: // not hit: p12 = not-hit bucket
CheckMiss $0 // 6 沒有找到則調(diào)用CheckMiss miss if bucket->sel == 0
cmp p12, p10 // wrap if bucket == buckets
b.eq 3f
ldp p17, p9, [x12, #-BUCKET_SIZE]! // {imp, sel} = *--bucket
b 1b // loop
3: // double wrap
JumpMiss $0
.endmacro
.macro CheckMiss
// miss if bucket->sel == 0
.if $0 == GETIMP
cbz p9, LGetImpMiss
.elseif $0 == NORMAL
cbz p9, __objc_msgSend_uncached //7 調(diào)用__objc_msgSend_uncached
.elseif $0 == LOOKUP
cbz p9, __objc_msgLookup_uncached
.else
.abort oops
.endif
.endmacro
STATIC_ENTRY __objc_msgSend_uncached
UNWIND __objc_msgSend_uncached, FrameWithNoSaves
// THIS IS NOT A CALLABLE C FUNCTION
// Out-of-band p16 is the class to search
MethodTableLookup // 8 查找方法表
TailCallFunctionPointer x17
END_ENTRY __objc_msgSend_uncached
STATIC_ENTRY __objc_msgLookup_uncached
UNWIND __objc_msgLookup_uncached, FrameWithNoSaves
// THIS IS NOT A CALLABLE C FUNCTION
// Out-of-band p16 is the class to search
MethodTableLookup
ret
END_ENTRY __objc_msgLookup_uncached
STATIC_ENTRY _cache_getImp
GetClassFromIsa_p16 p0
CacheLookup GETIMP
.macro MethodTableLookup
// push frame
SignLR
stp fp, lr, [sp, #-16]!
mov fp, sp
// save parameter registers: x0..x8, q0..q7
sub sp, sp, #(10*8 + 8*16)
stp q0, q1, [sp, #(0*16)]
stp q2, q3, [sp, #(2*16)]
stp q4, q5, [sp, #(4*16)]
stp q6, q7, [sp, #(6*16)]
stp x0, x1, [sp, #(8*16+0*8)]
stp x2, x3, [sp, #(8*16+2*8)]
stp x4, x5, [sp, #(8*16+4*8)]
stp x6, x7, [sp, #(8*16+6*8)]
str x8, [sp, #(8*16+8*8)]
// receiver and selector already in x0 and x1
mov x2, x16
bl __class_lookupMethodAndLoadCache3 // 9 跳轉(zhuǎn)調(diào)用__class_lookupMethodAndLoadCache3
// IMP in x0
mov x17, x0
// restore registers and return
ldp q0, q1, [sp, #(0*16)]
ldp q2, q3, [sp, #(2*16)]
ldp q4, q5, [sp, #(4*16)]
ldp q6, q7, [sp, #(6*16)]
ldp x0, x1, [sp, #(8*16+0*8)]
ldp x2, x3, [sp, #(8*16+2*8)]
ldp x4, x5, [sp, #(8*16+4*8)]
ldp x6, x7, [sp, #(8*16+6*8)]
ldr x8, [sp, #(8*16+8*8)]
mov sp, fp
ldp fp, lr, [sp], #16
AuthenticateLR
.endmacro
10 class_lookupMethodAndLoadCache3 定義
IMP _class_lookupMethodAndLoadCache3(id obj, SEL sel, Class cls)
{
return lookUpImpOrForward(cls, sel, obj,
YES/*initialize*/, NO/*cache*/, YES/*resolver*/);//cls 類對象, sel 類名, obj 消息接收者,
}
IMP lookUpImpOrForward(Class cls, SEL sel, id inst,
bool initialize, bool cache, bool resolver)
{
IMP imp = nil;
bool triedResolver = NO;
runtimeLock.assertUnlocked();
// Optimistic cache lookup
if (cache) {
imp = cache_getImp(cls, sel);
if (imp) return imp;
}
// runtimeLock is held during isRealized and isInitialized checking
// to prevent races against concurrent realization.
// runtimeLock is held during method search to make
// method-lookup + cache-fill atomic with respect to method addition.
// Otherwise, a category could be added but ignored indefinitely because
// the cache was re-filled with the old value after the cache flush on
// behalf of the category.
runtimeLock.lock();
checkIsKnownClass(cls);
if (!cls->isRealized()) {
cls = realizeClassMaybeSwiftAndLeaveLocked(cls, runtimeLock);
// runtimeLock may have been dropped but is now locked again
}
if (initialize && !cls->isInitialized()) {
cls = initializeAndLeaveLocked(cls, inst, runtimeLock);
// runtimeLock may have been dropped but is now locked again
// If sel == initialize, class_initialize will send +initialize and
// then the messenger will send +initialize again after this
// procedure finishes. Of course, if this is not being called
// from the messenger then it won't happen. 2778172
}
retry:
runtimeLock.assertLocked();
// Try this class's cache.
imp = cache_getImp(cls, sel);
if (imp) goto done;
// Try this class's method lists. 查找類對象的方法列表
{
Method meth = getMethodNoSuper_nolock(cls, sel);
if (meth) {
log_and_fill_cache(cls, meth->imp, sel, inst, cls);
imp = meth->imp;
goto done;
}
}
// Try superclass caches and method lists. 查找父類對象的方法列表
{
unsigned attempts = unreasonableClassCount();
for (Class curClass = cls->superclass;
curClass != nil;
curClass = curClass->superclass)
{
// Halt if there is a cycle in the superclass chain.
if (--attempts == 0) {
_objc_fatal("Memory corruption in class list.");
}
// Superclass cache.
imp = cache_getImp(curClass, sel);
if (imp) {
if (imp != (IMP)_objc_msgForward_impcache) {
// Found the method in a superclass. Cache it in this class.
log_and_fill_cache(cls, imp, sel, inst, curClass); //cls 將父類方法填充到類對像的緩存中
goto done; //找到了跳轉(zhuǎn) done
}
else {
// Found a forward:: entry in a superclass.
// Stop searching, but don't cache yet; call method
// resolver for this class first.
break;
}
}
// Superclass method list.
Method meth = getMethodNoSuper_nolock(curClass, sel); //查找父類的父類的方法列表
if (meth) {
log_and_fill_cache(cls, meth->imp, sel, inst, curClass); //添加緩存到當(dāng)前類對象
imp = meth->imp;
goto done; //找到了直接跳轉(zhuǎn)done
}
}
}
// No implementation found. Try method resolver once.
if (resolver && !triedResolver) {
runtimeLock.unlock();
resolveMethod(cls, sel, inst); //進(jìn)入消息轉(zhuǎn)發(fā)流程
runtimeLock.lock();
// Don't cache the result; we don't hold the lock so it may have
// changed already. Re-do the search from scratch instead.
triedResolver = YES;
goto retry;
}
// No implementation found, and method resolver didn't help.
// Use forwarding.
imp = (IMP)_objc_msgForward_impcache;
cache_fill(cls, sel, imp, inst);
done:
runtimeLock.unlock();
return imp; // 返回函數(shù)地址
}
objc_msgSend由于調(diào)用的頻次非常頻繁,為了提高執(zhí)行效率,所以使用了匯編來實(shí)現(xiàn),它的大致實(shí)現(xiàn)流程是
1.首先通過isa指針找到類對象然后去類對象的緩存cache中去查找方法,如果查找到該方法則直接調(diào)用
2.如果在類對象中未找到方法,則去類對象的方法列表尋找方法,如果找到方法,則調(diào)用該方法,同時緩存一份到cache中
3.如果在類對象的cache和方法列表中都沒有找到該方法,則通過類對象的superClass指針到父類的類對象的cache中查找,如果找到,則調(diào)用該方法,同時緩存一份到自身的類對象的cache中
4.如果在自身的類對象的cache中,方法列表中,父類的cache中都沒找到,則到父類的方法列表中查找,如果找到,則調(diào)用該方法,同時緩存一 份到父類類對象的cache中,也緩存一份到自己類對象的cache中.
5.如果在父類的方法列表里也找不到該方法,則重復(fù)執(zhí)行4,層層向上查找,直到找到NSObject,如果NSObject都沒有,那就會進(jìn)入動態(tài)方法解析。執(zhí)行流程圖如下:
講完了objc_msgSend 消息發(fā)送流程,接下來我們講下 動態(tài)方法解析流程。
5.1.2 動態(tài)方法解析
先看一下底層實(shí)現(xiàn)源碼:
我們截取了上面lookUpImpOrForward中的一段代碼
if (resolver && !triedResolver) {
runtimeLock.unlock();
resolveMethod(cls, sel, inst);
runtimeLock.lock();
// Don't cache the result; we don't hold the lock so it may have
// changed already. Re-do the search from scratch instead.
triedResolver = YES;
goto retry; //重新走查找方法流程
}
static void resolveMethod(Class cls, SEL sel, id inst)
{
runtimeLock.assertUnlocked();
assert(cls->isRealized());
if (! cls->isMetaClass()) { //如果不是元類對象
// try [cls resolveInstanceMethod:sel]
resolveInstanceMethod(cls, sel, inst); //調(diào)用resolveInstanceMethod
}
else {
// try [nonMetaClass resolveClassMethod:sel]
// and [cls resolveInstanceMethod:sel]
resolveClassMethod(cls, sel, inst); //否則調(diào)用resolveClassMethod
if (!lookUpImpOrNil(cls, sel, inst,
NO/*initialize*/, YES/*cache*/, NO/*resolver*/))
{
resolveInstanceMethod(cls, sel, inst);
}
}
}
static void resolveInstanceMethod(Class cls, SEL sel, id inst)
{
runtimeLock.assertUnlocked();
assert(cls->isRealized());
if (! lookUpImpOrNil(cls->ISA(), SEL_resolveInstanceMethod, cls,
NO/*initialize*/, YES/*cache*/, NO/*resolver*/))
{
// Resolver not implemented.
return;
}
BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend;
bool resolved = msg(cls, SEL_resolveInstanceMethod, sel); //那當(dāng)前類對象調(diào)用resolveInstanceMethod
// Cache the result (good or bad) so the resolver doesn't fire next time.
// +resolveInstanceMethod adds to self a.k.a. cls
IMP imp = lookUpImpOrNil(cls, sel, inst,
NO/*initialize*/, YES/*cache*/, NO/*resolver*/);
if (resolved && PrintResolving) {
if (imp) {
_objc_inform("RESOLVE: method %c[%s %s] "
"dynamically resolved to %p",
cls->isMetaClass() ? '+' : '-',
cls->nameForLogging(), sel_getName(sel), imp);
}
else {
// Method resolver didn't add anything?
_objc_inform("RESOLVE: +[%s resolveInstanceMethod:%s] returned YES"
", but no new implementation of %c[%s %s] was found",
cls->nameForLogging(), sel_getName(sel),
cls->isMetaClass() ? '+' : '-',
cls->nameForLogging(), sel_getName(sel));
}
}
}
static void resolveClassMethod(Class cls, SEL sel, id inst)
{
runtimeLock.assertUnlocked();
assert(cls->isRealized());
assert(cls->isMetaClass());
if (! lookUpImpOrNil(cls, SEL_resolveClassMethod, inst,
NO/*initialize*/, YES/*cache*/, NO/*resolver*/))
{
// Resolver not implemented.
return;
}
Class nonmeta;
{
mutex_locker_t lock(runtimeLock);
nonmeta = getMaybeUnrealizedNonMetaClass(cls, inst);
// +initialize path should have realized nonmeta already
if (!nonmeta->isRealized()) {
_objc_fatal("nonmeta class %s (%p) unexpectedly not realized",
nonmeta->nameForLogging(), nonmeta);
}
}
BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend;
bool resolved = msg(nonmeta, SEL_resolveClassMethod, sel);//拿當(dāng)前元類對象調(diào)用resolveClassMethod
// Cache the result (good or bad) so the resolver doesn't fire next time.
// +resolveClassMethod adds to self->ISA() a.k.a. cls
IMP imp = lookUpImpOrNil(cls, sel, inst,
NO/*initialize*/, YES/*cache*/, NO/*resolver*/);
if (resolved && PrintResolving) {
if (imp) {
_objc_inform("RESOLVE: method %c[%s %s] "
"dynamically resolved to %p",
cls->isMetaClass() ? '+' : '-',
cls->nameForLogging(), sel_getName(sel), imp);
}
else {
// Method resolver didn't add anything?
_objc_inform("RESOLVE: +[%s resolveClassMethod:%s] returned YES"
", but no new implementation of %c[%s %s] was found",
cls->nameForLogging(), sel_getName(sel),
cls->isMetaClass() ? '+' : '-',
cls->nameForLogging(), sel_getName(sel));
}
}
}
1 前面的消息發(fā)送沒有找到要調(diào)用的方法就會調(diào)用 resolveMethod(cls, sel, inst)
2 resolveMethod(cls, sel, inst)內(nèi)部做了議程判斷
* 如果是元類對象則調(diào)用resolveClassMethod(cls, sel, inst);
*否則調(diào)用:resolveInstanceMethod(cls, sel, inst);
3 resolveClassMethod(cls, sel, inst); 內(nèi)部則是拿當(dāng)前的元類對象調(diào)用resolveClassMethod方法,所以我們要進(jìn)行消息處理則要在類里實(shí)現(xiàn)這個方法
+ (BOOL)resolveInstanceMethod:(SEL)sel{
if(sel == @selector(eat)){
Method method = class_getInstanceMethod(self, @selector(other));
class_addMethod(self, sel, method_getImplementation(method), method_getTypeEncoding(method));
return YES;
}
return [super resolveInstanceMethod:<#sel#>];
}
4 resolveInstanceMethod(cls, sel, inst) 內(nèi)部這是拿當(dāng)前的類對象調(diào)用resolveInstanceMethod方法同理我們要進(jìn)行消息處理則要在類里實(shí)現(xiàn)這個方法
+ (BOOL)resolveClassMethod:(SEL)sel{
if(sel == @selector(eat)){
Method method = class_getInstanceMethod(self, @selector(other));
class_addMethod(self, sel, method_getImplementation(method), method_getTypeEncoding(method));
return YES;
}
return [super resolveClassMethod:<#sel#>];
}
最后看一下它的執(zhí)行流程圖:
動態(tài)解析過后,會重新走“消息發(fā)送”的流程
“從receiverClass的cache中查找方法”這一步開始執(zhí)行,如果開發(fā)者詳細(xì)處理的兩個方法都沒有實(shí)現(xiàn),會怎么辦呢,那么他就會走接下來的消息轉(zhuǎn)發(fā)流程。
5.1.3 消息轉(zhuǎn)發(fā)
lookUpImpOrForward接近最后兩行代碼
imp = (IMP)_objc_msgForward_impcache; //執(zhí)行__objc_msgForward_impcache
cache_fill(cls, sel, imp, inst);
objc-msg-arm64.s
STATIC_ENTRY __objc_msgForward_impcache
// No stret specialization.
b __objc_msgForward
END_ENTRY __objc_msgForward_impcache
ENTRY __objc_msgForward
adrp x17, __objc_forward_handler@PAGE
ldr p17, [x17, __objc_forward_handler@PAGEOFF] //執(zhí)行__objc_forward_handler
TailCallFunctionPointer x17
END_ENTRY __objc_msgForward
STATIC_ENTRY __objc_msgForward_impcache
// No stret specialization.
b __objc_msgForward //執(zhí)行__objc_msgForward
END_ENTRY __objc_msgForward_impcache
//為代碼
int __forwarding__(void *frameStackPointer, int isStret) {
id receiver = *(id *)frameStackPointer;
SEL sel = *(SEL *)(frameStackPointer + 8);
const char *selName = sel_getName(sel);
Class receiverClass = object_getClass(receiver);
// 調(diào)用 forwardingTargetForSelector:
if (class_respondsToSelector(receiverClass, @selector(forwardingTargetForSelector:))) {
id forwardingTarget = [receiver forwardingTargetForSelector:sel];
if (forwardingTarget && forwardingTarget != receiver) {
if (isStret == 1) {
int ret;
objc_msgSend_stret(&ret,forwardingTarget, sel, ...);
return ret;
}
return objc_msgSend(forwardingTarget, sel, ...);
}
}
// 僵尸對象
const char *className = class_getName(receiverClass);
const char *zombiePrefix = "_NSZombie_";
size_t prefixLen = strlen(zombiePrefix); // 0xa
if (strncmp(className, zombiePrefix, prefixLen) == 0) {
CFLog(kCFLogLevelError,
@"*** -[%s %s]: message sent to deallocated instance %p",
className + prefixLen,
selName,
receiver);
<breakpoint-interrupt>
}
// 調(diào)用 methodSignatureForSelector 獲取方法簽名后再調(diào)用 forwardInvocation
if (class_respondsToSelector(receiverClass, @selector(methodSignatureForSelector:))) {
NSMethodSignature *methodSignature = [receiver methodSignatureForSelector:sel];
if (methodSignature) {
BOOL signatureIsStret = [methodSignature _frameDescriptor]->returnArgInfo.flags.isStruct;
if (signatureIsStret != isStret) {
CFLog(kCFLogLevelWarning ,
@"*** 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.",
selName,
signatureIsStret ? "" : not,
isStret ? "" : not);
}
if (class_respondsToSelector(receiverClass, @selector(forwardInvocation:))) {
NSInvocation *invocation = [NSInvocation _invocationWithMethodSignature:methodSignature frame:frameStackPointer];
[receiver forwardInvocation:invocation];
void *returnValue = NULL;
[invocation getReturnValue:&value];
return returnValue;
} else {
CFLog(kCFLogLevelWarning ,
@"*** NSForwarding: warning: object %p of class '%s' does not implement forwardInvocation: -- dropping message",
receiver,
className);
return 0;
}
}
}
SEL *registeredSel = sel_getUid(selName);
// selector 是否已經(jīng)在 Runtime 注冊過
if (sel != registeredSel) {
CFLog(kCFLogLevelWarning ,
@"*** NSForwarding: warning: selector (%p) for message '%s' does not match selector known to Objective C runtime (%p)-- abort",
sel,
selName,
registeredSel);
} // doesNotRecognizeSelector
else if (class_respondsToSelector(receiverClass,@selector(doesNotRecognizeSelector:))) {
[receiver doesNotRecognizeSelector:sel];
}
else {
CFLog(kCFLogLevelWarning ,
@"*** NSForwarding: warning: object %p of class '%s' does not implement doesNotRecognizeSelector: -- abort",
receiver,
className);
}
// The point of no return.
kill(getpid(), 9);
}
最終我們發(fā)現(xiàn)它會調(diào)用兩個方法:
1 forwardingTargetForSelector: 返回yes 執(zhí)行objc_msgSend(返回值, SEL )返回nil這執(zhí)行第二步
2 methodSignatureForSelector //方法簽名如果這個方法有返回值的話這會執(zhí)行下一步
3 (void)forwardInvocation:(NSInvocation *)anInvocation //NSInvocation封裝了一個方法調(diào)用,包括,調(diào)用者,方法名,方法參數(shù),只要能進(jìn)入到這個方法,它里面的實(shí)現(xiàn)可以隨便寫,哪怕打印一句話也行或者任何處理也沒有也行。
消息轉(zhuǎn)發(fā)調(diào)用流程圖如下:
1 蘋果的文檔里,講述了這一個消息轉(zhuǎn)發(fā)的出發(fā)點(diǎn),其實(shí)是為了實(shí)現(xiàn)類似C多繼承的功能。我們知道,在C中如果一個類想要具有多個類的功能,是可以直接繼承多個類的。而Objective-C是單繼承,如果想實(shí)現(xiàn)類似的功能,就用消息轉(zhuǎn)發(fā),將消息轉(zhuǎn)發(fā)給有能力處理的類。蘋果是這樣描述他們的思想的:C的多繼承,是加法,在多繼承的同時,其實(shí)也增加了很多不需要的功能,而蘋果通過消息轉(zhuǎn)發(fā),實(shí)現(xiàn)了減法的思想,只留有用的方法,而不去增加過多內(nèi)容。
2 開發(fā)者可以在forwardInvocation:方法中自定義任何邏輯
3 以上方法都有對象方法、類方法2個版本(前面可以是加號+,也可以是減號-)
- (id)forwardingTargetForSelector:(SEL)aSelector{
if(aSelector == @selector(eat)) {
return [[Person alloc] init];//將eat方法j轉(zhuǎn)交給Person對象執(zhí)行,person要實(shí)現(xiàn)eat方法
}
return [super forwardingTargetForSelector:aSelector];
}
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
if(aSelector == @selector(eat)) {
return [NSMethodSignature signatureWithObjCTypes:"v@:"];
}
return [super methodSignatureForSelector:aSelector];
}
- (void)forwardInvocation:(NSInvocation *)anInvocation{
}
+ (id)forwardingTargetForSelector:(SEL)aSelector{
if(aSelector == @selector(eat)) {
return [[Person alloc] init];//將eat方法j轉(zhuǎn)交給Person對象執(zhí)行,person要實(shí)現(xiàn)eat方法
}
return [super forwardingTargetForSelector:aSelector];
}
+ (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
if(aSelector == @selector(eat)) {
NSMethodSignature *signature = [[[Person alloc] init] methodSignatureForSelector:@selector(eat)];
return signature;
}
return [super methodSignatureForSelector:aSelector];
}
+ (void)forwardInvocation:(NSInvocation *)anInvocation{
anInvocation.target;
anInvocation.selector;
[anInvocation getArgument:NULL atIndex:2];// 第三個參數(shù)才是你的方法參數(shù),前兩個是方法的默認(rèn)參數(shù)
[anInvocation invoke];
}
六 runtime常用api
6.1類
- 動態(tài)創(chuàng)建一個類(參數(shù):父類,類名,額外的內(nèi)存空間)
Class objc_allocateClassPair(Class superclass, const char *name, size_t extraBytes)
*注冊一個類(要在類注冊之前添加成員變量)
void objc_registerClassPair(Class cls)
*銷毀一個類
void objc_disposeClassPair(Class cls)
*獲取isa指向的Class
Class object_getClass(id obj)
*設(shè)置isa指向的Class
Class object_setClass(id obj, Class cls)
*判斷一個OC對象是否為Class
BOOL object_isClass(id obj)
*判斷一個Class是否為元類
BOOL class_isMetaClass(Class cls)
*獲取父類
Class class_getSuperclass(Class cls)
6.2成員變量
*獲取一個實(shí)例變量信息
Ivar class_getInstanceVariable(Class cls, const char *name)
*拷貝實(shí)例變量列表(最后需要調(diào)用free釋放)
Ivar *class_copyIvarList(Class cls, unsigned int *outCount)
*設(shè)置和獲取成員變量的值
void object_setIvar(id obj, Ivar ivar, id value)
id object_getIvar(id obj, Ivar ivar)
*動態(tài)添加成員變量(已經(jīng)注冊的類是不能動態(tài)添加成員變量的)
BOOL class_addIvar(Class cls, const char * name, size_t size, uint8_t alignment, const char * types)
*獲取成員變量的相關(guān)信息
const char *ivar_getName(Ivar v)
const char *ivar_getTypeEncoding(Ivar v)
6.3屬性
*獲取一個屬性
objc_property_t class_getProperty(Class cls, const char *name)
*拷貝屬性列表(最后需要調(diào)用free釋放)
objc_property_t *class_copyPropertyList(Class cls, unsigned int *outCount)
*動態(tài)添加屬性
BOOL class_addProperty(Class cls, const char *name, const objc_property_attribute_t *attributes,
unsigned int attributeCount)
*動態(tài)替換屬性
void class_replaceProperty(Class cls, const char *name, const objc_property_attribute_t *attributes,
unsigned int attributeCount)
*獲取屬性的一些信息
const char *property_getName(objc_property_t property)
const char *property_getAttributes(objc_property_t property)
6.4方法
*獲得一個實(shí)例方法、類方法
Method class_getInstanceMethod(Class cls, SEL name)
Method class_getClassMethod(Class cls, SEL name)
*方法實(shí)現(xiàn)相關(guān)操作
IMP class_getMethodImplementation(Class cls, SEL name)
IMP method_setImplementation(Method m, IMP imp)
void method_exchangeImplementations(Method m1, Method m2)
*拷貝方法列表(最后需要調(diào)用free釋放)
Method *class_copyMethodList(Class cls, unsigned int *outCount)
*動態(tài)添加方法
BOOL class_addMethod(Class cls, SEL name, IMP imp, const char *types)
*動態(tài)替換方法
IMP class_replaceMethod(Class cls, SEL name, IMP imp, const char *types)
*獲取方法的相關(guān)信息(帶有copy的需要調(diào)用free去釋放)
SEL method_getName(Method m)
IMP method_getImplementation(Method m)
const char *method_getTypeEncoding(Method m)
unsigned int method_getNumberOfArguments(Method m)
char *method_copyReturnType(Method m)
char *method_copyArgumentType(Method m, unsigned int index)
*選擇器相關(guān)
const char *sel_getName(SEL sel)
SEL sel_registerName(const char *str)
*用block作為方法實(shí)現(xiàn)
IMP imp_implementationWithBlock(id block)
id imp_getBlock(IMP anImp)
BOOL imp_removeBlock(IMP anImp)
到此我們整個runtime基本知識都以經(jīng)講完了,下面我們來看一下runtime在實(shí)際開發(fā)中的應(yīng)用
七 runtime開發(fā)中具體應(yīng)用
runtime的應(yīng)用,主要有幾種:
- 1 AOP,切面編程,做打點(diǎn)
- 2 method swizzling,黑魔法做崩潰等的保護(hù)
- 3 利用關(guān)聯(lián)對象(AssociatedObject)給分類添加屬性
- 4 遍歷類的所有成員變量(修改textfield的占位文字顏色、字典轉(zhuǎn)模型、自動歸檔解檔)
- 5 交換方法實(shí)現(xiàn)(交換系統(tǒng)的方法)
我們下面舉兩個例子來講一下
7.1 Method Swizzling
通過修改一個已存在類的方法, 來實(shí)現(xiàn)方法替換是比較常用的runtime技巧.
如在UIView的load方法中:
+ (void)load {
Method origin = class_getInstanceMethod([UIView class], @selector(touchesBegan:withEvent:));
Method custom = class_getInstanceMethod([UIView class], @selector(custom_touchesBegan:withEvent:));
method_exchangeImplementations(origin, custom);
}
- (void)custom_touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
// TODO
}
這樣, 想要觸發(fā)UIView的touchesBegan:withEvent:方法時, 實(shí)際調(diào)用的卻是自定義的custom_touchesBegan:withEvent:方法.
另外, 關(guān)于runtime method swizzling的一個使用場景, 請參考博客:
iOS — 使用runtime解決3D Touch導(dǎo)致UIImagePicker崩潰的問題
7.2 關(guān)聯(lián)對象
Objective-C中的Category無法向既有的類添加屬性, 因此可以使用runtime的關(guān)聯(lián)對象(associated objects)來實(shí)現(xiàn).
如將一個字符串關(guān)聯(lián)到一個數(shù)組:
static char overviewKey;
NSArray *array = [[NSArray alloc] initWithObjects:@"1", @"2", @"3", nil];
// 為了演示的目的,使用initWithFormat:來確保字符串可以被銷毀
NSString *overview = [[NSString alloc] initWithFormat:@"@", @"first three numbers"];
objc_setAssociatedObject(array, &overviewKey, overview, OBJC_ASSOCIATION_RETAIN);
這樣, 當(dāng)overview被手動釋放時, 卻不會被銷毀. 因?yàn)殛P(guān)聯(lián)策略指明了數(shù)組array要保有相關(guān)聯(lián)的對象.
而array也釋放時, overview才會被銷毀.
設(shè)置關(guān)聯(lián)對象, 指定被關(guān)聯(lián)對象array, 關(guān)聯(lián)關(guān)鍵字overviewKey, 關(guān)聯(lián)對象overview即可:
objc_setAssociatedObject(array, &overviewKey, overview, OBJC_ASSOCIATION_RETAIN);
獲取關(guān)聯(lián)對象, 需要傳遞被關(guān)聯(lián)對象array和關(guān)聯(lián)關(guān)鍵字overviewKey:
NSString *associatedObject = (NSString *)objc_getAssociatedObject(array, &overviewKey);
斷開關(guān)聯(lián), 只需要設(shè)置關(guān)聯(lián)對象為nil即可, 關(guān)聯(lián)策略就無所謂了.
objc_setAssociatedObject(array, &overviewKey, nil, OBJC_ASSOCIATION_ASSIGN);
使用objc_removeAssociatedObjects可斷開所有關(guān)聯(lián), 把對象恢復(fù)至原始狀態(tài).