網(wǎng)絡(luò)上搜集的iOS面試題

iOS基礎(chǔ)題


1. 分類(lèi)和擴(kuò)展有什么區(qū)別?可以分別用來(lái)做什么?分類(lèi)有哪些局限性?分類(lèi)的結(jié)構(gòu)體里面有哪些成員?
  • 分類(lèi)主要用來(lái)為某個(gè)類(lèi)添加方法,屬性,協(xié)議(我一般用來(lái)給系統(tǒng)的類(lèi)添加方法或者把某個(gè)復(fù)雜的類(lèi)按照功能拆分到不同的文件里)
  • 擴(kuò)展主要用來(lái)為某個(gè)類(lèi)添加成員變量、屬性、方法聲明。(我一般用擴(kuò)展來(lái)聲明私有屬性,或者把.h的只讀屬性重寫(xiě)成可讀寫(xiě)的)

分類(lèi)和擴(kuò)展的區(qū)別:

  • 分類(lèi)是在運(yùn)行時(shí)把分類(lèi)信息合并到類(lèi)信息中,而擴(kuò)展是在編譯時(shí),就把信息合并到類(lèi)中的
  • 分類(lèi)聲明的屬性,只會(huì)生成getter/setter方法的聲明,不會(huì)自動(dòng)生成成員變量和getter/setter方法的實(shí)現(xiàn),而擴(kuò)展會(huì)
  • 分類(lèi)不可用來(lái)為類(lèi)添加實(shí)例變量,而擴(kuò)展可以
  • 分類(lèi)可以為類(lèi)添加方法的實(shí)現(xiàn),而擴(kuò)展只能聲明方法,而不能實(shí)現(xiàn)

分類(lèi)的局限性:

  • 無(wú)法為類(lèi)添加實(shí)例變量,但可以通過(guò)關(guān)聯(lián)對(duì)象進(jìn)行實(shí)現(xiàn),注:關(guān)聯(lián)對(duì)象中內(nèi)存管理沒(méi)有weak,用時(shí)需要注意野指針的問(wèn)題,可以通過(guò)其他辦法來(lái)實(shí)現(xiàn),具體可參考iOS weak 關(guān)鍵字漫談
  • 分類(lèi)的方法若和類(lèi)中原本的實(shí)現(xiàn)重名,會(huì)覆蓋原本方法的實(shí)現(xiàn),注:并不是真正的覆蓋
  • 多個(gè)分類(lèi)的方法重名,會(huì)調(diào)用最后編譯的那個(gè)分類(lèi)的實(shí)現(xiàn)

分類(lèi)的結(jié)構(gòu)體有哪些成員:

struct category_t {
    const char *name; //名字
    classref_t cls; //類(lèi)的引用
    struct method_list_t *instanceMethods;//實(shí)例方法列表
    struct method_list_t *classMethods;//類(lèi)方法列表
    struct protocol_list_t *protocols;//協(xié)議列表
    struct property_list_t *instanceProperties;//實(shí)例屬性列表
    // 此屬性不一定真正的存在
    struct property_list_t *_classProperties;//類(lèi)屬性列表
};
2. 講一下atomic的實(shí)現(xiàn)機(jī)制;為什么不能保證絕對(duì)的線程安全(最好可以結(jié)合場(chǎng)景來(lái)說(shuō))?

atomic的實(shí)現(xiàn)機(jī)制:

  • atomic是property的修飾詞之一,表示是原子性的,使用方式為@property(atomic) int age;,此時(shí)編譯器會(huì)自動(dòng)生成getter/setter方法,最終會(huì)調(diào)用objc_getPropertyobjc_setProperty方法來(lái)進(jìn)行存取屬性。若此時(shí)屬性用atomic修飾的話,會(huì)在這兩個(gè)方法的內(nèi)部使用os_unfair_lock來(lái)進(jìn)行加鎖,來(lái)保證讀寫(xiě)的原子性。鎖都在PropertyLocks中保存著(在iOS平臺(tái)會(huì)初始化8個(gè),mac平臺(tái)64個(gè)),在用之前,會(huì)把鎖都初始化好,在需要用到時(shí),用對(duì)象的地址加上成員變量的偏移量為key,去PropertyLocks中去取。因此存取時(shí)用的時(shí)同一個(gè)鎖,所以atomic能保證屬性的存取時(shí)是線程安全的。注:由于鎖是有限的,不同對(duì)象,不同屬性的讀取用的也可能是同一個(gè)鎖

