性能优化3
循环引用
image.pngimage.png
image.png
解决办法
- weak
__weak typeof(self) weakSelf = self;
self.block = ^{
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"%@",weakSelf.name); // self - nil name - nil
});
};
在调用过程中 发现有时打印为nil,是因为用weak修饰的weakSelf是弱引用 有可能会提前释放 所以 需要对这个代码进行优化
优化之后的代码
__weak typeof(self) weakSelf = self;
self.block = ^{
__strong typeof(self) strongSelf = weakSelf;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"%@",strongSelf.name);
});
};
- __block
__block LGViewController *vc = self; // vc 结构体
self.block = ^{
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"%@",vc.name); // self - nil name - nil
vc = nil;
});
};
使用__block解决循环引用的弊端是
1.要在block作⽤用域内 置空对象
2.必须调用这个block才算解决了循环引用 否则 这个循环引用一直存在
- 传参的方式
self.blockVc = ^(LGViewController *vc){
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"%@",vc.name);
});
};
self.blockVc(self);
self持有block block对里面的参数没有持有关系 所以 不存在循环引用
该方式是性能最高的
NSTimer的强引用
image.png无法在- (void)viewWillDisappear:(BOOL)animated中设置[self.timer invalidate]; self.timer = nil;来解决NSTimer强引用的原因
因为不管从这个页面pop或者push到新的页面之后, 这个方法(- (void)viewWillDisappear:(BOOL)animated)都会调用,而push之后 原先的页面还是存在的,而timer已经销毁了 就会在回到这个页面之后 造成问题
解决办法
- -(void)didMoveToParentViewController:(UIViewController *)parent
- (void)didMoveToParentViewController:(UIViewController *)parent{
if (parent == nil) {
[self.timer invalidate];
self.timer = nil;
}
NSLog(@"我走了");
}
这个方法是只有在pop页面的时候回调用
- 创建一个中间变量
1.第一种方式直接设置中间变量为NSObject
self.target = [[NSObject alloc] init];
self.timer = [NSTimer scheduledTimerWithTimeInterval:1 target:self.target selector:@selector(fireHomeFunc) userInfo:nil repeats:YES];
需要为NSObject添加相应的方法实现
2.第二种方法是创建一个NSObject的子类
#import "LGTimerWapper.h"
@interface LGTimerWapper()
@property (nonatomic, weak) id target;
@property (nonatomic, assign) SEL aSelector;
@property (nonatomic, strong) NSTimer *timer;
@end
@implementation LGTimerWapper
- (instancetype)lg_initWithTimeInterval:(NSTimeInterval)ti target:(id)aTarget selector:(SEL)aSelector userInfo:(nullable id)userInfo repeats:(BOOL)yesOrNo{
if (self == [super init]) {
self.target = aTarget;
self.aSelector = aSelector;
self.timer = [NSTimer scheduledTimerWithTimeInterval:ti target:self selector:@selector(fireHome) userInfo:userInfo repeats:yesOrNo];
}
return self;
}
- (void)fireHome{
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
// 让编译器出栈,恢复状态,继续编译后续的代码!
if ([self.target respondsToSelector:self.aSelector]) {
[self.target performSelector:self.aSelector];
}
#pragma clang diagnostic pop
}
- (void)lg_invalidate{
[self.timer invalidate];
self.timer = nil;
}
- (void)dealloc{
NSLog(@"%s",__func__);
}
@end
self.timerWapper = [[LGTimerWapper alloc] lg_initWithTimeInterval:1 target:self selector:@selector(fireHome) userInfo:nil repeats:YES];
- (void)dealloc{
[self.timerWapper lg_invalidate];
}
image.png
-
NSTimer的block方法
image.png - NSProxy
#import "LGProxy.h"
@interface LGProxy()
@property (nonatomic, weak) id object;
@end
@implementation LGProxy
+ (instancetype)proxyWithTransformObject:(id)object{
LGProxy *proxy = [LGProxy alloc];
proxy.object = object;
return proxy;
}
// sel - imp -
// 消息转发 self.object
- (NSMethodSignature *)methodSignatureForSelector:(SEL)sel{
if (self.object) {
}else{
NSLog(@"麻烦收集 stack");
}
return [self.object methodSignatureForSelector:sel];
}
- (void)forwardInvocation:(NSInvocation *)invocation{
if (self.object) {
[invocation invokeWithTarget:self.object];
}else{
// add imp - 动态创建 - imp
// imp 常规 -- pop
// 收集奔溃信息
NSLog(@"麻烦收集 stack");
}
}
@end
self.proxy = [LGProxy proxyWithTransformObject:self];
self.timer = [NSTimer scheduledTimerWithTimeInterval:1 target:self.proxy selector:@selector(fireHome) userInfo:nil repeats:YES]
内存分析
-
静态分析
image.png - MLeakFinder
监听UIViewController类是否发生内存泄漏(这里只考虑push,pop的情况)
#import <Foundation/Foundation.h>
#import <objc/runtime.h>
NS_ASSUME_NONNULL_BEGIN
@interface NSObject (LGLeaks)
+ (BOOL)lg_hookOrigInstanceMenthod:(SEL)oriSEL newInstanceMenthod:(SEL)swizzledSEL;
- (void)lg_willDealloc;
@end
#import "NSObject+LGLeaks.h"
@implementation NSObject (LGLeaks)
- (void)lg_willDealloc{
__weak id weakSelf = self;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
__strong id strongSelf = weakSelf;
NSLog(@"来了");
[strongSelf assertNotDealloc];
});
}
- (void) assertNotDealloc {
NSLog(@"Leaks %@", NSStringFromClass([self class]));
}
/**
对象方法交换
@param oriSEL 原始方法
@param swizzledSEL 新交换方法
@return 交换结果
*/
+ (BOOL)lg_hookOrigInstanceMenthod:(SEL)oriSEL newInstanceMenthod:(SEL)swizzledSEL {
Class cls = self;
Method oriMethod = class_getInstanceMethod(cls, oriSEL);
Method swiMethod = class_getInstanceMethod(cls, swizzledSEL);
if (!swiMethod) {
return NO;
}
if (!oriMethod) {
class_addMethod(cls, oriSEL, method_getImplementation(swiMethod), method_getTypeEncoding(swiMethod));
method_setImplementation(swiMethod, imp_implementationWithBlock(^(id self, SEL _cmd){ }));
}
BOOL didAddMethod = class_addMethod(cls, oriSEL, method_getImplementation(swiMethod), method_getTypeEncoding(swiMethod));
if (didAddMethod) {
class_replaceMethod(cls, swizzledSEL, method_getImplementation(oriMethod), method_getTypeEncoding(oriMethod));
}else{
method_exchangeImplementations(oriMethod, swiMethod);
}
return YES;
}
@end
image.png
#import "UIViewController+LGLeaks.h"
#import "NSObject+LGLeaks.h"
/**
目标:监听UIViewController类是否发生内存泄漏(这里只考虑push,pop的情况)
思路:我们在视图控制器弹出栈,并在这个视图完全消失的时候,监听对象是否还还活着。
步骤:
1)交换视图控制器的viewWillAppear与swizzled_viewWillAppear方法,viewDidDisappear与swizzled_viewDidDisappear方法
2)使用关联方法,获取和设置视图控制进出栈状态
3)且在界面完全消失,并控制器的状态是出栈状态,这时,观察延时观察对象是否存活
*/
// vc - dealloc
// pop vc 存在 泄漏
// 不存在
@implementation UIViewController (LGLeaks)
const void* const kLGHasBeenPoppedKey = &kLGHasBeenPoppedKey;
+ (void)load{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
[self lg_hookOrigInstanceMenthod:@selector(viewWillAppear:) newInstanceMenthod:@selector(lg_viewWillAppear:)];
[self lg_hookOrigInstanceMenthod:@selector(viewDidDisappear:) newInstanceMenthod:@selector(lg_viewDidDisappear:)];
});
}
// 页面进来 - key no
// 页面出去 - key - YES - lg_willDealloc 告诉析构 - dealloc
- (void)lg_viewWillAppear:(BOOL)animated{
[self lg_viewWillAppear:animated];
objc_setAssociatedObject(self, kLGHasBeenPoppedKey, @(NO), OBJC_ASSOCIATION_RETAIN);
}
- (void)lg_viewDidDisappear:(BOOL)animated{
[self lg_viewDidDisappear:animated];
if ([objc_getAssociatedObject(self, kLGHasBeenPoppedKey) boolValue]) {
[self lg_willDealloc];
}
}
@end
#import "UINavigationController+LGLeaks.h"
#import "NSObject+LGLeaks.h"
extern const void* const kLGHasBeenPoppedKey;
@implementation UINavigationController (LGLeaks)
+ (void)load{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
[self lg_hookOrigInstanceMenthod:@selector(popViewControllerAnimated:) newInstanceMenthod:@selector(lg_popViewControllerAnimated:)];
});
}
- (UIViewController *)lg_popViewControllerAnimated:(BOOL)animated{
UIViewController *popView = [self lg_popViewControllerAnimated:animated];
objc_setAssociatedObject(popView, kLGHasBeenPoppedKey, @(YES), OBJC_ASSOCIATION_RETAIN);
return popView;
}
@end
-
instrument
image.png - 查看是否执行了dealloc方法
启动优化
启动分为2种: 冷启动和热启动
启动的时间分为两部分:main函数执⾏行行之前、main函数⾄至应⽤用启动完成
- 查看main启动之前的耗时
main函数之前
-减少动态库、合并⼀一些动态库 减少Objc类、分类的数量量、减少Selector数量量
main函数⾄至应⽤用启动完成
-耗时操作,不不要放在finishLaunching⽅方法中
-
查看文件的大小 LinkMap
image.png
2.按照路径 /Users/a111/Library/Developer/Xcode/DerivedData/002-强引用-garvmecllakalvebeutuisucmipd/Build/Intermediates.noindex/002-强引用.build/Debug-iphoneos/002-强引用.build找到txt文件
3.运行LinkMap
4.选择这个txt文件 开始解析
image.png
image.png -
查看无用文件 LSUnusedResources
1.运行LSUnusedResources
image.png
image.png - 卡顿 WSL_FPS(FPS检测)
1.开启屏幕刷新的计时器
- (instancetype)init{
if (self = [super init]) {
_displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(displayLinkTick:)];
[_displayLink setPaused:YES];
[_displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];
}
return self;
}
- 拿到屏幕刷新的时间点 如果begin未设置过设置为begin 然后等下次屏幕刷新 再次拿到屏幕刷新的时间点 两次的时间点进行相减 同时,刷新次数进行累加 刷新次数除与时间差值 就是刷新的频率
//这个方法的执行频率跟当前屏幕的刷新频率是一样的,屏幕每渲染刷新一次,就执行一次,那么1秒的时长执行刷新的次数就是当前的FPS值
- (void)displayLinkTick:(CADisplayLink *)link{
// duration 是只读的, 表示屏幕刷新的间隔 = 1/fps
// timestamp 是只读的, 表示上次屏幕渲染的时间点
// frameInterval 是表示定时器被触发的间隔, 默认值是1, 就是表示跟屏幕的刷新频率一致。
// NSLog(@"timestamp= %f duration= %f frameInterval= %f",link.timestamp, link.duration, frameInterval);
//初始化屏幕渲染的时间
if (_beginTime == 0) {
_beginTime = link.timestamp;
return;
}
//刷新次数累加
_count++;
//刚刚屏幕渲染的时间与最开始幕渲染的时间差
// 渲染的时间差
NSTimeInterval interval = link.timestamp - _beginTime;
if (interval < 1) {
//不足1秒,继续统计刷新次数
return;
}
//刷新频率
float fps = _count / interval;
if (self.FPSBlock != nil) {
self.FPSBlock(fps);
}
//1秒之后,初始化时间和次数,重新开始监测
_beginTime = link.timestamp;
_count = 0;
}
- 异步渲染(查看YYKit)