最近從公司離職了,準備了一下接下來的面試,翻出了J_Knight的高級面試題複習了一下面試html
iOS 基礎題ios
分類和擴展有什麼區別?能夠分別用來作什麼?分類有哪些侷限性?分類的結構體裏面有哪些成員?c++
區別: extension在編譯期決議 它伴隨類的產生而產生,亦隨之一塊兒消亡 category則徹底不同,它是在運行期決議的 extension能夠添加實例變量,而category是沒法添加實例變量的(由於在運行期,對象的內存佈局已經肯定,若是添加實例變量就會破壞類的內部佈局,這對編譯型語言來講是災難性的)。面試
分類應用 能夠把類的實現分開在幾個不一樣的文件裏面。這樣作有幾個顯而易見的好處,編程
1:能夠減小單個文件的體積緩存
2:能夠把不一樣的功能組織到不一樣的category裏安全
3:能夠由多個開發者共同完成一個類bash
4:能夠按需加載想要的category 等等。數據結構
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.im/post/5a66e28c6fb9a01cbf387da1
來源:掘金
複製代碼
講一下對象,類對象,元類,跟元類結構體的組成以及他們是如何相關聯的?爲何對象方法沒有保存的對象結構體裏,而是保存在類對象的結構體裏?
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.im/post/5bb09160f265da0adb30e30d
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.im/post/5af590c5f265da0b7964f1c2 iOS底層原理探究-Runloop
https://juejin.im/post/5afcf305f265da0b8e7f9b74 RunLoop終極解析:輸入源,定時源,觀察者,線程間通訊,端口通訊,NSPort,NSMessagePort,NSMachPort,NSPortMessage
https://juejin.im/post/5add46606fb9a07abf721d1d iOS底層原理總結 - RunLoop
https://juejin.im/post/58fc3ec8a0bb9f0065bd2889 iOS RunLoop 探究
http://www.cocoachina.com/ios/20180814/24550.html 老司機出品——源碼解析之RunLoop詳解
https://juejin.im/post/5aca2b0a6fb9a028d700e1f8 iOS RunLoop詳解
http://www.cocoachina.com/ios/20180522/23447.html iOS開發·RunLoop源碼與用法徹底解析
http://www.cocoachina.com/ios/20180626/23932.html 深刻理解RunLoop
複製代碼
哪些場景能夠觸發離屏渲染?(知道多少說多少)
做者:J_Knight_ 連接:juejin.im/post/5b5615… 來源:掘金 著做權歸做者全部。商業轉載請聯繫做者得到受權,非商業轉載請註明出處。