一邑时、分類(Category)
問題1:你用分類都做了哪些事情?
- 聲明私有方法
- 分解體積龐大的類文件
- 把Framework的私有方法公開
1.1、特點
運行時決議
編寫完成的分類文件坤学,并沒有把分類內(nèi)容添加到宿主類中。
也就說宿主中還沒有分類中的方法报慕,而是在運行時深浮,通過runtime將分類的方法添加到宿主當中。
這也是分類的最大特點眠冈,也是分類和擴展的最大區(qū)別飞苇。可以為系統(tǒng)類添加分類
擴展不能為系統(tǒng)添加擴展
1.2:分類中都可以添加哪些內(nèi)容菌瘫?
- 實例方法
- 類方法
- 協(xié)議
- 屬性
在分類中定義一個屬性,實際是只是生成了setter和getter方法布卡,并沒有在分類添加實例變量雨让。想要添加實例變量,可以通過關(guān)聯(lián)對象的方法忿等。
1.3栖忠、分類數(shù)據(jù)結(jié)構(gòu)
Category 是表示一個指向分類的結(jié)構(gòu)體的指針,其定義如下:
typedef struct objc_category *Category;
struct objc_category {
//分類名
char *category_name;
//分類所屬的類名
char *class_name;
//實例方法列表
struct objc_method_list *instance_methods;
//類方法列表
struct objc_method_list *class_methods;
//分類所實現(xiàn)的協(xié)議列表
struct objc_protocol_list *protocols;
//實例屬性列表
struct property_list_t *instanceProperties;
}
1.4贸街、加載調(diào)用棧(了解)
備注:
- _objc_init:runtime初始化方法庵寞。
- images:指的是鏡像。
1.5薛匪、源碼分析
這里以添加實例方法舉例:
從remethodizeClass開始入手分析(只列出主要代碼):
1.5.1
我們只分析分類當中實例方法添加的邏輯
因此在這里我們假設(shè) isMeta = NO
bool isMeta = cls->isMetaClass();
1.5.2
生成一個新的二維數(shù)組捐川,用來存放分類中的方法
method_list_t **mlists = (method_list_t **)
malloc(cats->count * sizeof(*mlists));
1.5.3
遍歷分類數(shù)組,采用倒敘遍歷
//宿主分類的總數(shù)
int i = cats->count;
while (i--) {
//獲取一個分類
auto& entry = cats->list[i];
//獲取該分類的方法列表
method_list_t *mlist = entry.cat->methodsForMeta(isMeta);
if (mlist) {
//最后編譯的分類逸尖,方法最先添加到分類數(shù)組中
mlists[mcount++] = mlist;
fromBundle |= entry.hi->isBundle();
}
}
1.5.4
獲取宿主類當中的rw數(shù)據(jù)古沥,其中包含宿主類的方法列表信息
auto rw = cls->data();
1.5.5
將分類方法拼接到rw的methods上
參數(shù)/變量含義:
rw代表類。
methods代表類的方法列表娇跟。
attachLists方法:將含有mcount個元素的mlists拼接到rw的methods上岩齿。
rw->methods.attachLists(mlists, mcount);
1.5.6
attachLists方法解析
假設(shè)列表中原有元素總數(shù)為2(oldCount = 2)。
假設(shè)將要添加的分類元素總數(shù)為3(addedCount = 3)逞频。
[ [method_t , method_t], [method_t],
[method_t , method_t , method_t] ]
這里只分析有數(shù)組的情況
//列表中原有元素總數(shù)。假設(shè)oldCount = 2
uint32_t oldCount = array()->count;
//拼接之后的元素總數(shù)
uint32_t newCount = oldCount + addedCount;
//根據(jù)新總數(shù)重新分配內(nèi)存
setArray((array_t *)realloc(array(), array_t::byteSize(newCount)));
//重新設(shè)置元素總數(shù)
array()->count = newCount;
/*
內(nèi)存移動
[ [ ], [ ], [ ], [原有的第一個元素], [原有的第二個元素]]
*/
memmove(array()->lists + addedCount, array()->lists,
oldCount * sizeof(array()->lists[0]));
/*
內(nèi)存拷貝
[
A ---> [addedLists中的第一個元素],
B ---> [addedLists中的第二個元素],
C ---> [addedLists中的第三個元素],
[原有的第一個元素],
[原有的第二個元素]
]
這也是分類方法會"覆蓋"宿主類的方法的原因
*/
memcpy(array()->lists, addedLists,
addedCount * sizeof(array()->lists[0]));
問題2:一個類的多個分類包含同名方法栋齿,最后哪個會生效苗胀?
解釋:
誰最后編譯,誰就生效瓦堵。
1.6基协、分類總結(jié)
- 分類添加的方法可以"覆蓋"原類方法
效果上是"覆蓋",實際上原類方法還是存在的菇用。 - 同名分類方法誰能生效取決于編譯順序
- 名字相同的分類會引起編譯報錯
二澜驮、關(guān)聯(lián)對象
問題3:能否給分類添加成員變量?
解釋:
可以添加惋鸥。
在分類的聲明時杂穷,不能添加成員變量;但是可以通過關(guān)聯(lián)對象的方法添加卦绣。
問題4:給分類添加的成員變量是否添加到宿主中耐量?
解釋:
沒有添加到宿主中。被存放在一個全局容器中滤港,并且為不同類添加的關(guān)聯(lián)對象都放在同一個全局容器中廊蜒。
2.1、獲取屬性值
objc_getAssociatedObject(id object, const void *key);
- object
目標對象。即獲取哪個對象的關(guān)聯(lián)屬性 - key
根據(jù)對應(yīng)的key查找對象的屬性
2.2山叮、添加并設(shè)置屬性值
objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy);
- object
目標對象著榴。給自己添加屬性,就用self屁倔。 - key
屬性名脑又。 - value
關(guān)聯(lián)值。 - policy
策略汰现。屬性以什么形式保存挂谍。
主要形式有以下幾種:
typedef OBJC_ENUM(uintptr_t, objc_AssociationPolicy) {
OBJC_ASSOCIATION_ASSIGN = 0, // 指定一個弱引用相關(guān)聯(lián)的對象
OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1, // 指定相關(guān)對象的強引用,非原子性
OBJC_ASSOCIATION_COPY_NONATOMIC = 3, // 指定相關(guān)的對象被復(fù)制瞎饲,非原子性
OBJC_ASSOCIATION_RETAIN = 01401, // 指定相關(guān)對象的強引用口叙,原子性
OBJC_ASSOCIATION_COPY = 01403 // 指定相關(guān)的對象被復(fù)制,原子性
};
2.3嗅战、移除所有的關(guān)聯(lián)對象
objc_removeAssociatedObjects(id object);
2.4妄田、關(guān)聯(lián)對象的本質(zhì)
- 關(guān)聯(lián)對象由AssociationsManager管理;并在AssociationsHashMap存儲驮捍。
- 所有對象的關(guān)聯(lián)內(nèi)容都在同一個全局容器中疟呐。
這里包含三部分數(shù)據(jù)結(jié)構(gòu):
-
AssociationsManager
管理對象,里面有一個AssociationsHashMap對象东且。 -
AssociationsHashMap
可以理解為全局容器启具。
由多個obj:ObjectAssociationMap組成。
一個實例對象就對應(yīng)一個ObjectAssociationMap珊泳。 -
ObjectAssociationMap
由多個屬性名key:ObjcAssociation組成鲁冯。 -
ObjcAssociation
由value和policy組成。
由上面可以知道色查,關(guān)聯(lián)對象沒有存放到原來的對象里面薯演,而是自己維護了一個全局map來存放的。
問題5:怎樣清除一個關(guān)聯(lián)對象秧了?
解釋:
通過objc_setAssociatedObject跨扮,將其中value傳值為nil,就可以了验毡。
三衡创、擴展(Extension)
- 聲明私有屬性
- 聲明私有方法(只是為了方便閱讀,無太大作用)
- 聲明私有變量
問題6:屬性和變量的區(qū)別晶通?
屬性 = 變量 + set方法 + get方法
問題7:分類和擴展的區(qū)別钧汹?
- 擴展是編譯時決議,分類是運行時決議
- 擴展是以聲明的形式存在录择,不是獨立的存在碗降,多數(shù)寄存于宿主類.m文件中;而分類是單獨存在的塘秦。
- 不能為系統(tǒng)類添加擴展讼渊;但是可以為系統(tǒng)的類添加分類。
四尊剔、代理
- 是一種軟件設(shè)計模式爪幻。
- iOS中以@protocal形式體現(xiàn)。
- 傳遞方式是一對一须误。
問題8:代理的工作流程是什么挨稿?
問題9:為什么代理聲明的時候,要使用weak?
因為代理方一般強引用委托方京痢,為了防止循環(huán)引用奶甘,委托方必須對代理方進行弱引用。
五祭椰、通知
- 使用觀察者模式來實現(xiàn)的臭家,用于跨層傳遞消息的機制。
- 傳遞方式為一對多方淤。
問題10:通知和代理的區(qū)別钉赁?
- 代理是由代理模式實現(xiàn)的;通知是由觀察者模式實現(xiàn)的携茂。
- 代理傳遞是一對一你踩,通知是一對多。
問題11:如何實現(xiàn)通知機制讳苦?
在通知中心带膜,內(nèi)部維護一個Map表,它的key是通知名稱医吊,value是觀察者表List钱慢。觀察者表List內(nèi)部包含多個observer對象逮京;observer對象包含通知接收的觀察者卿堂,觀察者調(diào)用的方法等等。
六懒棉、KVO
6.1草描、概念
- KVO是Key-value observing的縮寫。
- KVO是OC對觀察者設(shè)計模式的又一實現(xiàn)策严。
- Apple使用了isa混寫(isa-swizzling)來實現(xiàn)KVO穗慕。
6.2、KVO調(diào)用發(fā)生過程圖
- A類對象調(diào)用系統(tǒng)方法addObserver:forKeyPath:options:context:
- 在運行時會創(chuàng)建一個NSKVONotifying_A類妻导,這個NSKVONotifying_A類是A類的子類
- 此時逛绵,A類的isa指針會指向NSKVONotifying_A類
- 然后在NSKVONotifying_A中會重寫setter方法
- 在重寫的setter方法中怀各,先調(diào)用willChangeValueForKey: ??調(diào)用父類方法,給父類賦值??給子類屬性賦值??調(diào)用didChangeValueForKey:??最終會調(diào)用observeValueForKeyPath通知了所有的觀察者术浪。
問題12:isa混寫技術(shù)在KVO中是怎么體現(xiàn)的瓢对?
見6.2.KVO調(diào)用發(fā)生過程圖
6.3、重寫setter方法的具體實現(xiàn)
6.3.1胰苏、兩個重要方法
6.3.2硕蛹、具體實現(xiàn)
備注:
- 一定會調(diào)用父類方法實現(xiàn),否則數(shù)值不統(tǒng)一硕并。
- didChangeValueForKey調(diào)用后會觸發(fā)observeValueForKeyPath方法法焰。
問題13:通過KVC設(shè)置value能否生效?
可以生效
問題14:為什么通過KVC設(shè)置value能生效倔毙?
因為KVC內(nèi)部原理表明埃仪,這樣設(shè)置value會調(diào)用obj對象的setter方法。
問題15:通過成員變量直接賦值value能夠生效普监?
- 不能贵试。
- 可以手動觸發(fā)KVO,在給變量賦值的代碼前后加入willChangeValueForKey:和didChangeValueForKey:
-
代碼如下:
手動KVO
6.4凯正、KVO總結(jié)
- 使用setter方法改變值KVO才生效毙玻。
- 使用setValue:forKey:改變值KVO才會生效。
- 成員變量直接修改需手動添加KVO才會生效廊散。
七桑滩、KVC
問題16:什么是KVC
- 鍵值編碼技術(shù)、Key-value coding縮寫
- (id)valueForKey: (NSString *)key
- (void)setValue:(id)value forKey:(NSString *)key
問題17:KVC會不會破壞面向?qū)ο蟮木幊趟枷耄?/h3>
會破壞允睹。
因為上面的key是沒有限制的运准,即使是私有變量,也可以通過KVC設(shè)置私有變量的value缭受。
7.1胁澳、valueForKey系統(tǒng)實現(xiàn)流程
7.1.1、流程圖
- 先查找對應(yīng)的get方法是否存在米者。
如果存在韭畸,則調(diào)用對應(yīng)方法,結(jié)束流程蔓搞。
如果不存在胰丁,則查找對應(yīng)的實例變量是否存在。 - 查找實例變量是否存在喂分。
系統(tǒng)提供了一個開關(guān)函數(shù)accessInstanceVariablesDirectly锦庸,默認是YES,允許查詢相同或相似的實例變量蒲祈。
如果實例變量存在甘萧,則獲取當前值返回萝嘁。 - 如果實例變量不存在,則會調(diào)用valueForUndefinedKey方法扬卷,拋出一個NSUndefinedKeyException異常酿愧。
7.1.2、流程圖 get方法是否存在的判斷規(guī)則
- <getKey>
- <key>
- <isKey>
如果有以上命名的訪問器方法邀泉,則默認當前get方法是存在的嬉挡。
7.1.3、流程圖 實例方法是否存在的判斷規(guī)則
- _key
- _isKey
- key
- isKey
如果找到上述同名或者類似的變量汇恤,則默認當前的成員變量是存在的庞钢。
7.2、setValue:forKey:系統(tǒng)實現(xiàn)流程
7.2.1因谎、流程圖
- 先查找對應(yīng)的set方法是否存在基括。
如果存在,則調(diào)用對應(yīng)方法财岔,結(jié)束流程风皿。
如果不存在,則查找對應(yīng)的實例變量是否存在匠璧。 - 其余步驟同valueForKey相同桐款。
八、屬性關(guān)鍵字
8.1夷恍、屬性關(guān)鍵字分類:
1魔眨、讀寫權(quán)限
- readonly
- readwrite(默認關(guān)鍵字)
2、 原子性
- atomic(系統(tǒng)默認)
可以賦值和獲取是線程安全的酿雪。但是操作對象是無法保證線程安全的遏暴。
比如:一個數(shù)組Array。如果對Array進行賦值或獲取是線程安全的指黎,如果對Array進行操作(添加/移除對象)朋凉,則無法保證線程安全 - nonatomic
3、 引用計數(shù)
- retain/strong
retain:MRC
strong:ARC - assign/unsafe_unretained
unsafe_unretained:ARC基本不使用 - weak
8.2醋安、assign特點
- 修飾基本數(shù)據(jù)類型杂彭,如int、Bool等茬故。
- 修飾對象類型時盖灸,不改變其引用計數(shù)蚁鳖。
- 會產(chǎn)生懸垂指針磺芭。
assign指針指向的對象被釋放后,assgin指針仍然指向原對象內(nèi)存地址醉箕。
8.2钾腺、weak特點
- 不改變被修飾對象的引用計數(shù)徙垫。
- 所指對象在被釋放后會自動置為nil。
問題18:assgin與weak區(qū)別
- 修飾數(shù)據(jù)類型方面
assign既可以修飾基本數(shù)據(jù)類型放棒,也可以修飾對象姻报。
weak只可以修飾對象。 - 指針
assgin可以產(chǎn)生懸垂指針间螟。
weak指針自動指向nil吴旋。
二者的共同點:都不影響引用計數(shù)。
8.3厢破、copy
問題19:見下圖
- 如果賦值過來的是NSMutableArray荣瑟,copy之后是NSArray。
- 如果賦值過來的是NSArray摩泪,copy之后是NSArray笆焰。
因為最終array是NSArray,不可變對象见坑。
8.3.1嚷掠、深拷貝和淺拷貝
是否開辟新的內(nèi)存空間
深拷貝會,淺拷貝不會-
是否影響引用計數(shù)
深拷貝不會荞驴,淺拷貝會
總結(jié) 可變對象的copy和mutableCopy都是深拷貝不皆。
不可變對象的copy是淺拷貝,mutableCopy都是深拷貝熊楼。
copy方法返回的都是不可變對象粟焊。
九、筆試題總結(jié)
9.1孙蒙、MRC下如何重寫retain修飾變量的setter方法项棠?
9.2、請簡述分類的實現(xiàn)原理
1挎峦、分類的實現(xiàn)原理是由運行時決議的香追。
2、不同分類的含有同名分類方法坦胶,誰最后編譯透典,誰就生效。
3顿苇、如果分類中的方法恰好與目標類中的方法同名峭咒,分類會覆蓋同名的目標類方法。
9.3纪岁、KVO的實現(xiàn)原理是怎樣的凑队?
1、KVO是系統(tǒng)關(guān)于觀察者模式的一種體現(xiàn)幔翰。
2漩氨、KVO運用了isa混寫技術(shù)西壮;在動態(tài)運行時,為某一個類動態(tài)添加一個子類叫惊,重寫了它的setter方法款青;將原有類的isa指針指向新創(chuàng)建的子類。
9.4霍狰、能否為分類添加成員變量抡草?
不能。因為它的數(shù)據(jù)結(jié)構(gòu)中蔗坯,沒有成員變量渠牲。
可以通過關(guān)聯(lián)對象。