runtime心得

来源:互联网 发布:淘宝花呗分期扣全款 编辑:程序博客网 时间:2024/05/19 03:17

近日看了一些 runtime 的资料,结合之前对 runtime 的认识,理解又深了一些。runtime 的使用网上资料很多,不再赘述,此处只分享一些我学习和使用过程中遇到的点。

 一:成员变量包括实例变量和类变量,因为 iOS中无真正的类变量,所以,成员变量都是(就是)实例变量。

首先我想大家先搞懂一些基础知识,因为看 runtime 博客的时候会经常看到实例变量和成员变量,如果不清楚两者的关系,有可能会搞混淆,对于相关知识的理解就会很吃力。
java 里面默认情况下,成员变量是实例成员,在外部需要通过对象才能操作。如果用static修饰,就成为了静态成员,也称为类变量,无需通过对象就可以操作。(成员变量包括实例变量和类变量)
对于Object-C ,我查了相关资料说OC不支持真正的类变量。所以应该可以理解为 iOS 中成员变量都是实例变量。

好,接下来咱们聊 runtime。

 二:一旦完成类定义,就不能再添加成员变量了

class_addProperty方法添加的属性,系统不会自动添加实例变量,即不会在class_copyIvarList内获取到该属性的成员变量。有别于在类定义时category 里@property 修饰,系统也不会自动添加实例变量)直接用@property 修饰

在Objective-C提供的runtime函数中,确实有一个class_addIvar()函数用于给类添加成员变量,但是文档中特别说明:
This function may only be called after objc_allocateClassPair and before objc_registerClassPair. Adding an instance variable to an existing class is not supported.
意思是说,这个函数只能在“构建一个类的过程中”调用。一旦完成类定义,就不能再添加成员变量了。经过编译的类在程序启动后就被runtime加载,没有机会调用addIvar。程序在运行时动态构建的类需要在调用objc_registerClassPair之后才可以被使用,同样没有机会再添加成员变量。

以下是代码验证:

.h文件

@interface SomeClass : NSObject {    NSString *age;    NSInteger weight;}@property (nonatomic, copy) NSString *aaa;@end

.m 文件

