欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

iOS開(kāi)發(fā)底層探索界面優(yōu)化示例詳解

 更新時(shí)間:2022年07月26日 09:35:19   作者:曉之衛(wèi)  
這篇文章主要為大家介紹了iOS開(kāi)發(fā)底層探索界面優(yōu)化示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪

1、卡頓原理

1.1、界面顯示原理

  • CPU:Layout UI布局、文本計(jì)算、Display繪制、Prepare圖片解碼、Commit提交位圖給 GPU
  • GPU:用于渲染,將結(jié)果放入 FrameBuffer
  • FrameBuffer:幀緩沖
  • Video Controller:根據(jù)Vsync(垂直同步)信號(hào),逐行讀取 FrameBuffer 中的數(shù)據(jù),經(jīng)過(guò)數(shù)模轉(zhuǎn)換傳遞給 Monitor
  • Monitor:顯示器,用于顯示;對(duì)于顯示模塊來(lái)說(shuō),會(huì)按照手機(jī)刷新率以固定的頻率:1 / 刷新率 向 FrameBuffer 索要數(shù)據(jù),這個(gè)索要數(shù)據(jù)的命令就是 垂直同步信號(hào)Vsync(低刷60幀為16.67毫秒,高刷120幀為 8.33毫秒,下邊舉例主要以低刷16.67毫秒為主)

1.2、界面撕裂

顯示端每16.67ms從 FrameBuffer(幀緩存區(qū))讀取一幀數(shù)據(jù),如果遇到耗時(shí)操作交付不了,那么當(dāng)前畫面就還是舊一幀的畫面,但顯示過(guò)程中,下一幀數(shù)據(jù)準(zhǔn)備完畢,導(dǎo)致部分顯示的又是新數(shù)據(jù),這樣就會(huì)造成屏幕撕裂

1.3、界面卡頓

為了解決界面撕裂,蘋果使用雙緩沖機(jī)制 + 垂直同步信號(hào),使用 2個(gè)FrameBuffer 存儲(chǔ) GPU 處理結(jié)果,顯示端交替從這2個(gè)FrameBuffer中讀取數(shù)據(jù),一個(gè)被讀取時(shí)另一個(gè)去緩存;但解決界面撕裂的問(wèn)題也帶來(lái)了新的問(wèn)題:掉幀

如果遇到畫面帶馬賽克等情況,導(dǎo)致GPU渲染能力跟不上,會(huì)有2種掉幀情況;

如圖,F(xiàn)rameBuffer2 未渲染完第2幀,下一個(gè)16.67ms去 FrameBuffer1 中拿第3幀:

  • 掉幀情況1:第3幀渲染完畢,接下來(lái)需要第4幀,第2幀被丟棄
  • 掉幀情況2:第3幀未渲染完,再一個(gè)16.67ms去 FrameBuffer2 拿到第2幀,但第1幀多停留了16.67*2毫秒

小結(jié)

固定的時(shí)間間隔會(huì)收到垂直同步信號(hào)(Vsync),如果 CPU 和 GPU 還沒(méi)有將下一幀數(shù)據(jù)放到對(duì)應(yīng)的幀 FrameBuffer緩沖區(qū),就會(huì)出現(xiàn) 掉幀

2、卡頓檢測(cè)

2.1、CADisplayLink

系統(tǒng)在每次發(fā)送 VSync 時(shí),就會(huì)觸發(fā)CADisplayLink,通過(guò)統(tǒng)計(jì)每秒發(fā)送 VSync 的數(shù)量來(lái)查看 App 的 FPS 是否穩(wěn)定

#import "ViewController.h"
@interface ViewController ()
@property (nonatomic, strong) CADisplayLink *link;
@property (nonatomic, assign) NSTimeInterval lastTime;  // 每隔1秒記錄一次時(shí)間
@property (nonatomic, assign) NSUInteger count;         // 記錄VSync1秒內(nèi)發(fā)送的數(shù)量
@end
@implementation ViewController
- (void)viewDidLoad {
    [super viewDidLoad];
    self.link = [CADisplayLink displayLinkWithTarget:self selector:@selector(linkAction:)];
    [_link addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];
}
- (void)linkAction: (CADisplayLink *)link {
    if (_lastTime == 0) {
        _lastTime = link.timestamp;
        return;
    }
    _count++;
    NSTimeInterval delta = link.timestamp - _lastTime;
    if (delta < 1) return;
    _lastTime = link.timestamp;
    float fps = _count / delta;
    _count = 0;
    NSLog(@"?? FPS : %f ", fps);
}
@end

