YYText源碼閱讀(一):YYLabel

YYText介紹

YYText 功能強(qiáng)大的 iOS 富文本編輯與顯示框架监憎,是 YYKit 的組件之一乘客。在此感謝作者 ibireme 開源如此優(yōu)秀的組件狡刘。本文將以一個(gè)例子分析YYLabel的具體實(shí)現(xiàn)俺祠。

YYLabel源碼分析

本人在閱讀的過程中畫了一個(gè)簡(jiǎn)單的類圖如下:(圖畫的不對(duì)的地方還請(qǐng)指出,對(duì)UML還是不太熟悉 /(ㄒoㄒ)/~~ )

YYLabel類圖

說明如下:
**YYAsyncLayerDelegate **
這是一個(gè)協(xié)議逼纸,該協(xié)議只有一個(gè)必須實(shí)現(xiàn)的方法newAsyncDisplayTask洋措。當(dāng)layer的內(nèi)容需要刷新的時(shí)候該方法被調(diào)用,并返回一個(gè)刷新任務(wù)杰刽。
YYAsyncLayerDisplayTask
這是一個(gè)抽象類菠发,定義顯示任務(wù)王滤,沒有具體具體的實(shí)現(xiàn),該類具有三個(gè)Block類型的屬性滓鸠,分別用來執(zhí)行顯示任務(wù)之前雁乡,執(zhí)行時(shí),執(zhí)行后的操作糜俗。
YYAsyncLayer
該類繼承自CALayer蔗怠,并添加了一個(gè)屬性displaysAsynchronously,用來表示是否異步渲染界面吩跋,實(shí)現(xiàn)中,覆蓋了CALayer的setNeedsDisplay和display方法渔工。
YYSentinel
該類是一個(gè)線程安全的原子遞增計(jì)數(shù)器锌钮,多用于多線程的情況下。
YYLabel
該類繼承自UIVIew,實(shí)現(xiàn)了YYAsyncLayerDelegate引矩,并在代理的方法中創(chuàng)建了task梁丘。

OK,下面以作者源碼中的一個(gè)例子來分析源代碼

Demo

使用代碼:

    NSMutableAttributedString *text = [NSMutableAttributedString new];
    
    {
        NSMutableAttributedString *one = [[NSMutableAttributedString alloc] initWithString:@"Shadow"];
        one.font = [UIFont boldSystemFontOfSize:30];
        one.color = [UIColor whiteColor];
        
        //(1-a)
        YYTextShadow *shadow = [YYTextShadow new];
        shadow.color = [UIColor colorWithWhite:0.000 alpha:0.490];
        shadow.offset = CGSizeMake(0, 1);
        shadow.radius = 5;
        one.textShadow = shadow;    //設(shè)置陰影
        [text appendAttributedString:one];   
    }

    YYLabel *label = [YYLabel new]; 
    //(1-b)
    label.attributedText = text;
    label.width = self.view.width;
    label.height = self.view.height - (kiOS7Later ? 64 : 44);
    label.top = (kiOS7Later ? 64 : 0);
    label.textAlignment = NSTextAlignmentCenter;
    label.textVerticalAlignment = YYTextVerticalAlignmentCenter;
    label.numberOfLines = 0;
    label.backgroundColor = [UIColor colorWithWhite:0.933 alpha:1.000];
    [self.view addSubview:label];

首先我們看 (1-a) 這個(gè)創(chuàng)建label的時(shí)候調(diào)用了YYLabel的- (void)_initLabel方法:

- (void)_initLabel {
    //(2-a)
    ((YYAsyncLayer *)self.layer).displaysAsynchronously = NO;
    self.layer.contentsScale = [UIScreen mainScreen].scale;
    self.contentMode = UIViewContentModeRedraw;
    
    _attachmentViews = [NSMutableArray new];
    _attachmentLayers = [NSMutableArray new];
    
    _debugOption = [YYTextDebugOption sharedDebugOption];
    [YYTextDebugOption addDebugTarget:self];
    
    _font = [self _defaultFont];
    _textColor = [UIColor blackColor];
    _textVerticalAlignment = YYTextVerticalAlignmentCenter;
    _numberOfLines = 1;
    _lineBreakMode = NSLineBreakByTruncatingTail;
    _innerText = [NSMutableAttributedString new];
    _innerContainer = [YYTextContainer new];
    _innerContainer.truncationType = YYTextTruncationTypeEnd;
    _innerContainer.maximumNumberOfRows = _numberOfLines;
    _clearContentsBeforeAsynchronouslyDisplay = YES;
    _fadeOnAsynchronouslyDisplay = YES;
    _fadeOnHighlight = YES;
    
    self.isAccessibilityElement = YES;
}

