本篇文章主要解决以下问题

  1. 说说你对 runtime 的理解。
  2. 你了解 isa 指针吗?
  3. 类的结构是怎样的?
    • class_rw_t 与 class_ro_t 的区别?
    • runtime 中,SEL 和 IMP 的区别?
    • runtime 如何通过 selector 找到对应 IMP 的地址?
  4. objc 的消息发送机制是怎样的?
    • objc 中向一个 nil 对象发送消息,会发生什么?
    • 消息转发到 forwordinginvacation 方法,如何拿到返回值?
    • 什么时候会报 unrecognized selector 异常?
  5. runtime 中常用的方法。
    • runtime怎么添加属性、方法等
    • 使用runtime Associate方法关联的对象,需要在主对象dealloc的时候释放么?
    • 什么是method swizzling?
    • 能否向编译后得到的类中增加实例变量?能否向运行时创建的类中添加实例变量?为什么?
  6. 你在项目中用过runtime吗?举个例子。

1. 说说你对 runtime 的理解。

答:

Runtime 提供了一套 C/C++ 的 API,使 OC 程序可以在运行期改变其结构,因此 OC 是一门动态性比较强的语言。

在 iOS 系统中 KVO、Category、weak 等都是基于 runtime 实现的。

自己也经常利用 runtime 的特性来实现一些东西。(详见本篇第 6 问)

****** 低调的分割线 ******

我觉得上面第一部分的答案还是不够口语化,也不太好,显得空洞,感觉面试答起来比较像是背的,听起来比较尴尬,我重新反思了一下,这是新的第一部分的答案:

像其它的语言,比如说 C 语言,在编译的时候就已经知道了程序应该执行的代码。但是在 OC 中,会尽可能地把决策去推迟到运行的时候再去做。比如到运行的时候再去确定对象的类型、确定方法的接收者,给对象添加方法等等。这些都是可以通过 runtime 的 api 来实现的,用书面一点的话来说就是:runtime 的 API 使 OC 程序可以在它运行到时候改变结构,所以 OC 是一门动态性比较强的语言。

对于 runtime 来说,最重要的就是消息传递机制,要理解清楚消息传递机制就需要弄明白 isa 的作用,再弄清楚类的的结构,再去理解消息传递机制的流程,才能理解的透彻。(扯到这一步就好说了,这些问题下边都有讲到)

分析:

这种开放的问题难以把握如何回答好,笔者认为首先要答到 objc 是一门动态性比较强的语言(为什么说比较强,因为还有更强的),然后再说说系统的一些基于 runtime 实现的东西,再说说自己项目中基于 runtime 实现的一些东西。

2. 你了解 isa 指针吗?

答:

在 64bit 之前 isa 是一个普通的指针,指向 Class/Meta-Class。
在 64bit 之后,苹果对其进行了优化,把它做成了一个共用体,运用了位域的技术存储了更多的信息。(isa 还是只占用 8 个字节,用 8 个字节存储了更多的信息,但是要找到相应了 class 或者 meta-class 需要与 ISA_MASK 进行一次位运算,要更详细的了解可以点击这里。)

我们可以简单地认为,instance 的 isa 指向 class,class 的 isa 指向 meta-class,meta-class 的 isa 指向基类的 meta-class。

在使用 objc_msgSend 的时候,是通过 receiverisa 找到它的类或者元类,然后去找方法的。

分析:

回顾一下之前的内容,OC 中的对象分为 instance、class与meta-class,它们的 isa 与 superclass 的指向关系如图所示。

这里需要注意的是途中右上角有一根看起来不太和谐的箭头,即 Root class(meta) 的 superclass 指向 Root class(class)。

这个问题主要也是谈三点:

  • isa 是什么
  • 怎么指向的
  • 在消息发送中的作用

3. 类的结构是怎样的?

答:

分析:

前面的文章在讲 OC 对象的存储结构时说到 class 对象中存储的有 isa、superclass 指针、方法列表、属性列表、协议列表。meta-classclass 都是 Class 类型的,所以他们的结构其实是一样的,只不过存储的东西有区别,meta-class 的方法列表中存储的是类方法,class 的方法列表中存储的是对象方法。

