iOS|iOS runtime应用整理

页面统计埋点

#import @implementation UIViewController (Stastistics) + (void)load{ static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ //页面进入 Method orginWillAppear = class_getInstanceMethod([self class], @selector(viewWillAppear:)); Method swizWillAppear = class_getInstanceMethod([self class], @selector(stastistics_viewWillAppear:)); bool isAddWillAppear = class_addMethod([self class], @selector(viewWillAppear:), method_getImplementation(swizWillAppear), method_getTypeEncoding(swizWillAppear)); if (isAddWillAppear) { class_replaceMethod([self class], @selector(stastistics_viewWillAppear:), method_getImplementation(orginWillAppear), method_getTypeEncoding(orginWillAppear)); } else { method_exchangeImplementations(orginWillAppear, swizWillAppear); } //页面离开 Method orginWillDisAppear = class_getInstanceMethod([self class], @selector(viewWillDisappear:)); Method swizWillDisAppear = class_getInstanceMethod([self class], @selector(stastistics_viewWillDisappear:)); bool isAddWillDisAppear = class_addMethod([self class], @selector(viewWillDisappear:), method_getImplementation(swizWillDisAppear), method_getTypeEncoding(swizWillDisAppear)); if (isAddWillDisAppear) { class_replaceMethod([self class], @selector(stastistics_viewWillDisappear:), method_getImplementation(orginWillDisAppear), method_getTypeEncoding(orginWillDisAppear)); } else { method_exchangeImplementations(orginWillDisAppear, swizWillDisAppear); } }); } - (void)stastistics_viewWillAppear:(BOOL)animated{ //执行页面进入埋点 [self stastistics_viewWillAppear:animated]; } - (void)stastistics_viewWillDisappear:(BOOL)animated{ //执行页面离开埋点 [self stastistics_viewWillDisappear:animated]; }

  • Swizzling应该总在+load中执行
    Objective-C在运行时会自动调用类的两个方法+load和+initialize。+load会在类初始加载时调用, +initialize方法是以懒加载的方式被调用的,如果程序一直没有给某个类或它的子类发送消息,那么这个类的 +initialize方法是永远不会被调用的。
  • Swizzling应该总是在dispatch_once中执行以及在+load中执行时,不应调用[super load]
    Swizzling会改变全局状态,所以在运行时采取一些预防措施,使用dispatch_once就能够确保代码不管有多少线程都只被执行一次以及调用父类可能造成重复调用
实现数组越界异常保护
#import "objc/runtime.h" @implementation NSArray (Swizzling) + (void)load { Method fromMethod = class_getInstanceMethod(objc_getClass("__NSArrayI"), @selector(objectAtIndex:)); Method toMethod = class_getInstanceMethod(objc_getClass("__NSArrayI"), @selector(swizzling_objectAtIndex:)); method_exchangeImplementations(fromMethod, toMethod); } - (id)swizzling_objectAtIndex:(NSUInteger)index { if (self.count-1 < index) { // 异常处理 @try { return [self swizzling_objectAtIndex:index]; } @catch (NSException *exception) { // 打印崩溃信息 NSLog(@"---------- %s Crash Because Method %s----------\n", class_getName(self.class), __func__); NSLog(@"%@", [exception callStackSymbols]); return nil; } @finally {} } else { return [self swizzling_objectAtIndex:index]; } }

  • NSArray其实在Runtime中对应着__NSArrayI,NSMutableArray对应着__NSArrayM,NSDictionary对应着__NSDictionaryI,NSMutableDictionary对应着__NSDictionaryM。
  • 对NSArray增加分类实现异常保护后,NSMutableArray仍然会出现数组越界。
