8、UI事件處理
iOS事件分類:觸摸事件、定時器事件、傳感器事件、遠(yuǎn)程控制事件
UIWindow:是一種特殊的View,可以作為試圖容器協(xié)調(diào)VC,還可以處理事件分發(fā)
1、通常在一個程序中只會有一個UIWindow為keyWindow,但可以手動創(chuàng)建多個UIWindow
2、觸摸事件傳給當(dāng)前window,其他事件只交給keyWindow
3、alert和鍵盤都是單獨的window,每一個UIWindow之間是獨立的
4、WindowLevel控制顯示等級,默認(rèn)值是 0,值越高就展示在越前面
觸摸事件產(chǎn)生:
IOKit庫捕獲到事件,交給桌面app,再通過進(jìn)程通訊交給當(dāng)前app的runloop的source1任務(wù),然后轉(zhuǎn)成source0,封裝成uievent添加到UIApplication事件隊列,再交給當(dāng)前UIWindow分發(fā)處理
UIWindow沿著視圖樹不斷逆序遍歷subviews,調(diào)用子視圖的hitTest:withEvent:和pintInside:找到第一響應(yīng)者,形成響應(yīng)者鏈,將事件傳遞給第一響應(yīng)者進(jìn)行處理
觸摸事件的傳遞
繼承了UIResponder的UI對象能接受并處理事件,UIResponder內(nèi)部提供了一系列的touch方法處理事件,提供nextResponder屬性可以向上傳遞事件
hitTest:方法根據(jù)試圖是否允許交互、是否隱藏、是否透明、觸摸點是否在當(dāng)前視圖范圍內(nèi)pointInside方法等查找響應(yīng)者


事件的響應(yīng)
1、事件傳遞給第一響應(yīng)者后開始處理響應(yīng),有三種事件響應(yīng)方式:UIResponder的touches方法,UIGestureRecognzier和UIButton的UIControlEvent事件
2、UIWindow將事件優(yōu)先交給手勢處理,如果有手勢處理直接調(diào)用響應(yīng)者的touchcancel方法,事件不再傳遞處理結(jié)束
3、如無手勢,系統(tǒng)會調(diào)用第一響應(yīng)者的touches方法處理事件,第一響應(yīng)者不能處理事件,會順著響應(yīng)鏈向上傳遞,直到UIApplication也不能處理將其丟棄
4、UIButton重寫了UIResponder的touchs方法不調(diào)用super的touchs使事件不再往上傳遞
父試圖加了手勢,子button即時沒添加target+action,手勢也不響應(yīng),除非設(shè)置userEnabled=NO,UIControl沒有重寫touch方法,沒有添加事件,父試圖的手勢也會響應(yīng)
同時響應(yīng)子按鈕click和父試圖的tap,設(shè)置tap的cancelsTouchesInView=no,參考:http://www.itdecent.cn/p/617577ff4be1
響應(yīng)鏈應(yīng)用場景:
1、事件攔截和事件轉(zhuǎn)發(fā):有兩種方案實現(xiàn)
(1)重寫touchesBegan等系列方法進(jìn)行攔截
(2)重寫hitTest方法添加邏輯返回自己想要的響應(yīng)者
2、擴(kuò)大按鈕點擊范圍:創(chuàng)建button分類重寫pointInside方法使的系統(tǒng)調(diào)用hitTest時判斷范圍更大

3、回溯響應(yīng)鏈生成埋點控件唯一id
4、基于UI響應(yīng)鏈的事件傳遞方式,實現(xiàn)步驟:
(1)添加一個UIResponder的分類方法實現(xiàn)中調(diào)用nextResponder的該方法
- (void)routerEventWithSelectorName:(NSString *)selectorName?object:(id)object?userInfo:(NSDictionary *)userInfo {
?[[self nextResponder] routerEventWithSelectorName:selectorName?object:object?userInfo:userInfo];
}
(2)當(dāng)?shù)谝豁憫?yīng)者發(fā)生事件后,調(diào)用該方法就會沿著響應(yīng)鏈向上傳遞
(3)響應(yīng)鏈上的控件可以實現(xiàn)該方法獲取事件進(jìn)行處理,或者添加內(nèi)容讓事件繼續(xù)向上傳遞
手勢和UI事件響應(yīng):
通過hittest找到第一響應(yīng)者后,可能會有兩條路線,手勢識別和touchBegan系列方法同時執(zhí)行,手勢識別優(yōu)先級高如果識別成功會調(diào)用touchCancel方法取消響應(yīng)鏈上所有的touch方法調(diào)用

