目录:

1:对象、类、元类

2:方法缓存(Method cache)

3:类别

正文

1:对象、类、元类,结构体

1:对象 objc_object

对象里面有一个isa指针,指向的是objc_class的指针。

2:类  objc_class

3:元类 metaClass

1:实例对象,类对象以及元类对象之间的isa指向和继承关系的规则为:

规则一: 实例对象的isa指向该类,类的isa指向元类(metaClass)

规则二: 类的superClass指向其父类,如果该类为根类则值为nil

规则三: 元类的isa指向根元类,如果该元类是根元类则指向自身

规则四: 元类的superClass指向父元类,若根元类则指向该根类

2:实例在寻找方法时的规则为:

当发送消息给实例对象时,消息是在寻找这个对象的类的方法列表(实例方法)

当发送消息给类对象时,消息是在寻找这个类的元类的方法列表(类方法)

3:总结

实例对象是类的实例,

类作为对象又是元类的实例。

元类是根元类的实例。

根元类是其自身的实例。

4:例对象、类对象、元类对象,有什么区别?

实例对象:当我们在代码中new一个实例对象时,拷贝了实例所属的类的成员变量,但不拷贝类定义的方法。调用实例方法时,根据实例的isa指针去寻找方法对应的函数指针。

类对象:是一个功能完整的对象。特殊之处在于它们是由程序员定义而在运行时由编译器创建的,它没有自己的实例变量(这里区别于类的成员变量,他们是属于实例对象的,而不是属于类对象的,类方法是属于类对象自己的),但类对象中存着成员变量与实例方法列表。

元类对象:OC 的类方法是使用元类的根本原因,因为其中存储着对应的类对象调用的方法即类方法。其他时候都倾向于隐藏元类,因此真实世界没有人发送消息给元类对象。元类的定义和创建看起来都是编译器自动完成的,无需人为干涉。

2:Objective-C在Runtime层的方法决议(Method resolving)过程和方法缓存(Method cache)

1:消息决议

消息发送

  1. 判断receiver是否为nil,也就是objc_msgSend的第一个参数self,也就是要调用的那个方法所属对象
  2. 从缓存里寻找,找到了则分发,否则
  3. 利用objc-class.mm中_class_lookupMethodAndLoadCache3(为什么有个这么奇怪的方法。本文末尾会解释)方法去寻找selector
    1. 如果支持GC,忽略掉非GC环境的方法(retain等)
    2. 从本class的method list寻找selector,如果找到,填充到缓存中,并返回selector,否则
    3. 寻找父类的method list,并依次往上寻找,直到找到selector,填充到缓存中,并返回selector,否则
    4. 调用_class_resolveMethod,如果可以动态resolve为一个selector,不缓存,方法返回,否则
    5. 转发这个selector,否则
  4. 报错,抛出异常

2:方法缓存

objc_cache的定义看起来很简单,它包含了下面三个变量:

  1. mask:可以认为是当前能达到的最大index(从0开始的),所以缓存的size(total)是mask+1
  2. occupied:被占用的槽位,因为缓存是以散列表的形式存在的,所以会有空槽,而occupied表示当前被占用的数目
  3. buckets:用数组表示的hash表,cache_entry类型,每一个cache_entry代表一个方法缓存

(buckets定义在objc_cache的最后,说明这是一个可变长度的数组)

cache_entry的定义:分别是:

  1. name,被缓存的方法名字
  2. imp,方法实现

3:总结

3.1:类的所有缓存都存在metaclass上,所以每个类都只有一份方法缓存,而不是每一个类的object都保存一份。

3.2:即便是从父类取到的方法,也会存在类本身的方法缓存里。而当用一个父类对象去调用那个方法的时候,也会在父类的metaclass里缓存一份。

3.3:static const int _class_slow_grow = 1;

注释中说明,当_class_slow_grow是非0值的时候,只有当方法缓存第奇数次满(使用的槽位超过3/4)的时候,方法缓存的大小才会增长(会清空缓存,否则hash值就不对了);当第偶数次满的时候,方法缓存会被清空并重新利用。 如果_class_slow_grow值为0,那么每一次方法缓存满的时候,其大小都会增长。

所以单就问题而言,答案是没有限制,虽然这个值被设置为1,方法缓存的大小增速会慢一点,但是确实是没有上限的。

3.4:为什么类的方法列表不直接做成散列表呢,做成list,还要单独缓存,多费事?

这个问题么,我觉得有以下三个原因:

  • 散列表是没有顺序的,Objective-C的方法列表是一个list,是有顺序的;Objective-C在查找方法的时候会顺着list依次寻找,并且category的方法在原始方法list的前面,需要先被找到,如果直接用hash存方法,方法的顺序就没法保证。
  • list的方法还保存了除了selector和imp之外其他很多属性
  • 散列表是有空槽的,会浪费空间

一、怎么理解OC是动态语言,Runtime又是什么?

1: Runtime 又叫运行时,

1.1: Runtime是一套底层纯C语言API,

1.2: OC代码最终都会被编译器转化为运行时代码,通过消息机制决定函数调用方式,这也是OC作为动态语言使用的基础。

1.3: OC代码被编译器转化为C语言,然后再通过运行时执行,最终实现了动态调用。

静态语言:如C语言,编译阶段就要决定调用哪个函数,如果函数未实现就会编译报错。

动态语言:如OC语言,编译阶段并不能决定真正调用哪个函数,只要函数声明过即使没有实现也不会报错。

我们常说OC是一门动态语言,就是因为它总是把一些决定性的工作从编译阶段推迟到运行时阶段。OC代码的运行不仅需要编译器,还需要运行时系统(Runtime Sytem)来执行编译后的代码。

二、理解消息机制的基本原理

OC的方法调用都是类似[receiver selector]的形式,其实每次都是一个运行时消息发送过程。

第一步:编译阶段
[receiver selector]方法被编译器转化,分为两种情况:
1.不带参数的方法被编译为:objc_msgSend(receiver,selector)
2.带参数的方法被编译为:objc_msgSend(recevier,selector,org1,org2,…)

第二步:运行时阶段
消息接收者recever寻找对应的selector,也分为两种情况:
1.接收者能找到对应的selector,直接执行接收receiver对象的selector方法。
2.接收者找不到对应的selector,消息被转发或者临时向接收者添加这个selector对应的实现内容,否则崩溃。

说明:OC调用方法[receiver selector],编译阶段确定了要向哪个接收者发送message消息,但是接收者如何响应决定于运行时的判断。

三、与Runtime的交互

Runtime的官方文档中将OC与Runtime的交互划分三种层次:OC源代码NSObject方法Runtime 函数。这其实也是按照与Runtime交互程度从低到高排序的三种方式。

1.OC源代码(Objec-C Source Code)

