OC本質底層都是C,C++代碼混合實現的--編譯彙編代碼--機器代碼前端
對象和類結構是基於C和C++中的結構體struct實現的。探究NSObject的本質,OC代碼轉換爲C和C++混合代碼。xcode用的編譯器前端是clang。xcode
由於1個NSObject對象對應1個結構體內只有1個isa指針,指針在iOS64位系統內佔8個字節,所以一個NSObject對象在內存裏是佔用1個指針的大小。類內部的方法和方法的實現存儲空間並不在對象內,obj指針就是isa地址其實就是結構體地址就是NSObject對象的地址。緩存
只要繼承自NSObject對象,結構中確定會有一個isa指針。bash
instance實例對象:alloc出來的。存放成員變量的值,isa。函數
class類對象 :class類型,[實例對像 class],object_getClass(object1),每個類在內存中有且只有一個class對象,存放對象方法信息,屬性信息,成員變量信息(名字等),協議信息,superClass指針,isa等。ui
metaclass元類對象 :也是class類型,每個類在內存中有且只有一個元類對象,在內存中和類對象結構同樣的,可是用途不同。object_getClass(類對象),object_getClass([NSObject class]);存放有用信息和class類對象不同,static類型成員變量,屬性多是空的,存放類方法,superClass指針,isa等。編碼
- 熟悉runtime的都知道,OC的方法調用其實應該叫消息傳遞,消息傳遞是動態綁定的機制來決定須要調用的方法;
[person age];
會被翻譯爲objc_msgSend(person, @selector(age));
。objc_msgSend
查找方法時,會先從Person緩存中查找,找到直接返回 (緩存是存在類中的,每一個類都有一份方法緩存struct objc_cache * _Nonnull cache OBJC2_UNAVAILABLE;
)。- 找不到,再去 Class 的方法列表中找。在 objc-runtime-new.mm 文件中有一個函數
lookUpImpOrForward
,這個函數的做用就是無緩存時去查找方法的實現。lookUpImpOrForward
並非objc_msgSend
直接調用的,而是經過_class_lookupMethodAndLoadCache3
方法。
IMP _class_lookupMethodAndLoadCache3(id obj, SEL sel, Class cls)
{
return lookUpImpOrForward(cls, sel, obj,
YES/*initialize*/, NO/*cache*/, YES/*resolver*/);
}
複製代碼
lookUpImpOrForward屬於源代碼層級的了,想要具體瞭解能夠直接 draveness.me/messagespa
cache_getImp
從某個類的cache 屬性中獲取對應的實現,若是查找到實現,跳轉到done。methodLists
找到對應的Method,最後找到method中的IMP,執行具體實現並添加到緩存。_objc_msgForward_impcache
實現會交給當前類處理。當前類中和父類中都沒有找到對應方法處理,系統會提供三次補救機會翻譯
+ (BOOL)resolveInstanceMethod:(SEL)sel {}
(實例方法) 和+ (BOOL)resolveClassMethod:(SEL)sel {}
(類方法)void myMethod(id self, SEL _cmd,NSString *nub) {
NSLog(@"ifelseagexx%@",nub);
};
+ (BOOL)resolveInstanceMethod:(SEL)sel
{
NSLog(@"Method Resolution");
if (sel == @selector(age)) { // 方法沒有被實現
class_addMethod([self class], sel, imp_implementationWithBlock(^() {
// 實現方法的代碼寫在這裏
}), "v@:");
return YES;
}
return [super resolveInstanceMethod:sel];
}
複製代碼
咱們只須要在resolveInstanceMethod:
方法中,利用class_addMethod
方法,將未實現的 age
綁定到(IMP)myMethod
上。這樣就能完成轉發,最後返回YES。若是實現了這個方法,系統就會從新啓動一次消息發送。指針
- (id)forwardingTargetForSelector:(SEL)aSelector {}
複製代碼
肯定是哪一個對象處理(找到該對象的方法名與消息中的選擇器的方法名一致的方法並調用)這個消息。使用場景通常是將 A 類的某個方法,轉發到 B 類的實現中去。
forwardingTargetForSelector:
若是實現這個方法時,返回值爲nil或者self即表明不處理消息。執行第三次:- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {};
- (void)forwardInvocation:(NSInvocation *)anInvocation {};
複製代碼
第一個要求返回一個方法簽名,第二個方法轉發具體的實現。兩者相互依賴,只有返回了正確的方法簽名,纔會執行第二個方法。
此次的轉發做用和第二次的比較相似,都是將 A 類的某個方法,轉發到 B 類的實現中去。不一樣的是,第三次的轉發相對於第二次更加靈活,forwardingTargetForSelector:
只能固定的轉發到一個對象;forwardInvocation:
可讓咱們轉發到多個對象中去。
tips:若是傳遞走到最後都沒有處理,系統就會崩潰並報錯:unrecognized selector sent to instance 0x7fea0ac2b0a0
NSKVONotifying_class
。
willChangeValueForKey:
原來的seter方法setClass:
didChangeValueForkey:
,(這個方法內部又會調用監聽器方法observeValueForKeyPath:ofObject:change:context:
)
在
addObserver:selector:name:object:
後手動調用willChangeValueForKey:
和didChangeValueForkey:
,能夠實現不改變屬性值手動觸發監聽KVO方法。注意:必須will和did一塊兒成對調用,猜想可能didChangeValueForkey
在觸發監聽方法的時候會檢測will方法有沒有被調用,進而成功觸發observeValueForKeyPath:ofObject:change:context:
。
NSKVONotifying_class
中其實除了會重寫原類的setClass:
方法(不會重寫get方法)外,經過class_copyMethodList()
能夠發現,class()
dealloc
_isKVOA
會新出如今NSKVONotifying_class
類對象的方法裏。由於子類重寫了KVO的class方法,[object class]
獲取的類對象仍是原類對象,object_getClass(object)
獲取到的是NSKVONotifying_class
類對象。KVC的全稱是Key-Value Coding,俗稱「鍵值編碼」,能夠經過一個Key來訪問某個屬性
- (void)setValue:(nullable id)value forKey:(NSString *)key;
- (nullable id)valueForKey:(NSString *)key;
- (void)setValue:(nullable id)value forKeyPath:(NSString *)keyPath;
- (nullable id)valueForKeyPath:(NSString *)keyPath;
setKey:``,_setKey:
順序尋找方法。若是有找到方法存在會傳遞參數直接調用這個方法。accessInstanceVariablesDirectly
方法的返回值。若是返回值爲NO,不容許直接返回成員變量,調用setValue:forUndefineKey:
並拋出異常。若是容許會去訪問成員變量,若是找到了成員變量會直接賦值(依然會觸發KVO,內部作了willChangeValueForKey:
和didChangeValueForkey:
),若是沒找到依然會拋出異常錯誤。