atomic為什么不能保證絕對(duì)的線程安全:

  • atomic在getter/setter方法中加鎖,僅保證了存取時(shí)的線程安全,假設(shè)我們的屬性是@property(atomic) NSMutableArray *array;可變?nèi)萜鲿r(shí),無(wú)法保證對(duì)容器的修改時(shí)線程安全的
  • 在編譯器自動(dòng)產(chǎn)生的getter/setter方法,最終會(huì)調(diào)用objc_getPropertyobjc_setProperty方法來(lái)進(jìn)行存取屬性,在此方法內(nèi)部保證了讀寫(xiě)時(shí)的線程安全,當(dāng)我們重寫(xiě)setter/getter方法時(shí),就只能依靠自己在getter/setter中保證線程安全
3. 被weak修飾的對(duì)象在被釋放的時(shí)候會(huì)發(fā)生什么?是如何實(shí)現(xiàn)的?知道sideTable么?里面的結(jié)構(gòu)可以畫(huà)出來(lái)么?

被weak修飾的對(duì)象在被釋放的時(shí)候會(huì)發(fā)生什么:

  • 會(huì)把weak指針自動(dòng)置為nil

weak是如何實(shí)現(xiàn)的:

  • runtime會(huì)把被weak修飾的對(duì)象放到一個(gè)全局的哈希表中,用weak修飾的對(duì)象的內(nèi)存地址為key,weak指針為值,在對(duì)象進(jìn)行銷(xiāo)毀時(shí),通過(guò)對(duì)象自身地址去哈希表中查找到所有指向此對(duì)象的weak指針,并把所有的weak指針置為nil

sideTable的結(jié)構(gòu):

struct SideTable {
    spinlock_t slock;//操作SideTable時(shí)用到的鎖
    RefcountMap refcnts;//引用計(jì)數(shù)器的值
    weak_table_t weak_table;//存放weak指針的哈希表
};
4.關(guān)聯(lián)對(duì)象有什么應(yīng)用,系統(tǒng)如何管理關(guān)聯(lián)對(duì)象?其被釋放的時(shí)候需要手動(dòng)將所有的關(guān)聯(lián)對(duì)象的指針置空么?

關(guān)聯(lián)對(duì)象有什么應(yīng)用:

  • 一般用于在分類(lèi)中給類(lèi)添加實(shí)例變量

系統(tǒng)如何管理關(guān)聯(lián)對(duì)象:

  • 首先系統(tǒng)中有一個(gè)全局AssociationsManager,里面有個(gè)AssociationsHashMap哈希表,哈希表中的key時(shí)對(duì)象的內(nèi)存地址,value是ObjectAssociationMap,也是一個(gè)哈希表,其中key是我們?cè)O(shè)置關(guān)聯(lián)對(duì)象所設(shè)置的key,value是ObjcAssociation,里面存放著關(guān)聯(lián)對(duì)象設(shè)置的值和內(nèi)存管理的策略。以void objc_setAssociatedObject(id object, const void * key, id value, objc_AssociationPolicy policy)為例,首先會(huì)通過(guò)AssociationManager獲取AssociationsHashMap,然后以object的內(nèi)存地址為key,從AssociationsHashMap中取出ObjectAssociationMap,若沒(méi)有,則新創(chuàng)建一個(gè),然后通過(guò)key獲取舊值,以及通過(guò)key和policy生成新值objcAssociation(policy, new_value),把新值存放到ObjectAssociationMap中,若新值不為nil,并且內(nèi)存管理策略為retain,則會(huì)對(duì)新值進(jìn)行一次retain,若新值為nil,則會(huì)刪除舊值,若舊值不為空并且內(nèi)存管理的策略是retain,則對(duì)舊值進(jìn)行一次release

其被釋放的時(shí)候需要手動(dòng)將所有的關(guān)聯(lián)對(duì)象的指針置空么:

  • 不需要,因?yàn)樵趯?duì)象的dealloc中,若發(fā)現(xiàn)對(duì)象有關(guān)聯(lián)對(duì)象時(shí),會(huì)調(diào)用_object_remove_associations方法來(lái)移除所有的關(guān)聯(lián)對(duì)象,并根據(jù)內(nèi)存策略,來(lái)判斷是否需要對(duì)關(guān)聯(lián)的對(duì)象的值進(jìn)行release
5. KVO的底層實(shí)現(xiàn)?如何取消系統(tǒng)默認(rèn)的KVO并手動(dòng)觸發(fā)(給KVO的觸發(fā)設(shè)定條件:改變的值符合某個(gè)條件時(shí)再觸發(fā)KVO)?

