看其他人的代码,发现weak出现在了几乎所有有block的地方,比如 GCD 比如,使用Masnory布局的地方,
问了几个同学,理由大都是,避免循环引用,被循环引用整怕了。
反正用了也没什么不好之类的。。。
打个符号断点,观察一下,这两个函数在不断的调用,函数内部在操作一个不简单的list,有兴趣可以自己阅读 runtime源码。你看,不但每次调用两个开销挺大的函数,而且还有一个list
虽说现在手机好了,但是,对自己狠点,能规范的地方就规范,app会有意想不到的收获。。
以布局Monsary 为栗子,我们看看到底要不要使用weak。。
看到QQ群里有人说,出现block 必然会copy block中引用到的东西。
暂且不管Monsary 源码实现部分,
我们使用符号断点。符号断点断在
_Block_copy
_Block_release
部分,测试代码:
[self.personsLabel mas_remakeConstraints:^(MASConstraintMaker *make) {
make.left.equalTo(self.mostCostLabel);
make.right.equalTo(self.mostCostLabel);
make.top.equalTo(self.mostCostLabel.mas_bottom).offset(6.0f);
make.height.mas_equalTo(10.0f);
}];
通过断点可以看到,当执行 block 中的语句的时候,的确会调用 _Block_copy,但是在 block 执行完之后,断点会停在 _Block_release 这里。
所以,安全可靠,不用使用weak 的,GCD 和 系统 的动画样式如果不放心,也可以这样子测试是否释放。
后记
@implementation NSTimer (PTVSafe)
+ (id)safe_scheduledTimerWithTimeInterval:(NSTimeInterval)inTimeInterval block:(void (^)())inBlock repeats:(BOOL)inRepeats {
void (^block)() = [inBlock copy];
id ret = [self scheduledTimerWithTimeInterval:inTimeInterval target:self selector:@selector(ptv_jdExecuteSimpleBlock:) userInfo:block repeats:inRepeats];
return ret;
}
+ (void)ptv_jdExecuteSimpleBlock:(NSTimer *)inTimer; {
if ([inTimer userInfo]) {
void (^block)() = (void (^)())[inTimer userInfo];
block();
}
}
@end
对于NSTimer 这样子进行扩展,完全没有必要的感觉,每次都要调用copy,全局给项目中的 _Block_copy 打符号断点,timer 这里调用的甚多。释放的也不是很及时。
反思:设计一个通用组件给别人调用的时候,要想的东西,不止使用方便这个层面!
UIView 自带的动画实现探究
- (void)viewDidLoad {
UIView *view = [UIView new];
view.backgroundColor = [UIColor redColor];
view.frame = CGRectMake(0, 30, 200, 200);
[self.view addSubview:view];
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
//①
// view.frame = CGRectMake(10, 20, 30, 40);
//②
[UIView animateWithDuration:0.5
animations:^{
view.frame = CGRectMake(10, 20, 30, 40);
}];
});
}
我们顺势探究一下动画的实现过程。
使用 ① 的方式直接修改frame 是不会有动画的。
使用 ② 的方式是有动画的。
but why?
我们知道,UIView 和 CALayer 的关系,想完成动画,肯定是在layer 层做的,于是我们打符号断点给 layer 层的
- (void)addAnimation:(CAAnimation *)anim forKey:(nullable NSString *)key;
函数,看看是否会调用,该函数。重新运行,真的调用了。
QuartzCore`-[CALayer addAnimation:forKey:]:
-> 0x10c569654 <+0>: pushq %rbp
0x10c569655 <+1>: movq %rsp, %rbp
0x10c569658 <+4>: pushq %r15
0x10c56965a <+6>: pushq %r14
0x10c56965c <+8>: pushq %r13
0x10c56965e <+10>: pushq %r12
0x10c569660 <+12>: pushq %rbx
0x10c569661 <+13>: subq $0x18, %rsp
0x10c569665 <+17>: movq %rcx, %r14
0x10c569668 <+20>: movq %rdx, %r12
0x10c56966b <+23>: movq %rdi, %r15
0x10c56966e <+26>: movq 0xc8203(%rip), %rdi ; (void *)0x000000010c633108: CATransition
当然,我们不看汇编函数,下来我们改造一下我们的demo, 看看究竟!!
改造后的代码如下:
@interface PDLayer : CALayer
@end
@implementation PDLayer
- (void)addAnimation:(CAAnimation *)anim forKey:(NSString *)key {
[super addAnimation:anim
forKey:key];
NSLog(@"%@ %@", anim, key);
}
@end
@interface PDView : UIView
@end
@implementation PDView
+ (Class)layerClass {
return [PDLayer class];
}
@end
- (void)viewDidLoad {
PDView *view = [PDView new];
view.backgroundColor = [UIColor redColor];
view.frame = CGRectMake(0, 30, 200, 200);
[self.view addSubview:view];
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
//①
// view.frame = CGRectMake(10, 20, 30, 40);
//②
[UIView animateWithDuration:0.5
animations:^{
view.frame = CGRectMake(10, 20, 30, 40);
}];
});
其实什么都没变,只是把上面的UIView 替换成了 PDView. 给 PDLayer 的 addAnimation 断点。看看会发生什么。
我们po 一下输出的东东
(lldb) po anim
; fillMode = both; timingFunction = easeInEaseOut; duration = 0.5; fromValue = NSPoint: {75, 90}; keyPath = position>
(lldb) po key
position
(lldb)
可以看到,给view 的 layer 层添加了CABasicAnimation 动画, key 的 值为 position
继续执行,断点又断在了同样的地方,输出如下
(lldb) po key
bounds.origin
(lldb) po anim
; fillMode = both; timingFunction = easeInEaseOut; duration = 0.5>
这回加的key 是 bounds.origin
继续运行
断点又断了
(lldb) po key
bounds.size
(lldb) po anim
; fillMode = both; timingFunction = easeInEaseOut; duration = 0.5>
(lldb)
这回添加的动画key 是 bounds.size。
可见UIView 的动画过程调用的都是 CABasicAnimation 系列的动画添加给 layer.
那么问题来了,为什么上面的 1 和 2 会有不同的表现呢
我们再观察两秒
//①
// view.frame = CGRectMake(10, 20, 30, 40);
//②
[UIView animateWithDuration:0.5
animations:^{
view.frame = CGRectMake(10, 20, 30, 40);
}];
我们观察calayer 的函数,又发现了
- (nullable id)actionForKey:(NSString *)event;
难道这个函数在不同的场景返回值不一样?
我们测试一下。
代码修改如下:
//①
// view.frame = CGRectMake(10, 20, 30, 40);
id cc = [view.layer actionForKey:@"position"];
NSLog(@"%@",cc);
//②
[UIView animateWithDuration:0.5
animations:^{
id cc = [view.layer actionForKey:@"position"];
NSLog(@"%@",cc);
view.frame = CGRectMake(10, 20, 30, 40);
}];
我们观察两次 cc 有何不同
还真被蒙对了
(lldb) po cc
nil
(lldb) c
Process 13382 resuming
2017-09-07 14:48:32.908 imageName[13382:1401757] (null)
(lldb) po cc
<_UIViewAdditiveAnimationAction: 0x60000003e660>
(lldb)
在动画block里面打印出的值是完全不一样的。第一次是nil 第二次是个动画
我们再观察一次
[UIView performWithoutAnimation:^{
id cc = [view.layer actionForKey:@"position"];
NSLog(@"%@",cc);
}];
这次打印出来依旧是 nil.由此,结论十分明确了。
由此,我们明白了,UIView 自带的系统动画是如何实现的,而且知道了,这种情况下,是绝对不用weak的。