1. 介紹下runtime的內(nèi)存模型(isa、對(duì)象、類、metaclass、結(jié)構(gòu)體的存儲(chǔ)信息等)
首先,關(guān)于NSObject,objc_class 和 objc_object 三者之間的關(guān)系,我們可以用下面的圖來(lái)更清晰的了解:

一 NSObject
NSObject是OC 中的基類,除了NSProxy其他都繼承自NSObject
@interface NSObject <NSObject> {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wobjc-interface-ivars"
Class isa OBJC_ISA_AVAILABILITY;
#pragma clang diagnostic pop
}
二 對(duì)象結(jié)構(gòu)體 objc_object
在運(yùn)行時(shí),類的對(duì)象被定義為objc_object類型,就是對(duì)象結(jié)構(gòu)體,在OC 中每一個(gè)對(duì)象都是一個(gè)結(jié)構(gòu)體,結(jié)構(gòu)體都包含了一個(gè)isa成員變量。根據(jù)isa的定義可以知道,類型為isa_t類型的
struct objc_object {
private:
isa_t isa;
public:
// ISA() assumes this is NOT a tagged pointer object
Class ISA();
// getIsa() allows this to be a tagged pointer object
Class getIsa();
// 省略其余方法
...
}
isa_t 的定義是什么?
是一個(gè)union的結(jié)構(gòu)對(duì)象,類似于C++結(jié)構(gòu)體,其內(nèi)部可以定義為成員變量和函數(shù),是一個(gè)聯(lián)合類型,其中的 isa_t、cls、 bits 還有結(jié)構(gòu)體共用同一塊地址空間。
三 類結(jié)構(gòu)體 objc_class
類也是一個(gè)對(duì)象,類的結(jié)構(gòu)體objc_class 是繼承自objc_object的,具備對(duì)象的所有特征
iOS中不管類對(duì)象還是元類對(duì)象類型都是Class,而Class的結(jié)構(gòu)則是objc_class結(jié)構(gòu)體
typedef struct objc_class *Class;
typedef struct objc_object *id;
//注意,有些人看到的objc_class結(jié)構(gòu)體定義不一樣,有OBJC2_UNAVAILABLE 的注釋,在OC 2.0中,
//這種關(guān)于之前objc_class的定義已經(jīng)廢棄掉了,可以在 objc-runtime-new.h 看OC 2.0之后的,如下:
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
class_rw_t *data() const {
return bits.data();
}
void setData(class_rw_t *newData) {
bits.setData(newData);
}
...//此處省略
}
struct class_data_bits_t {
friend objc_class;
// Values are the FAST_ flags above.
uintptr_t bits;
private:
...
public:
class_rw_t* data() {
return (class_rw_t *)(bits & FAST_DATA_MASK);
}
void setData(class_rw_t *newData)
{
assert(!data() || (newData->flags & (RW_REALIZING | RW_FUTURE)));
// Set during realization or construction only. No locking needed.
// Use a store-release fence because there may be concurrent
// readers of data and data's contents.
uintptr_t newBits = (bits & ~FAST_DATA_MASK) | (uintptr_t)newData;
atomic_thread_fence(memory_order_release);
bits = newBits;
}
...
cache_t cache
#if __LP64__
typedef uint32_t mask_t; // x86_64 & arm64 asm are less efficient with 16-bits
#else
typedef uint16_t mask_t;
#endif
struct cache_t {
struct bucket_t *_buckets;
mask_t _mask;
mask_t _occupied;
public:
struct bucket_t *buckets();
mask_t mask();
mask_t occupied();
void incrementOccupied();
void setBucketsAndMask(struct bucket_t *newBuckets, mask_t newMask);
void initializeToEmpty();
mask_t capacity();
bool isConstantEmptyCache();
bool canBeFreed();
static size_t bytesForCapacity(uint32_t cap);
static struct bucket_t * endMarker(struct bucket_t *b, uint32_t cap);
void expand();
void reallocate(mask_t oldCapacity, mask_t newCapacity);
struct bucket_t * find(cache_key_t key, id receiver);
static void bad_cache(id receiver, SEL sel, Class isa) __attribute__((noreturn));
};

根據(jù)源碼,我們可以知道 cache_t 中存儲(chǔ)了一個(gè) bucket_t 的結(jié)構(gòu)體,和兩個(gè)unsigned int的變量。
mask:分配用來(lái)緩存bucket的總數(shù)。
occupied:表明目前實(shí)際占用的緩存bucket的個(gè)數(shù)。
bucket_t 的結(jié)構(gòu)體中存儲(chǔ)了一個(gè)unsigned long和一個(gè)IMP。IMP是一個(gè)函數(shù)指針,指向了一個(gè)方法的具體實(shí)現(xiàn)。
cache_t 中的bucket_t *_buckets其實(shí)就是一個(gè)散列表,用來(lái)存儲(chǔ)Method的鏈表。
Cache 的作用主要是為了優(yōu)化方法調(diào)用的性能。當(dāng)對(duì)象receiver調(diào)用方法message時(shí),首先根據(jù)對(duì)象receiver的isa指針查找到它對(duì)應(yīng)的類,然后在類的methodLists中搜索方法,如果沒(méi)有找到,就使用super_class指針到父類中的methodLists查找,一旦找到就調(diào)用方法。如果沒(méi)有找到,有可能消息轉(zhuǎn)發(fā),也可能忽略它。但這樣查找方式效率太低,因?yàn)橥粋€(gè)類大概只有20%的方法經(jīng)常被調(diào)用,占總調(diào)用次數(shù)的80%。所以使用Cache來(lái)緩存經(jīng)常調(diào)用的方法,當(dāng)調(diào)用方法時(shí),優(yōu)先在Cache查找,如果沒(méi)有找到,再到methodLists查找。
class_data_bits_t bits
存儲(chǔ)類的方法、屬性、遵循的協(xié)議等信息的地方
class_data_bits_t bits有一個(gè)成員uintptr_t bits, 可以理解為一個(gè)‘復(fù)合指針’。什么意思呢,就是bits不僅包含了指針,同時(shí)包含了Class的各種異或flag,來(lái)說(shuō)明Class的屬性。把這些信息復(fù)合在一起,僅用一個(gè)uint指針bits來(lái)表示。當(dāng)需要取出這些信息時(shí),需要用對(duì)應(yīng)的以FAST_前綴開(kāi)頭的flag掩碼對(duì)bits做按位與操作。
例如,我們需要取出Classs的核心信息class_rw_t, 則需要調(diào)用方法:
class_rw_t* data() {
return (class_rw_t *)(bits & FAST_DATA_MASK);
}


