iOS-UIView異步繪制

1.異步繪制原理

UIView 中有一個(gè) CALayer 的屬性裸卫,負(fù)責(zé) UIView 具體內(nèi)容的顯示仿贬。具體過(guò)程是系統(tǒng)會(huì)把 UIView 顯示的內(nèi)容(包括 UILabel 的文字,UIImageView 的圖片等)繪制在一張畫布上墓贿,完成后倒出圖片賦值給 CALayercontents 屬性茧泪,完成顯示蜓氨。

這其中的工作都是在主線程中完成的,這就導(dǎo)致了主線程頻繁的處理 UI 繪制的工作队伟,如果要繪制的元素過(guò)多穴吹,過(guò)于頻繁,就會(huì)造成卡頓嗜侮。

那么是否可以將復(fù)雜的繪制過(guò)程放到后臺(tái)線程中執(zhí)行刀荒,從而減輕主線程負(fù)擔(dān),來(lái)提升 UI 流暢度呢棘钞?

答案是可以的缠借,系統(tǒng)給我們留下的異步繪制的口子,請(qǐng)看下面的流程圖宜猜,它是我們進(jìn)行基本繪制的基礎(chǔ)泼返。

UIView繪制流程
  1. UIView調(diào)用setNeedsDisplay并沒(méi)有立刻進(jìn)行繪制;
  2. UIView調(diào)用setNeedsDisplay方法其實(shí)是調(diào)用其layer屬性的同名方法;
  3. 在當(dāng)前runloop即將結(jié)束時(shí)調(diào)用display進(jìn)入繪制流程姨拥;
  4. UIViewlayer.delegate就是UIView本身绅喉,UIView 并沒(méi)有實(shí)現(xiàn)displayLayer:方法,所以進(jìn)入系統(tǒng)的繪制流程叫乌,我們可以通過(guò)實(shí)現(xiàn)displayLayer:方法來(lái)進(jìn)行異步繪制柴罐。

有了上面的異步繪制原理流程圖,我們可以得到一個(gè)實(shí)現(xiàn)異步繪制的初步思路:在“異步繪制入口”去開辟子線程憨奸,然后在子線程中實(shí)現(xiàn)和系統(tǒng)類似的繪制流程革屠。

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

要實(shí)現(xiàn)異步繪制,首先要了解系統(tǒng)的繪制流程排宰,看下面一張流程圖:

系統(tǒng)繪制流程
  1. CALayer在內(nèi)部創(chuàng)建一個(gè)上下文環(huán)境(CGContextRef);
  2. 判斷layer是否有代理:
    沒(méi)有代理:調(diào)用layerdrawInContext:方法似芝,
    有代理:調(diào)用delegatedrawLayer:inContext:方法,然后在合適的時(shí)機(jī)回調(diào)代理板甘,在[UIView drawRect]中進(jìn)行UI的繪制工作党瓮,
  3. 最后layer上傳backingStorebitmap位圖到GPU,也就是將生成的bitmap位圖賦值給layer.content屬性盐类,結(jié)束系統(tǒng)繪制流程寞奸。

3.異步繪制流程

關(guān)于異步繪制,參考如下圖:

異步繪制流程
  1. 某個(gè)時(shí)機(jī)調(diào)用setNeedsDisplay;
  2. runloop將要結(jié)束時(shí)調(diào)用[CALayer display];
  3. 若代理實(shí)現(xiàn)了displayLayer將會(huì)調(diào)用此方法在跳,在子線程中做異步繪制的工作枪萄;
  4. 在子線程中創(chuàng)建上下文、繪制控件并生成圖片硬毕;
  5. 在主線程中設(shè)置layer.contents呻引,將生成的視圖展示在layer上。

異步繪制示例代碼:

#import <UIKit/UIKit.h>

NS_ASSUME_NONNULL_BEGIN

@interface AsyncDrawLabel : UIView

@property (nonatomic, copy) NSString *text;
@property (nonatomic, strong) UIFont *font;

@end

NS_ASSUME_NONNULL_END
#import "AsyncDrawLabel.h"
#import <CoreText/CoreText.h>

@implementation AsyncDrawLabel

- (void)setText:(NSString *)text {
    _text = text;
}

- (void)setFont:(UIFont *)font {
    _font = font;
}


// 除了在drawRect方法中, 其他地方獲取context需要自己創(chuàng)建[http://www.reibang.com/p/86f025f06d62] coreText用法簡(jiǎn)介:[https://www.cnblogs.com/purple-sweet-pottoes/p/5109413.html]
 
- (void)displayLayer:(CALayer *)layer {
    CGSize size = self.bounds.size;
    CGFloat scale = [UIScreen mainScreen].scale;
    // 異步繪制吐咳,切換至子線程
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        UIGraphicsBeginImageContextWithOptions(size, NO, scale);
        // 獲取當(dāng)前上下文
        CGContextRef context = UIGraphicsGetCurrentContext();
        [self draw:context size:size];
        UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
        UIGraphicsEndImageContext();
        // 子線程完成工作逻悠,切換至主線程顯示
        dispatch_async(dispatch_get_main_queue(), ^{
            self.layer.contents = (__bridge id)image.CGImage;
        });
    });
}

