iOS底層原理(二):Runtime研究(一)
Objective-C 擴充套件了 C 語言,並加入了物件導向特性和 Smalltalk 式的訊息傳遞機制。而這個擴充套件的核心是一個用 C 和 編譯語言 寫的 Runtime 庫。它是 Objective-C 物件導向和動態機制的基石。
Objective-C 是一個動態語言,這意味著它不僅需要一個編譯器,也需要一個執行時系統來動態得建立類和物件、進行訊息傳遞和轉發。理解 Objective-C 的 Runtime 機制可以幫我們更好的瞭解這個語言,適當的時候還能對語言進行擴充套件,從系統層面解決專案中的一些設計或技術問題。一句話: 學好Runtime , iOS躺著走
# Runtime Versions and Platforms
There are different versions of the Objective-C runtime on different platforms.
## Legacy and Modern Versions
There are two versions of the Objective-C runtime—“modern” and “legacy”. The modern version was introduced with Objective-C 2.0 and includes a number of new features. The programming interface for the legacy version of the runtime is described in *Objective-C 1 Runtime Reference*; the programming interface for the modern version of the runtime is described in *[Objective-C Runtime Reference](https://developer.apple.com/documentation/objectivec/objective_c_runtime)*.
The most notable new feature is that instance variables in the modern runtime are “non-fragile”:
* In the legacy runtime, if you change the layout of instance variables in a class, you must recompile classes that inherit from it.
* In the modern runtime, if you change the layout of instance variables in a class, you do not have to recompile classes that inherit from it.
In addition, the modern runtime supports instance variable synthesis for declared properties (see [Declared Properties](https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/ObjectiveC/Chapters/ocProperties.html#//apple_ref/doc/uid/TP30001163-CH17) in *[The Objective-C Programming Language](https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/ObjectiveC/Introduction/introObjectiveC.html#//apple_ref/doc/uid/TP30001163)*).
## Platforms
iPhone applications and 64-bit programs on OS X v10.5 and later use the modern version of the runtime.
Other programs (32-bit programs on OS X desktop) use the legacy version of the runtime.
Runtime
其實有兩個版本: “modern
” 和 “legacy
”。我們現在用的 Objective-C 2.0
採用的是現行 (Modern
) 版的 Runtime
系統,只能執行在 iOS
和 macOS 10.5
之後的 64
位程式中。而 macOS
較老的32
位程式仍採用 Objective-C 1
中的(早期)Legacy
版本的 Runtime
系統。這兩個版本最大的區別在於當你更改一個類的例項變數的佈局時,在早期版本中你需要重新編譯它的子類,而現行版就不需要。
Runtime
基本是用 C
和彙編
寫的,可見蘋果為了動態系統的高效而作出的努力。你可以在這裡下到蘋果維護的開原始碼。蘋果和GNU各自維護一個開源的 runtime/GNUStep 版本,這兩個版本之間都在努力的保持一致。
平時的業務中主要是使用官方Api,解決我們框架性的需求。
高階程式語言
想要成為可執行檔案需要先編譯為組合語言再彙編為機器語言,機器語言也是計算機能夠識別的唯一語言,但是OC
並不能直接編譯為組合語言,而是要先轉寫為純C
語言再進行編譯和彙編的操作,從OC
到C
語言的過渡就是由runtime來實現的。然而我們使用OC
進行物件導向開發,而C
語言更多的是程式導向開發,這就需要將物件導向的類轉變為程式導向的結構體。
OK 我們先來看看與runtime 互動的三種方式:
-
OC 原生底層就是runtime 會在後臺執行 比如方法的實質就是訊息
對於大多數情況下,OC執行時系統自動的在後臺執行。你只需編寫和編譯OC程式碼就能使用它。
當你編譯包含OC類和方法的程式碼時,編譯器建立用來實現語言動態特性的資料結構體和方法呼叫。資料結構獲取類和類定義的資訊和協議中定義的資訊,包含了在《The Objective-C Programming Language》中對“ Defining a Class and Protocols”談論的類和協議的物件,以及方法選擇,例項變數模版,和其他蔥原始碼中提取出來的資訊。執行時主要的一個功能是傳送訊息,正如在Messaging 中的描述。它是由原始碼的訊息表示式呼叫的。 -
通過呼叫
NSObject
的方法 間接呼叫runtime
+ (BOOL)isSubclassOfClass:(Class)aClass;
+ (BOOL)instancesRespondToSelector:(SEL)aSelector;
+ (BOOL)conformsToProtocol:(Protocol *)protocol;
- (IMP)methodForSelector:(SEL)aSelector;
+ (IMP)instanceMethodForSelector:(SEL)aSelector;
- (void)doesNotRecognizeSelector:(SEL)aSelector;
- (BOOL)isKindOfClass:(Class)aClass;
- (BOOL)isMemberOfClass:(Class)aClass;
- (BOOL)conformsToProtocol:(Protocol *)aProtocol;
- (BOOL)respondsToSelector:(SEL)aSelector;
這裡給大家解釋一下: 以上方法都是在執行時會編譯成響應的方法:比如- (BOOL)respondsToSelector:(SEL)aSelector
我們看編譯會來到objc 的這裡
BOOL class_respondsToSelector(Class cls, SEL sel)
{
return class_respondsToSelector_inst(cls, sel, nil);
}
//繼續跟蹤 看到回來到下面的方法 ,會去查詢當前sel 對應的imp是否存在
bool class_respondsToSelector_inst(Class cls, SEL sel, id inst)
{
IMP imp;
if (!sel || !cls) return NO;
// Avoids +initialize because it historically did so.
// We're not returning a callable IMP anyway.
imp = lookUpImpOrNil(cls, sel, inst,
NO/*initialize*/, YES/*cache*/, YES/*resolver*/);
return bool(imp);
}
//下面這裡就是真正去查詢imp的方法,我會在注重介紹一下
IMP lookUpImpOrNil(Class cls, SEL sel, id inst,
bool initialize, bool cache, bool resolver)
{
IMP imp = lookUpImpOrForward(cls, sel, inst, initialize, cache, resolver);
if (imp == _objc_msgForward_impcache) return nil;
else return imp;
}
上面的兩部跳動,都是給下面的方法做鋪墊的,下面的方法也runtime
非常重要的方法,下面我們花點篇幅介紹一下
IMP lookUpImpOrForward(Class cls, SEL sel, id inst,
bool initialize, bool cache, bool resolver)
{
IMP imp = nil;
bool triedResolver = NO;
runtimeLock.assertUnlocked();
// 如果cache是YES,則從快取中查詢IMP。
if (cache) {
// 通過cache_getImp函式查詢IMP,查詢到則返回IMP並結束呼叫
imp = cache_getImp(cls, sel);
if (imp) return imp;
}
runtimeLock.read();
// 判斷類是否已經被建立,如果沒有被建立,則將類例項化
if (!cls->isRealized()) {
// Drop the read-lock and acquire the write-lock.
// realizeClass() checks isRealized() again to prevent
// a race while the lock is down.
runtimeLock.unlockRead();
runtimeLock.write();
// 對類進行例項化操作
realizeClass(cls);
runtimeLock.unlockWrite();
runtimeLock.read();
}
// 第一次呼叫當前類的話,執行initialize的程式碼
if (initialize && !cls->isInitialized()) {
runtimeLock.unlockRead();
// 對類進行初始化,並開闢記憶體空間
_class_initialize (_class_getNonMetaClass(cls, inst));
runtimeLock.read();
// 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.assertReading();
// 嘗試獲取這個類的快取
imp = cache_getImp(cls, sel);
if (imp) goto done;
{
// 如果沒有從cache中查詢到,則從方法列表中獲取Method
Method meth = getMethodNoSuper_nolock(cls, sel);
if (meth) {
// 如果獲取到對應的Method,則加入快取並從Method獲取IMP
log_and_fill_cache(cls, meth->imp, sel, inst, cls);
imp = meth->imp;
goto done;
}
}
// Try superclass caches and method lists.
{
unsigned attempts = unreasonableClassCount();
// 迴圈獲取這個類的快取IMP 或 方法列表的IMP
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
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);
goto 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_t物件。如果找到則快取查詢到的IMP
Method meth = getMethodNoSuper_nolock(curClass, sel);
if (meth) {
log_and_fill_cache(cls, meth->imp, sel, inst, curClass);
imp = meth->imp;
goto done;
}
}
}
// No implementation found. Try method resolver once.
// 如果沒有找到,則嘗試動態方法解析
if (resolver && !triedResolver) {
runtimeLock.unlockRead();
_class_resolveMethod(cls, sel, inst);
runtimeLock.read();
// 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 = (IMP)_objc_msgForward_impcache;
cache_fill(cls, sel, imp, inst);
done:
runtimeLock.unlockRead();
return imp;
}
lookUpImpOrForward
這個方法裡面篇幅很長裡面介紹了以下幾點:
- 如果cache是YES,則從快取中查詢IMP。這裡也就是說我們如果之前響應過的,在cache存過,就不需要下面的操作了
- 判斷類是否已經被建立,如果沒有被建立,則將類例項化
- 第一次呼叫當前類的話,執行initialize的程式碼
- 嘗試獲取這個類的快取 (這裡很多小夥伴就會質疑,為什麼還要取一次記憶體,要知道OC是動態語言,在我們執行這個獲取imp的時候,外界在開鎖,解鎖的時候是可以訪問的,動態操作)
- 如果沒有從cache中查詢到,則從方法列表中獲取Method
- 如果還沒有,就從父類快取或者方法列表獲取imp
- 如果沒有找到,則嘗試動態方法解析
- 如果沒有IMP被發現,並且動態方法解析也沒有處理,則進入訊息轉發階段
裡面還有關於
runtimeLock
執行時鎖,這裡加鎖了read()
對讀取,其中runtimeLock
是通過pthread_rwlock_t
實現的,更加底層的,大家如果感興趣鎖可以參考這篇互斥鎖-讀寫鎖-條件鎖
以上設計了訊息
,動態方法解析
,還有訊息轉發
,我們在接下來的篇幅中還會更加深入研究.我們繼續回來,第三種runtime
互動
- 直接呼叫
runtime
的API
API的介紹篇幅,我們就放到下一篇.
以上就是這篇文章的全部內容了,希望本文的內容對大傢俱有一定的參考學習價值,同時歡迎大家進入小編交流群:624212887,一起交流學習,謝謝大家的支援
相關文章
- iOS底層原理:Runtime研究,玩出新花樣iOS
- iOS底層原理探究-RuntimeiOS
- iOS底層原理總結 – 探尋Runtime本質(二)iOS
- iOS底層原理總結 - 探尋Runtime本質(二)iOS
- iOS底層原理總結 - 探尋Runtime本質(一)iOS
- iOS 開發:『Runtime』詳解(三)Category 底層原理iOSGo
- iOS底層面試題--RuntimeiOS面試題
- iOS底層原理總結 -- 利用Runtime原始碼 分析Category的底層實現iOS原始碼Go
- iOS底層原理總結 – 探尋Runtime本質(三)iOS
- iOS底層原理總結 - 探尋Runtime本質(四)iOS
- iOS底層原理總結 - 探尋Runtime本質(三)iOS
- runtime的底層原理和使用
- iOS底層原理 runtime - super、hook、以及簡單應用--(8)iOSHook
- iOS底層原理-CategoryiOSGo
- iOS底層原理 runtime-object_class拾遺基礎篇--(6)iOSObject
- iOS底層原理探究-RunloopiOSOOP
- 底層原理探究(二)RunLoopOOP
- Runtime底層原理探究(二) --- 訊息傳送機制(慢速查詢)
- iOS底層原理總結--OC物件的本質(二)iOS物件
- iOS底層原理總結 – RunLoopiOSOOP
- iOS底層原理總結 - RunLoopiOSOOP
- iOS底層原理 runtime- objc_msgSend拾遺基礎篇--(7)iOSOBJGse
- iOS底層原理總結 - 探尋block的本質(二)iOSBloC
- iOS底層原理 - Block本質探究iOSBloC
- Runtime底層原理探究(一) --- 訊息轉發機制(快速轉發)
- iOS底層原理總結--OC物件的本質(一)iOS物件
- php底層原理之變數(二)PHP變數
- 理解PHP底層原理(一)PHP
- IOS 底層原理 物件的本質--(1)iOS物件
- IOS 底層原理 類的本質--(2)iOS
- iOS底層原理 - 常駐執行緒iOS執行緒
- iOS Runtime 原理iOS
- Category:從底層原理研究到面試題分析Go面試題
- iOS底層原理總結 - 探尋block的本質(一)iOSBloC
- 二叉樹add底層原理二叉樹
- iOS底層原理總結 - 關聯物件實現原理iOS物件
- java學習----底層原理一Java
- iOS底層原理總結 - Category的本質iOSGo