這里我們看(2-a)旺韭,將layer強(qiáng)轉(zhuǎn)成了YYAsyncLayer氛谜,設(shè)置了displaysAsynchronously。
再看代碼片段1中的(1-b)調(diào)用了YYLabel的setAttributedText:(NSAttributedString *)attributedText

- (void)setAttributedText:(NSAttributedString *)attributedText {
    if (attributedText.length > 0) {
        _innerText = attributedText.mutableCopy;
        switch (_lineBreakMode) {
            case NSLineBreakByWordWrapping:
            case NSLineBreakByCharWrapping:
            case NSLineBreakByClipping: {
                _innerText.lineBreakMode = _lineBreakMode;
            } break;
            case NSLineBreakByTruncatingHead:
            case NSLineBreakByTruncatingTail:
            case NSLineBreakByTruncatingMiddle: {
                _innerText.lineBreakMode = NSLineBreakByWordWrapping;
            } break;
            default: break;
        }
    } else {
        _innerText = [NSMutableAttributedString new];
    }
    
    [_textParser parseText:_innerText selectedRange:NULL];
    if (!_ignoreCommonProperties) {
        if (_displaysAsynchronously && _clearContentsBeforeAsynchronouslyDisplay) {
            [self _clearContents];
        }
        [self _updateOuterTextProperties];
        //(3-a)
        [self _setLayoutNeedUpdate];
        [self _endTouch];
        [self invalidateIntrinsicContentSize];
    }
}

(3-a)調(diào)用了 _setLayoutNeedUpdate

- (void)_setLayoutNeedUpdate {
    _state.layoutNeedUpdate = YES;
    [self _clearInnerLayout];
    [self _setLayoutNeedRedraw];
}

- (void)_setLayoutNeedRedraw {
    //(4-a)刷新
    [self.layer setNeedsDisplay];
}

在(4-a)中調(diào)用了layer的setNeedsDisplay区端,因?yàn)樵赺initLabel中值漫,已經(jīng)將layer轉(zhuǎn)成了YYAsyncLayer,這里就會(huì)調(diào)用到Y(jié)YAsyncLayer的setNeedsDisplay

- (void)setNeedsDisplay {
    [self _cancelAsyncDisplay];
   //(5-a)
    [super setNeedsDisplay];
}
- (void)display {
    super.contents = super.contents;
    //調(diào)用刷新
    [self _displayAsync:_displaysAsynchronously];
}

在(5-a)中织盼,調(diào)用了CALayer的setNeedsDisplay杨何,會(huì)自動(dòng)調(diào)用display方法,而YYAsyncLayer覆蓋了CALayer的display沥邻,所以走到了YYAsyncLayer的display危虱。然后調(diào)用了_displayAsync

