【iOS】架構師之路~底層原理篇 一 :(OC本質、KVC、KVO、Categroy、Block)

【iOS】架構師之路~底層原理二: (Runtime、Runloop)objective-c

一. OC 對象本質

1.1 OC對象數據結構

咱們平時編寫的 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;
複製代碼

1.2 查看一個對象佔用了多少內存

建立一個實例對象,至少須要多少內存?
#import <objc/runtime.h>
class_getInstanceSize([NSObject class]);

建立一個實例對象,實際上分配了多少內存?
#import <malloc/malloc.h>
malloc_size((__bridge const void *)obj);
複製代碼

1.3 經常使用的LLDB指令

print、p:打印
po:打印對象

讀取內存
memory read/數量格式字節數  內存地址
x/數量格式字節數  內存地址
x/3xw  0x10010

修改內存中的值
memory  write  內存地址  數值
memory  write  0x0000010  10

bt 打印調用堆棧信息

call 調用方法
複製代碼

二. OC對象的分類

OC對象 能夠分爲3種:
1. instance對象 (實例對象)
2. class對象 (類對象)
3. meta-class對象 (元類對象)
複製代碼

2.1 instance對象 (實例對象)

instance對象就是經過類alloc出來的對象,每次調用alloc都會產生新的instance對象bash

NSObject *object1 = [[NSObject alloc] init];
NSObject *object2 = [[NSObject alloc] init];

object一、object2是NSObject的instance對象(實例對象)
它們是不一樣的兩個對象,分別佔據着兩塊不一樣的內存

instance對象在內存中存儲的信息包括
    -isa指針
    -其餘成員變量
複製代碼

2.2 Class對象 (類對象)

咱們平時說的類,其實也是對象,稱爲類對象, 每一個類在內存中有且只有一個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)
......
複製代碼

一個類對象的內存佈局:多線程

2.3 meta-Class 元類對象

每一個類在內存中有且只有一個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

三. isa指針 & superclass指針

3.1 isa 指針

instance的isa指向class
當調用對象方法時,經過instance的isa找到class,最後找到對象方法的實現進行調用

class的isa指向meta-class
當調用類方法時,經過class的isa找到meta-class,最後找到類方法的實現進行調用
複製代碼

3.2 superClass 指針

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,最後找到類方法的實現進行調用
複製代碼

3.3 isa 和 superClass 總結

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找父類
複製代碼

四. 窺探struct objc_class的結構

objc4源碼下載 opensource.apple.com/tarballs/ob…

五. KVO 原理及實現

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 原理及實現

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 給類擴展方法,或者關聯屬性, Categroy底層結構也是一個結構體: 內部存儲這結構體的名字,那個類的分類,以及對象和類方法列表,協議,屬性信息

- 經過Runtime加載某個類的全部Category數據

- 把全部Category的方法、屬性、協議數據,合併到一個大數組中
  後面參與編譯的Category數據,會在數組的前面

- 將合併後的分類數據(方法、屬性、協議),插入到類原來數據的前面
複製代碼

Category的底層結構

八. Load

+load方法會在runtime加載類、分類時調用

每一個類、分類的+load,在程序運行過程當中只調用一次

調用順序
1. 先調用類的+load   
    按照編譯前後順序調用(先編譯,先調用)
    調用子類的+load以前會先調用父類的+load

2.再調用分類的+load
    按照編譯前後順序調用(先編譯,先調用)
複製代碼

九. Initialze

+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))
複製代碼

關聯對象的原理

十一. Block

###11.1 Block 本質

block本質上也是一個OC對象,它內部也有個isa指針
block是封裝了函數調用以及函數調用環境的OC對象
複製代碼

block的底層結構以下圖所示

block的變量捕獲(capture) 爲了保證block內部可以正常訪問外部的變量,block有個變量捕獲機制

11.2 Block分類

block有3種類型,能夠經過調用class方法或者isa指針查看具體類型,最終都是繼承自NSBlock類型
__NSGlobalBlock__ ( _NSConcreteGlobalBlock ) 全局 block   沒有訪問auto變量
__NSStackBlock__ ( _NSConcreteStackBlock )   棧 block     訪問了auto變量
__NSMallocBlock__ ( _NSConcreteMallocBlock ) 堆 block     __NSStackBlock__調用了copy
複製代碼

11.3 Block 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);
複製代碼

11.4 __Block修飾符

__block能夠用於解決block內部沒法修改auto變量值的問題
__block不能修飾全局變量、靜態變量(static)
複製代碼

編譯器會將__block變量包裝成一個對象

11.5 __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)
複製代碼

11.6 Block 循環引用

arc    用__weak、__unsafe_unretained解決
 mrc   用__Block、__unsafe_unretained解決
複製代碼

11.7 __unsafe_unretained

unsafe / 不安全   unretained/不引用
也能夠解決循環引用,可是 指向對象銷魂,指針存儲地址不變,因此不推薦使用.__weak 會自動將指針變量設置爲 nil     .__Block也能夠解決循環引用,須要手動將引用的對象設置 nil,手動解決循環引用

MRC 下經過 __unsafe_unretained解決 或者__block 解決 __Block 修飾之後,Block 內部不會對引用對象進行強引用,計數器不會+1
複製代碼

參考:iOS底層原理班(下)/OC對象/關聯對象/多線程/內存管理/性能優化

相關文章
相關標籤/搜索