父View加了tap手勢,子view也會相應(yīng)手勢,但是點擊子button不會響應(yīng)手勢,不管button有沒有添加事件
原因是UIButton,UISwitch,UISegmentedControl,UIStepper和UIPageControl等控件的是否接受觸摸事件方法(gestureRecognizerShouldBegin:)返回值默認(rèn)是NO,這樣點擊按鈕時手勢識別會失敗,只處理button的touch事件和lick事件,button默認(rèn)重寫的touch事件默認(rèn)不向上調(diào)用touch方法
如果想同時響應(yīng)tap和button事件,設(shè)置設(shè)置tap的cancelsTouchesInView為NO,這樣不再調(diào)用button的是否接受觸摸事件方法,兩個事件同時響應(yīng)
aview上增加button,button添加點擊事件,再添加一個子bview,點擊bview時,bview和button都走了touchbegan和end系列方法,但是button事件沒有執(zhí)行,猜測只有button是第一響應(yīng)者時才執(zhí)行
如果aview加了tap手勢,點擊bview也會觸發(fā)tap方法
父View加了tap手勢,點擊子視圖uicollectionview的cell,發(fā)現(xiàn)cell沒有被響應(yīng),響應(yīng)的是tap手勢事件
原因是手勢識別時可以延時響應(yīng)touch事件,只有手勢識別完成才調(diào)用touch相關(guān)事件,cell的響應(yīng)機(jī)制就是這樣,第一響應(yīng)者是cell,但是手勢識別優(yōu)先處理手勢并沒有處理touch事件
如果想要響應(yīng)cell事件不響應(yīng)tap,在tap的應(yīng)接受觸摸代理方法shouldReceiveTouch:判斷點擊的是tableview就返回NO
嵌套滾動試圖沖突,如一個UICollectionView嵌套了一個UICollectionView,希望嵌套的UICollectionView在父視圖達(dá)到一定高度時,父視圖不再滾動,而是子視圖滾動,有三種方案:
a、設(shè)置只有底層響應(yīng)滾動上層不響應(yīng),底層scrollview可滾,上層tableview不可滾動,tableview.height = tableview.contentSize.height
b、創(chuàng)建底層滾動試圖的子類,實現(xiàn)手勢代理方法使兩個滾動手勢同時響應(yīng),監(jiān)聽兩個viewdidscroll方法,在同一方法里分情況設(shè)置兩個視圖的滾動偏移量,如愛錢進(jìn)產(chǎn)品列表頁
c、上下試圖同時響應(yīng)滾動手勢,在手勢沖突的代理方法里根據(jù)偏移量判斷響應(yīng)哪個手勢,http://www.itdecent.cn/p/adc8d45f0fef
手勢:
參考:http://www.itdecent.cn/p/617577ff4be1
常見的手勢沖突處理:http://www.itdecent.cn/p/adc8d45f0fef
我們可以通過配置手勢的屬性來改變它的表現(xiàn),下面介紹三個常用的屬性:
cancelsTouchesInView:該屬性默認(rèn)是 true。顧名思義,如果設(shè)置成 false,當(dāng)手勢識別成功時,將不會發(fā)送 touchesCancelled 給目標(biāo)視圖,從而也不會打斷視圖本身方法的觸發(fā),最后的結(jié)果是手勢和本身方法同時觸發(fā)。有的時候我們不希望手勢覆蓋掉視圖本身的方法,就可以更改這個屬性來達(dá)到效果。
delaysTouchesBegan:該屬性默認(rèn)是 false。在上個例子中我們得知,在手指觸摸屏幕之后,手勢處于 .possible 狀態(tài)時,視圖的 touches 方法已經(jīng)開始觸發(fā)了,當(dāng)手勢識別成功之后,才會取消視圖的 touches 方法。當(dāng)該屬性時 true 時,視圖的 touches 方法會被延遲到手勢識別成功或者失敗之后才開始。也就是說,假如設(shè)置該屬性為 true ,在整個過程中識別手勢又是成功的話,視圖的 touches 系列方法將不會被觸發(fā)。
delaysTouchesEnded:該屬性默認(rèn)是 true。與上個屬性類似,該屬性為 true 時,視圖的 touchesEnded 將會延遲大約 0.15s 觸發(fā)。該屬性常用于連擊,比如我們需要觸發(fā)一個雙擊手勢,當(dāng)我們手指離開屏幕時應(yīng)當(dāng)觸發(fā) touchesEnded,如果這時該屬性為 false,那就不會延遲視圖的 touchesEnded 方法,將會立馬觸發(fā) ,那我們的雙擊就會被識別為兩次單擊。當(dāng)該屬性是 true 時,會延遲 touchesEnded 的觸發(fā),將兩次單擊連在一起,來正常識別這種雙擊手勢。
9、分類Category
分類:
Category編譯之后的底層結(jié)構(gòu)是category_t,里面存儲著分類的對象方法、類方法、屬性聲明、協(xié)議信息
在程序運(yùn)行的時候,runtime會將Category的數(shù)據(jù),合并到類信息class_rw_t的方法表頭中(類對象、元類對象中)
rw_t->method是一個二維數(shù)據(jù)結(jié)構(gòu),原始類的只有一個指針指向類的方法list,runtime在運(yùn)行時將數(shù)組擴(kuò)容,將分類方法list插入二維數(shù)據(jù)前面,后編譯的在前面