我们已经说过,OC代码会在编译阶段被编译器转化。OC中的类、方法和协议等在Runtime中都由一些数据结构来定义。所以,我们平时直接使用OC编写代码,其实这已经是在和Runtime进行交互了,只不过这个过程对于我们来说是无感的。

2.NSObject方法(NSObject Methods)

Runtime的最大特征就是实现了OC语言的动态特性。作为大部分Objective-C类继承体系的根类的NSObject,其本身就具有了一些非常具有运行时动态特性的方法,比如respondsToSelector:方法可以检查在代码运行阶段当前对象是否能响应指定的消息,所以使用这些方法也算是一种与Runtme的交互方式,类似的方法还有如下:

  1. -description//返回当前类的描述信息
  2. -class //方法返回对象的类;
  3. -isKindOfClass: -isMemberOfClass: //检查对象是否存在于指定的类的继承体系中
  4. -respondsToSelector: //检查对象能否响应指定的消息;
  5. -conformsToProtocol: //检查对象是否实现了指定协议类的方法;
  6. -methodForSelector: //返回指定方法实现的地址。

3.使用Runtime函数(Runtime Functions)

Runtime系统是一个由一系列函数和数据结构组成,具有公共接口的动态共享库。头文件存放于/usr/include/objc目录下。在我们工程代码里引用Runtime的头文件,同样能够实现类似OC代码的效果,一些代码示例如下:

  1. //相当于:Class class = [UIView class];
  2. Class viewClass = objc_getClass("UIView");
  3. //相当于:UIView *view = [UIView alloc];
  4. UIView *view = ((id (*)(id, SEL))(void *)objc_msgSend)((id)viewClass, sel_registerName("alloc"));
  5. //相当于:UIView *view = [view init];
  6. ((id (*)(id, SEL))(void *)objc_msgSend)((id)view, sel_registerName("init"));

三、分析Runtime中数据结构

OC代码被编译器转化为C语言,然后再通过运行时执行,最终实现了动态调用。这其中的OC类、对象和方法等都对应了C中的结构体,而且我们都可以在Rutime源码中找到它们的定义。

那么,我们如何来查看Runtime的代码呢?其实很简单,只需要我们在当前代码文件中引用头文件:

  1. #import <objc/runtime.h>
  2. #import <objc/message.h>

然后,我们需要使用组合键"Command +鼠标点击",即可进入Runtime的源码文件,下面我们继续来一一分析OC代码在C中对应的结构。

1.id—>objc_object

id是一个指向objc_object结构体的指针,即在Runtime中:

  1. ///A pointer to an instance of a class.
  2. typedef struct objc_object *id;

下面是Runtime中对objc_object结构体的具体定义:

  1. ///Represents an instance of a class.
  2. struct objc_object {
  3. Class _Nonnull isa OBJC_ISA_AVAILABILITY;
  4. };

我们都知道id在OC中是表示一个任意类型的类实例,从这里也可以看出,OC中的对象虽然没有明显的使用指针,但是在OC代码被编译转化为C之后,每个OC对象其实都是拥有一个isa的指针的。

2.Class - >objc_classs

class是一个指向objc_class结构体的指针,即在Runtime中:

  1. typedef struct objc_class *Class;

下面是Runtime中对objc_class结构体的具体定义:

  1. //usr/include/objc/runtime.h
  2. struct objc_class {
  3. Class _Nonnull isa OBJC_ISA_AVAILABILITY;
  4. #if !OBJC2
  5. Class Nullable super_class OBJC2UNAVAILABLE;
  6. const char * Nonnull name OBJC2UNAVAILABLE;
  7. long version OBJC2_UNAVAILABLE;
  8. long info OBJC2_UNAVAILABLE;
  9. long instance_size OBJC2_UNAVAILABLE;
  10. struct objc_ivar_list * Nullable ivars OBJC2UNAVAILABLE;
  11. struct objc_method_list * Nullable * _Nullable methodLists OBJC2UNAVAILABLE;
  12. struct objc_cache * Nonnull cache OBJC2UNAVAILABLE;
  13. struct objc_protocol_list * Nullable protocols OBJC2UNAVAILABLE;
  14. #endif
  15. } OBJC2_UNAVAILABLE;

理解objc_class定义中的参数:

isa指针:

我们会发现objc_class和objc_object同样是结构体,而且都拥有一个isa指针。我们很容易理解objc_object的isa指针指向对象的定义,那么objc_class的指针是怎么回事呢?
其实,在Runtime中Objc类本身同时也是一个对象。Runtime把类对象所属类型就叫做元类,用于描述类对象本身所具有的特征,最常见的类方法就被定义于此,所以objc_class中的isa指针指向的是元类,每个类仅有一个类对象,而每个类对象仅有一个与之相关的元类。

super_class指针:

super_class指针指向objc_class类所继承的父类,但是如果当前类已经是最顶层的类(如NSProxy),则super_class指针为NULL

cache:

为了优化性能,objc_class中的cache结构体用于记录每次使用类或者实例对象调用的方法。这样每次响应消息的时候,Runtime系统会优先在cache中寻找响应方法,相比直接在类的方法列表中遍历查找,效率更高。

ivars:

ivars用于存放所有的成员变量和属性信息,属性的存取方法都存放在methodLists中。

methodLists:

methodLists用于存放对象的所有成员方法。

3.SEL

SEL是一个指向objc_selector结构体的指针,即在Runtime中:

  1. /// An opaque type that represents a method selector.
  2. typedef struct objc_selector *SEL;

SEL在OC中称作方法选择器,用于表示运行时方法的名字,然而我们并不能在Runtime中找到它的结构体的详细定义。Objective-C在编译时,会依据每一个方法的名字、参数序列,生成一个唯一的整型标识(Int类型的地址),这个标识就是SEL。

注意
1.不同类中相同名字的方法对应的方法选择器是相同的。
2.即使是同一个类中,方法名相同而变量类型不同也会导致它们具有相同的方法选择器。

通常我们获取SEL有三种方法:
1.OC中,使用@selector(“方法名字符串”)
2.OC中,使用NSSelectorFromString(“方法名字符串”)
3.Runtime方法,使用sel_registerName(“方法名字符串”)

4.Ivar

Ivar代表类中实例变量的类型,是一个指向ojbcet_ivar的结构体的指针,即在Runtime中:

  1. /// An opaque type that represents an instance variable.
  2. typedef struct objc_ivar *Ivar;

下面是Runtime中对objc_ivar结构体的具体定义:

  1. struct objc_ivar {
  2. char * Nullable ivar_name OBJC2UNAVAILABLE;
  3. char * Nullable ivar_type OBJC2UNAVAILABLE;
  4. int ivar_offset OBJC2_UNAVAILABLE;
  5. #ifdef LP64
  6. int space OBJC2_UNAVAILABLE;
  7. #endif
  8. }

