runtime心得

近日看了一些 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下载

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值