目錄
一. GCD和OperationQueue
二. CADisplayLink涣旨、NSTimer使用注意
三. 內(nèi)存布局
四. Tagged Pointer
五. copy和mutableCopy
六. OC對(duì)象的內(nèi)存管理
七. AutoreleasePool自動(dòng)釋放池
八. 圖片的解壓縮到渲染過程
九. 應(yīng)用卡頓的原因以及優(yōu)化
十. APP的啟動(dòng)
一. GCD和NSOperationQueue
GCD 可用于多核的并行運(yùn)算伤极;
GCD 會(huì)自動(dòng)利用更多的 CPU 內(nèi)核(比如雙核滤祖、四核)柴墩;
GCD 會(huì)自動(dòng)管理線程的生命周期(創(chuàng)建線程摹恨、調(diào)度任務(wù)登下、銷毀線程);
程序員只需要告訴 GCD 想要執(zhí)行什么任務(wù)淹冰,不需要編寫任何線程管理代碼库车。
NSOperation、NSOperationQueue 是基于 GCD 更高一層的封裝樱拴,完全面向?qū)ο竽堋5潜?GCD 更簡單易用洋满、代碼可讀性也更高。
- 可添加完成的代碼塊珍坊,在操作完成后執(zhí)行牺勾。
- 添加操作之間的依賴關(guān)系,設(shè)定操作執(zhí)行的優(yōu)先級(jí)垫蛆,方便的控制執(zhí)行順序禽最;設(shè)置最大并發(fā)數(shù)。
- 可以很方便的取消一個(gè)操作的執(zhí)行袱饭。
- 使用 KVO 觀察對(duì)操作執(zhí)行狀態(tài)的更改:isExecuteing、isFinished呛占、isCancelled虑乖。
同步和異步主要影響:能不能開啟新的線程
同步:在當(dāng)前線程中執(zhí)行任務(wù),不具備開啟新線程的能力
異步:在新的線程中執(zhí)行任務(wù)晾虑,具備開啟新線程的能力
并發(fā)和串行主要影響:任務(wù)的執(zhí)行方式
并發(fā):多個(gè)任務(wù)并發(fā)(同時(shí))執(zhí)行
串行:一個(gè)任務(wù)執(zhí)行完畢后疹味,再執(zhí)行下一個(gè)任務(wù)
二. CADisplayLink、NSTimer使用注意
- CADisplayLink帜篇、NSTimer會(huì)對(duì)target產(chǎn)生強(qiáng)引用糙捺,如果target又對(duì)它們產(chǎn)生強(qiáng)引用,那么就會(huì)引發(fā)循環(huán)引用笙隙。解決辦法是使用代理對(duì)象NSProxy洪灯。
- NSTimer依賴于RunLoop,如果RunLoop的任務(wù)過于繁重竟痰,可能會(huì)導(dǎo)致NSTimer不準(zhǔn)時(shí)签钩。而GCD的定時(shí)器會(huì)更加準(zhǔn)時(shí)。
三. 內(nèi)存布局
- 棧區(qū)(heap):由系統(tǒng)去管理坏快。地址從高到低分配铅檩。先進(jìn)后出。會(huì)存一些局部變量莽鸿,函數(shù)跳轉(zhuǎn)跳轉(zhuǎn)時(shí)現(xiàn)場保護(hù)(寄存器值保存于恢復(fù))昧旨,這些系統(tǒng)都會(huì)幫我們自動(dòng)實(shí)現(xiàn),無需我們干預(yù)祥得。所以大量的局部變量兔沃,深遞歸,函數(shù)循環(huán)調(diào)用都可能耗盡棧內(nèi)存而造成程序崩潰 啃沪。
- 堆區(qū)(stack):需要我們自己管理內(nèi)存粘拾,alloc申請(qǐng)內(nèi)存release釋放內(nèi)存。創(chuàng)建的對(duì)象也都放在這里创千。 地址是從低到高分配缰雇。堆是所有程序共享的內(nèi)存入偷,當(dāng)N個(gè)這樣的內(nèi)存得不到釋放,堆區(qū)會(huì)被擠爆械哟,程序立馬癱瘓疏之。這就是內(nèi)存泄漏。
- 全局區(qū)/靜態(tài)區(qū)(staic):全局變量和靜態(tài)變量的存儲(chǔ)是放在一塊的暇咆,初始化的全局變量和靜態(tài)變量在一塊區(qū)域锋爪, 未初始化的全局變量和未初始化的靜態(tài)變量在相鄰的另一塊區(qū)域。程序結(jié)束后有系統(tǒng)釋放爸业。
- 常量區(qū):常量字符串就是放在這里的其骄,還有const常量。
- 代碼區(qū):存放App代碼扯旷,App程序會(huì)拷貝到這里拯爽。
四. Tagged Pointer
- 從64bit開始,iOS引入了Tagged Pointer技術(shù)钧忽,用于優(yōu)化NSNumber毯炮、NSDate、NSString等小對(duì)象的存儲(chǔ)
- 在沒有使用Tagged Pointer之前耸黑, NSNumber等對(duì)象需要?jiǎng)討B(tài)分配內(nèi)存桃煎、維護(hù)引用計(jì)數(shù)等,NSNumber指針存儲(chǔ)的是堆中NSNumber對(duì)象的地址值
- 使用Tagged Pointer之后大刊,NSNumber指針里面存儲(chǔ)的數(shù)據(jù)變成了:Tag + Data为迈,也就是將數(shù)據(jù)直接存儲(chǔ)在了指針中
- 當(dāng)指針不夠存儲(chǔ)數(shù)據(jù)時(shí),才會(huì)使用動(dòng)態(tài)分配內(nèi)存的方式來存儲(chǔ)數(shù)據(jù)
- objc_msgSend能識(shí)別Tagged Pointer奈揍,比如NSNumber的intValue方法曲尸,直接從指針提取數(shù)據(jù),節(jié)省了以前的調(diào)用開銷
- 如何判斷一個(gè)指針是否為Tagged Pointer男翰?
iOS平臺(tái)另患,最高有效位是1(第64bit);Mac平臺(tái)蛾绎,最低有效位是1
五. copy和mutableCopy
六. OC對(duì)象的內(nèi)存管理
- 在iOS中昆箕,使用引用計(jì)數(shù)來管理OC對(duì)象的內(nèi)存。
- 一個(gè)新創(chuàng)建的OC對(duì)象引用計(jì)數(shù)默認(rèn)是1租冠,當(dāng)引用計(jì)數(shù)減為0鹏倘,OC對(duì)象就會(huì)銷毀,釋放其占用的內(nèi)存空間顽爹。
- 調(diào)用retain會(huì)讓OC對(duì)象的引用計(jì)數(shù)+1纤泵,調(diào)用release會(huì)讓OC對(duì)象的引用計(jì)數(shù)-1。
- 當(dāng)調(diào)用alloc镜粤、new捏题、copy玻褪、mutableCopy方法返回了一個(gè)對(duì)象,在不需要這個(gè)對(duì)象時(shí)公荧,要調(diào)用release或者autorelease來釋放它带射;想擁有某個(gè)對(duì)象,就讓它的引用計(jì)數(shù)+1循狰;不想再擁有某個(gè)對(duì)象窟社,就讓它的引用計(jì)數(shù)-1。
七. AutoreleasePool自動(dòng)釋放池
AutoreleasePool(自動(dòng)釋放池) 是OC中的一種內(nèi)存自動(dòng)回收機(jī)制绪钥,在釋放池中的調(diào)用了autorelease方法的對(duì)象都會(huì)被壓在該池的頂部(以棧的形式管理對(duì)象)灿里。當(dāng)自動(dòng)釋放池被銷毀的時(shí)候,在該池中的對(duì)象會(huì)自動(dòng)調(diào)用release方法來釋放資源程腹,銷毀對(duì)象钠四。以此來達(dá)到自動(dòng)管理內(nèi)存的目的。
__AtAutoreleasePool 實(shí)際是一個(gè)結(jié)構(gòu)體跪楞,在內(nèi)部首先執(zhí)行objc_autoreleasePoolPush(),然后在調(diào)用objc_autoreleasePoolPop(atautoreleasepoolobj)侣灶。
struct __AtAutoreleasePool {
__AtAutoreleasePool() {
//構(gòu)造函數(shù)甸祭,在創(chuàng)建結(jié)構(gòu)體時(shí)調(diào)用
atautoreleasepoolobj = objc_autoreleasePoolPush();
}
~__AtAutoreleasePool() {
//析構(gòu)函數(shù),在結(jié)構(gòu)體銷毀的時(shí)候調(diào)用
objc_autoreleasePoolPop(atautoreleasepoolobj);
}
void * atautoreleasepoolobj;
};
AutoreleasePoolPage的結(jié)構(gòu)
- 每個(gè)AutoreleasePoolPage對(duì)象占用4096字節(jié)內(nèi)存褥影,除了用來存放它內(nèi)部的成員變量池户,剩下的空間用來存放autorelease對(duì)象的地址。
- 所有的AutoreleasePoolPage對(duì)象通過雙向鏈表的形式連接在一起凡怎。
- 調(diào)用push方法會(huì)將一個(gè)POOL_BOUNDARY入棧校焦,并且返回其存放的內(nèi)存地址
- 調(diào)用pop方法時(shí)傳入一個(gè)POOL_BOUNDARY的內(nèi)存地址,會(huì)從最后一個(gè)入棧的對(duì)象開始發(fā)送release消息统倒,直到遇到這個(gè)POOL_BOUNDARY
- id *next指向了下一個(gè)能存放autorelease對(duì)象地址的區(qū)域
Autorelease何時(shí)釋放寨典?
1、手動(dòng)調(diào)用AutoreleasePool的釋放方法(drain方法)
2房匆、Autorelease對(duì)象是在當(dāng)前的runloop迭代結(jié)束時(shí)釋放的耸成,而它能夠釋放的原因是系統(tǒng)在每個(gè)runloop迭代中都加入了自動(dòng)釋放池Push和Pop
Runloop和Autorelease的關(guān)系
- App啟動(dòng)后,蘋果在主線程 RunLoop 里注冊(cè)了兩個(gè) Observer浴鸿,其回調(diào)都是 _wrapRunLoopWithAutoreleasePoolHandler()井氢。
- 第一個(gè) Observer 監(jiān)視的事件是 Entry(即將進(jìn)入Loop),其回調(diào)內(nèi)會(huì)調(diào)用 _objc_autoreleasePoolPush() 創(chuàng)建自動(dòng)釋放池岳链。其 order 是 -2147483647花竞,優(yōu)先級(jí)最高,保證創(chuàng)建釋放池發(fā)生在其他所有回調(diào)之前掸哑。
- 第二個(gè) Observer 監(jiān)視了兩個(gè)事件: BeforeWaiting(準(zhǔn)備進(jìn)入休眠) 時(shí)調(diào)用_objc_autoreleasePoolPop() 和 _objc_autoreleasePoolPush() 釋放舊的池并創(chuàng)建新池约急;Exit(即將退出Loop) 時(shí)調(diào)用 _objc_autoreleasePoolPop() 來釋放自動(dòng)釋放池零远。這個(gè) Observer 的 order 是 2147483647,優(yōu)先級(jí)最低烤宙,保證其釋放池子發(fā)生在其他所有回調(diào)之后遍烦。
八. 圖片的解壓縮到渲染過程
- 假設(shè)我們使用 +imageWithContentsOfFile: 方法從磁盤中加載一張圖片,這個(gè)時(shí)候的圖片并沒有解壓縮躺枕;
- 然后將生成的 UIImage 賦值給 UIImageView 服猪;
- 接著一個(gè)隱式的 CATransaction 捕獲到了 UIImageView 圖層樹的變化;
- 在主線程的下一個(gè) runloop 到來時(shí)拐云,Core Animation 提交了這個(gè)隱式的 transaction 罢猪,這個(gè)過程可能會(huì)對(duì)圖片進(jìn)行 copy 操作,而受圖片是否字節(jié)對(duì)齊等因素的影響叉瘩,這個(gè) copy 操作可能會(huì)涉及以下部分或全部步驟:
- 分配內(nèi)存緩沖區(qū)用于管理文件 IO 和解壓縮操作膳帕;
- 將文件數(shù)據(jù)從磁盤讀到內(nèi)存中;
- 將壓縮的圖片數(shù)據(jù)解碼成未壓縮的位圖形式薇缅,這是一個(gè)非常耗時(shí)的 CPU 操作危彩;
- 最后 Core Animation 中CALayer使用未壓縮的位圖數(shù)據(jù)渲染 UIImageView 的圖層。
- CPU計(jì)算好圖片的Frame,對(duì)圖片解壓之后.就會(huì)交給GPU來做圖片渲染
- 渲染流程
- GPU獲取獲取圖片的坐標(biāo)
- 將坐標(biāo)交給頂點(diǎn)著色器(頂點(diǎn)計(jì)算)
- 將圖片光柵化(獲取圖片對(duì)應(yīng)屏幕上的像素點(diǎn))
- 片元著色器計(jì)算(計(jì)算每個(gè)像素點(diǎn)的最終顯示的顏色值)
- 從幀緩存區(qū)中渲染到屏幕上
總結(jié):圖片渲染到屏幕的過程: 讀取文件->計(jì)算Frame->圖片解碼->解碼后紋理圖片位圖數(shù)據(jù)通過數(shù)據(jù)總線交給GPU->GPU獲取圖片F(xiàn)rame->頂點(diǎn)變換計(jì)算->光柵化->根據(jù)紋理坐標(biāo)獲取每個(gè)像素點(diǎn)的顏色值(如果出現(xiàn)透明值需要將每個(gè)像素點(diǎn)的顏色*透明度值)->渲染到幀緩存區(qū)->渲染到屏幕
九. 應(yīng)用卡頓的原因以及優(yōu)化
CPU: 計(jì)算視圖frame泳桦,文本計(jì)算和排版汤徽,圖片解碼,需要繪制紋理圖片通過數(shù)據(jù)總線交給GPU灸撰。
GPU: 紋理混合谒府,頂點(diǎn)變換與計(jì)算,像素點(diǎn)的填充計(jì)算,渲染到幀緩沖區(qū)浮毯。
平時(shí)所說的“卡頓”主要是因?yàn)樵谥骶€程執(zhí)行了比較耗時(shí)的操作完疫,
可以添加Observer到主線程RunLoop中,通過監(jiān)聽RunLoop狀態(tài)切換的耗時(shí)债蓝,以達(dá)到監(jiān)控卡頓的目的壳鹤。
-
1. 屏幕呈像原理
-
2. 卡頓產(chǎn)生的原因
在 VSync 信號(hào)到來后,系統(tǒng)圖形服務(wù)會(huì)通過 CADisplayLink 等機(jī)制通知 App惦蚊,App 主線程開始在 CPU 中計(jì)算顯示內(nèi)容器虾,比如視圖的創(chuàng)建、布局計(jì)算蹦锋、圖片解碼兆沙、文本繪制等。隨后 CPU 會(huì)將計(jì)算好的內(nèi)容提交到 GPU 去莉掂,由 GPU 進(jìn)行變換葛圃、合成、渲染。隨后 GPU 會(huì)把渲染結(jié)果提交到幀緩沖區(qū)去库正,等待下一次 VSync 信號(hào)到來時(shí)顯示到屏幕上曲楚。由于垂直同步的機(jī)制,如果在一個(gè) VSync 時(shí)間內(nèi)褥符,CPU 或者 GPU 沒有完成內(nèi)容提交龙誊,則那一幀就會(huì)被丟棄,等待下一次機(jī)會(huì)再顯示喷楣,而這時(shí)顯示屏?xí)A糁暗膬?nèi)容不變趟大。這就是界面卡頓的原因。
從上面的圖中可以看到铣焊,CPU 和 GPU 不論哪個(gè)阻礙了顯示流程逊朽,都會(huì)造成掉幀現(xiàn)象。所以開發(fā)時(shí)曲伊,也需要分別對(duì) CPU 和 GPU 壓力進(jìn)行評(píng)估和優(yōu)化叽讳。
-
3. 卡頓優(yōu)化
CPU
- 盡量用輕量級(jí)的對(duì)象,比如用不到事件處理的地方坟募,可以考慮使用CALayer取代UIView
- 不要頻繁地調(diào)用UIView的相關(guān)屬性岛蚤,比如frame、bounds懈糯、transform等屬性灭美,盡量減少不必要的修改
- 盡量提前計(jì)算好布局,在有需要時(shí)一次性調(diào)整對(duì)應(yīng)的屬性昂利,不要多次修改屬性
- Autolayout會(huì)比直接設(shè)置frame消耗更多的CPU資源
- 圖片的size最好剛好跟UIImageView的size保持一致
- 控制一下線程的最大并發(fā)數(shù)量
- 盡量把耗時(shí)的操作放到子線程:文本處理(尺寸計(jì)算、繪制)铁坎、圖片處理(解碼蜂奸、繪制)等
GPU
- 盡量避免短時(shí)間內(nèi)大量圖片的顯示,盡可能將多張圖片合成一張進(jìn)行顯示
- GPU能處理的最大紋理尺寸是4096x4096硬萍,一旦超過這個(gè)尺寸扩所,就會(huì)占用CPU資源進(jìn)行處理,所以紋理盡量不要超過這個(gè)尺寸
- 盡量減少視圖數(shù)量和層次
- 減少透明的視圖(alpha<1)朴乖,不透明的就設(shè)置opaque為YES
- 盡量避免出現(xiàn) 離屏渲染
十. APP的啟動(dòng)
APP的冷啟動(dòng)可以概括為3大階段:dyld祖屏、runtime、main
-
1. dyld
dyld(dynamic link editor)买羞,Apple的動(dòng)態(tài)鏈接器袁勺,可以用來裝載Mach-O文件(可執(zhí)行文件、動(dòng)態(tài)庫等)畜普。
啟動(dòng)APP時(shí)期丰,dyld所做的事情有:
- 裝載APP的可執(zhí)行文件,同時(shí)會(huì)遞歸加載所有依賴的動(dòng)態(tài)庫.
- 當(dāng)dyld把可執(zhí)行文件、動(dòng)態(tài)庫都裝載完畢后钝荡,會(huì)通知Runtime進(jìn)行下一步的處理.
-
2. runtime
啟動(dòng)APP時(shí)街立,runtime所做的事情有:
- 調(diào)用map_images進(jìn)行可執(zhí)行文件內(nèi)容的解析和處理
- 在load_images中調(diào)用call_load_methods,調(diào)用所有Class和Category的+load方法
- 進(jìn)行各種objc結(jié)構(gòu)的初始化(注冊(cè)O(shè)bjc類 埠通、初始化類對(duì)象等等)
- 調(diào)用C++靜態(tài)初始化器和attribute((constructor))修飾的函數(shù)
-
3. main
接下來就是UIApplicationMain函數(shù)赎离,AppDelegate的application:didFinishLaunchingWithOptions:方法
-
4. APP啟動(dòng)優(yōu)化
推薦學(xué)習(xí)資料:
Swift從入門到精通
每周一道算法題
戀上數(shù)據(jù)結(jié)構(gòu)與算法(一)
戀上數(shù)據(jù)結(jié)構(gòu)與算法(二)
如果需要跟我交流的話:
※ Github: https://github.com/wsl2ls
※ 簡書:http://www.reibang.com/u/e15d1f644bea
※ 微信公眾號(hào):iOS2679114653
※ QQ群:835303405