美文网首页
性能优化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