Runtime
01
问题:objc在向一个对象发送消息时,发生了什么?
解答: 根据对象的 isa 指针找到类对象 id,在查询类对象里面的 methodLists 方法函数列表,如果没有在好到,在沿着 superClass ,寻找父类,再在父类 methodLists 方法列表里面查询,最终找到 SEL ,根据 id 和 SEL 确认 IMP(指针函数),在发送消息;
03
问题: 什么时候会报unrecognized selector错误?iOS有哪些机制来避免走到这一步?
解答: 当发送消息的时候,我们会根据类里面的 methodLists 列表去查询我们要动用的SEL,当查询不到的时候,我们会一直沿着父类查询,当最终查询不到的时候我们会报 unrecognized selector错误,当系统查询不到方法的时候,会调用 +(BOOL)resolveInstanceMethod:(SEL)sel 动态解释的方法来给我一次机会来添加,调用不到的方法。或者我们可以再次使用 -(id)forwardingTargetForSelector:(SEL)aSelector重定向的方法来告诉系统,该调用什么方法,一来保证不会崩溃。
04
问题: 能否向编译后得到的类中增加实例变量?能否向运行时创建的类中添加实例变量?为什么?
解答: 1、不能向编译后得到的类增加实例变量 2、能向运行时创建的类中添加实例变量。【解释】:1. 编译后的类已经注册在 runtime 中,类结构体中的 objc_ivar_list 实例变量的链表和 instance_size 实例变量的内存大小已经确定,runtime会调用 class_setvarlayout 或 class_setWeaklvarLayout 来处理strong weak 引用.所以不能向存在的类中添加实例变量。2. 运行时创建的类是可以添加实例变量,调用class_addIvar函数. 但是的在调用 objc_allocateClassPair 之后,objc_registerClassPair 之前,原因同上.
05
问题:runtime如何实现weak变量的自动置nil?
解答:runtime 对注册的类, 会进行布局,对于 weak 对象会放入一个 hash 表中。 用 weak 指向的对象内存地址作为 key,当此对象的引用计数为0的时候会 dealloc,假如 weak 指向的对象内存地址是a,那么就会以a为键, 在这个 weak 表中搜索,找到所有以a为键的 weak 对象,从而设置为 nil。
06
问题: 给类添加一个属性后,在类结构体里哪些元素会发生变化?
解答:instance_size :实例的内存大小;objc_ivar_list *ivars:属性列表
RunLoop
01
问题: runloop是来做什么的?runloop和线程有什么关系?主线程默认开启了runloop么?子线程呢?
解答: runloop: 从字面意思看:运行循环、跑圈,其实它内部就是do-while循环,在这个循环内部不断地处理各种任务(比如Source、Timer、Observer)事件。runloop和线程的关系:一个线程对应一个RunLoop,主线程的RunLoop默认创建并启动,子线程的RunLoop需手动创建且手动启动(调用run方法)。RunLoop只能选择一个Mode启动,如果当前Mode中没有任何Source(Sources0、Sources1)、Timer,那么就直接退出RunLoop。
02
问题: 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
03
问题: 为什么把NSTimer对象以NSDefaultRunLoopMode(kCFRunLoopDefaultMode)添加到主运行循环以后,滑动scrollview的时候NSTimer却不动了?
解答: nstime对象是在 NSDefaultRunLoopMode下面调用消息的,但是当我们滑动scrollview的时候,NSDefaultRunLoopMode模式就自动切换到UITrackingRunLoopMode模式下面,却不可以继续响应nstime发送的消息。所以如果想在滑动scrollview的情况下面还调用nstime的消息,我们可以把nsrunloop的模式更改为NSRunLoopCommonModes.
04
问题: 苹果是如何实现Autorelease Pool的?
解答:Autorelease Pool作用:缓存池,可以避免我们经常写relase的一种方式。其实就是延迟release,将创建的对象,添加到最近的autoreleasePool中,等到autoreleasePool作用域结束的时候,会将里面所有的对象的引用计数器 - autorelease.
1.纯swift无法用runtime,
2.继承自UIKit的为了兼容可以用runtime,
3.动态获取方法,如果参数包含swift支持而oc不支持的数据,不能获取,比如Character,元组
4.添加@objc dynamic 标示动态,可以获取变量名和方法名
5.继承自NSObject的Swift类,其继承自父类的方法具有动态性,其他自定义方法、属性需要加dynamic修饰才可以获得动态性
runtime释义:
Objective-C 是基于 C 的,它为 C 添加了面向对象的特性。它将很多静态语言在编译和链接时期做的事放到了 runtime 运行时来处理,可以说 runtime 是我们 Objective-C 幕后工作者。
1、runtime(简称运行时),是一套 纯C(C和汇编)写的API。而 OC 就是运行时机制,也就是在运行时候的一些机制,其中最主要的是 消息机制。
2、对于 C 语言,函数的调用在编译的时候会决定调用哪个函数。
3、运行时机制原理:OC的函数调用称为消息发送,属于 动态调用过程。在 编译的时候并不能决定真正调用哪个函数,只有在真 正运行的时候才会根据函数的名称找到对应的函数来调用。
4、事实证明:在编译阶段,OC 可以 调用任何函数,即使这个函数并未实现,只要声明过就不会报错,只有当运行的时候才会报错,这是因为OC是运行时动态调用的。而 C 语言 调用未实现的函数 就会报错。
消息机制
任何方法调用本质:就是发送一个消息(用 runtime发送消息,OC 底层实现通过 runtime实现),每一个 OC 的方法,底层必然有一个与之对应的 runtime方法。
消息机制原理:对象根据方法编号SEL去映射表查找对应的方法实现。注解: 1、必须要导入头文件 #import
面试:消息机制方法调用流程
怎么去调用eat方法,对象方法:(保存到类对象的方法列表) ,类方法:(保存到元类(Meta Class)中方法列表)。 1、OC 在向一个对象发送消息时,runtime 库会根据对象的 isa指针找到该对象对应的类或其父类中查找方法。。 2、注册方法编号(这里用方法编号的好处,可以快速查找)。 3、根据方法编号去查找对应方法。 4、找到只是最终函数实现地址,根据地址去方法区调用对应函数
补充:一个objc 对象的 isa 的指针指向什么?有什么作用? 每一个对象内部都有一个isa指针,这个指针是指向它的真实类型,根据这个指针就能知道将来调用哪个类的方法。
/
1、动态交换两个方法的实现
1、给系统的方法添加分类 2、自己实现一个带有扩展功能的方法 3、交换方法,只需要交换一次,
// 1.获取 imageNamed方法地址
Method imageNamedMethod = class_getClassMethod(self, @selector(imageNamed:));
// 2.获取 ln_imageNamed方法地址
Method ln_imageNamedMethod = class_getClassMethod(self, @selector(ln_imageNamed:));
// 3.交换方法地址,相当于交换实现方式;「method_exchangeImplementations 交换两个方法的实现」
method_exchangeImplementations(imageNamedMethod, ln_imageNamedMethod);
2、动态添加属性
- (NSString *)name
{
// 利用参数key 将对象object中存储的对应值取出来
return objc_getAssociatedObject(self, @"name");
}
- (void)setName:(NSString *)name
{
/**
将某个值跟某个对象关联起来,将某个值存储到某个对象中
objc_setAssociatedObject(<#id _Nonnull object#>:给哪个对象添加属性, <#const void * _Nonnull key#>:属性名称, <#id _Nullable value#>:属性值, <#objc_AssociationPolicy policy#>:保存策略)
*/
objc_setAssociatedObject(self, @"name", name, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
NSLog(@"name---->%p",name);
}
属性赋值的本质,就是让属性与一个对象产生关联,所以要给NSObject的分类的name属性赋值就是让name和NSObject产生关联,而runtime可以做到这一点。
3、实现字典转模型的自动转换
4、动态添加方法
如果一个类方法非常多,加载类到内存的时候也比较耗费资源,需要给每个方法生成映射表,可以使用动态给某个类,添加方法解决。
5、拦截并替换方法
6、实现 NSCoding 的自动归档和解档
#import "Movie.h"
#import
#define encodeRuntime(A) \
\
unsigned int count = 0;\
Ivar *ivars = class_copyIvarList([A class], &count);\
for (int i = 0; i Ivar ivar = ivars[i];\ const char *name = ivar_getName(ivar);\ NSString *key = [NSString stringWithUTF8String:name];\ id value = [self valueForKey:key];\ [encoder encodeObject:value forKey:key];\ }\ free(ivars);\ \ #define initCoderRuntime(A) \ \ if (self = [super init]) {\ unsigned int count = 0;\ Ivar *ivars = class_copyIvarList([A class], &count);\ for (int i = 0; i Ivar ivar = ivars[i];\ const char *name = ivar_getName(ivar);\ NSString *key = [NSString stringWithUTF8String:name];\ id value = [decoder decodeObjectForKey:key];\ [self setValue:value forKey:key];\ }\ free(ivars);\ }\ return self;\ \ - - - @implementation Movie - (void)encodeWithCoder:(NSCoder *)encoder { encodeRuntime(Movie) } - (id)initWithCoder:(NSCoder *)decoder { initCoderRuntime(Movie) } @end 补充常用runtime示例:Demo中有体现 1.添加属性和交换方法示例:UITextField占位文字颜色placeholderColor 2.交换方法示例:交换dealloc方法实现,添加功能那个控制器被销毁了 */ #warning - 以下为功能模块相关的方法示例, 具体方法作用、使用、注解请移步 -> github.com/CoderLN 以下的这些方法应该算是`runtime`在实际场景中所应用的大部分的情况了,平常的编码中差不多足够用了。 0、class_copyPropertyList 获取类中所有的属性 objc_property_t *propertyList = class_copyPropertyList([self class], &count); for (unsigned int i=0; i const char *propertyName = property_getName(propertyList[i]); NSLog(@"property---->%@", [NSString stringWithUTF8String:propertyName]); } 0、class_copyMethodList 获取类的所有方法 Method *methodList = class_copyMethodList([self class], &count); for (unsigned int i; i Method method = methodList[i]; NSLog(@"method---->%@", NSStringFromSelector(method_getName(method))); } 0、class_copyIvarList 获取类中所有的成员变量(outCount 会返回成员变量的总数) Ivar *ivarList = class_copyIvarList([self class], &count); for (unsigned int i; i Ivar myIvar = ivarList[i]; const char *ivarName = ivar_getName(myIvar); NSLog(@"Ivar---->%@", [NSString stringWithUTF8String:ivarName]); } 0、class_copyProtocolList 获取协议列表 __unsafe_unretained Protocol **protocolList = class_copyProtocolList([self class], &count); for (unsigned int i; i Protocol *myProtocal = protocolList[i]; const char *protocolName = protocol_getName(myProtocal); NSLog(@"protocol---->%@", [NSString stringWithUTF8String:protocolName]); } 0、object_getClass 获得类方法 Class PersonClass = object_getClass([Person class]); SEL oriSEL = @selector(test1); Method oriMethod = _class_getMethod(xiaomingClass, oriSEL); 0、class_getInstanceMethod 获得实例方法 Class PersonClass = object_getClass([xiaoming class]); SEL oriSEL = @selector(test2); Method cusMethod = class_getInstanceMethod(xiaomingClass, oriSEL); 0、class_addMethod 动态添加方法 BOOL addSucc = class_addMethod(xiaomingClass, oriSEL, method_getImplementation(cusMethod), method_getTypeEncoding(cusMethod)); 0、class_replaceMethod 替换原方法实现 class_replaceMethod(toolClass, cusSEL, method_getImplementation(oriMethod), method_getTypeEncoding(oriMethod)); 0、method_exchangeImplementations 交换两个方法的实现 method_exchangeImplementations(method1, method2); 0、根据名字得到类变量的Ivar指针,但是这个在OC中好像毫无意义 Ivar oneCVIvar = class_getClassVariable([Person class], name); 0、根据名字得到实例变量的Ivar指针 Ivar oneIVIvar = class_getInstanceVariable([Person class], name); 0、找到后可以直接对私有成员变量赋值(强制修改name属性) object_setIvar(_per, oneIVIvar, @"age"); 0、动态添加方法 class_addMethod([person class]:Class cls 类型, @selector(eat):待调用的方法名称, (IMP)myAddingFunction:(IMP)myAddingFunction,IMP是一个函数指针,这里表示指定具体实现方法myAddingFunction, 0:0代表没有参数); 0、获得某个类的类方法 Method class_getClassMethod(Class cls , SEL name) 0、获得成员变量的名字 const char *ivar_getName(Ivar v); 0、将某个值跟某个对象关联起来,将某个值存储到某个对象中 void objc_setAssociatedObject(id object:表示关联者,是一个对象,变量名理所当然也是object , const void *key:获取被关联者的索引key ,id value :被关联者 ,objc_AssociationPolicy policy:关联时采用的协议,有assign,retain,copy等协议,一般使用OBJC_ASSOCIATION_RETAIN_NONATOMIC) 0、利用参数key 将对象object中存储的对应值取出来 id objc_getAssociatedObject(id object , const void *key) */ class_copyIvarList与class_copyPropertyList的区别? 1.class_copyIvarList:能够获取.h和.m中的所有属性以及大括号中声明的变量,获取的属性名称有下划线(大括号中的除外)。 2.class_copyPropertyList:只能获取由property声明的属性,包括.m中的,获取的属性名称不带下划线。 3.OC中没有真正的私有属性。 黑魔法 简单说就是进行方法交换 在Objective-C中调用一个方法,其实是向一个对象发送消息,查找消息的唯一依据是selector的名字。利用Objective-C的动态特性,可以实现在运行时偷换selector对应的方法实现,达到给方法挂钩的目的 每个类都有一个方法列表,存放着方法的名字和方法实现的映射关系,selector的本质其实就是方法名,IMP有点类似函数指针,指向具体的Method实现,通过selector就可以找到对应的IMP 交换方法的几种实现方式 利用 method_exchangeImplementations交换两个方法的实现 利用 class_replaceMethod替换方法的实现 利用 method_setImplementation来直接设置某个方法的IMP。 swift 实现归档解档 /// 归档 func encode(with aCoder: NSCoder) { //获取成员变量list数组 var count: UInt32 = 0 //将获取类中所有成员变量 存入堆空间中 class_copyPropertyList// 少用 或者不用 与class_copyIvarList有区别 // guard let propertyList = class_copyPropertyList(self.classForCoder, &count) else { // return // } guard let ivars = class_copyIvarList(self.classForCoder, &count) else { return } for index in 0.. let ivar = ivars[index] //获取类成员名 // let cName = property_getName(pty) let cName = ivar_getName(ivar) let name = String(utf8String: cName!) //遍历kvc进行类成员赋值 进行归档 let value = self.value(forKey: name!) aCoder.encode(value, forKey: name!) } //释放堆空间 free(ivars) } /// 解档 required init?(coder aDecoder: NSCoder) { super.init() //获取类中所有属性列表 存入堆中 var count: UInt32 = 0 guard let ivars = class_copyIvarList(self.classForCoder, &count) else { return } for index in 0.. let ivar = ivars[index] // 获取类成员名 let cName = ivar_getName(ivar) let name = String(utf8String: cName!) //解档 let value = aDecoder.decodeObject(forKey: name!) self.setValue(value, forKey: name!) } free(ivars) } swift 实现交换方法 https://www.jianshu.com/p/cfd56c76f7a0 https://www.jianshu.com/p/23ea81be5cc2 swift 实现动态增加修改 属性 https://www.jianshu.com/p/e1f325eee49b //动态增加属性 extension UserToken { private struct AssociatedKeys { static var personName = "yf_PersonName" } var personName: String? { get { return objc_getAssociatedObject(self, &AssociatedKeys.personName) as? String } set { if let newValue = newValue { objc_setAssociatedObject( self, &AssociatedKeys.personName, newValue as NSString?, .OBJC_ASSOCIATION_RETAIN_NONATOMIC ) } } } } //MARK:runtime遍历所有属性名,动态修改 func setTitleTextColor(vc:UIViewController){ var count:UInt32 = 0 let propertys = class_copyPropertyList(UIViewController.self, &count) for index in 0.. let i = Int(index) let property = propertys![i] let propertyName = property_getName(property) let strName = String.init(cString: propertyName, encoding: String.Encoding.utf8) if strName == "title"{ vc.setValue("鹏哥哥", forKey: strName!) //也可以修改颜色等等 各种属性值均可修改 vc.setValue(.red, forKey: strName!) } } } //方法一: //parameters是你要修改某个属性的值如:["titleString": "GoodsDetailPage"] func runtimeChangeValue(vc: UIViewController, parameters: [String: Any]) { var ivarCount: UInt32 = 0 let ivarList = class_copyIvarList(vc.classForCoder, &ivarCount) for i in 0.. let ivar = ivarList![Int(i)] let key = String(cString: ivar_getName(ivar)!) if let param = parameters[key] as? String { vc.setValue(param, forKey: key) } } } //方法二: func runtimeChangeValue(vc: UIViewController, parameters: [String: Any]) { var outCount: UInt32 = 0 let properties = class_copyPropertyList(vc.classForCoder, &outCount) for i in 0.. let property = properties![Int(i)] let key = String(cString: property_getName(property)) if let param = parameters[key] as? String { vc.setValue(param, forKey: key) } } }