iOS基礎(chǔ)題
1. 分類和擴(kuò)展有什么區(qū)別棒搜?可以分別用來(lái)做什么诵肛?分類有哪些局限性镀虐?分類的結(jié)構(gòu)體里面有哪些成員箱蟆?
- 分類主要用來(lái)為某個(gè)類添加方法,屬性刮便,協(xié)議(我一般用來(lái)給系統(tǒng)的類添加方法或者把某個(gè)復(fù)雜的類按照功能拆分到不同的文件里)
- 擴(kuò)展主要用來(lái)為某個(gè)類添加成員變量空猜、屬性、方法聲明恨旱。(我一般用擴(kuò)展來(lái)聲明私有屬性辈毯,或者把.h的只讀屬性重寫成可讀寫的)
分類和擴(kuò)展的區(qū)別:
- 分類是在運(yùn)行時(shí)把分類信息合并到類信息中,而擴(kuò)展是在編譯時(shí)搜贤,就把信息合并到類中的
- 分類聲明的屬性谆沃,只會(huì)生成getter/setter方法的聲明,不會(huì)自動(dòng)生成成員變量和getter/setter方法的實(shí)現(xiàn)仪芒,而擴(kuò)展會(huì)
- 分類不可用來(lái)為類添加實(shí)例變量唁影,而擴(kuò)展可以
- 分類可以為類添加方法的實(shí)現(xiàn),而擴(kuò)展只能聲明方法掂名,而不能實(shí)現(xiàn)
分類的局限性:
- 無(wú)法為類添加實(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)鍵字漫談
- 分類的方法若和類中原本的實(shí)現(xiàn)重名猾警,會(huì)覆蓋原本方法的實(shí)現(xiàn)孔祸,注:并不是真正的覆蓋
- 多個(gè)分類的方法重名,會(huì)調(diào)用最后編譯的那個(gè)分類的實(shí)現(xiàn)
分類的結(jié)構(gòu)體有哪些成員:
struct category_t {
const char *name; //名字
classref_t cls; //類的引用
struct method_list_t *instanceMethods;//實(shí)例方法列表
struct method_list_t *classMethods;//類方法列表
struct protocol_list_t *protocols;//協(xié)議列表
struct property_list_t *instanceProperties;//實(shí)例屬性列表
// 此屬性不一定真正的存在
struct property_list_t *_classProperties;//類屬性列表
};
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)保證讀寫的原子性誉结。鎖都在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)部保證了讀寫時(shí)的線程安全编振,當(dāng)我們重寫setter/getter方法時(shí),就只能依靠自己在getter/setter中保證線程安全
3. 被weak修飾的對(duì)象在被釋放的時(shí)候會(huì)發(fā)生什么臭埋?是如何實(shí)現(xiàn)的踪央?知道sideTable么?里面的結(jié)構(gòu)可以畫出來(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)行銷毀時(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)用:
- 一般用于在分類中給類添加實(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è)類的屬性被觀察時(shí)符喝,系統(tǒng)會(huì)在運(yùn)行時(shí)動(dòng)態(tài)的創(chuàng)建一個(gè)該類的子類甜孤,并且將對(duì)象的
isa
指向這個(gè)子類 - 假設(shè)被觀察的屬性名是
name
,若父類里有setName:
或者_setName:
茉稠,那么在子類里重寫這兩個(gè)方法把夸,若兩個(gè)方法同時(shí)存在,則只會(huì)重寫setName:
一個(gè)(這里和KVC的set搜索時(shí)的順序時(shí)一樣的) - 若被觀察的類型是
NSString
膀篮,那么重寫的方法的實(shí)現(xiàn)會(huì)指向_NSSetObjectValueAndNotify
這個(gè)函數(shù)岂膳,若是Bool
類型谈截,那么重寫的方法的實(shí)現(xiàn)會(huì)指向_NSSetBoolValueAndNotify
這個(gè)函數(shù),這個(gè)函數(shù)里會(huì)調(diào)用willChangeValueForKey:
和didChangevlueForKey:
毙死,并且會(huì)在這兩個(gè)方法調(diào)用之間娘赴,調(diào)用父類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:
方法里秃症,若父類不存在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
類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è)添加滿了踊谋,會(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ì)象枷恕,類對(duì)象谭胚,元類灾而,跟元類結(jié)構(gòu)體的組成以及他們是如何相關(guān)聯(lián)的?為什么對(duì)象方法沒(méi)有保存的對(duì)象結(jié)構(gòu)體里旁趟,而是保存在類對(duì)象的結(jié)構(gòu)體里锡搜?
講一下對(duì)象,類對(duì)象凡傅,元類肠缔,跟元類結(jié)構(gòu)體的組成以及他們是如何相關(guān)聯(lián)的:
-
對(duì)象的結(jié)構(gòu)體里存放著isa和成員變量,isa指向類對(duì)象槽华。
類對(duì)象的isa指向元類趟妥,元類的isa指向NSObject的元類。
類對(duì)象和元類的結(jié)構(gòu)體有isa亲雪、superclass藕咏、cache、bits,bits里存放著class_rw_t的指針贞让。
放一張經(jīng)典的圖
16f36f8c010dade8.jpg
為什么對(duì)象方法沒(méi)有保存的對(duì)象結(jié)構(gòu)體里,而是保存在類對(duì)象的結(jié)構(gòu)體里:
- 方法是每個(gè)對(duì)象互相可以共用的续镇,如果每個(gè)對(duì)象都存儲(chǔ)一份方法列表太浪費(fèi)內(nèi)存销部,由于對(duì)象的isa是指向類對(duì)象的舅桩,當(dāng)調(diào)用的時(shí)候,直接去類對(duì)象中查找就行了擂涛∪雎瑁可以節(jié)約很多內(nèi)存空間的
8. class_ro_t
和class_rw_t
的區(qū)別?
從字面上理解杰捂,class_ro_t
是只讀棋蚌,class_rw_t
可寫可讀。這兩個(gè)變量共同點(diǎn)都是存儲(chǔ)類的屬性脱拼、方法坷备、協(xié)議等信息的,不同的有兩點(diǎn):1赌蔑、class_ro_t
還存儲(chǔ)了類的成員變量竟秫,而class_rw_t
則沒(méi)有,從這方面也驗(yàn)證了類的成員變量一旦確定了趾浅,就不能寫了,就是分類不能增加成員變量的原因浅侨;2证膨、class_ro_t
是在編譯期就確定了固定的值,在整個(gè)運(yùn)行時(shí)都只讀不可寫的狀態(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)省:
在計(jì)算機(jī)科學(xué)中喻奥,內(nèi)省是指計(jì)算機(jī)程序在運(yùn)行時(shí)(Run time)檢查對(duì)象(Object)類型的一種能力捏悬,通常也可以稱作運(yùn)行時(shí)類型檢查。不應(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è)類型的對(duì)象 -
isKindOfClass
//對(duì)象是否是某個(gè)類型或某個(gè)類型子類的對(duì)象 -
isSubclassOfClass
//某個(gè)類對(duì)象是否是另一個(gè)類型的子類 -
isAncestorOfObject
//某個(gè)類對(duì)象是否是另一個(gè)類型的父類 -
respondsToSelector
//是否能響應(yīng)某個(gè)方法 -
conformsToProtocol
//是否遵循某個(gè)協(xié)議
class
方法和object_getClass
方法有什么區(qū)別:
- 實(shí)例
class
方法就直接返回object_getClass(self)
缘挑,類class
方法直接返回self
语淘,而object_getClass(類對(duì)象)
际歼,則返回的是元類
10. 在運(yùn)行時(shí)創(chuàng)建類的方法objc_allocateClassPair
的方法名尾部為什么是pair(成對(duì)的意思)焕窝?
因?yàn)榇朔椒〞?huì)創(chuàng)建一個(gè)類對(duì)象以及元類它掂,正好組成一隊(duì)
Class objc_allocateClassPair(Class superclass, const char *name,
size_t extraBytes){
...省略了部分代碼
//生成一個(gè)類對(duì)象
cls = alloc_class_for_subclass(superclass, extraBytes);
//生成一個(gè)類對(duì)象元類對(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í)處理事件但并不退出兰英。這種模型通常被稱作 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,保證主線程不被銷毀 - 處理APP中的各種事件(如
touch
猜极,timer
消玄,performSelector
等) - 界面更新
- 手勢(shì)識(shí)別
-
AutoreleasePool
- 系統(tǒng)在主線程RunLoop注冊(cè)了2個(gè)
observer
- 第一個(gè)
observe
監(jiān)聽即將進(jìn)入RunLoop莱找,調(diào)用_objc_autoreleasePoolPush()
創(chuàng)建自動(dòng)釋放池 - 第二個(gè)
observe
監(jiān)聽兩個(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
或者背景不為空 - 開啟光柵化
shouldRasterize = true