分類的屬性:
Category可以添加屬性,但是只有屬性聲明,不會自動生成set/get方法的實現(xiàn),更不會添加成員變量
因為category_t結(jié)構(gòu)體中并不存在成員變量,且成員變量列表class_ro_t是只讀的,不可以在運(yùn)行時添加,可以自己實現(xiàn)set/get方法通過關(guān)聯(lián)對象間接實現(xiàn)
分類和延展的區(qū)別:
延展Class Extension在編譯的時候包含在類信息中,Category是在運(yùn)行時合并過去
如何做到分類方法不覆蓋原方法:
可以在調(diào)用時runtime遍歷方法list找到最后的方法調(diào)用
load、initialize方法
1、+load是啟動時runtime加載類和分類的過程中根據(jù)函數(shù)地址調(diào)用,只調(diào)用一次;
runtime在調(diào)用+load方法之前,準(zhǔn)備了兩個數(shù)組分別存儲類和分類列表,類數(shù)組是先根據(jù)編譯先加入數(shù)組,加入子類時會先把父類加入;
分類只按編譯順序加入數(shù)據(jù),調(diào)用時先遍歷類數(shù)組調(diào)用+load,所有類都調(diào)用完再調(diào)用分類數(shù)組
2、+initialize方法會在類第一次接收到消息時調(diào)用,通過objc_msgSend進(jìn)行調(diào)用
runtime的objc_msgSend內(nèi)部實現(xiàn),先判斷類是否初始化,如未初始化會先調(diào)用父類的+initialize,再調(diào)用子類的+initialize,每個類只會初始化1次

3、因為initialize是通過objc_msgSend調(diào)用,存在兩個問題:
分類實現(xiàn)了+initialize,就覆蓋類本身的+initialize調(diào)用,如果子類沒有實現(xiàn)+initialize,會調(diào)用父類的+initialize(所以父類的+initialize可能會被調(diào)用多次)所以initialize實現(xiàn)代碼要配合GCD ONCE函數(shù)
10、關(guān)聯(lián)對象AssociatedObject:
關(guān)聯(lián)對象:
一個對象可以通過key關(guān)聯(lián)多個對象,使用key添加修改刪除關(guān)聯(lián)對象
對象走dealloc時會從全局HashMap中查找并刪除他的關(guān)聯(lián)對象
關(guān)聯(lián)對象的key要保證全局唯一,有很多種方案實現(xiàn),一般使用全局靜態(tài)唯一地址值,建議使用@selecor(name)

