- Runtime 簡介
- Runtime 消息機制和相關函數
- Runtime 三次轉發流程
- Runtime 應用
- Runtime 面試題
Objective-C
是一個動態語言,這意味着它不只須要一個編譯器,也須要一個運行時系統來動態得建立類和對象、進行消息傳遞和轉發。Runtime
是 Objective-C
面向對象和動態機制的基石,能夠從系統層面解決一些設計或技術問題。它基本是用 C
和彙編寫的,屬於1個 C
語言庫,包含了不少底層的 C
語言 API
,如跟類、成員變量、方法相關的API。它的核心是 - 消息傳遞 ( Messaging
)。html
isa
指針定位對象的類,並以此爲起點肯定被調用的方法,方法和消息是動態綁定的。Runtime
交互Objective-C
從三種不一樣的層級上與 Runtime
系統進行交互:
Objective-C
源代碼Foundation
框架的 NSObject
類定義的方法runtime
函數的直接調用NSProxy
Cocoa
中大多數類都繼承於 NSObject
類,也就天然繼承了它的方法。最特殊的例外是 NSProxy
,它是個抽象超類,它實現了一些消息轉發有關的方法,能夠經過繼承它來實現一個其餘類的替身類或是虛擬出一個不存在的類。selector
是否是要忽略的。好比 Mac OS X 開發,有了垃圾回收就不理會 retain
, release
這些函數了。target
是否是 nil
對象。Objective-C
的特性是容許對一個 nil
對象執行任何一個方法不會 Crash
,由於會被忽略掉。IMP
,先從 cache
裏面找,完了找獲得就跳到對應的函數去執行。cache
找不到就找一下方法分發表。NSObject
類爲止。doesNotRecognizeSelector:
方法報 unrecognized selector
錯。[obj eat]
,編譯器轉成消息發送objc_msgSend(obj, eat)
,Runtime
時執行的流程是這樣的:
obj
的 isa
指針找到它的 class
class
的 method list
找 eat
class
中沒找到 eat
,繼續往它的 superclass
中找,一旦找到 eat
這個函數,就去執行它的實現IMP
<objc/runtime.h>
<objc/message.h>
objc_object
objc_class
Meta Class
objc_method
objc_selector
objc_cache
objc_category
id objc_msgSend ( id self, SEL op, ... );
複製代碼
objc_msgSend
第一個參數類型爲id,是一個指向類實例的指針typedef struct objc_object *id;
複製代碼
objc_selector
)objc_msgSend
第二個參數類型爲SEL,它是 selector
在 Objective-C
中的表示類型( Swift
中是 Selector
類)。selector
是方法選擇器,能夠理解爲區分方法的 ID
,而這個 ID
的數據結構是SEL
。能夠看到selector
是SEL
的一個實例typedef struct objc_selector *SEL;
複製代碼
@property SEL selector;
複製代碼
其實它就是個映射到方法的C字符串,你能夠用 Objc
編譯器命令 @selector()
或者 Runtime
系統的 sel_registerName
函數來得到一個 SEL
類型的方法選擇器。C
代碼的時候,常常會用到函數重載,就是函數名相同,參數不一樣,可是這在Objc
中是行不通的,由於selector
只記了 method
的 name
,沒有參數,因此無法區分不一樣的 method
。[[Person alloc] init]
objc_msgSend(objc_msgSend("Person" , "alloc"), "init")
objc_msgSend
第一個參數類型爲id
指向類實例的指針,即objc_object
ios
objc_object
結構體包含一個 isa
指針,類型爲 isa_t
聯合體。根據 isa
指向對象所屬的類。isa
這裏還涉及到 tagged pointer 等概念。由於 isa_t
使用 union
實現,因此可能表示多種形態,既能夠當成是指針,也能夠存儲標誌位置。git
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
指針不老是指向實例對象所屬的類,不能依靠它來肯定類型,而是應該用 class
方法來肯定實例對象的類。由於 KVO
的實現機理就是將被觀察對象的 isa
指針指向一箇中間類而不是真實的類,這是一種叫作 isa-swizzling
的技術。github
Objective-C
類是由 Class
類型來表示的,它其實是一個指向 objc_class
結構體的指針。面試
typedef struct objc_class *Class;
複製代碼
objc/runtime.h
中 objc_class
結構體的定義以下:objective-c
struct objc_class {
Class _Nonnull isa OBJC_ISA_AVAILABILITY;
#if !__OBJC2__
Class _Nullable super_class OBJC2_UNAVAILABLE;
const char * _Nonnull name OBJC2_UNAVAILABLE;
long version OBJC2_UNAVAILABLE;
long info OBJC2_UNAVAILABLE;
long instance_size OBJC2_UNAVAILABLE;
struct objc_ivar_list * _Nullable ivars OBJC2_UNAVAILABLE;
struct objc_method_list * _Nullable * _Nullable methodLists OBJC2_UNAVAILABLE;
struct objc_cache * _Nonnull cache OBJC2_UNAVAILABLE;
struct objc_protocol_list * _Nullable protocols OBJC2_UNAVAILABLE;
#endif
} OBJC2_UNAVAILABLE;
複製代碼
結構體裏保存了指向父類的指針、類的名字、版本、實例大小、實例變量列表、方法列表、緩存、遵照的協議列表等。json
對象在內存中的排布能夠當作一個結構體,該結構體的大小並不能動態變化,因此沒法在運行時動態給對象增長成員變量。相對的,對象的方法定義都保存在類的可變區域中。以下圖所示爲 Class
的描述信息,其中 methodList
爲可訪問類中定義的方法的指針的指針,經過修改該指針所指向的指針的值,咱們能夠實現爲類動態增長方法實現。數組
objc_class
繼承於 objc_object
,也就是說一個 Objective-C
類自己同時也是一個對象,咱們稱之爲類對象,類對象就是一個結構體 struct objc_class
,這個結構體存放的數據稱爲元數據。爲了處理類和對象的關係,runtime
庫建立了一種叫作元類 (Meta Class
) 的東西,類對象所屬類型就叫作元類,它用來表述類對象自己所具有的元數據。類方法就定義於此處,由於這些方法能夠理解成類對象的實例方法。每一個類僅有一個類對象,而每一個類對象僅有一個與之相關的元類。緩存
當你發出一個相似 [NSObject alloc]
的消息時,你事實上是把這個消息發給了一個類對象 (Class Object
) ,這個類對象必須是一個元類的實例,而這個元類同時也是一個根元類 (root meta class
) 的實例。全部的元類最終都指向根元類爲其超類。全部的元類的方法列表都有可以響應消息的類方法。因此當 [NSObject alloc]
這條消息發給類對象的時候,objc_msgSend()
會去它的元類裏面去查找可以響應消息的方法,若是找到了,而後對這個類對象執行方法調用。bash
元類(Meta Class
)是一個類對象的類。 在上面咱們提到,全部的類自身也是一個對象,咱們能夠向這個對象發送消息(即調用類方法)。 爲了調用類方法,這個類的 isa
指針必須指向一個包含這些類方法的一個 objc_class
結構體,這就引出了 meta-class
的概念。
類對象中的元數據存儲的都是如何建立一個實例的相關信息,那麼類對象和類方法應該從哪裏建立呢? 就是從 isa
指針指向的結構體建立,類對象的 isa
指針指向的咱們稱之爲元類(metaclass
),元類中保存了建立類對象以及類方法所需的全部信息。
Class
都有一個 isa
指針指向一個惟一的 Meta Class
Meta Class
的 isa
指針都指向最上層的 Meta Class
(圖中的 NSObject
的 Meta Class
)Meta Class
的 isa
指針指向本身,造成一個迴路Meta Class
的 super_class
指針指向它本來 Class
的 super_class
的 Meta Class
。可是最上層的 Meta Class
的 super_class
指向 NSObject Class
自己NSObject Class
的 super_class
爲 nil
,也就是它沒有超類
objc/runtime.h
:
typedef struct objc_method *Method;
struct objc_method {
SEL method_name OBJC2_UNAVAILABLE;
char *method_types OBJC2_UNAVAILABLE;
IMP method_imp OBJC2_UNAVAILABLE;
}
複製代碼
SEL method_name
: 方法名,相同名字的方法即便在不一樣類中定義,它們的方法選擇器也相同char *method_types
: 方法類型,是個char指針,其實存儲着方法的參數類型和返回值類型IMP method_imp
: 方法實現,本質上是一個函數指針在 iOS
的 Runtime
中,Method
經過 selector
和 IMP
兩個屬性,實現了快速查詢方法及實現,相對提升了性能,又保持了靈活性
cache
爲方法調用的性能進行優化。每一個消息都須要遍歷一次 isa
指向的類的方法列表(objc_method_list
),這樣效率過低了。Runtime
系統會把被調用的方法存到 cache
中( method_name
做爲key
,method_imp
做爲value
)。下次查找的時候會優先在 cache
中查找,效率更高。
objc_cache
是存在 objc_class
結構體中的。
cache_t
中 _buckets
、_mask
和 _occupied
:
struct cache_t {
struct bucket_t *_buckets;
mask_t _mask;
mask_t _occupied;
... 省略其餘方法
}
複製代碼
bucket_t
中存儲了 指針
與 IMP
的鍵值對:
struct bucket_t {
private:
cache_key_t _key;
IMP _imp;
public:
inline cache_key_t key() const { return _key; }
inline IMP imp() const { return (IMP)_imp; }
inline void setKey(cache_key_t newKey) { _key = newKey; }
inline void setImp(IMP newImp) { _imp = newImp; }
void set(cache_key_t newKey, IMP newImp);
};
複製代碼
Category
爲現有的類提供了拓展性,它是 category_t
一個指向分類的結構體的指針。
typedef struct category_t *Category;
複製代碼
struct category_t {
const char *name;
classref_t cls;
struct method_list_t *instanceMethods;
struct method_list_t *classMethods;
struct protocol_list_t *protocols;
struct property_list_t *instanceProperties;
};
複製代碼
name:是指 class_name 而不是 category_name。
cls:要擴展的類對象,編譯期間是不會定義的,而是在Runtime階段經過name對 應到對應的類對象。
instanceMethods:category中全部給類添加的實例方法的列表。
classMethods:category中全部添加的類方法的列表。
protocols:category實現的全部協議的列表。
instanceProperties:表示Category裏全部的properties,這就是咱們能夠經過objc_setAssociatedObject和objc_getAssociatedObject增長實例變量的緣由,不過這個和通常的實例變量是不同的。
複製代碼
從上邊category_t
的結構體中能夠看出,分類中能夠添加實例方法,類方法,甚至能夠實現協議,添加屬性,不能夠添加成員變量。
Ivar
是一種表明類中實例變量的類型。
typedef struct ivar_t *Ivar;
複製代碼
ivar_t
:
struct ivar_t {
int32_t *offset;
const char *name;
const char *type;
// alignment is sometimes -1; use alignment() instead
uint32_t alignment_raw;
uint32_t size;
uint32_t alignment() const {
if (alignment_raw == ~(uint32_t)0) return 1U << WORD_SHIFT;
return 1 << alignment_raw;
}
};
複製代碼
class_copyIvarList
函數獲取的不只有實例變量,還有屬性。但會在本來的屬性名前加上一個下劃線。
@property
標記了類中的屬性,它是一個指向objc_property
結構體的指針:
typedef struct property_t *objc_property_t;
複製代碼
能夠經過 class_copyPropertyList
和 protocol_copyPropertyList
方法來獲取類和協議中的屬性:
objc_property_t *class_copyPropertyList(Class cls, unsigned int *outCount)
objc_property_t *protocol_copyPropertyList(Protocol *proto, unsigned int *outCount)
複製代碼
返回類型爲指向指針的指針,由於屬性列表是個數組,每一個元素內容都是一個 objc_property_t
指針,而這兩個函數返回的值是指向這個數組的指針。
class_copyIvarList
和 class_copyPropertyList
對比:
- (void)runtimeGetPropertyList {
id RuntimeExploreInfo = objc_getClass("RuntimeExploreInfo");
unsigned int outCount, i;
objc_property_t *properties = class_copyPropertyList(RuntimeExploreInfo, &outCount);
for (i = 0; i < outCount; i++) {
objc_property_t property = properties[i];
fprintf(stdout, "runtimeGetPropertyList---%s %s\n", property_getName(property), property_getAttributes(property));
}
}
- (void)runtimeGetIvarList {
id RuntimeExploreInfo = objc_getClass("RuntimeExploreInfo");
unsigned int numIvars = 0;
Ivar *ivars = class_copyIvarList(RuntimeExploreInfo, &numIvars);
for(int i = 0; i < numIvars; i++) {
Ivar thisIvar = ivars[i];
const char *type = ivar_getTypeEncoding(thisIvar);
NSString *stringType = [NSString stringWithCString:type encoding:NSUTF8StringEncoding];
if (![stringType hasPrefix:@"@"]) {
continue;
}
fprintf(stdout, "runtimeGetIvarList---%s\n", ivar_getName(thisIvar));
}
}
複製代碼
打印結果:
就是指向最終實現程序的內存地址的指針。
typedef void (*IMP)(void /* id, SEL, ... */ );
複製代碼
它就是一個函數指針,這是由編譯器生成的。當你發起一個 Objective-C
消息以後,最終它會執行的那段代碼,就是由這個函數指針指定的。而 IMP 這個函數指針就指向了這個方法的實現。
你會發現 IMP
指向的方法與 objc_msgSend
函數類型相同,參數都包含 id
和 SEL
類型。每一個方法名都對應一個 SEL
類型的方法選擇器,而每一個實例對象中的 SEL
對應的方法實現確定是惟一的,經過一組 id
和 SEL
參數就能肯定惟一的方法實現地址;反之亦然。
進行一次發送消息會在相關的類對象中搜索方法列表,若是找不到則會沿着繼承樹向上一直搜索直到繼承樹根部(一般爲 NSObject
),若是仍是找不到而且消息轉發都失敗了就回執行 doesNotRecognizeSelector:
方法報 unrecognized selector
錯。
+resolveInstanceMethod:
, +resolveClassMethod:
forwardingTargetForSelector
methodSignatureForSelector:
, forwardInvocation:
Objective-C
運行時會調用 +resolveInstanceMethod:
或者 +resolveClassMethod:
,讓你有機會提供一個函數實現。若是你添加了函數並返回YES, 那運行時系統就會從新啓動一次消息發送的過程。
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
//執行foo函數
[self performSelector:@selector(foo:)];
}
+ (BOOL)resolveInstanceMethod:(SEL)sel {
if (sel == @selector(foo:)) {//若是是執行foo函數,就動態解析,指定新的IMP
class_addMethod([self class], sel, (IMP)fooMethod, "v@:");
return YES;
}
return [super resolveInstanceMethod:sel];
}
void fooMethod(id obj, SEL _cmd) {
NSLog(@"Doing foo");//新的foo函數
}
複製代碼
若是resolve
方法返回 NO
,運行時就會移到下一步 :forwardingTargetForSelector
若是目標對象實現了 -forwardingTargetForSelector:
,Runtime
這時就會調用這個方法,給你把這個消息轉發給其餘對象的機會。
Controller
:
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
[self performSelector:@selector(runtimeMessageTest)];
}
+ (BOOL)resolveInstanceMethod:(SEL)sel {
return YES; // 返回YES,進入下一步轉發
}
- (id)forwardingTargetForSelector:(SEL)aSelector {
if (aSelector == @selector(runtimeMessageTest)) {
return [RuntimeExploreInfo new]; // 返回RuntimeExploreInfo對象,讓RuntimeExploreInfon對象接收這個消息
}
return [super forwardingTargetForSelector:aSelector];
}
複製代碼
RuntimeExploreInfo
:
#import "RuntimeExploreInfo.h"
@implementation RuntimeExploreInfo
- (void)runtimeMessageTest {
NSLog(@"runtimeMessageTest---");
}
@end
複製代碼
經過 forwardingTargetForSelector
把當前 Controller
的方法轉發給了 RuntimeExploreInfo
去執行。
若是在上一步還不能處理未知消息,則惟一能作的就是啓用完整的消息轉發機制了。 首先它會發送 -methodSignatureForSelector:
消息得到函數的參數和返回值類型。若是 -methodSignatureForSelector:
返回 nil
,Runtime
則會發出 -doesNotRecognizeSelector:
消息,程序這時也就掛掉了。若是返回了一個函數簽名,Runtime
就會建立一個 NSInvocation
對象併發送 -forwardInvocation:
消息給目標對象。
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
[self performSelector:@selector(runtimeMessageTest)];
}
+ (BOOL)resolveInstanceMethod:(SEL)sel {
return YES; // 返回YES,進入下一步轉發
}
- (id)forwardingTargetForSelector:(SEL)aSelector {
return nil; // 返回nil,進入下一步轉發
}
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
if ([NSStringFromSelector(aSelector) isEqualToString:@"runtimeMessageTest"]) {
return [NSMethodSignature signatureWithObjCTypes:"v@:"]; // 簽名,進入forwardInvocation
}
return [super methodSignatureForSelector:aSelector];
}
- (void)forwardInvocation:(NSInvocation *)anInvocation {
SEL sel = anInvocation.selector;
RuntimeExploreInfo *p = [RuntimeExploreInfo new];
if([p respondsToSelector:sel]) {
[anInvocation invokeWithTarget:p];
}else {
[self doesNotRecognizeSelector:sel];
}
}
複製代碼
咱們實現了完整的轉發。經過簽名,Runtime
生成了一個對象 anInvocation
,發送給了 forwardInvocation
,咱們在 forwardInvocation
方法裏面讓 RuntimeExploreInfo
對象去執行了 runtimeMessageTest
函數。
Objective-C Associated Objects
)給分類增長屬性Method Swizzling
)方法添加和替換KVO
實現NSCoding
的自動歸檔和自動解檔MJExtension
、 YYModel
)Objective-C Associated Objects
)給分類增長屬性RuntimeExploreInfo+RuntimeAddProperty.h
添加了 phoneNum
屬性
#import "RuntimeExploreInfo+RuntimeAddProperty.h"
#import "objc/runtime.h"
@implementation RuntimeExploreInfo (RuntimeAddProperty)
static char kPhoneNumKey;
- (void)setPhoneNum:(NSString *)phoneNum {
objc_setAssociatedObject(self, &kPhoneNumKey, phoneNum, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
- (id)phoneNum {
return objc_getAssociatedObject(self, &kPhoneNumKey);
}
@end
複製代碼
- (void)runtimeAddProperty {
RuntimeExploreInfo *test = [RuntimeExploreInfo new];
test.phoneNum = @"12342424242";
NSLog(@"RuntimeAddProperty---%@", test.phoneNum);
}
複製代碼
Method Swizzling
)方法添加和替換和 KVO
實現/**
class_addMethod(Class _Nullable __unsafe_unretained cls, SEL _Nonnull name, IMP _Nonnull imp, const char * _Nullable types)
cls 被添加方法的類
name 添加的方法的名稱的SEL
imp 方法的實現。該函數必須至少要有兩個參數,self,_cmd
類型編碼
*/
class_addMethod([self class], sel, (IMP)fooMethod, "v@:");
複製代碼
class_replaceMethod
替換類方法的定義method_exchangeImplementations
交換兩個方法的實現method_setImplementation
設置一個方法的實現class_replaceMethod
試圖替換一個不存在的方法時候,會調用 class_addMethod
爲該類增長一個新方法+ (void)load {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
Class class = [self class];
SEL originalSelector = @selector(viewDidLoad);
SEL swizzledSelector = @selector(runtimeReplaceViewDidLoad);
Method originalMethod = class_getInstanceMethod(class, originalSelector);
Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);
//judge the method named swizzledMethod is already existed.
BOOL didAddMethod = class_addMethod(class, originalSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod));
// if swizzledMethod is already existed.
if (didAddMethod) {
class_replaceMethod(class, swizzledSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod));
}else {
method_exchangeImplementations(originalMethod, swizzledMethod);
}
});
}
- (void)runtimeReplaceViewDidLoad {
NSLog(@"替換的方法");
//[self runtimeReplaceViewDidLoad];
}
複製代碼
swizzling應該只在 +load
中執行一次( dispatch_once
)完成。在 Objective-C
的運行時中,每一個類有兩個方法都會自動調用。+load
是在一個類被初始裝載時調用,+initialize
是在應用第一次調用該類的類方法或實例方法前調用的。兩個方法都是可選的,而且只有在方法被實現的狀況下才會被調用。Apple 使用了 isa-swizzling
來實現 KVO
。當觀察對象A時,KVO
機制動態建立一個新的名爲:NSKVONotifying_A
的新類,該類繼承自對象A的本類,且 KVO
爲 NSKVONotifying_A
重寫觀察屬性的 setter
方法,setter
方法會負責在調用原 setter
方法以前和以後,通知全部觀察對象屬性值的更改狀況。
NSKVONotifying_A
類剖析
NSLog(@"self->isa:%@",self->isa);
NSLog(@"self class:%@",[self class]);
複製代碼
在創建KVO監聽前,打印結果爲:
self->isa:A
self class:A
複製代碼
在創建KVO監聽以後,打印結果爲:
self->isa:NSKVONotifying_A
self class:A
複製代碼
子類setter方法剖析:
KVO
的鍵值觀察通知依賴於 NSObject
的兩個方法: willChangeValueForKey:
和 didChangeValueForKey:
,在存取數值的先後分別調用 2 個方法: 被觀察屬性發生改變以前,willChangeValueForKey:
被調用,通知系統該 keyPath
的屬性值即將變動;當改變發生後, didChangeValueForKey:
被調用,通知系統該keyPath
的屬性值已經變動;以後, observeValueForKey:ofObject:change:context:
也會被調用。且重寫觀察屬性的 setter
方法這種繼承方式的注入是在運行時而不是編譯時實現的。
KVO 爲子類的觀察者屬性重寫調用存取方法的工做原理在代碼中至關於: - (void)setName:(NSString *)newName { [self willChangeValueForKey:@"name"]; //KVO 在調用存取方法以前總調用 [super setValue:newName forKey:@"name"]; //調用父類的存取方法 [self didChangeValueForKey:@"name"]; //KVO 在調用存取方法以後總調用 }
原理描述:用 runtime
提供的函數遍歷 Model
自身全部屬性,並對屬性進行 encode
和 decode
操做。
核心方法:在Model的基類中重寫方法:
- (id)initWithCoder:(NSCoder *)aDecoder {
if (self = [super init]) {
unsigned int outCount;
Ivar * ivars = class_copyIvarList([self class], &outCount);
for (int i = 0; i < outCount; i ++) {
Ivar ivar = ivars[i];
NSString * key = [NSString stringWithUTF8String:ivar_getName(ivar)];
[self setValue:[aDecoder decodeObjectForKey:key] forKey:key];
}
}
return self;
}
- (void)encodeWithCoder:(NSCoder *)aCoder {
unsigned int outCount;
Ivar * ivars = class_copyIvarList([self class], &outCount);
for (int i = 0; i < outCount; i ++) {
Ivar ivar = ivars[i];
NSString * key = [NSString stringWithUTF8String:ivar_getName(ivar)];
[aCoder encodeObject:[self valueForKey:key] forKey:key];
}
}
複製代碼
原理描述:用runtime提供的函數遍歷Model自身全部屬性,若是屬性在json中有對應的值,則將其賦值。
核心方法:在NSObject的分類中添加方法
- (instancetype)initWithDict:(NSDictionary *)dict {
if (self = [self init]) {
//(1)獲取類的屬性及屬性對應的類型
NSMutableArray * keys = [NSMutableArray array];
NSMutableArray * attributes = [NSMutableArray array];
unsigned int outCount;
objc_property_t * properties = class_copyPropertyList([self class], &outCount);
for (int i = 0; i < outCount; i ++) {
objc_property_t property = properties[i];
//經過property_getName函數得到屬性的名字
NSString * propertyName = [NSString stringWithCString:property_getName(property) encoding:NSUTF8StringEncoding];
[keys addObject:propertyName];
//經過property_getAttributes函數能夠得到屬性的名字和@encode編碼
NSString * propertyAttribute = [NSString stringWithCString:property_getAttributes(property) encoding:NSUTF8StringEncoding];
[attributes addObject:propertyAttribute];
}
//當即釋放properties指向的內存
free(properties);
//(2)根據類型給屬性賦值
for (NSString * key in keys) {
if ([dict valueForKey:key] == nil) continue;
[self setValue:[dict valueForKey:key] forKey:key];
}
}
return self;
}
複製代碼
Self & Super
@implementation Son : Father
- (id)init
{
self = [super init];
if (self)
{
NSLog(@"%@", NSStringFromClass([self class]));
NSLog(@"%@", NSStringFromClass([super class]));
}
return self;
}
@end
複製代碼
答案:都輸出 Son
解惑:這個題目主要是考察關於 objc
中對 self
和 super
的理解。
self
是類的隱藏參數,指向當前調用方法的這個類的實例。而 super
是一個 Magic Keyword
, 它本質是一個編譯器標示符,和 self
是指向的同一個消息接受者。上面的例子無論調用 [self class]
仍是 [super class]
,接受消息的對象都是當前 Son *xxx 這個對象。而不一樣的是,super
是告訴編譯器,調用 class
這個方法時,要去父類的方法,而不是本類裏的。
當使用 self
調用方法時,會從當前類的方法列表中開始找,若是沒有,就從父類中再找;
而當使用 super
時,則從父類的方法列表中開始找。而後調用父類的這個方法。
當調用 [self class]
時,實際先調用的是 objc_msgSend
函數,第一個參數是 Son
當前的這個實例,而後在 Son
這個類裏面去找 - (Class)class
這個方法,沒有,去父類 Father
裏找,也沒有,最後在 NSObject
類中發現這個方法。而 - (Class)class
的實現就是返回 self
的類別,故上述輸出結果爲 Son
。
當調用 [super class]
時,會轉換成 objc_msgSendSuper
函數。第一步先構造 objc_super
結構體,結構體第一個成員就是 self
。第二個成員是 (id)class_getSuperclass(objc_getClass(「Son」))
, 實際該函數輸出結果爲 Father
。第二步是去 Father
這個類裏去找 - (Class)class
,沒有,而後去 NSObject
類去找,找到了。最後內部是使用 objc_msgSend(objc_super->receiver
, @selector(class))
去調用,此時已經和 [self class]
調用相同了,故上述輸出結果仍然返回 Son
。
Object
& Class
& Meta Clas
@interface Sark : NSObject
@end
@implementation Sark
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
BOOL res1 = [(id)[NSObject class] isKindOfClass:[NSObject class]];
BOOL res2 = [(id)[NSObject class] isMemberOfClass:[NSObject class]];
BOOL res3 = [(id)[Sark class] isKindOfClass:[Sark class]];
BOOL res4 = [(id)[Sark class] isMemberOfClass:[Sark class]];
NSLog(@"%d %d %d %d", res1, res2, res3, res4);
}
return 0;
}
複製代碼
答案: 1 0 0 0
咱們看到在 Objective-C
的設計哲學中,一切都是對象。Class在設計中自己也是一個對象。而這個 Class
對象的對應的類,咱們叫它 Meta Class
。即 Class
結構體中的 isa
指向的就是它的 Meta Class
。
Meta Class
理解爲 一個 Class
對象的 Class
。簡單的說:
當咱們發送一個消息給一個 NSObject
對象時,這條消息會在對象的類的方法列表裏查找;
當咱們發送一個消息給一個類時,這條消息會在類的 Meta Class
的方法列表裏查找
消息 和 Category
@interface NSObject (Sark)
+ (void)foo;
@end
@implementation NSObject (Sark)
- (void)foo
{
NSLog(@"IMP: -[NSObject(Sark) foo]");
}
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
[NSObject foo];
[[NSObject new] foo];
}
return 0;
}
複製代碼
答案:
IMP: -[NSObject(Sark) foo]
IMP: -[NSObject(Sark) foo]
解釋:
objc runtime
加載完後,NSObject
的 Sark Category
被加載。而 NSObject
的 Sark Category
的頭文件 + (void)foo
並無實質參與到工做中,只是給編譯器進行靜態檢查,全部咱們編譯上述代碼會出現警告,提示咱們沒有實現 + (void)foo
方法。而在代碼編譯中,它已經被註釋掉了。Class
的 method list
的方法是 - (void)foo
,它是一個實例方法,因此加入到當前類對象 NSObject
的方法列表中,而不是 NSObject
Meta class
的方法列表中。[NSObject foo]
時,咱們看下整個 objc_msgSend
的過程:objc_msgSend
第一個參數是 (id)objc_getClass("NSObject")
,得到 NSObject Class
的對象。Meta Class
的方法列表中找,咱們在 load Category
方法時加入的是 - (void)foo
實例方法,因此並不在 NSOBject
Meta Class
的方法列表中super class
中找,NSObject
Meta Class
的 super class
是 NSObject
自己。因此,這個時候咱們可以找到 - (void)foo
這個方法。[[NSObject new] foo]
,咱們看下整個 objc_msgSend
的過程:[NSObject new]
生成一個 NSObject
對象。直接在該對象的類( NSObject
)的方法列表裏找。可以找到,因此正常輸出結果。成員變量與屬性
@interface Sark : NSObject
@property (nonatomic, copy) NSString *name;
@end
@implementation Sark
- (void)speak
{
NSLog(@"my name is %@", self.name);
}
@end
@interface Test : NSObject
@end
@implementation Test
- (instancetype)init
{
self = [super init];
if (self) {
id cls = [Sark class];
void *obj = &cls;
[(__bridge id)obj speak];
}
return self;
}
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
[[Test alloc] init];
}
return 0;
}
複製代碼
答案: my name is