KVC-->Key-Value Coding: 键值编码 (KVC)
原理:KVC运用了一个isa-swizzling技术. isa-swizzling就是类型混合指针机制, 将2个对象的isa指针互相调换, 就是俗称的黑魔法.
KVC主要通过isa-swizzling, 来实现其内部查找定位的. 默认的实现方法由NSOject提供
isa指针, 如其名称所指,(就是is a kind of的意思), 指向分发表对象的类. 该分发表实际上包含了指向实现类中的方法的指针, 和其它数据。
KVC是一种非正式的Protocol,提供一种机制来间接访问对象的属性
获取值方式:
1.valueForKey: 传入NSString属性的名字。
2.valueForKeyPath: 属性的路径,xx.xx
3.valueForUndefinedKey 默认实现是抛出异常,可重写这个函数做错误处理
修改值方式:
1.setValue:forKey:
2.setValue:forKeyPath:
3.setValue:forUnderfinedKey:
4.setNilValueForKey: 对非类对象属性设置nil时调用,默认抛出异常。
举例:Person对象有2个属性 name(NSSting),age(NSInteger) KVC赋值代码如下
Person *person = [[Person alloc]init]; //KVC进行赋值
[person setValue:[NSNumber numberWithInteger:18] forKey:@"age"];
[person setValue:@"Jany" forKey:@"name"];
KVC字典转模型赋值
//KVC 字典转模型 字典要和model中的属性一一对应 为防止闪退报错重写model中setValue: forUndefinedKey: 方法
NSDictionary *dict = @{@"name":@"Jenny",@"age":@26};
[person setValuesForKeysWithDictionary:dict];
KVO--> Key-Value Observing 键值观察(KVO), KVO是观察者模式的一种应用
原理:通过对某个对象的某个属性添加观察者,若该属性值改变,就会调用observeValueForKeyPath:方法
给上面的Person对象person 添加观察者 观察name属性变化 代码如下
//KVO 添加观察者 观察person对象中属性name的值改变
[person addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionOld|NSKeyValueObservingOptionNew context:nil];
观察者监听到值改变了回调方法
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary
*)change context:(void *)context { NSLog(@"keyPath: %@", keyPath);
NSLog(@"object: %@", object);
NSLog(@"change: %@", change);
NSLog(@"context: %@", context);
}
全代码如下
#import "Person.h"
#import
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
Person *person = [[Person alloc]init];
//KVO 添加观察者 观察person对象中属性name的值改变
[person addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionOld|NSKeyValueObservingOptionNew context:nil];
//KVC进行赋值
[person setValue:[NSNumber numberWithInteger:18] forKey:@"age"];
[person setValue:@"Jany" forKey:@"name"];
//KVC 字典转模型 字典要和model中的属性一一对应 为防止闪退报错重写model中setValue:(id)value forUndefinedKey: 方法
NSDictionary *dict = @{@"name":@"Jenny",@"age":@26};
[person setValuesForKeysWithDictionary:dict];
NSLog(@"%zd",person.age);
}
// 观察者监听到之后回调方法
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary
*)change context:(void *)context { NSLog(@"keyPath: %@", keyPath);
NSLog(@"object: %@", object);
NSLog(@"change: %@", change);
NSLog(@"context: %@", context);
}
KVO使用场景:
1、下拉刷新、下拉加载监听UIScrollerView的ContentOffset方法
2、webView混排监听contentsize
3、监听模型属性实时更新UI
4、监听控制器frame改变,实现抽屉效果
RunTime
简介:RunTime简称运行时。OC就是运行时机制,也就是在运行时候的一些机制,其中最主要的是消息机制。
作用:
1.发送消息。 objc_msgSend只有对象才能发送消息,因此以objc开头。使用消息机制前提必须导入#import
举例:Person中声明两个方法-(void)eat; +(void)eat;
调用对象方法
[person eat];
本质让对象发送消息 objc_msgSend(person,@selector(eat));
调用类方法:两种方式
1.[Person eat];
2.[[Person class] eat];
本质让类对象发送消息 objc_msgSend([Person class],@selector(eat));
2.交换方法
开发使用场景:系统自带的方法功能不够,给系统自带的方法扩展一些功能,并保持原有的功能。
方式:
1.集成系统的类,重写方法
2.使用runtime,交换方法
举例:创建UIImage类别Image
.m中实现方法交换代码
#import
@implementation UIImage (Image)
+ (void)load{
//获取imageWithName方法地址
Method imageWithName = class_getClassMethod(self, @selector(imageWithName:));
//获取imageName方法地址
Method imageName = class_getClassMethod(self, @selector(imageNamed:));
method_exchangeImplementations(imageWithName, imageName);
}
// 不能在分类中重写系统方法imageNamed,因为会把系统的功能给覆盖掉,而且分类中不能调用super.
// 既能加载图片又能打印
+ (instancetype)imageWithName:(NSString *)name
{
// 这里调用imageWithName,相当于调用imageName
UIImage *image = [self imageWithName:name];
if (image == nil) {
NSLog(@"加载空的图片");
}
return image;
}
实现:
[UIImage imageNamed:@"123"]; //方法交换imageNamed-->imageWithName
3.给分类添加属性
说明:可以为已有的类添加方法,但是却不能直接添加属性,因为即使你添加了@property,它既不会生成实例变量,也不会生成setter、getter方法,即使你添加了也无法使用。所以我们首先需要自己去添加setter、getter方法,这个好办,直接在.m文件里加就可以了,但是要真正添加可以使用的属性,还需要利用Runtime来关联对象
原理:给一个类声明属性,其实本质就是给这个类添加关联,并不是直接把这个值的内存空间添加到类存空间。
实现:创建一个NSObject类别 名为Property,添加属性为name @property (nonatomic,strong)NSString *name;
@interface NSObject (Property)
@property (nonatomic,strong)NSString *name;
@end
#import
// 定义关联的key
static const char *key = "name";
@implementation NSObject (Property)
- (NSString *)name{
// 根据关联的key,获取关联的值。
return objc_getAssociatedObject(self, key);
}
- (void)setName:(NSString *)name
{
// 第一个参数:给哪个对象添加关联
// 第二个参数:关联的key,通过这个key获取
// 第三个参数:关联的value
// 第四个参数:关联的策略
objc_setAssociatedObject(self, key, name, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
调用:
//runtime给NSObject对象添加属性name(通过类别property)
NSObject *objc = [[NSObject alloc]init];
objc.name = @"Jany";
NSLog(@"%@",objc.name);
RunTime还有些其他的功能
4.动态添加方法
5.字典转模型
RunLoop
说明:
一般来讲,一个线程一次只能执行一个任务,执行完成后线程就会退出。如果我们需要一个机制,让线程能随时处理事件但并不退出.
基本作用:
1.保持程序的持续运行
2.处理App中的各种事件(比如触摸事件、定时器事件、Selector事件)
3.节约CPU资源,提高程序性能:该做事时做事,该休息时休息
. . . . . .
main函数中的RunLoop
int main(int argc, char * argv[]) {
@autoreleasepool {
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}
}
UIApplicationMain函数内部就启动了一个RunLoop,所以UIApplicationMain函数一直没有返回,保持了程序的持续运行,这个默认启动的RunLoop是跟主线程相关联的
RunLoop与线程
1.每条线程都有唯一的一个与之对应的RunLoop对象
2.主线程的RunLoop已经自动创建好了,子线程的RunLoop需要主动去创建
3.RunLoop在第一次获取时创建,在线程结束时销毁
获得RunLoop对象
1.Foundation
NSRunLoop *mainRunLoop = [NSRunLoop mainRunLoop];//获得主线程的RunLoop对象
NSRunLoop *currentRunLoop = [NSRunLoop currentRunLoop];//获得当前线程的RunLoop对象
2.Core Foundation
CFRunLoopGetMain()
CFRunLoopGetCurrent()
NSRunLoop使用须知
1.NSLog(@"%@",[NSRunLoop currentRunLoop]);打印当前线程的RunLoop,懒加载模式,一条线程对应一个RunLoop对象,有返回,没有创建,主线程的RunLoop默认创建,子线程的RunLoop需要手动创建,[NSRunLoop currentRunLoop],同一个线程中若是创建多个RunLoop,则返回的都是同一个RunLoop对象,一个RunLoop里会有多个mode运行模式(系统提供了5个),但运行时只能指定一个RunLoop,若是切换RunLoop,则需要退出当前的RunLoop
2.定时器NSTimer问题:1:若是创建定时器用timerWithTimeInterval,则需要手动将定时器添加到NSRunLoop中,指定的运行模式为default,但是如果有滚动事件的时候,定时器就会停止工作。解决办法:更改NSRunLoop的运行模式,UITrackingRunLoopMode界面追踪,此模式是当只有发生滚动事件的时候才会开启定时器。若是任何时候都会开启定时器: NSRunLoopCommonModes,
NSRunLoopCommonModes = NSDefaultRunLoopMode + UITrackingRunLoopMode
占用,标签,凡是添加到NSRunLoopCommonModes中的事件爱你都会被同时添加到打上commmon标签的运行模式上
3. 1:scheduledTimerWithTimeInterval此方法创建的定时器默认加到了NSRunLoop中,并且设置运行模式为默认。 2:若是想在子线程开启NSRunLoop:需要手动开启:NSRunLoop *currentRunloop = [NSRunLoop currentRunLoop];等到线程销毁的时候currentRunloop对象也随即销毁。2:在子线程的定时器,需要手动加入到runloop:不要忘记调用run方法
代码案例:
- (void)touchesBegan:(NSSet
*)touches withEvent:(UIEvent *)event{ [self timer1];
// [self timer2];
}
//在runloop中有多个运行模式,但是runloop只能选择一种模式运行
//mode里面至少要有一个timer或者是source
-(void)run
{
NSLog(@"子线程开启Runloop中添加timer事件,让线程持续存");
}
//[NSTimer scheduledTimerWithTimeInter..]此方法创建的定时器默认加到了NSRunLoop中,并且设置运行模式为默认
-(void)timer2
{
NSRunLoop *currentRunloop = [NSRunLoop currentRunLoop];
//该方法内部自动添加到runloop中,并且设置运行模式为默认
[NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:@selector(run) userInfo:nil repeats:YES];
//开启runloop
[currentRunloop run];
}
//timerWithTimeInterval,需要手动将定时器添加到NSRunLoop中,指定的运行模式为default
- (void)timer1{
NSRunLoop *currentRunloop = [NSRunLoop currentRunLoop];
[currentRunloop addTimer:[NSTimer timerWithTimeInterval:2.0 target:self selector:@selector(run) userInfo:nil repeats:YES] forMode:NSDefaultRunLoopMode];
//控制循环时间
// [currentRunloop runUntilDate:[NSDate dateWithTimeIntervalSinceNow:10]];
}