具体的代码结构如上图所示。(将就看一下吧,作图工具:网页版美图秀秀....)

这是在源码中找到的结构,笔者对其做了精简,保留了现在我们需要关注的信息,源码可以点击这里下载。

现在分析一下这些"美图"。过程比较长,但这是这年头出去面试毫无疑问必须肯定百分之百要掌握的,建议准备面试的同学每天默写一遍笔者下边敲出来的代码。

struct objc_class : objc_object {// Class ISA;Class superclass;cache_t cache;class_data_bits_t bits;
}
复制代码

在源码中可以找到这样一句代码 typedef struct objc_class *Class;,所以 Class 其实本质上就是 objc_class结构体。

我们都知道 Class 中有 isa 和 superclass,但是这里成员变量 ISA 前面有两个斜线,它被注释掉了,很奇怪,这是因为 objc_class 是继承于 objc_object 的,来看一下 objc_object 的结构就明白了。

objc_object 中有 isa 这个共用体,所以objc_class中也有它。

superclass 指向父类。(回忆:基类的元类对象有什么特殊的地方?)

cache 是方法缓存,用于存储先前已经调用过的方法的信息。

使用 objc_msgSend 向接受者发送消息时,会先根据 isa 找到 class/meta-class,然后去 class/meta-class 的成员 cache 中找方法,如果没有找到,才会去方法列表中找。这样可以提升效率。

bits 是一串很长的数字,它存储了多项信息,把它和 FAST_DATA_MASK 进行与运算,可以得到一个新的数字,这个数字是一个地址,指向 class_rw_t 结构体。

objc_class 的成员变量已经简单地介绍完了,现在来看一看更具体的。

先来看一看 cache_tcache 的类型是 cache_t

struct cache_t {struct bucket_t *_buckets;mask_t _mask;mask_t _occupied;
}
复制代码

_buckets 是一个数组,用于存放方法信息,数组中元素的类型是 struct bucket_t

_mask 翻译过来就是掩码,看见 mask 就要意识到它是用来做位运算的,比如 ISA_MASKFAST_DATA_MASK。先记住一个结论,_mask 的长度是 _buckets 的长度减一

_oppcupied 表示的是 _buckets 中存储的方法的数量。

bucket_t 的结构是这样的:

struct bucket_t {SEL _key; //笔者这里与源码不同,因为这里实际上就是以 @selector(xxx) 作为 key 的,这样写方便记忆一些IMP _imp; //函数的内存地址
}
复制代码

方法缓存的机制并不是简单的调用一个就往 _buckets 中添加一个,否则这里也就不需要有 _mask_occupied 这两个成员了。它是一个散列表。

当创建一个类之后,系统自动会给 _buckets 分配一段内存空间,它里面有 N 个元素,都用 NULL 填充,_mask 的值是 N-1

当调用 objc_msgSend(obj, foo) 的时候

  • objClass 中找到了 cache
  • 然后得到@selector(foo) 的地址p,计算 index = p & _mask
  • 使这个 index 做为 _buckets的索引,找到 _buckets[index]
    • 两个数相与 C = A & B,那么 C 必定不大于 A 和 B 中最小的数。所以 _buckets 的索引最大的就是 _mask,所以 _mask 的值是 _buckets 的长度减一。
  • 判断 if (_bucket[index]._key == @selector(foo))
    • 如果相等的话就证明找到了,就可以直接使用了。
    • 如果 _bucket[index]._key == 0,证明这个方法没有存入数组中并且这个位置还没有被别的方法占用,将 @selector(foo) 存入 _bucket[index] 做为 _key,将函数 foo 的地址存入 _imp
    • 如果 _bucket[index]._key != 0 && _bucket[index]._key != @selector(foo)
      • 不同的两个数和 _mask 相与,是有可能得到一个结果的。
      • 出现这种情况说明这个位置被比 foo 先调用的一个函数占用了。现在还不能确定 foo 在不在 _buckets 中。
      • 这时让 index -= 1,然后又进入上一步:使 index 作为索引...
      • 如果 index 已经减到零了,还是没有空位,就令 index = _mask,然后继续找
      • 如果找完一圈之后,还是没有找到
        • 那么就证明 foo 确实不在散列表中
        • 也说明散列表已经满了
        • 散列表会扩容变成原来的两倍,然后修改 _mask 的值
        • 将散列表清空。因为 _mask 已经变了,对于之前的元素来说,已经不能通过 _mask 准确地计算出索引了
        • 计算 @selector(foo) 的地址,与新的 _mask 相与得到新的索引 index,然后将 foo 存入散列表
      • 查询过程的代码如下
        bucket_t * cache_t::find(cache_key_t k, id receiver)
        {assert(k != 0);bucket_t *b = buckets();mask_t m = mask();mask_t begin = cache_hash(k, m);mask_t i = begin;do {if (b[i].key() == 0  ||  b[i].key() == k) {return &b[i];}} while ((i = cache_next(i, m)) != begin);// hackClass cls = (Class)((uintptr_t)this -     offsetof(objc_class, cache));cache_t::bad_cache(receiver, (SEL)k, cls);
        }static inline mask_t cache_next(mask_t i, mask_t mask) {return i ? i-1 : mask;
        }
        复制代码

