运行时相关

作者: Tuberose | 来源:发表于2015-12-21 02:05 被阅读606次
  • 在CYPerson.h文件中
#import <Foundation/Foundation.h>


@interface CYPerson : NSObject
{
    int _money;
}
@property (nonatomic, assign, readonly) int age;
@property (nonatomic, assign) double height;
@property (nonatomic, copy) NSString *name;
@property (nonatomic, strong) NSArray *books;
@property (nonatomic, strong) id test;
@property (nonatomic, assign) CGRect rect;
@property (nonatomic, copy) void (^block)();
@property (nonatomic, assign) int *p;
@property (nonatomic, strong) CYCat *cat;
@end
  • 在CYPerson.m中
#import "CYPerson.h"

@implementation CYPerson

@end
  • 在main.m文件中
#import <Foundation/Foundation.h>
#import <objc/runtime.h>
#import "CYPerson.h"

void getIvars()
{
    unsigned int count = 0;
    Ivar *ivars = class_copyIvarList([CYPerson class], &count);
    // class_copyIvarList获取成员变量列表,它返回的是一个指针指向的是最前面的那一个
    // ([CYPerson class], &count)这里面放的是类,你把类给我,我就获取里面所有的成员变量。&count放的是成员变量的个数
    for (int i = 0; i < count; i++) {
        // for循环把所有成员变量遍历出来
        Ivar ivar = ivars[i]; // *(ivars + i)
        // 取出每一个成员变量Ivar,而且Ivar是以数组的形式去访问
        // ivars[i]的写法相当于*(ivars + i)---跳到下一个,取出这个指针指向的东西
        NSLog(@"%s %s", ivar_getName(ivar), ivar_getTypeEncoding(ivar));
       // %s是因为这里是C语言的东西
       // ivar_getName(ivar)这是去获取所有成员变量的名字,这样就挖掘到了所有成员变量
       // ivar_getTypeEncoding(ivar)表示获取一个成员变量的类型字符串
       // 而且今后如果你有要获取所有什么double,int类型的成员变量,这种方式,一打印就出来了
    }

    free(ivars);
    // 这里是通过copy出来的,所以需要释放的
}

// 这里获取所有属性,和上面一样
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        unsigned int count = 0;
        objc_property_t *properties = class_copyPropertyList([CYPerson class], &count);
        // class_copyPropertyList获取属性列表
        // class_copyProtocolList([CYPerson class], &count);是获取协议列表,这样可以获取这个类遵循的所有协议
        // class_copyMethodList([CYPerson class], &count);是获取方法列表,这样可以知道这个类遵循 那些方法
        for (int i = 0; i < count; i++) {
            objc_property_t property = properties[i];
            NSLog(@"%s   ----     %s", property_getName(property), property_getAttributes(property));
       // property_getName(property)这是去获取所有属性的名字
       // property_getAttributes(property)获取所有属性的类型
        }

        free(properties);

//        method_exchangeImplementations(<#Method m1#>, <#Method m2#>)
//        方法交换---这个方法的意义在于我可以将苹果系统内部的方法替换成我自己的方法。当你认为系统自带的方法不好用时,你就将它替换为自己的方法
    }
    return 0;
}

*


*

*


*

*


*

  • 运行时还可以动态添加成员变量方法,动态添加方法,动态添加属性,动态添加协议这四个方法
  • 假如说在代码里面你只写了如上几个属性,但是在我动态添加后,在程序运行过程中会无缘无故多一个属性出来,但是你在代码中是看不见的。这就是运行时能干的事情,相当于操纵你的内存,但是这也仅仅是运行时的冰山一角

*


*

        method_exchangeImplementations(<#Method m1#>, <#Method m2#>)
//      方法交换---这个方法的意义在于我可以将苹果系统内部的方法替换成我自己的方法。当你认为系统自带的方法不好用时,你就将它替换为自己的方法





  • RunTime
    • 1.获取内部所有属性和成员变量
    • 2.Method Swizzle--iOS黑魔法
      • 直观用途:交换方法实现
  • 在CYPerson.h文件中
#import <Foundation/Foundation.h>

@interface CYPerson : NSObject
- (void)run;
- (void)study;
@end
  • 在CYPerson.m文件中
#import "CYPerson.h"
@implementation CYPerson

- (void)run
{
    NSLog(@"%s", __func__);
}

- (void)study
{
    NSLog(@"%s", __func__);
}

@end
#import "ViewController.h"
#import "CYPerson.h"
#import <objc/runtime.h>

@interface ViewController ()

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];

        Method method1 = class_getInstanceMethod([CYPerson class], @selector(run));
        Method method2 = class_getInstanceMethod([CYPerson class], @selector(study));
        method_exchangeImplementations(method1, method2);


        CYPerson *p = [[CYPerson alloc] init];
        [p run];

        [p study];
  • 可以看出来:我调用run,它执行的study。我调用study,它执行的run

  • 那这个有啥用呢?
    • 在这里看出来是没什么卵用,但是它的价值就在于可以交换系统自带的方法
    • 比如说我可以让控制器的viewDidLoad方法换成我自己的viewDidLoad方法,让它执行的时候执行到我自己的方法中去了。
    • 下面举个例子:
      • 我想监控控制器什么时候死亡,那我就得重写dealloc方法。那么假如我们的项目中有30个或者更多的控制器,我想看它死掉没有,防止循环引用,那我是不是20个都得实现dealloc方法?那我何不再搞一个方法出来,比如说CY_dealloc:方法。你本来想执行dealloc的,现在执行到我CY_dealloc:方法里去。那我就可以在这里统一监控所有控制器的死亡
      • 下面我就做一下这个事情。为了保证所有控制器都有,我弄一个控制器的分类--UIViewController+CYExtention
