利用 NSProxy 实现弱引用和延迟初始化

NSProxy 作为 Objective-C 的一个根类,本身只是提供了少量的几个方法和实现了 NSObject 协议的方法,很多方法的调用都找不到实现,从而触发消息转发的动作,因此适合作为消息转发的代理类。利用 NSProxy 适合做消息转发的特性,可以实现对象之间的弱引用和对象的延迟初始化。

实现弱引用(Weak Reference)

使用场景:在 View Controller 里面使用 NSTimer 每隔一定时间执行指定事件,由于 RunLoop 强引用 NSTimer,而 NSTimer 通过 Target 强引用 View Controller,导致 View Controller 释放不了,造成内存泄露。

1
_timer = [NSTimer scheduledTimerWithTimeInterval:1 target:[[WeakProxy alloc] initWithTarget:self] selector:@selector(timerFire:) userInfo:nil repeats:YES];

这样子就可以打破 RunLoop 和 View Controller 之间的强引用了。

具体实现:

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
#import <Foundation/Foundation.h>
@interface WeakProxy : NSProxy
- (instancetype)initWithTarget:(id)target;
@end
@implementation WeakProxy {
__weak id _target;
}
- (instancetype)initWithTarget:(id)target {
_target = target;
return self;
}
- (NSMethodSignature *)methodSignatureForSelector:(SEL)sel {
return [_target methodSignatureForSelector:sel];
}
- (void)forwardInvocation:(NSInvocation *)invocation {
SEL sel = invocation.selector;
if ([_target respondsToSelector:sel]) {
[invocation invokeWithTarget:_target];
}
}
- (BOOL)respondsToSelector:(SEL)aSelector {
return [_target respondsToSelector:aSelector];
}
@end

实现延迟初始化(Lazy Initialization)

使用场景:

  • 第一种情况,在 [SomeClass lazy] 之后调用 doSomthing,首先进入 forwardingTargetForSelector,_object 为 nil 并且不是 init 开头的方法的时候会调用 init 初始化对象,然后将消息转发给代理对象 _object;
  • 第二种情况,在 [SomeClass lazy] 之后调用 initWithXXX:,首先进入 forwardingTargetForSelector 返回 nil,然后进入 methodSignatureForSelector: 和 forwardInvocation: 保存自定义初始化方法的调用,最后调用 doSomthing,进入 forwardingTargetForSelector,_object 为 nil 并且不是 init 开头的方法的时候会调用自定义初始化方法,然后将消息转发给代理对象 _object;
1
2
3
4
5
SomeClass *object = [SomeClass lazy];
// other thing ...
[object doSomething];// 在这里,object 才会调用初始化方法,然后调用 doSomething

具体实现(代码来源:Lazy Initialization for Objective-C):

LazyInitialization.h

1
2
3
4
5
#import <Foundation/Foundation.h>
@interface NSObject (LazyInitialization)
+ (instancetype)lazy;
@end

LazyInitialization.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
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
#import "LazyInitialization.h"
@interface LazyProxy : NSProxy
- (instancetype)initWithClass:(Class)class;
@end
@implementation NSObject (LazyInitialization)
+ (instancetype)lazy {
return (id)[[LazyProxy alloc] initWithClass:[self class]];
}
@end
@implementation LazyProxy {
id _object;// 代理对象
Class _objectClass;// 代理类
NSInvocation *_initInvocation;// 自定义 init 调用
}
- (instancetype)initWithClass:(Class)cls {
_objectClass = cls;
return self;
}
- (void)instantiateObject {
_object = [_objectClass alloc];
if (_initInvocation == nil) {// 允许一些类 [SomeClass lazy] (没有调用 init)
_object = [_object init];
} else {// 调用自定义 init 方法
[_initInvocation invokeWithTarget:_object];
[_initInvocation getReturnValue:&_object];
_initInvocation = nil;
}
}
- (id)forwardingTargetForSelector:(SEL)selector {
if (_object == nil) {// _object 没有初始化
if (![NSStringFromSelector(selector) hasPrefix:@"init"]) {// 调用 init 开头之外的方法前进行 _object 初始化
[self instantiateObject];
}
}
return _object;// 将消息转发给 _object
}
// 调用自定义 init 方法会进入这个方法
- (NSMethodSignature *)methodSignatureForSelector:(SEL)selector {
NSMethodSignature *signature = [_objectClass instanceMethodSignatureForSelector:selector];
return signature;
}
// 保存自定义 init 方法的调用
- (void)forwardInvocation:(NSInvocation *)invocation {
_initInvocation = invocation;
}
//----------------------------------------------------------------------------
// 手工转发 NSProxy 提供的实现
//----------------------------------------------------------------------------
- (Class)class {
if (_object == nil) {
[self instantiateObject];
}
return [_object class];
}
- (Class)superclass {
if (_object == nil) {
[self instantiateObject];
}
return [_object superclass];
}
- (BOOL)conformsToProtocol:(Protocol *)aProtocol {
if (self->_object == nil) {
[self instantiateObject];
}
return [self->_object conformsToProtocol:aProtocol];
}
- (NSString *)description {
if (self->_object == nil) {
[self instantiateObject];
}
return [self->_object description];
}
- (NSUInteger)hash {
if (self->_object == nil) {
[self instantiateObject];
}
return [self->_object hash];
}
- (BOOL)isEqual:(id)obj {
if (self->_object == nil) {
[self instantiateObject];
}
return [self->_object isEqual:obj];
}
- (BOOL)isKindOfClass:(Class)aClass {
if (self->_object == nil) {
[self instantiateObject];
}
return [self->_object isKindOfClass:aClass];
}
- (BOOL)isMemberOfClass:(Class)aClass {
if (self->_object == nil) {
[self instantiateObject];
}
return [self->_object isMemberOfClass:aClass];
}
- (BOOL)respondsToSelector:(SEL)selector {
if (self->_object == nil) {
[self instantiateObject];
}
return [self->_object respondsToSelector:selector];
}
@end