cache 缓存以及方法查找方式是要重点掌握的,至少得知道是以散列表的方式,然后以什么为索引,通过什么进行比较。这里已经介绍完毕,我先去抽根烟,呆会说说 bits

bits 是一串 64 位的二进制数。不同的位上写着不同的数,控制它和不同的掩码 A_MASKB_MASKC_MASK 相与,就可以得到我们想要的位上的数据。bits & FAST_DATA_MASK 就可以得到 class_rw_t 的地址。

class_rw_t 中我们关注有一个指向 struct class_ro_t 的指针 ro,以及存放类信息的二维数组们。这里的代码就不敲了,要记住的是 class_rw_t 中有 ro 指针,还有存放方法列表、属性列表、协议列表的二维数组

method_array_t 是一个二维数组,它里面存放的是 method_list_tmethod_list_t 中存放的是 method_t。(property_arry_tprotocol_array_t 也是,它们类似。)

method_t 结构如下

struct method_t {SEL name;const char * types;IMP imp;
}
复制代码

name 就代表的是方法的名称,imp 代表的是方法实现的地址。types 是方法的类型编码,主要就是返回值以及各个参数的类型。当我们想找一个方法的时候,我们找到了 method_t,也就知道了它的名字,地址,以及返回值、参数的类型,不就是找到它了吗?当你想报复一个人的时候,你有他的名字、地址以及钥匙......

class_rw_tclass_ro_t

  • 从名字上来看,一个是 readwrite 、一个是readonly。所以你懂的,前者是可读写的,后者是只读的。
  • class_ro_t 包含的是类的初始信息,class_rw_t 会包含分类中的信息。
    • 前面介绍过了,category 编译之后是一个结构体,里面有各种信息。运行过程中 runtime 会把这些信息合并到 class/meta-class 中去,其实就是合并到 class/meta-class 中通过 bits 找到的 class_rw_t 的那些二维数组中来。而类初始信息只有一份,所以 class_ro_t 结构体中的那些数组用一维的就可以了。
    • 你可能注意到了(我知道你没有),class_ro_t 中有一个 ivarsclass_rw_t 中是没有的。这个是类的成员变量。ivars 存在于一个 readonly 数组中,这也可以作为解释以下两个问题的一个角度
      • 为啥 category 不能添加成员变量呢?

        • 为啥能添加方法呢?
      • 为啥不能向编译后的类中添加实例变量呢?

类的结构说完了,你能否回答这两道面试题?

  • runtime 中,SEL 和 IMP 的区别?
  • runtime 如何通过 selector 找到对应 IMP 的地址?

4. objc 的消息发送机制是怎样的?

ObjC 的动态特性是基于 Runtime 的消息传递机制的,在 ObjC 中,消息的传递都是动态的。

ObjC - 基于 Runtime 的语言,它会尽可能地把决策从编译时和连接时推迟到运行时(简单来说,就是编译后的文件不全是机器指令,还有一部分中间代码,在运行的时候,通过 Runtime 再把需要转换的中间代码在翻译成机器指令)这使得 ObjC 有着很大的灵活性。比如:
1、动态的确定类型
2、我们可以动态的确定消息传递的对象
3、动态的给对象增加方法的实现 等等

