美文网首页
Runtime和Runloop部分理解

Runtime和Runloop部分理解

作者: yycache | 来源:发表于2019-08-19 22:18 被阅读0次

    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)

            }

        }

    }

    相关文章

      网友评论

          本文标题:Runtime和Runloop部分理解

          本文链接:https://www.haomeiwen.com/subject/etdysctx.html