YYText介绍
YYText 功能强大的 iOS 富文本编辑与显示框架,是 YYKit 的组件之一。在此感谢作者 ibireme 开源如此优秀的组件。本文将以一个例子分析YYLabel的具体实现。
YYLabel源码分析
本人在阅读的过程中画了一个简单的类图如下:(图画的不对的地方还请指出,对UML还是不太熟悉 /(ㄒoㄒ)/~~ )
说明如下:
**YYAsyncLayerDelegate **
这是一个协议,该协议只有一个必须实现的方法newAsyncDisplayTask。当layer的内容需要刷新的时候该方法被调用,并返回一个刷新任务。
YYAsyncLayerDisplayTask
这是一个抽象类,定义显示任务,没有具体具体的实现,该类具有三个Block类型的属性,分别用来执行显示任务之前,执行时,执行后的操作。
YYAsyncLayer
该类继承自CALayer,并添加了一个属性displaysAsynchronously,用来表示是否异步渲染界面,实现中,覆盖了CALayer的setNeedsDisplay和display方法。
YYSentinel
该类是一个线程安全的原子递增计数器,多用于多线程的情况下。
YYLabel
该类继承自UIVIew,实现了YYAsyncLayerDelegate,并在代理的方法中创建了task。
OK,下面以作者源码中的一个例子来分析源代码
使用代码:
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; //设置阴影
[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) 这个创建label的时候调用了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强转成了YYAsyncLayer,设置了displaysAsynchronously。
再看代码片段1中的(1-b)调用了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)调用了 _setLayoutNeedUpdate
- (void)_setLayoutNeedUpdate {
_state.layoutNeedUpdate = YES;
[self _clearInnerLayout];
[self _setLayoutNeedRedraw];
}
- (void)_setLayoutNeedRedraw {
//(4-a)刷新
[self.layer setNeedsDisplay];
}
在(4-a)中调用了layer的setNeedsDisplay,因为在_initLabel中,已经将layer转成了YYAsyncLayer,这里就会调用到YYAsyncLayer的setNeedsDisplay
- (void)setNeedsDisplay {
[self _cancelAsyncDisplay];
//(5-a)
[super setNeedsDisplay];
}
- (void)display {
super.contents = super.contents;
//调用刷新
[self _displayAsync:_displaysAsynchronously];
}
在(5-a)中,调用了CALayer的setNeedsDisplay,会自动调用display方法,而YYAsyncLayer覆盖了CALayer的display,所以走到了YYAsyncLayer的display。然后调用了_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,之后强转为YYAsyncLayerDelegate,YYLabel中实现了YYAsyncLayerDelegate和newAsyncDisplayTask,拿到task之后进行显示操作。这里大家可能有一个疑问,YYLabel中并没有见到对self.layer.delegate=self的操作啊?这里拿到的delegate怎么就掉到了YYLabel的实现了呢?经过一番查阅,发现在UIVIew的nitWithFrame:(CGRect)theFrame 方法中对代理进行了赋值。参见:Chameleon UIKit源码。
从上面代码中可以看出:task.display是在异步队列中执行的。而willDisplay和didDisplay是在主线程中执行的。
OK现在我们已经将YYLabel的具体组织形式看清楚了,剩下的只是代码的渲染和异步过程的控制。这部分需要大家细致的看每一行代码,具体问题大家可以留言讨论。
交流群
移动开发交流群:264706196