什么是消息传递?和 C语言 的调用函数有什么区别?

  • 函数调用就是直接跳到地址执行。代码在编译、优化之后生成了汇编代码,然后连接各种库,完了就生成了可以执行的代码。
  • C语言 在编译时就已经决定了程序所应执行的代码。
  • Objc 中向 receiver 发送消息,receiver 并不一定调用这个方法,而是到了运行时才会去看 receiver 是否响应这个消息,再决策是执行这个方法还是其它方法,或者转发给其它对象。

Tips: 编译时,编译器只是简单的进行语法分析。比如 NSData *obj = [[NSObject alloc] init];,在编译时 objNSData 类型的,在运行时它是 NSObject 类型的。

接下来对消息机制进行讲述。

当我们程序执行 [obj foo]的时候,你可能会和笔者一样去想foo是个什么鬼东西?。

当我们程序执行 [obj foo] 的时候,这句代码会被编译成 objc_msgSend(obj, @selector(foo)),即向 obj 发送消息 foo。然后就会进入消息机制的三大阶段,消息发送、动态解析、消息转发。

  • 消息发送runtime 会先找到 objisa,然后通过 isa 找到 class/meta-class,在 class/meta-class 以及他们的父类的 cache 和方法列表中去找 foo
  • 动态解析:如果在上阶段没有找到就会进入该阶段。runtime 就会给 class/meta-class 发送 resolveInstanceMethod:/resolveClassMethod: 消息,在这里可以给接受者增加执行方法。
  • 消息转发:如果没有在动态解析中进行处理,就会进入到该阶段。

下边这张图描述了消息发送的具体流程:

这张图注意两个地方:

  • 可以向 nil 发送消息,不会崩溃,也不会有结果。
  • 就算是从父类找到的方法,最后也会缓存到消息接收者的 class/meta-class 的 cache 中。

这一张是动态方法解析的流程图:

我们已经提到了好几遍动态解析这个词语,你可能还不明白它是什么意思。我们来看一个实际的例子:

@interface Person : NSObject
- (void)run;
@end@implementation Person
@end
复制代码

实现一个 Person 类,声明一个 -(void)run 方法,但并不实现它。然后在某个 main 函数中让 person 实例对象调用 run,嗯,你知道的它会崩溃。

现在,我们在 Personimplementation 中添加代码:

@implementation Person+ (BOOL)resolveInstanceMethod:(SEL)sel
{return [super resolveInstanceMethod:sel];
}@end
复制代码

然后在方法内打上断点,再运行程序,通过断点可以发现,程序会进入这个方法。

既然它来了,我们就可以在这里做点事情。
我们用 Runtime API 给类把添加一个 run 方法吧,在 OC 中,编译的时候你没添加方法的实现,没关系,运行的时候动态地添加一个也是 OK 的。

void aTmpMethod(id self, SEL _cmd)
{NSLog(@"%s", __func__);
}+ (BOOL)resolveInstanceMethod:(SEL)sel
{if (sel == @selector(run)) {class_addMethod(self, sel, (IMP)aTmpMethod, "v@:");return YES;}return [super resolveInstanceMethod:sel];
}
复制代码

我们利用 class_addMethodPerson 动态地添加了一个方法,四个参数分类代表要添加给谁,你的名字是啥,你家在哪,钥匙呢。

如果 aTmpMethod 不是 C 语言的函数,而是一个 OC 方法。那么可以这样写:

Method method = class_getInstanceMethod(self, @selector(aTmpMethod));
class_addMethod(self, self, method_getImplementation(method), method_getTypeEncoding(method))
复制代码

这个时候再运行程序,发现它不会崩溃了,并且调用了 aTmpMethod 函数。

上述过程我们就是动态地给 Person 添加了一个名为 run 的方法。如果给 Person meta-class 添加方法,流程类似。

现在我们再来回过头看这张流程图。

