一、概述
如何给NSArray添加一个属性(不能使用继承)?不能用继承,难道用分类?但是分类只能添加方法不能添加属性啊(Category不允许为已有的类添加新的成员变量,实际上允许添加属性的,同样可以使用@property,但是不会生成_变量(带下划线的成员变量),也不会生成添加属性的getter和setter方法,所以,尽管添加了属性,也无法使用点语法调用getter和setter方法。但实际上可以使用runtime去实现Category为已有的类添加新的属性并生成getter和setter方法),这篇博文则告诉你方法。
关联对象是指某个OC对象通过一个唯一的key连接到一个类的实例上。
举个例子:xiaoming是Person类的一个实例,他的dog(一个OC对象)通过一根绳子(key)被他牵着散步,这可以说xiaoming和dog是关联起来的,当然xiaoming可以牵着多个dog。
二、如何关联对象
runtime提供给我们的方法:
|
//关联对象
void
objc_setAssociatedObject
(
id
object
,
const
void
*
key
,
id
value
,
objc_AssociationPolicy
policy
)
//获取关联的对象
id
objc_getAssociatedObject
(
id
object
,
const
void
*
key
)
//移除关联的对象
void
objc_removeAssociatedObjects
(
id
object
)
|
参数说明:
|
id
object:被关联的对象(如
xiaoming)
const
void
*
key:关联的
key,要求唯一
id
value:关联的对象(如
dog)
objc_AssociationPolicy
policy:内存管理的策略
|
objc_AssociationPolicy policy的enum值有:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
typedef
OBJC_ENUM
(
uintptr_t
,
objc_AssociationPolicy
)
{
OBJC_ASSOCIATION_ASSIGN
=
0
,
/**< Specifies a weak reference to the associated object. */
OBJC_ASSOCIATION_RETAIN_NONATOMIC
=
1
,
/**< Specifies a strong reference to the associated object.
* The association is not made atomically. */
OBJC_ASSOCIATION_COPY_NONATOMIC
=
3
,
/**< Specifies that the associated object is copied.
* The association is not made atomically. */
OBJC_ASSOCIATION_RETAIN
=
01401
,
/**< Specifies a strong reference to the associated object.
* The association is made atomically. */
OBJC_ASSOCIATION_COPY
=
01403
/**< Specifies that the associated object is copied.
* The association is made atomically. */
}
;
|
当对象被释放时,会根据这个策略来决定是否释放关联的对象,当策略是RETAIN/COPY时,会释放(release)关联的对象,当是ASSIGN,将不会释放。
值得注意的是,我们不需要主动调用removeAssociated来接触关联的对象,如果需要解除指定的对象,可以使用setAssociatedObject置nil来实现。
三、应用实例(Category添加属性并生成getter和setter方法)
我们现在来解决峰哥在概述中提出的问题:如何给NSArray添加一个属性(不能使用继承)?
我们现在为NSArray增加一个blog属性:
我们先按照往常方式创建一个NSArray的Category,NSArray+MyCategory.h文件:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
//
// NSArray+MyCategory.h
// RunTimeTest
//
// Created by 李峰峰 on 2016/12/25.
// Copyright © 2016年 李峰峰. All rights reserved.
//
#import <Foundation/Foundation.h>
@interface
NSArray
(
MyCategory
)
//不会生成添加属性的getter和setter方法,必须我们手动生成
@property
(
nonatomic
,
copy
)
NSString
*blog
;
@end
|
NSArray+MyCategory.m文件:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
|
//
// NSArray+MyCategory.m
// RunTimeTest
//
// Created by 李峰峰 on 2016/12/25.
// Copyright © 2016年 李峰峰. All rights reserved.
//
#import "NSArray+MyCategory.h"
#import <objc/runtime.h>
@implementation
NSArray
(
MyCategory
)
// 定义关联的key
static
const
char
*key
=
"blog"
;
/**
blog的getter方法
*/
-
(
NSString
*
)
blog
{
// 根据关联的key,获取关联的值。
return
objc_getAssociatedObject
(
self
,
key
)
;
}
/**
blog的setter方法
*/
-
(
void
)
setBlog
:
(
NSString
*
)
blog
{
// 第一个参数:给哪个对象添加关联
// 第二个参数:关联的key,通过这个key获取
// 第三个参数:关联的value
// 第四个参数:关联的策略
objc_setAssociatedObject
(
self
,
key
,
blog
,
OBJC_ASSOCIATION_RETAIN_NONATOMIC
)
;
}
@end
|
测试代码:
|
-
(
void
)
categoryTest
{
NSArray *
myArray
=
[
[
NSArray
alloc
]
init
]
;
myArray
.
blog
=
@
"http://www.imlifengfeng.com"
;
NSLog
(
@
"谁说Category不能添加属性?我用Category为NSArray添加了一个blog属性,blog=%@"
,
myArray
.
blog
)
;
}
|
打印结果:
|
2016
-
12
-
25
20
:
00
:
37.824
RunTimeTest
[
9447
:
958867
]
谁说
Category不能添加属性?我用
Category为
NSArray添加了一个
blog属性,
blog
=
http
:
//www.imlifengfeng.com
|
——————Method Swizzling————
一、概述
Objective-C 中的 Method Swizzling 是一项异常强大的技术,它可以允许我们动态地替换方法的实现,实现 Hook 功能,是一种比子类化更加灵活的“重写”方法的方式。
Method Swizzling 是一把双刃剑,使用得当可以让我们非常轻松地实现复杂的功能,而如果一旦误用,它也很可能会给我们的程序带来毁灭性的伤害。但是我们不能因噎废食,当我们理解了Method Swizzling原理之后,它将会变成我们强大的武器。
二、Method Swizzling 的原理
在上篇博文中我们讲过在Objective-C同一个类(及类的继承体系)中,不能存在2个同名的方法,即使参数类型不同也不行。所以下面两个方法在 runtime 看来就是同一个方法:
|
-
(
void
)
viewWillAppear
:
(
BOOL
)
animated
;
-
(
void
)
viewWillAppear
:
(
NSString *
)
string
;
|
而下面两个方法却是可以共存的:
|
-
(
void
)
viewWillAppear
:
(
BOOL
)
animated
;
+
(
void
)
viewWillAppear
:
(
BOOL
)
animated
;
|
因为实例方法和类方法是分别保存在类对象和元类对象中的。
原则上,方法的名称 name
和方法的实现 imp
是一一对应的,而 Method Swizzling 的原理就是动态地改变它们的对应关系,以达到替换方法实现的目的。如下图所示:
图一
图二
上面图一中selector2原本对应着IMP2,但是为了更方便的实现特定业务需求,我们在图二中添加了selector3和IMP3,并且让selector2指向了IMP3,而selector3则指向了IMP2,这样就实现了“方法互换”。
在OC语言的runtime特性中,调用一个对象的方法就是给这个对象发送消息。是通过查找接收消息对象的方法列表,从方法列表中查找对应的SEL,这个SEL对应着一个IMP(一个IMP可以对应多个SEL),通过这个IMP找到对应的方法调用。
在每个类中都有一个Dispatch Table,这个Dispatch Table本质是将类中的SEL和IMP(可以理解为函数指针)进行对应。而我们的Method Swizzling就是对这个table进行了操作,让SEL对应另一个IMP。
三、Method Swizzling使用
想必大家都用过友盟统计,我们需要在每个页面的 view controller 中添加如下代码:
|
-
(
void
)
viewWillAppear
:
(
BOOL
)
animated
{
[
super
viewWillAppear
:
animated
]
;
[
MobClick
beginLogPageView
:
@
"PageOne"
]
;
}
-
(
void
)
viewWillDisappear
:
(
BOOL
)
animated
{
[
super
viewWillDisappear
:
animated
]
;
[
MobClick
endLogPageView
:
@
"PageOne"
]
;
}
|
这是最简单的方法,直接简单粗暴的在每个控制器中加入统计,复制、粘贴、复制、粘贴…
实际上非常不建议使用这种方法的,不仅消耗时间而且以后非常难以维护。那我们有其他什么好的办法吗?
当然我们还可以用以下两种方法:
(1)直接修改每个页面的 view controller 代码,简单粗暴。
(2)子类化 view controller ,并让我们的 view controller 都继承这些子类。
第 1 种方式的缺点是不仅会产生大量重复的代码,而且还很容易遗漏某些页面,非常难维护;第 2 种方式稍微好一点,但是也同样需要我们子类化 UIViewController 、UITableViewController 和 UITabBarController 等不同类型的 view controller 。
除此之外还有什么比较简单优雅的解决方案吗?答案是肯定的,Method Swizzling 就是解决此类问题的最佳方式。
在实现Method Swizzling时,核心代码主要就是一个runtime的C语言API:
|
BJC_EXPORT
void
method_exchangeImplementations
(
Method
m1
,
Method
m2
)
__OSX_AVAILABLE_STARTING
(
__MAC_10_5
,
__IPHONE_2_0
)
;
|
下面我们通过Method Swizzling
简单的实现上面那个添加统计的需求。
我们先给UIViewController添加一个Category,然后在Category中的+(void)load方法中添加Method Swizzling方法,我们用来替换的方法也写在这个Category中。由于load类方法是程序运行时这个类被加载到内存中就调用的一个方法,执行比较早,并且不需要我们手动调用。而且这个方法具有唯一性,也就是只会被调用一次,不用担心资源抢夺的问题。
定义Method Swizzling中我们自定义的方法时,需要注意尽量加前缀,以防止和其他地方命名冲突,Method Swizzling的替换方法命名一定要是唯一的,至少在被替换的类中必须是唯一的。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
|
#import "UIViewController+swizzling.h"
#import <objc/runtime.h>
@
implementation
UIViewController
(
swizzling
)
+
(
void
)
load
{
// 通过class_getInstanceMethod()函数从当前对象中的method list获取method结构体,如果是类方法就使用class_getClassMethod()函数获取。
Method
fromMethod
=
class_getInstanceMethod
(
[
self
class
]
,
@
selector
(
viewDidLoad
)
)
;
Method
toMethod
=
class_getInstanceMethod
(
[
self
class
]
,
@
selector
(
swizzlingViewDidLoad
)
)
;
/**
* 我们在这里使用class_addMethod()函数对Method Swizzling做了一层验证,如果self没有实现被交换的方法,会导致失败。
* 而且self没有交换的方法实现,但是父类有这个方法,这样就会调用父类的方法,结果就不是我们想要的结果了。
* 所以我们在这里通过class_addMethod()的验证,如果self实现了这个方法,class_addMethod()函数将会返回NO,我们就可以对其进行交换了。
*/
if
(
!
class_addMethod
(
[
self
class
]
,
@
selector
(
swizzlingViewDidLoad
)
,
method_getImplementation
(
toMethod
)
,
method_getTypeEncoding
(
toMethod
)
)
)
{
method_exchangeImplementations
(
fromMethod
,
toMethod
)
;
}
}
// 我们自己实现的方法,也就是和self的viewDidLoad方法进行交换的方法。
-
(
void
)
swizzlingViewDidLoad
{
NSString *
str
=
[
NSString
stringWithFormat
:
@
"%@"
,
self
.
class
]
;
// 我们在这里加一个判断,将系统的UIViewController的对象剔除掉
if
(
!
[
str
containsString
:
@
"UI"
]
)
{
NSLog
(
@
"统计打点 : %@"
,
self
.
class
)
;
}
[
self
swizzlingViewDidLoad
]
;
}
@
end
|
不知道大家注意没有,swizzlingViewDidLoad方法中又调用了[self swizzlingViewDidLoad];,这难道不会产生递归调用吗?
实际上是不会的,Method Swizzling的实现原理可以理解为”方法互换“。假设我们将A和B两个方法进行互换,向A方法发送消息时执行的却是B方法,向B方法发送消息时执行的是A方法。
例如我们上面的代码,系统调用UIViewController的viewDidLoad方法时,实际上执行的是我们实现的swizzlingViewDidLoad方法。而我们在swizzlingViewDidLoad方法内部调用[self swizzlingViewDidLoad];时,执行的是UIViewController的viewDidLoad方法。
四、Method Swizzling类簇
在我们项目开发过程中,经常因为NSArray数组越界或者NSDictionary的key或者value值为nil等问题导致的崩溃,我们可以尝试使用前面知识对NSArray、NSMutableArray、NSDictionary、NSMutableDictionary等类进行Method Swizzling,但是结果发现Method Swizzling根本就不起作用,到底为什么呢?
这是因为Method Swizzling对NSArray这些的类簇是不起作用的。因为这些类簇类,其实是一种抽象工厂的设计模式。抽象工厂内部有很多其它继承自当前类的子类,抽象工厂类会根据不同情况,创建不同的抽象对象来进行使用。例如我们调用NSArray的objectAtIndex:方法,这个类会在方法内部判断,内部创建不同抽象类进行操作。
所以也就是我们对NSArray类进行操作其实只是对父类进行了操作,在NSArray内部会创建其他子类来执行操作,真正执行操作的并不是NSArray自身,所以我们应该对其“真身”进行操作。
下面我们实现了防止NSArray因为调用objectAtIndex:方法,取下标时数组越界导致的崩溃:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
|
#import "NSArray+ MyArray.h"
#import "objc/runtime.h"
@
implementation
NSArray
MyArray
)
+
(
void
)
load
{
Method
fromMethod
=
class_getInstanceMethod
(
objc_getClass
(
"__NSArrayI"
)
,
@
selector
(
objectAtIndex
:
)
)
;
Method
toMethod
=
class_getInstanceMethod
(
objc_getClass
(
"__NSArrayI"
)
,
@
selector
(
my_objectAtIndex
:
)
)
;
method_exchangeImplementations
(
fromMethod
,
toMethod
)
;
}
-
(
id
)
my_objectAtIndex
:
(
NSUInteger
)
index
{
if
(
self
.
count
-
1
<
index
)
{
// 这里做一下异常处理,不然都不知道出错了。
@
try
{
return
[
self
my_objectAtIndex
:
index
]
;
}
@
catch
(
NSException *
exception
)
{
// 在崩溃后会打印崩溃信息,方便我们调试。
NSLog
(
@
"---------- %s Crash Because Method %s ----------\n"
,
class_getName
(
self
.
class
)
,
__func__
)
;
NSLog
(
@
"%@"
,
[
exception
callStackSymbols
]
)
;
return
nil
;
}
@
finally
{
}
}
else
{
return
[
self
my_objectAtIndex
:
index
]
;
}
}
@
end
|
也就是说__NSArrayI才是NSArray真正的类。我们可以通过runtime函数获取真正的类:
|
objc_getClass
(
"__NSArrayI"
)
|
下面我们列举一些常用的类簇的“真身”:
五、Method Swizzling使用注意事项
1、Swizzling应该总是在+load中执行
在Objective-C中,运行时会自动调用每个类的两个方法。+load会在类初始加载时调用,+initialize会在第一次调用类的类方法或实例方法之前被调用。这两个方法是可选的,且只有在实现了它们时才会被调用。由于method swizzling会影响到类的全局状态,因此要尽量避免在并发处理中出现竞争的情况。+load能保证在类的初始化过程中被加载,并保证这种改变应用级别的行为的一致性。相比之下,+initialize在其执行时不提供这种保证—事实上,如果在应用中没为给这个类发送消息,则它可能永远不会被调用。
2、Swizzling应该总是在dispatch_once中执行
与上面相同,因为swizzling会改变全局状态,所以我们需要在运行时采取一些预防措施。原子性就是这样一种措施,它确保代码只被执行一次,不管有多少个线程。GCD的dispatch_once可以确保这种行为,我们应该将其作为method swizzling的最佳实践。
3、Method Swizzling有成熟的第三方框架可用
在项目中我们肯定会在很多地方用到Method Swizzling
,而且在使用这个特性时有很多需要注意的地方。我们可以将Method Swizzling
封装起来,也可以使用一些比较成熟的第三方。
在这里我推荐Github上星最多的一个第三方-jrswizzle
里面核心就两个类,代码看起来非常清爽。
|
#import <Foundation/Foundation.h>
@
interface
NSObject
(
JRSwizzle
)
+
(
BOOL
)
jr_swizzleMethod
:
(
SEL
)
origSel_
withMethod
:
(
SEL
)
altSel_
error
:
(
NSError*
*
)
error_
;
+
(
BOOL
)
jr_swizzleClassMethod
:
(
SEL
)
origSel_
withClassMethod
:
(
SEL
)
altSel_
error
:
(
NSError*
*
)
error_
;
@
end
// MethodSwizzle类
#import <objc/objc.h>
BOOL
ClassMethodSwizzle
(
Class
klass
,
SEL
origSel
,
SEL
altSel
)
;
BOOL
MethodSwizzle
(
Class
klass
,
SEL
origSel
,
SEL
altSel
)
;
|
具体使用方法大家自己研究。
————
拾遗————
一、概述
这是峰哥最后一篇iOS Runtime总结,前面几篇的博文通过浅显易懂的描述涵盖了大部分知识点,相信有人会发现我的所有博文都是使用一种非常易懂的语言描述,没有那些高大上但难以理解的词汇,我整理iOS Runtime也是对自己学习的一个总结。这里还有几个重要但琐碎的知识点,就不像前面那样详细总结了。
二、其他知识点
1、super
在Objective-C中,如果我们需要在类的方法中调用父类的方法时,通常都会用到super,如下所示:
|
@
interface
MyViewController
:
UIViewController
@
end
@
implementation
MyViewController
-
(
void
)
viewDidLoad
{
[
super
viewDidLoad
]
;
// do something
.
.
.
}
@
end
|
如何使用super我们都知道。现在的问题是,它是如何工作的呢?
首先我们需要知道的是super与self不同。self是类的一个隐藏参数,每个方法的实现的第一个参数即为self。而super并不是隐藏参数,它实际上只是一个”编译器标示符”,它负责告诉编译器,当调用viewDidLoad方法时,去调用父类的方法,而不是本类中的方法。而它实际上与self指向的是相同的消息接收者。为了理解这一点,我们先来看看super的定义:
|
struct
objc_super
{
id
receiver
;
Class
superClass
;
}
;
|
这个结构体有两个成员:
(1)receiver:即消息的实际接收者
(2)superClass:指针当前类的父类
当我们使用super来接收消息时,编译器会生成一个objc_super结构体。就上面的例子而言,这个结构体的receiver就是MyViewController对象,与self相同;superClass指向MyViewController的父类UIViewController。
接下来,发送消息时,不是调用objc_msgSend函数,而是调用objc_msgSendSuper函数,其声明如下:
|
id
objc_msgSendSuper
(
struct
objc_super *
super
,
SEL
op
,
.
.
.
)
;
|
该函数第一个参数即为前面生成的objc_super结构体,第二个参数是方法的selector。该函数实际的操作是:从objc_super结构体指向的superClass的方法列表开始查找viewDidLoad的selector,找到后以objc->receiver去调用这个selector,而此时的操作就是如下方式了:
|
objc_msgSend
(
objc_super
->
receiver
,
@
selector
(
viewDidLoad
)
)
|
由于objc_super->receiver就是self本身,所以该方法实际与下面这个调用是相同的:
|
objc_msgSend
(
self
,
@
selector
(
viewDidLoad
)
)
|
为了便于理解,我们看以下实例:
|
@
interface
MyClass
:
NSObject
@
end
@
implementation
MyClass
-
(
void
)
test
{
NSLog
(
@
"self class: %@"
,
self
.
class
)
;
NSLog
(
@
"super class: %@"
,
super
.
class
)
;
}
@
end
|
调用MyClass的test方法后,其输出是:
|
2016
-
12
-
27
00
:
44
:
03.256
[
824
:
209297
]
self
class
:
MyClass
2016
-
12
-
27
00
:
44
:
03.256
[
824
:
209297
]
super
class
:
MyClass
|
从上例中可以看到,两者的输出都是MyClass。
2、库相关的操作
库相关的操作主要是用于获取由系统提供的库相关的信息,主要包含以下函数:
|
// 获取所有加载的Objective-C框架和动态库的名称
const
char
*
*
objc_copyImageNames
(
unsigned
int
*
outCount
)
;
// 获取指定类所在动态库
const
char
*
class_getImageName
(
Class
cls
)
;
// 获取指定库或框架中所有类的类名
const
char
*
*
objc_copyClassNamesForImage
(
const
char
*
image
,
unsigned
int
*
outCount
)
;
|
通过这几个函数,我们可以了解到某个类所有的库,以及某个库中包含哪些类。如下代码所示:
|
NSLog
(
@
"获取指定类所在动态库"
)
;
NSLog
(
@
"UIView's Framework: %s"
,
class_getImageName
(
NSClassFromString
(
@
"UIView"
)
)
)
;
NSLog
(
@
"获取指定库或框架中所有类的类名"
)
;
const
char
*
*
classes
=
objc_copyClassNamesForImage
(
class_getImageName
(
NSClassFromString
(
@
"UIView"
)
)
,
&
outCount
)
;
for
(
int
i
=
0
;
i
<
outCount
;
i
++
)
{
NSLog
(
@
"class name: %s"
,
classes
[
i
]
)
;
}
|
其输出结果如下:
|
2016
-
12
-
27
00
:
47
:
32.689
[
747
:
184013
]
获取指定类所在动态库
2016
-
12
-
27
00
:
47
:
32.690
[
747
:
184013
]
UIView'
s
Framework
:
/
System
/
Library
/
Frameworks
/
UIKit
.
framework
/
UIKit
2016
-
12
-
27
00
:
47
:
32.690
[
747
:
184013
]
获取指定库或框架中所有类的类名
2016
-
12
-
27
00
:
47
:
32.691
[
747
:
184013
]
class
name
:
UIKeyboardPredictiveSettings
2016
-
12
-
27
00
:
47
:
32.691
[
747
:
184013
]
class
name
:
_UIPickerViewTopFrame
2016
-
12
-
27
00
:
47
:
32.691
[
747
:
184013
]
class
name
:
_UIOnePartImageView
2016
-
12
-
27
00
:
47
:
32.692
[
747
:
184013
]
class
name
:
_UIPickerViewSelectionBar
2016
-
12
-
27
00
:
47
:
32.692
[
747
:
184013
]
class
name
:
_UIPickerWheelView
2016
-
12
-
27
00
:
47
:
32.692
[
747
:
184013
]
class
name
:
_UIPickerViewTestParameters
.
.
.
.
.
.
|
3、块操作
我们都知道block给我们带到极大的方便,苹果也不断提供一些使用block的新的API。同时,苹果在runtime中也提供了一些函数来支持针对block的操作,这些函数包括:
|
// 创建一个指针函数的指针,该函数调用时会调用特定的block
IMP
imp_implementationWithBlock
(
id
block
)
;
// 返回与IMP(使用imp_implementationWithBlock创建的)相关的block
id
imp_getBlock
(
IMP
anImp
)
;
// 解除block与IMP(使用imp_implementationWithBlock创建的)的关联关系,并释放block的拷贝
BOOL
imp_removeBlock
(
IMP
anImp
)
;
|
imp_implementationWithBlock函数:参数block的签名必须是method_return_type ^(id self, method_args …)形式的。该方法能让我们使用block作为IMP。如下代码所示:
|
@
interface
MyRuntimeBlock
:
NSObject
@
end
@
implementation
MyRuntimeBlock
@
end
// 测试代码
IMP
imp
=
imp_implementationWithBlock
(
^
(
id
obj
,
NSString *
str
)
{
NSLog
(
@
"%@"
,
str
)
;
}
)
;
class_addMethod
(
MyRuntimeBlock
.
class
,
@
selector
(
testBlock
:
)
,
imp
,
"v@:@"
)
;
MyRuntimeBlock *
runtime
=
[
[
MyRuntimeBlock
alloc
]
init
]
;
[
runtime
performSelector
:
@
selector
(
testBlock
:
)
withObject
:
@
"hello world!"
]
;
|
输出结果是:
|
2016
-
12
-
27
00
:
49
:
19.779
[
1172
:
395446
]
hello
world
!
|
4、弱引用操作
|
// 加载弱引用指针引用的对象并返回
id
objc_loadWeak
(
id *
location
)
;
// 存储__weak变量的新值
id
objc_storeWeak
(
id *
location
,
id
obj
)
;
|
objc_loadWeak函数:该函数加载一个弱指针引用的对象,并在对其做retain和autoreleasing操作后返回它。这样,对象就可以在调用者使用它时保持足够长的生命周期。该函数典型的用法是在任何有使用__weak变量的表达式中使用。
objc_storeWeak函数:该函数的典型用法是用于__weak变量做为赋值对象时。
5、宏定义
在runtime中,还定义了一些宏定义供我们使用,有些值我们会经常用到,如表示BOOL值的YES/NO;而有些值不常用,如OBJC_ROOT_CLASS。在此我们做一个简单的介绍:
(1)布尔值
|
#define YES (BOOL)1
#define NO (BOOL)0
|
这两个宏定义定义了表示布尔值的常量,需要注意的是YES的值是1,而不是非0值。
(2)空值
|
#define nil __DARWIN_NULL
#define Nil __DARWIN_NULL
|
其中nil用于空的实例对象,而Nil用于空类对象。
(3)分发函数原型
|
#define OBJC_OLD_DISPATCH_PROTOTYPES 1
|
该宏指明分发函数是否必须转换为合适的函数指针类型。当值为0时,必须进行转换
(4)Objective-C根类
如果我们定义了一个Objective-C根类,则编译器会报错,指明我们定义的类没有指定一个基类。这种情况下,我们就可以使用这个宏定义来避过这个编译错误。该宏在iOS 7.0后可用。
其实在NSObject的声明中,我们就可以看到这个宏的身影,如下所示:
|
__OSX_AVAILABLE_STARTING
(
__MAC_10_0
,
__IPHONE_2_0
)
OBJC_ROOT_CLASS
OBJC_EXPORT
@
interface
NSObject
<
NSObject
>
{
Class
isa
OBJC_ISA_AVAILABILITY
;
}
|
我们可以参考这种方式来定义我们自己的根类。
(5)局部变量存储时长
|
#define NS_VALID_UNTIL_END_OF_SCOPE
|
该宏表明存储在某些局部变量中的值在优化时不应该被编译器强制释放。
我们将局部变量标记为id类型或者是指向ObjC对象类型的指针,以便存储在这些局部变量中的值在优化时不会被编译器强制释放。相反,这些值会在变量再次被赋值之前或者局部变量的作用域结束之前都会被保存。