存儲結(jié)構(gòu):切套哈希表
外層HashMap以對象地址為key值為內(nèi)層哈希表,內(nèi)層哈希表以屬性地址為key值為存儲的value和內(nèi)存管理變量
關(guān)聯(lián)對象不會對傳入的對象obj進(jìn)行引用,底層實現(xiàn)支持根據(jù)obj生成一個唯一key,對應(yīng)存儲的value進(jìn)行持有

關(guān)聯(lián)對象如何實現(xiàn)weak修飾
關(guān)聯(lián)對象默認(rèn)修飾符只有retain, assgin copy沒有weak
可以對象對象賦值給一個__weak變量,創(chuàng)建一個返回值為__weak變量的block,將block存儲為關(guān)聯(lián)對象值,取值時執(zhí)行block
11、通知
注冊通知有兩種方式:selector和block
selecer需要傳入接收者observer,selecter,name,和發(fā)送者anObject
Block不需要傳入接收者,可以傳指定隊列中執(zhí)行block,可以指定到主線程執(zhí)行
- (void)addObserver:(id)observer selector:(SEL)aSelector name:(nullable NSNotificationName)aName object:(nullable id)anObject;
- (id <NSObject>)addObserverForName:(nullable NSNotificationName)name object:(nullable id)obj queue:(nullable NSOperationQueue *)queue usingBlock:(void (^)(NSNotification *note))block
anObject參數(shù):
anObject:發(fā)送通知的對象,如果傳nil表示不關(guān)心是誰發(fā)送的通知,觀察者那邊傳入nil表示可以接受任何發(fā)送者的通知。
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(test:) name:@"test" object:@"1"];
[[NSNotificationCenter defaultCenter] postNotificationName:@"test" object:@"2"];
object相同才會接收到通知方法調(diào)用,如果我有一個需求,用戶點擊某個按鈕十次才會觸發(fā)某個事件,那么我就可以把這個參數(shù)設(shè)置為 @10,每次點擊將發(fā)送的 object 加1,這樣當(dāng)點擊的數(shù)量等于10的時候就可以自動觸發(fā)回調(diào)事件而不需要寫
通知的存儲結(jié)構(gòu):哈希表+鏈表(name和anObject是key,observer和seleter是鏈表節(jié)點值)
注冊的通知存儲在通知中心單利的NCTbl結(jié)構(gòu)體中,以有無name和object分別儲存在三個表結(jié)構(gòu)中
1、有name和無論有無object,使用二維哈希表+鏈表Obs,外層key是name地址值為內(nèi)層哈希表,內(nèi)層哈希表key是object值時存儲observer和select的鏈表

