美文网首页
性能优化3

性能优化3

作者: 纳兰沫 | 来源:发表于2020-05-20 09:58 被阅读0次

循环引用

image.png
image.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启动之前的耗时
image.png

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;
}
  1. 拿到屏幕刷新的时间点 如果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)

相关文章

网友评论

      本文标题:性能优化3

      本文链接:https://www.haomeiwen.com/subject/vofbohtx.html