我们在objc_class中看到的ivars成员列表,其中的元素就是Ivar,我可以通过实例查找其在类中的名字,这个过程被称为反射,下面的class_copyIvarList获取的不仅有实例变量还有属性:

  1. Ivar *ivarList = class_copyIvarList([self class], &count);
  2. for (int i= 0; i<count; i++) {
  3. Ivar ivar = ivarList[i];
  4. const char *ivarName = ivar_getName(ivar);
  5. NSLog(@"Ivar(%d): %@", i, [NSString stringWithUTF8String:ivarName]);
  6. }
  7. free(ivarList);

5.Method

Method表示某个方法的类型,即在Runtime中:

  1. /// An opaque type that represents a method in a class definition.
  2. typedef struct objc_method *Method;

我们可以在objct_class定义中看到methodLists,其中的元素就是Method,下面是Runtime中objc_method结构体的具体定义:

  1. struct objc_method {
  2. SEL Nonnull method_name OBJC2UNAVAILABLE;
  3. char * Nullable method_types OBJC2UNAVAILABLE;
  4. IMP Nonnull method_imp OBJC2UNAVAILABLE;
  5. } OBJC2_UNAVAILABLE;

理解objc_method定义中的参数:
method_name:方法名类型SEL
method_types: 一个char指针,指向存储方法的参数类型和返回值类型
method_imp:本质上是一个指针,指向方法的实现
这里其实就是SEL(method_name)与IMP(method_name)形成了一个映射,通过SEL,我们可以很方便的找到方法实现IMP。

5.IMP

IMP是一个函数指针,它在Runtime中的定义如下:

  1. /// A pointer to the function of a method implementation.
  2. typedef void (IMP)(void / id, SEL, ... */ );

IMP这个函数指针指向了方法实现的首地址,当OC发起消息后,最终执行的代码是由IMP指针决定的。利用这个特性,我们可以对代码进行优化:当需要大量重复调用方法的时候,我们可以绕开消息绑定而直接利用IMP指针调起方法,这样的执行将会更加高效,相关的代码示例如下:

  1. void (*setter)(id, SEL, BOOL);
  2. int i;
  3. setter = (void (*)(id, SEL, BOOL))[target methodForSelector:@selector(setFilled:)];
  4. for ( i = 0 ; i < 1000 ; i++ )
  5. setter(targetList[i], @selector(setFilled:), YES);

注意:这里需要注意的就是函数指针的前两个参数必须是id和SEL。

四、深入理解Rutime消息发送

我们在分析了OC语言对应的底层C结构之后,现在可以进一步理解运行时的消息发送机制。先前讲到,OC调用方法被编译转化为如下的形式:

  1. id _Nullable objc_msgSend(id _Nullable self, SEL _Nonnull op, ...)

其实,除了常见的objc_msgSend,消息发送的方法还有objc_msgSend_stret,objc_msgSendSuper,objc_msgSendSuper_stret等,如果消息传递给超类就使用带有super的方法,如果返回值是结构体而不是简单值就使用带有stret的值。

运行时阶段的消息发送的详细步骤如下

  1. 检测selector 是不是需要忽略的。比如 Mac OS X 开发,有了垃圾回收就不理会retain,release 这些函数了。
  2. 检测target 是不是nil 对象。ObjC 的特性是允许对一个 nil对象执行任何一个方法不会 Crash,因为会被忽略掉。
  3. 如果上面两个都过了,那就开始查找这个类的 IMP,先从 cache 里面找,若可以找得到就跳到对应的函数去执行。
  4. 如果在cache里找不到就找一下方法列表methodLists。
  5. 如果methodLists找不到,就到超类的方法列表里寻找,一直找,直到找到NSObject类为止。
  6. 如果还找不到,Runtime就提供了如下三种方法来处理:动态方法解析消息接受者重定向消息重定向,这三种方法的调用关系如下图:
     
    消息转发流程图.png

1.动态方法解析(Dynamic Method Resolution)

所谓动态解析,我们可以理解为通过cache和方法列表没有找到方法时,Runtime为我们提供一次动态添加方法实现的机会,主要用到的方法如下:

  1. //OC方法:
  2. //类方法未找到时调起,可于此添加类方法实现
  3. + (BOOL)resolveClassMethod:(SEL)sel
  4. //实例方法未找到时调起,可于此添加实例方法实现
  5. + (BOOL)resolveInstanceMethod:(SEL)sel
  6. //Runtime方法:
  7. /**
  8. 运行时方法:向指定类中添加特定方法实现的操作
  9. @param cls 被添加方法的类
  10. @param name selector方法名
  11. @param imp 指向实现方法的函数指针
  12. @param types imp函数实现的返回值与参数类型
  13. @return 添加方法是否成功
  14. */
  15. BOOL class_addMethod(Class _Nullable cls,
  16. SEL _Nonnull name,
  17. IMP _Nonnull imp,
  18. const char * _Nullable types)

下面使用一个示例来说明动态解析:Perosn类中声明方法却未添加实现,我们通过Runtime动态方法解析的操作为其他添加方法实现,具体代码如下:

  1. //Person.h文件
  2. @interface Person : NSObject
  3. //声明类方法,但未实现
  4. + (void)haveMeal:(NSString *)food;
  5. //声明实例方法,但未实现
  6. - (void)singSong:(NSString *)name;
  7. @end
  1. //Person.m文件
  2. #import "Person.h"
  3. #import <objc/runtime.h>
  4. @implementation Person
  5. //重写父类方法:处理类方法
  6. + (BOOL)resolveClassMethod:(SEL)sel{
  7. if(sel == @selector(haveMeal:)){
  8. class_addMethod(object_getClass(self), sel, class_getMethodImplementation(object_getClass(self), @selector(zs_haveMeal:)), "v@");
  9. return YES; //添加函数实现,返回YES
  10. }
  11. return [class_getSuperclass(self) resolveClassMethod:sel];
  12. }
  13. //重写父类方法:处理实例方法
  14. + (BOOL)resolveInstanceMethod:(SEL)sel{
  15. if(sel == @selector(singSong:)){
  16. class_addMethod([self class], sel, class_getMethodImplementation([self class], @selector(zs_singSong:)), "v@");
  17. return YES;
  18. }
  19. return [super resolveInstanceMethod:sel];
  20. }
  21. + (void)zs_haveMeal:(NSString *)food{
  22. NSLog(@"%s",__func__);
  23. }
  24. - (void)zs_singSong:(NSString *)name{
  25. NSLog(@"%s",__func__);
  26. }
  1. //TestViewController.m文件
  2. //测试:Peson调用并未实现的类方法、实例方法,并没有崩溃
  3. Person *ps = [[Person alloc] init];
  4. [Person haveMeal:@"Apple"]; //打印:+[Person zs_haveMeal:]
  5. [ps singSong:@"纸短情长"]; //打印:-[Person zs_singSong:]

注意1:我们注意到class_addMethod方法中的特殊参数“v@”,具体可参考这里
注意2:成功使用动态方法解析还有个前提,那就是我们必须存在可以处理消息的方法,比如上述代码中的zs_haveMeal:与zs_singSong:

