系統分配了16個字節給NSObject對象(經過malloc_size函數得到)
但NSObject對象內部只使用了8個字節的空間(64bit環境下,能夠經過class_getInstanceSize函數得到)
複製代碼
instance對象的isa指向class對象
class對象的isa指向meta-class對象
meta-class對象的isa指向基類的meta-class對象
複製代碼
對象方法、屬性、成員變量、協議信息,存放在class對象中
類方法,存放在meta-class對象中
成員變量的具體值,存放在instance對象
複製代碼
- 利用RuntimeAPI動態生成一個子類,而且讓instance對象的isa指向這個全新的子類
- 當修改instance對象的屬性時,會調用Foundation的_NSSetXXXValueAndNotify函數
willChangeValueForKey:
父類原來的setter
didChangeValueForKey:
- 內部會觸發監聽器(Oberser)的監聽方法(observeValueForKeyPath:ofObject:change:context:)
複製代碼
手動調用willChangeValueForKey:和didChangeValueForKey:
//
- (void)viewDidLoad {
[super viewDidLoad];
Person *person = [[Person alloc]init];;
[p addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:nil];
[p willChangeValueForKey:@"name"];
[p didChangeValueForKey:@"name"];
}
-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context{
NSLog(@"被觀測對象:%@, 被觀測的屬性:%@, 值的改變: %@\n, 攜帶信息:%@", object, keyPath, change, context);
}
複製代碼
不會觸發KVO
複製代碼
會觸發KVO
KVC在賦值時候,內部會觸發監聽器(Oberser)的監聽方法(observeValueForKeyPath:ofObject:change:context:) 發送通知
複製代碼
KVC的全稱是Key-Value Coding,俗稱「鍵值編碼」,能夠經過一個key來訪問某個屬性
調用 setValue:forKey:
setKey,_setKey ->找到了則進行賦值,未找到調用 accessInstanceVarlableDirctly 是否運行 修改值,返回YES
調用_key, _isKey, key, isKey 進行賦值
複製代碼
- 在不修改原有類代碼的狀況下,爲類添對象方法或者類方法
- 或者爲類關聯新的屬性
- 分解龐大的類文件
使用場合:
- 添加實例方法
- 添加類方法
- 添加協議
- 添加屬性
- 關聯成員變量
複製代碼
Category編譯以後的底層結構是struct category_t,裏面存儲着分類的對象方法、類方法、屬性、協議信息
在程序運行的時候,runtime會將Category的數據,合併到類信息中(類對象、元類對象中)
複製代碼
Class Extension在編譯的時候,它的數據就已經包含在類信息中
Category是在運行時,纔會將數據合併到類信息中
複製代碼
- 有load方法
- load方法在runtime加載類、分類的時候調用
- load方法能夠繼承,可是通常狀況下不會主動去調用load方法,都是讓系統自動調用
複製代碼
- 當類第一次收到消息的時候會調用類的initialize方法
- 是經過 runtime 的消息機制 objc_msgSend(obj,@selector()) 進行調用的
- 優先調用分類的 initialize, 若是沒有分類會調用 子類的,若是子類未實現則調用 父類的
複製代碼
- load 是類加載到內存時候調用, 優先父類->子類->分類
- initialize 是類第一次收到消息時候調用,優先分類->子類->父類
- 同級別和編譯順序有關係
- load 方法是在 main 函數以前調用的
複製代碼
不能直接給Category添加成員變量,可是能夠間接實現Category有成員變量的效果
Category是發生在運行時,編譯完畢,類的內存佈局已經肯定,沒法添加成員變量(Category的底層數據結構也沒有成員變量的結構)
能夠經過 runtime 動態的關聯屬性
複製代碼
block 本質實際上是OC對象
block 內部封裝了函數調用以及調用環境
複製代碼
若是須要在 block 內部修改外部的 局部變量的值,就須要使用__block 修飾(全局變量和靜態變量不須要加__block 能夠修改)
__block 修飾之後,局部變量的數據結構就會發生改變,底層會變成一個結構體的對象,結構內部會聲明 一個 __block修飾變量的成員, 而且將 __block修飾變量的地址保存到堆內存中. 後面若是修改 這個變量的值,能夠經過 isa 指針找到這個結構體,進來修改 這個變量的值;
能夠在 block 內部修改 變量的值
複製代碼
block 一旦沒有進行copy操做,就不會在堆上
使用注意:循環引用問題 (外部使用__weak 解決)
複製代碼
若是是操做 NSMutableArray 對象不須要,由於 block 內部拷貝了 NSMutableArray對象的內存地址,實際是經過內存地址操做的
若是 NSMutableArray 對象要從新賦值,就須要加__block
複製代碼
經過查看Block 源碼,能夠發現, block 內部若是單純使用 外部變量, 會在 block 內部建立一樣的一個變量,而且將 外部變量的值引用過來..(只是將外部變量值拷貝到 block 內部), 內部這個變量和外部 實際已經不要緊了
從另外一方面分析,block 本質也是一個 函數指針, 外部的變量也是一個局部變量,頗有可能 block 在使用這個變量時候,外部變量已經釋放了,會形成錯誤
加了__block 之後, 會將外部變量的內存拷貝到堆中, 內存由 block 去管理.
複製代碼
OC中的方法調用其實都是轉成了objc_msgSend函數的調用,給receiver(方法調用者)發送了一條消息(selector方法名)
objc_msgSend底層有3大階段
消息發送(當前類、父類中查找)、動態方法解析、消息轉發
複製代碼
當咱們的一個 receiver(實例對象)收到消息的時候, 會經過 isa 指針找到 他的類對象, 而後在類對象方法列表中查找 對應的方法實現,若是 未找到,則會經過 superClass 指針找到其父類的類對象, 找到則返回,未找打則會一級一級往上查到,最終到NSObject 對象, 若是仍是未找到就會進行動態方法解析
類方法調用同上,只不過 isa 指針找到元類對象;
複製代碼
當咱們發送消息未找到方法實現,就會進入第二步,動態方法解析: 代碼實現以下
// 動態方法綁定- 實例法法調用
+ (BOOL)resolveInstanceMethod:(SEL)sel{
if (sel == @selector(run)) {
Method method = class_getInstanceMethod(self, @selector(test));
class_addMethod(self, sel, method_getImplementation(method), method_getTypeEncoding(method));
return YES;
}
return [super resolveInstanceMethod:sel];
}
// 類方法調用
+(BOOL) resolveClassMethod:(SEL)sel....
複製代碼
未找到動態方法綁定,就會進行消息轉發階段
// 快速消息轉發- 指定消息處理對象
- (id)forwardingTargetForSelector:(SEL)aSelector{
if (aSelector == @selector(run)) {
return [Student new];
}
return [super forwardingTargetForSelector:aSelector];
}
// 標準消息轉發-消息簽名
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
if(aSelector == @selector(run))
{
return [NSMethodSignature signatureWithObjCTypes:"v@:"];
}
return [super methodSignatureForSelector:aSelector];
}
- (void)forwardInvocation:(NSInvocation *)anInvocation{
//內部邏輯本身處理
}
複製代碼
Objective-C runtime是一個`運行時`庫,它爲Objective-C語言的動態特性提供支持,咱們所寫的OC代碼在運行時都轉成了runtime相關的代碼,類轉換成C語言對應的結構體,方法轉化爲C語言對應的函數,發消息轉成了C語言對應的函數調用。經過了解runtime以及源碼,能夠更加深刻的瞭解OC其特性和原理
OC是一門動態性比較強的編程語言,容許不少操做推遲到程序運行時再進行
OC的動態性就是由Runtime來支撐和實現的,Runtime是一套C語言的API,封裝了不少動態性相關的函數
平時編寫的OC代碼,底層都是轉換成了Runtime API進行調用
複製代碼
利用關聯對象(AssociatedObject)給分類添加屬性
遍歷類的全部成員變量(修改textfield的佔位文字顏色、字典轉模型、自動歸檔解檔)
交換方法實現(交換系統的方法)
利用消息轉發機制解決方法找不到的異常問題
複製代碼
[self class] 和 [super class] 都是給當前類返送消息,spuer 表示在父類中查找
[self superClass] 和 [super superclass] 也是也當前類發消息,返回父類
第一個打印:
MJStudent / MJStudent/ MJerson / MJPerson
isKindOfClass 表示對象是否爲當前類或者子類的 類型
isMemberOfClass 表示是否爲當前類的的類型
isMemberOfClass 分爲- 對象方法 和+ 類方法2中
- (bool)isMemberOfClass; 比較的是類對象
+ (bool)isMemberOfClass; 比較的是元類
第二個打印:
1 ,0, 0, 0
複製代碼
打印結果: <ViewController: 0x7f9396c16300>
複製代碼
runloop運行循環,保證程序一直運行,主線程默認開啓
用於處理線程上的各類事件,定時器等
能夠提升程序性能,節約CPU資源,有事情作就作,沒事情作就讓線程休眠
應用範疇:
定時器,事件響應,手勢識別,界面刷新,以及autoreleasePool 等等
複製代碼
實際上 RunLoop 就是這樣一個函數,其內部是一個 do-while 循環。當你調用 CFRunLoopRun() 時,線程就會一直停留在這個循環裏;直到超時或被手動中止,該函數纔會返回。
複製代碼
每條線程都有惟一的一個與之對應的RunLoop對象
RunLoop保存在一個全局的Dictionary裏,線程做爲key,RunLoop做爲value
線程剛建立時並無RunLoop對象,RunLoop會在第一次獲取它時建立
RunLoop會在線程結束時銷燬
主線程的RunLoop已經自動獲取(建立),子線程默認沒有開啓RunLoop
複製代碼
timer 定時器,是基於 runloop 來實現的, runloop 在運行循環當中,監聽到了定製器 就會執行;因此 timer 須要添加到 runloop 中去, 注意子線程的 runloop 默認是不開啓的,若是在子線程執行 timer 須要手動開啓 runloop
複製代碼
將 timer 對象添加到 runloop 中,並修改 runloop 的運行 mode
NSTimer *timer = [NSTimer timerWithTimeInterval:1 repeats:YES block:nil];
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
複製代碼
不明白問題想問什麼?
複製代碼
添加Observer監聽RunLoop的全部狀態html
runloop 只能在一種 mode 下運行, 作不一樣的事情,runloop 會切換到對應的 model 下來執行,默認是 kCFRunLoopDefaultMode 若是視圖滑動再回切換到 UITrackingRunLoopMode,若是須要在多種 mode 下運行則須要手動設置 kCFRunLoopCommonModes;
1. kCFRunLoopDefaultMode:App的默認Mode,一般主線程是在這個Mode下運行
2. UITrackingRunLoopMode:界面跟蹤 Mode,用於 ScrollView 追蹤觸摸滑動,保證界面滑動時不受其餘 Mode 影響
3. UIInitializationRunLoopMode: 在剛啓動 App 時第進入的第一個 Mode,啓動完成後就再也不使用,會切換到kCFRunLoopDefaultMode
4. GSEventReceiveRunLoopMode: 接受系統事件的內部 Mode,一般用不到
5. kCFRunLoopCommonModes: 這是一個佔位用的Mode,做爲標記kCFRunLoopDefaultMode和UITrackingRunLoopMode用,並非一種真正的Mode
複製代碼
同一時間,CPU 只能處理理一條線程, 只有一條線程在⼯工做 多線程併發執行,實際上是 CPU 快速的在多條線程之間調度(切換) 若是 CPU 調度線程的時間⾜足夠快, 就形成了多線程併發執⾏的假象
優點
充分發揮多核處理器的優點,將不一樣線程任務分配給不一樣的處理器,真正進入「⾏行 計算」狀態
弊端
新線程會消耗內存控件和cpu時間,線程太多會下降系統行性能。
複製代碼
傾向於GCD ,簡單靈活,使用方便
複製代碼
使用過
GCD中有2個用來執行任務的函數
用同步的方式執行任務
dispatch_sync(dispatch_queue_t queue, dispatch_block_t block);
queue:隊列
block:任務
用異步的方式執行任務
dispatch_async(dispatch_queue_t queue, dispatch_block_t block);
複製代碼
GCD的隊列能夠分爲2大類型
併發隊列(Concurrent Dispatch Queue)
可讓多個任務併發(同時)執行(自動開啓多個線程同時執行任務)
併發功能只有在異步(dispatch_async)函數下才有效
串行隊列(Serial Dispatch Queue)
讓任務一個接着一個地執行(一個任務執行完畢後,再執行下一個任務)
複製代碼
1> GCD是純C語⾔言的API,NSOperationQueue是基於GCD的OC版本封裝
2> GCD只⽀支持FIFO的隊列列,NSOperationQueue能夠很⽅方便便地調整執⾏行行順 序、設 置最⼤大併發數量量
3> NSOperationQueue能夠在輕鬆在Operation間設置依賴關係,⽽而GCD 須要寫很 多的代碼才能實現
4> NSOperationQueue⽀支持KVO,能夠監測operation是否正在執⾏行行 (isExecuted)、 是否結束(isFinished),是否取消(isCanceld)
5> GCD的執⾏行行速度⽐比NSOperationQueue快 任務之間不不太互相依賴:GCD 任務之間 有依賴\或者要監放任務的執⾏行行狀況:NSOperationQueue
複製代碼
1.加鎖
2.同步執行
複製代碼
os_unfair_lock ios10 開始
OSSpinLock ios10 廢棄
dispatch_semaphore 建議使用,性能也比較好
dispatch_mutex
dispatch_queue 串行
NSLock 對 mutex 封裝
@synchronized 性能最差
複製代碼
什麼狀況使用自旋鎖比較划算?
預計線程等待鎖的時間很短
加鎖的代碼(臨界區)常常被調用,但競爭狀況不多發生
CPU資源不緊張
多核處理器
什麼狀況使用互斥鎖比較划算?
預計線程等待鎖的時間較長
單核處理器
臨界區有IO操做
臨界區代碼複雜或者循環量大
臨界區競爭很是激烈
複製代碼
注意死鎖
在串行隊列使用同步,容易形成死鎖
複製代碼
兩種鎖的加鎖原理:
互斥鎖:線程會從sleep(加鎖)——>running(解鎖),過程當中有上下文的切換,cpu的搶佔,信號的發送等開銷。
自旋鎖:線程一直是running(加鎖——>解鎖),死循環檢測鎖的標誌位,
複製代碼
打印 1,3
performSelector after 是基於 timer 定製器,定時器又是基於 runloop 實現的
任務2在子線程中,子線程默認 runloop 是不開啓的,因此不執行2
複製代碼
打印1
start 執行完,線程就銷燬了.任務 test 無法執行了
複製代碼
CADisplayLink 保證調用頻率和刷幀頻率一直,60FPS, 不用設置時間間隔,每秒鐘60次
可使用 proxy 代理解決循環引用
CADisplayLink、NSTimer會對target產生強引用,若是target又對它們產生強引用,那麼就會引起循環引用
複製代碼
低地址-> 高地址
保留->代碼段->數據段(字符串常量,已初始化全局數據,未初始化數據)>堆->棧內存-> 內核區域
代碼段: 編譯以後的代碼
數據段: 字符串常量,已經初始化的全局變量,或者靜態變量,未初始化的全局變量,靜態變量
堆 (低>高) 經過 alloc malloc calloc 動態分配的內存
棧 (高地址 從 低地址) 函數調用開銷()
複製代碼
在iOS中,使用引用計數來管理OC對象的內存
一個新建立的OC對象引用計數默認是1,當引用計數減爲0,OC對象就會銷燬,釋放其佔用的內存空間
調用retain會讓OC對象的引用計數+1,調用release會讓OC對象的引用計數-1
內存管理的經驗總結
當調用alloc、new、copy、mutableCopy方法返回了一個對象,在不須要這個對象時,要調用release或者autorelease來釋放它
想擁有某個對象,就讓它的引用計數+1;不想再擁有某個對象,就讓它的引用計數-1
能夠經過如下私有函數來查看自動釋放池的狀況
extern void _objc_autoreleasePoolPrint(void);
複製代碼
LLVM + Runtime 會爲咱們代碼自動插入 retain 和 release 以及 autorelease等代碼,不須要咱們手動管理
複製代碼
Runtime維護了一個weak表,用於存儲指向某個對象的全部weak指針。weak表實際上是一個hash(哈希)表,Key是所指對象的地址,Value是weak指針的地址(這個地址的值是所指對象的地址)數組。
runtime對註冊的類, 會進行佈局,對於weak對象會放入一個hash表中。 用weak指向的對象內存地址做爲key,當此對象的引用計數爲0的時候會dealloc,假如weak指向的對象內存地址是a,那麼就會以a爲鍵, 在這個weak表中搜索,找到全部以a爲鍵的weak對象,從而設置爲nil。
複製代碼
iOS在主線程的Runloop中註冊了2個Observer
-第1個Observer監聽了kCFRunLoopEntry事件,會調用objc_autoreleasePoolPush()
-第2個Observer
監聽了kCFRunLoopBeforeWaiting事件,會調用objc_autoreleasePoolPop()、objc_autoreleasePoolPush()
監聽了kCFRunLoopBeforeExit事件,會調用objc_autoreleasePoolPop()
objc_autoreleasePoolPop()調用時候回給 pool 中的對象發送一次 release 消息
複製代碼
若是是普通的 局部對象 會當即釋放
若是是放在了 autoreleasePool 自動釋放吃,則會等runloop 循環,進入休眠前釋放
複製代碼
第一個內存會暴漲,self.name 會不停的建立
第二個內存固定,會使用 Tagged Pointer 將值存在地址中
複製代碼
內存優化能夠從 內存泄漏 和 內存開銷 2方面入口
- 減小內存泄露
可使用靜態分析以及instruments的leaks 分析
注意 NStimer 以及 block ,delegate 等的使用,避免循環引用
- 下降內存使用峯值
1. 關於圖片加載佔用內存問題:imageNamed: 方法會在內存中緩存圖片,用於經常使用的圖片。
imageWithContentsOfFile: 方法在視圖銷燬的時候會釋放圖片佔用的內存,適合不經常使用的大圖等。
2.tableView cell 儘可能使用重用機制,減小額外的開銷
3.tableView 列表圖片展現儘可能使用縮略圖
4.延遲加載 對象,節約內存開銷
5.避免短期大量建立對象,配合 autoreleasePool 減小內存峯值
6.重用大開銷對象,好比: NSDateFormatter和NSCalendar
7.加載 html 儘可能使用 wkwebView
8.單例使用不易過多
9.線程最大併發數
複製代碼
卡頓優化
啓動優化
耗電量優化
app 瘦身
CPU 佔用率、 內存使用狀況、網絡情況監控、啓動時閃退、卡頓、FPS、使用時崩潰、耗電量監控、流量監控....
複製代碼
1.最經常使用的就是cell的重用, 註冊重用標識符
若是不重用cell時,每當一個cell顯示到屏幕上時,就會從新建立一個新的cell;
若是有不少數據的時候,就會堆積不少cell。
若是重用cell,爲cell建立一個ID,每當須要顯示cell 的時候,都會先去緩衝池中尋找可循環利用的cell,若是沒有再從新建立cell
2.避免cell的從新佈局
cell的佈局填充等操做 比較耗時,通常建立時就佈局好
如能夠將cell單獨放到一個自定義類,初始化時就佈局好
3.提早計算並緩存cell的屬性及內容
當咱們建立cell的數據源方法時,編譯器並非先建立cell 再定cell的高度
而是先根據內容一次肯定每個cell的高度,高度肯定後,再建立要顯示的cell,滾動時,每當cell進入憑虛都會計算高度,提早估算高度告訴編譯器,編譯器知道高度後,緊接着就會建立cell,這時再調用高度的具體計算方法,這樣能夠方式浪費時間去計算顯示之外的cell
4.減小cell中控件的數量
儘可能使cell得佈局大體相同,不一樣風格的cell可使用不用的重用標識符,初始化時添加控件,
不適用的能夠先隱藏
5.不要使用ClearColor,無背景色,透明度也不要設置爲0
渲染耗時比較長
6.使用局部更新
若是隻是更新某組的話,使用reloadSection進行局部更新
7.加載網絡數據,下載圖片,使用異步加載,並緩存
8.少使用addView 給cell動態添加view
9.按需加載cell,cell滾動很快時,只加載範圍內的cell
10.不要實現無用的代理方法,tableView只遵照兩個協議
11.緩存行高:estimatedHeightForRow不能和HeightForRow裏面的layoutIfNeed同時存在,這二者同時存在纔會出現「竄動」的bug。因此個人建議是:只要是固定行高就寫預估行高來減小行高調用次數提高性能。若是是動態行高就不要寫預估方法了,用一個行高的緩存字典來減小代碼的調用次數便可
12.不要作多餘的繪製工做。在實現drawRect:的時候,它的rect參數就是須要繪製的區域,這個區域以外的不須要進行繪製。例如上例中,就能夠用CGRectIntersectsRect、CGRectIntersection或CGRectContainsRect判斷是否須要繪製image和text,而後再調用繪製方法。
13.預渲染圖像。當新的圖像出現時,仍然會有短暫的停頓現象。解決的辦法就是在bitmap context裏先將其畫一遍,導出成UIImage對象,而後再繪製到屏幕;
14.使用正確的數據結構來存儲數據。
複製代碼
1\. pre-main 以前
* 排查無用的動態庫(按期清理)
* 減小ObjC類(項目中不適用的的庫,廢棄的代碼等)、方法(selector)、分類(category)的數量、無用的庫
* 少在類的+load方法裏作事情,儘可能把這些事情推遲到+initiailize1.
2\. main 函數以後的 didFinishLaunchingWithOptions 加載完以前
* 不影響用戶體驗的操做,作延遲加載,不要所有放在 didFinishLaunchingWithOptions中去作
* 版本更新,一些三方初始化,不須要在 didFinishLaunchingWithOptions 初始化的放到,界面展現完之後再初始化
* 一些網絡請求延遲 請求..
* 一些業務邏輯延遲 加載
* 初始化第三方 SDK
* 配置 APP 運行須要的環境
* 本身的一些工具類的初始化
複製代碼
1.不要頻繁的刷新頁面,能刷新1行cell最好只刷新一行,儘可能不要使用reloadData.
2.選擇正確的集合
NSArray,使用index來查找很快(插入和刪除很慢)
字典,使用鍵來查找很快
NSSets,是無序的,用鍵查找很快,插入/刪除很快
3.少用運算得到圓角,必需要用圓角的話,不如把圖片自己就作成圓角
4.懶加載,不要一次性建立全部的subview,而是須要時才建立.
5.重用機制
6.圖片處理
圖片與imageView相同大小,避免多餘運算
可使用整副的圖片,增長應用體積,可是節省CPU
可調大小的圖片,能夠省去一些沒必要要的空間
CALayer,CoreGraphics,甚至OpenGL來繪製,消耗CPU
7.cache,cache,cache(緩存全部須要的)
服務器相應結果的緩存(圖片)
複雜計算結果的緩存(UITableView的行高)
8.儘可能少用透明或半透明,會產生額外的運算.
9.使用ARC減小內存失誤,dealloc須要重寫並對屬性置爲nil
10.避免龐大的xib,storyBoard,儘可能使用純代碼開發
CPU層面
1.Timer的時間間隔不宜過短,知足需求便可
2.線程適量,不宜過多,不要阻塞主線程
3.優化算法,減小循環次數
4.定位和藍牙按需取用,定位以後要關閉或下降定位頻率
5.一些硬件的使用,不使用就關掉
複製代碼
MVC Model-view-controller 數據-視圖-控制器
通常控制器用於管理數據和視圖, 數據和視圖交互都是經過控制器來進行的.視圖和數據進行了解耦, 可是咱們平常使用常常會將模型綁定給視圖.模型封裝在視圖內部,外部不用管理視圖內部業務邏輯,這數據 mvc 的變種, 控制器只給視圖模型數據就行了. 缺點是視圖和 模型有耦合;
MVVM Model-view-viewModel 模型-視圖-視圖模型
view 和 model 的交互經過viewmodel 來進行交互,實現數據的雙向綁定
MVP Model-view - Presenter 模型-視圖-主持人
view 和 model 的交互經過Presenter,controller經過Presenter來管理 model 和 View
複製代碼
結合本身項目來說吧
複製代碼
根據模塊,使用 mvc 功能劃分..結合本身項目講比較容易
涉及到東西也比較多,比較雜,大到整個項目架構,小到一個 view 的架構;沒具體的答案
複製代碼