iOS UIView繪制&異步繪制

1. UIView的繪制流程圖

  1. UIView調用setNeedsDisplay,但是沒立即進行視圖的繪制工作;
  2. UIView調用setNeedDisplay后,系統(tǒng)調用view對應layersetNeedsDisplay方法;
  3. 當前runloop即將結束的時候調用CALayerdisplay方法;
  4. runloop即將結束, 開始視圖的繪制流程;

知識點:為什么調用UIViewsetNeedsDisplay后界面并沒有立即繪制?
在當前Runloop將要結束的時候才會開始界面的繪制;


2. 系統(tǒng)繪制流程圖


1.CALayer內部創(chuàng)建一個backing store(CGContextRef)();

  1. 判斷l(xiāng)ayer是否有代理;
    2.1 有代理:調用delegetedrawLayer:inContext, 然后在合適的實際回調代理, 在[UIView drawRect]中做一些繪制工作;
    2.2 沒有代理:調用layerdrawInContext方法,
  2. layer上傳backingStoreGPU, 結束系統(tǒng)的繪制流程;

3. 異步繪制

什么是異步繪制?
個人理解為:展示界面的過程中將創(chuàng)建上下文和控件的繪制工作放到子線程中, 子線程將那些工作完成渲染成圖片后轉回主線程然后將位圖展示在界面上;
異步繪制的入口在[layer.delegate displayLayer], 異步繪制過程中代理負責生成對應的位圖(bitmap);然后將bitmap賦值給layer.content屬性展示;

關于異步繪制,可以參考下圖片的的解碼邏輯
例如 imgV.image = [UIImage imageNamed:@"1.png"];賦值左側后其實并不是直接展示到屏幕上的,而是需要壓縮圖片的二進制數(shù)據(jù), 然后再解碼成屏幕可以識別的數(shù)據(jù)格式(位圖); 這個解碼過程默認都是在主線程進行的; 將其放在子線程則可以減輕主線程壓力; 這個過程可以參考SDWebImage中的+ (UIImage *)decodedImageWithImage:(UIImage *)image函數(shù)的處理邏輯, 每次圖片下載后是先在子線程將圖片解碼成位圖, 然后用的時候在主線程展示;

異步繪制流程
  1. 某個時機調用setNeedsDisplay;
  2. runloop將要結束的時候調用[CALayer display]
  3. 如果代理實現(xiàn)了dispalyLayer將會調用此方法, 在子線程中去做異步繪制的工作;
  4. 子線程中做的工作:創(chuàng)建上下文, 控件的繪制, 生成圖片;
  5. 轉到主線程, 設置layer.contents, 將生成的視圖展示在layer上面;

異步繪制示例代碼:

#import <UIKit/UIKit.h>

NS_ASSUME_NONNULL_BEGIN
@interface AsyncLabel : UIView
@property (nonatomic, copy)     NSString    *asynText;
@property (nonatomic, strong)   UIFont      *asynFont;
@property (nonatomic, strong)   UIColor     *asynBGColor;
@end
NS_ASSUME_NONNULL_END
#import "AsyncLabel.h"
#import <CoreText/CoreText.h>

@implementation AsyncLabel

- (void)displayLayer:(CALayer *)layer {
    /**
     除了在drawRect方法中, 其他地方獲取context需要自己創(chuàng)建[http://www.itdecent.cn/p/86f025f06d62]
     coreText用法簡介:[https://www.cnblogs.com/purple-sweet-pottoes/p/5109413.html]
     */
      CGSize size = self.bounds.size;;
      CGFloat scale = [UIScreen mainScreen].scale;
    ///異步繪制:切換至子線程
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        UIGraphicsBeginImageContextWithOptions(size, NO, scale);
        ///獲取當前上下文
        CGContextRef context = UIGraphicsGetCurrentContext();
        ///將坐標系反轉
        CGContextSetTextMatrix(context, CGAffineTransformIdentity);
        ///文本沿著Y軸移動
        CGContextTranslateCTM(context, 0, size.height);
        ///文本反轉成context坐標系
        CGContextScaleCTM(context, 1.0, -1.0);
        ///創(chuàng)建繪制區(qū)域
        CGMutablePathRef path = CGPathCreateMutable();
        CGPathAddRect(path, NULL, CGRectMake(0, 0, size.width, size.height));
        ///創(chuàng)建需要繪制的文字
        NSMutableAttributedString *attStr = [[NSMutableAttributedString alloc] initWithString:self.asynText];
        [attStr addAttribute:NSFontAttributeName value:self.asynFont range:NSMakeRange(0, self.asynText.length)];
        [attStr addAttribute:NSBackgroundColorAttributeName value:self.asynBGColor range:NSMakeRange(0, self.asynText.length)];
        ///根據(jù)attStr生成CTFramesetterRef
        CTFramesetterRef frameSetter = CTFramesetterCreateWithAttributedString((CFAttributedStringRef)attStr);
        CTFrameRef frame = CTFramesetterCreateFrame(frameSetter, CFRangeMake(0, attStr.length), path, NULL);
        ///將frame的內容繪制到content中
        CTFrameDraw(frame, context);
        UIImage *getImg = UIGraphicsGetImageFromCurrentImageContext();
        UIGraphicsEndImageContext();
        ///子線程完成工作, 切換到主線程展示
        dispatch_async(dispatch_get_main_queue(), ^{
            self.layer.contents = (__bridge id)getImg.CGImage;
        });
    });
}

@end

#import "ViewController.h"
#import "AsyncLabel.h"

@interface ViewController ()
@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    AsyncLabel *asLabel = [[AsyncLabel alloc] initWithFrame:CGRectMake(50, 100, 200, 200)];
    asLabel.backgroundColor = [UIColor cyanColor];
    asLabel.asynBGColor = [UIColor greenColor];
    asLabel.asynFont = [UIFont systemFontOfSize:16 weight:20];
    asLabel.asynText = @"學習異步繪制相關知識點, 學習異步繪制相關知識點";
    [self.view addSubview:asLabel];
    ///不調用的話不會觸發(fā) displayLayer方法
    [asLabel.layer setNeedsDisplay];
}

@end

示例代碼下載
日常開發(fā)中推薦YYKit作者出的YYAsyncLayer


參考文檔:
iOS UIGraphicsGetCurrentContext()的使用
iOS CoreText原理及基本使用方法
iOS 圖片的解碼

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

友情鏈接更多精彩內容