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 <objc/message.h>2、我们导入系统的头文件,一般用尖括号。 3、OC 解决消息机制方法提示步骤【查找build setting-> 搜索msg-> objc_msgSend(YES --> NO)】 4、最终生成消息机制,编译器做的事情,最终代码,需要把当前代码用xcode重新编译,【clang -rewrite-objc main.m查看最终生成代码】,示例:cd main.m --> 输入前面指令,就会生成 .opp文件(C++代码)5、这里一般不会直接导入<objc/runtime.h>
面试:消息机制方法调用流程
怎么去调用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 <objc/runtime.h>
#define encodeRuntime(A) \
\
unsigned int count = 0;\
Ivar *ivars = class_copyIvarList([A class], &count);\
for (int i = 0; i<count; 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<count; 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<count; 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<count; 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<count; 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<count; 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..<Int(count) {
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..<Int(count) {
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..<count {
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..<ivarCount {
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..<outCount {
let property = properties![Int(i)]
let key = String(cString: property_getName(property))
if let param = parameters[key] as? String {
vc.setValue(param, forKey: key)
}
}
}
网友评论