- (void)draw:(CGContextRef)context size:(CGSize)size {
    // 將坐標(biāo)系上下翻轉(zhuǎn)元践,因?yàn)榈讓幼鴺?biāo)系和 UIKit 坐標(biāo)系原點(diǎn)位置不同。
    CGContextSetTextMatrix(context, CGAffineTransformIdentity);
    // 文本沿著Y軸移動(dòng)
    CGContextTranslateCTM(context, 0, size.height); // 原點(diǎn)為左下角
    // 文本反轉(zhuǎn)成context坐標(biāo)系
    CGContextScaleCTM(context, 1, -1);
    // 創(chuàng)建繪制區(qū)域
    CGMutablePathRef path = CGPathCreateMutable();
    CGPathAddRect(path, NULL, CGRectMake(0, 0, size.width, size.height));
    // 創(chuàng)建需要繪制的文字
    NSMutableAttributedString *attrStr = [[NSMutableAttributedString alloc]initWithString:self.text];
    [attrStr addAttribute:NSFontAttributeName value:self.font range:NSMakeRange(0, self.text.length)];
    // 根據(jù)attStr生成CTFramesetterRef
    CTFramesetterRef framesetter = CTFramesetterCreateWithAttributedString((CFAttributedStringRef)attrStr);
    CTFrameRef frame = CTFramesetterCreateFrame(framesetter, CFRangeMake(0, attrStr.length), path, NULL);
    // 將frame的內(nèi)容繪制到content中
    CTFrameDraw(frame, context);
}

@end
#import "ViewController.h"
#import "AsyncDrawLabel.h"

@interface ViewController ()

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view.
    
    AsyncDrawLabel *label = [[AsyncDrawLabel alloc] initWithFrame:CGRectMake(100, 100, 200, 100)];
    label.backgroundColor = [UIColor yellowColor];
    label.text = @"異步繪制text";
    label.font = [UIFont systemFontOfSize:16];
    [self.view addSubview:label];
    [label.layer setNeedsDisplay]; // 不調(diào)用的話不會(huì)觸發(fā)displayLayer方法
}

@end
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末童谒,一起剝皮案震驚了整個(gè)濱河市单旁,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌饥伊,老刑警劉巖象浑,帶你破解...
    沈念sama閱讀 218,284評(píng)論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異琅豆,居然都是意外死亡愉豺,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,115評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門茫因,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)蚪拦,“玉大人,你說(shuō)我怎么就攤上這事冻押〕鄞” “怎么了?”我有些...
    開封第一講書人閱讀 164,614評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵洛巢,是天一觀的道長(zhǎng)括袒。 經(jīng)常有香客問(wèn)我,道長(zhǎng)稿茉,這世上最難降的妖魔是什么锹锰? 我笑而不...
    開封第一講書人閱讀 58,671評(píng)論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮狈邑,結(jié)果婚禮上城须,老公的妹妹穿的比我還像新娘。我一直安慰自己米苹,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,699評(píng)論 6 392
  • 文/花漫 我一把揭開白布砰琢。 她就那樣靜靜地躺著蘸嘶,像睡著了一般。 火紅的嫁衣襯著肌膚如雪陪汽。 梳的紋絲不亂的頭發(fā)上训唱,一...
    開封第一講書人閱讀 51,562評(píng)論 1 305
  • 那天,我揣著相機(jī)與錄音挚冤,去河邊找鬼况增。 笑死,一個(gè)胖子當(dāng)著我的面吹牛训挡,可吹牛的內(nèi)容都是我干的澳骤。 我是一名探鬼主播歧强,決...
    沈念sama閱讀 40,309評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼为肮!你這毒婦竟也來(lái)了摊册?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,223評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤颊艳,失蹤者是張志新(化名)和其女友劉穎茅特,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體棋枕,經(jīng)...
    沈念sama閱讀 45,668評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡白修,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,859評(píng)論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了重斑。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片兵睛。...
    茶點(diǎn)故事閱讀 39,981評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖绸狐,靈堂內(nèi)的尸體忽然破棺而出卤恳,到底是詐尸還是另有隱情,我是刑警寧澤寒矿,帶...
    沈念sama閱讀 35,705評(píng)論 5 347
  • 正文 年R本政府宣布突琳,位于F島的核電站,受9級(jí)特大地震影響符相,放射性物質(zhì)發(fā)生泄漏拆融。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,310評(píng)論 3 330
  • 文/蒙蒙 一啊终、第九天 我趴在偏房一處隱蔽的房頂上張望镜豹。 院中可真熱鬧,春花似錦蓝牲、人聲如沸趟脂。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,904評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)昔期。三九已至,卻和暖如春佛玄,著一層夾襖步出監(jiān)牢的瞬間硼一,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,023評(píng)論 1 270
  • 我被黑心中介騙來(lái)泰國(guó)打工梦抢, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留般贼,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,146評(píng)論 3 370
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像哼蛆,于是被迫代替她去往敵國(guó)和親蕊梧。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,933評(píng)論 2 355