相信對於從事開發人員來講 runtime 這個名稱都不陌生,就像我起初只知道「 runtime 叫運行時 」,後來知道 runtime 一樣能夠像 KVC 同樣訪問私有成員變量,還有「 給類動態添加屬性:LNTextField.placeholderColor || 交換方法:imageNamed => ln_imageNamed 」,還有深刻的 「 消息機制的調用流程 || 字典轉模型 || 實現NSCoding歸解檔 」以及咱們常說的「黑魔法」 是什麼?git
runtime 是編程中比較難的模塊,想要深刻學習,這個模塊你必須掌握,一樣還有寫的另外一篇 runloop 模塊,下面是我對 runtime 的整理,從零開始,由淺入深,且帶了幾個 Runtime 實踐場景 --> 大廠來的工友們可選擇性路過。github
- runtime.h 釋義
- 消息機制
1.isa指針釋義
2.方法調用,是否真的是轉換爲消息機制?
2.objc_msgSend 參數概念釋義- 消息機制(方法調用流程)
- 常見做用
- 開發場景「工做掌握」
1.交換方法
2.給系統分類動態添加屬性
3.字典轉模型(Runtime 考慮三種狀況實現)- 其它做用「面試熟悉」
1.動態添加方法
2.動態變量控制
3.實現NSCoding的自動歸檔和解檔
4.runtime 部分函數
5.method swizzling(俗稱黑魔法)- 一道面試題的註解
- 模塊博文推薦(❤️數量較多)
- Runtime & Runloop 常面問題整理(附答案)
- Demo 重要的部分代碼中都有相應的註解和文字打印,運行程序能夠很直觀的表現
- iOS 模塊註解—「Runloop面試、工做」看我就 🐒 了 ^_^.
Objective-C 是基於 C 的,它爲 C 添加了面向對象的特性。它將不少靜態語言在編譯和連接時期作的事放到了 runtime 運行時來處理,能夠說 runtime 是咱們 Objective-C 幕後工做者。
1.runtime(簡稱運行時
),是一套 純C(C和彙編)寫的API。而 OC 就是運行時機制,也就是在運行時候的一些機制,其中最主要的是 消息機制。編程
2.對於 C 語言,函數的調用在編譯的時候會決定調用哪一個函數。數組
3.運行時機制原理:OC的函數調用稱爲消息發送,屬於 動態調用過程。在 編譯的時候 並不能決定真正調用哪一個函數,只有在真 正運行的時候 纔會根據函數的名稱找到對應的函數來調用。xcode
4.事實證實:在編譯階段,OC 能夠 調用任何函數,即便這個函數並未實現,只要聲明過就不會報錯,只有當運行的時候纔會報錯,這是由於OC是運行時動態調用的。而 C 語言 調用未實現的函數 就會報錯。緩存
咱們寫 OC 代碼,它在運行的時候也是轉換成了 runtime
方式運行的。任何方法調用本質:就是發送一個消息(用 runtime
發送消息,OC 底層實現經過 runtime
實現),每個 OC 的方法,底層必然有一個與之對應的 runtime
方法。
框架
消息機制原理:對象根據方法編號SEL去映射表查找對應的方法實現。
註解:
1.必需要導入頭文件 #import <objc/message.h>
2.咱們導入系統的頭文件,通常用尖括號。
3.OC 解決消息機制方法提示步驟【查找build setting
-> 搜索msg
-> objc_msgSend
(YES --> NO)】
4.最終生成消息機制,編譯器作的事情,最終代碼,須要把當前代碼用xcode從新編譯,【clang -rewrite-objc main.m
查看最終生成代碼】,示例:cd main.m --> 輸入前面指令,就會生成 .opp文件(C++代碼)
5.這裏通常不會直接導入<objc/runtime.h>
ide
示例代碼:OC 方法 <--> runtime 方法函數
面試:消息機制方法調用流程❓
怎麼去調用eat
方法,
對象方法:(保存到類對象的方法列表) ,類方法:(保存到元類(Meta Class
)中方法列表)。
1.OC 在向一個對象發送消息時,runtime 庫會根據對象的 isa指針找到該對象對應的類或其父類中查找方法。。
2.註冊方法編號(這裏用方法編號的好處,能夠快速查找)。
3.根據方法編號去查找對應方法。
4.找到只是最終函數實現地址,根據地址去方法區調用對應函數。
補充:一個objc 對象的 isa 的指針指向什麼?有什麼做用?
每個對象內部都有一個isa指針,這個指針是指向它的真實類型,根據這個指針就能知道未來調用哪一個類的方法。
上面也提到OC底層都是轉化爲runtime方式來實現的,類和類的實例(對象)都相對於的isa指針。
咱們能夠在Xcode中使用 [Shift+Cmd+O ] 快速打開文件objc.h 能看到類的定義:
總結:runtime 對象,類,元類的isa指針關係圖
一、每個對象本質上都是一個類的實例。其中類定義了成員變量和成員方法的列表。對象經過對象的isa指針指向所屬類。
二、每個類本質上都是一個對象,類實際上是元類(meteClass)的實例。元類定義了類方法的列表。類經過類的isa指針指向元類。
三、元類保存了類方法的列表。當類方法被調用時,先會從自己查找類方法的實現,若是沒有,元類會向他父類查找該方法。同時注意的是:元類(meteClass)也是類,它也是對象。元類經過isa指針最終指向的是一個根元類(root meteClass)。
四、根元類的isa指針指向自己,這樣造成了一個封閉的內循環。
場景:當第三方框架 或者 系統原生方法功能不能知足咱們的時候,咱們能夠在保持系統原有方法功能的基礎上,添加額外的功能。
需求:加載一張圖片直接用[UIImage imageNamed:@"image"];
是沒法知道到底有沒有加載成功。給系統的imageNamed
添加額外功能(是否加載圖片成功)。
方案一:繼承系統的類,重寫方法.(弊端:每次使用都須要導入)
方案二:使用 runtime,交換方法.
步驟:
1.給系統的方法添加分類
2.本身實現一個帶有擴展功能的方法
3.交換方法,只須要交換一次,
場景代碼:方法+調用+打印輸出
總結:
咱們所作的就是在方法調用流程第三步的時候,交換兩個方法地址指向。並且咱們改變指向要在系統的imageNamed:
方法調用前,因此將代碼寫在了分類的load
方法裏。最後當運行的時候系統的方法就會去找咱們的方法的實現。
場景:給系統的類添加額外屬性的時候,可使用runtime動態添加屬性方法。
原理:給一個類聲明屬性,其實本質就是給這個類添加關聯,並非直接把這個值的內存空間添加到類存空間。
註解:給系統 NSObject
添加一個分類,咱們知道在分類中是不可以添加成員屬性的,雖然咱們用了@property
,可是僅僅會自動生成get
和set
方法的聲明,並無帶下劃線的屬性和方法實現生成。可是咱們能夠經過runtime
就能夠作到給它方法的實現。
需求:給系統 NSObject 類動態添加屬性 name
字符串。
場景代碼:方法+調用+打印
總結:
其實,屬性賦值的本質,就是讓屬性與一個對象產生關聯,因此要給NSObject
的分類的name
屬性賦值就是讓name
和NSObject
產生關聯,而runtime
能夠作到這一點。
字典轉模型的方式:
key
一一對應。[<Status 0x7fa74b545d60> setValue:forUndefinedKey:]
報key
找不到的錯。key
不一一對應,系統就會調用setValue:forUndefinedKey:
報錯。setValue:forUndefinedKey:
,把系統的方法覆蓋,就能繼續使用KVC,字典轉模型了。思路:利用運行時,遍歷模型中全部屬性,根據模型的屬性名,去字典中查找key
,取出對應的值,給模型的屬性賦值(從提醒:字典中取值,不必定要所有取出來);提供一個NSObject分類,專門字典轉模型,之後全部模型均可以經過這個分類實現字典轉模型。
考慮狀況:
1.當字典的key
和模型的屬性匹配不上。
2.模型中嵌套模型(模型屬性是另一個模型對象)。
3.數組中裝着模型(模型的屬性是一個數組,數組中是一個個模型對象)。
key
和模型的屬性不對應的狀況。不對應有兩種,一種是字典的鍵值大於模型屬性數量,這時候咱們不須要任何處理,由於runtime
是先遍歷模型全部屬性,再去字典中根據屬性名找對應值進行賦值,多餘的鍵值對也固然不會去看了;另一種是模型屬性數量大於字典的鍵值對,這時候因爲屬性沒有對應值會被賦值爲nil
,就會致使crash
,咱們只需加一個判斷便可。考慮三種狀況下面一一註解runtime
的封裝,才能夠把一個模型中全部屬性遍歷出來。(我之因此看不懂,是MJ封裝了不少層而已^_^.)。示例:runtime 字典轉模型考慮三種狀況
註解:
這裏在獲取模型類中的全部屬性名,是採起 class_copyIvarList
先獲取成員變量(如下劃線開頭
) ,而後再處理成員變量名,字典中的key(去掉 _ ,從第一個角標開始截取
) 獲得屬性名。
緣由:
總結:
咱們既然能獲取到屬性類型,那就能夠攔截到模型的那個數組屬性,進而對數組中每一個模型遍歷並字典轉模型,可是咱們不知道數組中的模型都是什麼類型,咱們能夠聲明一個方法,該方法目的不是讓其調用,而是讓其實現並返回模型的類型。
這裏提到的你若是不是很清楚,建議參考個人Demo,重要的部分代碼中都有相應的註解和文字打印,運行程序能夠很直觀的表現。
場景:若是一個類方法很是多,加載類到內存的時候也比較耗費資源,須要給每一個方法生成映射表,可使用動態給某個類,添加方法解決。
註解:OC 中咱們很習慣的會用懶加載,當用到的時候纔去加載它,可是實際上只要一個類實現了某個方法,就會被加載進內存。當咱們不想加載這麼多方法的時候,就會使用到 runtime
動態的添加方法。
需求:runtime 動態添加方法處理調用一個未實現的方法 和 去除報錯。
場景代碼:方法+調用+打印輸出
若是你實現過自定義模型數據持久化的過程,那麼你也確定明白,若是一個模型有許多個屬性,那麼咱們須要對每一個屬性都實現一遍encodeObject
和 decodeObjectForKey
方法,若是這樣的模型又有不少個,這還真的是一個十分麻煩的事情。下面來看看簡單的實現方式。
假設如今有一個Movie
類,有3個屬性。先看下 .h文件
若是是正常寫法,.m 文件應該是這樣的:
若是這裏有100個屬性,那麼咱們也只能把100個屬性都給寫一遍嗎。
不過你會使用runtime
後,這裏就有更簡便的方法,以下。
這樣的方式實現,無論有多少個屬性,寫這幾行代碼就搞定了。
下面看看更加簡便的方法:兩句代碼搞定。
優化:
上面是encodeWithCoder
和 initWithCoder
這兩個方法抽成宏。咱們能夠把這兩個宏單獨放到一個文件裏面,這裏之後須要進行數據持久化的模型均可以直接使用這兩個宏。
1.runtime 部分函數
Objective-C
中調用一個方法,實際上是向一個對象發送消息,查找消息的惟一依據是selector
的名字。利用Objective-C
的動態特性,能夠實如今運行時偷換selector
對應的方法實現,達到給方法掛鉤的目的selector
的本質其實就是方法名,IMP
有點相似函數指針,指向具體的Method
實現,經過selector
就能夠找到對應的IMP
。method_exchangeImplementations
交換兩個方法的實現class_replaceMethod
替換方法的實現method_setImplementation
來直接設置某個方法的IMP
。這裏能夠參考簡友這篇:Runtime Method Swizzling開發實例彙總
下面的代碼輸出什麼?
先思考一下,會打印出來什麼❓
答案:都輸出 Son
class
獲取當前方法的調用者的類,superClass
獲取當前方法的調用者的父類,super
僅僅是一個編譯指示器,就是給編譯器看的,不是一個指針。super
這個標誌,就會讓當前對象去調用父類方法,本質仍是當前對象在調用這個題目主要是考察關於objc
中對 self
和 super
的理解:
self
是類的隱藏參數,指向當前調用方法的這個類的實例。而 super
本質是一個編譯器標示符,和 self
是指向的同一個消息接受者
當使用 self
調用方法時,會從當前類的方法列表中開始找,若是沒有,就從父類中再找;
而當使用 super
時,則從父類的方法列表中開始找。而後調用父類的這個方法
調用 [self class]
時,會轉化成 objc_msgSend
函數
做者 | Runtime 模塊推薦閱讀博文 |
---|---|
西木 | 完整總結 http://www.jianshu.com/p/6b905584f536 |
天口三水羊 | objc_msgSend http://www.jianshu.com/p/9e1bc8d890f9 |
夜千尋墨 | 詳解 http://www.jianshu.com/p/46dd81402f63 |
袁崢Seemygo | 快速上手 http://www.jianshu.com/p/e071206103a4 |
鄭欽洪_ | 實現自動化歸檔 http://www.jianshu.com/p/bd24c3f3cd0a |
HenryCheng | 消息機制 http://www.jianshu.com/p/f6300eb3ec3d |
賣報的小畫家Sure | Method Swizzling開發實例彙總 http://www.jianshu.com/p/f6dad8e1b848 |
滕大鳥 | OC最實用的runtime總結 http://www.jianshu.com/p/ab966e8a82e2 |
黑花白花 | Runtime在實際開發中的應用 http://www.jianshu.com/p/851b21870d91 |
同一個面試問題並不是只有一個答案,而同一個答案並非在任何面試場合都有效,關鍵在於應聘者掌握了規律後,對面試的具體狀況進行把握,有意識地揣摩面試官提出問題的心理 (真實問答),要 get 的到問的點,而後答其所問,算是「 投其所好 」吧。
摘錄:
http://www.jianshu.com/p/56e40ea56813
http://www.jianshu.com/p/f9eb6b315c08
01 / objc在向一個對象發送消息時,發生了什麼? |
參考1:根據對象的 isa 指針找到類對象 id,在查詢類對象裏面的 methodLists 方法函數列表,若是沒有在好到,在沿着 superClass ,尋找父類,再在父類 methodLists 方法列表裏面查詢,最終找到 SEL ,根據 id 和 SEL 確認 IMP(指針函數),在發送消息; |
02 / 問題:何時會報unrecognized selector錯誤?iOS有哪些機制來避免走到這一步? |
參考1:當發送消息的時候,咱們會根據類裏面的 methodLists 列表去查詢咱們要動用的SEL,當查詢不到的時候,咱們會一直沿着父類查詢,當最終查詢不到的時候咱們會報 unrecognized selector 錯誤,當系統查詢不到方法的時候,會調用 +(BOOL)resolveInstanceMethod:(SEL)sel 動態解釋的方法來給我一次機會來添加,調用不到的方法。或者咱們能夠再次使用 -(id)forwardingTargetForSelector:(SEL)aSelector 重定向的方法來告訴系統,該調用什麼方法,一來保證不會崩潰。 |
03 / 問題:可否向編譯後獲得的類中增長實例變量?可否向運行時建立的類中添加實例變量?爲何? |
參考1:一、不能向編譯後獲得的類增長實例變量 二、能向運行時建立的類中添加實例變量。 |
分析:1. 編譯後的類已經註冊在 runtime 中,類結構體中的 objc_ivar_list 實例變量的鏈表和 instance_size 實例變量的內存大小已經肯定,runtime會調用 class_setvarlayout 或 class_setWeaklvarLayout 來處理strong weak 引用.因此不能向存在的類中添加實例變量。2. 運行時建立的類是能夠添加實例變量,調用class_addIvar函數. 可是的在調用 objc_allocateClassPair 以後,objc_registerClassPair 以前,緣由同上. |
04 / 問題:runtime如何實現weak變量的自動置nil? |
參考1:runtime 對註冊的類, 會進行佈局,對於 weak 對象會放入一個 hash 表中。 用 weak 指向的對象內存地址做爲 key,當此對象的引用計數爲0的時候會 dealloc,假如 weak 指向的對象內存地址是a,那麼就會以a爲鍵, 在這個 weak 表中搜索,找到全部以a爲鍵的 weak 對象,從而設置爲 nil。 |
05 / 問題:給類添加一個屬性後,在類結構體裏哪些元素會發生變化? |
參考1:instance_size :實例的內存大小;objc_ivar_list *ivars:屬性列表 |
01 / 問題:runloop是來作什麼的?runloop和線程有什麼關係?主線程默認開啓了runloop麼?子線程呢? |
參考1:runloop: 從字面意思看:運行循環、跑圈,其實它內部就是do-while循環,在這個循環內部不斷地處理各類任務(好比Source、Timer、Observer)事件。runloop和線程的關係:一個線程對應一個RunLoop,主線程的RunLoop默認建立並啓動,子線程的RunLoop需手動建立且手動啓動(調用run方法)。RunLoop只能選擇一個Mode啓動,若是當前Mode中沒有任何Source(Sources0、Sources1)、Timer,那麼就直接退出RunLoop。 |
02 / 問題:runloop的mode是用來作什麼的?有幾種mode? |
參考1:model:是runloop裏面的運行模式,不一樣的模式下的runloop處理的事件和消息有必定的差異。系統默認註冊了5個Mode:(1)kCFRunLoopDefaultMode: App的默認 Mode,一般主線程是在這個 Mode 下運行的。(2)UITrackingRunLoopMode: 界面跟蹤 Mode,用於 ScrollView 追蹤觸摸滑動,保證界面滑動時不受其餘 Mode 影響。(3)UIInitializationRunLoopMode: 在剛啓動 App 時第進入的第一個 Mode,啓動完成後就再也不使用。(4)GSEventReceiveRunLoopMode: 接受系統事件的內部 Mode,一般用不到。(5)kCFRunLoopCommonModes: 這是一個佔位的 Mode,沒有實際做用。注意iOS 對以上5中model進行了封裝 NSDefaultRunLoopMode、NSRunLoopCommonModes |
03 / 問題:爲何把NSTimer對象以NSDefaultRunLoopMode(kCFRunLoopDefaultMode)添加到主運行循環之後,滑動scrollview的時候NSTimer卻不動了? |
參考1:nstime對象是在 NSDefaultRunLoopMode下面調用消息的,可是當咱們滑動scrollview的時候,NSDefaultRunLoopMode模式就自動切換到UITrackingRunLoopMode模式下面,卻不能夠繼續響應nstime發送的消息。因此若是想在滑動scrollview的狀況下面還調用nstime的消息,咱們能夠把nsrunloop的模式更改成NSRunLoopCommonModes. |
04 / 問題:蘋果是如何實現Autorelease Pool的? |
參考1:Autorelease Pool做用:緩存池,能夠避免咱們常常寫relase的一種方式。其實就是延遲release,將建立的對象,添加到最近的autoreleasePool中,等到autoreleasePool做用域結束的時候,會將裏面全部的對象的引用計數器 - autorelease. |
附上寫的小樣 Demo,重要的部分代碼中都有相應的註解和文字打印,運行程序能夠很直觀的表現