2、無name有anObject的,一維表+鏈表Obs,key為object值為鏈表
3、全無的,直接存鏈表Obs
通知的發(fā)送:
1、一條通知對象是由三個元素組成:通知名name,發(fā)送者object,附加參數(shù)userInfo,發(fā)送消息時選擇性傳入
- (void)postNotificationName:(NSNotificationName)aName object:(nullable id)anObject userInfo:(nullable NSDictionary *)aUserInfo;
2、發(fā)送通知時根據(jù)name和object查找到所有的鏈表節(jié)點obs對象(保存了observer和sel),放到數(shù)組中,依次調(diào)用performSelector同步發(fā)送,block形式注冊的通知放到隊列中執(zhí)行
3、添加通知隊列NSNotificationQueue的通知,可以根據(jù)添加的策略結(jié)合runloop運(yùn)行時機(jī)將通知交給通知中心發(fā)送,并提供通知合并管理功能
從線程的角度看并不是真正的異步發(fā)送,或可稱為延時發(fā)送,它是利用了runloop的時機(jī)來觸發(fā)的
通知與多線程:
1、通知是線程安全的,通過runtime執(zhí)行selecter,通知的發(fā)送和接收在同一線程且多個接收者順序執(zhí)行
2、想要接受時在指定線程執(zhí)行可以在selecter方法內(nèi)部切換線程,或使用block注冊時指定隊列,或使用NSMachPort實現(xiàn)線程通訊參考:https://juejin.cn/post/6844904147691503624
通知移除:
iOS9之后對接收者的持有由unsafe_unretained 變成了weak,不移除也安全
但是通過 addobserverForName :object: queue:usingBlock 方法注冊的觀察者是強(qiáng)引用的需要手動釋放
12、KVO
KVO:鍵值監(jiān)聽,用于監(jiān)聽某個對象屬性值的改變
使用NSObject分類實現(xiàn)監(jiān)聽的注冊和移除方法,- (void)addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(nullable void *)context;
一個KVO包含的數(shù)據(jù)有5個:調(diào)用add的對象為注冊KVO的對象(被觀察者)、實現(xiàn)監(jiān)聽代理的Obsever(觀察者)、屬性keyPath、新老值option,環(huán)境信息context(不同對象相同屬性key時用于區(qū)分環(huán)境)
觸發(fā)KVO的方式點語言self.age和KVC,直接訪問成員變量不行self->age
KVC可以觸發(fā)KVO,不管KVC是通過set賦值還是直接查找成員變量賦值,也就是KVC內(nèi)部實現(xiàn)有調(diào)用KVO方法的邏輯,不是通過調(diào)用KVO派生子類的set方法實現(xiàn)的,應(yīng)該是在setValue:forKey前后調(diào)用了willchangevalue和didchangevalue
KVO的原理:
1、注冊KVO的類在運(yùn)行時自動生成一個子類即派生類,派生會被緩存下來,將實例對象的isa指向派生子類,并重寫set/class/delloc,添加isKVO方法
set方法調(diào)用fundation框架的_NSSetXXXValueAndNotify函數(shù),函數(shù)內(nèi)部調(diào)用willChangeValueForKey、父類原來的setter、didChange…方法,向監(jiān)聽者發(fā)消息調(diào)用代理方法
2、存儲:這些數(shù)據(jù)存儲在全局的嵌套hashMap+數(shù)組中,外層以被觀察者為key,內(nèi)層以被觀察屬性keyPath為key,數(shù)組里的元素存儲觀察者obsver和option等信息
3、KVO是線程同步的,回調(diào)函數(shù)執(zhí)行與KVO發(fā)生在同一線程
http://www.itdecent.cn/p/56baca325824

手動觸發(fā)KVO:
automaticallyNotifiesObserversForName函數(shù)默認(rèn)返回YES自動觸發(fā)KVO,可以重寫該函數(shù)返回NO,手動觸發(fā)KVO
手動觸發(fā)需要成對調(diào)用willChangeValueForKey和didChangeValueForKey
KVO 監(jiān)聽數(shù)組變化
1、對可變數(shù)組屬性添加KVO默認(rèn)數(shù)組內(nèi)容變化不會觸發(fā)KVO,可以通過mutableArrayValueForKey:獲取可變數(shù)組再添加元素可以實現(xiàn)監(jiān)聽,如:[[self.selectedsArr mutableArrayValueForKey:@"selecteds"] addObject:]];
2、原理猜測是通過mutableArrayValueForKey函數(shù)獲取一個重寫了add和remove方法的數(shù)組,添加了willChange和didChange
KVO異常防護(hù):
KVO異常原因:KVO的注冊和移除必須成對出現(xiàn),否則會出現(xiàn)崩潰,觀察者已銷毀未移除KVO、多次移除、未注冊就移除都會出現(xiàn)崩潰、KVO使用期間keypath銷毀、為實現(xiàn)代理方法等等
防護(hù)原理:
1、先建立NSObject分類,攔截add、remode、和delloc方法
2、創(chuàng)建一個KVODeleage類充當(dāng)觀察者,將原始的被觀察者、觀察者、keypath、options、context等信息全部存儲在一個字典中,存儲結(jié)構(gòu)嵌套哈希表+數(shù)組,key是被觀察者地址+屬性keypath,值為觀察者等
3、添加和移除觀察者時可以結(jié)合字典排重
4、攔截dealloc后可以進(jìn)行移除kvo避免漏移
https://www.imgeek.org/article/825358174
13、KVC:
鍵值編碼:通過key和keypath存儲訪問實例變量的方式
KVC的屬性賦值是以NSObject分類方式NSObject(NSKeyValueCoding)實現(xiàn)的
分類中方法+ (BOOL)accessInstanceVariablesDirectly;默認(rèn)返回YES,表示如果沒有找到Set<Key>方法的話,會按照_key,_iskey,key,iskey的順序搜索成員,設(shè)置成NO就不這樣搜索
KVC賦值流程:取值流程類似