KVO的底層實(shí)現(xiàn):

  • 當(dāng)某個(gè)類(lèi)的屬性被觀察時(shí),系統(tǒng)會(huì)在運(yùn)行時(shí)動(dòng)態(tài)的創(chuàng)建一個(gè)該類(lèi)的子類(lèi),并且將對(duì)象的isa指向這個(gè)子類(lèi)
  • 假設(shè)被觀察的屬性名是name,若父類(lèi)里有setName:或者_setName:,那么在子類(lèi)里重寫(xiě)這兩個(gè)方法,若兩個(gè)方法同時(shí)存在,則只會(huì)重寫(xiě)setName:一個(gè)(這里和KVC的set搜索時(shí)的順序時(shí)一樣的)
  • 若被觀察的類(lèi)型是NSString,那么重寫(xiě)的方法的實(shí)現(xiàn)會(huì)指向_NSSetObjectValueAndNotify這個(gè)函數(shù),若是Bool類(lèi)型,那么重寫(xiě)的方法的實(shí)現(xiàn)會(huì)指向_NSSetBoolValueAndNotify這個(gè)函數(shù),這個(gè)函數(shù)里會(huì)調(diào)用willChangeValueForKey:didChangevlueForKey:,并且會(huì)在這兩個(gè)方法調(diào)用之間,調(diào)用父類(lèi)set方法的實(shí)現(xiàn)
  • 系統(tǒng)會(huì)在willChangeValueForKey:對(duì)observe里的change[old]賦值,取值是用valueForKey:取值的,didChangevlueForKey:對(duì)observe里的change[new]賦值,然后調(diào)用observe的這個(gè)方法- (void)observeValueForKeyPath:(nullable NSString *)keyPath ofObject:(nullable id)object change:(nullable NSDictionary<NSKeyValueChangeKey, id> *)change context:(nullable void *)context;
  • 當(dāng)使用KVC賦值的時(shí)候,在NSObject里的setValue:forKey:方法里,若父類(lèi)不存在setName:或這_setName:這些方法,會(huì)調(diào)用_NSSetValueAndNotifyForKeyInIvar這個(gè)函數(shù),這個(gè)函數(shù)里同樣也會(huì)調(diào)用willChangeValueForKey:didChangevlueForKey:,若存在則調(diào)用

如何取消系統(tǒng)默認(rèn)的KVO并手動(dòng)觸發(fā)(給KVO的觸發(fā)設(shè)定條件:改變的值符合某個(gè)條件時(shí)再觸發(fā)KVO):
舉例:取消Person類(lèi)age屬性的默認(rèn)KVO,設(shè)置age大于18時(shí),手動(dòng)觸發(fā)KVO

+ (BOOL)automaticallyNotifiesObserversForKey:(NSString *)key {
    if ([key isEqualToString:@"age"]) {
        return NO;
    }
    return [super automaticallyNotifiesObserversForKey:key];
}

- (void)setAge:(NSInteger)age {
    if (age > 18 ) {
        [self willChangeValueForKey:@"age"];
        _age = age;
        [self didChangeValueForKey:@"age"];
    } else {
        _age = age;
    }
}

6. Autoreleasepool所使用的數(shù)據(jù)結(jié)構(gòu)是什么?AutoreleasePoolPage結(jié)構(gòu)體了解么?

Autoreleasepool是由多個(gè)AutoreleasePoolPage以雙向鏈表的形式連接起來(lái)的,
Autoreleasepool的基本原理:在每個(gè)自動(dòng)釋放池創(chuàng)建的時(shí)候,會(huì)在當(dāng)前的AutoreleasePoolPage中設(shè)置一個(gè)標(biāo)記位,在此期間,當(dāng)有對(duì)象調(diào)用autorelsease時(shí),會(huì)把對(duì)象添加到AutoreleasePoolPage中,若當(dāng)前頁(yè)添加滿(mǎn)了,會(huì)初始化一個(gè)新頁(yè),然后用雙向量表鏈接起來(lái),并把新初始化的這一頁(yè)設(shè)置為hotPage,當(dāng)自動(dòng)釋放池pop時(shí),從最下面依次往上pop,調(diào)用每個(gè)對(duì)象的release方法,直到遇到標(biāo)志位。
AutoreleasePoolPage結(jié)構(gòu)如下:

class AutoreleasePoolPage {
    magic_t const magic;
    id *next;//下一個(gè)存放autorelease對(duì)象的地址
    pthread_t const thread; //AutoreleasePoolPage 所在的線程
    AutoreleasePoolPage * const parent;//父節(jié)點(diǎn)
    AutoreleasePoolPage *child;//子節(jié)點(diǎn)
    uint32_t const depth;//深度,也可以理解為當(dāng)前page在鏈表中的位置
    uint32_t hiwat;
}
7. 講一下對(duì)象,類(lèi)對(duì)象,元類(lèi),跟元類(lèi)結(jié)構(gòu)體的組成以及他們是如何相關(guān)聯(lián)的?為什么對(duì)象方法沒(méi)有保存的對(duì)象結(jié)構(gòu)體里,而是保存在類(lèi)對(duì)象的結(jié)構(gòu)體里?