当消息机制在消息发送阶段没有找到方法的后,会来到动态方法解析阶段。

  • 系统会判断,如果这个方法曾经来到过动态解析阶段,那么就直接进入下一个阶段-消息转发。

    • 因为如果它之前到过动态解析阶段,然后有回到消息发送阶段,但是走完消息发送阶段又没有找到方法,说明它上次来的时候就没有给它动态添加方法。所以就直接进入下一阶段。
  • 如果这是第一次来动态方法解析,那么就会进入 resolveInstanceMethod:/resolveClassMethod:
  • 然后将它标记为:已经动态解析过的。
  • 最后再回到消息发送,再走一遍消息发送的流程。

最后一个阶段是消息转发:

如果第一二阶段都没有处理成功,就会来到这一阶段-消息转发。

  • 消息转发会先调用 forwardingTargetForSelector: 方法

    • 如果这个方法返回了一个对象,那么就让这个对象去处理这个消息
    • objc_msgSend(对象, sel)
  • 返回值为nil,就会调用 methodSignatureForSelector: 方法
    • methodSignatureForSelector: 要求返回一个方法的签名

      • 方法的签名指的是方法的返回值类型以及参数的类型,常用的生成方式如下:
      • [NSMethodSignature signatureWithObjCTypes:"v@:"];
    • 如果返回nil,则会直接调用doesNotRecognizeSelector: 方法。
  • 如果 methodSignatureForSelector: 返回不为 nil,则会调用 forwardInvocation: 方法。
    • 来到这个方法后,就算什么都不处理,运行程序也不会崩溃了。

      • 比如你这个方法里面什么都不写
      • 比如你只写一句 NSLog(@"Hello world!");
    • 还可以处理这个参数 anInvocation
    • NSInvocation封装了方法调用者、方法名、方法的签名。
    • 比如可以修改方法的调用者
      • [anInvocation invokeWithTarget:[[Cat alloc] init]]
    • 比如拿到方法的返回值类型
      • 上边提到 NSInvocation 中有方法、调用者、方法签名
      • 所以只要拿到方法签名,就能找到返回值的类型
      NSMethodSignature *sig = [anInvocation valueForKey:@"_signature"]; //拿到方法签名
      const char *returnType = sig.methodReturnType;//这个字符串中的第一个字符就是返回值的类型
      复制代码

objc_msgSend 已经讲完了,相信你已经可以回答下边这三个问题了:

  • objc 中向一个 nil 对象发送消息,会发生什么?
  • 消息转发到 forwordinginvacation 方法,如何拿到返回值?
  • 什么时候会报 unrecognized selector 异常?

讲一下 super 的问题,先看一个实际的例子:

现在问你打印什么?如果你对 super 和消息机制不了解的话,这道题你是不明白所以然的。 下边是打印的结果:

现在讲明白为什么是这样:

  • self 是什么?

    • selfrun 的隐藏参数
    • 每一个方法都有隐藏参数 self_cmd
    • 比如 - (void)run 编译后会变成 - (void)run:(id)self sel: (SEL)_cmd_cmd 就是 @selector(run)
    • 所以 self 是一个对象,类型是方法调用者
  • [self class] 发生了什么?
    • self 发送消息 class
    • 通过 selfisa 找到了 Person,然而 Person 中找不到 class 的实现
    • 就通过 Personsuperclass 指针找到了 NSObject
    • NSObject 中找到 class 的实现并调用。
  • super 是什么?
    • super 是编译器的一个标识、一个关键字
    • 它并不是一个对象
  • [super class] 发生了什么?
    • 编译器看见这句代码的时候会把它编译成

      struct __rw_objc_super arg = {self,class_getSuperclass(objc_getClass("Person"))
      }
      objc_msgSendSuper2(arg, @selector(class))
      复制代码
    • objc_msgSendSuper2 的第一个参数是结构体,结构体的第一个成员是 self,第二个成员是 Person 的父类
    • 当执行 objc_msgSendSuper2(arg, @selector(class))
      • 会向 arg 的第一个参数 self 发送消息 class,即 self 是消息的接收者
      • 但是会从第二个参数的缓存中开始查找方法 class
      • 制在 NSObject 中找到了 class 方法,进行调用,由于消息的接收者是 self,所以返回的是 self 的类,而不是 NSObject

5. runtime 中常用的方法。

