Example1:NSTimer 、CADisplayLink 循环引用问题
先看以下代码:
self.timer = [NSTimer timerWithTimeInterval:1.0 target:self selector:@selector(TimerTest) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:self.timer forMode:NSRunLoopCommonModes];
self.timer
为强引用,这里Timer与控制器形成了循环引用,如果要解决这个问题,一可以使用block代替即:
[NSTimer timerWithTimeInterval:1.0 repeats:YES block:^(NSTimer * _Nonnull timer) {
// todo 使用weakSelf
}]
当类没有block时,亦或者使用中间变量 : proxy
,可以让 Timer
强引用Proxy
,让Proxy
弱引用 Target
请看以下代码:
self.timer = [NSTimer timerWithTimeInterval:1.0 target:[MJProxy proxyWithTarget:self] selector:@selector(TimerTest) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:self.timer forMode:NSRunLoopCommonModes];
@interface MJProxy : NSObject
+ (MJProxy *)proxyWithTarget:(id)target;
@property (nonatomic, weak)id target;
@end
+ (MJProxy *)proxyWithTarget:(id)target {
MJProxy *proxy = [MJProxy new];
proxy.target = target;
return proxy;
}
- (id)forwardingTargetForSelector:(SEL)aSelector {
return self.target;
}
这里运用了runtime的消息转发,当Timer的Target为MJProxy对象时,找不到TimerTest
方法,即进入动态方法解析,再进入消息转发,会调用forwardingTargetForSelector
方法,返回target对象即传进来的Target
,调用Target
的TimerTest
方法
系统还有另外一个类NSProxy
,我们集成它,他的原理和刚刚的MJProxy
是一样的,只是在找不到方法的时候,他会直接进入:
@interface MJProxy1 : NSProxy
+ (MJProxy1 *)proxyWithTarget:(id)target;
@property (nonatomic, weak)id target;
@end
- (NSMethodSignature *)methodSignatureForSelector:(SEL)sel {
return [self.target methodSignatureForSelector:sel];
}
- (void)forwardInvocation:(NSInvocation *)invocation {
[invocation invokeWithTarget:self.target];
}
省去了objc_msgSend
前面的动态方法解析,以及forwardingTargetForSelector
,性能更佳
CADisplayLink
也是一个定时器,跟NSTimer
的解决方法是一样的
Example2:GCD定时器
NSTimer有可能会出现不准时的情况:因为NSTImer是基于RunLoop实现的,而RunLoop是循环执行的,有可能在繁忙的时候,循环执行出现延迟
解决方案:使用GCD定时器
使用方法:
// 定时器的执行队列
dispatch_queue_t queue = dispatch_get_main_queue();
// 创建定时器
dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
// 设置开始时间与间隔
uint64_t start = 2.0f;
uint64_t interval = 1.0f;
dispatch_source_set_timer(timer,
dispatch_time(DISPATCH_TIME_NOW, start * NSEC_PER_SEC),
interval * NSEC_PER_SEC,
0);
// 设置回调
dispatch_source_set_event_handler(timer, ^{
NSLog(@"111222333");
});
// 启动定时器
dispatch_resume(timer);
Example3:内存布局
如图所示,内存地址由低往高分别是 代码段
、数据段
、堆
、栈
,栈的内存分配地址顺序是由高往低,堆的内存分配地址是由低往高
Tagged Point
以NSNumber
为栗子
NSNumber *number = [NSNumber numberWithInt:10];
NSNumber *number1 = [NSNumber numberWithInt:11];
NSNumber *number2 = @(0xFFFFFFFFFFFFFFF);
NSLog(@"%p ---%p---- %p ",number,number1,number2);
我们看他的输出:
0xb0000000000000a2 ---0xb0000000000000b2---- 0x604000035e60
我们可以看到他们的内存有很大的区别,这就是Tagged Point的作用,在使用Tagged Point 之前,像NSNumber
、NSString
、NSData
这些数据都是以对象的形式存储于堆空间,这样无疑是浪费内存空间的,而使用了Tagged Point之后,数据会存储在指针内,以标记+数据
的形式存在
如:
0xb0000000000000a2
,b
、2
即为NSNumber
的标记,而a
则对应数字10
的存储
如何判断一个内存是否为Tagged Point
?
mac 下最低有效位 &1
为 1
iOS 下最高有效位 &1<<63
(64位) 为 1<<63
Copy
请看以下代码:
NSString *str = @"123";
NSString *str2 = [str copy];
NSMutableString *str3 = [str mutableCopy];
NSLog(@"%p --- %p --- %p",str,str2,str3);
NSMutableString *str4 = [NSMutableString stringWithFormat:@"33333"];
NSString *str5 = [str copy];
NSMutableString *str6 = [str mutableCopy];
NSLog(@"%p --- %p --- %p",str4,str5,str6);
打印输出:
0x10b3090a0 --- 0x10b3090a0 --- 0x6000004492d0
0x604000442760 --- 0x10b3090a0 --- 0x604000442940
我们可以看到内存地址的变化,当类进行copy
时返回的都是不可变的类,而进行MutableCopy
时,返回的都是可变的类
当不可变类进行copy
时,只是对指针进行复制,指向同一个对象(不可变对象copy不需要多一块内存地址,可以节省内存空间),而mutableCopy
时则拷贝了一块新内存地址
无论可变类进行copy
/mutableCopy
,都是拷贝一份新的内存地址
同理 数组
、字典
也是一样的
用一张图总结:
由于copy
是Foundation
框架的方法,当对象要进行copy
时,对象则需要遵守NSCopying
协议,并实现copyWithZone
方法
内存总结
weak 、 unsafe_unretain
weak
与unsafe_unretain
都不会对对象进行强引用,但是weak
是相对安全的,在对象释放后,指向对象的指针会自动置nil
,而unsafe_unretain
不会