對於RunTime恐怕幾乎每個作iOS的人都據說過,都用過吧,可是對於其具體實現好多人應該都不太清楚吧,今天我這分4部分,詳細的講解一下Runtime,讓你們對Runtime有一個全局的瞭解git
咱們在研究OC對象的時候已經知道了,實力對象的isa
指向類對象,類對象的isa
指向元類對象。其實這樣說仍是有一點不對的,應該說在arm64架構
以前,isa就是一個普通的指針,存儲着Class
、 Meta-Class
對象的內存地址;可是從arm64
以後,對isa
進行了優化,變成了一個共用體(union)
結構,還使用位域
來存放跟多的信息。github
咱們在這裏下載runtime源碼,而後查找struct objc_object
裏面的isa
,這裏咱們只研究arm64架構isa
面試
struct {
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
的結構是這種共用體(union)
結構,其實使用這種共用體是一種優化,isa
不在單獨存放的是一個指針信息了,裏面存放了更多的其餘信息。算法
想要明白isa
變成共用體(union)
結構,是一種優化,咱們須要先了解一些概念數組
位運算的運算符有下面幾個緩存
<<
>>
|
&
~
^
其功能是參與運算的兩數各對應的二進位相異或,當兩對應的二進位相異時,結果爲1與操做& 與操做&
:都是1則爲1,一個0就是0。能夠用來取出來特定的位。例如一個二進制0b 0000 0111
,咱們分別想取出第一位1
和第四位0
。安全
0000 0111 0000 0111
&0000 0001 &0000 1000
-------------- --------------
0000 0001 0000 0000
複製代碼
咱們能夠發現咱們使用按位與&的時候,咱們若是想取出哪一位,把改成設置爲1,其餘位設置爲0就能夠了。bash
介紹到了&
,我再來介紹一個概念,掩碼:通常用來按位與(&)運算的
,具體有什麼做用,咱們下面會進行講解數據結構
或操做|多線程
或操做|
:一個是1,則爲1,所有是0才爲0。 例如一個二進制0b 0101 1010
。
0101 1010
| 0001 1100
--------------
0101 1110
複製代碼
若是咱們想要某一位,就該該位或上一個0
左移:<<
二進制位所有左移若干位,左邊的丟棄,右邊補0
右移:>>
二進制右移若干位,正數左邊補0,負數左邊補1,右邊丟棄。
例如 12>>2
0000 1100 = 12
0000 0011 = 3 (右移後)
特色:每右移一位,就除以一次2。a>>n 就是 a除以2的n次方
一般用bit來做數據傳輸的單位,由於物理層,數據鏈路層的傳輸對於用戶是透明的,而這種通訊傳輸是基於二進制的傳輸。在應用層一般是用byte來做單位,表示文件的大小,在用戶看來就是可見的數據大小
換算 1 Byte = 8 Bits 1 KB = 1024 Bytes 1 MB = 1024 KB 1 GB = 1024 MB 另外,Byte一般簡寫爲B(大寫),而bit一般簡寫爲b(小寫)。能夠這麼記憶,大寫的爲大單位,實際數值小,小寫的爲小單位,實際數值較大,1B=8b。
所謂」位域「是把一個字節中的二進位劃分爲幾 個不一樣的區域, 並說明每一個區域的位數。每一個域有一個域名,容許在程序中按域名進行操做。它其實是C語言提供的一種數據結構。
使用位域的好處是:
struct 位域結構名 { 位域列表 }; 其中位域列表的形式爲: 類型說明符 位域名:位域長度;
struct {
char tall : 1;
char rich : 1;
char handsome : 1;
} _tallRichHandsome;
複製代碼
union中能夠定義多個成員,union的大小由最大的成員的大小決定
;
union成員共享同一塊大小的內存,一次只能使用其中的一個成員; 對union某一個成員賦值,會覆蓋其餘成員的值(但前提是成員所佔字節數相同,當成員所佔字節數不一樣時只會覆蓋相應字節上的值,好比對char成員賦值就不會把整個int成員覆蓋掉,由於char只佔一個字節,而int佔四個字節); union量的存放順序是全部成員都從低地址開始存放的。
例如咱們建立一個Person
類,裏面有三個Bool
屬性,tall
、rich
、handsome
。
@property (nonatomic,assign) BOOL tall;
@property (nonatomic,assign) BOOL rich;
@property (nonatomic,assign) BOOL handsome;
複製代碼
咱們知道這三個屬性佔用了3個字節
。其實這個時候咱們能夠考慮到使用位域
或者共用體
的概念,使用位(Bit)的0和1來表明這三個屬性的YES NO
,那個三個屬性就只是佔用了2個字節
位域代碼
@interface Person()
{
// 位域
struct {
char tall : 1;
char rich : 1;
char handsome : 1;
} _tallRichHandsome;
}
@end
@implementation Person
- (void)setTall:(BOOL)tall
{
_tallRichHandsome.tall = tall;
}
- (BOOL)isTall
{
return !!_tallRichHandsome.tall;
}
- (void)setRich:(BOOL)rich
{
_tallRichHandsome.rich = rich;
}
- (BOOL)isRich
{
return !!_tallRichHandsome.rich;
}
- (void)setHandsome:(BOOL)handsome
{
_tallRichHandsome.handsome = handsome;
}
- (BOOL)isHandsome
{
return !!_tallRichHandsome.handsome;
}
複製代碼
爲何會出現!!
,咱們知道!(-1) == NO
,!
上一個存在的值是NO
,!!
兩次那麼只會出現YES 和 NO了。
共用體
其實咱們觀察isa的類型,發現isa實際上是使用的共用體
,
#define TallMask (1<<0)
#define RichMask (1<<1)
#define HandsomeMask (1<<2)
@interface Person()
{
union {
int bits;
struct {
char tall : 1;
char rich : 1;
char handsome : 1;
};
} _tallRichHandsome;
}
@end
@implementation Person
- (void)setTall:(BOOL)tall
{
if (tall) {
_tallRichHandsome.bits |= TallMask;
} else {
_tallRichHandsome.bits &= ~TallMask;
}
}
- (BOOL)isTall
{
return !!(_tallRichHandsome.bits & TallMask);
}
- (void)setRich:(BOOL)rich
{
if (rich) {
_tallRichHandsome.bits |= RichMask;
} else {
_tallRichHandsome.bits &= ~RichMask;
}
}
- (BOOL)isRich
{
return !!(_tallRichHandsome.bits & RichMask);
}
- (void)setHandsome:(BOOL)handsome
{
if (handsome) {
_tallRichHandsome.bits |= HandsomeMask;
} else {
_tallRichHandsome.bits &= ~HandsomeMask;
}
}
- (BOOL)isHandsome
{
return !!(_tallRichHandsome.bits & HandsomeMask);
}
複製代碼
#define TallMask (1<<0)
這是掩碼,爲了方便閱讀。
struct {
char tall : 1;
char rich : 1;
char handsome : 1;
};
複製代碼
其實也僅僅是方便閱讀的做用,讓咱們知道tall
、rich
、handsome
是在哪一位上,去掉並不影響代碼。
其實咱們能夠看到蘋果官方文檔上面有不少地方運用到了位運算
typedef NS_ENUM(NSInteger, LXDAuthorizationType)
{
LXDAuthorizationTypeNone = 0,
LXDAuthorizationTypePush = 1 << 0, ///< 推送受權
LXDAuthorizationTypeLocation = 1 << 1, ///< 定位受權
LXDAuthorizationTypeCamera = 1 << 2, ///< 相機受權
LXDAuthorizationTypePhoto = 1 << 3, ///< 相冊受權
LXDAuthorizationTypeAudio = 1 << 4, ///< 麥克風受權
LXDAuthorizationTypeContacts = 1 << 5, ///< 通信錄受權
};
複製代碼
typedef NS_OPTIONS(NSUInteger, UIViewAutoresizing) {
UIViewAutoresizingNone = 0,
UIViewAutoresizingFlexibleLeftMargin = 1 << 0,
UIViewAutoresizingFlexibleWidth = 1 << 1,
UIViewAutoresizingFlexibleRightMargin = 1 << 2,
UIViewAutoresizingFlexibleTopMargin = 1 << 3,
UIViewAutoresizingFlexibleHeight = 1 << 4,
UIViewAutoresizingFlexibleBottomMargin = 1 << 5
};
複製代碼
太多了,我就不一一列舉了。其實咱們在有些狀況下也能夠參考這樣的設計。 例如
typedef enum {
OptionsOne = 1<<0, // 0b0001
OptionsTwo = 1<<1, // 0b0010
OptionsThree = 1<<2, // 0b0100
OptionsFour = 1<<3 // 0b1000
} Options
- (void)setOptions:(Options)options
{
if (options & OptionsOne) {
NSLog(@"包含了OptionsOne");
}
if (options & OptionsTwo) {
NSLog(@"包含了OptionsTwo");
}
if (options & OptionsThree) {
NSLog(@"包含了OptionsThree");
}
if (options & OptionsFour) {
NSLog(@"包含了OptionsFour");
}
}
調用上面方法
[self setOptions: OptionsOne | OptionsFour];
複製代碼
最後咱們在看一下isa結構吧
nonpointer
:0,表明普通的指針,存儲着Class、Meta-Class對象的內存地址;1,表明優化過,使用位域存儲更多的信息has_assoc
:是否有設置過關聯對象,若是沒有,釋放時會更快shiftcls
:存儲着Class、Meta-Class對象的內存地址信息magic
:用於在調試時分辨對象是否未完成初始化weakly_referenced
:是否有被弱引用指向過,若是沒有,釋放時會更快deallocating
:對象是否正在釋放extra_rc
:裏面存儲的值是引用計數器has_sidetable_rc
:引用計數器是否過大沒法存儲在isa中;若是爲1,那麼引用計數會存儲在一個叫SideTable的類的屬性中第三條解釋不知道爲啥違反政治安全問題了,不讓寫,只能截圖了
咱們先來總體的看一下結構
isa指針
、superClass
、cache方法緩存
、bits具體的類信息
bits & FAST_DATA_MASK
指向一個新的結構體Class_rw_t
,裏面包含着methods方法列表
、properties屬性列表
、protocols協議列表
、class_ro_t類的初始化信息
等一些類信息Class_rw_t Class_rw_t
裏面的methods方法列表
、properties屬性列表
都是二維數組,是可讀可寫的,包含類的初始內容
,分類的內容
class_ro_t
class_ro_t
裏面的baseMethodList,baseProtocols,Ivars,baseProperties是一維數組,是只讀的,包含類的初始化內容
method_t
method_t
是對方法的封裝
struct method_t{
SEL name;//函數名
const char *types;//編碼(返回值類型,參數類型)
IMP imp;//指向函數的指針(函數地址)
}
複製代碼
IMP
IMP表明函數的具體實現
typedef id _Nullable (*IMP)(id _Nonnull, SEL _Nonnull, ...);
複製代碼
第一個參數是指向self的指針(若是是實例方法,則是類實例的內存地址;若是是類方法,則是指向元類的指針),第二個參數是方法選擇器(selector)
SEL
SEL表明方法名,通常叫作選擇器,底層結構跟char *
相似
@selector()
和sel_registerName()
得到sel_getName()
和NSStringFromSelector()
轉成字符串typedef struct objc_selector *SEL
types
types包含了函數返回值,參數編碼的字符串
結構爲:返回值 參數1 參數2...參數N
iOS中提供了一個叫作@encode
的指令,能夠將具體的類型表示成字符串編碼
例如
// "i24@0:8i16f20"
// 0id 8SEL 16int 20float == 24
- (int)test:(int)age height:(float)height
複製代碼
每個方法都有兩個默認參數self
和_msg
咱們能夠查到id
類型爲@
,SEL
類型爲:
i
返回值@
是id 類型的self
:
是SEL 類型的_msg
i
是Int age
f
是float height
其中加載的數字實際上是跟所佔字節有關
24
總共佔有多少字節@0
是id 類型的self
的起始位置爲0:8
是由於id 類型的self
佔字節爲8,因此SEL 類型的_msg`的起始位置爲8Class內部結構中有一個方法緩存cache_t
,用散列表(哈希表)來緩存曾經調用過的方法,能夠提升方法的查找速度。
cache_t
結構體裏面有三個元素
buckets
散列表,是一個數組,數組裏面的每個元素就是一個bucket_t
,bucket_t
裏面存放兩個
_key
SEL做爲key_imp
函數的內存地址_mask
散列表的長度
_occupied
已經緩存的方法數量
爲何會用到方法緩存
這張圖片是咱們方法產找路徑,若是咱們的一個類有多個父類,須要調用父類方法,他的查找路徑爲
系統級方法
來講,其實仍是比較消耗資源的,爲了應對這個狀況。出現了方法緩存
,調用過的方法,都放在緩存列表中,下次查找方法的時候,如今緩存中查找,若是緩存中查找不到,而後在執行上面的方法查找流程。散列表結構
散列表的結構大概就像上面那樣,數組的下標是經過@selector(方法名)&_mask
來求得,具體每個數組的元素是一個結構體,裏面包含兩個元素_imp
和@selector(方法名)做爲的key
咱們在上一篇文章中知道,一個值與&上一個_mask
,得出的結果必定小於等於_mask
值,而_mask
值爲數組長度-1,因此任什麼時候候,也不會越界。
其實這就是散列表的算法,也有一些其餘的算法,取餘
,一個值取餘
和&
的效果是相同的。
可是這實際上是有幾個疑慮的
_mask
是多少? - 初始_mask
我簡單了嘗試了一下,第一次可能給3_mask
值了怎麼辦 - 隨着方法的增多,方法數量確定會超過_mask
,這個時候會清空緩存散列表,而後_mask
*2&_mask
的值相同了怎麼辦 - 若是兩個值&_mask
的值相同時,第二個&
減一,知道找到空值,若是減到0尚未找到空位置,那就放在最大位置cach_t
的數組位置怎麼處理
NULL
源碼查看 咱們在objc-cache.mm
文件中查找bucket_t * cache_t::find(cache_key_t k, id receiver)
方法。
bucket_t * cache_t::find(cache_key_t k, id receiver)
{
assert(k != 0);
bucket_t *b = buckets();
mask_t m = mask();
mask_t begin = cache_hash(k, m);
mask_t i = begin;
do {
if (b[i].key() == 0 || b[i].key() == k) {
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)k, cls);
}
複製代碼
計算index值
mask_t begin = cache_hash(k, m);
複製代碼
這個方式是計算下標的,咱們點擊進入查看具體實現,就是@selector(方法名)&_mask
當兩個值求的下標相同時
(i = cache_next(i, m)) != begin
複製代碼
具體實現爲
arm64
和x86
實現方法不同
這裏有一個MJ老師
封裝的可以查看對象各類屬性的方法,想要使用的能夠在這裏查看
OC中的方法調用,其實都是轉化爲objc_msgSend
函數的調用,objc_msgSend
的執行流程能夠分爲3大階段
消息發送流程是咱們平時最常用的流程,其餘的像動態方法解析
和消息轉發
實際上是補救措施。具體流程以下
receiver
是否爲nil,若是爲nil直接退出消息發送receiverClass
,首先在消息接受者receiverClass
的cache
中查找方法,若是找到方法,直接調用。若是找不到,往下進行receiverClass
的cache
中找到方法,則從receiverClass
的class_rw_t
中查找方法,若是找到方法,執行方法,並把該方法緩存到receiverClass
的cache
中;若是沒有找到,往下進行receiverClass
中找到方法,則經過superClass指針
找到superClass
,也是如今緩存中查找,若是找到,執行方法,並把該方法緩存到receiverClass
的cache
中;若是沒有找到,往下進行superClass
的cache
中找到方法,則從superClass
的class_rw_t
中查找方法,若是找到方法,執行方法,並把該方法緩存到receiverClass
的cache
中;若是沒有找到,重複四、5步驟。若是找不到了superClass
了,往下進行superClass
也找不到該方法,則要轉到動態方法解析
開發者能夠實現如下方法,來動態添加方法實現
動態解析事後,會從新走「消息發送」的流程,從receiverClass的cache中查找方法這一步開始執行
咱們建立一個Person
類,而後在.h
文件中寫一個- (void)test
,可是不寫具體實現,而後調用。會打印出最多見的unrecognized selector sent to instance 0x100559b60
。
動態方法解析1
動態方法解析須要調用resolveInstanceMethod
或者resolveClassMethod
一個對應實例方法,一個對應類方法。咱們這裏是實例方法使用resolveInstanceMethod
咱們看一下resolveInstanceMethod
的解釋,在咱們須要執行動態方法解析
的時候咱們最好返回YES
。
- (void)other{
NSLog(@"%s",__func__);
}
+ (BOOL)resolveInstanceMethod:(SEL)sel{
if (sel == @selector(test)) {
//獲取其餘方法
Method method = class_getInstanceMethod(self, @selector(other));
//動態添加test的方法
class_addMethod(self, sel, method_getImplementation(method), method_getTypeEncoding(method));
}
return [super resolveInstanceMethod:sel];
}
@end
複製代碼
在class_addMethod
方法中咱們須要imp
,types
,可是OC並無提供相關屬性,全部咱們能夠調用相關方法來獲取相關參數
動態方法解析2
這裏咱們在隨便驗證一下method
的結構是否是這種
struct method_t {
SEL sel;
char *types;
IMP imp;
};
複製代碼
咱們代碼改爲這樣
struct method_t {
SEL sel;
char *types;
IMP imp;
};
+ (BOOL)resolveInstanceMethod:(SEL)sel{
if (sel == @selector(test)) {
//獲取其餘方法
struct method_t *method = (struct method_t *)class_getInstanceMethod(self, @selector(other));
//動態添加test的方法
class_addMethod(self, sel, method->imp, method->types);
return YES;
}
return [super resolveInstanceMethod:sel];
}
複製代碼
動態方法解析3
其實咱們還能夠用C語言驗證一下,提示:C語言中函數方法就是函數的地址
void c_other(id self, SEL _cmd)
{
NSLog(@"c_other - %@ - %@", self, NSStringFromSelector(_cmd));
}
+ (BOOL)resolveInstanceMethod:(SEL)sel{
if (sel == @selector(test)) {
class_addMethod(self, sel, (IMP)c_other, "v16@0:8");
return YES;
}
return [super resolveInstanceMethod:sel];
}
複製代碼
若是方法一個方法在消息發送階段
沒有找到相關方法,也沒有進行動態方法解析
,這個時候就會走到消息轉發階段了。
forwardingTargetForSelector
,返回值不爲nil時,會調用objc_msgSend(返回值, SEL)
methodSignatureForSelector
,返回值不爲nil,調用forwardInvocation:
方法;返回值爲nil時,調用doesNotRecognizeSelector:
方法forwardingTargetForSelector
咱們建立一個命令行項目,建立兩個類,person
和Student
,在person.h
裏面寫一個實例方法,可是不去實現相關方法。
@interface Person : NSObject
- (void)test;
@end
@interface Student : NSObject
- (void)test;
@end
#import "Student.h"
@implementation Student
- (void)test{
NSLog(@"%s",__func__);
}
@end
複製代碼
調用的時候回報出咱們最多見的錯誤unrecognized selector sent to instance 0x100747a50
若是咱們在person
裏面實現這個方法
- (id)forwardingTargetForSelector:(SEL)aSelector{
if (aSelector == @selector(test)) {
return [[Student alloc]init];
}
return nil;
}
複製代碼
調用forwardingTargetForSelector
,返回值不爲nil時,會調用objc_msgSend(返回值, SEL)
,結果就是調用了objc_msgSend(Student,test)
methodSignatureForSelector(方法簽名)
當forwardingTargetForSelector
返回值爲nil,或者都沒有調用該方法的時候,系統會調用methodSignatureForSelector
方法。調用methodSignatureForSelector
,返回值不爲nil,調用forwardInvocation:
方法;返回值爲nil時,調用doesNotRecognizeSelector:
方法
對於方法簽名的生成方式
[NSMethodSignature signatureWithObjCTypes:"i@:i"]
[[[Student alloc]init] methodSignatureForSelector:aSelector];
實現方法簽名之後咱們還要實現forwardInvocation
方法,當調用person
的test
的方法的時候,就會走到這個方法中
NSInvocation封裝了一個方法調用,包括:方法調用者、方法名、方法參數
咱們也能夠先執行NSLog(@"========");
在執行Student的test方法
- (void)forwardInvocation:(NSInvocation *)anInvocation
{
NSLog(@"========");
anInvocation.target = [[Student alloc]init];
[anInvocation invoke];
// [anInvocation invokeWithTarget:[[Student alloc] init]];
}
複製代碼
其中這兩個方法是同樣的 [anInvocation invokeWithTarget:[[Student alloc] init]];
anInvocation.target = [[Student alloc]init];
[anInvocation invoke];
複製代碼
其實這個方法仍是比較有用的,像網上一些對bug處理都會用到這個方法
Class objc_allocateClassPair(Class superclass, const char *name, size_t extraBytes)
動態建立一個類(參數:父類,類名,額外的內存空間)void objc_registerClassPair(Class cls)
註冊一個類(要在類註冊以前添加成員變量)void objc_disposeClassPair(Class cls)
銷燬一個類Class object_getClass(id obj)
獲取isa指向的ClassClass object_setClass(id obj, Class cls)
設置isa指向的ClassBOOL object_isClass(id obj)
判斷一個OC對象是否爲ClassBOOL class_isMetaClass(Class cls)
判斷一個Class是否爲元類Class class_getSuperclass(Class cls)
獲取父類我在方法緩存講過,在建立一個實例對象之後,裏面的成員變量就固定了,不能在修改了。所以咱們在用objc_registerClassPair
註冊類的時候,咱們必須把成員變量寫在註冊以前。 簡單使用,由於這裏面的都是runtime底層方法寫的,全部點語法和set方法都不可使用,若是想要遍歷裏面的屬性和方法仍是須要使用runtime
提供的方法
建立類
// 建立類
Class newClass = objc_allocateClassPair([NSObject class], "MJDog", 0);
class_addIvar(newClass, "_age", 4, 1, @encode(int));
class_addIvar(newClass, "_weight", 4, 1, @encode(int));
//註冊類
objc_registerClassPair(newClass);
// 成員變量的數量
unsigned int count;
Ivar *ivars = class_copyIvarList(newClass, &count);
for (int i = 0; i < count; i++) {
// 取出i位置的成員變量
Ivar ivar = ivars[i];
NSLog(@"%s %s", ivar_getName(ivar), ivar_getTypeEncoding(ivar));
}
free(ivars);
// 在不須要這個類時釋放
objc_disposeClassPair(newClass);
複製代碼
設置isa指向的Class
Person *p = [[Person alloc]init];
object_setClass(p, [Cat class]);
NSLog(@"%@",p);
複製代碼
Ivar class_getInstanceVariable(Class cls, const char *name)
獲取一個實例變量信息Ivar *class_copyIvarList(Class cls, unsigned int *outCount)
拷貝實例變量列表(最後須要調用free釋放)void object_setIvar(id obj, Ivar ivar, id value)
設置成員變量的值id object_getIvar(id obj, Ivar ivar)
獲取成員變量的值BOOL class_addIvar(Class cls, const char * name, size_t size, uint8_t alignment, const char * types)
動態添加成員變量(已經註冊的類是不能動態添加成員變量的)const char *ivar_getName(Ivar v), const char *ivar_getTypeEncoding(Ivar v)
獲取成員變量的相關信息最經常使用的方法就是獲取類的成員變量
unsigned int count;
Ivar *ivars = class_copyIvarList([Person class], &count);
for (int i = 0; i < count; i++) {
// 取出i位置的成員變量
Ivar ivar = ivars[i];
NSLog(@"%s %s", ivar_getName(ivar), ivar_getTypeEncoding(ivar));
}
free(ivars);
複製代碼
經常使用的方案
一、objc_property_t class_getProperty(Class cls, const char *name)
獲取一個屬性
二、objc_property_t *class_copyPropertyList(Class cls, unsigned int *outCount)
拷貝屬性列表(最後須要調用free釋放)
三、BOOL class_addProperty(Class cls, const char *name, const objc_property_attribute_t *attributes, unsigned int attributeCount)
動態添加屬性
四、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)
獲取屬性的一些信息
一、得到一個實例方法、類方法 - Method class_getInstanceMethod(Class cls, SEL name)
- Method class_getClassMethod(Class cls, SEL name)
二、方法實現相關操做 - IMP class_getMethodImplementation(Class cls, SEL name)
- IMP method_setImplementation(Method m, IMP imp)
- void method_exchangeImplementations(Method m1, Method m2)
三、拷貝方法列表(最後須要調用free釋放)
Method *class_copyMethodList(Class cls, unsigned int *outCount)
四、動態添加方法
BOOL class_addMethod(Class cls, SEL name, IMP imp, const char *types)
五、動態替換方法
IMP class_replaceMethod(Class cls, SEL name, IMP imp, const char *types)
六、選擇器相關
const char *sel_getName(SEL sel)
SEL sel_registerName(const char *str)
七、用block做爲方法實現
IMP imp_implementationWithBlock(id block)
id imp_getBlock(IMP anImp)
BOOL imp_removeBlock(IMP anImp)
最多見的就是動態方法交換
Method runMethod = class_getInstanceMethod([Person class], @selector(run));
Method testMethod = class_getInstanceMethod([Person class], @selector(test));
method_exchangeImplementations(runMethod, testMethod)
複製代碼
還有一個方法替換
MJPerson *person = [[Person alloc] init];
// class_replaceMethod([Person class], @selector(run), (IMP)myrun, "v");
class_replaceMethod([Person class], @selector(run), imp_implementationWithBlock(^{
NSLog(@"123123");
}), "v");
[person run];
複製代碼
咱們常常會看一些面試題,可是好多面試題咱們都是知其然不知其因此然,你若是認真的看了我上面總結的幾十篇文章,那麼你也會知其因此然。