2. 為什么要設(shè)計(jì)metaclass
類對(duì)象(class object)中包含了類的實(shí)例變量,實(shí)例方法的定義,
元類對(duì)象(metaclass object)中包括了類對(duì)象的方法,也就是類方法(也就是C++中的靜態(tài)方法)的定義。
那么可不可以把元類干掉,在類中把實(shí)例方法和類方法存在兩個(gè)不同的數(shù)組中?
__class_lookupMethodAndLoadCache3
IMP _class_lookupMethodAndLoadCache3(id obj, SEL sel, Class cls)
{
return lookUpImpOrForward(cls, sel, obj,
YES/*initialize*/, NO/*cache*/, YES/*resolver*/);
}
`lookUpImpOrForward`用于查找方法
1、首先會(huì)再一次的從類中尋找需要調(diào)用方法的緩存,如果能命中緩存直接返回該方法的實(shí)現(xiàn),如果不能命中則繼續(xù)往下走。
2、從類的方法列表中尋找該方法,如果能從列表中找到方法則對(duì)方法進(jìn)行緩存并返回該方法的實(shí)現(xiàn),如果找不到該方法則繼續(xù)往下走。
3、從父類的緩存尋找該方法,如果父類緩存能命中則將方法緩存至當(dāng)前調(diào)用方法的類中(注意這里不是存進(jìn)父類),如果緩存未命中則遍歷父類的方法列表,之后操作如同第2步,未能命中則繼續(xù)走第3步直到尋找到基類。
4、如果到基類依然沒(méi)有找到該方法則觸發(fā)動(dòng)態(tài)方法解析流程。=
5、還是找不到就觸發(fā)消息轉(zhuǎn)發(fā)流程
答:行是肯定可行的,但是在lookUpImpOrForward執(zhí)行的時(shí)候就得標(biāo)注上傳入的cls到底是實(shí)例對(duì)象還是類對(duì)象,這也就意味著在查找方法的緩存時(shí)同樣也需要判斷cls到底是個(gè)啥。
從OC的消息機(jī)制分析了元類存在的意義,元類的存在巧妙的簡(jiǎn)化了實(shí)例方法和類方法的調(diào)用流程,大大提升了消息發(fā)送的效率。
metaclass代表的是類對(duì)象的對(duì)象,它存儲(chǔ)了類的類方法,它的目的是將實(shí)例和類的相關(guān)方法列表以及構(gòu)建信息區(qū)分開(kāi)來(lái),方便各司其職,符合單一職責(zé)設(shè)計(jì)原則。
具體可以參考這篇文章
3. class_copyIvarList & class_copyPropertyList區(qū)別
class_copyIvarList
獲取 類對(duì)象 中的所有實(shí)例變量信息,從 class_ro_t 中獲取
class_copyPropertyList
獲取 類對(duì)象 中的屬性信息, class_rw_t 的 properties,先后輸出了 category / extension/ baseClass 的屬性,而且僅輸出 當(dāng)前的類 的屬性信息,而不會(huì)向上去找 superClass 中定義的屬性。
Q1: class_ro_t 中的 baseProperties 呢?
Q2: class_rw_t 中的 properties 包含了所有屬性,那何時(shí)注入進(jìn)去的呢?
An: methodizeClass 方法,會(huì)把類里面的屬性,協(xié)議,方法都加載進(jìn)來(lái)
property_list_t *proplist = ro->baseProperties;
if (proplist) {
rw->properties.attachLists(&proplist, 1);
}
4. class_rw_t 和 class_ro_t 的區(qū)別