2.2、RunLoop檢測(cè)

RunLoop 的退出和進(jìn)入實(shí)質(zhì)都是Observer的通知,我們可以監(jiān)聽(tīng)Runloop的狀態(tài),并在相關(guān)回調(diào)里發(fā)送信號(hào),如果在設(shè)定的時(shí)間內(nèi)能夠收到信號(hào)說(shuō)明是流暢的;如果在設(shè)定的時(shí)間內(nèi)沒(méi)有收到信號(hào),說(shuō)明發(fā)生了卡頓。

#import "LZBlockMonitor.h"
@interface LZBlockMonitor (){
    CFRunLoopActivity activity;
}
@property (nonatomic, strong) dispatch_semaphore_t semaphore;
@property (nonatomic, assign) NSUInteger timeoutCount;
@end
@implementation LZBlockMonitor
+ (instancetype)sharedInstance {
    static id instance = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        instance = [[self alloc] init];
    });
    return instance;
}
- (void)start{
    [self registerObserver];
    [self startMonitor];
}
- (void)registerObserver{
    CFRunLoopObserverContext context = {0, (__bridge void*)self, NULL, NULL};
    //NSIntegerMax : 優(yōu)先級(jí)最小
    CFRunLoopObserverRef observer = CFRunLoopObserverCreate(kCFAllocatorDefault,
                                                            kCFRunLoopAllActivities,
                                                            YES,
                                                            NSIntegerMax,
                                                            &CallBack,
                                                            &context);
    CFRunLoopAddObserver(CFRunLoopGetMain(), observer, kCFRunLoopCommonModes);
}
static void CallBack(CFRunLoopObserverRef observer, CFRunLoopActivity activity, void *info)
{
    LZBlockMonitor *monitor = (__bridge LZBlockMonitor *)info;
    monitor->activity = activity;
    // 發(fā)送信號(hào)
    dispatch_semaphore_t semaphore = monitor->_semaphore;
    dispatch_semaphore_signal(semaphore);
}
- (void)startMonitor{
    // 創(chuàng)建信號(hào)
    _semaphore = dispatch_semaphore_create(0);
    // 在子線程監(jiān)控時(shí)長(zhǎng)
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        while (YES)
        {
            // 超時(shí)時(shí)間是 1 秒,沒(méi)有等到信號(hào)量,st 就不等于 0, RunLoop 所有的任務(wù)
            long st = dispatch_semaphore_wait(self->_semaphore, dispatch_time(DISPATCH_TIME_NOW, 1 * NSEC_PER_SEC));
            if (st != 0)
            {
                if (self->activity == kCFRunLoopBeforeSources || self->activity == kCFRunLoopAfterWaiting)
                {
                    if (++self->_timeoutCount < 2){
                        NSLog(@"timeoutCount==%lu",(unsigned long)self->_timeoutCount);
                        continue;
                    }
                    // 一秒左右的衡量尺度 很大可能性連續(xù)來(lái) 避免大規(guī)模打印!
                    NSLog(@"檢測(cè)到超過(guò)兩次連續(xù)卡頓");
                }
            }
            self->_timeoutCount = 0;
        }
    });
}
@end
  • 主線程監(jiān)聽(tīng) kCFRunLoopBeforeSources(即將處理事件)和kCFRunLoopAfterWaiting(即將休眠),子線程監(jiān)控時(shí)長(zhǎng),若連續(xù)兩次 1秒 內(nèi)沒(méi)有收到信號(hào),說(shuō)明發(fā)生了卡頓

2.3、微信matrix

  • 微信的matrix也是借助 runloop 實(shí)現(xiàn),大體流程與上面 Runloop 方式相同,它使用退火算法優(yōu)化捕獲卡頓的效率,防止連續(xù)捕獲相同的卡頓,并且通過(guò)保存最近的20個(gè)主線程堆棧信息,獲取最近最耗時(shí)堆棧

2.4、滴滴DoraemonKit

  • DoraemonKit的卡頓檢測(cè)方案不使用 RunLoop,它也是while循環(huán)中根據(jù)一定的狀態(tài)判斷,通過(guò)主線程中不斷發(fā)送信號(hào)semaphore,循環(huán)中等待信號(hào)的時(shí)間為5秒,等待超時(shí)則說(shuō)明主線程卡頓,并進(jìn)行相關(guān)上報(bào)

3、優(yōu)化方法

