目錄
1.給分類(lèi)增加屬性
2.方法添加和替換和KVO實(shí)現(xiàn)
3.weak 釋放 nil 的過(guò)程
4.消息轉(zhuǎn)發(fā)(熱更新)解決 Bug(JSPatch)
5.實(shí)現(xiàn) NSCoding 的自動(dòng)歸檔和自動(dòng)解檔
6.實(shí)現(xiàn)字典和模型的自動(dòng)轉(zhuǎn)換(MJExtension)
7.[self class] 和 [super class]
8.Runtime補(bǔ)充說(shuō)明
相關(guān)鏈接:
https://juejin.cn/post/6844903586216804359
http://www.itdecent.cn/p/c85e478d984c
https://juejin.cn/post/6844904079957688328
https://juejin.cn/post/6921348387501834254
1.關(guān)聯(lián)對(duì)象(Objective-C Associated Objects)給分類(lèi)增加屬性
我們都是知道分類(lèi)是不能自定義屬性和變量的,下面通過(guò)關(guān)聯(lián)對(duì)象實(shí)現(xiàn)給分類(lèi)添加屬性。關(guān)聯(lián)對(duì)象 Runtime 提供了幾個(gè)接口如下代碼。
其中參數(shù)解釋為:
id object:被關(guān)聯(lián)的對(duì)象
const void *key:關(guān)聯(lián)的key,要求唯一
id value:關(guān)聯(lián)的對(duì)象
objc_AssociationPolicy policy:內(nèi)存管理的策略
//關(guān)聯(lián)對(duì)象
void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy)
//獲取關(guān)聯(lián)的對(duì)象
id objc_getAssociatedObject(id object, const void *key)
//移除關(guān)聯(lián)的對(duì)象
void objc_removeAssociatedObjects(id object)
- 內(nèi)存管理的策略
下面我們看看內(nèi)存策略對(duì)應(yīng)的屬性修飾。
typedef OBJC_ENUM(uintptr_t, objc_AssociationPolicy) {
OBJC_ASSOCIATION_ASSIGN = 0, /**< Specifies a weak reference to the associated object. */
OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1, /**< Specifies a strong reference to the associated object.
* The association is not made atomically. */
OBJC_ASSOCIATION_COPY_NONATOMIC = 3, /**< Specifies that the associated object is copied.
* The association is not made atomically. */
OBJC_ASSOCIATION_RETAIN = 01401, /**< Specifies a strong reference to the associated object.
* The association is made atomically. */
OBJC_ASSOCIATION_COPY = 01403 /**< Specifies that the associated object is copied.
* The association is made atomically. */
};

- 舉例-下面實(shí)現(xiàn)一個(gè) UIView 的 Category 添加自定義屬性 defaultColor:
#import "ViewController.h"
#import "objc/runtime.h"
@interface UIView (DefaultColor)
@property (nonatomic, strong) UIColor *defaultColor;
@end
@implementation UIView (DefaultColor)
@dynamic defaultColor;
static char kDefaultColorKey;
- (void)setDefaultColor:(UIColor *)defaultColor {
objc_setAssociatedObject(self, &kDefaultColorKey, defaultColor, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
- (id)defaultColor {
return objc_getAssociatedObject(self, &kDefaultColorKey);
}
@end
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
UIView *test = [UIView new];
test.defaultColor = [UIColor blackColor];
NSLog(@"%@", test.defaultColor);
}
@end
打印結(jié)果: 2018-04-01 15:41:44.977732+0800 ocram[2053:63739] UIExtendedGrayColorSpace 0 1
打印結(jié)果來(lái)看,我們成功在分類(lèi)上添加了一個(gè)屬性,實(shí)現(xiàn)了它的 setter 和 getter 方法。通過(guò)關(guān)聯(lián)對(duì)象實(shí)現(xiàn)的屬性的內(nèi)存管理也是有 ARC 管理的,所以我們只需要給定適當(dāng)?shù)膬?nèi)存策略就行了,不需要操心對(duì)象的釋放。
2.方法魔法(Method Swizzling)方法添加和替換和KVO實(shí)現(xiàn)
- 2.1 方法添加
實(shí)際上添加方法在講消息轉(zhuǎn)發(fā)的時(shí)候,動(dòng)態(tài)方法解析的時(shí)候就提到了。
- cls 被添加方法的類(lèi)
- name 添加的方法的名稱(chēng)的SEL
- imp 方法的實(shí)現(xiàn)。該函數(shù)必須至少要有兩個(gè)參數(shù),self,_cmd
- 類(lèi)型編碼
//class_addMethod(Class _Nullable __unsafe_unretained cls, SEL _Nonnull name, IMP _Nonnull imp, const char * _Nullable types)
class_addMethod([self class], sel, (IMP)fooMethod, "v@:");
- 2.2 方法替換
例子1:實(shí)現(xiàn)替換 ViewController 的 viewDidLoad 方法
其中:
1.swizzling 應(yīng)該只在 +load 中完成。 在 Objective-C 的運(yùn)行時(shí)中,每個(gè)類(lèi)有兩個(gè)方法都會(huì)自動(dòng)調(diào)用。+load 是在一個(gè)類(lèi)被初始裝載時(shí)調(diào)用,+initialize 是在應(yīng)用第一次調(diào)用該類(lèi)的類(lèi)方法或?qū)嵗椒ㄇ罢{(diào)用的。兩個(gè)方法都是可選的,并且只有在方法被實(shí)現(xiàn)的情況下才會(huì)被調(diào)用。
2.swizzling 應(yīng)該只在 dispatch_once 中完成,由于 swizzling 改變了全局的狀態(tài),所以我們需要確保每個(gè)預(yù)防措施在運(yùn)行時(shí)都是可用的。原子操作就是這樣一個(gè)用于確保代碼只會(huì)被執(zhí)行一次的預(yù)防措施,就算是在不同的線程中也能確保代碼只執(zhí)行一次。Grand Central Dispatch 的 dispatch_once 滿(mǎn)足了所需要的需求,并且應(yīng)該被當(dāng)做使用 swizzling 的初始化單例方法的標(biāo)準(zhǔn)。
@implementation ViewController
+ (void)load {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
Class class = [self class];
SEL originalSelector = @selector(viewDidLoad);
SEL swizzledSelector = @selector(jkviewDidLoad);
Method originalMethod = class_getInstanceMethod(class,originalSelector);
Method swizzledMethod = class_getInstanceMethod(class,swizzledSelector);
//judge the method named swizzledMethod is already existed.
BOOL didAddMethod = class_addMethod(class, originalSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod));
// if swizzledMethod is already existed.
if (didAddMethod) {
class_replaceMethod(class, swizzledSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod));
}
else {
method_exchangeImplementations(originalMethod, swizzledMethod);
}
});
}
- (void)jkviewDidLoad {
NSLog(@"替換的方法");
[self jkviewDidLoad];
}
- (void)viewDidLoad {
NSLog(@"自帶的方法");
[super viewDidLoad];
}
@end