class_rw_t 表示read write,class_ro_t 表示 read only
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;
Class firstSubclass;
Class nextSiblingClass;
char *demangledName;
#if SUPPORT_INDEXED_ISA
uint32_t index;
#endif
}
struct class_ro_t {
uint32_t flags;
uint32_t instanceStart;
uint32_t instanceSize;
#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;
method_list_t *baseMethods() const {
return baseMethodList;
}
};
每個(gè)類都對(duì)應(yīng)有一個(gè)class_ro_t結(jié)構(gòu)體和一個(gè)class_rw_t結(jié)構(gòu)體。在編譯期間,class_ro_t結(jié)構(gòu)體就已經(jīng)確定,objc_class中的bits的data部分存放著該結(jié)構(gòu)體的地址。在runtime運(yùn)行之后,具體說(shuō)來(lái)是在運(yùn)行runtime的realizeClass 方法時(shí),會(huì)生成class_rw_t結(jié)構(gòu)體,該結(jié)構(gòu)體包含了class_ro_t,并且更新data部分,換成class_rw_t結(jié)構(gòu)體的地址
區(qū)別在于:class_ro_t存放的是編譯期間就確定的屬性、方法和遵守協(xié)議;而class_rw_t是在runtime時(shí)才確定,它會(huì)先將class_ro_t的內(nèi)容拷貝過(guò)去,然后再將當(dāng)前類的分類的這些屬性、方法等拷貝到其中。所以可以說(shuō)class_rw_t是class_ro_t的超集,當(dāng)然實(shí)際訪問(wèn)類的方法、屬性等也都是訪問(wèn)的class_rw_t中的內(nèi)容
OC對(duì)象中存儲(chǔ)的屬性、方法、遵循的協(xié)議數(shù)據(jù)其實(shí)被存儲(chǔ)在這兩塊兒內(nèi)存區(qū)域的,而我們通過(guò)runtime動(dòng)態(tài)修改類的方法時(shí),是修改在class_rw_t區(qū)域中存儲(chǔ)的方法列表。
參考這篇文章
更加詳細(xì)的分析,請(qǐng)看@Draveness 的這篇文章深入解析 ObjC 中方法的結(jié)構(gòu)
5. category如何被加載的,兩個(gè)category的load方法的加載順序,兩個(gè)category的同名方法的加載順序
objc_init ->... -> realizeClass -> methodizeClass(用于Attach categories)-> attachCategories -> attachLists
在運(yùn)行時(shí)調(diào)用 realizeClass方法,會(huì)做以下3件事情:
- 從
class_data_bits_t調(diào)用data方法,將結(jié)果從class_rw_t強(qiáng)制轉(zhuǎn)換為class_ro_t指針 - 初始化一個(gè)
class_rw_t結(jié)構(gòu)體 - 設(shè)置結(jié)構(gòu)體 ro的值以及 flag
最后調(diào)用methodizeClass方法,把分類里面的屬性,協(xié)議,方法都加載進(jìn)來(lái)。
關(guān)鍵就是在methodizeClass 方法實(shí)現(xiàn)中
category的加載是在運(yùn)行時(shí)發(fā)生的,加載過(guò)程是,把category的實(shí)例方法、屬性、協(xié)議添加到類對(duì)象上。把category的類方法、屬性、協(xié)議添加到metaclass上。
category的load方法執(zhí)行順序是根據(jù)類的編譯順序決定的,即:xcode中的Build Phases中的Compile Sources中的文件從上到下的順序加載的。
category并不會(huì)替換掉同名的方法的,也就是說(shuō)如果 category 和原來(lái)類都有 methodA,那么 category 附加完成之后,類的方法列表里會(huì)有兩個(gè) methodA,并且category添加的methodA會(huì)排在原有類的methodA的前面,因此如果存在category的同名方法,那么在調(diào)用的時(shí)候,則會(huì)先找到最后一個(gè)編譯的 category里的對(duì)應(yīng)方法
6、category & extension區(qū)別,能給NSObject添加Extension嗎,結(jié)果如何?
category:分類
- 給類添加新的方法
- 不能給類添加成員變量
- 通過(guò)@property定義的變量,只能生成對(duì)應(yīng)的getter和setter的方法聲明,但是不能實(shí)現(xiàn)getter和* setter方法,同時(shí)也不能生成帶下劃線的成員屬性
- 是運(yùn)行期決定的
注意:為什么不能添加屬性,原因就是category是運(yùn)行期決定的,在運(yùn)行期類的內(nèi)存布局已經(jīng)確定,如果添加實(shí)例變量會(huì)破壞類的內(nèi)存布局,會(huì)產(chǎn)生意想不到的錯(cuò)誤。
extension:擴(kuò)展
- 可以給類添加成員變量,但是是私有的
- 可以給類添加方法,但是是私有的
- 添加的屬性和方法是類的一部分,在編譯期就決定的。在編譯器和頭文件的@interface和實(shí)現(xiàn)文件里的@implement一起形成了一個(gè)完整的類。
- 伴隨著類的產(chǎn)生而產(chǎn)生,也隨著類的消失而消失
- 必須有類的源碼才可以給類添加extension,所以對(duì)于系統(tǒng)一些類,如nsstring,就無(wú)法添加類擴(kuò)展
不能給NSObject添加Extension,因?yàn)樵趀xtension中添加的方法或?qū)傩员仨氃谠搭惖奈募?m文件中實(shí)現(xiàn)才可以,即:你必須有一個(gè)類的源碼才能添加一個(gè)類的extension。
8. 在方法調(diào)用的時(shí)候,方法查詢-> 動(dòng)態(tài)解析-> 消息轉(zhuǎn)發(fā) 之前做了什么
OC中的方法調(diào)用,編譯后的代碼最終都會(huì)轉(zhuǎn)成objc_msgSend(id , SEL, ...)方法進(jìn)行調(diào)用,這個(gè)方法第一個(gè)參數(shù)是一個(gè)消息接收者對(duì)象,runtime通過(guò)這個(gè)對(duì)象的isa指針找到這個(gè)對(duì)象的類對(duì)象,從類對(duì)象中的cache中查找是否存在SEL對(duì)應(yīng)的IMP,若不存在,則會(huì)在 method_list中查找,如果還是沒(méi)找到,則會(huì)到supper_class中查找,仍然沒(méi)找到的話,就會(huì)調(diào)用_objc_msgForward(id, SEL, ...)進(jìn)行消息轉(zhuǎn)發(fā)。
9. IMP、SEL、Method的區(qū)別和使用場(chǎng)景
typedef struct objc_selector *SEL
SEL: 方法選擇器,雖然 SEL 是 objc_selector 結(jié)構(gòu)體指針,但實(shí)際上它只是一個(gè) C 字符串,方法的名稱
typedef id (*IMP)(id, SEL, …)
IMP-函數(shù)指針,指向?qū)嶋H執(zhí)行函數(shù)體
typedef struct objc_method *Method
/// Method
struct objc_method {
SEL method_name;
char *method_types;
IMP method_imp;
};
方法名 method_name 類型為 SEL,前面提到過(guò)相同名字的方法即使在不同類中定義,它們的方法選擇器也相同。
方法類型 method_types 是個(gè) char 指針,其實(shí)存儲(chǔ)著方法的參數(shù)類型和返回值類型,即是 Type Encoding 編碼。
method_imp 指向方法的實(shí)現(xiàn),本質(zhì)上是一個(gè)函數(shù)的指針,就是前面講到的 Implementation。
使用場(chǎng)景:
實(shí)現(xiàn)類的swizzle的時(shí)候會(huì)用到,通過(guò)class_getInstanceMethod(class, SEL)來(lái)獲取類的方法Method,其中用到了SEL作為方法名
調(diào)用method_exchangeImplementations(Method1, Method2)進(jìn)行方法交換
我們還可以給類動(dòng)態(tài)添加方法,此時(shí)我們需要調(diào)用class_addMethod(Class, SEL, IMP, types),該方法需要我們傳遞一個(gè)方法的實(shí)現(xiàn)函數(shù)IMP,例如:
static void funcName(id receiver, SEL cmd, 方法參數(shù)...) {
// 方法具體的實(shí)現(xiàn)
}
函數(shù)第一個(gè)參數(shù):方法接收者,第二個(gè)參數(shù):調(diào)用的方法名SEL,方法對(duì)應(yīng)的參數(shù),這個(gè)順序是固定的。
10、load、initialize方法的區(qū)別什么?在繼承關(guān)系中他們有什么區(qū)別
load:當(dāng)類被裝載的時(shí)候被調(diào)用,只調(diào)用一次
- 調(diào)用方式并不是采用runtime的objc_msgSend方式調(diào)用的,而是直接采用
函數(shù)的內(nèi)存地址直接調(diào)用的 - 多個(gè)類的load調(diào)用順序,是依賴于
compile sources中的文件順序決定的,根據(jù)文件從上到下的順序調(diào)用 - 子類和父類同時(shí)實(shí)現(xiàn)load的方法時(shí),父類的方法先被調(diào)用
- 本類與category的調(diào)用順序是,優(yōu)先調(diào)用本類的(注意:category是在最后被裝載的)
- 多個(gè)category,每個(gè)load都會(huì)被調(diào)用(這也是load的調(diào)用方式不是采用objc_msgSend的方式調(diào)用的),同樣按照
compile sources中的順序調(diào)用的 - load是被動(dòng)調(diào)用的,在類裝載時(shí)調(diào)用的,不需要手動(dòng)觸發(fā)調(diào)用
initialize:當(dāng)類或子類第一次收到消息時(shí)被調(diào)用(即:靜態(tài)方法或?qū)嵗椒ǖ谝淮伪徽{(diào)用,也就是這個(gè)類第一次被用到的時(shí)候),只調(diào)用一次
- 調(diào)用方式是通過(guò)runtime的
objc_msgSend的方式調(diào)用的,此時(shí)所有的類都已經(jīng)裝載完畢 - 子類和父類同時(shí)實(shí)現(xiàn)initialize,
父類的先被調(diào)用,然后調(diào)用子類的 - 本類與category同時(shí)實(shí)現(xiàn)initialize,
category會(huì)覆蓋本類的方法,只調(diào)用category的initialize一次(這也說(shuō)明initialize的調(diào)用方式采用objc_msgSend的方式調(diào)用的) - initialize是主動(dòng)調(diào)用的,只有當(dāng)類第一次被用到的時(shí)候才會(huì)觸發(fā)
參考這篇文章
11. _objc_msgForward函數(shù)是做什么的,直接調(diào)用它將會(huì)發(fā)生什么?
_objc_msgForward是一個(gè)函數(shù)指針(和 IMP 的類型一樣),是用于消息轉(zhuǎn)發(fā)的:
當(dāng)向一個(gè)對(duì)象發(fā)送一條消息,但它并沒(méi)有實(shí)現(xiàn)的時(shí)候,_objc_msgForward會(huì)嘗試做消息轉(zhuǎn)發(fā)。
也就是說(shuō)_objc_msgForward在進(jìn)行消息轉(zhuǎn)發(fā)的過(guò)程中會(huì)涉及以下這幾個(gè)方法:
resolveInstanceMethod:方法 (或 resolveClassMethod:)。
forwardingTargetForSelector:方法
methodSignatureForSelector:方法
forwardInvocation:方法
doesNotRecognizeSelector: 方法
12. 通過(guò)runtime動(dòng)態(tài)創(chuàng)建一個(gè)類
要?jiǎng)?chuàng)建一個(gè)新類,
- 首先調(diào)用
objc_allocateClassPair。 - 然后使用
class_addMethod和class_addIvar等函數(shù)設(shè)置類的屬性。 - 完成構(gòu)建類后調(diào)用
objc_registerClassPair。
/**
* 創(chuàng)建一個(gè)新類和元類.
*
* @param superclass 這個(gè)類是新創(chuàng)建的類的父類,可以傳入Nil去創(chuàng)建一個(gè)新根類.
* @param name 這個(gè)字符串是類的名字(例:"NSObject")
* @param extraBytes 一般傳入0
* @return 新的類,如果返回的是Nil,那么就是這個(gè)類創(chuàng)建失敗了(例:創(chuàng)建的是"NSObject"類,然而這個(gè)類已經(jīng)存在了)
*/
objc_allocateClassPair(
Class _Nullable superclass,
const char * _Nonnull name,
size_t extraBytes
)
objc_allocateClassPair只返回一個(gè)值:Class。那么pair的另一半在哪里呢?
是的,估計(jì)你已經(jīng)猜到了這個(gè)另一半就是meta-class
/**
* 注冊(cè)使用`objc_allocateClassPair`方法創(chuàng)建的類
*
* @param cls 需要注冊(cè)的類(不能為Nil)
*/
objc_registerClassPair(Class _Nonnull cls)
代碼樣例
/// 創(chuàng)建一個(gè)元類
Class class = objc_allocateClassPair([NSObject class], "Person", 0);
/// 添加方法
//class_addMethod(<#Class _Nullable __unsafe_unretained cls#>, <#SEL _Nonnull name#>, <#IMP _Nonnull imp#>, <#const char * _Nullable types#>)
/// 添加屬性
//class_addIvar(<#Class _Nullable __unsafe_unretained cls#>, <#const char * _Nonnull name#>, <#size_t size#>, <#uint8_t alignment#>, <#const char * _Nullable types#>)
/// 注冊(cè)元類
objc_registerClassPair(class);
13. object_getClass 和 object_setClass
1. Class object_getClass(id obj)
//返回對(duì)象的isa指針
eg:參數(shù)為實(shí)例對(duì)象返回類對(duì)象
eg:參數(shù)為類對(duì)象返回元類對(duì)象
eg:參數(shù)為元類對(duì)象返回根元類對(duì)象
eg:參數(shù)為根元類對(duì)象返回自身
Class object_getClass(id obj)
// 根據(jù)字符串返回類對(duì)象
Class _Nullable objc_getClass(const char * _Nonnull name)
// 創(chuàng)建一個(gè)NSObject對(duì)象obj,然后獲取obj的類
NSObject *obj = [[NSObject alloc] init]; // 1
Class objClass = object_getClass(obj); // 2
NSLog(@"%@", NSStringFromClass(objClass)); // 3
Class nsobjectClass = object_getClass([NSObject class]); // 4
NSLog(@"%@", NSStringFromClass(nsobjectClass)); // 5
BOOL isMeta1 = class_isMetaClass(objClass); // 6
BOOL isMeta2 = class_isMetaClass(nsobjectClass); // 7
NSLog(@"isMeta1:%i, isMeta2:%i", isMeta1, isMeta2); // 8
//打印結(jié)果
2020-04-03 18:08:48.875921+0800 ImplementKVO[81240:4259330] NSObject
2020-04-03 18:08:48.876057+0800 ImplementKVO[81240:4259330] NSObject
2020-04-03 18:08:48.876155+0800 ImplementKVO[81240:4259330] isMeta1:0, isMeta2:1
說(shuō)明:class_isMetaClass函數(shù)的作用是判斷傳入的類是不是元類,經(jīng)過(guò)判斷是不是元類可看出區(qū)別,因此可得出結(jié)論:
object_getClass函數(shù)的參數(shù)傳一個(gè)類的實(shí)例時(shí),返回的是該實(shí)例的類對(duì)象;
參數(shù)傳類時(shí),返回的是該類的元類。
Class class1 = [obj class];
Class class2 = [[NSObject class] class];
NSLog(@"%@", NSStringFromClass(class1));
NSLog(@"%@", NSStringFromClass(class2));
NSLog(@"%i, %i", class_isMetaClass(class1), class_isMetaClass(class2));
2020-04-03 18:17:05.175123+0800 ImplementKVO[81544:4270627] NSObject
2020-04-03 18:17:05.175261+0800 ImplementKVO[81544:4270627] NSObject
2020-04-03 18:17:05.175363+0800 ImplementKVO[81544:4270627] 0, 0
通過(guò)打印結(jié)果可以看出,class方法的調(diào)用者是一個(gè)實(shí)例時(shí),獲取到的是該實(shí)例的類,此時(shí)和object_getClass函數(shù)作用相同;而調(diào)用者是一個(gè)類(比如[NSObject class])時(shí),獲取到的并不是該類的元類,此時(shí)和object_getClass函數(shù)的作用不同.
//[xxx class]方法內(nèi)部實(shí)現(xiàn)
+ (Class)class {
return self;
}
- (Class)class {
return object_getClass(self);
}
2. Class object_setClass(id obj, Class cls)
/**
將一個(gè)對(duì)象設(shè)置為別的類類型,返回原來(lái)的Class ,將一個(gè)對(duì)象的isa指針指向設(shè)置的Class
* Sets the class of an object.
*
* @param obj The object to modify.
* @param cls A class object.
*
* @return The previous value of \e object's class, or \c Nil if \e object is \c nil.
*/
Class _Nullable object_setClass(id _Nullable obj, Class _Nonnull cls)
// 分別創(chuàng)建一個(gè)可變數(shù)組對(duì)象mutArray和不可變數(shù)組對(duì)象array
NSMutableArray *mutArray = [NSMutableArray arrayWithObjects:@"a", @"b", nil]; // 1
NSArray *array = @[@"c", @"d"]; // 2
// 獲取這兩個(gè)對(duì)象mutArray和array的類并打印
Class mutArrayClassBefore = object_getClass(mutArray); // 3
Class arrayClassBefore = object_getClass(array); // 4
NSLog(@"%@ -- %@", NSStringFromClass(mutArrayClassBefore), NSStringFromClass(arrayClassBefore)); // 5
Class setclass = object_setClass(mutArray, arrayClassBefore); // 6
NSLog(@"%@", NSStringFromClass(setclass)); // 7
Class mutArrayClassNow = object_getClass(mutArray); // 8
Class arrayClassNow = object_getClass(array); // 9
NSLog(@"%@ -- %@", NSStringFromClass(mutArrayClassNow), NSStringFromClass(arrayClassNow)); // 10
2020-04-03 18:27:43.569594+0800 ImplementKVO[81775:4277728] __NSArrayM -- __NSArrayI
2020-04-03 18:27:43.569724+0800 ImplementKVO[81775:4277728] __NSArrayM
2020-04-03 18:27:43.569822+0800 ImplementKVO[81775:4277728] __NSArrayI -- __NSArrayI
從打印結(jié)果可以看出用object_setClass函數(shù)將可變數(shù)組對(duì)象mutArray的類設(shè)置為它的父類是可以的,此時(shí)再用mutArray調(diào)用NSMutableArray的方法會(huì)導(dǎo)致程序crash,如
[mutArray addObject:@"e"]; // 11
crash信息為經(jīng)典的:
2020-04-03 18:31:04.101045+0800 ImplementKVO[81860:4280357] -[__NSArrayI addObject:]: unrecognized selector sent to instance 0x6000000fec40
總結(jié)
- 使用object_setClass后會(huì)使對(duì)象的
isa的值指向新的Class。 - 使用object_setClass對(duì)象的內(nèi)存布局不會(huì)發(fā)生變化。
- 使用object_setClass
不能訪問(wèn)超過(guò)原對(duì)象申請(qǐng)的內(nèi)存區(qū)域,否則程序會(huì)crash。
14. Method Swizzle注意事項(xiàng)
- 對(duì)于一般的Class.推薦在load方法中交換, 系統(tǒng)的類,可以通過(guò)Category添加方法交換
- 避免交換父類方法(先
class_addMethod,判斷是否成功) - 交換方法應(yīng)在
+load方法 - 交換方法應(yīng)該放到
dispatch_once中執(zhí)行 - 交換的分類方法應(yīng)該添加
自定義前綴,避免沖突 - 交換的分類方法應(yīng)
調(diào)用原實(shí)現(xiàn)
注意:如果是交換不同類的方法,并且在方法中訪問(wèn)了類的屬性,會(huì)造成Crash,更安全的做法,Runtime 還提供了另一種 Swizzle 函數(shù) method_setImplementation。
method_setImplementation 可以讓我們提供一個(gè)新的函數(shù)來(lái)代替我們要替換的方法。 而不是將兩個(gè)方法的實(shí)現(xiàn)做交換。 這樣就不會(huì)造成 method_exchangeImplementations 的潛在對(duì)已有實(shí)現(xiàn)的副作用了。
method_setImplementation 接受兩個(gè)參數(shù),第一個(gè)還是我們要替換的方法, 而第二個(gè)參數(shù)是一個(gè) IMP 類型的。
Objc 黑科技 - Method Swizzle 的一些注意事項(xiàng)
例如:統(tǒng)計(jì)VC加載次數(shù)并打印
- UIViewController+Logging.m
#import "UIViewController+Logging.h"
#import <objc/runtime.h>
@implementation UIViewController (Logging)
+ (void)load
{
swizzleMethod([self class], @selector(viewDidAppear:), @selector(swizzled_viewDidAppear:));
}
- (void)swizzled_viewDidAppear:(BOOL)animated
{
//交換前需要先調(diào)用系統(tǒng)的方法初始化調(diào)用[self xxx]的系統(tǒng)方法的時(shí)候.需要把xxx改成xx_xxx(你交換的方法)
//這里是因?yàn)槟愕姆椒ㄒ呀?jīng)和系統(tǒng)的方法交換了,調(diào)用你的方法其實(shí)是調(diào)用的系統(tǒng)方法,調(diào)用系統(tǒng)方法的話就調(diào)用的是你的方法,
//然后就會(huì)產(chǎn)生循環(huán)調(diào)用.
// call original implementation
[self swizzled_viewDidAppear:animated];
// Logging
NSLog(@"%@", NSStringFromClass([self class]));
}
void swizzleMethod(Class class, SEL originalSelector, SEL swizzledSelector)
{
// the method might not exist in the class, but in its superclass
Method originalMethod = class_getInstanceMethod(class, originalSelector);
Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);
// class_addMethod will fail if original method already exists
BOOL didAddMethod = class_addMethod(class, originalSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod));
// the method doesn’t exist and we just added one
if (didAddMethod) {
class_replaceMethod(class, swizzledSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod));
}
else {
method_exchangeImplementations(originalMethod, swizzledMethod);
}
}
15. iOS 中內(nèi)省的幾個(gè)方法有哪些??jī)?nèi)部實(shí)現(xiàn)原理是什么
反射機(jī)制
16. 怎么防止UI控件短時(shí)間多次激活事件?
需求
當(dāng)前項(xiàng)目寫(xiě)好的按鈕,還沒(méi)有全局地控制他們短時(shí)間內(nèi)不可連續(xù)點(diǎn)擊(也許有過(guò)零星地在某些網(wǎng)絡(luò)請(qǐng)求接口之前做過(guò)一些控制)?,F(xiàn)在來(lái)了新需求:本APP所有的按鈕1秒內(nèi)不可連續(xù)點(diǎn)擊。你怎么做?一個(gè)個(gè)改?這種低效率低維護(hù)度肯定是不妥的。
方案
給按鈕添加分類,并添加一個(gè)點(diǎn)擊事件間隔的屬性,執(zhí)行點(diǎn)擊事件的時(shí)候判斷一下是否時(shí)間到了,如果時(shí)間不到,那么攔截點(diǎn)擊事件。
怎么攔截點(diǎn)擊事件呢?其實(shí)點(diǎn)擊事件在runtime里面是發(fā)送消息,我們可以把要發(fā)送的消息的SEL 和自己寫(xiě)的SEL交換一下,然后在自己寫(xiě)的SEL里面判斷是否執(zhí)行點(diǎn)擊事件。
實(shí)踐
UIButton是UIControl的子類,因而根據(jù)UIControl新建一個(gè)分類即可
- UIControl+Limit.m
#import "UIControl+Limit.h"
#import <objc/runtime.h>
static const char *UIControl_acceptEventInterval="UIControl_acceptEventInterval";
static const char *UIControl_ignoreEvent="UIControl_ignoreEvent";
@implementation UIControl (Limit)
#pragma mark - acceptEventInterval
- (void)setAcceptEventInterval:(NSTimeInterval)acceptEventInterval
{
objc_setAssociatedObject(self,UIControl_acceptEventInterval, @(acceptEventInterval), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
-(NSTimeInterval)acceptEventInterval {
return [objc_getAssociatedObject(self,UIControl_acceptEventInterval) doubleValue];
}
#pragma mark - ignoreEvent
-(void)setIgnoreEvent:(BOOL)ignoreEvent{
objc_setAssociatedObject(self,UIControl_ignoreEvent, @(ignoreEvent), OBJC_ASSOCIATION_ASSIGN);
}
-(BOOL)ignoreEvent{
return [objc_getAssociatedObject(self,UIControl_ignoreEvent) boolValue];
}
#pragma mark - Swizzling
+(void)load {
Method a = class_getInstanceMethod(self,@selector(sendAction:to:forEvent:));
Method b = class_getInstanceMethod(self,@selector(swizzled_sendAction:to:forEvent:));
method_exchangeImplementations(a, b);//交換方法
}
- (void)swizzled_sendAction:(SEL)action to:(id)target forEvent:(UIEvent*)event
{
if(self.ignoreEvent){
NSLog(@"btnAction is intercepted");
return;
}
if(self.acceptEventInterval>0){
self.ignoreEvent=YES;
//延遲執(zhí)行
[self performSelector:@selector(setIgnoreEventWithNo) withObject:nil afterDelay:self.acceptEventInterval];
}
//調(diào)用原方法實(shí)現(xiàn)
[self swizzled_sendAction:action to:target forEvent:event];
}
-(void)setIgnoreEventWithNo{
self.ignoreEvent=NO;
}
@end
- ViewController.m
-(void)setupSubViews{
UIButton *btn = [UIButton new];
btn =[[UIButton alloc]initWithFrame:CGRectMake(100,100,100,40)];
[btn setTitle:@"btnTest"forState:UIControlStateNormal];
[btn setTitleColor:[UIColor redColor]forState:UIControlStateNormal];
btn.acceptEventInterval = 3;
[self.view addSubview:btn];
[btn addTarget:self action:@selector(btnAction)forControlEvents:UIControlEventTouchUpInside];
}
- (void)btnAction{
NSLog(@"btnAction is executed");
}
//日志打印
2020-04-06 21:07:44.244251+0800 Runtime[5230:326947] btnAction is executed
2020-04-06 21:07:45.077352+0800 Runtime[5230:326947] btnAction is intercepted
2020-04-06 21:07:45.871633+0800 Runtime[5230:326947] btnAction is intercepted
2020-04-06 21:07:46.844650+0800 Runtime[5230:326947] btnAction is intercepted
2020-04-06 21:07:47.719857+0800 Runtime[5230:326947] btnAction is executed