- (void)_displayAsync:(BOOL)async {
    //(6-a)
    __strong id<YYAsyncLayerDelegate> delegate = self.delegate;
    YYAsyncLayerDisplayTask *task = [delegate newAsyncDisplayTask];
    if (!task.display) {
        if (task.willDisplay) task.willDisplay(self);
        self.contents = nil;
        if (task.didDisplay) task.didDisplay(self, YES);
        return;
    }
     
    //(6-b)
    if (async) {
        if (task.willDisplay) task.willDisplay(self);
        YYSentinel *sentinel = _sentinel;
        int32_t value = sentinel.value;
        //?
        BOOL (^isCancelled)() = ^BOOL() {
            return value != sentinel.value;
        };
        CGSize size = self.bounds.size;
        BOOL opaque = self.opaque;
        CGFloat scale = self.contentsScale;
        CGColorRef backgroundColor = (opaque && self.backgroundColor) ? CGColorRetain(self.backgroundColor) : NULL;
        if (size.width < 1 || size.height < 1) {
            CGImageRef image = (__bridge_retained CGImageRef)(self.contents);
            self.contents = nil;
            if (image) {
                dispatch_async(YYAsyncLayerGetReleaseQueue(), ^{
                    CFRelease(image);
                });
            }
            if (task.didDisplay) task.didDisplay(self, YES);
            CGColorRelease(backgroundColor);
            return;
        }
        //異步了
        dispatch_async(YYAsyncLayerGetDisplayQueue(), ^{
            if (isCancelled()) {
                CGColorRelease(backgroundColor);
                return;
            }
            UIGraphicsBeginImageContextWithOptions(size, opaque, scale);
            CGContextRef context = UIGraphicsGetCurrentContext();
            if (opaque) {
                CGContextSaveGState(context); {
                    if (!backgroundColor || CGColorGetAlpha(backgroundColor) < 1) {
                        CGContextSetFillColorWithColor(context, [UIColor whiteColor].CGColor);
                        CGContextAddRect(context, CGRectMake(0, 0, size.width * scale, size.height * scale));
                        CGContextFillPath(context);
                    }
                    if (backgroundColor) {
                        CGContextSetFillColorWithColor(context, backgroundColor);
                        CGContextAddRect(context, CGRectMake(0, 0, size.width * scale, size.height * scale));
                        CGContextFillPath(context);
                    }
                } CGContextRestoreGState(context);
                CGColorRelease(backgroundColor);
            }
             // display是異步的
            task.display(context, size, isCancelled);
            if (isCancelled()) {
                UIGraphicsEndImageContext();
                dispatch_async(dispatch_get_main_queue(), ^{
                    if (task.didDisplay) task.didDisplay(self, NO);
                });
                return;
            }
            UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
            UIGraphicsEndImageContext();
            if (isCancelled()) {
                dispatch_async(dispatch_get_main_queue(), ^{
                    if (task.didDisplay) task.didDisplay(self, NO);
                });
                return;
            }
            dispatch_async(dispatch_get_main_queue(), ^{
                if (isCancelled()) {
                    if (task.didDisplay) task.didDisplay(self, NO);
                } else {
                    self.contents = (__bridge id)(image.CGImage);
                    if (task.didDisplay) task.didDisplay(self, YES);
                }
            });
        });
    } else {
        [_sentinel increase];
        if (task.willDisplay) task.willDisplay(self);
        UIGraphicsBeginImageContextWithOptions(self.bounds.size, self.opaque, self.contentsScale);
        CGContextRef context = UIGraphicsGetCurrentContext();
        if (self.opaque) {
            CGSize size = self.bounds.size;
            size.width *= self.contentsScale;
            size.height *= self.contentsScale;
            CGContextSaveGState(context); {
                if (!self.backgroundColor || CGColorGetAlpha(self.backgroundColor) < 1) {
                    CGContextSetFillColorWithColor(context, [UIColor whiteColor].CGColor);
                    CGContextAddRect(context, CGRectMake(0, 0, size.width, size.height));
                    CGContextFillPath(context);
                }
                if (self.backgroundColor) {
                    CGContextSetFillColorWithColor(context, self.backgroundColor);
                    CGContextAddRect(context, CGRectMake(0, 0, size.width, size.height));
                    CGContextFillPath(context);
                }
            } CGContextRestoreGState(context);
        }
        task.display(context, self.bounds.size, ^{return NO;});
        UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
        UIGraphicsEndImageContext();
        self.contents = (__bridge id)(image.CGImage);
        if (task.didDisplay) task.didDisplay(self, YES);
    }
}

(6-a)中拿到了delegate,之后強(qiáng)轉(zhuǎn)為YYAsyncLayerDelegate唐全,YYLabel中實(shí)現(xiàn)了YYAsyncLayerDelegate和newAsyncDisplayTask埃跷,拿到task之后進(jìn)行顯示操作。這里大家可能有一個(gè)疑問邮利,YYLabel中并沒有見到對(duì)self.layer.delegate=self的操作懊直ⅰ?這里拿到的delegate怎么就掉到了YYLabel的實(shí)現(xiàn)了呢近弟?經(jīng)過一番查閱缅糟,發(fā)現(xiàn)在UIVIew的nitWithFrame:(CGRect)theFrame 方法中對(duì)代理進(jìn)行了賦值。參見:Chameleon UIKit源碼祷愉。
從上面代碼中可以看出:task.display是在異步隊(duì)列中執(zhí)行的窗宦。而willDisplay和didDisplay是在主線程中執(zhí)行的赦颇。
OK現(xiàn)在我們已經(jīng)將YYLabel的具體組織形式看清楚了,剩下的只是代碼的渲染和異步過程的控制赴涵。這部分需要大家細(xì)致的看每一行代碼媒怯,具體問題大家可以留言討論。