NSCoding的自动归档和自动解档
#import @implementation NSObject (Extension)- (void)decode:(NSCoder *)aDecoder { // 一层层父类往上查找,对父类的属性执行归解档方法 Class c = self.class; while (c &&c != [NSObject class]) {unsigned int outCount = 0; Ivar *ivars = class_copyIvarList(c, &outCount); for (int i = 0; i < outCount; i++) { Ivar ivar = ivars[i]; NSString *key = [NSString stringWithUTF8String:ivar_getName(ivar)]; // 如果有实现该方法再去调用 if ([self respondsToSelector:@selector(ignoredNames)]) { if ([[self ignoredNames] containsObject:key]) continue; }id value = https://www.it610.com/article/[aDecoder decodeObjectForKey:key]; [self setValue:value forKey:key]; } free(ivars); c = [c superclass]; }}- (void)encode:(NSCoder *)aCoder { // 一层层父类往上查找,对父类的属性执行归解档方法 Class c = self.class; while (c &&c != [NSObject class]) {unsigned int outCount = 0; 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)]; // 如果有实现该方法再去调用 if ([self respondsToSelector:@selector(ignoredNames)]) { if ([[self ignoredNames] containsObject:key]) continue; }id value = [self valueForKeyPath:key]; [aCoder encodeObject:value forKey:key]; } free(ivars); c = [c superclass]; } }

在需要归解档的对象中实现下面方法即可
// 设置需要忽略的属性 - (NSArray *)ignoredNames { return @[@"bone"]; }// 在系统方法内来调用我们的方法 - (instancetype)initWithCoder:(NSCoder *)aDecoder { if (self = [super init]) { [self decode:aDecoder]; } return self; }- (void)encodeWithCoder:(NSCoder *)aCoder { [self encode:aCoder]; }

利用runtime 进行字典转模型
@implementation NSObject (JSONExtension)- (void)setDict:(NSDictionary *)dict {Class c = self.class; while (c &&c != [NSObject class]) {unsigned int outCount = 0; Ivar *ivars = class_copyIvarList(c, &outCount); for (int i = 0; i < outCount; i++) { Ivar ivar = ivars[i]; NSString *key = [NSString stringWithUTF8String:ivar_getName(ivar)]; // 成员变量名转为属性名(去掉下划线 _ ) key = [key substringFromIndex:1]; // 取出字典的值 id value = https://www.it610.com/article/dict[key]; // 如果模型属性数量大于字典键值对数理,模型属性会被赋值为nil而报错 if (value == nil) continue; // 获得成员变量的类型 NSString *type = [NSString stringWithUTF8String:ivar_getTypeEncoding(ivar)]; // 如果属性是对象类型 NSRange range = [type rangeOfString:@"@"]; if (range.location != NSNotFound) { // 那么截取对象的名字(比如@"Dog",截取为Dog) type = [type substringWithRange:NSMakeRange(2, type.length - 3)]; // 排除系统的对象类型 if (![type hasPrefix:@"NS"]) { // 将对象名转换为对象的类型,将新的对象字典转模型(递归) Class class = NSClassFromString(type); value = https://www.it610.com/article/[class objectWithDict:value]; }else if ([type isEqualToString:@"NSArray"]) {// 如果是数组类型,将数组中的每个模型进行字典转模型,先创建一个临时数组存放模型 NSArray *array = (NSArray *)value; NSMutableArray *mArray = [NSMutableArray array]; // 获取到每个模型的类型 id class ; if ([self respondsToSelector:@selector(arrayObjectClass)]) {NSString *classStr = [self arrayObjectClass]; class = NSClassFromString(classStr); } // 将数组中的所有模型进行字典转模型 for (int i = 0; i < array.count; i++) { [mArray addObject:[class objectWithDict:value[i]]]; }value = https://www.it610.com/article/mArray; } }// 将字典中的值设置到模型上 [self setValue:value forKeyPath:key]; } free(ivars); c = [c superclass]; } }+ (instancetype )objectWithDict:(NSDictionary *)dict { NSObject *obj = [[self alloc]init]; [obj setDict:dict]; return obj; }

Associated Object 关联对象
static const char imgIdKey ; @implementation UIImage (Category) - (void)setImgId:(NSString *)imgId{ objc_setAssociatedObject(self, &imgIdKey, imgId, OBJC_ASSOCIATION_COPY_NONATOMIC); } - (NSString *)imgId{ return objc_getAssociatedObject(self, &imgIdKey); }

  • const void *key 区分不同的关联对象的 key。有3种写法。
【iOS|iOS runtime应用整理】使用 &AssociatedObjectKey 作为key值
static char AssociatedObjectKey = "AssociatedKey";

使用AssociatedKey 作为key值
static const void *AssociatedKey = "AssociatedKey";

使用@selector
@selector(associatedKey)

    推荐阅读