@implementation SomeClass-(id)init {       self = [super init];    //添加属性    [self addPropertyWithPropertyName:@"ccc" withValue:@"1234"];    unsigned int count1 = 0;    //获取属性列表    objc_property_t *pros = class_copyPropertyList([SomeClass class], &count1);    for (int i = 0; i < count1; i++) {        objc_property_t pro = pros[i];        NSString *proName = [NSString stringWithCString:property_getName(pro) encoding:NSUTF8StringEncoding];        //***打印1***        NSLog(@"proName:%@\n",proName);    }    free(pros);    unsigned int count = 0;    //获取成员变量列表    Ivar *members = class_copyIvarList([SomeClass class], &count);    for (int i = 0; i < count;  i++) {        Ivar ivar = members[i];        NSString *ivarName = [NSString stringWithCString:ivar_getName(ivar) encoding:NSUTF8StringEncoding];        //***打印2***        NSLog(@"ivarName:%@\n",ivarName);    }    free(members);    return self;}-(void)addPropertyWithPropertyName:(NSString *)propertyName withValue:(id)value{    objc_property_attribute_t type = { "T", [[NSString stringWithFormat:@"@\"%@\"",NSStringFromClass([value class])] UTF8String] };    objc_property_attribute_t ownership = { "&", "N" };    objc_property_attribute_t backingivar  = { "V", [[NSString stringWithFormat:@"_%@", propertyName] UTF8String] };    objc_property_attribute_t attrs[] = { type, ownership, backingivar };    if (class_addProperty([self class], [propertyName UTF8String], attrs, 3)) {        //赋值        [self setValue:value forKey:propertyName];        NSLog(@"%@", [self valueForKey:propertyName]);        NSLog(@"创建属性Property成功");    }}

打印1:
2017-02-15 18:21:43.934774 SomeThingTest[2715:953599] proName:ccc
2017-02-15 18:21:43.934788 SomeThingTest[2715:953599] proName:aaa
此处打印的是.h 里的aaa,运行时添加的属性ccc

打印2:
2017-02-15 18:21:43.934876 SomeThingTest[2715:953599] ivarName:age
2017-02-15 18:21:43.934897 SomeThingTest[2715:953599] ivarName:weight
2017-02-15 18:21:43.934910 SomeThingTest[2715:953599] ivarName:_aaa
此处打印的是.h 里的实例变量 age,weight 以及@property 修饰的 aaa,系统自动生成的实例变量_aaa.

 三:Method Swizzling 实现友盟页面统计。我把下载链接放到下面了

应该大多数应用都有页面统计的要求。大多实现,1.最冗余的做法就是每个 VC都写上相关代码。2.好点的写个 Base 类,在该类中写上统计的相关代码,开发中让其他 VC 继承自改 Base类。
做法2肯定比做法1简洁了很多,但其实并不好维护, 比如项目进来个新人,或者交接时必须告知new 新的 ViewController时继承该 Base 类,而且有些同事要写一个 tableView 的界面,可能他直接创建了一个 继承自UITableViewController的 VC…
所以这个时候就该 Method Swizzling出场了,当然也许还有更好的方法。此处用 swizzling的原理就是用一个新的方法的实现和系统方法的实现作交换,这个新方法的实现包括系统方法的实现,同时还注入了一些自定义的行为,自定义行为就是我们想要做的操作。
首先 new 一个 category,UIViewController 的分类,此处我命名为UIViewController+Swizzling。

+ (void)load 方法是在运行期提前并且自动调用的方法。我们可以利用他们在类被使用前,做一些预处理工作。父类,子类,分类的 load 方法依次调用,在子类或者分类重写 load 方法不用调用[super load]; 在category 里除了+ (void)load 方法,重写类的同名方法会覆盖该类这个方法。使用dispatch_once保证 method swizzling 只发生一次,避免手动调用+ (void)load交换方法实现后还原到最初状态。
+ (void)load {    static dispatch_once_t onceToken;    dispatch_once(&onceToken, ^{        [self exchangeViewWillAppear ];        [self exchangeViewWillDisappear];    });}//交换 viewWillAppear+ (void)exchangeViewWillAppear{    // 通过class_getInstanceMethod()函数从当前对象中的method list获取method结构体,如果是类方法就使用class_getClassMethod()函数获取。    Method fromMethod = class_getInstanceMethod([self class], @selector(viewWillAppear:));    Method toMethod = class_getInstanceMethod([self class], @selector(swizzlingViewWillAppear));    /**     *  我们在这里使用class_addMethod()函数对Method Swizzling做了一层验证,如果self没有实现被交换的方法,会导致失败。     *  而且self没有交换的方法实现,但是父类有这个方法,这样就会调用父类的方法,结果就不是我们想要的结果了。     *  所以我们在这里通过class_addMethod()的验证,如果self实现了这个方法,class_addMethod()函数将会返回NO,我们就可以对其进行交换了。     */    if (!class_addMethod([self class], @selector(swizzlingViewWillAppear), method_getImplementation(toMethod), method_getTypeEncoding(toMethod))) {        method_exchangeImplementations(fromMethod, toMethod);    }}//交换 viewWillDisappear+ (void)exchangeViewWillDisappear{    Method fromMethod = class_getInstanceMethod([self class], @selector(viewWillDisappear:));    Method toMethod = class_getInstanceMethod([self class], @selector(swizzlingViewWillDisappear));    if (!class_addMethod([self class], @selector(swizzlingViewWillAppear), method_getImplementation(toMethod), method_getTypeEncoding(toMethod))) {        method_exchangeImplementations(fromMethod, toMethod);    }}/* 我们自己实现的用来替换viewWillAppear的方法 */- (void)swizzlingViewWillAppear {    NSString *class = NSStringFromClass(self.class);//[NSString stringWithFormat:@"%@", self.class];    // 我们在这里加一个判断,将系统的UIViewController的对象剔除掉    if(![class containsString:@"UI"]){        NSLog(@"统计打点-beginLogPageView : %@", self.class);        [MobClick beginLogPageView:class];    }    //此处调的方法内部实现已替换成 viewWillAppear 方法    [self swizzlingViewWillAppear];}/* 我们自己实现的用来替换viewWillDisappear的方法 */- (void)swizzlingViewWillDisappear {    NSString *class = NSStringFromClass(self.class);//[NSString stringWithFormat:@"%@", self.class];    // 我们在这里加一个判断,将系统的UIViewController的对象剔除掉    if(![class containsString:@"UI"]){        NSLog(@"统计打点-endLogPageView : %@", self.class);        [MobClick endLogPageView:class];    }    //此处调的方法内部实现已替换成 viewWillDisappear 方法    [self swizzlingViewWillDisappear];}

UIViewController+Swizzling下载

0 0