Runtime和Runloop部分理解

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 2、我们导入系统的头文件,一般用尖括号。 3、OC 解决消息机制方法提示步骤【查找build setting-> 搜索msg-> objc_msgSend(YES --> NO)】 4、最终生成消息机制,编译器做的事情,最终代码,需要把当前代码用xcode重新编译,【clang -rewrite-objc main.m查看最终生成代码】,示例:cd main.m --> 输入前面指令,就会生成 .opp文件(C++代码)5、这里一般不会直接导入

面试:消息机制方法调用流程

怎么去调用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)

        }

    }

}

你可能感兴趣的:(Runtime和Runloop部分理解)