交流群

移動(dòng)開發(fā)交流群:264706196

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末髓窜,一起剝皮案震驚了整個(gè)濱河市扇苞,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌寄纵,老刑警劉巖鳖敷,帶你破解...
    沈念sama閱讀 222,000評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異程拭,居然都是意外死亡定踱,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,745評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門恃鞋,熙熙樓的掌柜王于貴愁眉苦臉地迎上來崖媚,“玉大人,你說我怎么就攤上這事恤浪〕┭疲” “怎么了?”我有些...
    開封第一講書人閱讀 168,561評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵水由,是天一觀的道長荠呐。 經(jīng)常有香客問我,道長绷杜,這世上最難降的妖魔是什么直秆? 我笑而不...
    開封第一講書人閱讀 59,782評(píng)論 1 298
  • 正文 為了忘掉前任,我火速辦了婚禮鞭盟,結(jié)果婚禮上圾结,老公的妹妹穿的比我還像新娘。我一直安慰自己齿诉,他們只是感情好筝野,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,798評(píng)論 6 397
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著粤剧,像睡著了一般歇竟。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上抵恋,一...
    開封第一講書人閱讀 52,394評(píng)論 1 310
  • 那天焕议,我揣著相機(jī)與錄音,去河邊找鬼弧关。 笑死盅安,一個(gè)胖子當(dāng)著我的面吹牛唤锉,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播别瞭,決...
    沈念sama閱讀 40,952評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼窿祥,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了蝙寨?” 一聲冷哼從身側(cè)響起晒衩,我...
    開封第一講書人閱讀 39,852評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎墙歪,沒想到半個(gè)月后听系,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,409評(píng)論 1 318
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡虹菲,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,483評(píng)論 3 341
  • 正文 我和宋清朗相戀三年跛锌,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片届惋。...
    茶點(diǎn)故事閱讀 40,615評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖菠赚,靈堂內(nèi)的尸體忽然破棺而出脑豹,到底是詐尸還是另有隱情,我是刑警寧澤衡查,帶...
    沈念sama閱讀 36,303評(píng)論 5 350
  • 正文 年R本政府宣布瘩欺,位于F島的核電站,受9級(jí)特大地震影響拌牲,放射性物質(zhì)發(fā)生泄漏俱饿。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,979評(píng)論 3 334
  • 文/蒙蒙 一塌忽、第九天 我趴在偏房一處隱蔽的房頂上張望拍埠。 院中可真熱鬧,春花似錦土居、人聲如沸枣购。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,470評(píng)論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽棉圈。三九已至,卻和暖如春眷蜓,著一層夾襖步出監(jiān)牢的瞬間分瘾,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,571評(píng)論 1 272
  • 我被黑心中介騙來泰國打工吁系, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留德召,地道東北人白魂。 一個(gè)月前我還...
    沈念sama閱讀 49,041評(píng)論 3 377
  • 正文 我出身青樓,卻偏偏與公主長得像氏捞,于是被迫代替她去往敵國和親碧聪。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,630評(píng)論 2 359

推薦閱讀更多精彩內(nèi)容

  • 轉(zhuǎn)載:http://www.reibang.com/p/32fcadd12108 每個(gè)UIView有一個(gè)伙伴稱為l...
    F麥子閱讀 6,220評(píng)論 0 13
  • 每個(gè)UIView有一個(gè)伙伴稱為layer液茎,一個(gè)CALayer逞姿。UIView實(shí)際上并沒有把自己畫到屏幕上;它繪制本身...
    shenzhenboy閱讀 3,113評(píng)論 0 17
  • YYText 簡(jiǎn)單介紹 YYText 是YYKit中的一個(gè)富文本顯示,編輯組件,擁有YYLabel捆等,YYText...
    喬克_叔叔閱讀 6,017評(píng)論 8 40
  • iOS的異步渲染 最近看了YYAsyncLayer在這里總結(jié)一下滞造。YYAsyncLayer是整個(gè)YYKit異步渲染...
    uncle_charlie閱讀 3,185評(píng)論 0 5
  • *面試心聲:其實(shí)這些題本人都沒怎么背,但是在上海 兩周半 面了大約10家 收到差不多3個(gè)offer,總結(jié)起來就是把...
    Dove_iOS閱讀 27,165評(píng)論 30 470