美文网首页
Objective-C--Runtime

Objective-C--Runtime

作者: 人生看淡不服就干 | 来源:发表于2017-07-28 18:02 被阅读27次

    一、Runtime简介


    什么是Runtime?

    OC = C + Runtime
    OC是基于C语言的扩展,不仅增加了面向对象的功能,而且增加了强大的动态特性,这一切都要归功于OC的Runtime。

    OC语言将很多原本需要编译或链接时决定的特性延迟到运行时决定,它会尽可能的动态处理一些事情,这就是我们为什么说OC是一门动态语言。

    OC的动态性让其如此强大和灵活:

    • 动态类型,直到运行时才会决定一个对象的类型。动态类型保证了多态,有了抽象和多态,才有了美妙的设计模式~
    • 动态绑定,在运行时决定哪个方法被真正调用。这样,方法的调用过程不会在被接收者的类型限制,甚至不会被方法名限制,开发者可以更灵活的设计。
    • 动态访问和调整,我们可以随时获取应用的运行信息,并能跟踪、干预应用的运行过程。各种Hook,各种Patch,让我们为所欲为~

    为了支持OC的动态性,光有一个编译器是不够的,还需要一个运行时系统去执行编译后的代码。运行时系统就像是OC语言的操作系统,可以保证OC语言特性的正常表现。OC运行时是以动态库的形式,参与所有OC应用的链接过程。

    如何学习Runtime

    学习就要学最官方的资料:
    Programming with Objective-C
    Object-Oriented Programming with Objective-C
    Objective-C Runtime Programming Guide
    Objective-C Runtime Reference
    Objective-C Runtime 源代码下载

    Runtime--动态类型


    NSObject定义

    OC中所有的类都最终继承自NSObject,因此所有的OC对象都可以看成NSObject类型的。先看看NSObject的相关定义:

    @interface NSObject <NSObject> {
        Class isa  OBJC_ISA_AVAILABILITY;
    }
    
    // An opaque type that represents an Objective-C class.
    typedef struct objc_class *Class;
    
    struct objc_class {
        Class isa  OBJC_ISA_AVAILABILITY;
    #if !__OBJC2__
        Class super_class                                        OBJC2_UNAVAILABLE;
        const char *name                                         OBJC2_UNAVAILABLE;
        long version                                             OBJC2_UNAVAILABLE;
        long info                                                OBJC2_UNAVAILABLE;
        long instance_size                                       OBJC2_UNAVAILABLE;
        struct objc_ivar_list *ivars                             OBJC2_UNAVAILABLE;
        struct objc_method_list **methodLists                    OBJC2_UNAVAILABLE;
        struct objc_cache *cache                                 OBJC2_UNAVAILABLE;
        struct objc_protocol_list *protocols                     OBJC2_UNAVAILABLE;
    #endif
    } 
    

    isa是什么?

    每个对象都有一个isa的实例变量,它继承自NSObject,是对象连接到运行时系统的桥梁。isa标示了对象的类型,它指向了一个编译时定义好的的类结构(objc_class)。通过isa,一个对象可以找到所有它在运行时需要的信息,比如它在继承层次中的位置,实例变量的大小和结构,以及所有方法实现的位置。

    我们发现,objc_class结构体中也有一个isa,它是什么作用呢?
    类对象的isa指向了metaclass(元类,可以理解为类的类)。通过它我们可以找到类中的静态变量和方法。

    下图解释了isa在继承层次中的作用:

    动态类型原理

    答案就是运行时通过isa指针判断当前对象所属的类

    /// Represents an instance of a class.
    struct objc_object {
        Class isa  OBJC_ISA_AVAILABILITY;
    };
    
    /// A pointer to an instance of a class.
    typedef struct objc_object *id;
    

    由id类型的结构可以得知,id可以指向任意的NSObject及其子类对象。
    在源代码中,我们用id类型的变量指向一个对象,编译器是无法得知这个对象的真实类型的。而运行时通过id指向对象的isa属性,可以访问到该对象的真实类型信息。

    如何获取类型

    1. object_getClass(obj)返回的是obj中的isa指针,因此能得到最正确的类型

    2. [obj class]则分两种情况:一是当obj为实例对象时,[obj class]中class是实例方法:- (Class)class,返回的obj对象中的isa指针;二是当obj为类对象(包括元类)时,调用的是类方法:+ (Class)class,返回的结果为其本身。

    + (Class)class {
        return self;
    }
    
    - (Class)class {
        return object_getClass(self);
    }
    

    动态类型应用

    动态类型最常见的应用就是多态。多态就是相似但不同的两个对象对同一事件的响应不一样,这就要求二者不是同一个类但最终继承自同一个类。因此我们可以用父类指针指向子类对象,不同的场景下指向不同的子类对象,这样调用对象方法时会执行不同的逻辑。 谈到多态,C++也是有多态的,通过虚函数表实现,语法上有限制。而OC在原理上完全支持多态,用起来更方便(还要配合动态绑定来讲解)。

    特殊情况下,isa会被修改,指向另一个类对象。比如KVO的原理:当一个类型为T的对象O的属性P被观察时,会在运行时创建一个新的类型NSNotifying_T,NSNotifying_T继承了T,并重新实现P的set方法,注入了通知触发的逻辑。同时会将O的isa指针指向了NSNotifying_T,因此给O设置P时,调用的是NSNotifying_T中的方法。而这一切对开发者而言都是透明的,多么神奇的黑魔法。

    Runtime--动态绑定


    消息传递

    调用一个对象的方法,实际上就是给这个对象发送一条消息,在运行时系统会根据消息去查找对应的方法,然后执行。

    调用一个对象的方法,到底发生了什么呢?我们可以测试一下:

    @implementation RuntimeObject
    
    - (void)method1{
    }
    
    - (void)method2{
        [self method1];
    }
    @end
    

    通过编译器命令xcrun -sdk iphonesimulator clang -rewrite-objc RuntimeObject.m将以上OC代码转成C++代码:

    static void _I_RuntimeObject_method1(RuntimeObject * self, SEL _cmd) {
    }
    
    static void _I_RuntimeObject_method2(RuntimeObject * self, SEL _cmd) {
        ((void (*)(id, SEL))(void *)objc_msgSend)((id)self, sel_registerName("method1"));
    }
    

    objc_msgSend是什么鬼?我们看看message.h中的声明

     /*
     * @note When it encounters a method call, the compiler generates a call to one of the
     *  functions \c objc_msgSend, \c objc_msgSend_stret, \c objc_msgSendSuper, or \c objc_msgSendSuper_stret.
     *  Messages sent to an object’s superclass (using the \c super keyword) are sent using \c objc_msgSendSuper; 
     *  other messages are sent using \c objc_msgSend. Methods that have data structures as return values
     *  are sent using \c objc_msgSendSuper_stret and \c objc_msgSend_stret.
     */
    OBJC_EXPORT id objc_msgSend(id self, SEL op, ...)
        OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0);
    

    这里我们知道OC中的方法调用会在编译过程中被转成objc_msgSend的调用方式:

    • objc_msgSend: 调用一个返回非结构体的方法
    • objc_msgSend_stret:调用一个返回结构体的方法
    • objc_msgSendSuper:调用super的方法

    objc_msgSend到底做了什么?
    objc_msgSend最终会调用lookUpImpOrForward,它会在运行时查找具体要调用的方法实现:

    1. 缓存中有对应的方法实现,则直接返回
    2. 如果类还没有完整构建(isRealized),则去构建
    3. 如果类还没有初始化(isInitialized),则初始化,这里会调用+initialize方法
    4. 从当前类的缓存中查找,若找到则结束
    5. 从当前类的方法列表中查找,若找到则缓存起来并结束
    6. 一层层的从父类的缓存和方法列表中查找,若找到则缓存起来并结束
    7. 如果还没尝试过动态解决方法,则调用动态解决,并跳入第4步重新查找
    8. 返回消息转发流程的IMP _objc_msgForward_impcache
    /***********************************************************************
    * lookUpImpOrForward.
    * The standard IMP lookup. 
    * initialize==NO tries to avoid +initialize (but sometimes fails)
    * cache==NO skips optimistic unlocked lookup (but uses cache elsewhere)
    * Most callers should use initialize==YES and cache==YES.
    * inst is an instance of cls or a subclass thereof, or nil if none is known. 
    *   If cls is an un-initialized metaclass then a non-nil inst is faster.
    * May return _objc_msgForward_impcache. IMPs destined for external use 
    *   must be converted to _objc_msgForward or _objc_msgForward_stret.
    *   If you don't want forwarding at all, use lookUpImpOrNil() instead.
    **********************************************************************/
    IMP lookUpImpOrForward(Class cls, SEL sel, id inst, 
                           bool initialize, bool cache, bool resolver)
    {
        IMP imp = nil;
        bool triedResolver = NO;
    
        runtimeLock.assertUnlocked();
    
        // Optimistic cache lookup
        if (cache) {
            imp = cache_getImp(cls, sel);
            if (imp) return imp;
        }
    
        // runtimeLock is held during isRealized and isInitialized checking
        // to prevent races against concurrent realization.
    
        // runtimeLock is held during method search to make
        // method-lookup + cache-fill atomic with respect to method addition.
        // Otherwise, a category could be added but ignored indefinitely because
        // the cache was re-filled with the old value after the cache flush on
        // behalf of the category.
    
        runtimeLock.read();
    
        if (!cls->isRealized()) {
            // Drop the read-lock and acquire the write-lock.
            // realizeClass() checks isRealized() again to prevent
            // a race while the lock is down.
            runtimeLock.unlockRead();
            runtimeLock.write();
    
            realizeClass(cls);
    
            runtimeLock.unlockWrite();
            runtimeLock.read();
        }
    
        if (initialize  &&  !cls->isInitialized()) {
            runtimeLock.unlockRead();
            _class_initialize (_class_getNonMetaClass(cls, inst));
            runtimeLock.read();
            // If sel == initialize, _class_initialize will send +initialize and 
            // then the messenger will send +initialize again after this 
            // procedure finishes. Of course, if this is not being called 
            // from the messenger then it won't happen. 2778172
        }
    
        
     retry:    
        runtimeLock.assertReading();
    
        // Try this class's cache.
    
        imp = cache_getImp(cls, sel);
        if (imp) goto done;
    
        // Try this class's method lists.
        {
            Method meth = getMethodNoSuper_nolock(cls, sel);
            if (meth) {
                log_and_fill_cache(cls, meth->imp, sel, inst, cls);
                imp = meth->imp;
                goto done;
            }
        }
    
        // Try superclass caches and method lists.
        {
            unsigned attempts = unreasonableClassCount();
            for (Class curClass = cls;
                 curClass != nil;
                 curClass = curClass->superclass)
            {
                // Halt if there is a cycle in the superclass chain.
                if (--attempts == 0) {
                    _objc_fatal("Memory corruption in class list.");
                }
                
                // Superclass cache.
                imp = cache_getImp(curClass, sel);
                if (imp) {
                    if (imp != (IMP)_objc_msgForward_impcache) {
                        // Found the method in a superclass. Cache it in this class.
                        log_and_fill_cache(cls, imp, sel, inst, curClass);
                        goto done;
                    }
                    else {
                        // Found a forward:: entry in a superclass.
                        // Stop searching, but don't cache yet; call method 
                        // resolver for this class first.
                        break;
                    }
                }
                
                // Superclass method list.
                Method meth = getMethodNoSuper_nolock(curClass, sel);
                if (meth) {
                    log_and_fill_cache(cls, meth->imp, sel, inst, curClass);
                    imp = meth->imp;
                    goto done;
                }
            }
        }
    
        // No implementation found. Try method resolver once.
    
        if (resolver  &&  !triedResolver) {
            runtimeLock.unlockRead();
            _class_resolveMethod(cls, sel, inst);
            runtimeLock.read();
            // Don't cache the result; we don't hold the lock so it may have 
            // changed already. Re-do the search from scratch instead.
            triedResolver = YES;
            goto retry;
        }
    
        // No implementation found, and method resolver didn't help. 
        // Use forwarding.
    
        imp = (IMP)_objc_msgForward_impcache;
        cache_fill(cls, sel, imp, inst);
    
     done:
        runtimeLock.unlockRead();
    
        return imp;
    }
    

    方法决议

    如果在类的方法列表中未找到方法实现,则运行时系统会给你第一次补救机会: 方法决议,你可以动态添加方法实现。返回YES表示已决议,否则进入消息转发流程。

    方法决议包括类方法和实例方法:

    +(BOOL)resolveClassMethod:(SEL)sel;
    +(BOOL)resolveInstanceMethod:(SEL)sel;
    

    在obj-class.m中,我们找了消息动态解决的实现

    void _class_resolveMethod(Class cls, SEL sel, id inst)
    {
        if (! cls->isMetaClass()) {
            // try [cls resolveInstanceMethod:sel]
            _class_resolveInstanceMethod(cls, sel, inst);
        } 
        else {
            // try [nonMetaClass resolveClassMethod:sel]
            // and [cls resolveInstanceMethod:sel]
            _class_resolveClassMethod(cls, sel, inst);
            if (!lookUpImpOrNil(cls, sel, inst, 
                                NO/*initialize*/, YES/*cache*/, NO/*resolver*/)) 
            {
                _class_resolveInstanceMethod(cls, sel, inst);
            }
        }
    }
    
    static void _class_resolveInstanceMethod(Class cls, SEL sel, id inst)
    {
        if (! lookUpImpOrNil(cls->ISA(), SEL_resolveInstanceMethod, cls, 
                             NO/*initialize*/, YES/*cache*/, NO/*resolver*/)) 
        {
            // Resolver not implemented.
            return;
        }
    
        BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend;
        bool resolved = msg(cls, SEL_resolveInstanceMethod, sel);
    
        // Cache the result (good or bad) so the resolver doesn't fire next time.
        // +resolveInstanceMethod adds to self a.k.a. cls
        IMP imp = lookUpImpOrNil(cls, sel, inst, 
                                 NO/*initialize*/, YES/*cache*/, NO/*resolver*/);
    
       ...
    }
    

    常见用到方法决议的场景:@dynamic修饰过的属性不会自动生成访问器方法,我们可以在方法决议时动态添加访问器方法的实现。

    消息转发

    方法决议失败后,运行时还会给你第二次补救机会:消息转发。可以将消息转发给其他对象,或者直接处理Invocation对象。这也是最后一次机会,如果仍未处理,则抛出异常。

    消息转发涉及的方法如下:

    -(id)forwardingTargetForSelector:(SEL)aSelector;
    - (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector;
    -(void)forwardInvocation:(NSInvocation *)anInvocation;
    

    消息转发的流程:

    1. 调用forwardingTargetForSelector尝试转发给另外一个对象,也叫快速转发。返回self或nil则走第2步;默认实现返回nil
    2. 调用methodSignatureForSelector:获取方法签名(包含方法名、参数、返回类型),以构造NSInvocation对象;返回nil直接抛异常:unrecognized selector sent to instance
    3. 调用forwardInvocation:并传入构造好的NSInvocation对象,也叫正式转发;默认实现会调用doesNotRecognizeSelector:直接抛出异常

    方法决议和消息转发整体流程如下:

    消息转发的应用场景最经典的还是JSPatch,它将已有的方法实现直接替换成了消息转发流程,最终在forwardInvocation:中拿到所有调用相关信息(方法名、参数类型等),然后传递给JS代码。

    动态访问和调整


    访问变量和属性

        //获取变量
        unsigned int count = 0;
        Ivar* varList = class_copyIvarList([RuntimeObject class], &count);
        for (int i=0; i<count; ++i) {
            Ivar var = varList[i];
            NSLog(@"var name %s",ivar_getName(var));
        }
        free(varList); //必须释放
        
        //获取属性
        objc_property_t* propertyList = class_copyPropertyList([RuntimeObject class], &count);
        for (int i=0; i<count; ++i) {
            objc_property_t property = propertyList[i];
            NSLog(@"property name %s",property_getName(property));
        }
        free(propertyList); //必须释放
        
        //修改变量
        Ivar nameVar = class_getInstanceVariable([RuntimeObject class], "_name");
        object_setIvar(obj, nameVar, @"啦啦啦啦");
        NSLog(@"nameVar %@",obj.name);
    

    访问方法

        //获取方法
        unsigned int count = 0;
        Method* methodList = class_copyMethodList([RuntimeObject class], &count);
        for (int i=0; i<count; ++i) {
            Method method = methodList[i];
            NSLog(@"method name %@",NSStringFromSelector(method_getName(method)));
        }
        free(methodList);
        
        //增加方法
        SEL printSEL = sel_registerName("print");
        class_addMethod([RuntimeObject class], printSEL, (IMP)print, "v@:");
        [obj performSelector:printSEL];
        
        //替换方法
        Method runMethod = class_getInstanceMethod([RuntimeObject class], @selector(run));
        Method printMethod = class_getInstanceMethod([RuntimeObject class], printSEL);
        method_exchangeImplementations(runMethod, printMethod);
        [obj run];
    

    Method Swizzle

    即交换两个方法的实现,常用于AOP,为已有方法增加功能。

    Isa Swizzle

    即运行时修改Isa,使其指向另外一个类,比如KVO实现原理。

    动态生成类

    可以在运行时动态创建一个类,并给它添加变量和方法。注意添加变量的时机必须在 objc_allocateClassPair之后且objc_registerClassPair之前

        //创建类
        //return Nil if the class could not be created (for example, the desired name is already in use)
        Class cls = objc_allocateClassPair([NSObject class], "RuntimeClass", 0);
        
        if (cls) {
            //添加成员变量
            //    * @note This function may only be called after objc_allocateClassPair and before objc_registerClassPair.
            //    *       Adding an instance variable to an existing class is not supported.
            //    * @note The class must not be a metaclass. Adding an instance variable to a metaclass is not supported.
            class_addIvar(cls, "address", sizeof(NSString*), log2(sizeof(NSString*)), @encode(NSString*));
            class_addIvar([RuntimeObject class], "sex", sizeof(int), sizeof(int), @encode(int));
            
            
            //添加方法
            //    * @note class_addMethod will add an override of a superclass's implementation,
            //    *  but will not replace an existing implementation in this class.
            //    *  To change an existing implementation, use method_setImplementation.
            SEL printSEL = sel_registerName("print");
            class_addMethod(cls, printSEL, (IMP)print, "v@:");
            
            //注册类
            objc_registerClassPair(cls);
        }else{
            cls = objc_getClass("RuntimeClass");
        }
        
        //创建对象
        id obj = [[cls alloc] init];
        
        //访问成员变量
        Ivar addressVar = class_getInstanceVariable(cls, "address");
        object_setIvar(obj, addressVar, @"上海市");
        NSLog(@"addressVar %@",object_getIvar(obj, addressVar));
        
        //访问方法
        [obj performSelector:@selector(print)];
    

    关联对象

    一个已注册的类是不能再添加实例变量了,而关联对象可以为已注册的类增加类似实例变量的存储变量

    @implementation RuntimeObject (AddProperty)
    
    static char key;
    
    -(void)setAddress:(NSString *)address{
       //key只要是唯一的标示就行,比如一个固定的地址
       //objc_setAssociatedObject(self, @selector(address), address, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
        objc_setAssociatedObject(self, &key, address, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    }
    
    -(NSString*)address{
       return objc_getAssociatedObject(self, &key);
    }
    
    @end
    

    相关文章

      网友评论

          本文标题:Objective-C--Runtime

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