美文网首页Runtime我爱编程runtime
iOS运行时runtime及相关Objective-C底层讲解

iOS运行时runtime及相关Objective-C底层讲解

作者: 2897275c8a00 | 来源:发表于2017-12-20 11:59 被阅读63次

    2.Runtime储备知识

    2.1.前言

           在体验中,我写过这样一句话“并且我认为这个技术算是高阶开发里面一个投机的技巧,绝大多数的UI开发都不会使用runtime,容易出现很严重的问题,并且官方也不是特别推荐使用”。

            但是经过了第一张体验,我会告诉你。虽然说第一在UI开发中你可能不会经常使用,第二使用不当时,会出现很严重的问题。第三官方也不是推荐使用的。但是!!即使你并不使用,学习Objective-C的运行时Runtime系统也是非常有必要的,并且当你学会了Runtime系统,你会发现你可以在任何地方使用到它。毕竟Runtime系统被称之为iOS开发的黑魔法。

           Objective-C提供了编译运行时,只要有可能,它都可以动态地运作。这意味着不仅需要编译器,还需要运行时系统执行编译的代码。运行时系统充当Objective-C语言的操作系统,有了它才能运作。

           运行时系统所提供功能是非常强大的,在一些第三方库开发中是经常使用到的。比如,苹果不允许我们给Category追加扩展属性,是因为它不会自动生成成员变量,那么我们通过运行时就可以很好的解决这个问题。另外,常见的模型转字典或者字典转模型,对象归档等。后续我们再来学习如何应用,本节只是讲讲理论。

    2.2.与Runtime交互

           在使用runtime中,我们会通过如下三种方法来使用runtime。并且使用的难度级风险也越来越大。

    通过Objective-C源代码

    通过Foundation库中定义的NSObject提供的方法

    通过直接调用runtime方法

    2.2.1.通过Objective-C源代码

    安全系数及难度:零

           在我们日常开发中,所有编写的Objective-C代码系统都会自动帮助我们编译成runtime代码。我们使用它只是写源代码并编译源代码。当编译包含Objective-C类和方法的代码时,编译器会创建实现了语言动态特性的数据结构和函数调用。

    2.2.2.通过NSObject提供的方法

    安全系数及难度:低级

            在Cocoa编程中,大部分的类都继承于NSObject,也就是说NSObject通常是根类,大部分的类都继承于NSObject。有些特殊的情况下,NSObject只是提供了它应该要做什么的模板,却没有提供所有必须的代码。

           有些NSObject提供的方法仅仅是为了查询运动时系统的相关信息,这此方法都可以反查自己。比如-isKindOfClass:和-isMemberOfClass:都是用于查询在继承体系中的位置。-respondsToSelector:指明是否接受特定的消息。+conformsToProtocol:指明是否要求实现在指定的协议中声明的方法。-methodForSelector:提供方法实现的地址。

    2.2.3.通过直接调用runtime函数

    安全系数及难度:高级

    runtime库函数在usr/include/objc目录下,我们主要关注是这两个头文件:

    #import <objc/runtime.h>

    #import <objc/objc.h>

    2.3.消息(Message)

           为什么叫消息呢?因为面向对象编程中,对象调用方法叫做发送消息。在编译时,应用的源代码就会被编将对象发送消息转换成runtime的objc_msgSend函数调用。

           在Objective-C,消息在运行时并不要求实现。编译器会转换消息表达式:

    [receiver message];

           在编译时会转换成类似这样的函数调用:

    id objc_msgSend(id self,SEL op,...)

            具体会转换成哪个,我们来看看官方的文章注释:

    /**

    * Sends a message with a simple return value to an instance of a class.

    * @param self A pointer to the instance of the class that is to receive the message.

    * @param op The selector of the method that handles the message.

    * @param ...

    *   A variable argument list containing the arguments to the method.

    * @return The return value of the method.

    * @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_msgSend:其它普通的消息都会通过该函数来发送

    objc_msgSend_stret:消息中需要有数据结构作为返回值时,会通过该函数来发送消息并接收返回值

    objc_msgSendSuper:与objc_msgSend函数类似,只是它把消息发送给父类实例

    objc_msgSendSuper_stret:与objc_msgSend_stret函数类似,只是它把消息发送给父类实例并接收数组结构作为返回值

           另外,如果函数返回值是浮点类型,官方说明如下:

           注意:有另外一个需要注意的地方是当函数返回的值是浮点类型是,官方注释有这样的解释:

    /* Floating-point-returning Messaging Primitives

    *

    * Use these functions to call methods that return floating-point values

    * on the stack.

    * Consult your local function call ABI documentation for details.

    *

    * arm:    objc_msgSend_fpret not used

    * i386:   objc_msgSend_fpret used for `float`, `double`, `long double`.

    * x86-64: objc_msgSend_fpret used for `long double`.

    *

    * arm:    objc_msgSend_fp2ret not used

    * i386:   objc_msgSend_fp2ret not used

    * x86-64: objc_msgSend_fp2ret used for `_Complex long double`.

    *

    * These functions must be cast to an appropriate function pointer type

    * before being called.

    */

           其实总来来说不用担心这个问题,只需要调用objc_msgSend_fpret函数就好了。

            注意事项:一定要调用所调用的API支持哪些平台,乱调在导致部分平台上不支持而崩溃的。

           现在我们来看看当消息被发送到实例对象时,它是如何处理的:

    消息传递

            这个算是iOS开发消息传递的基础知识了,就不在更多解释。一直找到NSObject如果都还没有找到,就会崩溃报错Unreconized selector。

    2.4.Message Forwarding

           当发送消息给一个不处理该消息的对象是错误的。然后在宣布错误之前,运行时系统给了接收消息的对象处理消息的第二个机会。

           当某对象不处理某消息时,可以通过重写-forwardInvocation:方法来提供一个默认的消息响应或者避免出错。当对象中找不到方法实现时,会按照类继承关系一层层往上找。我们看看类继承关系图:

    类继承关系图

           所有元类中的isa指针都指向根元类,而根元类的isa指针则指向自身。根元类是继承于根类的,与根类的结构体成员一致,都是objc_class结构体,不同的是根元类的isa指针指向自身,而根类的isa指针为nil

    我们再看看消息处理流程:

    消息处理

            当对象查询不到相关的方法,消息得不到该对象处理,会启动“消息转发”机制。消息转发还分为几个阶段:先询问receiver或者说是它所属的类是否能动态添加方法,以处理当前这个消息,这叫做“动态方法解析”,runtime会通过+resolveInstanceMethod:判断能否处理。如果runtime完成动态添加方法的询问之后,receiver仍然无法正常响应则Runtime会继续向receiver询问是否有其它对象即其它receiver能处理这条消息,若返回能够处理的对象,Runtime会把消息转给返回的对象,消息转发流程也就结束。若无对象返回,Runtime会把消息有关的全部细节都封装到NSInvocation对象中,再给receiver最后一次机会,令其设法解决当前还未处理的这条消息。

            我们可以这样理解:

            向一个对象发送它不处理的消息是一个会报错,但在报错之前 Runtime系统会给接收对象来处理这些错误的机会。这个需要用到以下方法:

    -(void) forwardInvocation: (NSInvocation*)invocation

            如果对象没有实现这个方法,就调用NSObject 的forwardInvocation:方法。那句不能识别消息的错误,实际就是NSObject 的forwardInvocation 抛出来的异常。

            也就是说,Message Forwarding的作用就是你可以覆盖forwardInvocation方法,来改变NSObject 的抛异常的处理方式。所以,你可以把A不能处理的消息转发给B去处理。

    提示:消息处理越往后,开销也就会越大,因此最好直接在第一步就可以得到消息处理。

            我们来看看NSInvocation官方头文件:

    @interfaceNSInvocation:

    NSObject{

    @private

    __strongvoid*_frame;

    __strongvoid*_retdata;

    id_signature;

    id_container;

    uint8_t_retainedArgs;

    uint8_t_reserved[15];

    }

    +(NSInvocation*)invocationWithMethodSignature:(NSMethodSignature*)sig;

    @property(readonly,retain)NSMethodSignature*methodSignature;

    -(void)retainArguments;

    @property(readonly)BOOLargumentsRetained;

    @property(nullable,assign)idtarget;

    @propertySELselector;

    -(void)getReturnValue:(void*)retLoc;

    -(void)setReturnValue:(void*)retLoc;

    -(void)getArgument:(void*)argumentLocationatIndex:(NSInteger)idx;

    -(void)setArgument:(void*)argumentLocationatIndex:(NSInteger)idx;

    -(void)invoke;

    -(void)invokeWithTarget:(id)target;

    @end

            实际上NSInvocation是一个包含了target、selector ,Arguments,也就是它包含了向一个对象发送消息的所有元素:对象、方法名、参数序列。可以调用NSInvocation 的invoke 方法将这个消息激活。

            后面Message Forwarding会单独继续精讲。这里做一个理解。

    2.5.类与对象基础数据结构

    2.5.1.Class

            Objective-C类是由Class类型来表示的,它实际上是一个指向objc_class结构体的指针。

            查看objc/objc.h中Class的定义如下:

    /// An opaque type that represents an Objective-C class.

    typedefstructobjc_class*Class;

             查看objc/runtime.h中objc_class结构体的定义如下:

    structobjc_class{

    ClassisaOBJC_ISA_AVAILABILITY;

    #if !__OBJC2__

    Classsuper_classOBJC2_UNAVAILABLE;//

    父类

    constchar*nameOBJC2_UNAVAILABLE;//

    类名

    longversionOBJC2_UNAVAILABLE;//

    类的版本信息,默认为0

    longinfoOBJC2_UNAVAILABLE;//

    类信息,供运行期使用的一些位标识

    longinstance_sizeOBJC2_UNAVAILABLE;//

    该类的实例变量大小

    structobjc_ivar_list*ivarsOBJC2_UNAVAILABLE;//

    该类的成员变量链表

    structobjc_method_list**methodListsOBJC2_UNAVAILABLE;//

    方法定义的链表

    structobjc_cache*cacheOBJC2_UNAVAILABLE;//

    方法缓存

    structobjc_protocol_list*protocolsOBJC2_UNAVAILABLE;//

    协议链表

    #endif

    }OBJC2_UNAVAILABLE;

             我们可以看到每个类结构体都会有一个isa指针,它是指向元类的。它还有一个父类指针super_class,指针父类。还包含了类的名称name、类的版本信息version、类的一些标识信息info、实例大小instance_size、成员变量地址列表ivars、方法地址列表methodLists、缓存最近使用的方法地址cache、协议列表protocols。其中有一下几个字段是我们需要特别关注的:

    isa:需要注意的是在Objective-C中,所有的类自身也是一个对象,这个对象的Class里面也有一个isa指针,它指向metaClass(元类),我们会在后面介绍它。

    super_class:指向该类的父类,如果该类已经是最顶层的根类(如NSObject或NSProxy),则super_class为NULL。

    cache:用于缓存最近使用的方法。一个接收者对象接收到一个消息时,它会根据isa指针去查找能够响应这个消息的对象。在实际使用中,这个对象只有一部分方法是常用的,很多方法其实很少用或者根本用不上。这种情况下,如果每次消息来时,我们都是methodLists中遍历一遍,性能势必很差。这时,cache就派上用场了。在我们每次调用过一个方法后,这个方法就会被缓存到cache列表中,下次调用的时候runtime就会优先去cache中查找,如果cache没有,才去methodLists中查找方法。这样,对于那些经常用到的方法的调用,但提高了调用的效率。

    version:我们可以使用这个字段来提供类的版本信息。这对于对象的序列化非常有用,它可是让我们识别出不同类定义版本中实例变量布局的改变。

             针对cache,我们用下面例子来说明其执行过程:

    NSArray*array=[[NSArrayalloc]init];

    其流程是:

            [NSArray alloc]先被执行。因为NSArray没有+alloc方法,于是去父类NSObject去查找。

            检测NSObject是否响应+alloc方法,发现响应,于是检测NSArray类,并根据其所需的内存空间大小开始分配内存空间,然后把isa指针指向NSArray类。同时,+alloc也被加进cache列表里面。

            接着,执行-init方法,如果NSArray响应该方法,则直接将其加入cache;如果不响应,则去父类查找。

             在后期的操作中,如果再以[[NSArray alloc] init]这种方式来创建数组,则会直接从cache中取出相应的方法,直接调用。

    2.5.1.1.objc_object与id

             objc_object是表示一个类的实例的结构体,它的定义如下(objc/objc.h):

    #if !OBJC_TYPES_DEFINED

    /// An opaque type that represents an Objective-C class.

    typedefstructobjc_class*Class;

    /// Represents an instance of a class.

    structobjc_object{

    ClassisaOBJC_ISA_AVAILABILITY;

    };

    /// A pointer to an instance of a class.

    typedefstructobjc_object*id;

    #endif

           从官方的头文件可以看到objc_object是一个结构体并且只有一个成员,即指向其类的isa指针。这样,当我们向一个Objective-C对象发送消息时,runtime系统会根据实例对象的isa指针找到这个实例对象所属的类。Runtime系统会在类的方法列表及父类的方法列表中去寻找与消息对应的selector指向的方法。找到后即运行这个方法。

           当创建一个特定类的实例对象时,分配的内存包含一个objc_object数据结构,然后是类的实例变量的数据。NSObject类的alloc和allocWithZone:方法使用函数class_createInstance来创建objc_object数据结构。

           另外还有我们常见的id,它是一个objc_object结构类型的指针。它的存在可以让我们实现类似于C++中泛型的一些操作。该类型的对象可以转换为任何一种对象,有点类似于C语言中void *指针类型的作用。

    2.5.1.2.objc_cache

            上面提到了objc_class结构体中的cache字段,它用于缓存调用过的方法。这个字段是一个指向objc_cache结构体的指针,其定义如下:

    typedefstructobjc_cache*CacheOBJC2_UNAVAILABLE;

    #define CACHE_BUCKET_NAME(B)  ((B)->method_name)

    #define CACHE_BUCKET_IMP(B)   ((B)->method_imp)

    #define CACHE_BUCKET_VALID(B) (B)

    #ifndef __LP64__

    #define CACHE_HASH(sel, mask) (((uintptr_t)(sel)>>2) & (mask))

    #else

    #define CACHE_HASH(sel, mask) (((unsigned int)((uintptr_t)(sel)>>3)) & (mask))

    #endif

    structobjc_cache{

    unsignedintmask/* total = mask + 1 */OBJC2_UNAVAILABLE;

    unsignedintoccupiedOBJC2_UNAVAILABLE;

    Methodbuckets[1]OBJC2_UNAVAILABLE;

    };

    该结构体的字段描述如下:

    mask:一个整数,指定分配的缓存bucket的总数。在方法查找过程中,Objective-C runtime使用这个字段来确定开始线性查找数组的索引位置。指向方法selector的指针与该字段做一个AND位操作(index = (mask & selector))。这可以作为一个简单的hash散列算法。

    occupied:一个整数,指定实际占用的缓存bucket的总数。

    buckets:指向Method数据结构指针的数组。这个数组可能包含不超过mask+1个元素。需要注意的是,指针可能是NULL,表示这个缓存bucket没有被占用,另外被占用的bucket可能是不连续的。这个数组可能会随着时间而增长。

    2.5.2.元类(Meta Class)

            在上面我们提到,所有的类自身也是一个对象,我们可以向这个对象发送消息(即调用类方法)。如:

    NSArray*array=[NSArrayarray];

            这个例子中,+array消息发送给了NSArray类,而这个NSArray也是一个对象。既然是对象,那么它也是一个objc_object指针,它包含一个指向其类的一个isa指针。那么这些就有一个问题了,这个isa指针指向什么呢?为了调用+array方法,这个类的isa指针必须指向一个包含这些类方法的一个objc_class结构体。这就引出了meta-class的概念

    meta-class是一个类对象的类。

            当我们向一个对象发送消息时,runtime会在这个对象所属的这个类的方法列表中查找方法;而向一个类发送消息时,会在这个类的meta-class的方法列表中查找。

             meta-class之所以重要,是因为它存储着一个类的所有类方法。每个类都会有一个单独的meta-class,因为每个类的类方法基本不可能完全相同。

            其实,meta-class也是一个类,也可以向它发送一个消息,那么它的isa又是指向什么呢?为了不让这种结构无限延伸下去,Objective-C的设计者让所有的meta-class的isa指向基类的meta-class,以此作为它们的所属类。即,任何NSObject继承体系下的meta-class都使用NSObject的meta-class作为自己的所属类,而基类的meta-class的isa指针是指向它自己。这样就形成了一个完美的闭环。

            通过上面的描述,再加上对objc_class结构体中super_class指针的分析,我们就可以描绘出类及相应meta-class类的一个继承体系了,如下图所示:

    类及相应meta-class类的一个继承体系

            对于NSObject继承体系来说,其实例方法对体系中的所有实例、类和meta-class都是有效的;而类方法对于体系内的所有类和meta-class都是有效的。

    3.操作函数

    3.1.类名(name)

    /**

    * Returns the name of a class.

    *

    * @param cls A class object.

    *

    * @return The name of the class, or the empty string if \e cls is \c Nil.

    */

    OBJC_EXPORTconstchar*class_getName(Classcls)

    __OSX_AVAILABLE_STARTING(__MAC_10_5,__IPHONE_2_0);

            对于class_getName函数,如果传入的cls为Nil,则返回一个char字字符串。

    3.2.父类(super_class)和元类(meta-class)

    /**

    * Returns a Boolean value that indicates whether a class object is a metaclass.

    *

    * @param cls A class object.

    *

    * @return \c YES if \e cls is a metaclass, \c NO if \e cls is a non-meta class,

    *  \c NO if \e cls is \c Nil.

    */

    OBJC_EXPORTBOOLclass_isMetaClass(Classcls)

    __OSX_AVAILABLE_STARTING(__MAC_10_5,__IPHONE_2_0);

    /**

    * Returns the superclass of a class.

    *

    * @param cls A class object.

    *

    * @return The superclass of the class, or \c Nil if

    *  \e cls is a root class, or \c Nil if \e cls is \c Nil.

    *

    * @note You should usually use \c NSObject's \c superclass method instead of this function.

    */

    OBJC_EXPORTClassclass_getSuperclass(Classcls)

    __OSX_AVAILABLE_STARTING(__MAC_10_5,__IPHONE_2_0);

    /**

    * Sets the superclass of a given class.

    *

    * @param cls The class whose superclass you want to set.

    * @param newSuper The new superclass for cls.

    *

    * @return The old superclass for cls.

    *

    * @warning You should not use this function.

    */

    OBJC_EXPORTClassclass_setSuperclass(Classcls,ClassnewSuper)

    __OSX_AVAILABLE_BUT_DEPRECATED(__MAC_10_5,__MAC_10_5,__IPHONE_2_0,__IPHONE_2_0);

    class_isMetaClass函数,如果是cls是元类,则返回YES;如果否或者传入的cls为Nil,则返回NO。

    class_getSuperclass函数,当cls为Nil或者cls为根类时,返回Nil。不过通常我们可以使用NSObject类的superclass方法来达到同样的目的。

    class_setSuperclass函数,为一个cls设置一个新的superclass,并且返回cls以前的superclass。

    3.3.版本(version)

    /**

    * Returns the version number of a class definition.

    *

    * @param cls A pointer to a \c Class data structure. Pass

    *  the class definition for which you wish to obtain the version.

    *

    * @return An integer indicating the version number of the class definition.

    *

    * @see class_setVersion

    */

    OBJC_EXPORTintclass_getVersion(Classcls)

    __OSX_AVAILABLE_STARTING(__MAC_10_0,__IPHONE_2_0);

    /**

    * Sets the version number of a class definition.

    *

    * @param cls A pointer to an Class data structure.

    *  Pass the class definition for which you wish to set the version.

    * @param version An integer. Pass the new version number of the class definition.

    *

    * @note You can use the version number of the class definition to provide versioning of the

    *  interface that your class represents to other classes. This is especially useful for object

    *  serialization (that is, archiving of the object in a flattened form), where it is important to

    *  recognize changes to the layout of the instance variables in different class-definition versions.

    * @note Classes derived from the Foundation framework \c NSObject class can set the class-definition

    *  version number using the \c setVersion: class method, which is implemented using the \c class_setVersion function.

    */

    OBJC_EXPORTvoidclass_setVersion(Classcls,intversion)

    __OSX_AVAILABLE_STARTING(__MAC_10_0,__IPHONE_2_0);

    class_getVersion函数可以获取类定义的版本号,返回一个int类型。

    class_setVersion函数对cls设置一个int类型的版本号,不过通常我们可以使用NSObject类的setVersion方法来达到同样的目的。

    3.4.实例变量大小(instance_size)

    /**

    * Returns the size of instances of a class.

    *

    * @param cls A class object.

    *

    * @return The size in bytes of instances of the class \e cls, or \c 0 if \e cls is \c Nil.

    */

    OBJC_EXPORTsize_tclass_getInstanceSize(Classcls)

    __OSX_AVAILABLE_STARTING(__MAC_10_5,__IPHONE_2_0);

    3.5.成员变量(ivars)及属性

    3.5.1.成员变量

    /**

    * Returns the \c Ivar for a specified instance variable of a given class.

    *

    * @param cls The class whose instance variable you wish to obtain.

    * @param name The name of the instance variable definition to obtain.

    *

    * @return A pointer to an \c Ivar data structure containing information about

    *  the instance variable specified by \e name.

    */

    OBJC_EXPORTIvarclass_getInstanceVariable(Classcls,constchar*name)

    __OSX_AVAILABLE_STARTING(__MAC_10_0,__IPHONE_2_0);

    /**

    * Returns the Ivar for a specified class variable of a given class.

    *

    * @param cls The class definition whose class variable you wish to obtain.

    * @param name The name of the class variable definition to obtain.

    *

    * @return A pointer to an \c Ivar data structure containing information about the class variable specified by \e name.

    */

    OBJC_EXPORTIvarclass_getClassVariable(Classcls,constchar*name)

    __OSX_AVAILABLE_STARTING(__MAC_10_5,__IPHONE_2_0);

    /**

    * Describes the instance variables declared by a class.

    *

    * @param cls The class to inspect.

    * @param outCount On return, contains the length of the returned array.

    *  If outCount is NULL, the length is not returned.

    *

    * @return An array of pointers of type Ivar describing the instance variables declared by the class.

    *  Any instance variables declared by superclasses are not included. The array contains *outCount

    *  pointers followed by a NULL terminator. You must free the array with free().

    *

    *  If the class declares no instance variables, or cls is Nil, NULL is returned and *outCount is 0.

    */

    OBJC_EXPORTIvar*class_copyIvarList(Classcls,unsignedint*outCount)

    __OSX_AVAILABLE_STARTING(__MAC_10_5,__IPHONE_2_0);

    /**

    * Adds a new instance variable to a class.

    *

    * @return YES if the instance variable was added successfully, otherwise NO

    *         (for example, the class already contains an instance variable with that name).

    *

    * @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.

    * @note The instance variable's minimum alignment in bytes is 1<

    *       variable depends on the ivar's type and the machine architecture.

    *       For variables of any pointer type, pass log2(sizeof(pointer_type)).

    */

    OBJC_EXPORTBOOLclass_addIvar(Classcls,constchar*name,size_tsize,

    uint8_talignment,constchar*types)

    __OSX_AVAILABLE_STARTING(__MAC_10_5,__IPHONE_2_0);

    class_getInstanceVariable函数,它返回一个指向包含name指定的成员变量信息的objc_ivar结构体的指针(Ivar)。

    class_getClassVariable函数,目前没有找到关于Objective-C中类变量的信息,一般认为Objective-C不支持类变量。注意,返回的列表不包含父类的成员变量和属性。

    class_copyIvarList函数,它返回一个指向成员变量信息的数组,数组中每个元素是指向该成员变量信息的objc_ivar结构体的指针。这个数组不包含在父类中声明的变量。outCount指针返回数组的大小。需要注意的是,我们必须使用free()来释放这个数组。

    class_addIvar函数,我们知道Objective-C不支持往已存在的类中添加实例变量,因此不管是系统库提供的提供的类,还是我们自定义的类,都无法动态添加成员变量。但如果我们通过运行时来创建一个类的话,又应该如何给它添加成员变量呢?这时我们就可以使用class_addIvar函数了。不过需要注意的是,这个方法只能在objc_allocateClassPair函数与objc_registerClassPair之间调用。另外,这个类也不能是元类。成员变量的按字节最小对齐量是1<

    原文链接

    相关文章

      网友评论

        本文标题:iOS运行时runtime及相关Objective-C底层讲解

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