ios RunTime运用

来源:互联网 发布:淘宝数据魔方有用吗 编辑:程序博客网 时间:2024/04/29 22:17

一、什么是Runtime?

Runtime简称运行时,系统在运行的时候的一些机制,其中最主要的是消息机制

OC是一门动态语言,所以它总是想办法把一些决定工作从编译连接推迟到运行时。也就是说只有编译器是不够的,还需要一个运行时系统 (runtime system) 来执行编译后的代码。这就是 Objective-C Runtime 系统存在的意义,它是整个Objc运行框架的一块基石。

例如: [receiver message] 这句话 的意思是:向receiver 发送message消息。
这行代码会被编译器转换成objc_msgSend(receiver,@selector(message))。
如果包含参数,则编译成objc_msgSend(receiver,@selector(_message),arg1,arg2…..)

如果receiver能够找到对应的message,那么就直接执行message这个方法,否则,消息要么被转发,要么被临时向receiver动态添加message对应的实现内容,要么就直接crash。
[receiver message] 可不是简单的方法调用哦,编译阶段只是确定向receiver 发送message这条消息,而对于receiver将要如何响应这条消息,那就要看运行时发生的情况来决定了。

二、Runtime有什么作用?

总结到工程中运用到的:
( 1 ) 动态变量控制
( 2 ) 动态添加变量
( 3 ) 动态添加图层
( 4 ) 动态添加方法
( 5 ) 交换方法(修改IMP指向)
( 6 ) 字典转模型
( 7 ) 监听键盘上的删除键方法(跟第五条是一致的)

1、动态变量控制(获取对象成员属性,包括私有,并修改其值)

创建一个Person类,继承NSObject,并公布两个成员属性name、height,私有变量age, 并声明方法- (void)_Postmessage;

- (void)_Postmessage{    NSLog(@"my age is :%@",self.age);}

利用Runtime对其属性值进行修改

unsigned   int count = 0;Person *jack = [[Person alloc]init];jack.name = @"json";jack.height = @"190";// 获取属性列表Ivar *ivar = class_copyIvarList([jack class], &count);// 遍历for (int i = 0; i < count; i++) {  Ivar var = ivar[i]; const char *varname = ivar_getName(var); // 获取变量名(包括私有变量) NSString *name = [NSString stringWithUTF8String:varname];  if ([name isEqualToString:@"_name"]) {     object_setIvar(jack, var, @"rose");  }else if ([name isEqualToString:@"_height"]){     object_setIvar(jack, var, @"150");  }else if ([name isEqualToString:@"_age"]){     object_setIvar(jack, var, @"22");  }  } NSLog(@"%@--%@",jack.name,jack.height);  //调用方法 [jack _Postmessage];

输出结果

2016-04-19 15:55:05.013 RunTime[1246:49002] _name2016-04-19 15:55:05.015 RunTime[1246:49002] _height2016-04-19 15:55:05.015 RunTime[1246:49002] _age2016-04-19 15:55:05.015 RunTime[1246:49002] rose--1502016-04-19 15:55:05.016 RunTime[1246:49002] my age is :22

2、动态添加属性(Category)

如何为category增加属性?例如,我们为Person写一个类别,然后在类别中增加一个属性chineseName。
在.h文件

#import "Person.h"@interface Person (AddMutipleProprety)@property (nonatomic,copy) NSString *chineseName;@end

在.m文件

char cName;- (void)setChineseName:(NSString *)chineseName{    objc_setAssociatedObject(self, &cName, chineseName, OBJC_ASSOCIATION_COPY_NONATOMIC);}//set方法- (NSString*)chineseName{    return  objc_getAssociatedObject(self, &cName);}// get 方法

调用

jack.chineseName = @"杰克";NSLog(@"%@",jack.chineseName);

注意: set方法
object 参数作为待扩展的对象实例,
key作为该对象实例的属性的键,
而value就是对象实例的属性的值,
policy作为关联的策略。

最后一个参数,涉及到引用计数的问题。

 OBJC_ASSOCIATION_ASSIGN = 0,           OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1,  OBJC_ASSOCIATION_COPY_NONATOMIC = 3,   OBJC_ASSOCIATION_RETAIN = 01401,      OBJC_ASSOCIATION_COPY = 01403 