平時(shí)簡(jiǎn)單的方案有:

  • 避免使用 透明UIView
  • 盡量使用PNG圖片
  • 避免離屏渲染(圓角使用貝塞爾曲線等)

3.1、預(yù)排版

  • 就是常規(guī)的在Model層請(qǐng)求數(shù)據(jù)后提前將cell高度算好

3.2、預(yù)編碼 / 解碼

UIImage 是一個(gè)Model,二進(jìn)制流數(shù)據(jù) 存儲(chǔ)在DataBuffer中,經(jīng)過(guò)decode解碼,加載到imageBuffer中,最終進(jìn)入FrameBuffer才能被渲染

  • 當(dāng)使用 UIImage 或CGImageSource的方法創(chuàng)建圖片時(shí),圖片的數(shù)據(jù)不會(huì)立即解碼,而是在設(shè)置UIImageView.image時(shí)解碼
  • 將圖片設(shè)置到UIImageView/CALayer.contents中,然后在CALayer提交至GPU渲染前,CGImage中的數(shù)據(jù)才進(jìn)行解碼
  • 如果任由系統(tǒng)處理,這一步則無(wú)法避免,并且會(huì)發(fā)生在主線程中。如果想避免這個(gè)機(jī)制,在子線程先將圖片繪制到CGBitmapContext,然后從Bitmap中創(chuàng)建圖片

3.3、按需加載

如果目標(biāo)行與當(dāng)前行相差超過(guò)指定行數(shù),只加載目標(biāo)滾動(dòng)范圍的前后指定3行

- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView{
    [needLoadArr removeAllObjects];
}
- (void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoint)velocity targetContentOffset:(inout CGPoint *)targetContentOffset{
    NSIndexPath *ip = [self indexPathForRowAtPoint:CGPointMake(0, targetContentOffset->y)];
    NSIndexPath *cip = [[self indexPathsForVisibleRows] firstObject];
    NSInteger skipCount = 8;
    if (labs(cip.row-ip.row)>skipCount) {
        NSArray *temp = [self indexPathsForRowsInRect:CGRectMake(0, targetContentOffset->y, self.width, self.height)];
        NSMutableArray *arr = [NSMutableArray arrayWithArray:temp];
        if (velocity.y<0) {
            NSIndexPath *indexPath = [temp lastObject];
            if (indexPath.row+3<datas.count) {
                [arr addObject:[NSIndexPath indexPathForRow:indexPath.row+1 inSection:0]];
                [arr addObject:[NSIndexPath indexPathForRow:indexPath.row+2 inSection:0]];
                [arr addObject:[NSIndexPath indexPathForRow:indexPath.row+3 inSection:0]];
            }
        } else {
            NSIndexPath *indexPath = [temp firstObject];
            if (indexPath.row>3) {
                [arr addObject:[NSIndexPath indexPathForRow:indexPath.row-3 inSection:0]];
                [arr addObject:[NSIndexPath indexPathForRow:indexPath.row-2 inSection:0]];
                [arr addObject:[NSIndexPath indexPathForRow:indexPath.row-1 inSection:0]];
            }
        }
        [needLoadArr addObjectsFromArray:arr];
    }
}

在滑動(dòng)結(jié)束時(shí)進(jìn)行 Cell 的渲染

- (BOOL)scrollViewShouldScrollToTop:(UIScrollView *)scrollView{
    scrollToToping = YES;
    return YES;
}
- (void)scrollViewDidEndScrollingAnimation:(UIScrollView *)scrollView{
    scrollToToping = NO;
    [self loadContent];
}
- (void)scrollViewDidScrollToTop:(UIScrollView *)scrollView{
    scrollToToping = NO;
    [self loadContent];
}
//用戶觸摸時(shí)第一時(shí)間加載內(nèi)容
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event{
    if (!scrollToToping) {
        [needLoadArr removeAllObjects];
        [self loadContent];
    }
    return [super hitTest:point withEvent:event];
}
- (void)loadContent{
    if (scrollToToping) {
        return;
    }
    if (self.indexPathsForVisibleRows.count<=0) {
        return;
    }
    if (self.visibleCells && self.visibleCells.count>0) {
        for (id temp in [self.visibleCells copy]) {
            VVeboTableViewCell *cell = (VVeboTableViewCell *)temp;
            [cell draw];
        }
    }
}
  • 這種方式會(huì)導(dǎo)致滑動(dòng)時(shí)有空白內(nèi)容,因此要做好占位內(nèi)容

