底層原理一: (OC 本質(zhì)然爆、KVC、KVO黍图、Category曾雕、Block)
底層原理二: (Runtime、Runloop)
底層原理三 : (多線程助被、內(nèi)存管理)
底層原理四 : (性能優(yōu)化剖张、架構(gòu))
底層原理五 : (面試題目整理)
十四. 多線程
14.1 ios 多線程方案
pthread / NSThread /GCD /NSOperation
45.png
14.2GCD的常用函數(shù)
GCD中有2個(gè)用來(lái)執(zhí)行任務(wù)的函數(shù)
用同步的方式執(zhí)行任務(wù)
dispatch_sync(dispatch_queue_t queue, dispatch_block_t block);
queue:隊(duì)列
block:任務(wù)
用異步的方式執(zhí)行任務(wù)
dispatch_async(dispatch_queue_t queue, dispatch_block_t block);
GCD源碼:https://github.com/apple/swift-corelibs-libdispatch
14.3 GCD的隊(duì)列
GCD的隊(duì)列可以分為2大類(lèi)型
并發(fā)隊(duì)列(Concurrent Dispatch Queue)
可以讓多個(gè)任務(wù)并發(fā)(同時(shí))執(zhí)行(自動(dòng)開(kāi)啟多個(gè)線程同時(shí)執(zhí)行任務(wù))
并發(fā)功能只有在異步(dispatch_async)函數(shù)下才有效
串行隊(duì)列(Serial Dispatch Queue)
讓任務(wù)一個(gè)接著一個(gè)地執(zhí)行(一個(gè)任務(wù)執(zhí)行完畢后,再執(zhí)行下一個(gè)任務(wù))
14.4 容易混淆的術(shù)語(yǔ)
有4個(gè)術(shù)語(yǔ)比較容易混淆:同步恰起、異步修械、并發(fā)、串行
同步和異步主要影響:能不能開(kāi)啟新的線程
同步:在當(dāng)前線程中執(zhí)行任務(wù)检盼,不具備開(kāi)啟新線程的能力
異步:在新的線程中執(zhí)行任務(wù)肯污,具備開(kāi)啟新線程的能力
并發(fā)和串行主要影響:任務(wù)的執(zhí)行方式
并發(fā):多個(gè)任務(wù)并發(fā)(同時(shí))執(zhí)行
串行:一個(gè)任務(wù)執(zhí)行完畢后,再執(zhí)行下一個(gè)任務(wù)
14.5 各種隊(duì)列的執(zhí)行效果
46.png
14.6 GCD隊(duì)列組的使用
47.png
14.7 多線程的安全隱患
資源共享
1塊資源可能會(huì)被多個(gè)線程共享吨枉,也就是多個(gè)線程可能會(huì)訪問(wèn)同一塊資源
比如多個(gè)線程訪問(wèn)同一個(gè)對(duì)象蹦渣、同一個(gè)變量、同一個(gè)文件
當(dāng)多個(gè)線程訪問(wèn)同一塊資源時(shí)貌亭,很容易引發(fā)數(shù)據(jù)錯(cuò)亂和數(shù)據(jù)安全問(wèn)題
48.png
14.8 多線程安全隱患的解決方案
49.png
14.9 iOS中的線程同步方案 線程安全.線程鎖
解決方案: 使用線程同步技術(shù)(同步,就是協(xié)同步調(diào),按預(yù)定的先后次序進(jìn)行)
常見(jiàn)線程同步技術(shù): 加鎖
OSSpinLock
os_unfair_lock
pthread_mutex
dispatch_semaphore
dispatch_queue(DISPATCH_QUEUE_SERIAL)
NSLock
NSRecursiveLock
NSCondition
NSConditionLock
@synchronized
14.10 OSSpinLock (自旋鎖) 不安全
50.png
14.11 OSUnfairLock (互斥鎖) 運(yùn)行效率最高
51.png
14.12 pthread_mutex
52.png
mutex 互斥鎖,等待鎖的線程會(huì)處于休眠狀態(tài)
14.13 NSLock柬唯、NSRecursiveLock
53.png
14.14 NSCondition
54.png
14.15 dispatch_queue (SerialQueue)
使用GCD串行隊(duì)列,實(shí)現(xiàn)同步
55.png
14.16 dispatch_semaphore (信號(hào)量) 可以用于控制最大并發(fā)數(shù)量
semaphore叫做”信號(hào)量”
信號(hào)量的初始值,可以用來(lái)控制線程并發(fā)訪問(wèn)的最大數(shù)量
信號(hào)量的初始值為1圃庭,代表同時(shí)只允許1條線程訪問(wèn)資源锄奢,保證線程同步
56.png
14.17 @synchronized (互斥鎖)
@synchronized是對(duì)mutex遞歸鎖的封裝
源碼查看:objc4中的objc-sync.mm文件
@synchronized(obj)內(nèi)部會(huì)生成obj對(duì)應(yīng)的遞歸鎖,然后進(jìn)行加鎖剧腻、解鎖操作
14.18 iOS線程同步方案性能比較
57.png
os_unfair_lock ios10 開(kāi)始
OSSpanLock ios10 廢棄
dispatch_semaphore
dispatch_mutex
dispatch_queue 串行
NSLock 對(duì) mutex 封裝
@synchronized 最差
14.19 自旋鎖拘央、互斥鎖比較
什么情況使用自旋鎖比較劃算?
預(yù)計(jì)線程等待鎖的時(shí)間很短
加鎖的代碼(臨界區(qū))經(jīng)常被調(diào)用书在,但競(jìng)爭(zhēng)情況很少發(fā)生
CPU資源不緊張
多核處理器
什么情況使用互斥鎖比較劃算灰伟?
預(yù)計(jì)線程等待鎖的時(shí)間較長(zhǎng)
單核處理器
臨界區(qū)有IO操作
臨界區(qū)代碼復(fù)雜或者循環(huán)量大
臨界區(qū)競(jìng)爭(zhēng)非常激烈
14.20 Atomic 和 Noatomic
atomic用于保證屬性setter、getter的原子性操作儒旬,相當(dāng)于在getter和setter內(nèi)部加了線程同步的鎖
可以參考源碼objc4的objc-accessors.mm
它并不能保證使用屬性的過(guò)程是線程安全的
14.21 多線程 讀寫(xiě)線程安全方案
思考如何實(shí)現(xiàn)以下場(chǎng)景
同一時(shí)間栏账,只能有1個(gè)線程進(jìn)行寫(xiě)的操作
同一時(shí)間帖族,允許有多個(gè)線程進(jìn)行讀的操作
同一時(shí)間,不允許既有寫(xiě)的操作挡爵,又有讀的操作
上面的場(chǎng)景就是典型的“多讀單寫(xiě)”竖般,經(jīng)常用于文件等數(shù)據(jù)的讀寫(xiě)操作,iOS中的實(shí)現(xiàn)方案有
pthread_rwlock:讀寫(xiě)鎖
dispatch_barrier_async:異步柵欄調(diào)用
14.22 pthread_rwlock
58.png
14.23 dispatch_barrier_async
59.png
十五. 內(nèi)存管理
15.1 CADisplayLink了讨、NSTimer使用注意
CADisplayLink 保證調(diào)用頻率和刷幀頻率一直,60FPS, 不用設(shè)置時(shí)間間隔,每秒鐘60次
可以使用 proxy 代理解決循環(huán)引用
CADisplayLink捻激、NSTimer會(huì)對(duì)target產(chǎn)生強(qiáng)引用,如果target又對(duì)它們產(chǎn)生強(qiáng)引用前计,那么就會(huì)引發(fā)循環(huán)引用
解決方案1.使用block
60.png
解決方案2.使用代理對(duì)象(NSProxy)
61.png
15.2 NSProxy 也屬于基類(lèi)
代理,用于解決循環(huán)引用,,用于消息轉(zhuǎn)發(fā),不會(huì)在父類(lèi)查找方法
NSObject 和 NSProxy 區(qū)別
15.3 GCD定時(shí)器
NSTimer依賴于RunLoop胞谭,如果RunLoop的任務(wù)過(guò)于繁重,可能會(huì)導(dǎo)致NSTimer不準(zhǔn)時(shí)
而GCD的定時(shí)器會(huì)更加準(zhǔn)時(shí),GCD定時(shí)器,不依賴 Runloop ,會(huì)很準(zhǔn)時(shí),依賴內(nèi)核
62.png
15.4 iOS 程序的內(nèi)存布局
低地址-> 高地址
保留->代碼段->數(shù)據(jù)段(字符串常量,已初始化全局?jǐn)?shù)據(jù),未初始化數(shù)據(jù))>堆->棧內(nèi)存-> 內(nèi)核區(qū)域
代碼段: 編譯之后的代碼
數(shù)據(jù)段: 字符串常量,已經(jīng)初始化的全局變量,或者靜態(tài)變量,未初始化的全局變量,靜態(tài)變量
堆 (低>高) 通過(guò) alloc malloc calloc 動(dòng)態(tài)分配的內(nèi)存
棧 (高地址 從 低地址) 函數(shù)調(diào)用開(kāi)銷(xiāo)()
63.png
//main.cpp
int a = 0; 全局初始化區(qū)
char *p1; 全局未初始化區(qū)
main() {
int b; 棧
char s[] = "abc"; 棧
char *p2; 棧
char *p3 = "123456"; 123456\0在常量區(qū)男杈,p3在棧上丈屹。
static int c =0; 全局(靜態(tài))初始化區(qū)
p1 = (char *)malloc(10);
p2 = (char *)malloc(20);
分配得來(lái)得10和20字節(jié)的區(qū)域就在堆區(qū)伶棒。
strcpy(p1, "123456"); 123456\0放在常量區(qū)旺垒,編譯器可能會(huì)將它與p3所指向的"123456"優(yōu)化成一個(gè)地方。
}
15.5 Tagged Pointer
從64bit開(kāi)始肤无,iOS引入了Tagged Pointer技術(shù)先蒋,用于優(yōu)化NSNumber、NSDate宛渐、NSString等小對(duì)象的存儲(chǔ)
在沒(méi)有使用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)存的方式來(lái)存儲(chǔ)數(shù)據(jù)
objc_msgSend能識(shí)別Tagged Pointer笔时,比如NSNumber的intValue方法,直接從指針提取數(shù)據(jù)仗岸,節(jié)省了以前的調(diào)用開(kāi)銷(xiāo)
如何判斷一個(gè)指針是否為T(mén)agged Pointer允耿?
iOS平臺(tái),最高有效位是1(第64bit)
Mac平臺(tái)扒怖,最低有效位是1
判斷是否為T(mén)agged Pointer
64.png
15.6 OC對(duì)象的內(nèi)存管理
在iOS中右犹,使用引用計(jì)數(shù)來(lái)管理OC對(duì)象的內(nèi)存
一個(gè)新創(chuàng)建的OC對(duì)象引用計(jì)數(shù)默認(rèn)是1,當(dāng)引用計(jì)數(shù)減為0姚垃,OC對(duì)象就會(huì)銷(xiāo)毀,釋放其占用的內(nèi)存空間
調(diào)用retain會(huì)讓OC對(duì)象的引用計(jì)數(shù)+1盼忌,調(diào)用release會(huì)讓OC對(duì)象的引用計(jì)數(shù)-1
內(nèi)存管理的經(jīng)驗(yàn)總結(jié)
當(dāng)調(diào)用alloc积糯、new掂墓、copy、mutableCopy方法返回了一個(gè)對(duì)象看成,在不需要這個(gè)對(duì)象時(shí)君编,要調(diào)用release或者autorelease來(lái)釋放它
想擁有某個(gè)對(duì)象,就讓它的引用計(jì)數(shù)+1川慌;不想再擁有某個(gè)對(duì)象吃嘿,就讓它的引用計(jì)數(shù)-1
可以通過(guò)以下私有函數(shù)來(lái)查看自動(dòng)釋放池的情況
extern void _objc_autoreleasePoolPrint(void);
15.7 copy和mutableCopy
65.png
15.8 引用計(jì)數(shù)器的存儲(chǔ) retaincount
66.png
15.9 dealloc
67.png
15.10 autoreleasePool 自動(dòng)釋放池
自動(dòng)釋放池的主要底層數(shù)據(jù)結(jié)構(gòu)是:__AtAutoreleasePool、AutoreleasePoolPage
調(diào)用了autorelease的對(duì)象最終都是通過(guò)AutoreleasePoolPage對(duì)象來(lái)管理的
源碼分析
-clang重寫(xiě)@autoreleasepool
-objc4源碼:NSObject.mm
68.png
15.11 AutoreleasePoolPage的結(jié)構(gòu)
69.png
調(diào)用push方法會(huì)將一個(gè)POOL_BOUNDARY入棧梦重,并且返回其存放的內(nèi)存地址
調(diào)用pop方法時(shí)傳入一個(gè)POOL_BOUNDARY的內(nèi)存地址兑燥,會(huì)從最后一個(gè)入棧的對(duì)象開(kāi)始發(fā)送release消息,直到遇到這個(gè)POOL_BOUNDARY
id *next指向了下一個(gè)能存放autorelease對(duì)象地址的區(qū)域
15.12 runloop 和 autoreleasePool
iOS在主線程的Runloop中注冊(cè)了2個(gè)Observer
-第1個(gè)Observer監(jiān)聽(tīng)了kCFRunLoopEntry事件琴拧,會(huì)調(diào)用objc_autoreleasePoolPush()
-第2個(gè)Observer
監(jiān)聽(tīng)了kCFRunLoopBeforeWaiting事件降瞳,會(huì)調(diào)用objc_autoreleasePoolPop()、objc_autoreleasePoolPush()
監(jiān)聽(tīng)了kCFRunLoopBeforeExit事件蚓胸,會(huì)調(diào)用objc_autoreleasePoolPop()