最近從公司離職了,準備了一下接下來的面試,翻出了J_Knight的高級面試題複習了一下面試html
iOS 基礎題ios
分類和擴展有什麼區別?能夠分別用來作什麼?分類有哪些侷限性?分類的結構體裏面有哪些成員?c++
區別: extension在編譯期決議 它伴隨類的產生而產生,亦隨之一塊兒消亡 category則徹底不同,它是在運行期決議的 extension能夠添加實例變量,而category是沒法添加實例變量的(由於在運行期,對象的內存佈局已經肯定,若是添加實例變量就會破壞類的內部佈局,這對編譯型語言來講是災難性的)。面試
分類應用 能夠把類的實現分開在幾個不一樣的文件裏面。這樣作有幾個顯而易見的好處,編程
1:能夠減小單個文件的體積緩存
2:能夠把不一樣的功能組織到不一樣的category裏安全
3:能夠由多個開發者共同完成一個類bash
4:能夠按需加載想要的category 等等。markdown
5:聲明私有方法數據結構
擴展應用 extension通常用來隱藏類的私有信息
struct category_t {
constchar*name;//類的名字(name)
classref_t cls;//類(cls)
struct method_list_t *instanceMethods; //category中全部給類添加的實例方法的列表(instanceMethods)
structmethod_list_t *classMethods;//category中全部添加的類方法的列表(classMethods)
structprotocol_list_t *protocols; //category實現的全部協議的列表(protocols)
structproperty_list_t *instanceProperties;//category中添加的全部屬性(instanceProperties)
};
複製代碼
講一下atomic的實現機制;爲何不能保證絕對的線程安全(最好能夠結合場景來講)?
atomic只是保證了getter和setter存取方法的線程安全,並不能保證整個對象是線程安全的,所以在多線程編程時,線程安全還須要開發者本身來處理.關 於選擇:atomic系統生成的getter、setter會保證get、set操做的安全性,但相對nonatomic來講,atomic要更耗費資源,且速度要慢,故在iPhone等小型設備上,若是沒有多線程之間的通信,使用nonatomic是更好的選 atomic系統自動生成的getter/setter方法會進行加鎖操做 nonatomic系統自動生成的getter/setter方法不會進行加鎖操做
例如:線程1調用了某一屬性的setter方法並進行到了一半,線程2調用其getter方法,那麼會執行完setter操做後,在執行getter操做,線程2會獲取到線程1 setter後的完整的值. 當幾個線程同時調用同一屬性的setter、getter方法時,會get到一個完整的值,但get到的值不可控.例如:線程1 調用getter線程2 調用setter線程3 調用setter這3個線程並行同時開始,線程1會get到一個值,可是這個值不可控,多是線程2,線程3 set以前的原始值,多是線程2set的值,也多是線程3 set的值
被weak修飾的對象在被釋放的時候會發生什麼?是如何實現的?知道sideTable麼?裏面的結構能夠畫出來麼?
https://blog.csdn.net/future_one/article/details/81606895 。 淺談iOS之weak底層實現原理
https://www.jianshu.com/p/f331bd5ce8f8 淺談iOS之weak底層實現原理
複製代碼
關聯對象有什麼應用,系統如何管理關聯對象?其被釋放的時候須要手動將全部的關聯對象的指針置空麼?
1.2 如何關聯對象
runtime提供了給咱們3個API以管理關聯對象(存儲、獲取、移除):
123456 //關聯對象void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy)//獲取關聯的對象id objc_getAssociatedObject(id object, const void *key)//移除關聯的對象void objc_removeAssociatedObjects(id object)
其中的參數
id object:被關聯的對象
const void *key:關聯的key,要求惟一
id value:關聯的對象
objc_AssociationPolicy policy:內存管理的策略
void objc_removeAssociatedObjects(id object);,會移除全部的關聯,包括其餘模塊添加的,所以應該用 objc_setAssociatedObject(..,nil,..) 的方式去卸載。
蘋果官方文檔說 OBJC_ASSOCIATION_ASSIGN 至關於一個 weak reference,但其實等於 assign/unsafe_unretained。
對於與weak的區別不在本文討論範圍內,淺顯的區別在於變量釋放後,weak 會把引用置空,unsafe_unretained會保留內存地址,一旦獲取可能會野指針閃退。
詳情:https://www.jianshu.com/p/1feae48a5dda AssociatedObject關聯對象原理實現
複製代碼
KVO的底層實現?如何取消系統默認的KVO並手動觸發(給KVO的觸發設定條件:改變的值符合某個條件時再觸發KVO)?
KVO是經過isa-swizzling技術實現的(這句話是整個KVO實現的重點)。 在運行時根據原類建立一箇中間類,這個中間類是原類的子類, 並動態修改當前對象的isa指向中間類。而且將class方法重寫,返回原類的 Class。 1.在運行的時候建立被監類的子類 2.在子類中重寫父類屬性的set方法(故kvo之監聽屬性) 3註冊這個子類 4.修改當前被監聽的isa指針指向子類 5.實現set函數 在分析KVO的內部實現以前,先來分析一下KVO的存儲結構,主要用到了如下幾個類: GSKVOInfo GSKVOPathInfo GSKVOObservation @interface GSKVOInfo : NSObject { NSObject *instance; // Not retained. observer保存觀察者 注意這裏也是 Not retained 釋放以後,在調用會崩潰,須要在對象銷燬前,移除全部觀察者 GSLazyRecursiveLock *iLock; NSMapTable *paths; paths 用於保存keyPath 到 GSKVOPathInfo 的映射: } @interface GSKVOPathInfo : NSObject { @public unsigned recursion; unsigned allOptions; 保存了觀察者的options集合 NSMutableArray *observations; 保存了全部的觀察者(GSKVOObservation 類型) NSMutableDictionary *change; 保存了KVO觸發要傳遞的內容 } @interface GSKVOObservation : NSObject { @public NSObject *observer; // Not retained (zeroing weak pointer) void *context; 都是添加觀察者時傳入的參數 int options; 都是添加觀察者時傳入的參數 } @end KVO內部屢次用到了KVC 1️⃣ 重寫 setValue:forKey 2️⃣ 使用valueForKey --- valueForKeyPath獲取屬性的值,尤爲是在使用點語法的時候,只有valueForKeyPath能夠得到深層次的屬性值。 因此KVO是基於KVC而實現的。 詳細連接:https://www.jianshu.com/p/d6e4ba25acd2 複製代碼
Autoreleasepool所使用的數據結構是什麼?AutoreleasePoolPage結構體瞭解麼?
Autorelease pool的實現原理 Autorelease pool是有objc_autoreleasePoolpush和objc_autoreleasePoolpop實現 objc_autoreleasePoolpush和objc_autoreleasePoolpop是由AutoreleasePoolPage 實現的 class AutoreleasePoolPage { //大小4096 字節字節 雙向列列表實現的 magic_t const magic; 用於對當前 AutoreleasePoolPage 完整性的校驗 id *next; 指向了下一個爲空的內存地址 pthread_t const thread; /當前所在的線程 AutoreleasePoolPage * const parent; 頭節點 AutoreleasePoolPage *child; 尾節點 。 uint32_t const depth; 深度 uint32_t hiwat; POOL_SENTINEL(哨兵對象) id next next 指向了下一個爲空的內存地址,若是 next 指向的地址加入一個 object }; 在每一個自動釋放池初始化調用 objc_autoreleasePoolPush 的時候,都會把一個 POOL_SENTINEL push 到自動釋放池的棧頂,而且返回這個 POOL_SENTINEL 哨兵對象。 而當方法 objc_autoreleasePoolPop 調用時,就會向自動釋放池中的對象發送 release 消息,直到第一個 POOL_SENTINEL: 注意:在這裏會進入一個比較關鍵的方法 autoreleaseFast,並傳入哨兵對象 POOL_SENTINEL: void *objc_autoreleasePoolPush(void) { return AutoreleasePoolPage::push(); } static inline void *push() { return autoreleaseFast(POOL_SENTINEL); } static inline id *autoreleaseFast(id obj) { AutoreleasePoolPage *page = hotPage(); hotPage 能夠理解爲當前正在使用的 AutoreleasePoolPage。 if (page && !page->full()) { return page->add(obj); } 有 hotPage 而且當前 page 不滿,有 hotPage 而且當前 page 不滿 調用 page->add(obj) 方法將對象添加至 AutoreleasePoolPage 的棧中 else if (page) { return autoreleaseFullPage(obj, page); 有 hotPage 而且當前 page 已滿,調用 autoreleaseFullPage 初始化一個新的頁,調用 page->add(obj) 方法將對象添加至 AutoreleasePoolPage 的棧中 } else { return autoreleaseNoPage(obj); } 無 hotPage 調用 autoreleaseNoPage 建立一個 hotPage } id *add(id obj) { id *ret = next; *next = obj; next++; return ret; } 這個方法其實就是一個壓棧的操做,將對象加入 AutoreleasePoolPage 而後移動棧頂的指針 它會從傳入的 page 開始遍歷整個雙向鏈表,直到: 查找到一個未滿的 AutoreleasePoolPage 使用構造器傳入 parent 建立一個新的 AutoreleasePoolPage 在查找到一個可使用的 AutoreleasePoolPage 以後,會將該頁面標記成 hotPage,而後調動上面分析過的 page->add 方法添加對象。 既然當前內存中不存在 AutoreleasePoolPage,就要從頭開始構建這個自動釋放池的雙向鏈表,也就是說,新的 AutoreleasePoolPage 是沒有 parent 指針的。 初始化以後,將當前頁標記爲 hotPage,而後會先向這個 page 中添加一個 POOL_SENTINEL 對象,來確保在 pop 調用的時候,不會出現異常。 最後,將 obj 添加到自動釋放池中。 4.3 objc_autoreleasePoolPop 方法 static inline void pop(void *token) { AutoreleasePoolPage *page = pageForPointer(token); //使用 pageForPointer 獲取當前 token 所在的 AutoreleasePoolPage static AutoreleasePoolPage *pageForPointer(const void *p) { return pageForPointer((uintptr_t)p); } static AutoreleasePoolPage *pageForPointer(uintptr_t p) { AutoreleasePoolPage *result; uintptr_t offset = p % SIZE; assert(offset >= sizeof(AutoreleasePoolPage)); result = (AutoreleasePoolPage *)(p - offset); result->fastcheck(); return result; } pageForPointer 方法主要是經過內存地址的操做,獲取當前指針所在頁的首地址 將指針與頁面的大小,也就是 4096 取模,獲得當前指針的偏移量,由於全部的 AutoreleasePoolPage 在內存中都是對齊的 而最後調用的方法 fastCheck() 用來檢查當前的 result 是否是一個 AutoreleasePoolPage。 id *stop = (id *)token; page->releaseUntil(stop); // 調用 releaseUntil 方法釋放棧中的對象,直到 stop //它的實現仍是很容易的,用一個 while 循環持續釋放 AutoreleasePoolPage 中的內容,直到 next 指向了 stop。 if (page->child) { if (page->lessThanHalfFull()) { page->child->kill(); //調用 child 的 kill 方法 } else if (page->child->child) { page->child->child->kill(); } } } 整個自動釋放池 autoreleasepool 的實現以及 autorelease 方法都已經分析完了,咱們再來回顧一下文章中的一些內容: 自動釋放池是由 AutoreleasePoolPage 以雙向鏈表的方式實現的。 當對象調用 autorelease 方法時,會將對象加入 AutoreleasePoolPage 的棧中。 調用 AutoreleasePoolPage::pop 方法會向棧中的對象發送 release 消息。 做者:卡布達巨人 連接:https://juejin.cn/post/1 來源:掘金 複製代碼
講一下對象,類對象,元類,跟元類結構體的組成以及他們是如何相關聯的?爲何對象方法沒有保存的對象結構體裏,而是保存在類對象的結構體裏?
class_ro_t 和 class_rw_t 的區別?
class_rw_t 和 class_ro_t
ObjC 類中的屬性、方法還有遵循的協議等信息都保存在 class_rw_t 中:
struct class_rw_t {
uint32_t flags;
uint32_t version;
const class_ro_t *ro;
method_array_t methods;
property_array_t properties;
protocol_array_t protocols;
Class firstSubclass;
Class nextSiblingClass;
};
其中還有一個指向常量的指針 class_ro_t,其中存儲了當前類在編譯期就已經肯定的屬性、方法以及遵循的協議。
struct class_ro_t {
uint32_t flags;
uint32_t instanceStart;
uint32_t instanceSize;
uint32_t reserved;
const uint8_t * ivarLayout;
const char * name;
method_list_t * baseMethodList;
protocol_list_t * baseProtocols;
const ivar_list_t * ivars;
const uint8_t * weakIvarLayout;
property_list_t *baseProperties;
};
在編譯期間類的結構中的 class_data_bits_t *data 指向的是一個 class_ro_t * 指針:
isa 是指向元類的指針,不瞭解元類的能夠看:Classes and Metaclasses
super_class 指向當前類的父類
cache 用於緩存指針和 vtable,加速方法的調用
bits 就是存儲類的方法、屬性、遵循的協議等信息的地方
而後在加載 ObjC 運行時的過程當中在 realizeClass 方法中:
從 class_data_bits_t 調用 data 方法,將結果從 class_rw_t 強制轉換爲 class_ro_t 指針
初始化一個 class_rw_t 結構體
設置結構體 ro 的值以及 flag
最後設置正確的 data。
可是,在這段代碼運行以後 class_rw_t 中的方法,屬性以及協議列表均爲空。這時須要 realizeClass 調用 methodizeClass 方法來將類本身實現的方法(包括分類)、屬性和遵循的協議加載到 methods、 properties 和 protocols 列表中。
詳情:https://blog.csdn.net/fishmai/article/details/71157861
複製代碼
iOS 中內省的幾個方法?class方法和objc_getClass方法有什麼區別?
Class objc_getClass(const chat *aClassName)
1:Class objc_getClass(const chat *aClassName)
1> 傳入字符串類名
2> 返回對應的類對象
Class object_getClass(id obj)
2. Class object_getClass(id obj)
1> 傳入的obj多是instance對象,class對象、meta-class對象
2> 返回值
a:若是是instance對象,返回class對象
b:若是是class對象,返回meta-class對象
c:若是是meta-class對象,返回NSObject(基類)的meta-class對象
- (class)class、+(class)class
3:- (class)class、+(class)class
1>返回的就是類對象
結論:當obj爲實例變量時,object_getClass(obj)與[obj class]輸出結果一直,均得到isa指針,即指向類對象的指針。
總結:經上面初步的探索得知,object_getClass(obj)返回的是obj中的isa指針;而[obj class]則分兩種狀況:一是當obj爲實例對象時,[obj class]中class是實例方法:- (Class)class,返回的obj對象中的isa指針;二是當obj爲類對象(包括元類和根類以及根元類)時,調用的是類方法:+ (Class)class,返回的結果爲其自己。
做者:洲洲哥
連接:https://www.jianshu.com/p/5cfd52d222f0
來源:簡書
簡書著做權歸做者全部,任何形式的轉載都請聯繫做者得到受權並註明出處。
複製代碼
在運行時建立類的方法objc_allocateClassPair的方法名尾部爲何是pair(成對的意思)
動態建立類
動態建立類涉及到如下幾個函數:
12345678 // 建立一個新類和元類Class objc_allocateClassPair ( Class superclass, const char *name, size_t extraBytes );// 銷燬一個類及其相關聯的類void objc_disposeClassPair ( Class cls );// 在應用中註冊由objc_allocateClassPair建立的類void objc_registerClassPair ( Class cls );
objc_allocateClassPair函數:若是咱們要建立一個根類,則superclass指定爲Nil。extraBytes一般指定爲0,該參數是分配給類和元類對象尾部的索引ivars的字節數。
爲了建立一個新類,咱們須要調用objc_allocateClassPair。而後使用諸如class_addMethod,class_addIvar等函數來爲新建立的類添加方法、實例變量和屬性等。完成這些後,咱們須要調用objc_registerClassPair函數來註冊類,以後這個新類就能夠在程序中使用了。
實例方法和實例變量應該添加到類自身上,而類方法應該添加到類的元類上。
objc_disposeClassPair函數用於銷燬一個類,不過須要注意的是,若是程序運行中還存在類或其子類的實例,則不能調用針對類調用該方法。
http://www.cocoachina.com/ios/20141031/10105.html
https://blog.csdn.net/hypercode/article/details/53931517
複製代碼
一個int變量被__block修飾與否的區別?
Block的本質<一>
http://www.cocoachina.com/ios/20180910/24846.html
理清 Block 底層結構及其捕獲行爲 https://juejin.cn/post/6844903686615859213
iOS底層原理總結 - 探尋block的本質(一) http://www.cocoachina.com/ios/20180628/23965.html
iOS底層原理總結 - 探尋block的本質(二) http://www.cocoachina.com/ios/20180628/23968.html
iOS 看懂此文,你的block不再須要WeakSelf弱引用了! http://www.cocoachina.com/ios/20180110/21817.html
iOS中Block的用法,舉例,解析與底層原理(這多是最詳細的Block解析 . http://www.cocoachina.com/ios/20180424/23147.html
複製代碼
爲何在block外部使用__weak修飾的同時須要在內部使用__strong修飾?
關於使用__weak和__strong 你們都看到別人在block裏面使用self或者self的屬性的時候要使用__weak修飾self,而後才能block裏面使用,在block裏面使用的時候又將weakSelf使用__strong修飾進行使用,好比: __weak __typeof(self) weakSelf = self; self.block = ^{ __strong __typeof(self) strongSelf = weakSelf; [strongSelf doSomeThing]; [strongSelf doOtherThing]; }; 爲何使用weakSelf 經過 clang -rewrite-objc 源代碼文件名 將代碼轉爲c++代碼(實質是c代碼),能夠看到block是一個結構體,它會將全局變量保存爲一個屬性(是__strong的),而self強引用了block這會形成循環 引用。因此須要使用__weak修飾的weakSelf。 爲何在block裏面須要使用strongSelf 是爲了保證block執行完畢以前self不會被釋放,執行完畢的時候再釋放。這時候會發現爲何在block外邊使用了__weak修飾self,裏面使用__strong修飾weakSelf的時候不會發生循環引用?! PS:strongSelf只是爲了保證在block內部執行的時候不會釋放,但存在執行前self就已經被釋放的狀況,致使strongSelf=nil。注意判空處理。 不會引發循環引用的緣由 由於block截獲self以後self屬於block結構體中的一個由__strong修飾的屬性會強引用self, 因此須要使用__weak修飾的weakSelf防止循環引用。 block使用的__strong修飾的weakSelf是爲了在block(能夠理解爲函數)生命週期中self不會提早釋放。strongSelf實質是一個局部變量(在block這個「函數」裏面的局部變量),當block執行完畢就會釋放自動變量strongSelf,不會對self進行一直進行強引用。 總結 外部使用了weakSelf,裏面使用strongSelf卻不會形成循環,究其緣由就是由於weakSelf是block截獲的屬性,而strongSelf是一個局部變量會在「函數」執行完釋放。 複製代碼
RunLoop的做用是什麼?它的內部工做機制瞭解麼?(最好結合線程和內存管理來講)
https://juejin.cn/post/6844903604965523464 iOS底層原理探究-Runloop https://juejin.cn/post/6844903606932471822 RunLoop終極解析:輸入源,定時源,觀察者,線程間通訊,端口通訊,NSPort,NSMessagePort,NSMachPort,NSPortMessage https://juejin.cn/post/6844903598350925831 iOS底層原理總結 - RunLoop https://juejin.cn/post/1fc3ec8a0bb9f0065bd2889 iOS RunLoop 探究 http://www.cocoachina.com/ios/20180814/24550.html 老司機出品——源碼解析之RunLoop詳解 https://juejin.cn/post/6844903588712415239 iOS RunLoop詳解 http://www.cocoachina.com/ios/20180522/23447.html iOS開發·RunLoop源碼與用法徹底解析 http://www.cocoachina.com/ios/20180626/23932.html 深刻理解RunLoop 複製代碼
哪些場景能夠觸發離屏渲染?(知道多少說多少)
做者:J_Knight_ 連接:juejin.cn/post/684490… 來源:掘金 著做權歸做者全部。商業轉載請聯繫做者得到受權,非商業轉載請註明出處。