【iOS】架構師之路~底層原理二: (Runtime、Runloop)objective-c
咱們平時編寫的 Objective-C 代碼,底層實現其實都是 C\C++\彙編代碼 數組
因此 Objective-C 的面向對象都是基於 C\C++ 的數據結構實現的將 Objective-C 代碼轉換爲C\C++代碼:安全
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc OC源文件 -o 輸出的CPP文件
若是須要連接其餘框架,使用 -framework 參數。好比-framework UIKit
複製代碼
能夠獲得 OC 對象的數據結構:性能優化
// NSObject Implementation
struct NSObject_IMPL {
Class isa; // 8個字節,Class也是一個結構體指針
... //成員變量
};
// Class指針
typedef struct objc_class *Class;
複製代碼
建立一個實例對象,至少須要多少內存?
#import <objc/runtime.h>
class_getInstanceSize([NSObject class]);
建立一個實例對象,實際上分配了多少內存?
#import <malloc/malloc.h>
malloc_size((__bridge const void *)obj);
複製代碼
print、p:打印
po:打印對象
讀取內存
memory read/數量格式字節數 內存地址
x/數量格式字節數 內存地址
x/3xw 0x10010
修改內存中的值
memory write 內存地址 數值
memory write 0x0000010 10
bt 打印調用堆棧信息
call 調用方法
複製代碼
OC對象 能夠分爲3種:
1. instance對象 (實例對象)
2. class對象 (類對象)
3. meta-class對象 (元類對象)
複製代碼
instance對象就是經過類alloc出來的對象,每次調用alloc都會產生新的instance對象bash
NSObject *object1 = [[NSObject alloc] init];
NSObject *object2 = [[NSObject alloc] init];
object一、object2是NSObject的instance對象(實例對象)
它們是不一樣的兩個對象,分別佔據着兩塊不一樣的內存
instance對象在內存中存儲的信息包括
-isa指針
-其餘成員變量
複製代碼
咱們平時說的類,其實也是對象,稱爲類對象, 每一個類在內存中有且只有一個class對象數據結構
NSObject *object1 = [[NSObject alloc] init];
NSObject *object2 = [[NSObject alloc] init];
Class objectClass1 = [object1 class];
Class objectClass2 = [object2 class];
Class objectClass3 = object_getClass(object1);
Class objectClass4 = object_getClass(object2);
Class objectClass5 = [NSObject class];
objectClass1 ~ objectClass5都是NSObject的class對象(類對象)
它們是同一個對象。每一個類在內存中有且只有一個class對象
class對象在內存中存儲的信息主要包括
isa指針
superclass指針
類的屬性信息(@property)、類的對象方法信息(instance method)
類的協議信息(protocol)、類的成員變量信息(ivar)
......
複製代碼
一個類對象的內存佈局:多線程
每一個類在內存中有且只有一個meta-class對象架構
// 將類對象當作參數傳入,得到元類對象
Class objectMetaClass = object_getClass(objectClass5);
objectMetaClass是NSObject的meta-class對象(元類對象)
每一個類在內存中有且只有一個meta-class對象
meta-class對象和class對象的內存結構是同樣的,可是用途不同,在內存中存儲的信息主要包括
isa指針
superclass指針
類的類方法信息(class method)
......
查看是否爲元類對象:
Bool result = class_isMetaClass(objectMetaClass)
複製代碼
元類對象內存佈局:app
instance的isa指向class
當調用對象方法時,經過instance的isa找到class,最後找到對象方法的實現進行調用
class的isa指向meta-class
當調用類方法時,經過class的isa找到meta-class,最後找到類方法的實現進行調用
複製代碼
class對象的superclass指針框架
當Student的instance對象要調用Person的對象方法時,會先經過isa找到Student的class,
而後經過superclass找到Person的class,最後找到對象方法的實現進行調用
複製代碼
meta-class對象的superclass指針
當Student的class要調用Person的類方法時,會先經過isa找到Student的meta-class,
而後經過superclass找到Person的meta-class,最後找到類方法的實現進行調用
複製代碼
instance的isa指向class
class的isa指向meta-class
meta-class的isa指向基類的meta-class
class的superclass指向父類的class
-若是沒有父類,superclass指針爲nil
meta-class的superclass指向父類的meta-class
-基類的meta-class的superclass指向基類的class
instance調用對象方法的軌跡
-isa找到class,方法不存在,就經過superclass找父類
class調用類方法的軌跡
-isa找meta-class,方法不存在,就經過superclass找父類
複製代碼
objc4源碼下載 opensource.apple.com/tarballs/ob…
KVO的全稱是Key-Value Observing,俗稱「鍵值監聽」,能夠用於監聽某個對象屬性值的改變
使用了KVO監聽的對象
- 利用RuntimeAPI動態生成一個子類,而且讓instance對象的isa指向這個全新的子類
- 當修改instance對象的屬性時,會調用Foundation的_NSSetXXXValueAndNotify函數
willChangeValueForKey:
父類原來的setter
didChangeValueForKey:
- 內部會觸發監聽器(Oberser)的監聽方法(observeValueForKeyPath:ofObject:change:context:)
複製代碼
// 代碼
- (void)viewDidLoad {
[super viewDidLoad];
Person *person = [[Person alloc]init];;
[p addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:nil];
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context{
NSLog(@"被觀測對象:%@, 被觀測的屬性:%@, 值的改變: %@\n, 攜帶信息:%@", object, keyPath, change, context);
}
複製代碼
KVC的全稱是Key-Value Coding,俗稱「鍵值編碼」,能夠經過一個key來訪問某個屬性
常見的API有
- (void)setValue:(id)value forKeyPath:(NSString *)keyPath;
- (void)setValue:(id)value forKey:(NSString *)key;
- (id)valueForKeyPath:(NSString *)keyPath;
- (id)valueForKey:(NSString *)key;
複製代碼
Categroy 給類擴展方法,或者關聯屬性, Categroy底層結構也是一個結構體: 內部存儲這結構體的名字,那個類的分類,以及對象和類方法列表,協議,屬性信息
- 經過Runtime加載某個類的全部Category數據
- 把全部Category的方法、屬性、協議數據,合併到一個大數組中
後面參與編譯的Category數據,會在數組的前面
- 將合併後的分類數據(方法、屬性、協議),插入到類原來數據的前面
複製代碼
Category的底層結構
+load方法會在runtime加載類、分類時調用
每一個類、分類的+load,在程序運行過程當中只調用一次
調用順序
1. 先調用類的+load
按照編譯前後順序調用(先編譯,先調用)
調用子類的+load以前會先調用父類的+load
2.再調用分類的+load
按照編譯前後順序調用(先編譯,先調用)
複製代碼
+initialize方法會在類第一次接收到消息時調用
- 調用順序
先調用父類的+initialize,再調用子類的+initialize
(先初始化父類,再初始化子類,每一個類只會初始化1次)
- +initialize和+load的很大區別是,+initialize是經過objc_msgSend進行調用的,因此有如下特色
若是子類沒有實現+initialize,會調用父類的+initialize(因此父類的+initialize可能會被調用屢次)
若是分類實現了+initialize,就覆蓋類自己的+initialize調用
複製代碼
十. Categroy 添加屬性
默認狀況下,由於分類底層結構的限制,不能添加成員變量到分類中。但能夠經過關聯對象來間接實現
關聯對象提供瞭如下API
添加關聯對象
void objc_setAssociatedObject(id object, const void * key,
id value, objc_AssociationPolicy policy)
得到關聯對象
id objc_getAssociatedObject(id object, const void * key)
移除全部的關聯對象
void objc_removeAssociatedObjects(id object)
key的常見用法
static void *MyKey = &MyKey;
objc_setAssociatedObject(obj, MyKey, value, OBJC_ASSOCIATION_RETAIN_NONATOMIC)
objc_getAssociatedObject(obj, MyKey)
static char MyKey;
objc_setAssociatedObject(obj, &MyKey, value, OBJC_ASSOCIATION_RETAIN_NONATOMIC)
objc_getAssociatedObject(obj, &MyKey)
使用屬性名做爲key
objc_setAssociatedObject(obj, @"property", value, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
objc_getAssociatedObject(obj, @"property");
使用get方法的@selecor做爲key
objc_setAssociatedObject(obj, @selector(getter), value, OBJC_ASSOCIATION_RETAIN_NONATOMIC)
objc_getAssociatedObject(obj, @selector(getter))
複製代碼
關聯對象的原理
###11.1 Block 本質
block本質上也是一個OC對象,它內部也有個isa指針
block是封裝了函數調用以及函數調用環境的OC對象
複製代碼
block的底層結構以下圖所示
block的變量捕獲(capture) 爲了保證block內部可以正常訪問外部的變量,block有個變量捕獲機制
block有3種類型,能夠經過調用class方法或者isa指針查看具體類型,最終都是繼承自NSBlock類型
__NSGlobalBlock__ ( _NSConcreteGlobalBlock ) 全局 block 沒有訪問auto變量
__NSStackBlock__ ( _NSConcreteStackBlock ) 棧 block 訪問了auto變量
__NSMallocBlock__ ( _NSConcreteMallocBlock ) 堆 block __NSStackBlock__調用了copy
複製代碼
在ARC環境下,編譯器會根據狀況自動將棧上的block複製到堆上,好比如下狀況
-block做爲函數返回值時
-將block賦值給__strong指針時
-block做爲Cocoa API中方法名含有usingBlock的方法參數時
-block做爲GCD API的方法參數時
MRC下block屬性的建議寫法
-@property (copy, nonatomic) void (^block)(void);
ARC下block屬性的建議寫法
-@property (strong, nonatomic) void (^block)(void);
-@property (copy, nonatomic) void (^block)(void);
複製代碼
__block能夠用於解決block內部沒法修改auto變量值的問題
__block不能修飾全局變量、靜態變量(static)
複製代碼
編譯器會將__block變量包裝成一個對象
當block在棧上時,並不會對__block變量產生強引用
當block被copy到堆時
會調用block內部的copy函數
copy函數內部會調用_Block_object_assign函數
_Block_object_assign函數會對__block變量造成強引用(retain)
當block從堆中移除時
會調用block內部的dispose函數
dispose函數內部會調用_Block_object_dispose函數
_Block_object_dispose函數會自動釋放引用的__block變量(release)
![](https://user-gold-cdn.xitu.io/2019/8/23/16cbc949417acef8?w=1000&h=274&f=png&s=60780)
複製代碼
arc 用__weak、__unsafe_unretained解決
mrc 用__Block、__unsafe_unretained解決
複製代碼
unsafe / 不安全 unretained/不引用
也能夠解決循環引用,可是 指向對象銷魂,指針存儲地址不變,因此不推薦使用.__weak 會自動將指針變量設置爲 nil .__Block也能夠解決循環引用,須要手動將引用的對象設置 nil,手動解決循環引用
MRC 下經過 __unsafe_unretained解決 或者__block 解決 __Block 修飾之後,Block 內部不會對引用對象進行強引用,計數器不會+1
複製代碼