3、仿QQ空间滑动表导航栏图层变化(动态添加图层)

思路: 为navigationbar动态添加图层,根据偏移量设置图层的背景颜色,并同时设置navigationbar上的leftitem、titleview、rightItem跟随变化。

首先,为UINavigationController添加一个类别,并在.h文件声明如下方法:

// 导航栏背景(动态添加图层)颜色- (void)lt_setBackgroundColor:(UIColor *)backgroundColor;// 导航栏leftItem、titleview、rightItem 跟随动态添加图层颜色变化- (void)lt_setElementsAlpha:(CGFloat)alpha;//  还原- (void)lt_reset;

在实现.m文件文件里面,采用runtime机制动态添加图层,和分别实现声明的方法

static char overlayKey;// 动态添加图层- (UIView *)overlay{    return objc_getAssociatedObject(self, &overlayKey);}//get方法- (void)setOverlay:(UIView *)overlay{    objc_setAssociatedObject(self, &overlayKey, overlay, OBJC_ASSOCIATION_RETAIN_NONATOMIC);}// set方法// 设置成员属性(背景颜色)- (void)lt_setBackgroundColor:(UIColor *)backgroundColor{if (!self.overlay) {[self setBackgroundImage:[UIImage new] forBarMetrics:UIBarMetricsDefault];self.overlay = [[UIView alloc] initWithFrame:CGRectMake(0, -20, [UIScreen mainScreen].bounds.size.width, CGRectGetHeight(self.bounds) + 20)]; self.overlay.userInteractionEnabled = NO; self.overlay.autoresizingMask = UIViewAutoresizingFlexibleWidth|UIViewAutoresizingFlexibleHeight;[self insertSubview:self.overlay atIndex:0];}self.overlay.backgroundColor = backgroundColor;}// 导航栏 的左、右、中间 视图 跟着变化- (void)lt_setElementsAlpha:(CGFloat)alpha{[[self valueForKey:@"_leftViews"] enumerateObjectsUsingBlock:^(UIView *view, NSUInteger i, BOOL *stop) {        view.alpha = alpha;    }];[[self valueForKey:@"_rightViews"] enumerateObjectsUsingBlock:^(UIView *view, NSUInteger i, BOOL *stop) {        view.alpha = alpha;    }];UIView *titleView = [self valueForKey:@"_titleView"];titleView.alpha = alpha;}// 重置- (void)lt_reset{ [self setBackgroundImage:nil forBarMetrics:UIBarMetricsDefault];[self.overlay removeFromSuperview];self.overlay = nil;}

tableview继承scrollview,所以自然继承如下方法