KVC對容器類也實現(xiàn)了分類,添加了setValue: forKey:方法
如果是字典的話,則修改key對應(yīng)的value,如果是數(shù)組或者集合,則會向每個對象發(fā)送此消息,去修改元素的key對應(yīng)的property
對于字典value為nil時不會crash,對于字典相當(dāng)于刪除key-value對,相當(dāng)于調(diào)用 -removeObjectForKey,調(diào)用字典的setObject: forKey:方法會crash
KVC可以觸發(fā)KVO,不管KVC是通過set賦值還是直接查找成員變量賦值,也就是KVC內(nèi)部實現(xiàn)有調(diào)用KVO方法的邏輯,不是通過調(diào)用KVO派生子類的set方法實現(xiàn)的,應(yīng)該是在setValue:forKey前后調(diào)用了willchangevalue和didchangevalue方法
KVC引用場景:
1、解析數(shù)據(jù)時字典轉(zhuǎn)模型,路由參數(shù)解析賦值
2、可視化無痕埋點時附加字段抓取
14、數(shù)組
數(shù)組:是一種順序存儲的線性表,所有元素的內(nèi)存地址是連續(xù)的
OC數(shù)組本質(zhì):
NSArray和NSMutableArray底層使用的是隱藏類__NSArrayI、__NSArrayM和__NSSingleObjectArrayI
數(shù)據(jù)存儲在id *list數(shù)組指針中,可變數(shù)組的list是循環(huán)數(shù)組,_offset標(biāo)識起始插入位置
可變數(shù)組在超限時自動創(chuàng)建大小為原來二倍的新數(shù)組并復(fù)制過去
數(shù)組遍歷方式:
OC中數(shù)組有4類:for、for in、枚舉器enumerate、dispatch_apply函數(shù)等
for in性能最好,基于快速枚舉,直接從c數(shù)組中取值
枚舉器enumerate包含一種子線程遍歷,耗時較大的遍歷可用
為什么數(shù)組遍歷刪除元素crash
for in刪除會Crash,因為底層遍歷時會做檢查,原數(shù)組發(fā)生改變就crash
for刪除不會crash,因為底層調(diào)用n遍objectAtIndex:,for()中做了越界保護(hù)
數(shù)組遍歷刪除方案:
遍歷數(shù)組取出待刪除元素,調(diào)用removeObjectsInArray:
直接調(diào)用for刪除會發(fā)生元素跳位,刪除元素時可配合使用i--
15、NSDictionary和哈希表
NSDictionary底層實現(xiàn):
1、NSDictionary是對NSMapTable的封裝,底層采用哈希表存儲,開放定制法解決哈希沖突
2、哈希表本質(zhì)是一個數(shù)組,字典在存值時,key和value都不能為空,會根據(jù)key的哈希值經(jīng)過位運(yùn)算(取余%)計算得到存儲的index
3、key可以是字符串或NSobject類型,但是必須遵守copying協(xié)議實現(xiàn)copyWithZone方法,繼承NSObject作為key需要重載hash:和isEqual:方法,如不重載默認(rèn)NSObject的hash方法返回內(nèi)存地址,isEqual:只是判斷內(nèi)存地址是否相等==
4、NSMapTable是可變的,可以設(shè)置對key和value的weak引用,當(dāng)key和value被釋放時自動清除實體,也可以設(shè)置添加value時復(fù)制,一般用于實現(xiàn)弱引用表如SD內(nèi)存緩存表
5、KVC通過字典分類添加setValue:forKey:方法進(jìn)行字典賦值,key必須是字符串類型不能是對象,key不能為nil,value可以為空,為nil時刪除key元素
6、NSSet底層封裝NSHashTable,NSHashTable同樣可以更靈活的定制規(guī)則
關(guān)于==、isEqual、hash
==基礎(chǔ)類型比較值是否相等,對象類型判斷兩個對象的內(nèi)存地址是否相等
isEqual:方法NSObject的默認(rèn)實現(xiàn)是==,即比較內(nèi)存地址,子類可以重載,可實現(xiàn)更高級別的比較,如地址不同但兩個對象屬性相同可認(rèn)為是同一個對象
hash:方法NSObject默認(rèn)返回對象地址,字典存值時根據(jù)哈希值計算位置,為了避免存值的哈希沖突在使用非字符串作為key是盡量重寫該方法減少不同哈希沖突,NSString類型的hash函數(shù)經(jīng)過重載已經(jīng)不是簡單的內(nèi)存地址,不容易出現(xiàn)哈希沖突建議使用字符串作為key。https://juejin.cn/post/6844903717578211341

