iOS 问题整理04----Runtime
本篇文章主要解决以下问题
- 说说你对 runtime 的理解。
- 你了解 isa 指针吗?
- 类的结构是怎样的?
- class_rw_t 与 class_ro_t 的区别?
- runtime 中,SEL 和 IMP 的区别?
- runtime 如何通过 selector 找到对应 IMP 的地址?
- objc 的消息发送机制是怎样的?
- objc 中向一个 nil 对象发送消息,会发生什么?
- 消息转发到 forwordinginvacation 方法,如何拿到返回值?
- 什么时候会报 unrecognized selector 异常?
- runtime 中常用的方法。
- runtime怎么添加属性、方法等
- 使用runtime Associate方法关联的对象,需要在主对象dealloc的时候释放么?
- 什么是method swizzling?
- 能否向编译后得到的类中增加实例变量?能否向运行时创建的类中添加实例变量?为什么?
- 你在项目中用过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
的时候,是通过 receiver
的 isa
找到它的类或者元类,然后去找方法的。
分析:
回顾一下之前的内容,OC 中的对象分为 instance、class与meta-class,它们的 isa 与 superclass 的指向关系如图所示。
这里需要注意的是途中右上角有一根看起来不太和谐的箭头,即 Root class(meta) 的 superclass 指向 Root class(class)。
这个问题主要也是谈三点:
- isa 是什么
- 怎么指向的
- 在消息发送中的作用
3. 类的结构是怎样的?
答:
分析:
前面的文章在讲 OC 对象的存储结构时说到 class
对象中存储的有 isa、superclass 指针、方法列表、属性列表、协议列表。meta-class
与 class
都是 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_t
,cache
的类型是 cache_t
。
struct cache_t {struct bucket_t *_buckets;mask_t _mask;mask_t _occupied;
}
复制代码
_buckets
是一个数组,用于存放方法信息,数组中元素的类型是 struct bucket_t
。
_mask
翻译过来就是掩码,看见 mask
就要意识到它是用来做位运算的,比如 ISA_MASK
、FAST_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_MASK
、B_MASK
、C_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_t
,method_list_t
中存放的是 method_t
。(property_arry_t
与 protocol_array_t
也是,它们类似。)
method_t
结构如下
struct method_t {SEL name;const char * types;IMP imp;
}
复制代码
name
就代表的是方法的名称,imp
代表的是方法实现的地址。types
是方法的类型编码,主要就是返回值以及各个参数的类型。当我们想找一个方法的时候,我们找到了 method_t
,也就知道了它的名字,地址,以及返回值、参数的类型,不就是找到它了吗?当你想报复一个人的时候,你有他的名字、地址以及钥匙......
class_rw_t
与 class_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
中有一个ivars
在class_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];
,在编译时 obj
是 NSData
类型的,在运行时它是 NSObject
类型的。
接下来对消息机制进行讲述。
当我们程序执行 [obj foo]
的时候,你可能会和笔者一样去想foo是个什么鬼东西?。
当我们程序执行 [obj foo]
的时候,这句代码会被编译成 objc_msgSend(obj, @selector(foo))
,即向 obj
发送消息 foo
。然后就会进入消息机制的三大阶段,消息发送、动态解析、消息转发。
- 消息发送:
runtime
会先找到obj
的isa
,然后通过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
,嗯,你知道的它会崩溃。
现在,我们在 Person
的 implementation
中添加代码:
@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_addMethod
给 Person
动态地添加了一个方法,四个参数分类代表要添加给谁,你的名字是啥,你家在哪,钥匙呢。
如果 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
是什么?self
是run
的隐藏参数- 每一个方法都有隐藏参数
self
与_cmd
- 比如
- (void)run
编译后会变成- (void)run:(id)self sel: (SEL)_cmd
,_cmd
就是@selector(run)
- 所以
self
是一个对象,类型是方法调用者
[self class]
发生了什么?- 向
self
发送消息class
- 通过
self
的isa
找到了Person
,然而Person
中找不到class
的实现 - 就通过
Person
的superclass
指针找到了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相关推荐
- IOS小技巧–用runtime 解决UIButton 重复点击问题
IOS小技巧–用runtime 解决UIButton 重复点击问题 什么是这个问题 我们的按钮是点击一次响应一次, 即使频繁的点击也不会出问题, 可是某些场景下还偏偏就是会出问题. 通常是如何解决 我 ...
- iOS之深入解析Runtime的objc_msgSend“慢速查找”底层原理
CacheLookup 快速查找 objc_msgSend 通过汇编 快速查找方法缓存 ,如果能找到则调用 TailCallCachedImp 直接将方法缓存起来然后进行调用,如果查找不到就跳到 Ch ...
- iOS 开发:『Runtime』详解(二)Method Swizzling
本文用来介绍 iOS 开发中『Runtime』中的黑魔法Method Swizzling. 通过本文,您将了解到: Method Swizzling(动态方法交换)简介 Method Swizzlin ...
- 【iOS开发进阶】-RunTime
1.基本概念 编译时与运行时 源代码转换为可执行的程序,通常需要经过三个步骤:编译.链接.运行,不同的编译语言,这三个步骤中所进行的操作又有些不同. 编译时就是正在编译的时候,即编译器将源代码翻译成机 ...
- iOS模式详解runtime面试工作
简书:http://www.jianshu.com/p/19f280afcb24 对于从事 iOS 开发人员来说,所有的人都会答出「runtime 是运行时」,什么情况下用runtime?,大部分人能 ...
- iOS 知识点整理 (持续更新...)
整理了些iOS相关的基础问题,每个问题可能会再写些扩展,需要具体了解可以看题目下方的链接 如有错漏,欢迎指出,谢谢 一.Swift 1.给一个数组,要求写一个函数,交换数组中的两个元素(swift可用 ...
- iOS运行时-使用Runtime向Category中添加属性以及运行时介绍
前言 了解OC的都应该知道,在一般情况下,我们是不能向Category中添加属性的,只能添加方法,但有些情况向,我们确实需要向Category中添加属性,而且很多系统的API也有一些在Category ...
- ios nslog 例子_iOS Runtime常用示例总结
前言 Runtime是iOS里面非常重要的基础知识,初次与它见面时,甚是懵懂,但没有关系,万事万物都是要由陌生到熟悉.学就完了. 正文 经常有小伙伴私下在Q上问一些关于Runtime的东西,问我有没有 ...
- iOS之深入解析Runtime的objc_msgSend“快速查找”底层原理
Runtime 一.什么是 runtime ? Objective-C 语言将尽可能多的决策从 编译时和链接时 推迟到运行时.只要有可能,它就 动态 地做事情,这意味着该语言不仅需要一个编译器,还需要 ...
最新文章
- 【C语言】两种方式实现冒泡排序算法
- 服务器文件夹同步到手机,本机文件夹同步到云服务器
- 对称式加密和非对称式加密
- html快捷键_Mac进阶:掌握这 5 个冷门快捷键,让Mac更好用
- 移动前端webApp开发点滴积累20140524
- mysql5.0无法访问_MYSQL版本升级到后5.0后无法连接的问题
- 如何通过直接复制frm文件以实现恢复/复制innodb数据表?
- 机器视觉——单目相机模型(坐标标定以及去畸变)
- python输入一个区间_Python 学习笔记:根据输入年月区间,返回期间所有的月份...
- matlab哈明窗带阻,MATLAB数字滤波器程序 Hamming窗带通滤波器
- 2020年云计算就业前景怎么样?
- 通过R访问世界银行数据(World Bank Data)分析经济
- 优盘弹出文件或目录损坏且无法读取实测解决教程
- Android 5.0状态栏通知图标的实现
- PVE7.2-3直通独显 nvidia 1080ti
- 《网络攻防》Web基础
- excel中表格行高最大值是多少?如果超过了怎么调整?
- 关系模式的任何属性(关系模式的任何属性为什么不可再分)
- 第二章 关系模型和关系运算理论 3类完整性
- 华里士公式(点火公式)与区间再现公式
热门文章
- kaggle和colab入门
- centos7 vnc oracle,Centos7远程桌面的安装与vnc/vnc-server的设置
- 为什么matplotlib显示opencv图像不正常
- Python的生成器(generator)
- 【星球知识卡片】模型压缩重要方向-动态模型,如何对其长期深入学习
- 【Python进阶】Python进阶专栏、编程与开源框架知识星球上线,等你来follow
- 全球及中国油气装备行业投资状况及运营前景研究报告2021版
- 全球及中国润滑油市场产销规模及营销竞争分析报告2021-2027年
- 全球与中国丙烷脱氢制丙烯市场发展形势与前景规划分析报告2022-2028年版
- 计算机生活工作原理,计算机基本工作原理是什么?