J_Knight_ iOS 高級面試題 基礎題解答

最近從公司離職了,準備了一下接下來的面試,翻出了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
來源:掘金
複製代碼

講一下對象,類對象,元類,跟元類結構體的組成以及他們是如何相關聯的?爲何對象方法沒有保存的對象結構體裏,而是保存在類對象的結構體裏?

www.cnblogs.com/wsnb/p/6163…

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
複製代碼

哪些場景能夠觸發離屏渲染?(知道多少說多少)

  • shouldRasterize(光柵化)
  • masks(遮罩)
  • shadows(陰影)
  • edge antialiasing(抗鋸齒)
  • group opacity(不透明)
  • 複雜形狀設置圓角等
  • 漸變

做者:J_Knight_ 連接:juejin.im/post/5b5615… 來源:掘金 著做權歸做者全部。商業轉載請聯繫做者得到受權,非商業轉載請註明出處。

相關文章
相關標籤/搜索