/*动态创建一个类参数分别是要创建的类的父类、类名、额外的内存空间(一般传0)常考问题:为什么是 Pair,它是是什么意思?答:pair 的意思是一对。创建一个类就是创建 Class 和 Meta-Class
*/
objc_allocateClassPair(Class  _Nullable __unsafe_unretained superclass, const char * _Nonnull name, size_t extraBytes)//注册一个类(要在类注册之前添加成员变量)
void objc_registerClassPair(Class cls)//销毁一个类
void objc_disposeClassPair(Class cls)//获取isa指向的Class
Class object_getClass(id obj)//设置isa指向的Class
Class object_setClass(id obj, Class cls)//判断一个OC对象是否为Class
BOOL object_isClass(Class cls)//判断一个Class是否为元类
BOOL class_isMetaClass(Class cls)//获取父类
Class class_getSuperclass(Class cls)//获取一个实例变量
Ivar class_getInstanceVariable(Class cls, const char *name)//拷贝实例变量列表(最后需要调用free释放)
Ivar *class_copyIvarList(Class cls, unsigned int *outCount)//动态添加成员变量(已经注册的类是不能动态添加成员变量的)
class_addIvar(Class  _Nullable __unsafe_unretained cls, const char * _Nonnull name, size_t size, uint8_t alignment, const char * _Nullable types)//获取成员变量的相关信息
const char *ivar_getName(Ivar v)
const char *ivar_getTypeEncoding(Ivar v)//获取一个属性
objc_property_t class_getProperty(Class cls, const char *name)//拷贝属性列表(最后需要调用free释放)
objc_property_t *class_copyPropertyList(Class cls, unsigned int *outCount)//动态添加属性
class_addProperty(Class  _Nullable __unsafe_unretained cls, const char * _Nonnull name, const objc_property_attribute_t * _Nullable attributes, unsigned int attributeCount)//动态替换属性
class_replaceProperty(Class  _Nullable __unsafe_unretained cls, const char * _Nonnull name, const objc_property_attribute_t * _Nullable attributes, unsigned int attributeCount)//获取属性的一些信息
const char *property_getName(objc_property_t property)
const char *property_getAttributes(objc_property_t property)//获得一个实例方法、类方法
Method class_getInstanceMethod(Class cls, SEL name)
Method class_getClassMethod(Class cls, SEL name)//方法实现相关操作
IMP class_getMethodImplementation(Class cls, SEL name)
IMP method_setImplementation(Method m, IMP imp)
void method_exchangeImplementations(Method m1, Method m2)//拷贝方法列表(最后需要调用free释放)
Method *class_copyMethodList(Class cls, unsigned int *outCount)//动态添加方法
BOOL class_addMethod(Class cls, SEL name, IMP imp, const char *types)//动态替换方法
IMP class_replaceMethod(Class cls, SEL name, IMP imp, const char *types)//获取方法相关的信息(带有copy的需要调用free去释放)
SEL method_getName(Method m)
IMP method_getImplementation(Method m)
const char *method_getTypeEncoding(Method m)
unsigned int method_getNumberOfArguments(Method m)
char *method_copyReturnType(Method m)
char *method_coayArgumentType(Method m, unsigned int index)
复制代码

6. 你在项目中用过runtime吗?举个例子。

  • 给分类添加属性
  • 实现一个线程安全的数组
  • 使用方法交换去 hook 系统的方法
  • 使用class_copyIvarList 实现 json 转 model
  • 使用class_copyPropertyList 实现自定义类的归档与 copy