2.消息接收者重定向

我们注意到动态方法解析过程中的两个resolve方法都返回了布尔值,当它们返回YES时方法即可正常执行,但是若它们返回NO,消息发送机制就进入了消息转发(Forwarding)的阶段了,我们可以使用Runtime通过下面的方法替换消息接收者的为其他对象,从而保证程序的继续执行。

  1. //重定向类方法的消息接收者,返回一个类
  2. + (id)forwardingTargetForSelector:(SEL)aSelector
  3. //重定向实例方法的消息接受者,返回一个实例对象
  4. - (id)forwardingTargetForSelector:(SEL)aSelector

下面使用一个示例来说明消息接收者的重定向:
我们创建一个Student类,声明并实现takeExam:、learnKnowledge:两个方法,然后在视图控制器TestViewController(一个继承了UIViewController的自定义类)里测试,关键代码如下:

  1. //Student.h文件
  2. @interface Student : NSObject
  3. //类方法:参加考试
  4. + (void)takeExam:(NSString *)exam;
  5. //实例方法:学习知识
  6. - (void)learnKnowledge:(NSString *)course;
  7. @end
  1. // Student.m文件
  2. @implementation Student
  3. + (void)takeExam:(NSString *)exam{
  4. NSLog(@"%s",__func__);
  5. }
  6. - (void)learnKnowledge:(NSString *)course{
  7. NSLog(@"%s",__func__);
  8. }
  9. @end
  1. //TestViewConroller.m文件
  2. //重定向类方法:返回一个类对象
  3. + (id)forwardingTargetForSelector:(SEL)aSelector{
  4. if (aSelector == @selector(takeExam:)) {
  5. return [Student class];
  6. }
  7. return [super forwardingTargetForSelector:aSelector];
  8. }
  9. //重定向实例方法:返回类的实例
  10. - (id)forwardingTargetForSelector:(SEL)aSelector{
  11. if (aSelector == @selector(learnKnowledge:)) {
  12. return self.student;
  13. }
  14. return [super forwardingTargetForSelector:aSelector];
  15. }
  16. //在TestViewConroller的viewDidLoad中测试:
  17. //调用并未声明和实现的类方法
  18. [TestViewController performSelector:@selector(takeExam:) withObject:@"语文"];
  19. //调用并未声明和实现的类方法
  20. self.student = [[Student alloc] init];
  21. [self performSelector:@selector(learnKnowledge:) withObject:@"天文学知识"];
  22. //正常打印:
  23. // +[Student takeExam:]
  24. // -[Student learnKnowledge:]

注意:动态方法解析阶段返回NO时,我们可以通过forwardingTargetForSelector可以修改消息的接收者,该方法返回参数是一个对象,如果这个对象是非nil,非self,系统会将运行的消息转发给这个对象执行。否则,继续查找其他流程。

3.消息重定向

当以上两种方法无法生效,那么这个对象会因为找不到相应的方法实现而无法响应消息,此时Runtime系统会通过forwardInvocation:消息通知该对象,给予此次消息发送最后一次寻找IMP的机会:

  1. - (void)forwardInvocation:(NSInvocation *)anInvocation

其实每个对象都从NSObject类中继承了forwardInvocation:方法,但是NSObject中的这个方法只是简单的调用了doesNotRecongnizeSelector:方法,提示我们错误。所以我们可以重写这个方法:对不能处理的消息做一些默认处理,也可以将消息转发给其他对象来处理,而不抛出错误。

我们注意到anInvocation是forwardInvocation唯一参数,它封装了原始的消息和消息参数。正是因为它,我们还不得不重写另一个函数:methodSignatureForSelector。这是因为在forwardInvocation: 消息发送前,Runtime系统会向对象发送methodSignatureForSelector消息,并取到返回的方法签名用于生成NSInvocation对象。

下面使用一个示例来重新定义转发逻辑:在上面的TestViewController添加如下代码:

  1. -(void)forwardInvocation:(NSInvocation *)anInvocation{
  2. //1.从anInvocation中获取消息
  3. SEL sel = anInvocation.selector;
  4. //2.判断Student方法是否可以响应应sel
  5. if ([self.student respondsToSelector:sel]) {
  6. //2.1若可以响应,则将消息转发给其他对象处理
  7. [anInvocation invokeWithTarget:self.student];
  8. }else{
  9. //2.2若仍然无法响应,则报错:找不到响应方法
  10. [self doesNotRecognizeSelector:sel];
  11. }
  12. }
  13. //需要从这个方法中获取的信息来创建NSInvocation对象,因此我们必须重写这个方法,为给定的selector提供一个合适的方法签名。
  14. - (NSMethodSignature*)methodSignatureForSelector:(SEL)aSelector{
  15. NSMethodSignature *methodSignature = [super methodSignatureForSelector:aSelector];
  16. if (!methodSignature) {
  17. methodSignature = [NSMethodSignature signatureWithObjCTypes:"v@:*"];
  18. }
  19. return methodSignature;
  20. }

