runtime运行时机制
1:通过runtime,实现方法交换(交换两个类方法、交换两个实例方法)
2:通过runtime,在分类中设置属性。
3:通过runtime,获取类的属性列表。可以通过KVC动态设置值。
4:通过runtime,获取类的方法列表。可以通过sendMessage动态发送消息。
5:通过runtime,获取类的协议列表。
6:通过runtime,获取类的成员变量列表。字典转模型
7:通过runtime,调用对象方法,让对象发送消息。
8:通过runtime,动态添加方法。
9:通过runtime,实现NSCoding的自动归档和解档。
10:通过runtime,实现字典转模型的自动转换。
11:通过runtime,动态修改变量的值。
1:通过runtime,实现方法交换(交换两个类方法、交换两个实例方法)
- (void)viewDidLoad {
[super viewDidLoad];
int a = 9;
int b = 11;
NSLog(@"交换前:a = %d --- b = %d",a,b);
//通过异或实现两个整数的交换
a = a ^ b;
b = a ^ b;
a = a ^ b;
NSLog(@"交换后:a = %d --- b = %d",a,b);
// Do any additional setup after loading the view.
Person *p = [Person new];
Method m1 = class_getClassMethod([Person class], @selector(run));
Method m2 = class_getClassMethod([Person class], @selector(study));
method_exchangeImplementations(m1, m2);
//交换两个类方法
[Person run];
[Person study];
Method m3 = class_getInstanceMethod([p class], @selector(eat));
Method m4 = class_getInstanceMethod([p class], @selector(sheep));
method_exchangeImplementations(m3, m4);
//交换两个实例方法
[p eat];
[p sheep];
}
2:通过runtime,在分类中设置属性。
@property(nonatomic,copy)NSString *name;
static const char *nameKey = "nameKey";
- (NSString *)name {
return objc_getAssociatedObject(self, nameKey);
}
- (void)setName:(NSString *)name {
objc_setAssociatedObject(self, nameKey, name, OBJC_ASSOCIATION_COPY_NONATOMIC);
}
3:获取类的属性列表
static const char *propertiesKey = "propertiesKey";
+ (NSArray *)propertiesList {
unsigned int count = 0;
objc_property_t *list = class_copyPropertyList([self class], &count);
NSMutableArray *arrayM = [NSMutableArray array];
//遍历所有的属性
for (unsigned int i = 0; i < count; i++) {
objc_property_t pty = list[I];
const char *cname = property_getName(pty);
//获取 属性 名称
NSString *name = [NSString stringWithUTF8String:cname];
[arrayM addObject:name];
}
free(list);
//设置关联对象
objc_setAssociatedObject(self, propertiesKey, arrayM, OBJC_ASSOCIATION_COPY_NONATOMIC);
return objc_getAssociatedObject(self, propertiesKey);
}
4:获取类的ivar成员变量列表
static const char *ivarsKey = "ivarsKey";
+ (NSArray *)ivarsList {
unsigned int count = 0;
Ivar *list = class_copyIvarList([self class], &count);
NSMutableArray *arrayM = [NSMutableArray array];
//遍历所有的属性
for (unsigned int i = 0; i < count; i++) {
Ivar ivar = list[I];
const char *cname = ivar_getName(ivar);
//获取 属性 名称
NSString *name = [NSString stringWithUTF8String:cname];
[arrayM addObject:name];
}
free(list);
objc_setAssociatedObject(self, ivarsKey, arrayM, OBJC_ASSOCIATION_COPY_NONATOMIC);
return objc_getAssociatedObject(self, ivarsKey);
}
5:获取类的方法列表
static const char *methodsKey = "methodsKey";
+ (NSArray *)methodsList {
unsigned int count = 0;
Method *list = class_copyMethodList([self class], &count);
NSMutableArray *arrayM = [NSMutableArray array];
//遍历所有的属性
for (unsigned int i = 0; i < count; i++) {
Method method = list[I];
SEL selector = method_getName(method);
NSString *methodName = NSStringFromSelector(selector);
[arrayM addObject:methodName];
}
free(list);
objc_setAssociatedObject(self, methodsKey, arrayM, OBJC_ASSOCIATION_COPY_NONATOMIC);
return objc_getAssociatedObject(self, methodsKey);
}
6:获取类的协议列表
static const char *protocolKey = "protocolKey";
+ (NSArray *)protocolList {
unsigned int count = 0;
__unsafe_unretained Protocol **list = class_copyProtocolList([self class], &count);
NSMutableArray *arrayM = [NSMutableArray array];
//遍历所有的属性
for (unsigned int i = 0; i < count; i++) {
Protocol *protocol = list[I];
const char *cname = protocol_getName(protocol);
//获取 属性 名称
NSString *name = [NSString stringWithUTF8String:cname];
[arrayM addObject:name];
}
free(list);
objc_setAssociatedObject(self, protocolKey, arrayM, OBJC_ASSOCIATION_COPY_NONATOMIC);
return objc_getAssociatedObject(self, protocolKey);
}
7:通过runtime,动态修改变量的值
- (void)dynamicModifyValue {
Teacher *teacher = [Teacher new];
teacher.address = @"东北省哈尔滨市";
NSLog(@"address1 = %@",teacher.address);
unsigned int count = 0;
Ivar *ivar = class_copyIvarList([teacher class], &count);
for (int i = 0; i<count; I++)
{
Ivar var = ivar[I];
const char *varName = ivar_getName(var);
NSString *proname = [NSString stringWithUTF8String:varName];
if ([proname isEqualToString:@"_address"])
{
//给属性加下划线
object_setIvar(teacher, var, @"河南省郑州市");
break;
}
}
NSLog(@"address2 = %@",teacher.address);
}
8:通过runtime,实现NSCoding的自动归档和解档
- (void)encodeAndDecoder
{
Teacher *teacher = [[Teacher alloc] init];
teacher.title = @"English Teacher";
teacher.course = @"English";
teacher.age = 60;
teacher.address = @"北京市朝阳区";
teacher.wage = 8000;
NSString *documentPath = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).lastObject;
NSString *filePath = [documentPath stringByAppendingPathComponent:@"userInfo"];
//模型写入文件
[NSKeyedArchiver archiveRootObject:teacher toFile:filePath];
//读取
Teacher *myTeacher = [NSKeyedUnarchiver unarchiveObjectWithFile: filePath];
NSLog(@"%@", myTeacher);
}
//归档
- (void)encodeWithCoder:(NSCoder *)coder
{
unsigned int count = 0;
Ivar *list = class_copyIvarList([self class], &count);
//遍历所有的属性
for (unsigned int i = 0; i < count; I++)
{
//取出i位置对应的成员变量
Ivar ivar = list[I];
//查看成员变量
const char *name = ivar_getName(ivar);
NSString *key = [NSString stringWithUTF8String:name];
//归档
id value = [self valueForKey:key];
[coder encodeObject:value forKey:key];
}
free(list);
}
//解归档
- (id)initWithCoder:(NSCoder *)coder
{
self = [super init];
if (self)
{
unsigned int count = 0;
Ivar *list = class_copyIvarList([self class], &count);
//遍历所有的属性
for (unsigned int i = 0; i < count; I++)
{
//取出i位置对应的成员变量
Ivar ivar = list[I];
//查看成员变量
const char *name = ivar_getName(ivar);
NSString *key = [NSString stringWithUTF8String:name];
//归档
id value = [coder decodeObjectForKey:key];
//设置到成员变量身上
[self setValue:value forKey:key];
}
free(list);
}
return self;
}
9:通过runtime,实现字典转模型的自动转换
//字典转模型
+ (instancetype)objectWithDict:(NSDictionary *)dict
{
// 创建对应模型对象
id objc = [[self alloc] init];
unsigned int count = 0;
// 1.获取成员属性数组
Ivar *ivarList = class_copyIvarList(self, &count);
// 2.遍历所有的成员属性名,一个一个去字典中取出对应的value给模型属性赋值
for (int i = 0; i < count; I++)
{
// 2.1 获取成员属性
Ivar ivar = ivarList[I];
// 2.2 获取成员属性名 C -> OC 字符串
NSString *ivarName = [NSString stringWithUTF8String:ivar_getName(ivar)];
// 2.3 _成员属性名 => 字典key
NSString *key = [ivarName substringFromIndex:1];
// 2.4 去字典中取出对应value给模型属性赋值
id value = dict[key];
// 获取成员属性类型
NSString *ivarType = [NSString stringWithUTF8String:ivar_getTypeEncoding(ivar)];
// 二级转换,字典中还有字典,也需要把对应字典转换成模型
// 判断下value,是不是字典
if ([value isKindOfClass:[NSDictionary class]] && ![ivarType containsString:@"NS"]) { // 是字典对象,并且属性名对应类型是自定义类型
// user User
// 处理类型字符串 @\"User\" -> User
ivarType = [ivarType stringByReplacingOccurrencesOfString:@"@" withString:@""];
ivarType = [ivarType stringByReplacingOccurrencesOfString:@"\"" withString:@""];
// 自定义对象,并且值是字典
// value:user字典 -> User模型
// 获取模型(user)类对象
Class modalClass = NSClassFromString(ivarType);
// 字典转模型
if (modalClass)
{
// 字典转模型 user
value = [modalClass objectWithDict:value];
}
}
// 三级转换:NSArray中也是字典,把数组中的字典转换成模型.
// 判断值是否是数组
if ([value isKindOfClass:[NSArray class]])
{
// 判断对应类有没有实现字典数组转模型数组的协议
if ([self respondsToSelector:@selector(arrayContainModelClass)])
{
// 转换成id类型,就能调用任何对象的方法
id idSelf = self;
// 获取数组中字典对应的模型
NSString *type = [idSelf arrayContainModelClass][key];
// 生成模型
Class classModel = NSClassFromString(type);
NSMutableArray *arrM = [NSMutableArray array];
// 遍历字典数组,生成模型数组
for (NSDictionary *dict in value)
{
// 字典转模型
id model = [classModel objectWithDict:dict];
[arrM addObject:model];
}
// 把模型数组赋值给value
value = arrM;
}
}
// 2.5 KVC字典转模型
if (value)
{
[objc setValue:value forKey:key];
}
}
// 返回对象
return objc;
}
1 isa指针?
对象的isa指针指向所属的类
类的isa指针指向了所属的元类
元类的isa指向了根元类
根元类指向了自己。
2 介绍一下分类,能用分类做什么?内部是如何实现的?它为什么会覆盖掉原来的方法?
category:可以给类或者系统类添加实例方法。
添加的实例方法,会被动态的添加到类结构里面的methodList列表里面。
3 给类添加一个属性后,在类结构体里哪些元素会发生变化?
//实例对象大小
NSInteger instanceSize = class_getInstanceSize([Test class]);
instance_size :实例的内存大小
属性列表
成员变量列表
4 类方法和实例方法有什么区别?
调用的方式不同,类方法必须使用类调用,在方法里面不能调用属性,类方法里面也必须调用类方法。存储在元类结构体里面的methodLists里面。
实例方法必须使用实例对象调用,可以在实例方法里面使用属性,实例方法也必须调用实例方法。存储在类结构体里面的methodLists里面。
5 runtime如何实现weak变量的自动置nil?
runtime 对注册的类, 会进行布局,对于 weak 对象会放入一个 hash 表中。 用 weak 指向的对象内存地址作为 key,当此对象的引用计数为0的时候会 dealloc,假如 weak 指向的对象内存地址是a,那么就会以a为键, 在这个 weak 表中搜索,找到所有以a为键的 weak 对象,从而设置为 nil。
6 能否向编译后得到的类中增加实例变量?能否向运行时创建的类中添加实例变量?为什么?
1.不能向编译后得到的类增加实例变量
2.能向运行时创建的类中添加实例变量
解释:
(1)编译后的类已经注册在runtime中,类结构体中的objc_ivar_list实例变量的链表和instance_size实例变量的内存大小已经确定,runtime会调用class_setvarlayout或class_setWeaklvarLayout来处理strong weak引用.所以不能向存在的类中添加实例变量
(2)运行时创建的类是可以添加实例变量,调用class_addIvar函数.但是的在调用objc_allocateClassPair之后,objc_registerClassPair之前,原因同上.
7 objc在向一个对象发送消息时,发生了什么?
根据对象的isa指针找到类对象id,在查询类对象里面的methodLists方法函数列表,如果没有找到,在沿着superClass,寻找父类,再在父类methodLists方法列表里面查询,最终找到SEL,根据id和SEL确认IMP(指针函数),发送消息;
8 什么时候会报unrecognized selector错误?iOS有哪些机制来避免走到这一步?
当发送消息的时候,我们会根据类里面的methodLists列表去查询我们要动用的SEL,当查询不到的时候,我们会一直沿着父类查询,当最终查询不到的时候我们会报unrecognized selector错误
当系统查询不到方法的时候,会调用+(BOOL)resolveInstanceMethod:(SEL)sel动态解释的方法来给我一次机会来添加,调用不到的方法。或者我们可以再次使用-(id)forwardingTargetForSelector:(SEL)aSelector重定向的方法来告诉系统,该调用什么方法,一来保证不会崩溃。
9 为什么在默认情况下无法修改被block捕获的变量? __block都做了什么?
默认情况下,block里面的变量,拷贝进去的是变量的值,而不是指向变量内存的指针。
当使用__block修饰后的变量,拷贝到block里面的就是指向变量的指针,所以我们就可以修改变量的值。
func propertyList() {
//属性的数量
var count: UInt32 = 0
let list = class_copyPropertyList(UITextField.self, &count)
var array = [String]()
for i in 0..<Int(count) {
//根据下标获取属性
guard let pty = list?[i] else { continue }
//获取属性名称C语言字符串
let cname = property_getName(pty)
//将属性名称,转换为String
guard let name = String(utf8String: cname) else { continue }
array.append(name)
}
//释放C语言对象
free(list)
print("array = \(array)")
//class_copyIvarList(<#T##cls: AnyClass?##AnyClass?#>, <#T##outCount: UnsafeMutablePointer<UInt32>?##UnsafeMutablePointer<UInt32>?#>)
//class_copyMethodList(<#T##cls: AnyClass?##AnyClass?#>, <#T##outCount: UnsafeMutablePointer<UInt32>?##UnsafeMutablePointer<UInt32>?#>)
//class_copyIvarList(<#T##cls: AnyClass?##AnyClass?#>, <#T##outCount: UnsafeMutablePointer<UInt32>?##UnsafeMutablePointer<UInt32>?#>)
}
10 rumtime、runloop原理和实用场景
runloop运行循环、跑圈。其实也就是一个循环跑圈,用来处理线程里面的事件和消息。
runloop和线程的关系:每条线程都有唯一的一个与之对应的runloop对象。每个线程如果想继续运行,不被释放,就必须有一个runloop来不停的跑圈,以来处理线程里面的各个事件和消息。
线程 =====》 runloop对象(处理线程里面的各个事件和消息)
主线程默认是开启一个runloop。也就是这个runloop才能保证我们程序正常的运行。
子线程是默认没有开启runloop的。
runloop基本作用:保持程序的持续运行,处理App中的各种事件,节省CPU资源,提高程序性能。
runloop的生命周期:Runloop在第一次获取时创建,在线程结束时销毁。
如何让子线程不死:给这条子线程开启一个runloop
11 runloop的mode是用来做什么的?有几种mode?
model:是runloop里面的模式,不同的模式下的runloop处理的事件和消息有一定的差别。
系统默认注册了5个Mode:
(1)kCFRunLoopDefaultMode: App的默认 Mode,通常主线程是在这个 Mode 下运行的。
(2)UITrackingRunLoopMode: 界面跟踪 Mode,用于 ScrollView 追踪触摸滑动,保证界面滑动时不受其他 Mode 影响。
(3)UIInitializationRunLoopMode: 在刚启动 App 时第进入的第一个 Mode,启动完成后就不再使用。
(4)GSEventReceiveRunLoopMode: 接受系统事件的内部 Mode,通常用不到。
(5)kCFRunLoopCommonModes: 这是一个占位的 Mode,没有实际作用。
注意iOS 对以上5中model进行了封装
NSDefaultRunLoopMode;
NSRunLoopCommonModes
12 为什么把NSTimer对象以NSDefaultRunLoopMode(kCFRunLoopDefaultMode)添加到主运行循环以后,滑动scrollview的时候NSTimer却不动了?
NSTimer对象是在 NSDefaultRunLoopMode下面调用消息的,但是当我们滑动scrollview的时候,NSDefaultRunLoopMode模式就自动切换到UITrackingRunLoopMode模式下面,却不可以继续响应NStime发送的消息。所以如果想在滑动scrollview的情况下面还调用NSTimer的消息,我们可以把NSRunLoop的模式更改为NSRunLoopCommonModes。
类结构
13 KVO的使用?实现原理?(为什么要创建子类来实现)
KVO(Key Value Observe)
每个对象都有isa 指针,指向该对象的类,它告诉 Runtime 系统这个对象的类是什么。所以对象注册为观察者时,isa指针指向新子类,那么这个被观察的对象就神奇地变成新子类的对象(或实例)了。
14 KVC的使用?
KVC(Key-value coding)键值编码
可以允许开发者通过Key名直接访问对象的属性,或者给对象的属性赋值。而不需要调用明确的存取方法。这样就可以在运行时动态地访问和修改对象的属性。而不是在编译时确定。
15 KVC实现原理?(KVC拿到key以后,是如何赋值的?
setValue forKey 通过key进行赋值
valueForKey key 直接通过key来取值

16 运行时能增加成员变量么?能增加属性么?如果能,如何增加?如果不能,为什么?
可以添加属性的,但必须我们实现它的getter和setter方法、同时没有生成带下滑线同名的成员变量。
网友评论