#import <UIKit/UIKit.h>
#import <objc/runtime.h>

@implementation UIViewController (CYExtension)
+ (void)load
{
    Method method1 = class_getInstanceMethod(self, NSSelectorFromString(@"dealloc"));
    Method method2 = class_getInstanceMethod(self, @selector(CY_dealloc));
    method_exchangeImplementations(method1, method2);
}

- (void)CY_dealloc
{
    NSLog(@"%@ - CY_dealloc", self);
}
@end
  • 这样就让所有控制器的dealloc来到了我CY_dealloc里面,我可以统一监控所有控制器
    • 上面我没有导入头文件,是因为load方法是只要载入到内存就会调一次,这个分类文件肯定是要装入内存的,所以头文件要不要都无所谓,没什么价值。但是你的导入#import <UIKit/UIKit.h>。因为我得拿控制器的名字
  • 我在Main.storyboard中弄多一些控制器会发现:
  • 这样的话,任何一个控制器挂了我都能监控得到

  • 这里有一个小问题:
    • 我在viewController.m文件中加上dealloc
- (void)dealloc
{
    NSLog(@"-------dealloc");
}
  • 然后让上图中的第二个控制器class为我们的viewController,运行打印,会发现
  • 按理来说,一运行点击button后点击返回是应该调用dealloc方法,但是一调用它是应该来到我们的CY_dealloc方法,应该是只打印出CY_dealloc的,但是打印出来还是调用了dealloc方法。这是为什么呢?
    • 你会发现,我们开始写Method method1 = class_getInstanceMethod(self, NSSelectorFromString(@"dealloc"));的时候,开始如果写的是@selector(@"dealloc"),这是禁止的,说明这个方法ARC情况下是比较特殊的。但是前面@selector(run)和@selector(study)它们交换方法后,只打印了对方的方法,自己的方法就不会调用了。
    • 但是这里这么弄也正是我要说的效果:用我们的方法交换系统的方法后还是想办到系统的方法还是能调。因为我们有时候是想在控制器死掉的时候做一些事情。比如说:在控制器死掉的时候清�掉一些东西,或者在控制器死掉的时候做一些请求:取消通知
- (void)dealloc
{
    NSLog(@"-------dealloc");

        self.data = nil;
        self.images = nil;

        [self cancel];
}
  • 但是如果按照开始的做法,我把dealloc换成CY_dealloc,就意味着我以后调dealloc只会来到CY_dealloc,以前系统的dealloc就没法执行。但是这里上面系统的dealloc可以执行,但我告诉你如果换成了普通的方法它是不可能执行的。就行前面我执行run的时候它不可能执行study,这两个方法只会执行它对应的方法
  • 所以今后你如果做了方法的调换,不是系统的,我说的是普通的方法换了。你如果调换后还想调回以前的方法,那就做一件事情执行以下:(self.对应的方法)。只有这样它才会又调回自己以前的方法。因为你这样仅仅是做一个拦截嘛!就像下面:
- (void)CY_dealloc
{
    NSLog(@"%@ - CY_dealloc", self);

        [self CY_dealloc];
        //  这样调用的是dealloc,又调回去了
}
  • 这里的意思:当一个控制器死掉的时候,它肯定会调dealloc,但dealloc方法实现被我换掉了。所以它来到了CY_dealloc。执行完- (void)CY_dealloc
    {
    NSLog(@"%@ - CY_dealloc", self);这段代码,我再调用[self CY_dealloc];,它的方法实现是dealloc,所以又调回去了。所以今后你把系统方法换掉以后,我建议你再调回去,当然有些特殊情况就特殊处理。
  • 那这个有什么价值呢?
    • 第一个以及体现了价值:拦截系统方法调用,我就可以在系统方法调用之前或者调用之后做一些事情
    • 第二个价值举个例子:
      • 使用字典和数组的时候一直有一个头疼的问题:字典和数组赋值的时候不能为空,否则报错: object cannot be nil'
    NSString *value = nil;

    NSMutableDictionary *dict = [NSMutableDictionary dictionary];
    //    dict[@"name"] = value;

    NSMutableArray *array = [NSMutableArray array];
    [array addObject:value];
    array[0] = value;
  • 但是我们很多时候不知道别人给你传的值是不是空啊!为了不报错,我们经常都得进行判断:如果value有值,我再执行。
  • 那有没有一个办法可以过滤掉这些东西呢?只要发现传进来的值是个空的,我就不处理,那可不可以一劳永逸做到这点呢?我们只要写下某一段代码,以后凡是项目中传空的值进来,它都不报错了,怎么去做呢?
    • 只要将NSMutable--的addObject:方法换掉,拦截这个方法,鸭蛋发现传进来的值是空的我就不调,如果传进来的值不是nil,我就调回它以前的方法。那么这样的话今后数组的访问或者字典的访问就不会存在传进来值为空报错的问题了
  • 还举个例子:[UIImage imageName:我完全可以将imageName:方法拦截一下,我就可以对它传进来的图片名进行统一处理,比如说什么夜间模式,什么皮肤,我就可以根据文件名去加载另外一个文件,统一拦截所有图片

  • 但是还是不能乱换乱用,只是有些特殊功能可以做一下,乱用的话就不好了

相关文章

网友评论

本文标题:运行时相关

本文链接:https://www.haomeiwen.com/subject/zbuhhttx.html