然后再在视图控制器里直接调用Student的方法如下:

  1. //self是当前的TestViewController,调用了自己并不存在的learnKonwledge:方法
  2. [self performSelector:@selector(learnKnowledge:) withObject:@"天文学”];
  3. //正常打印:
  4. //-[Student learnKnowledge:]

总结:

1.从以上的代码中就可以看出,forwardingTargetForSelector仅支持一个对象的返回,也就是说消息只能被转发给一个对象,而forwardInvocation可以将消息同时转发给任意多个对象,这就是两者的最大区别。

2.虽然理论上可以重载doesNotRecognizeSelector函数实现保证不抛出异常(不调用super实现),但是苹果文档着重提出“一定不能让这个函数就这么结束掉,必须抛出异常”。(If you override this method, you must call super or raise an invalidArgumentException exception at the end of your implementation. In other words, this method must not return normally; it must always result in an exception being thrown.)

3.forwardInvocation甚至能够修改消息的内容,用于实现更加强大的功能。

六、多继承的实现思路:Runtime

我们会发现Runtime消息转发的一个特点:一个对象可以调起它本身不具备的方法。这个过程与OC中的继承特性很相似,其实官方文档中图示也很好的说明了这个问题:

图中的Warrior通过forwardInvocation:将negotiate消息转发给了Diplomat,这就好像是Warrior使用了超类Diplomat的方法一样。所以从这个思路,我们可以在实际开发需求中模拟多继承的操作。

七:应用

一、动态方法交换:Method Swizzling

实现动态方法交换(Method Swizzling )是Runtime中最具盛名的应用场景,其原理是:通过Runtime获取到方法实现的地址,进而动态交换两个方法的功能。使用到关键方法如下:

  1. //获取类方法的Mthod
  2. Method _Nullable class_getClassMethod(Class _Nullable cls, SEL _Nonnull name)
  3. //获取实例对象方法的Mthod
  4. Method _Nullable class_getInstanceMethod(Class _Nullable cls, SEL _Nonnull name)
  5. //交换两个方法的实现
  6. void method_exchangeImplementations(Method _Nonnull m1, Method _Nonnull m2)

1.动态方法交换示例

现在演示一个代码示例:在视图控制中,定义两个实例方法printA与printB,然后执行交换

  1. - (void)printA{
  2. NSLog(@"打印A......");
  3. }
  4. - (void)printB{
  5. NSLog(@"打印B......");
  6. }
  7. //交换方法的实现,并测试打印
  8. Method methodA = class_getInstanceMethod([self class], @selector(printA));
  9. Method methodB = class_getInstanceMethod([self class], @selector(printB));
  10. method_exchangeImplementations(methodA, methodB);
  11. [self printA]; //打印B......
  12. [self printB]; //打印A......

2.拦截并替换系统方法

Runtime动态方法交换更多的是应用于系统类库和第三方框架的方法替换。在不可见源码的情况下,我们可以借助Rutime交换方法实现,为原有方法添加额外功能,这在实际开发中具有十分重要的意义。

下面将展示一个拦截并替换系统方法的示例:为了实现不同机型上的字体都按照比例适配,我们可以拦截系统UIFont的systemFontOfSize方法,具体操作如下:

步骤1:在当前工程中添加UIFont的分类:UIFont +Adapt,并在其中添用以替换的方法。

  1. + (UIFont *)zs_systemFontOfSize:(CGFloat)fontSize{
  2. //获取设备屏幕宽度,并计算出比例scale
  3. CGFloat width = [[UIScreen mainScreen] bounds].size.width;
  4. CGFloat scale = width/375.0;
  5. //注意:由于方法交换,系统的方法名已变成了自定义的方法名,所以这里使用了
  6. //自定义的方法名来获取UIFont
  7. return [UIFont zs_systemFontOfSize:fontSize * scale];
  8. }

步骤2:在UIFont的分类中拦截系统方法,将其替换为我们自定义的方法,代码如下:

  1. //load方法不需要手动调用,iOS会在应用程序启动的时候自动调起load方法,而且执行时间较早,所以在此方法中执行交换操作比较合适。
  2. + (void)load{
  3. //获取系统方法地址
  4. Method sytemMethod = class_getClassMethod([UIFont class], @selector(systemFontOfSize:));
  5. //获取自定义方法地址
  6. Method customMethod = class_getClassMethod([UIFont class], @selector(zs_systemFontOfSize:));
  7. //交换两个方法的实现
  8. method_exchangeImplementations(sytemMethod, customMethod);
  9. }

添加一段测试代码,切换不同的模拟器,观察在不同机型上文字的大小:

  1. UILabel *label = [[UILabel alloc] initWithFrame:CGRectMake(0, 100, 300, 50)];
  2. label.text = @"测试Runtime拦截方法";
  3. label.font = [UIFont systemFontOfSize:20];
  4. [self.view addSubview:label];

二、实现分类添加新属性

我们在开发中常常使用类目Category为一些已有的类扩展功能。虽然继承也能够为已有类增加新的方法,而且相比类目更是具有增加属性的优势,但是继承毕竟是一个重量级的操作,添加不必要的继承关系无疑增加了代码的复杂度。

遗憾的是,OC的类目并不支持直接添加属性,如果我们直接在分类的声明中写入Property属性,那么只能为其生成set与get方法声明,却不能生成成员变量,直接调用这些属性还会造成崩溃。

所以为了实现给分类添加属性,我们还需借助Runtime的关联对象(Associated Objects)特性,它能够帮助我们在运行阶段将任意的属性关联到一个对象上,下面是相关的三个方法:

  1. /**
  2. 1.给对象设置关联属性
  3. @param object 需要设置关联属性的对象,即给哪个对象关联属性
  4. @param key 关联属性对应的key,可通过key获取这个属性,
  5. @param value 给关联属性设置的值
  6. @param policy 关联属性的存储策略(对应Property属性中的assign,copy,retain等)
  7. OBJC_ASSOCIATION_ASSIGN @property(assign)。
  8. OBJC_ASSOCIATION_RETAIN_NONATOMIC @property(strong, nonatomic)。
  9. OBJC_ASSOCIATION_COPY_NONATOMIC @property(copy, nonatomic)。
  10. OBJC_ASSOCIATION_RETAIN @property(strong,atomic)。
  11. OBJC_ASSOCIATION_COPY @property(copy, atomic)。
  12. */
  13. void objc_setAssociatedObject(id _Nonnull object,
  14. const void * _Nonnull key,
  15. id _Nullable value,
  16. objc_AssociationPolicy policy)
  17. /**
  18. 2.通过key获取关联的属性
  19. @param object 从哪个对象中获取关联属性
  20. @param key 关联属性对应的key
  21. @return 返回关联属性的值
  22. */
  23. id _Nullable objc_getAssociatedObject(id _Nonnull object,
  24. const void * _Nonnull key)
  25. /**
  26. 3.移除对象所关联的属性
  27. @param object 移除某个对象的所有关联属性
  28. */
  29. void objc_removeAssociatedObjects(id _Nonnull object)

注意:key与关联属性一一对应,我们必须确保其全局唯一性,常用我们使用@selector(methodName)作为key。

现在演示一个代码示例:为UIImage增加一个分类:UIImage+Tools,并为其设置关联属性urlString(图片网络链接属性),相关代码如下:

  1. //UIImage+Tools.h文件中
  2. UIImage+Tools.m
  3. @interface UIImage (Tools)
  4. //添加一个新属性:图片网络链接
  5. @property(nonatomic,copy)NSString *urlString;
  6. @end
  1. //UIImage+Tools.m文件中
  2. #import "UIImage+Tools.h"
  3. #import <objc/runtime.h>
  4. @implementation UIImage (Tools)
  5. //set方法
  6. - (void)setUrlString:(NSString *)urlString{
  7. objc_setAssociatedObject(self,
  8. @selector(urlString),
  9. urlString,
  10. OBJC_ASSOCIATION_RETAIN_NONATOMIC);
  11. }
  12. //get方法
  13. - (NSString *)urlString{
  14. return objc_getAssociatedObject(self,
  15. @selector(urlString));
  16. }
  17. //添加一个自定义方法,用于清除所有关联属性
  18. - (void)clearAssociatedObjcet{
  19. objc_removeAssociatedObjects(self);
  20. }
  21. @end

测试文件中:

  1. UIImage *image = [[UIImage alloc] init];
  2. image.urlString = @"http://www.image.png";
  3. NSLog(@"获取关联属性:%@",image.urlString);
  4. [image clearAssociatedObjcet];
  5. NSLog(@"获取关联属性:%@",image.urlString);
  6. //打印:
  7. //获取关联属性:http://www.image.png
  8. // 获取关联属性:(null)

三、获取类的详细信息

1.获取属性列表

  1. unsigned int count;
  2. objc_property_t *propertyList = class_copyPropertyList([self class], &count);
  3. for (unsigned int i = 0; i<count; i++) {
  4. const char *propertyName = property_getName(propertyList[i]);
  5. NSLog(@"PropertyName(%d): %@",i,[NSString stringWithUTF8String:propertyName]);
  6. }
  7. free(propertyList);

2.获取所有成员变量

  1. Ivar *ivarList = class_copyIvarList([self class], &count);
  2. for (int i= 0; i<count; i++) {
  3. Ivar ivar = ivarList[i];
  4. const char *ivarName = ivar_getName(ivar);
  5. NSLog(@"Ivar(%d): %@", i, [NSString stringWithUTF8String:ivarName]);
  6. }
  7. free(ivarList);

3.获取所有方法

  1. Method *methodList = class_copyMethodList([self class], &count);
  2. for (unsigned int i = 0; i<count; i++) {
  3. Method method = methodList[i];
  4. SEL mthodName = method_getName(method);
  5. NSLog(@"MethodName(%d): %@",i,NSStringFromSelector(mthodName));
  6. }
  7. free(methodList);

4.获取当前遵循的所有协议

  1. __unsafe_unretained Protocol **protocolList = class_copyProtocolList([self class], &count);
  2. for (int i=0; i<count; i++) {
  3. Protocol *protocal = protocolList[i];
  4. const char *protocolName = protocol_getName(protocal);
  5. NSLog(@"protocol(%d): %@",i, [NSString stringWithUTF8String:protocolName]);
  6. }
  7. free(propertyList);

注意:C语言中使用Copy操作的方法,要注意释放指针,防止内存泄漏

四、解决同一方法高频率调用的效率问题

Runtime源码中的IMP作为函数指针,指向方法的实现。通过它,我们可以绕开发送消息的过程来提高函数调用的效率。当我们需要持续大量重复调用某个方法的时候,会十分有用,具体代码示例如下:

  1. void (*setter)(id, SEL, BOOL);
  2. int i;
  3. setter = (void (*)(id, SEL, BOOL))[target methodForSelector:@selector(setFilled:)];
  4. for ( i = 0 ; i < 1000 ; i++ )
  5. setter(targetList[i], @selector(setFilled:), YES);

五、方法动态解析与消息转发

其实该部分可以参考基础篇中内容,这里不再重复赘述,只是大概做出一些总结。

1.动态方法解析:动态添加方法

Runtime足够强大,能够让我们在运行时动态添加一个未实现的方法,这个功能主要有两个应用场景:
场景1:动态添加未实现方法,解决代码中因为方法未找到而报错的问题;
场景2:利用懒加载思路,若一个类有很多个方法,同时加载到内存中会耗费资源,可以使用动态解析添加方法。方法动态解析主要用到的方法如下:

  1. //OC方法:
  2. //类方法未找到时调起,可于此添加类方法实现
  3. + (BOOL)resolveClassMethod:(SEL)sel
  4. //实例方法未找到时调起,可于此添加实例方法实现
  5. + (BOOL)resolveInstanceMethod:(SEL)sel
  6. //Runtime方法:
  7. /**
  8. 运行时方法:向指定类中添加特定方法实现的操作
  9. @param cls 被添加方法的类
  10. @param name selector方法名
  11. @param imp 指向实现方法的函数指针
  12. @param types imp函数实现的返回值与参数类型
  13. @return 添加方法是否成功
  14. */
  15. BOOL class_addMethod(Class _Nullable cls,
  16. SEL _Nonnull name,
  17. IMP _Nonnull imp,
  18. const char * _Nullable types)

2.解决方法无响应崩溃问题

执行OC方法其实就是一个发送消息的过程,若方法未实现,我们可以利用方法动态解析与消息转发来避免程序崩溃,这主要涉及下面一个处理未实现消息的过程:

 
消息转发流程图.png

除了上述的方法动态解析,还使用到的相关方法如下:
消息接收者重定向

  1. //重定向类方法的消息接收者,返回一个类
  2. - (id)forwardingTargetForSelector:(SEL)aSelector
  3. //重定向实例方法的消息接受者,返回一个实例对象
  4. - (id)forwardingTargetForSelector:(SEL)aSelector

消息重定向

  1. - (void)forwardInvocation:(NSInvocation *)anInvocation
  2. - (NSMethodSignature*)methodSignatureForSelector:(SEL)aSelector;

六、动态操作属性

1.动态修改属性变量

现在假设这样一个情况:我们使用第三方框架里的Person类,在特殊需求下想要更改其私有属性nickName,这样的操作我们就可以使用Runtime可以动态修改对象属性。

基本思路:首先使用Runtime获取Peson对象的所有属性,找到nickName,然后使用ivar的方法修改其值。具体的代码示例如下:

  1. Person *ps = [[Person alloc] init];
  2. NSLog(@"ps-nickName: %@",[ps valueForKey:@"nickName"]); //null
  3. //第一步:遍历对象的所有属性
  4. unsigned int count;
  5. Ivar *ivarList = class_copyIvarList([ps class], &count);
  6. for (int i= 0; i<count; i++) {
  7. //第二步:获取每个属性名
  8. Ivar ivar = ivarList[i];
  9. const char *ivarName = ivar_getName(ivar);
  10. NSString *propertyName = [NSString stringWithUTF8String:ivarName];
  11. if ([propertyName isEqualToString:@"_nickName"]) {
  12. //第三步:匹配到对应的属性,然后修改;注意属性带有下划线
  13. object_setIvar(ps, ivar, @"梧雨北辰");
  14. }
  15. }
  16. NSLog(@"ps-nickName: %@",[ps valueForKey:@"nickName"]); //梧雨北辰

总结:此过程类似KVC的取值和赋值

2.实现 NSCoding 的自动归档和解档

归档是一种常用的轻量型文件存储方式,但是它有个弊端:在归档过程中,若一个Model有多个属性,我们不得不对每个属性进行处理,非常繁琐。
归档操作主要涉及两个方法:encodeObject 和 decodeObjectForKey,现在,我们可以利用Runtime来改进它们,关键的代码示例如下:

  1. //原理:使用Runtime动态获取所有属性
  2. //解档操作
  3. - (instancetype)initWithCoder:(NSCoder *)aDecoder{
  4. self = [super init];
  5. if (self) {
  6. unsigned int count = 0;
  7. Ivar *ivarList = class_copyIvarList([self class], &count);
  8. for (int i = 0; i < count; i++) {
  9. Ivar ivar = ivarList[i];
  10. const char *ivarName = ivar_getName(ivar);
  11. NSString *key = [NSString stringWithUTF8String:ivarName];
  12. id value = [aDecoder decodeObjectForKey:key];
  13. [self setValue:value forKey:key];
  14. }
  15. free(ivarList); //释放指针
  16. }
  17. return self;
  18. }
  19. //归档操作
  20. - (void)encodeWithCoder:(NSCoder *)aCoder{
  21. unsigned int count = 0;
  22. Ivar *ivarList = class_copyIvarList([self class], &count);
  23. for (NSInteger i = 0; i < count; i++) {
  24. Ivar ivar = ivarList[i];
  25. NSString *key = [NSString stringWithUTF8String:ivar_getName(ivar)];
  26. id value = [self valueForKey:key];
  27. [aCoder encodeObject:value forKey:key];
  28. }
  29. free(ivarList); //释放指针
  30. }

下面是有关归档的测试代码:

  1. //--测试归档
  2. Person *ps = [[Person alloc] init];
  3. ps.name = @"梧雨北辰";
  4. ps.age = 18;
  5. NSString *temp = NSTemporaryDirectory();
  6. NSString *fileTemp = [temp stringByAppendingString:@"person.archive"];
  7. [NSKeyedArchiver archiveRootObject:ps toFile:fileTemp];
  8. //--测试解档
  9. NSString *temp = NSTemporaryDirectory();
  10. NSString *fileTemp = [temp stringByAppendingString:@"person.henry"];
  11. Person *person = [NSKeyedUnarchiver unarchiveObjectWithFile:fileTemp];
  12. NSLog(@"person-name:%@,person-age:%ld",person.name,person.age);
  13. //person-name:梧雨北辰,person-age:18

3.实现字典与模型的转换

字典数据转模型的操作在项目开发中很常见,通常我们会选择第三方如YYModel;其实我们也可以自己来实现这一功能,主要的思路有两种:KVC、Runtime,总结字典转化模型过程中需要解决的问题如下:

 
字典转模型.png

现在,我们使用Runtime来实现字典转模型的操作,大致的思路是这样:
借助Runtime可以动态获取成员列表的特性,遍历模型中所有属性,然后以获取到的属性名为key,在JSON字典中寻找对应的值value;再将每一个对应Value赋值给模型,就完成了字典转模型的目的

首先准备下面的JSON数据用于测试:

  1. {
  2. "id":"2462079046",
  3. "name": "梧雨北辰",
  4. "age":"18",
  5. "weight":140,
  6. "address":{
  7. "country":"中国",
  8. "province": "河南"
  9. },
  10. "courses":[{
  11. "name":"Chinese",
  12. "desc":"语文课"
  13. },{
  14. "name":"Math",
  15. "desc":"数学课"
  16. },{
  17. "name":"English",
  18. "desc":"英语课"
  19. }
  20. ]
  21. }

具体的代码实现流程如下:

步骤1:创建NSObject的类目NSObject+ZSModel,用于实现字典转模型
  1. @interface NSObject (ZSModel)
  2. + (instancetype)zs_modelWithDictionary:(NSDictionary *)dictionary;
  3. @end
  4. //ZSModel协议,协议方法可以返回一个字典,表明特殊字段的处理规则
  5. @protocol ZSModel<NSObject>
  6. @optional
  7. + (nullable NSDictionary<NSString *, id> *)modelContainerPropertyGenericClass;
  8. @end;
  1. #import "NSObject+ZSModel.h"
  2. #import <objc/runtime.h>
  3. @implementation NSObject (ZSModel)
  4. + (instancetype)zs_modelWithDictionary:(NSDictionary *)dictionary{
  5. //创建当前模型对象
  6. id object = [[self alloc] init];
  7. //1.获取当前对象的成员变量列表
  8. unsigned int count = 0;
  9. Ivar *ivarList = class_copyIvarList([self class], &count);
  10. //2.遍历ivarList中所有成员变量,以其属性名为key,在字典中查找Value
  11. for (int i= 0; i<count; i++) {
  12. //2.1获取成员属性
  13. Ivar ivar = ivarList[i];
  14. NSString *ivarName = [NSString stringWithUTF8String:ivar_getName(ivar)] ;
  15. //2.2截取成员变量名:去掉成员变量前面的"_"号
  16. NSString *propertyName = [ivarName substringFromIndex:1];
  17. //2.3以属性名为key,在字典中查找value
  18. id value = dictionary[propertyName];
  19. //3.获取成员变量类型, 因为ivar_getTypeEncoding获取的类型是"@\"NSString\""的形式
  20. //所以我们要做以下的替换
  21. NSString *ivarType = [NSString stringWithUTF8String:ivar_getTypeEncoding(ivar)];// 替换:
  22. //3.1去除转义字符:@\"name\" -> @"name"
  23. ivarType = [ivarType stringByReplacingOccurrencesOfString:@"\"" withString:@""];
  24. //3.2去除@符号
  25. ivarType = [ivarType stringByReplacingOccurrencesOfString:@"@" withString:@""];
  26. //4.对特殊成员变量进行处理:
  27. //判断当前类是否实现了协议方法,获取协议方法中规定的特殊变量的处理方式
  28. NSDictionary *perpertyTypeDic;
  29. if([self respondsToSelector:@selector(modelContainerPropertyGenericClass)]){
  30. perpertyTypeDic = [self performSelector:@selector(modelContainerPropertyGenericClass) withObject:nil];
  31. }
  32. //4.1处理:字典的key与模型属性不匹配的问题,如id->uid
  33. id anotherName = perpertyTypeDic[propertyName];
  34. if(anotherName && [anotherName isKindOfClass:[NSString class]]){
  35. value = dictionary[anotherName];
  36. }
  37. //4.2.处理:模型嵌套模型
  38. if ([value isKindOfClass:[NSDictionary class]] && ![ivarType hasPrefix:@"NS"]) {
  39. Class modelClass = NSClassFromString(ivarType);
  40. if (modelClass != nil) {
  41. //将被嵌套字典数据也转化成Model
  42. value = [modelClass zs_modelWithDictionary:value];
  43. }
  44. }
  45. //4.3处理:模型嵌套模型数组
  46. //判断当前Vaue是一个数组,而且存在协议方法返回了perpertyTypeDic
  47. if ([value isKindOfClass:[NSArray class]] && perpertyTypeDic) {
  48. Class itemModelClass = perpertyTypeDic[propertyName];
  49. //封装数组:将每一个子数据转化为Model
  50. NSMutableArray *itemArray = @[].mutableCopy;
  51. for (NSDictionary *itemDic in value) {
  52. id model = [itemModelClass zs_modelWithDictionary:itemDic];
  53. [itemArray addObject:model];
  54. }
  55. value = itemArray;
  56. }
  57. //5.使用KVC方法将Vlue更新到object中
  58. if (value != nil) {
  59. [object setValue:value forKey:propertyName];
  60. }
  61. }
  62. free(ivarList); //释放C指针
  63. return object;
  64. }
  65. @end
步骤2:分别创建各个数据模型Student、Address、Course

Student类

  1. //Student.h文件
  2. #import "NSObject+ZSModel.h"
  3. #import "AddressModel.h"
  4. #import "CourseModel.h"
  5. @interface StudentModel : NSObject<ZSModel> //遵循协议
  6. //普通属性
  7. @property (nonatomic, copy) NSString *uid;
  8. @property(nonatomic,copy)NSString *name;
  9. @property (nonatomic, assign) NSInteger age;
  10. //嵌套模型
  11. @property (nonatomic, strong) AddressModel *address;
  12. //嵌套模型数组
  13. @property (nonatomic, strong) NSArray *courses;
  14. @end
  1. #import "StudentModel.h"
  2. @implementation StudentModel
  3. + (NSDictionary *)modelContainerPropertyGenericClass {
  4. //需要特别处理的属性
  5. return @{@"courses" : [CourseModel class],@"uid":@"id"};
  6. }
  7. @end

Address类

  1. //AddressModel.h文件
  2. @interface AddressModel : NSObject
  3. @property (nonatomic, copy) NSString *country; //国籍
  4. @property (nonatomic, copy) NSString *province; //省份
  5. @property (nonatomic, copy) NSString *city; //城市
  6. @end
  7. //-----------------优美的分割线------------------------
  8. //AddressModel.m文件
  9. #import "AddressModel.h"
  10. @implementation AddressModel
  11. @end

Course类

  1. @interface CourseModel : NSObject
  2. @property (nonatomic, copy) NSString *name; //课程名
  3. @property (nonatomic, copy) NSString *desc; //课程介绍
  4. @end
  5. //-----------------优美的分割线------------------------
  6. #import "CourseModel.h"
  7. @implementation CourseModel
  8. @end
步骤4:测试字典转模型操作
  1. //读取JSON数据
  2. NSDictionary *jsonData = [FileTools getDictionaryFromJsonFile:@"Student"];
  3. NSLog(@"%@",jsonData);
  4. //字典转模型
  5. StudentModel *student = [StudentModel zs_modelWithDictionary:jsonData];
  6. CourseModel *courseModel = student.courses[0];
  7. NSLog(@"%@",courseModel.name);

效果如下:

 
测试字典转模型操作.png

最后总结

以上就是我们在实际开发中常用的Runtime的操作了,Runtime的强大作用远不止如此。深入的了解和学习Runtime,不仅仅有助于iOS开发,而且对于理解编程语言的底层原理也十分有用,Keep Learning!~

18 (OC)* RunTime的更多相关文章

  1. OC - runtime 之关联对象

    header{font-size:1em;padding-top:1.5em;padding-bottom:1.5em} .markdown-body{overflow:hidden} .markdo ...

  2. OC Runtime

    OC 是面向运行时的语言.Runtime就是系统在运行的时候的一些机制,其中最主要的是消息发送机制.OC语言与其他语言(如C语言)在函数(方法)的调用有很大的不同.C语言,函数的调用在编译的时候就已经 ...

  3. iOS - OC RunTime 运行时

    1.运行时的使用 向分类中添加属性 // 包含运行时头文件 #import <objc/runtime.h> /* void objc_setAssociatedObject(id obj ...

  4. oc - runtime运行机制

      Objective-C语言是一门动态语言,它将很多静态语言在编译和链接时做的事放到了运行时来处理.同时OC也是一门简单的语言,很大一部分是C的内容,只是在语言层面上加了关键字和语法,真正让OC强大 ...

  5. OC - runtime - 1

  6. iOS运行时Runtime浅析

    运行时是iOS中一个很重要的概念,iOS运行过程中都会被转化为runtime的C代码执行.例如[target doSomething];会被转化成objc)msgSend(target,@select ...

  7. ObjC如何通过runtime修改Ivar的内存管理方式

    ObjC如何通过runtime修改Ivar的内存管理方式 为什么要这么做? 在iOS 9之前,UITableView(或者更确切的说是 UIScrollView)有一个众所周知的问题: propert ...

  8. Unity 的OCulus VR开发遇到的坑---OC版本差异

    我作为Unity新人,没有用过Unity5之前的任何版本,不熟悉任何操作.所以,就根据官方推荐,使用了5.1.1版本,然后根据官方版本对应推荐,果断选择下载了PC端的OC的0.6.0.1版本,对应的U ...

  9. OC 相关

    1.OC runtime的理解[转载] http://www.csdn.net/article/2015-07-06/2825133-objective-c-runtime/1

随机推荐

  1. element-ui表单验证无效解决

    最近在项目中遇到了一个需求,需要动态增减表单元素,同时给新增的表单元素增加校验规则. element-ui官网给出了解决方案:点击新增按钮时,向循环渲染的数组中push新的对象,数据驱动视图,通过增加 ...

  2. Socket通信封装MIna框架--含羞代放

    目录 核心类 各个击破 IoService IoFilter IoHandler 总结 # 加入战队 微信公众号 Mina异步IO使用的Java底层JNI框架,Mina提供服务端和客户端,将我们的业务 ...

  3. SpringBoot中快速实现邮箱发送

    前言 在许多企业级项目中,需要用到邮件发送的功能,如: 注册用户时需要邮箱发送验证 用户生日时发送邮件通知祝贺 发送邮件给用户等 创建工程导入依赖 <!-- 邮箱发送依赖 --> < ...

  4. DES加解密工具类

    这两天在跟友商对接接口,在对外暴露接口的时候,因为友商不需要登录即可访问对于系统来说存在安全隐患,所以需要友商在调用接口的时候需要将数据加密,系统解密验证后才执行业务.所有的加密方式并不是万能的,只是 ...

  5. C#开发学习人工智能的第一步

    前言 作为一个软件开发者,我们除了要学会复制,黏贴,还要学会调用API和优秀的开源类库. 也许,有人说C#做不了人工智能,如果你相信了,那只能说明你的思想还是狭隘的. 做不了人工智能的不是C#这种语言 ...

  6. JavaScript算法模式——动态规划和贪心算法

    动态规划 动态规划(Dynamic Programming,DP)是一种将复杂问题分解成更小的子问题来解决的优化算法.下面有一些用动态规划来解决实际问题的算法: 最少硬币找零 给定一组硬币的面额,以及 ...

  7. spring boot环境配置

    Eclipse+Maven创建webapp项目<一> 1.开启eclipse,右键new——>other,如下图找到maven project 2.选择maven project,显 ...

  8. Oracle中的一些基本sql语句

    --新建表create table table1 (id varchar(300) primary key,name varchar(200) not null);--插入数据insert into ...

  9. GO.数据库接口

    Go没有内置的驱动支持任何的数据库,但是Go定义了database/sql接口,用户可以基于驱动接口开发相应数据库的驱动. 目前NOSQL已经成为Web开发的一个潮流,很多应用采用了NOSQL作为数据 ...

  10. 【管理学】PDCA