哈希表(hash table,也叫散列表)
1、哈希表根據(jù)鍵key直接訪問Value的數(shù)據(jù)結(jié)構(gòu),哈希表的key和value封裝成節(jié)點,實際存儲在鏈表節(jié)點(拉鏈法)或數(shù)組中(開發(fā)定址法)
2、哈希函數(shù):字符串key經(jīng)過hash方法得到一個整數(shù)值x,根據(jù)除留余數(shù)法,將x值余哈希表大小M能得到最初的index,由于不同的key會得到相同的index所以需要處理哈希沖突
3、哈希表擴(kuò)容2倍?(數(shù)組擴(kuò)容一般1.5倍)
哈希表存值時根據(jù)key的哈希值對數(shù)組進(jìn)行取余XXX%9,可轉(zhuǎn)換成進(jìn)行&運(yùn)算XXX&(9-1),例如原始長度是16,擴(kuò)容后的長度是32,這樣做是為了&運(yùn)算的結(jié)果盡量保持一致,數(shù)組擴(kuò)容是2的n次冪時就可以盡量避免hash沖突的發(fā)生。https://blog.csdn.net/pk_sir/article/details/107858439

哈希沖突:
1、拉鏈法:數(shù)組 + 鏈表,最終的數(shù)據(jù)存儲在鏈表節(jié)點中
字典的key通過哈希函數(shù)得到數(shù)組index,數(shù)組的每一個元素指向一個鏈表,鏈表的節(jié)點存儲字典的key和value,出現(xiàn)哈希沖突時使用鏈表連接所有節(jié)點
鏈表過長時會轉(zhuǎn)成紅黑樹存儲

2、開放定址線性探測法:使用多個數(shù)組完成,最終的數(shù)據(jù)存儲在數(shù)組中
數(shù)組keys存儲所有key,values存儲所有值,得到index即可訪問數(shù)據(jù)
創(chuàng)建一個處理哈希沖突的數(shù)組存index,當(dāng)哈希函數(shù)得到相同的index時直接將下標(biāo)索引加一

哈希沖突方案對比:
拉鏈法鏈表動態(tài)申請,適合節(jié)點不固定情況,鏈表方便刪除節(jié)點,但拉鏈法的內(nèi)存使用較多,拉鏈法處理沖突簡單不會產(chǎn)生堆積問題,適用于數(shù)據(jù)量大的
開發(fā)定制法刪除節(jié)點時只能標(biāo)記刪除,不能真刪,容易產(chǎn)生堆積問題,適用于數(shù)據(jù)量小或臨時緩存表等如__weak表
iOS中常用哈希表的地方