- (void)scrollViewDidScroll:(UIScrollView *)scrollView{    CGFloat offsetY = scrollView.contentOffset.y;    CGFloat alpha = 0;    if (offsetY > 100) {        //取最小值 (0和1之间)        alpha = MIN(1, 1 - ((100 + 64 - offsetY) / 64));    }    // block    if (self.isTransent) {        self.isTransent(alpha);    }    [super scrollViewDidScroll:scrollView];}

监听偏移量

tableView.isTransent = ^(CGFloat alpahValue){UIColor *color = [UIColor colorWithRed:245/255.0 green:106/255.0 blue:48/255.0 alpha:1.0];// (你要渐变的颜色)[weakSelf.navigationController.navigationBar lt_setBackgroundColor:[color colorWithAlphaComponent:alpahValue]];};

移除该动态添加图层,可以在视图消失的时候移除

- (void)viewDidAppear:(BOOL)animated{ [self.navigationController.navigationBar lt_reset]; [super viewDidAppear:animated];}

demo地址:http://download.csdn.net/detail/qq_18505715/9498336

4、动态添加方法
在程序当中,假设某对象中没有message这个方法,后来被Runtime添加一个名字叫message的方法,最终再调用message方法做出相应。
4.1、 添加方法

class_addMethod([self class], @selector(message), (IMP)postMessage, "v@:");

说明:
(1)IMP 是函数指针,当你向一个对象发送消息的时候,最终他会执行的那段代码,就是由这个函数指针指定的。换句话说,IMP这个函数指针指向这个方法的实现。
(2)
v@:” 意思是,v代表无返回值void,如果是i则代表返回值是int类型;
@ 代表 id sel;
: 代表 SEL _cmd
v@:@@” 意思是,两个参数的没有返回值.

4.2、调用动态添加方法

[self performSelector:@selector(message)];

4.3、函数指针IMP所指message具体实现

void postMessage(id self,SEL _cmd){    NSLog(@" receive message !");}

说明 :
( 1 ) void的前面没有+、-号,因为只是C的代码
( 2 ) 必须有两个指定参数(id self,SEL _cmd)

5、动态交换方法

动态交换方法,其意思是交换两个方法的实现内容。
例如: 方法method1对应实现内容为content1,方法method2对应的实现内容为content2。当动态交换后,method1的实现内容为content2,metnod2的实现内容为contnent1。实质还是IMP的指向变化。

在Person头文件中声明两个方法

- (void) first;- (void) second;

对应两个方法的实现内容

- (void)first{    NSLog(@"first");}- (void)second{       NSLog(@"second");}

方法调用

Person *jack = [[Person alloc]init];Method m1 = class_getInstanceMethod([jack class], @selector(first));Method m2 = class_getInstanceMethod([jack class], @selector(second));method_exchangeImplementations(m1, m2);/*    本来应该输出first,    方法的具体实现交换后,    first的实现方法内容已经被替换成second的实现内容    所以输出second */ [jack first];

6、字典转模型(BaseModel)
在工程中,我们经常需要建立model,在其.h文件中用合成存取器声明我们需要的字段。然后在请求数据成功后,用一些第三方(例如:JSonModel、MJExtension等)进行解析,传入字典,批量解析。当然,我们也可以自己用runtime实现该效果。 拿到属性数组,利用KVC赋值。

6.1、属性数组

+ (NSArray *)propertList {    unsigned int count = 0;    //获取模型属性, 返回值是所有属性的数组    objc_property_t *list = class_copyPropertyList([self class], &count);    NSMutableArray *arr = [NSMutableArray array];    //便利数组    for (int i = 0; i< count; i++) {        //获取属性        objc_property_t property = list[i];        //获取属性名称        const char *cName = property_getName(property);        NSString *name = [[NSString alloc]initWithUTF8String:cName];        //添加到数组中        [arr addObject:name];    }    //释放属性组    free(list);    return arr.copy;}//获取属性数组

6.2 、利用KVC进行赋值

+ (id)initWithDictionary:(NSDictionary*)dict{    id obj = [[self alloc]init];    NSArray *properties = [self propertList];    for (NSString *key in properties) {        if (dict[key] != nil) {            [obj setValue:dict[key] forKeyPath:key];        }    }    return obj;}// 通过KVC 给属性赋值

运用: 之后所有的model都继承basemodel

 for (id  value in data[@"data"]) {         MainModel *model  =  [MainModel initWithDictionary:value]; NSLog(@"%@",model.myServiceManager);                           [array addObject:model];}

参考demo : http://download.csdn.net/detail/qq_18505715/9501957

7 、监听键盘上的删除键方法
需求是这样的,监听textfield或则textview,当length为空,继续点击键盘上删除文本键,直接删除单行或者多行文本控件。
为textview和textfield写个扩展。deleteBackward 交换实现方法。
demo地址: http://download.csdn.net/detail/qq_18505715/9627747

扩展 : Runtime其实有两个版本:“modern”和 “legacy”。我们现在用的 Objective-C 2.0 采用的是现行(Modern)版的Runtime系统,只能运行在 iOS 和 OS X 10.5 之后的64位程序中。而OS X较老的32位程序仍采用 Objective-C 1中的(早期)Legacy 版本的 Runtime 系统。这两个版本最大的区别在于当你更改一个类的实例变量的布局时,在早期版本中你需要重新编译它的子类,而现行版就不需要。

0 0
原创粉丝点击