講一下對(duì)象,類(lèi)對(duì)象,元類(lèi),跟元類(lèi)結(jié)構(gòu)體的組成以及他們是如何相關(guān)聯(lián)的:

  • 對(duì)象的結(jié)構(gòu)體里存放著isa和成員變量,isa指向類(lèi)對(duì)象。
    類(lèi)對(duì)象的isa指向元類(lèi),元類(lèi)的isa指向NSObject的元類(lèi)。
    類(lèi)對(duì)象和元類(lèi)的結(jié)構(gòu)體有isa、superclass、cache、bits,bits里存放著class_rw_t的指針。
    放一張經(jīng)典的圖


    16f36f8c010dade8.jpg

為什么對(duì)象方法沒(méi)有保存的對(duì)象結(jié)構(gòu)體里,而是保存在類(lèi)對(duì)象的結(jié)構(gòu)體里:

  • 方法是每個(gè)對(duì)象互相可以共用的,如果每個(gè)對(duì)象都存儲(chǔ)一份方法列表太浪費(fèi)內(nèi)存,由于對(duì)象的isa是指向類(lèi)對(duì)象的,當(dāng)調(diào)用的時(shí)候,直接去類(lèi)對(duì)象中查找就行了。可以節(jié)約很多內(nèi)存空間的
8. class_ro_tclass_rw_t的區(qū)別?

從字面上理解,class_ro_t是只讀,class_rw_t可寫(xiě)可讀。這兩個(gè)變量共同點(diǎn)都是存儲(chǔ)類(lèi)的屬性、方法、協(xié)議等信息的,不同的有兩點(diǎn):1、class_ro_t還存儲(chǔ)了類(lèi)的成員變量,而class_rw_t則沒(méi)有,從這方面也驗(yàn)證了類(lèi)的成員變量一旦確定了,就不能寫(xiě)了,就是分類(lèi)不能增加成員變量的原因;2、class_ro_t是在編譯期就確定了固定的值,在整個(gè)運(yùn)行時(shí)都只讀不可寫(xiě)的狀態(tài),在運(yùn)行時(shí)調(diào)用realizeClass方法將class_ro_t復(fù)制到class_rw_t對(duì)應(yīng)的變量上去。

9. iOS中內(nèi)省的幾個(gè)方法?class方法和objc_getClass方法有什么區(qū)別?

什么是內(nèi)?。?/p>

在計(jì)算機(jī)科學(xué)中,內(nèi)省是指計(jì)算機(jī)程序在運(yùn)行時(shí)(Run time)檢查對(duì)象(Object)類(lèi)型的一種能力,通常也可以稱(chēng)作運(yùn)行時(shí)類(lèi)型檢查。不應(yīng)該將內(nèi)省和反射混淆。相對(duì)于內(nèi)省,反射更進(jìn)一步,是指計(jì)算機(jī)程序在運(yùn)行時(shí)(Run time)可以訪問(wèn)、檢測(cè)和修改它本身狀態(tài)或行為的一種能力。

iOS中內(nèi)省的幾個(gè)方法:

  • isMemberOfClass //對(duì)象是否是某個(gè)類(lèi)型的對(duì)象
  • isKindOfClass //對(duì)象是否是某個(gè)類(lèi)型或某個(gè)類(lèi)型子類(lèi)的對(duì)象
  • isSubclassOfClass //某個(gè)類(lèi)對(duì)象是否是另一個(gè)類(lèi)型的子類(lèi)
  • isAncestorOfObject //某個(gè)類(lèi)對(duì)象是否是另一個(gè)類(lèi)型的父類(lèi)
  • respondsToSelector //是否能響應(yīng)某個(gè)方法
  • conformsToProtocol //是否遵循某個(gè)協(xié)議

class方法和object_getClass方法有什么區(qū)別:

  • 實(shí)例class方法就直接返回object_getClass(self),類(lèi)class方法直接返回self,而object_getClass(類(lèi)對(duì)象),則返回的是元類(lèi)
10. 在運(yùn)行時(shí)創(chuàng)建類(lèi)的方法objc_allocateClassPair的方法名尾部為什么是pair(成對(duì)的意思)?

因?yàn)榇朔椒〞?huì)創(chuàng)建一個(gè)類(lèi)對(duì)象以及元類(lèi),正好組成一隊(duì)

