目錄
1.給分類增加屬性
2.方法添加和替換和KVO實現(xiàn)
3.weak 釋放 nil 的過程
4.消息轉發(fā)(熱更新)解決 Bug(JSPatch)
5.實現(xiàn) NSCoding 的自動歸檔和自動解檔
6.實現(xiàn)字典和模型的自動轉換(MJExtension)
7.[self class] 和 [super class]
8.Runtime補充說明
相關鏈接:
https://juejin.cn/post/6844903586216804359
http://www.reibang.com/p/c85e478d984c
https://juejin.cn/post/6844904079957688328
https://juejin.cn/post/6921348387501834254
1.關聯(lián)對象(Objective-C Associated Objects)給分類增加屬性
我們都是知道分類是不能自定義屬性和變量的绎晃,下面通過關聯(lián)對象實現(xiàn)給分類添加屬性。關聯(lián)對象 Runtime 提供了幾個接口如下代碼。
其中參數(shù)解釋為:
id object:被關聯(lián)的對象
const void *key:關聯(lián)的key奸晴,要求唯一
id value:關聯(lián)的對象
objc_AssociationPolicy policy:內存管理的策略
//關聯(lián)對象
void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy)
//獲取關聯(lián)的對象
id objc_getAssociatedObject(id object, const void *key)
//移除關聯(lián)的對象
void objc_removeAssociatedObjects(id object)
- 內存管理的策略
下面我們看看內存策略對應的屬性修飾击敌。
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. */
};
- 舉例-下面實現(xiàn)一個 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
打印結果: 2018-04-01 15:41:44.977732+0800 ocram[2053:63739] UIExtendedGrayColorSpace 0 1
打印結果來看,我們成功在分類上添加了一個屬性,實現(xiàn)了它的 setter 和 getter 方法淌实。通過關聯(lián)對象實現(xiàn)的屬性的內存管理也是有 ARC 管理的,所以我們只需要給定適當?shù)膬却娌呗跃托辛瞬螅恍枰傩膶ο蟮尼尫拧?/p>
2.方法魔法(Method Swizzling)方法添加和替換和KVO實現(xiàn)
- 2.1 方法添加
實際上添加方法在講消息轉發(fā)的時候拆祈,動態(tài)方法解析的時候就提到了。
- cls 被添加方法的類
- name 添加的方法的名稱的SEL
- imp 方法的實現(xiàn)倘感。該函數(shù)必須至少要有兩個參數(shù)放坏,self,_cmd
- 類型編碼
//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:實現(xiàn)替換 ViewController 的 viewDidLoad 方法
其中:
1.swizzling 應該只在 +load 中完成。 在 Objective-C 的運行時中老玛,每個類有兩個方法都會自動調用淤年。+load 是在一個類被初始裝載時調用,+initialize 是在應用第一次調用該類的類方法或實例方法前調用的逻炊。兩個方法都是可選的互亮,并且只有在方法被實現(xiàn)的情況下才會被調用。
2.swizzling 應該只在 dispatch_once 中完成余素,由于 swizzling 改變了全局的狀態(tài)豹休,所以我們需要確保每個預防措施在運行時都是可用的。原子操作就是這樣一個用于確保代碼只會被執(zhí)行一次的預防措施桨吊,就算是在不同的線程中也能確保代碼只執(zhí)行一次威根。Grand Central Dispatch 的 dispatch_once 滿足了所需要的需求,并且應該被當做使用 swizzling 的初始化單例方法的標準视乐。
@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)計埋點時候交換系統(tǒng)方法
友盟的方法照著文檔寫就可以洛搀;runtime那個就是交換系統(tǒng)的方法達到無侵入的埋點。主要看要交換的方法佑淀,比如最常見的vc的那幾個周期方法留美。無侵入的意思就是不要到處都去寫。
例子3:修改重寫 KVC 的 undefinedKey 方法
KVC 鍵值對的時候 key 找不到 vaue 的時候就會調用 undefinedKey 方法伸刃,就無法字典轉模型谎砾,這個時候可以利用 Runtime 修改重寫這個 undefinedKey 方法解決奔潰,就可以繼續(xù)使用 KVC 進行字典轉模型來使用捧颅。
- 2.3 KVO 實現(xiàn)
全稱是 Key-value observing景图,翻譯成鍵值觀察。提供了一種當其它對象屬性被修改的時候能通知當前對象的機制碉哑,在 MVC 大行其道的 Cocoa 中挚币,KVO 機制很適合實現(xiàn) model 和 controller 類之間的通訊亮蒋。
3.weak 釋放 nil 的過程
Runtime 會對 weak 屬性進行內存布局,構建 hash 表:以 weak 屬性對象內存地址為 key妆毕,weak 屬性值(weak自身地址)為 value慎玖。當對象引用計數(shù)為0 dealloc 時,會將 weak 屬性值自動置 nil笛粘。(Hash 表你理解成字典就行)
- 補充:weak 屬性需要在 dealloc 中置 nil 么凄吏?
4.消息轉發(fā)(熱更新)解決 Bug(JSPatch)
JSPatch 是一個 iOS 動態(tài)更新框架,只需在項目中引入極小的引擎闰蛔,就可以使用 JavaScript 調用任何 Objective-C 原生接口,獲得腳本語言的優(yōu)勢:為項目動態(tài)添加模塊或替換項目原生代碼動態(tài)修復 bug图柏。
關于消息轉發(fā)序六,前面已經(jīng)講到過了,消息轉發(fā)分為三級蚤吹,我們可以在每級實現(xiàn)替換功能例诀,實現(xiàn)消息轉發(fā),從而不會造成崩潰裁着。JSPatch 不僅能夠實現(xiàn)消息轉發(fā)繁涂,還可以實現(xiàn)方法添加、替換等一系列功能二驰。
5.實現(xiàn) NSCoding 的自動歸檔和自動解檔
原理描述:用 Runtime 提供的函數(shù)遍歷 Model 自身所有屬性扔罪,并對屬性進行 encode 和 decode 操作。 核心方法為在 Model 的基類中重寫方法桶雀,代碼如下矿酵。
- (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.實現(xiàn)字典和模型的自動轉換(MJExtension)
原理描述:用 Runtime 提供的函數(shù)遍歷 Model 自身所有屬性,如果屬性在 json 中有對應的值矗积,則將其賦值全肮。 核心方法為在 NSObject 的分類中添加方法,代碼如下棘捣。
- (instancetype)initWithDict:(NSDictionary *)dict {
if (self = [self init]) {
//(1)獲取類的屬性及屬性對應的類型
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];
//通過property_getName函數(shù)獲得屬性的名字
NSString * propertyName = [NSString stringWithCString:property_getName(property) encoding:NSUTF8StringEncoding];
[keys addObject:propertyName];
//通過property_getAttributes函數(shù)可以獲得屬性的名字和@encode編碼
NSString * propertyAttribute = [NSString stringWithCString:property_getAttributes(property) encoding:NSUTF8StringEncoding];
[attributes addObject:propertyAttribute];
}
//立即釋放properties指向的內存
free(properties);
//(2)根據(jù)類型給屬性賦值
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補充說明
其實 KVC 就是 Runtime辜腺,因為 KVC 的賦值取值都是在運行時操作。
ios 中 Runtime 介紹以及使用:
http://www.reibang.com/p/595e4c63c732
http://www.reibang.com/p/add581f0e8bb#
例子 - 運行時獲取成員變量名稱
#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是一個指向成員變量的指針
// ivars默認指向第0個成員變量(最前面)
Ivar *ivars = class_copyIvarList([XMGPerson class], &outCount);
// 遍歷所有的成員變量
for (int i = 0; i<outCount; i++) {
// 取出i位置對應的成員變量
// Ivar ivar = *(ivars + i);
Ivar ivar = ivars[i];
// 獲得成員變量的名字
NSLog(@"%s", ivar_getName(ivar));
}
// 如果函數(shù)名中包含了copy\new\retain\create等字眼,那么這個函數(shù)返回的數(shù)據(jù)就需要手動釋放
free(ivars);
// Ivar ivar = *ivars;
// Ivar ivar2 = *(ivars + 1);
// NSLog(@"%s %s", ivar_getName(ivar), ivar_getName(ivar2));
// 一個Ivar就代表一個成員變量
// int *p; 指向int類型的變量
// Ivar *ivars; 指向Ivar類型的變量
}
return 0;
}