例子2:友盟統(tǒng)計(jì)埋點(diǎn)時(shí)候交換系統(tǒng)方法
友盟的方法照著文檔寫(xiě)就可以;runtime那個(gè)就是交換系統(tǒng)的方法達(dá)到無(wú)侵入的埋點(diǎn)。主要看要交換的方法,比如最常見(jiàn)的vc的那幾個(gè)周期方法。無(wú)侵入的意思就是不要到處都去寫(xiě)。
例子3:修改重寫(xiě) KVC 的 undefinedKey 方法
KVC 鍵值對(duì)的時(shí)候 key 找不到 vaue 的時(shí)候就會(huì)調(diào)用 undefinedKey 方法,就無(wú)法字典轉(zhuǎn)模型,這個(gè)時(shí)候可以利用 Runtime 修改重寫(xiě)這個(gè) undefinedKey 方法解決奔潰,就可以繼續(xù)使用 KVC 進(jìn)行字典轉(zhuǎn)模型來(lái)使用。
- 2.3 KVO 實(shí)現(xiàn)
全稱(chēng)是 Key-value observing,翻譯成鍵值觀察。提供了一種當(dāng)其它對(duì)象屬性被修改的時(shí)候能通知當(dāng)前對(duì)象的機(jī)制,在 MVC 大行其道的 Cocoa 中,KVO 機(jī)制很適合實(shí)現(xiàn) model 和 controller 類(lèi)之間的通訊。


3.weak 釋放 nil 的過(guò)程
Runtime 會(huì)對(duì) weak 屬性進(jìn)行內(nèi)存布局,構(gòu)建 hash 表:以 weak 屬性對(duì)象內(nèi)存地址為 key,weak 屬性值(weak自身地址)為 value。當(dāng)對(duì)象引用計(jì)數(shù)為0 dealloc 時(shí),會(huì)將 weak 屬性值自動(dòng)置 nil。(Hash 表你理解成字典就行)

- 補(bǔ)充:weak 屬性需要在 dealloc 中置 nil 么?

