目錄
- 對象引用計數(shù)放哪里区宇?
- MVVM和MVC的區(qū)別
- UIButton防止多次點擊
- 如何監(jiān)聽弱網(wǎng)
- 卡頓檢測
- NSCache轨蛤,NSDictionary朝氓,NSArray的區(qū)別
- SDWebImage里面用了哪種緩存策略缸逃?
- self + weakSelf + strongSelf ?
一 對象引用計數(shù)放哪里少辣?
我們先看看 struct objc_class
的結(jié)構(gòu)
再看看 isa
結(jié)構(gòu)
- 在arm64架構(gòu)之前耕突,isa就是一個普通的
指針
笤成,存儲著Class、Meta-Class對象的內(nèi)存地址
- 從arm64架構(gòu)開始眷茁,對isa進行了優(yōu)化炕泳,變成了一個
共用體(union)
結(jié)構(gòu),還使用位域
來存儲更多的信息
- isa結(jié)構(gòu)體
/** isa_t 結(jié)構(gòu)體 */
union isa_t {
Class cls;
uintptr_t bits;
struct {
uintptr_t nonpointer : 1;
uintptr_t has_assoc : 1;
uintptr_t has_cxx_dtor : 1;
uintptr_t shiftcls : 33;
uintptr_t magic : 6;
uintptr_t weakly_referenced : 1;
uintptr_t deallocating : 1;
uintptr_t has_sidetable_rc : 1;
uintptr_t extra_rc : 19;
};
};
extra_rc
:表示該對象的引用計數(shù)值蔼卡,實際上是引用計數(shù)值減 1喊崖,例如,如果對象的引用計數(shù)為 10雇逞,那么 extra_rc 為 9荤懂。如果引用計數(shù)大于 10,則需要使用到下面的 has_sidetable_rc塘砸。has_sidetable_rc
:當對象引用計數(shù)大于 10 時节仿,則has_sidetable_rc 的值為 1,那么引用計數(shù)會存儲在一個叫 SideTable 的類的屬性中掉蔬,這是一個散列表廊宪。
對象引用計數(shù)就存放在extra_rc
中
相關參考
iOS-底層原理(13)-runtime之isa詳解
iOS-底層原理(14)isa-Class的結(jié)構(gòu)詳解
二 MVVM和MVC的區(qū)別
2.1 MVC
MVC的弊端
-
厚重的View Controller
M:模型model的對象通常非常的簡單。根據(jù)Apple的文檔女轿,model應包括數(shù)據(jù)和操作數(shù)據(jù)的業(yè)務邏輯箭启。而在實踐中,model層往往非常薄蛉迹,不管怎樣傅寡,model層的業(yè)務邏輯不應被拖入到controller。
V:視圖view通常是UIKit控件(component,這里根據(jù)習慣譯為控件)或者編碼定義的UIKit控件的集合荐操。View的如何構(gòu)建(PS:IB或者手寫界面)何必讓Controller知曉芜抒,同時View不應該直接引用model(PS:現(xiàn)實中,你懂的M衅簟)钝腺,并且僅僅通過IBAction事件引用controller扼睬。業(yè)務邏輯很明顯不歸入view蜗细,視圖本身沒有任何業(yè)務送丰。
C:控制器controller。Controller是app的“膠水代碼”:協(xié)調(diào)模型和視圖之間的所有交互疗绣∵胙牵控制器負責管理他們所擁有的視圖的視圖層次結(jié)構(gòu),還要響應視圖的loading持痰、appearing、disappearing等等祟蚀,同時往往也會充滿我們不愿暴露的model的模型邏輯以及不愿暴露給視圖的業(yè)務邏輯工窍。網(wǎng)絡數(shù)據(jù)的請求及后續(xù)處理,本地數(shù)據(jù)庫操作前酿,以及一些帶有工具性質(zhì)輔助方法都加大了Massive View Controller的產(chǎn)生患雏。
遺失(無處安放)的網(wǎng)絡邏輯
蘋果使用的MVC的定義是這么說的:所有的對象都可以被歸類為一個model,一個view罢维,或是一個controller淹仑。
你可能試著把它放在Model對象里,但是也會很棘手肺孵,因為網(wǎng)絡調(diào)用應該使用異步匀借,這樣如果一個網(wǎng)絡請求比持有它的model生命周期更長,事情將變的復雜平窘。顯然View里面做網(wǎng)絡請求那就更格格不入了吓肋,因此只剩下Controller了。若這樣瑰艘,這又加劇了Massive View Controller的問題是鬼。若不這樣,何處才是網(wǎng)絡邏輯的家呢紫新?
- 較差的可測試性
由于View Controller混合了視圖處理邏輯和業(yè)務邏輯均蜜,分離這些成分的單元測試成了一個艱巨的任務。
2.2 MVVM
一種可以很好地解決Massive View Controller
問題的辦法就是將 Controller 中的展示邏輯抽取出來芒率,放置到一個專門的地方囤耳,而這個地方就是 viewModel
。MVVM衍生于MVC,是對 MVC 的一種演進紫皇,它促進了 UI 代碼與業(yè)務邏輯的分離慰安。它正式規(guī)范了視圖和控制器緊耦合的性質(zhì),并引入新的組件聪铺。他們之間的結(jié)構(gòu)關系如下:
2.2.1 MVVM 的基本概念
- 在
MVVM
中化焕,view
和view controller
正式聯(lián)系在一起,我們把它們視為一個組件 -
view
和view controller
都不能直接引用model
铃剔,而是引用視圖模型(viewModel
) -
viewModel
是一個放置用戶輸入驗證邏輯撒桨,視圖顯示邏輯,發(fā)起網(wǎng)絡請求和其他代碼的地方 - 使用
MVVM
會輕微的增加代碼量键兜,但總體上減少了代碼的復雜性
2.2.2 MVVM 的注意事項
-
view
引用viewModel
凤类,但反過來不行(即不要在viewModel
中引入#import UIKit.h
,任何視圖本身的引用都不應該放在viewModel
中)(PS:基本要求普气,必須滿足) -
viewModel
引用model
谜疤,但反過來不行* MVVM 的使用建議 -
MVVM
可以兼容你當下使用的MVC
架構(gòu)。 -
MVVM
增加你的應用的可測試性现诀。 -
MVVM
配合一個綁定機制效果最好(PS:ReactiveCocoa你值得擁有)夷磕。 -
viewController
盡量不涉及業(yè)務邏輯,讓viewModel
去做這些事情仔沿。 -
viewController
只是一個中間人坐桩,接收view
的事件、調(diào)用viewModel
的方法封锉、響應viewModel
的變化绵跷。 -
viewModel
絕對不能包含視圖view(UIKit.h)
,不然就跟view
產(chǎn)生了耦合成福,不方便復用和測試碾局。 -
viewModel
之間可以有依賴。 -
viewModel
避免過于臃腫闷叉,否則重蹈Controller
的覆轍擦俐,變得難以維護。
2.2.3 MVVM 的優(yōu)勢
- 低耦合:
View
可以獨立于Model
變化和修改握侧,一個viewModel
可以綁定到不同的View
上 - 可重用性:可以把一些視圖邏輯放在一個
viewModel
里面蚯瞧,讓很多view
重用這段視圖邏輯 - 獨立開發(fā):開發(fā)人員可以專注于業(yè)務邏輯和數(shù)據(jù)的開發(fā)
viewModel
,設計人員可以專注于頁面設計 - 可測試:通常界面是比較難于測試的品擎,而
MVVM
模式可以針對viewModel
來進行測試
2.2.4 MVVM 的弊端
- 數(shù)據(jù)綁定使得
Bug
很難被調(diào)試埋合。你看到界面異常了,有可能是你View
的代碼有Bug
萄传,也可能是Model
的代碼有問題甚颂。數(shù)據(jù)綁定使得一個位置的Bug
被快速傳遞到別的位置蜜猾,要定位原始出問題的地方就變得不那么容易了。 - 對于過大的項目振诬,數(shù)據(jù)綁定和數(shù)據(jù)轉(zhuǎn)化需要花費更多的內(nèi)存(成本)蹭睡。主要成本在于:
- 數(shù)組內(nèi)容的轉(zhuǎn)化成本較高:數(shù)組里面每項都要轉(zhuǎn)化成
Item
對象,如果Item對象中還有類似數(shù)組赶么,就很頭疼肩豁。 - 轉(zhuǎn)化之后的數(shù)據(jù)在大部分情況是不能直接被展示的,為了能夠被展示辫呻,還需要第二次轉(zhuǎn)化清钥。
- 只有在API返回的數(shù)據(jù)高度標準化時,這些對象原型(
Item
)的可復用程度才高放闺,否則容易出現(xiàn)類型爆炸祟昭,提高維護成本。 - 調(diào)試時通過對象原型查看數(shù)據(jù)內(nèi)容不如直接通過
NSDictionary/NSArray
直觀怖侦。 - 同一API的數(shù)據(jù)被不同View展示時篡悟,難以控制數(shù)據(jù)轉(zhuǎn)化的代碼,它們有可能會散落在任何需要的地方匾寝。
2.3 總結(jié)
MVC
的設計模式也并非是病入膏肓恰力,無藥可救的架構(gòu),最起碼目前MVC設計模式仍舊是iOS開發(fā)的主流框架旗吁,存在即合理。針對文章所述的弊端停局,我們依舊有許多可行的方法去避免和解決很钓,從而打造一個輕量級的ViewController
。MVVM
是MVC
的升級版董栽,完全兼容當前的MVC架構(gòu)码倦,MVVM雖然促進了UI 代碼與業(yè)務邏輯的分離,一定程度上減輕了ViewController
的臃腫度锭碳,但是View
和ViewModel
之間的數(shù)據(jù)綁定使得 MVVM變得復雜和難用了袁稽,如果我們不能更好的駕馭兩者之間的數(shù)據(jù)綁定,同樣會造成Controller 代碼過于復雜擒抛,代碼邏輯不易維護的問題推汽。一個輕量級的
ViewController
是基于MVC
和MVVM
模式進行代碼職責的分離而打造的。MVC和MVVM有優(yōu)點也有缺點歧沪,但缺點在他們所帶來的好處面前時不值一提的歹撒。他們的低耦合性,封裝性诊胞,可測試性暖夭,可維護性和多人協(xié)作便利大大提高了開法效率。同時,我們需要保持的是一個擁抱變化的心迈着,以及理性分析的態(tài)度竭望。在新技術的面前,不盲從裕菠,也不守舊咬清,一切的決策都應該建立在認真分析的基礎上,這樣才能應對技術的變化糕韧。
三 UIButton防止多次點擊
3.1 設置enabled或userInteractionEnabled屬性
通過UIButton
的enabled
屬性和userInteractionEnabled
屬性控制按鈕是否可點擊枫振。此方案在邏輯上比較清晰、易懂萤彩,但具體代碼書寫分散粪滤,常常涉及多個地方。
- (void)tapBtn:(UIButton *)btn {
NSLog(@"按鈕點擊...");
btn.enabled = NO;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
btn.enabled = YES;
});
}
3.2 借助cancelPreviousPerformRequestsWithTarget:selector:object實現(xiàn)
通過 NSObject 的兩個方法
實現(xiàn)步驟如下:
- 1 創(chuàng)建一個
UIButton
的分類雀扶,使用runtime
增加public
屬性cs_eventInterval
和private
屬性cs_eventInvalid
杖小。 - 2 在
+load
方法中使用runtime
將UIButton
的-sendAction:to:forEvent:
方法與自定義的cs_sendAction:to:forEvent:
方法進行交換 - 3 使用
cs_eventInterval
作為控制cs_eventInvalid
的計時因子,用cs_eventInvalid
控制UIButton
的event
事件是否有效愚墓。
// 此方法會在連續(xù)點擊按鈕時取消之前的點擊事件予权,從而只執(zhí)行最后一次點擊事件
+ (void)cancelPreviousPerformRequestsWithTarget:(id)aTarget selector:(SEL)aSelector object:(nullable id)anArgument;
// 多長時間后做某件事情
- (void)performSelector:(SEL)aSelector withObject:(nullable id)anArgument afterDelay:(NSTimeInterval)delay;
/** 方法一 */
- (void)tapBtn:(UIButton *)btn {
NSLog(@"按鈕點擊了...");
// 此方法會在連續(xù)點擊按鈕時取消之前的點擊事件,從而只執(zhí)行最后一次點擊事件
[NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(buttonClickedAction:) object:btn];
// 多長時間后做某件事情
[self performSelector:@selector(buttonClickedAction:) withObject:btn afterDelay:2.0];
}
- (void)buttonClickedAction:(UIButton *)btn {
NSLog(@"真正開始執(zhí)行業(yè)務 - 比如網(wǎng)絡請求...");
}
3.3 通過runtime交換方法實現(xiàn)
@interface UIButton (Extension)
/** 時間間隔 */
@property(nonatomic, assign)NSTimeInterval cs_eventInterval;
@end
#import "UIButton+Extension.h"
#import <objc/runtime.h>
static char *const kEventIntervalKey = "kEventIntervalKey"; // 時間間隔
static char *const kEventInvalidKey = "kEventInvalidKey"; // 是否失效
@interface UIButton()
/** 是否失效 - 即不可以點擊 */
@property(nonatomic, assign)BOOL cs_eventInvalid;
@end
@implementation UIButton (Extension)
+ (void)load {
// 交換方法
Method clickMethod = class_getInstanceMethod(self, @selector(sendAction:to:forEvent:));
Method cs_clickMethod = class_getInstanceMethod(self, @selector(cs_sendAction:to:forEvent:));
method_exchangeImplementations(clickMethod, cs_clickMethod);
}
#pragma mark - click
- (void)cs_sendAction:(SEL)action to:(id)target forEvent:(UIEvent *)event {
if (!self.cs_eventInvalid) {
self.cs_eventInvalid = YES;
[self cs_sendAction:action to:target forEvent:event];
[self performSelector:@selector(setCs_eventInvalid:) withObject:@(NO) afterDelay:self.cs_eventInterval];
}
}
#pragma mark - set | get
- (NSTimeInterval)cs_eventInterval {
return [objc_getAssociatedObject(self, kEventIntervalKey) doubleValue];
}
- (void)setCs_eventInterval:(NSTimeInterval)cs_eventInterval {
objc_setAssociatedObject(self, kEventIntervalKey, @(cs_eventInterval), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
- (BOOL)cs_eventInvalid {
return [objc_getAssociatedObject(self, kEventInvalidKey) boolValue];
}
- (void)setCs_eventInvalid:(BOOL)cs_eventInvalid {
objc_setAssociatedObject(self, kEventInvalidKey, @(cs_eventInvalid), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
四 如何監(jiān)聽弱網(wǎng)
查閱了一些資料浪册,暫時還沒有編碼實現(xiàn)扫腺,后期會更新
參考文章
iOS下的實際網(wǎng)絡連接狀態(tài)檢測
[前端測試] 弱網(wǎng)測試方法整理
五 卡頓檢測
參考以下文章
六 NSCache,NSDictionary村象,NSArray的區(qū)別
6.1 NSArray
NSArray作為一個存儲對象的有序集合笆环,可能是被使用最多的集合類。
性能特征
在數(shù)組的開頭和結(jié)尾插入/刪除元素通常是一個O(1)操作厚者,而隨機的插入/刪除通常是 O(N)的躁劣。
有用的方法
NSArray的大多數(shù)方法使用isEqual:來檢查對象間的關系(例如containsObject:)。有一個特別的方法
indexOfObjectIdenticalTo:
用來檢查指針相等库菲,如果你確保在同一個集合中搜索账忘,那么這個方法可以很大的提升搜索速度。
6.2 NSDictionary
一個字典存儲任意的對象鍵值對熙宇。 由于歷史原因鳖擒,初始化方法使用相反的對象到值的方法,
[NSDictionary dictionaryWithObjectsAndKeys:object, key, nil]
而新的快捷語法則從key開始
@{key : value, ...}
NSDictionary中的鍵是被拷貝的并且需要是恒定的烫止。如果在一個鍵在被用于在字典中放入一個值后被改變败去,那么這個值可能就會變得無法獲取了。一個有趣的細節(jié)烈拒,在NSDictionary中鍵是被拷貝的圆裕,而在使用一個toll-free橋接的CFDictionary時卻只被retain广鳍。CoreFoundation類沒有通用對象的拷貝方法,因此這時拷貝是不可能的(*)吓妆。這只適用于使用CFDictionarySetValue()的時候赊时。如果通過setObject:forKey使用toll-free橋接的CFDictionary,蘋果增加了額外處理邏輯來使鍵被拷貝行拢。反過來這個結(jié)論則不成立 — 轉(zhuǎn)換為CFDictionary的NSDictionary對象祖秒,對其使用CFDictionarySetValue()方法會調(diào)用回setObject:forKey并拷貝鍵。
6.3 NSCache
NSCache是一個非常奇怪的集合舟奠。在iOS 4/Snow Leopard中加入竭缝,默認為可變
并且線程安全
的。這使它很適合緩存那些創(chuàng)建起來代價高昂的對象沼瘫。它自動對內(nèi)存警告
做出反應并基于可設置的成本
清理自己抬纸。與NSDictionary相比,鍵是被retain而不是被拷貝
的耿戚。
NSCache的回收方法是不確定的湿故,在文檔中也沒有說明。向里面放一些類似圖片那樣比被回收更快填滿內(nèi)存的大對象不是個好主意膜蛔。(這是在PSPDFKit中很多跟內(nèi)存有關的crash的原因坛猪,在使用自定義的基于LRU的鏈表的緩存代碼之前,我們起初使用NSCache存儲事先渲染的圖片皂股。)
NSCache可以設置撐自動回收實現(xiàn)了NSDiscardableContent協(xié)議的對象墅茉。實現(xiàn)該屬性的一個比較流行的類是同時間加入的NSPurgeableData,但是在OS X 10.9之前呜呐,是非線程安全的(沒有信息表明這是否也影響到iOS或者是否在iOS 7中被修復了)躁锁。
NSCache性能
那么NSCache如何承受NSMutableDictionary的考驗?加入的線程安全必然會帶來一些消耗。
6.4 iOS 構(gòu)建緩存時選 NSCache 而非NSDictionary
當系統(tǒng)資源將要耗盡時卵史,NSCache可以自動刪減緩存。如果采用普通的字典搜立,那么就要自己編寫掛鉤以躯,在系統(tǒng)通知時手動刪減緩存,NSCache會先行刪減 時間最久為被使用的對象
NSCache 并不會拷貝鍵啄踊,而是會保留它忧设。此行為用NSDictionary也可以實現(xiàn),但是需要編寫比較復雜的代碼颠通。NSCache對象不拷貝鍵的原因在于址晕,很多時候鍵都是不支持拷貝操作的對象來充當?shù)摹R虼薔SCache對象不會自動拷貝鍵顿锰,所以在鍵不支持拷貝操作的情況下谨垃,該類比字典用起來更方便
NScache是線程安全的启搂,NSDictionary不是。在開發(fā)者自己不編寫加鎖代碼的前提下刘陶,多個線程可以同時訪問NSCache胳赌。對緩存來說,線程安全通常是很重要的匙隔,因為開發(fā)者可能在某個線程中讀取數(shù)據(jù)疑苫,此時如果發(fā)現(xiàn)緩存里找不著指定的鍵,那么就要下載該鍵對應的數(shù)據(jù)了
七 SDWebImage里面用了哪種緩存策略纷责?
使用了NSCache
做緩存策略捍掺,具體的參考下面的文章,寫的挺詳細的
推薦參考文章
iOS緩存 NSCache詳解及SDWebImage緩存策略源碼分析
八 self + weakSelf + strongSelf ?
__weak __typeof(self)weakSelf = self; //1
[self.context performBlock:^{
[weakSelf doSomething]; //2
__strong __typeof(weakSelf)strongSelf = weakSelf; //3
[strongSelf doAnotherSomething];
}];
- 解釋說明
1.使用__weak __typeof是在編譯的時候,另外創(chuàng)建一個局部變量weak對象來操作self再膳,引用計數(shù)不變挺勿。
block 會將這個局部變量捕獲為自己的屬性,
訪問這個屬性饵史,從而達到訪問 self 的效果满钟,因為他們的內(nèi)存地址都是一樣的。
2.因為weakSelf和self是兩個變量,doSomething有可能就直接對self自身引用計數(shù)減到0了.
所以在[weakSelf doSomething]的時候,你很難控制這里self是否就會被釋放了.weakSelf只能看著.
3.__strong __typeof在編譯的時候,實際是對weakSelf的強引用.
指針連帶關系self的引用計數(shù)會增加.但是你這個是在block里面,生命周期也只在當前block的作用域.
所以,當這個block結(jié)束, strongSelf隨之也就被釋放了.不會影響block外部的self的生命周期.
總結(jié)
- 在 Block 內(nèi)如果需要訪問 self 的方法胳喷、變量湃番,建議使用 weakSelf。
- 如果在 Block 內(nèi)需要多次 訪問 self吭露,則需要使用 strongSelf吠撮。