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_getProperty和objc_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_getProperty和objc_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_t和class_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- 系統(tǒng)在主線程RunLoop注冊(cè)了2個(gè)
observer - 第一個(gè)
observe監(jiān)聽(tīng)即將進(jìn)入RunLoop,調(diào)用_objc_autoreleasePoolPush()創(chuàng)建自動(dòng)釋放池 - 第二個(gè)
observe監(jiān)聽(tīng)兩個(gè)事件,進(jìn)入休眠之前和即將退出RunLoop - 在進(jìn)入休眠之前的回調(diào)里,會(huì)先釋放自動(dòng)釋放池,然后在創(chuàng)建一個(gè)自動(dòng)釋放池
- 在即將退出的回調(diào)里,會(huì)釋放自動(dòng)釋放池
- 系統(tǒng)在主線程RunLoop注冊(cè)了2個(gè)
- 線程保活
- 監(jiān)測(cè)卡頓
RunLoop的內(nèi)部邏輯:

14. 哪些場(chǎng)景可以觸發(fā)離屏渲染?(知道多少說(shuō)多少)
- 添加遮罩
mask - 添加陰影
shadow - 設(shè)置圓角并且設(shè)置
masksToBounds為true - 設(shè)置
allowsGroupOpacity為true并且layer.opacity小于1.0和有子layer或者背景不為空 - 開(kāi)啟光柵化
shouldRasterize = true