3.4、異步渲染

  • 異步渲染 就是在子線程把需要繪制的圖形提前處理好,然后將處理好的圖像數(shù)據(jù)直接返給主線程使用
  • 異步渲染操作的是layer層,將多層堆疊的控件們通過(guò)UIGraphics畫成一張位圖,然后展示在layer.content上

3.4.1、CALayer

  • CALayer基于CoreAnimation進(jìn)而基于QuartzCode,只負(fù)責(zé)顯示,且顯示的是位圖,不能處理用戶的觸摸事件
  • 不需要與用戶交互時(shí),使用 UIView 和 CALayer 都可以,甚至 CALayer 更簡(jiǎn)潔高效

3.4.2、異步渲染實(shí)現(xiàn)

  • 異步渲染的框架推薦:Graver、YYAsyncLayer
  • CALayer 在調(diào)用display方法后回去調(diào)用繪制相關(guān)的方法,繪制會(huì)執(zhí)行drawRect:方法

簡(jiǎn)單例子

繼承 CALayer

#import "LZLayer.h"
@implementation LZLayer
//前面斷點(diǎn)調(diào)用寫下的代碼
- (void)layoutSublayers{
    if (self.delegate && [self.delegate respondsToSelector:@selector(layoutSublayersOfLayer:)]) {
        //UIView
        [self.delegate layoutSublayersOfLayer:self];
    }else{
        [super layoutSublayers];
    }
}
//繪制流程的發(fā)起函數(shù)
- (void)display{
    // Graver 實(shí)現(xiàn)思路
    CGContextRef context = (__bridge CGContextRef)([self.delegate performSelector:@selector(createContext)]);
    [self.delegate layerWillDraw:self];
    [self drawInContext:context];
    [self.delegate displayLayer:self];
    [self.delegate performSelector:@selector(closeContext)];
}
@end

繼承 UIView

// - (CGContextRef)createContext 和 - (void)closeContext要在.h中聲明
#import "LZView.h"
#import "LZLayer.h"
@implementation LZView
- (void)drawRect:(CGRect)rect {
    // Drawing code, 繪制的操作, BackingStore(額外的存儲(chǔ)區(qū)域產(chǎn)于的) -- GPU
}
//子視圖的布局
- (void)layoutSubviews{
    [super layoutSubviews];
}
+ (Class)layerClass{
    return [LZLayer class];
}
//
- (void)layoutSublayersOfLayer:(CALayer *)layer{
    [super layoutSublayersOfLayer:layer];
    [self layoutSubviews];
}
- (CGContextRef)createContext{
    UIGraphicsBeginImageContextWithOptions(self.bounds.size, self.layer.opaque, self.layer.contentsScale);
    CGContextRef context = UIGraphicsGetCurrentContext();
    return context;
}
- (void)layerWillDraw:(CALayer *)layer{
    //繪制的準(zhǔn)備工作,do nontihing
}
//繪制的操作
- (void)drawLayer:(CALayer *)layer inContext:(CGContextRef)ctx{
    [super drawLayer:layer inContext:ctx];
    // 畫個(gè)不規(guī)則圖形
    CGContextMoveToPoint(ctx, self.bounds.size.width / 2- 20, 20);
    CGContextAddLineToPoint(ctx, self.bounds.size.width / 2 + 20, 20);
    CGContextAddLineToPoint(ctx, self.bounds.size.width / 2 + 40, 80);
    CGContextAddLineToPoint(ctx, self.bounds.size.width / 2 - 40, 100);
    CGContextAddLineToPoint(ctx, self.bounds.size.width / 2 - 20, 20);
    CGContextSetFillColorWithColor(ctx, UIColor.magentaColor.CGColor);
    CGContextSetStrokeColorWithColor(ctx, UIColor.magentaColor.CGColor); // 描邊
    CGContextDrawPath(ctx, kCGPathFillStroke);
    // 畫個(gè)紅色方塊
    [[UIColor redColor] set];
       //Core Graphics
    UIBezierPath *path = [UIBezierPath bezierPathWithRect:CGRectMake(self.bounds.size.width / 2- 20, self.bounds.size.height / 2- 20, 40, 40)];
    CGContextAddPath(ctx, path.CGPath);
    CGContextFillPath(ctx);
    // 文字
    [@"LZ" drawInRect:CGRectMake(self.bounds.size.width / 2 - 40, 100, 80, 24) withAttributes:@{NSFontAttributeName: [UIFont systemFontOfSize:20],NSForegroundColorAttributeName: UIColor.blueColor}];
    // 圖片
    [[UIImage imageWithContentsOfFile:@"/Volumes/Disk_D/test code/Test/Test/yasuo.png"] drawInRect:CGRectMake(10, self.bounds.size.height/2, self.bounds.size.width  - 20, self.bounds.size.height/2 -10)];
}
//layer.contents = (位圖)
- (void)displayLayer:(CALayer *)layer{
    UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
    dispatch_async(dispatch_get_main_queue(), ^{
        layer.contents = (__bridge id)(image.CGImage);
    });
}
- (void)closeContext{
    UIGraphicsEndImageContext();
}