iOS 问题整理04----Runtime相关推荐

  1. IOS小技巧–用runtime 解决UIButton 重复点击问题

    IOS小技巧–用runtime 解决UIButton 重复点击问题 什么是这个问题 我们的按钮是点击一次响应一次, 即使频繁的点击也不会出问题, 可是某些场景下还偏偏就是会出问题. 通常是如何解决 我 ...

  2. iOS之深入解析Runtime的objc_msgSend“慢速查找”底层原理

    CacheLookup 快速查找 objc_msgSend 通过汇编 快速查找方法缓存 ,如果能找到则调用 TailCallCachedImp 直接将方法缓存起来然后进行调用,如果查找不到就跳到 Ch ...

  3. iOS 开发:『Runtime』详解(二)Method Swizzling

    本文用来介绍 iOS 开发中『Runtime』中的黑魔法Method Swizzling. 通过本文,您将了解到: Method Swizzling(动态方法交换)简介 Method Swizzlin ...

  4. 【iOS开发进阶】-RunTime

    1.基本概念 编译时与运行时 源代码转换为可执行的程序,通常需要经过三个步骤:编译.链接.运行,不同的编译语言,这三个步骤中所进行的操作又有些不同. 编译时就是正在编译的时候,即编译器将源代码翻译成机 ...

  5. iOS模式详解runtime面试工作

    简书:http://www.jianshu.com/p/19f280afcb24 对于从事 iOS 开发人员来说,所有的人都会答出「runtime 是运行时」,什么情况下用runtime?,大部分人能 ...

  6. iOS 知识点整理 (持续更新...)

    整理了些iOS相关的基础问题,每个问题可能会再写些扩展,需要具体了解可以看题目下方的链接 如有错漏,欢迎指出,谢谢 一.Swift 1.给一个数组,要求写一个函数,交换数组中的两个元素(swift可用 ...

  7. iOS运行时-使用Runtime向Category中添加属性以及运行时介绍

    前言 了解OC的都应该知道,在一般情况下,我们是不能向Category中添加属性的,只能添加方法,但有些情况向,我们确实需要向Category中添加属性,而且很多系统的API也有一些在Category ...

  8. ios nslog 例子_iOS Runtime常用示例总结

    前言 Runtime是iOS里面非常重要的基础知识,初次与它见面时,甚是懵懂,但没有关系,万事万物都是要由陌生到熟悉.学就完了. 正文 经常有小伙伴私下在Q上问一些关于Runtime的东西,问我有没有 ...

  9. iOS之深入解析Runtime的objc_msgSend“快速查找”底层原理

    Runtime 一.什么是 runtime ? Objective-C 语言将尽可能多的决策从 编译时和链接时 推迟到运行时.只要有可能,它就 动态 地做事情,这意味着该语言不仅需要一个编译器,还需要 ...

最新文章

  1. 【C语言】两种方式实现冒泡排序算法
  2. 服务器文件夹同步到手机,本机文件夹同步到云服务器
  3. 对称式加密和非对称式加密
  4. html快捷键_Mac进阶:掌握这 5 个冷门快捷键,让Mac更好用
  5. 移动前端webApp开发点滴积累20140524
  6. mysql5.0无法访问_MYSQL版本升级到后5.0后无法连接的问题
  7. 如何通过直接复制frm文件以实现恢复/复制innodb数据表?
  8. 机器视觉——单目相机模型(坐标标定以及去畸变)
  9. python输入一个区间_Python 学习笔记:根据输入年月区间,返回期间所有的月份...
  10. matlab哈明窗带阻,MATLAB数字滤波器程序 Hamming窗带通滤波器
  11. 2020年云计算就业前景怎么样?
  12. 通过R访问世界银行数据(World Bank Data)分析经济
  13. 优盘弹出文件或目录损坏且无法读取实测解决教程
  14. Android 5.0状态栏通知图标的实现
  15. PVE7.2-3直通独显 nvidia 1080ti
  16. 《网络攻防》Web基础
  17. excel中表格行高最大值是多少?如果超过了怎么调整?
  18. 关系模式的任何属性(关系模式的任何属性为什么不可再分)
  19. 第二章 关系模型和关系运算理论 3类完整性
  20. 华里士公式(点火公式)与区间再现公式

热门文章

  1. kaggle和colab入门
  2. centos7 vnc oracle,Centos7远程桌面的安装与vnc/vnc-server的设置
  3. 为什么matplotlib显示opencv图像不正常
  4. Python的生成器(generator)
  5. 【星球知识卡片】模型压缩重要方向-动态模型,如何对其长期深入学习
  6. 【Python进阶】Python进阶专栏、编程与开源框架知识星球上线,等你来follow
  7. 全球及中国油气装备行业投资状况及运营前景研究报告2021版
  8. 全球及中国润滑油市场产销规模及营销竞争分析报告2021-2027年
  9. 全球与中国丙烷脱氢制丙烯市场发展形势与前景规划分析报告2022-2028年版
  10. 计算机生活工作原理,计算机基本工作原理是什么?