16、SDWebimage
SD通過OperationQueue子線程下載任務(wù)、內(nèi)存+磁盤雙重緩存、子線程解碼
下載并發(fā)數(shù)是6,超時15秒,緩存過期默認(rèn)一周,50M
SD的緩存:
1、SD請求圖片前先根據(jù)url的md5分別請求內(nèi)存緩存和磁盤緩存,都沒有再去下載
2、內(nèi)存緩存:使用NSCache的子類存uiimage+弱引用表NSMapTable管理UIImage
使用若引用表獲取圖片的好處是當(dāng)cache被清除時image還可能被頁面強(qiáng)持有,仍可以通過內(nèi)存訪問,避免不必要的磁盤訪問
3、磁盤緩存:使用url的md5拼接路徑存儲編碼后的data
4、緩存清理:內(nèi)存警告時內(nèi)存緩存全部清除,App進(jìn)入后臺和退出時先清理磁盤過期圖片,然后判斷緩存是否超限如仍超限折半清除磁盤
NSCache做緩存比NSDictionary區(qū)別:
1、資源耗盡時自動刪減,2、收到低內(nèi)存告警時先刪除最久未使用內(nèi)存,3、多線程安全,4、可設(shè)置大小,5、默認(rèn)不拷貝鍵key
url未變,但圖片資源變化如何及時更新:
1、SD設(shè)置Option忽略緩存直接下載
2、設(shè)置HTTP1.1的If-Modified判斷客戶端緩存與服務(wù)器資源是否更新,如緩存是最新的HTTP會返回304,否則正常下載返回200
子線程解碼:
JPEG/PNG不是位圖,是經(jīng)過編碼壓縮后的數(shù)據(jù),需要將其解碼成位圖才能渲染到屏幕上,磁盤緩存存的是編碼壓縮的PNG圖片
通常使用的imageNamed:默認(rèn)會在主線程解碼,消耗CPU,大量使用會卡頓,
imageNamed:有內(nèi)存緩存,加載大圖時可直接使用imageWithContentsOfFile:
SD的子線程解碼步驟:在子線程創(chuàng)建繪圖上下文,繪圖,成圖
17、CocoaPods原理:
CocoaPods實現(xiàn)原理:
1、使用Ruby編寫的iOS庫管理工具,包含若干個gems包,命令解析器、腳本解析器、下載器、工程集成器、pods插件管理器等
2、提供依賴庫版本管理,下載,pod工程創(chuàng)建,workspace集成,預(yù)編譯等功能
3、有三個主要目錄:索引庫、緩存庫、每個工程的pods代碼庫
pod install和pod update區(qū)別:
1、install只對podfile中不符合.lock中條件的pod庫更新,update會忽略.lock直接更新
2、這兩個命令默認(rèn)都會自動更新索引庫repo,可以使用--no-repo-update忽略更新
pod install執(zhí)行流程:
1、命令解析器解析pod命令,2、檢查更新索引庫,3、腳本解析器分析Podfile和.lock生成待下載列表,4、下載器下載pod庫,5、集成器生成Pods工程集成進(jìn)workspace,6、保存依賴到.lock,7、執(zhí)行pod插件
Pod插件編譯優(yōu)化:
利用pod插件在pod install過程將庫編譯成二進(jìn)制靜態(tài)庫,Xcode編譯時只需要鏈接靜態(tài)即可
18、Git原理:
Git是一個分布式版本控制系統(tǒng),每個開發(fā)者本地保存一個完整的文件版本鏡像,可以離線版本管理,然后再同步到中心版本庫
git分4個區(qū):本地工作區(qū),本地暫存區(qū),本地倉庫,遠(yuǎn)程倉庫
.git文件用于版本跟蹤,存儲本地倉庫和暫存區(qū),包含各文件版本的快照、分支、log等
Git底層實現(xiàn):git是一套內(nèi)容尋址的文件系統(tǒng)
git管理的內(nèi)容經(jīng)過加密算法生成三種對象
1、blob對象只跟文本文件的內(nèi)容;2、tree對象記錄文本文件內(nèi)容和名稱、目錄等信息;3、commit對象記錄本次提交的所有信息,包括提交人、提交時間,本次提交包含的tree及blob
代碼回滾:如A之后提交了B
1、B未push到遠(yuǎn)端:git reset A
2、push到遠(yuǎn)端:1、反轉(zhuǎn)提交Bgit revert B,2、git reset?—hard A 然后git reset B 然后commit加push,3、切分支處理
合并代碼:
1、git merge 合并分支,把一個分支合并進(jìn)當(dāng)前的分支,產(chǎn)生合并提交
2、git rebase 打補(bǔ)丁提交,先保存提交代碼,拉取新分支內(nèi)容,然后提交代碼補(bǔ)丁,不會產(chǎn)生合并提交