控件們被繪制成了一張圖

此外,雖然將控件畫到一張位圖上,但是還有問(wèn)題,就是控件的交互事件,內(nèi)容較多建議鉆研一下graver的源碼

以上就是iOS開(kāi)發(fā)底層探索界面優(yōu)化示例詳解的詳細(xì)內(nèi)容,更多關(guān)于iOS開(kāi)發(fā)界面優(yōu)化的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • IOS點(diǎn)擊按鈕隱藏狀態(tài)欄詳解及實(shí)例代碼

    IOS點(diǎn)擊按鈕隱藏狀態(tài)欄詳解及實(shí)例代碼

    這篇文章主要介紹了IOS點(diǎn)擊按鈕隱藏狀態(tài)欄詳解及實(shí)例代碼的相關(guān)資料,需要的朋友可以參考下
    2017-02-02
  • iOS改變UITextField占位文字顏色的三種方法

    iOS改變UITextField占位文字顏色的三種方法

    這篇文章主要為大家詳細(xì)介紹了iOS改變UITextField的占位文字顏色的三種方法,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2017-01-01
  • 詳解Swift 利用Opration和OprationQueue來(lái)下載網(wǎng)絡(luò)圖片

    詳解Swift 利用Opration和OprationQueue來(lái)下載網(wǎng)絡(luò)圖片

    這篇文章主要介紹了詳解Swift 利用Opration和OprationQueue來(lái)下載網(wǎng)絡(luò)圖片的相關(guān)資料,希望通過(guò)本文能幫助到大家,需要的朋友可以參考下
    2017-09-09
  • ios實(shí)現(xiàn)簡(jiǎn)易隊(duì)列

    ios實(shí)現(xiàn)簡(jiǎn)易隊(duì)列

    這篇文章主要為大家詳細(xì)介紹了ios實(shí)現(xiàn)簡(jiǎn)易隊(duì)列,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2020-02-02
  • iOS富文本的使用方法示例詳解

    iOS富文本的使用方法示例詳解

    這篇文章主要給大家介紹了關(guān)于iOS富文本的使用方法,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧。
    2017-12-12
  • iOS 雷達(dá)效果實(shí)例詳解

    iOS 雷達(dá)效果實(shí)例詳解

    這篇文章主要介紹了iOS 雷達(dá)效果實(shí)例詳解的相關(guān)資料,需要的朋友可以參考下
    2016-09-09
  • 淺談iOS開(kāi)發(fā)如何適配暗黑模式(Dark Mode)

    淺談iOS開(kāi)發(fā)如何適配暗黑模式(Dark Mode)

    這篇文章主要介紹了淺談iOS開(kāi)發(fā)如何適配暗黑模式(Dark Mode),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2019-09-09
  • ios 實(shí)現(xiàn)倒計(jì)時(shí)的兩種方式

    ios 實(shí)現(xiàn)倒計(jì)時(shí)的兩種方式

    這篇文章主要介紹了ios實(shí)現(xiàn)倒計(jì)時(shí)的兩種方式,第一種方式使用NSTimer來(lái)實(shí)現(xiàn),第二種方式使用GCD來(lái)實(shí)現(xiàn)。具體內(nèi)容詳情大家參考下本文
    2017-01-01
  • iOS如何改變UIBarButtonItem的大小詳解

    iOS如何改變UIBarButtonItem的大小詳解

    這篇文章主要給大家介紹了關(guān)于iOS如何改變UIBarButtonItem大小的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2019-02-02
  • iOS實(shí)現(xiàn)翻頁(yè)效果動(dòng)畫實(shí)例代碼

    iOS實(shí)現(xiàn)翻頁(yè)效果動(dòng)畫實(shí)例代碼

    本篇文章主要介紹了iOS實(shí)現(xiàn)翻頁(yè)效果動(dòng)畫實(shí)例代碼,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧
    2017-05-05

最新評(píng)論