【iOS】架構師之路~底層原理二: (Runtime、Runloop)
阿新 • • 發佈:2019-12-25
十二.Runtime
12.1 runtime 介紹
Objective-C是一門動態性比較強的程式語言,跟C、C++等語言有著很大的不同
Objective-C的動態性是由Runtime API來支撐的
Runtime API提供的介面基本都是C語言的,原始碼由C\C++\組合語言編寫
複製程式碼
12.2 isa 詳解
在arm64架構之前,isa就是一個普通的指標,儲存著Class、Meta-Class物件的記憶體地址
從arm64架構開始,對isa進行了優化,變成了一個共用體(union)結構,還使用位域來儲存更多的資訊
複製程式碼
12.3 isa詳解 – 位域
12.4 &(按位與符號介紹)
& 都是1才是1,一個1,就是0
0000&0010 可以取出某一個特定位數值
掩碼: 用來按位與(&)運算的
1<<0 左移0位 則為0b 0000 0001
1<<1 左移1位 則為0b 0000 0010
1<<2 左移2位 則為0b 0000 0400
1左移幾位
複製程式碼
12.5 |(按位或)
| 有1才是1,就是1,2個0就是0
0000 | 0010 結果就是 0010
複製程式碼
12.6 ~(按位取反)
~ 0000 1010 取反位1111 0101
複製程式碼
12.7 位域
struct{
char tall : 1; //表示只佔一位
char rich : 1;
char handsome: 1
} test // 0b0000 0111
複製程式碼
12.8 共用體
unioc{
char bits;
struct{
char tall : 1;
char rich : 1;
char handsome: 1
} test
}
複製程式碼
12.9 Class的結構
12.10 class_rw_t
12.11 class_ro_t
12.12 method_t
12.13 方法快取
Class內部結構中有個方法快取(cache_t),用散列表(雜湊表)來快取曾經呼叫過的方法,可以提高方法的查詢速度
複製程式碼
快取查詢
objc-cache.mm
bucket_t * cache_t::find(cache_key_t k,id receiver)
複製程式碼
12.14 runtime objc_msgSend() 訊息機制
objc_msgSend(物件,sel_registerName(方法名字字串))
// 訊息接收者(receiver)
// 訊息名稱
// OC的方法呼叫: 訊息機制,給方法呼叫者傳送訊息
內部執行分3大階段
1. 訊息傳送
2. 動態方法解析
3. 訊息轉發
複製程式碼
12.15 訊息傳送 過程
1.通過方法名字去 類物件 方法快取中查詢,如果有則返回方法地址
2.如果沒有快取,則會遍歷 方法列表查詢
3.查到了方法返回,並新增到 快取列表
4.如果沒找到則會去父類快取中查詢,在去父類方法列表中查詢,一層一層父類往上找
複製程式碼
12.16 動態方法解析
1.如果 訊息傳送 未找到方法,則會進行動態方法解析
2.如果是 物件方法呼叫會 呼叫_class_resoveInstanceMethod()
如果是 類方法呼叫 呼叫 _class_resoveClassMethod()
3.+(BOOL)ResoveInstanceMethod:(SEL) sel{
// 獲取其他方法
Method method = class_getInstanceMethod(self,@selector(test));
//動態新增方法
class_addMethod(self,@selecetor(test),method_getImplementation(method),method_getTypeEndcoing(method));
return YES;
}
類方法同理
複製程式碼
動態新增方法
12.17 訊息轉發
// 訊息轉發-
-(id)forwardTargetForSelector:(SEL) aSelector{
// 判斷方法名字
if(aSelector == @selector(test)){
//轉發給哪個物件解決
return [NSPerson alloc]init];
}
}
// 訊息轉發- 方法簽名,返回值型別,引數型別
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
if(aSelector == @selector(testMethod))
{
return [NSMethodSignature signatureWithObjCTypes:"v@:"];
}
return nil;
}
-(void)forwardInvocation:(NSInvocation *)anInvocation
{
if (anInvocation.selector == @selector(testMethod))
{
TestModelHelper1 *h1 = [[TestModelHelper1 alloc] init];
TestModelHelper2 *h2 = [[TestModelHelper2 alloc] init];
[anInvocation invokeWithTarget:h1];
[anInvocation invokeWithTarget:h2];
}
}
複製程式碼
12.18 [self class] & [super class]
[super message] 訊息接受者還是子類,方法尋找是從父類查詢的
super呼叫,底層會轉換為objc_msgSendSuper2函式的呼叫,接收2個引數
struct objc_super2
SEL
複製程式碼
receiver是訊息接收者
current_class是receiver的Class物件
複製程式碼
12.19 Runtime的應用01 – 檢視私有成員變數
12.20 Runtime的應用02 – 字典轉模型
利用Runtime遍歷所有的屬性或者成員變數
利用KVC設值
複製程式碼
12.21 runtime 方法交換
class_replaceMethod
method_exchangeImplementations
複製程式碼
12.22.常用 api
12.22.1 Runtime API01 – 類
動態建立一個類(引數:父類,類名,額外的記憶體空間)
Class objc_allocateClassPair(Class superclass,const char *name,size_t extraBytes)
註冊一個類(要在類註冊之前新增成員變數)
void objc_registerClassPair(Class cls)
銷燬一個類
void objc_disposeClassPair(Class cls)
獲取isa指向的Class
Class object_getClass(id obj)
設定isa指向的Class
Class object_setClass(id obj,Class cls)
判斷一個OC物件是否為Class
BOOL object_isClass(id obj)
判斷一個Class是否為元類
BOOL class_isMetaClass(Class cls)
獲取父類
Class class_getSuperclass(Class cls)
複製程式碼
12.22.2 Runtime API02 – 成員變數
獲取一個例項變數資訊
Ivar class_getInstanceVariable(Class cls,const char *name)
拷貝例項變數列表(最後需要呼叫free釋放)
Ivar *class_copyIvarList(Class cls,unsigned int *outCount)
設定和獲取成員變數的值
void object_setIvar(id obj,Ivar ivar,id value)
id object_getIvar(id obj,Ivar ivar)
動態新增成員變數(已經註冊的類是不能動態新增成員變數的)
BOOL class_addIvar(Class cls,const char * name,size_t size,uint8_t alignment,const char * types)
獲取成員變數的相關資訊
const char *ivar_getName(Ivar v)
const char *ivar_getTypeEncoding(Ivar v)
複製程式碼
12.22.3 Runtime API03 – 屬性
獲取一個屬性
objc_property_t class_getProperty(Class cls,const char *name)
拷貝屬性列表(最後需要呼叫free釋放)
objc_property_t *class_copyPropertyList(Class cls,unsigned int *outCount)
動態新增屬性
BOOL class_addProperty(Class cls,const objc_property_attribute_t *attributes,unsigned int attributeCount)
動態替換屬性
void class_replaceProperty(Class cls,unsigned int attributeCount)
獲取屬性的一些資訊
const char *property_getName(objc_property_t property)
const char *property_getAttributes(objc_property_t property)
複製程式碼
####12.22.4 Runtime API04 – 方法
獲得一個例項方法、類方法
Method class_getInstanceMethod(Class cls,SEL name)
Method class_getClassMethod(Class cls,SEL name)
方法實現相關操作
IMP class_getMethodImplementation(Class cls,SEL name)
IMP method_setImplementation(Method m,IMP imp)
void method_exchangeImplementations(Method m1,Method m2)
拷貝方法列表(最後需要呼叫free釋放)
Method *class_copyMethodList(Class cls,unsigned int *outCount)
動態新增方法
BOOL class_addMethod(Class cls,SEL name,IMP imp,const char *types)
動態替換方法
IMP class_replaceMethod(Class cls,const char *types)
獲取方法的相關資訊(帶有copy的需要呼叫free去釋放)
SEL method_getName(Method m)
IMP method_getImplementation(Method m)
const char *method_getTypeEncoding(Method m)
unsigned int method_getNumberOfArguments(Method m)
char *method_copyReturnType(Method m)
char *method_copyArgumentType(Method m,unsigned int index)
選擇器相關
const char *sel_getName(SEL sel)
SEL sel_registerName(const char *str)
用block作為方法實現
IMP imp_implementationWithBlock(id block)
id imp_getBlock(IMP anImp)
BOOL imp_removeBlock(IMP anImp)
複製程式碼
十三. runloop
13.1 runloop 簡介
執行迴圈-在程式執行中迴圈做一些事情
應用範疇
定時器(Timer)、PerformSelector
GCD Async Main Queue
事件響應、手勢識別、介面重新整理
網路請求
AutoreleasePool
複製程式碼
13.2 runloop 基本作用
RunLoop的基本作用
保持程式的持續執行
處理App中的各種事件(比如觸控事件、定時器事件等)
節省CPU資源,提高程式效能:該做事時做事,該休息時休息
......
複製程式碼
13.3 Runloop 物件
iOS中有2套API來訪問和使用RunLoop
Foundation:NSRunLoop
Core Foundation:CFRunLoopRef
NSRunLoop和CFRunLoopRef都代表著RunLoop物件
NSRunLoop是基於CFRunLoopRef的一層OC包裝
CFRunLoopRef是開源的
https://opensource.apple.com/tarballs/CF/
複製程式碼
13.4 runloop 和執行緒關係
每條執行緒都有唯一的一個與之對應的RunLoop物件
RunLoop儲存在一個全域性的Dictionary裡,執行緒作為key,RunLoop作為value
執行緒剛建立時並沒有RunLoop物件,RunLoop會在第一次獲取它時建立
RunLoop會線上程結束時銷燬
主執行緒的RunLoop已經自動獲取(建立),子執行緒預設沒有開啟RunLoop
複製程式碼
13.5 獲取 RunLoop 物件
Foundation
[NSRunLoop currentRunLoop]; // 獲得當前執行緒的RunLoop物件
[NSRunLoop mainRunLoop]; // 獲得主執行緒的RunLoop物件
Core Foundation
CFRunLoopGetCurrent(); // 獲得當前執行緒的RunLoop物件
CFRunLoopGetMain(); // 獲得主執行緒的RunLoop物件
複製程式碼
13.6 runloop 相關類
13.7 CFRunloopModeRef 模式
CFRunLoopModeRef代表RunLoop的執行模式
一個RunLoop包含若干個Mode,每個Mode又包含若干個Source0/Source1/Timer/Observer
RunLoop啟動時只能選擇其中一個Mode,作為currentMode
如果需要切換Mode,只能退出當前Loop,再重新選擇一個Mode進入
- 不同組的Source0/Source1/Timer/Observer能分隔開來,互不影響
如果Mode裡沒有任何Source0/Source1/Timer/Observer,RunLoop會立馬退出
複製程式碼
常見的2種Mode
kCFRunLoopDefaultMode(NSDefaultRunLoopMode):App的預設Mode,通常主執行緒是在這個Mode下執行
UITrackingRunLoopMode:介面跟蹤 Mode,用於 ScrollView 追蹤觸控滑動,保證介面滑動時不受其他 Mode 影響
複製程式碼
新增Observer監聽RunLoop的所有狀態
13.8 執行邏輯
13.9 RunLoop 在實際開中的應用
控制執行緒生命週期(執行緒保活)
解決NSTimer在滑動時停止工作的問題
監控應用卡頓
效能優化
複製程式碼
13.10 執行緒保活(常駐執行緒)
runloop 如果沒有任何 source/source0 timer/observer 就會退出
複製程式碼