4.消息轉(zhuǎn)發(fā)(熱更新)解決 Bug(JSPatch)
JSPatch 是一個(gè) iOS 動(dòng)態(tài)更新框架,只需在項(xiàng)目中引入極小的引擎,就可以使用 JavaScript 調(diào)用任何 Objective-C 原生接口,獲得腳本語(yǔ)言的優(yōu)勢(shì):為項(xiàng)目動(dòng)態(tài)添加模塊或替換項(xiàng)目原生代碼動(dòng)態(tài)修復(fù) bug。
關(guān)于消息轉(zhuǎn)發(fā),前面已經(jīng)講到過(guò)了,消息轉(zhuǎn)發(fā)分為三級(jí),我們可以在每級(jí)實(shí)現(xiàn)替換功能,實(shí)現(xiàn)消息轉(zhuǎn)發(fā),從而不會(huì)造成崩潰。JSPatch 不僅能夠?qū)崿F(xiàn)消息轉(zhuǎn)發(fā),還可以實(shí)現(xiàn)方法添加、替換等一系列功能。
5.實(shí)現(xiàn) NSCoding 的自動(dòng)歸檔和自動(dòng)解檔
原理描述:用 Runtime 提供的函數(shù)遍歷 Model 自身所有屬性,并對(duì)屬性進(jìn)行 encode 和 decode 操作。 核心方法為在 Model 的基類(lèi)中重寫(xiě)方法,代碼如下。
- (id)initWithCoder:(NSCoder *)aDecoder {
if (self = [super init]) {
unsigned int outCount;
Ivar * ivars = class_copyIvarList([self class], &outCount);
for (int i = 0; i < outCount; i ++) {
Ivar ivar = ivars[i];
NSString * key = [NSString stringWithUTF8String:ivar_getName(ivar)];
[self setValue:[aDecoder decodeObjectForKey:key] forKey:key];
}
}
return self;
}
- (void)encodeWithCoder:(NSCoder *)aCoder {
unsigned int outCount;
Ivar * ivars = class_copyIvarList([self class], &outCount);
for (int i = 0; i < outCount; i ++) {
Ivar ivar = ivars[i];
NSString * key = [NSString stringWithUTF8String:ivar_getName(ivar)];
[aCoder encodeObject:[self valueForKey:key] forKey:key];
}
}
6.實(shí)現(xiàn)字典和模型的自動(dòng)轉(zhuǎn)換(MJExtension)
原理描述:用 Runtime 提供的函數(shù)遍歷 Model 自身所有屬性,如果屬性在 json 中有對(duì)應(yīng)的值,則將其賦值。 核心方法為在 NSObject 的分類(lèi)中添加方法,代碼如下。
- (instancetype)initWithDict:(NSDictionary *)dict {
if (self = [self init]) {
//(1)獲取類(lèi)的屬性及屬性對(duì)應(yīng)的類(lèi)型
NSMutableArray * keys = [NSMutableArray array];
NSMutableArray * attributes = [NSMutableArray array];
/*
* 例子
* name = value3 attribute = T@"NSString",C,N,V_value3
* name = value4 attribute = T^i,N,V_value4
*/
unsigned int outCount;
objc_property_t * properties = class_copyPropertyList([self class], &outCount);
for (int i = 0; i < outCount; i ++) {
objc_property_t property = properties[i];
//通過(guò)property_getName函數(shù)獲得屬性的名字
NSString * propertyName = [NSString stringWithCString:property_getName(property) encoding:NSUTF8StringEncoding];
[keys addObject:propertyName];
//通過(guò)property_getAttributes函數(shù)可以獲得屬性的名字和@encode編碼
NSString * propertyAttribute = [NSString stringWithCString:property_getAttributes(property) encoding:NSUTF8StringEncoding];
[attributes addObject:propertyAttribute];
}
//立即釋放properties指向的內(nèi)存
free(properties);
//(2)根據(jù)類(lèi)型給屬性賦值
for (NSString * key in keys) {
if ([dict valueForKey:key] == nil) continue;
[self setValue:[dict valueForKey:key] forKey:key];
}
}
return self;
}
7.[self class] 和 [super class]
https://blog.csdn.net/qq_45836906/article/details/119176086
https://juejin.cn/post/6844904100228759560
https://juejin.cn/post/6844904100228759560
8.Runtime補(bǔ)充說(shuō)明
其實(shí) KVC 就是 Runtime,因?yàn)?KVC 的賦值取值都是在運(yùn)行時(shí)操作。
ios 中 Runtime 介紹以及使用:
http://www.itdecent.cn/p/595e4c63c732
http://www.itdecent.cn/p/add581f0e8bb#

例子 - 運(yùn)行時(shí)獲取成員變量名稱(chēng)
#import <Foundation/Foundation.h>
#import "XMGPerson.h"
#import <objc/runtime.h>
int main(int argc, const char * argv[]) {
@autoreleasepool {
// 成員變量的數(shù)量
unsigned int outCount = 0;
// 獲得所有的成員變量
// ivars是一個(gè)指向成員變量的指針
// ivars默認(rèn)指向第0個(gè)成員變量(最前面)
Ivar *ivars = class_copyIvarList([XMGPerson class], &outCount);
// 遍歷所有的成員變量
for (int i = 0; i<outCount; i++) {
// 取出i位置對(duì)應(yīng)的成員變量
// Ivar ivar = *(ivars + i);
Ivar ivar = ivars[i];
// 獲得成員變量的名字
NSLog(@"%s", ivar_getName(ivar));
}
// 如果函數(shù)名中包含了copy\new\retain\create等字眼,那么這個(gè)函數(shù)返回的數(shù)據(jù)就需要手動(dòng)釋放
free(ivars);
// Ivar ivar = *ivars;
// Ivar ivar2 = *(ivars + 1);
// NSLog(@"%s %s", ivar_getName(ivar), ivar_getName(ivar2));
// 一個(gè)Ivar就代表一個(gè)成員變量
// int *p; 指向int類(lèi)型的變量
// Ivar *ivars; 指向Ivar類(lèi)型的變量
}
return 0;
}