Class objc_allocateClassPair(Class superclass, const char *name, 
                             size_t extraBytes){
    ...省略了部分代碼
    //生成一個(gè)類(lèi)對(duì)象
    cls  = alloc_class_for_subclass(superclass, extraBytes);
    //生成一個(gè)類(lèi)對(duì)象元類(lèi)對(duì)象
    meta = alloc_class_for_subclass(superclass, extraBytes);
    objc_initializeClassPair_internal(superclass, name, cls, meta);
    return cls;
}
11. 一個(gè)int變量被__block修飾與否的區(qū)別?

int變量被__block修飾之后會(huì)生成一個(gè)結(jié)構(gòu)體,復(fù)制int的引用地址。達(dá)到修改數(shù)據(jù),如__block int age會(huì)被包裝成下面這樣:

struct __Block_byref_age_0 {
    void *__isa;
    __Block_byref_age_0 *__forwarding; //指向自己
    int __flags;
    int __size;
    int age;//包裝的具體的值
};
// age = 20;會(huì)被編譯成下面這樣
(age.__forwarding->age) = 20;
12. 為什么在block外部使用__weak修飾的同時(shí)需要在內(nèi)部使用__strong修飾?

__weak修飾之后block不會(huì)對(duì)該對(duì)象進(jìn)行retain,只是持有了weak指針,在block執(zhí)行之前或執(zhí)行的過(guò)程時(shí),隨時(shí)都有可能被釋放,將weak指針置位nil,產(chǎn)生一些未知的錯(cuò)誤。在內(nèi)部用__strong修飾,會(huì)在block執(zhí)行時(shí),對(duì)該對(duì)象進(jìn)行一次retain,保證在執(zhí)行時(shí)若該指針不指向nil,則在執(zhí)行過(guò)程中不會(huì)指向nil。但有可能在執(zhí)行執(zhí)行之前已經(jīng)為nil了。

13. RunLoop的作用是什么?它的內(nèi)部工作機(jī)制了解么?(最好結(jié)合線程和內(nèi)存管理來(lái)說(shuō))?

什么是RunLoop:

  • 一般來(lái)講,一個(gè)線程一次只能執(zhí)行一個(gè)任務(wù),執(zhí)行完成后線程就會(huì)退出。如果我們需要一個(gè)機(jī)制,讓線程能隨時(shí)處理事件但并不退出。這種模型通常被稱(chēng)作 Event Loop。 Event Loop 在很多系統(tǒng)和框架里都有實(shí)現(xiàn),比如 Node.js 的事件處理,比如 Windows 程序的消息循環(huán),再比如 OSX/iOS 里的 RunLoop。實(shí)現(xiàn)這種模型的關(guān)鍵點(diǎn)在于:如何管理事件/消息,如何讓線程在沒(méi)有處理消息時(shí)休眠以避免資源占用、在有消息到來(lái)時(shí)立刻被喚醒。

RunLoop的作用是什么:

  • 保持程序的持續(xù)運(yùn)行,在iOS線程中,會(huì)在main方法給主線程創(chuàng)建一個(gè)RunLoop,保證主線程不被銷(xiāo)毀
  • 處理APP中的各種事件(如touch,timer,performSelector等)
  • 界面更新
  • 手勢(shì)識(shí)別
  • AutoreleasePool
    1. 系統(tǒng)在主線程RunLoop注冊(cè)了2個(gè)observer
    2. 第一個(gè)observe監(jiān)聽(tīng)即將進(jìn)入RunLoop,調(diào)用_objc_autoreleasePoolPush()創(chuàng)建自動(dòng)釋放池
    3. 第二個(gè)observe監(jiān)聽(tīng)兩個(gè)事件,進(jìn)入休眠之前即將退出RunLoop
    4. 在進(jìn)入休眠之前的回調(diào)里,會(huì)先釋放自動(dòng)釋放池,然后在創(chuàng)建一個(gè)自動(dòng)釋放池
    5. 在即將退出的回調(diào)里,會(huì)釋放自動(dòng)釋放池
  • 線程保活
  • 監(jiān)測(cè)卡頓

RunLoop的內(nèi)部邏輯:


16f36f8727cf58f1.png
14. 哪些場(chǎng)景可以觸發(fā)離屏渲染?(知道多少說(shuō)多少)
  • 添加遮罩mask
  • 添加陰影shadow
  • 設(shè)置圓角并且設(shè)置masksToBoundstrue
  • 設(shè)置allowsGroupOpacitytrue并且layer.opacity小于1.0和有子layer或者背景不為空
  • 開(kāi)啟光柵化shouldRasterize = true
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容