前言:
本文主要介紹一些常用Runtime API的常用場景厘擂,用以解決初學(xué)者對于Runtime運用上的一些困惑,以便合理的將Runtime運用到項目中
*--- 歡迎指正和補充 ---*
1. 常用場景歸納
- 利用關(guān)聯(lián)對象(AssociatedObject)
給分類添加屬性
- 利用關(guān)聯(lián)對象(AssociatedObject)
- 遍歷類的所有成員變量或?qū)傩裕@取私有成員變量信息圃庭,以
修改私有屬性
(如:修改textfield的占位文字顏色)
- 遍歷類的所有成員變量或?qū)傩裕@取私有成員變量信息圃庭,以
- 遍歷類的屬性(字典轉(zhuǎn)模型吏颖、自動歸檔解檔、重寫
- description
等)
- 遍歷類的屬性(字典轉(zhuǎn)模型吏颖、自動歸檔解檔、重寫
-
- 交換方法實現(xiàn)
- 攔截系統(tǒng)方法季希,對其進行修改和補充褪那,拓展一些自己的業(yè)務(wù)邏輯(如監(jiān)聽一些事件等)
- 當(dāng)三方pod框架不滿足使用場景情況下,可以通過交換方法實現(xiàn)來達到
不修改原框架
的情況下式塌,實現(xiàn)對業(yè)務(wù)場景的支持
-
- 其它不常用使用:
- App喚醒時控制器的萬能跳轉(zhuǎn)(動態(tài)創(chuàng)建一個控制器然后博敬,傳入預(yù)定好的參數(shù)進行跳轉(zhuǎn))
- 熱點修復(fù)(先動態(tài)添加一個方法,然后替換有問題方法的實現(xiàn))
- 利用方法調(diào)用過程中的
消息轉(zhuǎn)發(fā)機制
峰尝,來優(yōu)化方法找不到的異常
問題
2. 應(yīng)用案例
?? 利用關(guān)聯(lián)對象
給TLPerson分類
添加name
和weight
屬性
- ??了解更多關(guān)于分類的知識
#import <objc/runtime.h>
@interface TLPerson (ExampleCode)
@property (copy, nonatomic) NSString *name;
@property (assign, nonatomic) int weight;
@end
@implementation TLPerson (ExampleCode)
- (void)setName:(NSString *)name
{
objc_setAssociatedObject(self, @selector(name), name, OBJC_ASSOCIATION_COPY_NONATOMIC);
}
- (NSString *)name
{
// 隱式參數(shù)
// _cmd == @selector(name)
return objc_getAssociatedObject(self, _cmd);
}
- (void)setWeight:(int)weight
{
objc_setAssociatedObject(self, @selector(weight), @(weight), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
- (int)weight
{
// _cmd == @selector(weight)
return [objc_getAssociatedObject(self, _cmd) intValue];
}
@end
?? 修改UITextView實例對象的
私有屬性
- 通過
class_copyIvarList
遍歷UITextField
的所有成員變量,
// 成員變量的數(shù)量
unsigned int count;
Ivar *ivars = class_copyIvarList([UITextField class], &count);
for (int i = 0; i < count; i++) {
// 取出i位置的成員變量
Ivar ivar = ivars[i];
NSLog(@"%s %s", ivar_getName(ivar), ivar_getTypeEncoding(ivar));
}
free(ivars);
- 可發(fā)現(xiàn)它有
_placeholderLabel
和_clearButton
兩個私有成員 - 然后可以通過
KVC
來修改其屬性
// 修改光標(biāo)顏色(placeholder)
[textField setValue:UIColorFromRGBA(0xcccdcd, 1.f) forKeyPath:@"_placeholderLabel.textColor"];
// 修改清除按鈕的圖片
UIButton *clearButton = [textField valueForKey:@"_clearButton"];
[clearButton setImage:[UIImage imageNamed:@"login_icon_clear"] forState:UIControlStateNormal];
?? 字典轉(zhuǎn)模型偏窝、自動歸檔解檔(來自
MJExtension
節(jié)選片段,僅作參考)
- 通過
class_copyPropertyList
獲取屬性列表
- 通過
+ (NSMutableArray *)properties
{
NSMutableArray *cachedProperties = [self propertyDictForKey:&MJCachedPropertiesKey][NSStringFromClass(self)];
if (cachedProperties == nil) {
MJExtensionSemaphoreCreate
MJExtensionSemaphoreWait
if (cachedProperties == nil) {
cachedProperties = [NSMutableArray array];
[self mj_enumerateClasses:^(__unsafe_unretained Class c, BOOL *stop) {
// 1.獲得所有的成員變量
unsigned int outCount = 0;
objc_property_t *properties = class_copyPropertyList(c, &outCount);
// 2.遍歷每一個成員變量
for (unsigned int i = 0; i<outCount; i++) {
MJProperty *property = [MJProperty cachedPropertyWithProperty:properties[i]];
// 過濾掉Foundation框架類里面的屬性
if ([MJFoundation isClassFromFoundation:property.srcClass]) continue;
property.srcClass = c;
[property setOriginKey:[self propertyKey:property.name] forClass:self];
[property setObjectClassInArray:[self propertyObjectClassInArray:property.name] forClass:self];
[cachedProperties addObject:property];
}
// 3.釋放內(nèi)存
free(properties);
}];
[self propertyDictForKey:&MJCachedPropertiesKey][NSStringFromClass(self)] = cachedProperties;
}
MJExtensionSemaphoreSignal
}
return cachedProperties;
}
- 遍歷properties將
字典轉(zhuǎn)為模型
- 遍歷properties將
- (instancetype)mj_setKeyValues:(id)keyValues context:(NSManagedObjectContext *)context
{
// 獲得JSON對象
keyValues = [keyValues mj_JSONObject];
MJExtensionAssertError([keyValues isKindOfClass:[NSDictionary class]], self, [self class], @"keyValues參數(shù)不是一個字典");
Class clazz = [self class];
NSArray *allowedPropertyNames = [clazz mj_totalAllowedPropertyNames];
NSArray *ignoredPropertyNames = [clazz mj_totalIgnoredPropertyNames];
//通過封裝的方法回調(diào)一個通過運行時編寫的,用于返回屬性列表的方法武学。
[clazz mj_enumerateProperties:^(MJProperty *property, BOOL *stop) {
@try {
// 0.檢測是否被忽略
if (allowedPropertyNames.count && ![allowedPropertyNames containsObject:property.name]) return;
if ([ignoredPropertyNames containsObject:property.name]) return;
// 1.取出屬性值
id value;
NSArray *propertyKeyses = [property propertyKeysForClass:clazz];
for (NSArray *propertyKeys in propertyKeyses) {
value = keyValues;
for (MJPropertyKey *propertyKey in propertyKeys) {
value = [propertyKey valueInObject:value];
}
if (value) break;
}
// 值的過濾
id newValue = [clazz mj_getNewValueFromObject:self oldValue:value property:property];
if (newValue != value) { // 有過濾后的新值
[property setValue:newValue forObject:self];
return;
}
// 如果沒有值祭往,就直接返回
if (!value || value == [NSNull null]) return;
// 2.復(fù)雜處理
MJPropertyType *type = property.type;
Class propertyClass = type.typeClass;
Class objectClass = [property objectClassInArrayForClass:[self class]];
// 不可變 -> 可變處理
if (propertyClass == [NSMutableArray class] && [value isKindOfClass:[NSArray class]]) {
value = [NSMutableArray arrayWithArray:value];
} else if (propertyClass == [NSMutableDictionary class] && [value isKindOfClass:[NSDictionary class]]) {
value = [NSMutableDictionary dictionaryWithDictionary:value];
} else if (propertyClass == [NSMutableString class] && [value isKindOfClass:[NSString class]]) {
value = [NSMutableString stringWithString:value];
} else if (propertyClass == [NSMutableData class] && [value isKindOfClass:[NSData class]]) {
value = [NSMutableData dataWithData:value];
}
if (!type.isFromFoundation && propertyClass) { // 模型屬性
value = [propertyClass mj_objectWithKeyValues:value context:context];
} else if (objectClass) {
if (objectClass == [NSURL class] && [value isKindOfClass:[NSArray class]]) {
// string array -> url array
NSMutableArray *urlArray = [NSMutableArray array];
for (NSString *string in value) {
if (![string isKindOfClass:[NSString class]]) continue;
[urlArray addObject:string.mj_url];
}
value = urlArray;
} else { // 字典數(shù)組-->模型數(shù)組
value = [objectClass mj_objectArrayWithKeyValuesArray:value context:context];
}
} else {
if (propertyClass == [NSString class]) {
if ([value isKindOfClass:[NSNumber class]]) {
// NSNumber -> NSString
value = [value description];
} else if ([value isKindOfClass:[NSURL class]]) {
// NSURL -> NSString
value = [value absoluteString];
}
} else if ([value isKindOfClass:[NSString class]]) {
if (propertyClass == [NSURL class]) {
// NSString -> NSURL
// 字符串轉(zhuǎn)碼
value = [value mj_url];
} else if (type.isNumberType) {
NSString *oldValue = value;
// NSString -> NSNumber
if (type.typeClass == [NSDecimalNumber class]) {
value = [NSDecimalNumber decimalNumberWithString:oldValue];
} else {
value = [numberFormatter_ numberFromString:oldValue];
}
// 如果是BOOL
if (type.isBoolType) {
// 字符串轉(zhuǎn)BOOL(字符串沒有charValue方法)
// 系統(tǒng)會調(diào)用字符串的charValue轉(zhuǎn)為BOOL類型
NSString *lower = [oldValue lowercaseString];
if ([lower isEqualToString:@"yes"] || [lower isEqualToString:@"true"]) {
value = @YES;
} else if ([lower isEqualToString:@"no"] || [lower isEqualToString:@"false"]) {
value = @NO;
}
}
}
} else if ([value isKindOfClass:[NSNumber class]] && propertyClass == [NSDecimalNumber class]){
// 過濾 NSDecimalNumber類型
if (![value isKindOfClass:[NSDecimalNumber class]]) {
value = [NSDecimalNumber decimalNumberWithDecimal:[((NSNumber *)value) decimalValue]];
}
}
// value和property類型不匹配
if (propertyClass && ![value isKindOfClass:propertyClass]) {
value = nil;
}
}
// 3.賦值
[property setValue:value forObject:self];
} @catch (NSException *exception) {
MJExtensionBuildError([self class], exception.reason);
MJExtensionLog(@"%@", exception);
}
}];
// 轉(zhuǎn)換完畢
if ([self respondsToSelector:@selector(mj_keyValuesDidFinishConvertingToObject)]) {
[self mj_keyValuesDidFinishConvertingToObject];
}
if ([self respondsToSelector:@selector(mj_keyValuesDidFinishConvertingToObject:)]) {
[self mj_keyValuesDidFinishConvertingToObject:keyValues];
}
return self;
}
- 歸檔與解檔
- (void)mj_encode:(NSCoder *)encoder
{
Class clazz = [self class];
NSArray *allowedCodingPropertyNames = [clazz mj_totalAllowedCodingPropertyNames];
NSArray *ignoredCodingPropertyNames = [clazz mj_totalIgnoredCodingPropertyNames];
// 遍歷歸檔
[clazz mj_enumerateProperties:^(MJProperty *property, BOOL *stop) {
// 檢測是否被忽略
if (allowedCodingPropertyNames.count && ![allowedCodingPropertyNames containsObject:property.name]) return;
if ([ignoredCodingPropertyNames containsObject:property.name]) return;
id value = [property valueForObject:self];
if (value == nil) return;
[encoder encodeObject:value forKey:property.name];
}];
}
- (void)mj_decode:(NSCoder *)decoder
{
Class clazz = [self class];
NSArray *allowedCodingPropertyNames = [clazz mj_totalAllowedCodingPropertyNames];
NSArray *ignoredCodingPropertyNames = [clazz mj_totalIgnoredCodingPropertyNames];
// 遍歷解檔
[clazz mj_enumerateProperties:^(MJProperty *property, BOOL *stop) {
// 檢測是否被忽略
if (allowedCodingPropertyNames.count && ![allowedCodingPropertyNames containsObject:property.name]) return;
if ([ignoredCodingPropertyNames containsObject:property.name]) return;
id value = [decoder decodeObjectForKey:property.name];
if (value == nil) { // 兼容以前的MJExtension版本
value = [decoder decodeObjectForKey:[@"_" stringByAppendingString:property.name]];
}
if (value == nil) return;
[property setValue:value forObject:self];
}];
}
?? 同交換方法實現(xiàn)
eg1. 給UIViewController分類中使用系統(tǒng)原生API,如:- dealloc
方法
eg2. 解決給數(shù)組添加nil時奔潰問題;
// eg1. 給UIViewController的的分類中使用`- dealloc`方法
@implementation UIViewController (TLTransition)
+ (void)load {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
Class cls = [UIViewController class];
//
// 注意直接使用:@selector(dealloc) 會報錯:ARC forbids use of 'dealloc' in a @selector
// Method method = class_getInstanceMethod(cls, @selector(dealloc));
// 使用字符串獲取NSSelectorFromString(@"dealloc")
Method method1 = class_getInstanceMethod(cls, NSSelectorFromString(@"dealloc"));
Method method2 = class_getInstanceMethod(cls, @selector(tl_dealloc));
method_exchangeImplementations(method1, method2);
});
}
- (void)tl_dealloc{
// do something
tl_Log(@"%@ %s", [self class], __func__);
[TLTransitionDelegate removeAnimatorForKey:self];
// 回到原來的dealloc方法
[self tl_dealloc];
}
@end
// eg2. 解決給數(shù)組添加nil時奔潰問題;
@implementation NSMutableArray (Extension)
+ (void)load {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
// 類簇:NSString火窒、NSArray硼补、NSDictionary,真實類型是其他類型(如:NSMutableArray 真實類型是 __NSArrayM)
Class cls = NSClassFromString(@"__NSArrayM");
Method method1 = class_getInstanceMethod(cls, @selector(insertObject:atIndex:));
Method method2 = class_getInstanceMethod(cls, @selector(swizzling__insertObject:atIndex:));
method_exchangeImplementations(method1, method2);
});
}
- (void)swizzling__insertObject:(id)anObject atIndex:(NSUInteger)index
{
if (anObject == nil) return;
[self swizzling__insertObject:anObject atIndex:index];
}
@end