如果看過 保持界面流暢技巧
這篇文章肯定會對AsyncDisplayKit 有強烈的好奇心。
此文章系 AsyncDisplayKit 的源碼閱讀筆記。零散的記錄一些點。讀完了說不定就能開始分析 AsyncDisplayKit 了
1:后臺釋放變量
void ASPerformBlockOnDeallocationQueue(void (^block)())
{
static dispatch_queue_t queue;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
queue = dispatch_queue_create("org.AsyncDisplayKit.deallocationQueue", DISPATCH_QUEUE_SERIAL);
});
dispatch_async(queue, block);
}
ASPerformBlockOnDeallocationQueue
用來釋放對象在后臺線程,讓界面更流暢。 比如應用
- (void)_clearImage
{
// Destruction of bigger images on the main thread can be expensive
// and can take some time, so we dispatch onto a bg queue to
// actually dealloc.
__block UIImage *image = self.image;
CGSize imageSize = image.size;
BOOL shouldReleaseImageOnBackgroundThread = imageSize.width > kMinReleaseImageOnBackgroundSize.width ||
imageSize.height > kMinReleaseImageOnBackgroundSize.height;
if (shouldReleaseImageOnBackgroundThread) {
ASPerformBlockOnDeallocationQueue(^{
image = nil;
});
}
///TODO
///
根據注釋看,說大圖在主線程釋放的時候會消耗更好的性能和時間。此處,最小尺寸是
static const CGSize kMinReleaseImageOnBackgroundSize = {20.0, 20.0};
20x20尺寸的圖,在我們的應用里比這個大很多的多的是吧!但是從來沒有想過,要在其他線程釋放!
所以 AsyncDisplayKit 性能好是有道理的,性能處理,做到極致!!
2:一些特定的方法只能在父類中實現。
BOOL ASSubclassOverridesSelector(Class superclass, Class subclass, SEL selector)
{
Method superclassMethod = class_getInstanceMethod(superclass, selector);
Method subclassMethod = class_getInstanceMethod(subclass, selector);
IMP superclassIMP = superclassMethod ? method_getImplementation(superclassMethod) : NULL;
IMP subclassIMP = subclassMethod ? method_getImplementation(subclassMethod) : NULL;
return (superclassIMP != subclassIMP);
}
ASSubclassOverridesSelector
用來判斷子類是不是實現了父類的某個方法,用法,配合#define ASDisplayNodeAssert(...) NSAssert(__VA_ARGS__)
宏,在initialize
方法中進行斷言,某些方法,子類一定不能實現。。
比如ASImageNode
中
+ (void)initialize
{
[super initialize];
if (self != [ASImageNode class]) {
// Prevent custom drawing in subclasses
ASDisplayNodeAssert(!ASSubclassOverridesClassSelector([ASImageNode class], self, @selector(displayWithParameters:isCancelled:)), @"Subclass %@ must not override displayWithParameters:isCancelled: method. Custom drawing in %@ subclass is not supported.", NSStringFromClass(self), NSStringFromClass([ASImageNode class]));
}
}
3:任務放到主線程的正確姿勢
void ASPerformBlockOnMainThread(void (^block)())
{
if (block == nil){
return;
}
if (ASDisplayNodeThreadIsMain()) {
block();
} else {
dispatch_async(dispatch_get_main_queue(), block);
}
}
如果不做判斷,就會讓任務執行的時機發生變化!
4:消耗性能的操作,盡量只做一次求值操作
比如:
CGFloat ASScreenScale()
{
static CGFloat __scale = 0.0;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
ASDisplayNodeCAssertMainThread();
__scale = [[UIScreen mainScreen] scale];
});
return __scale;
}
ASScreenScale
的調用時機在 ASDisplayNode
的 load
方法中
+ (void)load
{
// Ensure this value is cached on the main thread before needed in the background.
ASScreenScale();
}
確保scale 的值已經計算好了,并且是在主線程中計算的。因為使用UIKit相關代碼了,所以一定要確保在主線程。
5:ASSentinel
ASSentinel
的作用是防止 layer 被重復渲染使用的,比如,一個layer 已經超出屏幕或者下一次渲染的數據已經來了,那么,需要一個標示,來標記,此次不用渲染了。
@interface ASSentinel : NSObject
- (int32_t)value;
- (int32_t)increment;
@end
定義很簡單,返回一個值,increment 遞增當前的值。
在基類的 _initializeInstance
函數中初始化,
_displaySentinel = [[ASSentinel alloc] init];
在需要關閉當前渲染的時候進行自增
- (void)cancelDisplayAsyncLayer:(_ASDisplayLayer *)asyncLayer
{
[_displaySentinel increment];
}
或者
- (void)cancelAsyncDisplay
{
ASDisplayNodeAssertMainThread();
[_displaySentinel increment];
.........
}
delegate 函數的判斷,也可以使用結構體標示有沒有實現某個方法,比如:
struct ASDisplayNodeFlags {
// public properties
unsigned synchronous:1;
unsigned layerBacked:1;
unsigned displaysAsynchronously:1;
unsigned shouldRasterizeDescendants:1;
unsigned shouldBypassEnsureDisplay:1;
unsigned displaySuspended:1;
unsigned shouldAnimateSizeChanges:1;
unsigned hasCustomDrawingPriority:1;
}
具體使用如下(ps鄙人項目中的使用):
struct {
unsigned hasJingxiangAction:1;
unsigned hasCameraAction :1;
unsigned hasMuteAction :1;
unsigned hasRoomManagerAction:1;
unsigned hasFollowlistAction:1;
}_delefateAction; ///<delelgate 行為
設置代理的時候
- (void)setDelegate:(id<PXYMoreActionDelegate>)delegate {
_delegate = delegate;
_delefateAction.hasJingxiangAction = [_delegate respondsToSelector:@selector(jingxiangAction)];
_delefateAction.hasCameraAction = [_delegate respondsToSelector:@selector(cameraAction)];
_delefateAction.hasMuteAction = [_delegate respondsToSelector:@selector(muteAction)];
_delefateAction.hasRoomManagerAction = [_delegate respondsToSelector:@selector(roomManagerAciton)];
_delefateAction.hasFollowlistAction = [_delegate respondsToSelector:@selector(followerListAction)];
}
不用每次都調用respondsToSelector
方法,稍微提升一點效率,使用的時候就可以這樣子判斷了
if (_delefateAction.hasRoomManagerAction) {
[_delegate roomManagerAciton];
}
省去了之前繁瑣的判斷。
華麗分割線!!!!
我們看看 node 是什么時候創建 view 或者 layer 的。(顯示視圖,肯定是 view或者layer了)
- (void)addSubnode:(ASDisplayNode *)subnode
{
///斷言 #省略
ASDisplayNodeAssert(subnode, @"Cannot insert a nil subnode");
ASDisplayNode *oldParent = subnode.supernode;
///#不能重復添加或者自己添加自己
if (!subnode || subnode == self || oldParent == self) {
return;
}
///#添加子節點
BOOL isMovingEquivalentParents = disableNotificationsForMovingBetweenParents(oldParent, self);
if (isMovingEquivalentParents) {
[subnode __incrementVisibilityNotificationsDisabled];
}
[subnode removeFromSupernode];
if (!_subnodes) {
_subnodes = [[NSMutableArray alloc] init];
}
[_subnodes addObject:subnode];
// This call will apply our .hierarchyState to the new subnode.
// If we are a managed hierarchy, as in ASCellNode trees, it will also apply our .interfaceState.
[subnode __setSupernode:self];
///#如果當前的node 已經有 view 或者 layer 就給子節點同樣創建一個 view 或者 layer并且添加進來。。
if (self.nodeLoaded) {
ASPerformBlockOnMainThread(^{
[self _addSubnodeSubviewOrSublayer:subnode];
});
}
ASDisplayNodeAssert(isMovingEquivalentParents == disableNotificationsForMovingBetweenParents(oldParent, self), @"Invariant violated");
if (isMovingEquivalentParents) {
[subnode __decrementVisibilityNotificationsDisabled];
}
}
從流程上看,在 addSubnode 的時候,如果父node已經有顯示出來了,就創建。
還有另外一種情況,調用 view的 addSubnode 方法:
- (void)addSubnode:(ASDisplayNode *)subnode
{
if (subnode.layerBacked) {
// Call -addSubnode: so that we use the asyncdisplaykit_node path if possible.
[self.layer addSubnode:subnode];
} else {
ASDisplayNode *selfNode = self.asyncdisplaykit_node;
if (selfNode) {
[selfNode addSubnode:subnode];
} else {
[self addSubview:subnode.view];
}
}
}
最后如果一步 [self addSubview:subnode.view];的時候,就開始走 view的創建流程了。
view 的創建流程:
- (UIView *)view
{
if (_flags.layerBacked) {
return nil;
}
if (!_view) {
ASDisplayNodeAssertMainThread();
[self _loadViewOrLayerIsLayerBacked:NO];
}
return _view;
}
- (CALayer *)layer
{
if (!_layer) {
ASDisplayNodeAssertMainThread();
if (!_flags.layerBacked) {
return self.view.layer;
}
[self _loadViewOrLayerIsLayerBacked:YES];
}
return _layer;
}
創建 view 或者 layer 的時候,都會使用 ASDisplayNodeAssertMainThread(); 宏來限定,只能在主線程創建。。最后都會調用
_loadViewOrLayerIsLayerBacked
函數。如果創建layer 傳入 YES 創建 view 傳入 NO
- (void)_loadViewOrLayerIsLayerBacked:(BOOL)isLayerBacked
{
/// 如果node 已經是釋放狀態,不創建
if (self._isDeallocating) {
return;
}
/// 如果沒有父節點,直接返回
if (![self __shouldLoadViewOrLayer]) {
return;
}
/// 創建 layer
if (isLayerBacked) {
_layer = [self _layerToLoad];
_layer.delegate = (id<CALayerDelegate>)self;
} else {
/// 創建 view
_view = [self _viewToLoad];
_view.asyncdisplaykit_node = self;
_layer = _view.layer;
}
_layer.asyncdisplaykit_node = self;
self.asyncLayer.asyncDelegate = self;
///# 其他狀態設置
......
}
創建view 的過程 如下:
- (UIView *)_viewToLoad
{
UIView *view;
ASDN::MutexLocker l(__instanceLock__);
if (_viewBlock) {
view = _viewBlock();
ASDisplayNodeAssertNotNil(view, @"View block returned nil");
ASDisplayNodeAssert(![view isKindOfClass:[_ASDisplayView class]], @"View block should return a synchronously displayed view");
_viewBlock = nil;
_viewClass = [view class];
} else {
if (!_viewClass) {
_viewClass = [self.class viewClass];
}
view = [[_viewClass alloc] init];
}
// Update flags related to special handling of UIImageView layers. More details on the flags
if (_flags.synchronous && [_viewClass isSubclassOfClass:[UIImageView class]]) {
_flags.canClearContentsOfLayer = NO;
_flags.canCallSetNeedsDisplayOfLayer = NO;
}
return view;
}
如果初始化的時候有提供_viewBlock,就是用 _viewBlock 返回一個view,要么就是用默認的view進行創建。
+ (Class)viewClass
{
return [_ASDisplayView class];
}
+ (Class)layerClass
{
return [_ASDisplayLayer class];
}
ASDisplayNode
提供了內置的 view 和 layer ,_ASDisplayView 用來轉發一些事件,_ASDisplayLayer 是 性能提升的關鍵。
從上面可以看出 node 節點就是包裝了 view 和 layer 的 載體,可以通過node 設置 view 或者 layer的屬性。在view 或者 layer需要顯示的時候,把屬性設置上去。
這里忽略掉了布局相關的代碼。以后有空了補補
_ASDisplayView
_ASDisplayView 是 ASDisplayNode 的 一個私有類,只能在ASDisplayNode 中使用,不能繼承和重寫。前面有提到過
+ (Class)viewClass
{
return [_ASDisplayView class];
}
_ASDisplayView 的 作用就是重寫了UIView的一些方法用來轉發一些事件給 ASDisplayNode,下面,我們看看主要的函數。
+ (Class)layerClass
{
return [_ASDisplayLayer class];
}
重寫 layerClass 函數,使用可以異步繪制的 類 _ASDisplayLayer。
- (void)willMoveToWindow:(UIWindow *)newWindow
{
BOOL visible = (newWindow != nil);
if (visible && !_node.inHierarchy) {
[_node __enterHierarchy];
}
}
將要添加到window上的時候,把事件轉給Node. 當然還有其他的類似 didMoveToWindow
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
if (_node.methodOverrides & ASDisplayNodeMethodOverrideTouchesBegan) {
[_node touchesBegan:touches withEvent:event];
} else {
[super touchesBegan:touches withEvent:event];
}
}
touche 系列的函數的重寫,判斷當前node 是否實現了對應的方法,如果實現了,就把事件轉發給node. ASControlNode
的實現,就得益于touch 函數的轉發。
分割線
判斷兩個obj是否相等
ASDISPLAYNODE_INLINE BOOL ASObjectIsEqual(id<NSObject> obj, id<NSObject> otherObj)
{
return obj == otherObj || [obj isEqual:otherObj];
}
參數可以為nil 此處!
_ASDisplayLayer
看看_ASDisplayLayer 的實現。
typedef BOOL(^asdisplaynode_iscancelled_block_t)(void);
用來標示當前異步的操作是否已經結束
@interface _ASDisplayLayer : CALayer
///#設置是否異步展示
@property (nonatomic, assign) BOOL displaysAsynchronously;
///#關閉當前的異步展示
- (void)cancelAsyncDisplay;
///#異步展示的隊列
+ (dispatch_queue_t)displayQueue;
///#計數器,之前講過的
@property (nonatomic, strong, readonly) ASSentinel *displaySentinel;
///#展示過程中的代理
@property (nonatomic, weak) id<_ASDisplayLayerDelegate> asyncDelegate;
///#掛起異步顯示
@property (nonatomic, assign, getter=isDisplaySuspended) BOOL displaySuspended;
///#直接展示
- (void)displayImmediately;
ASCALayerExtendedDelegate
bounds 改變的時候回調用這個代理的方法。
_ASDisplayLayerDelegate
異步渲染的時候一些回調,node 可以實現這些方法,來實現自己的渲染,
_ASDisplayLayer實現
- (instancetype)init
{
if ((self = [super init])) {
_displaySentinel = [[ASSentinel alloc] init];
self.opaque = YES;
}
return self;
}
_displaySentinel 初始化異步展示關閉的條件。
看一些主要的方法。
+ (id)defaultValueForKey:(NSString *)key
{
if ([key isEqualToString:@"displaysAsynchronously"]) {
return @YES;
} else {
return [super defaultValueForKey:key];
}
}
設置layer的默認的屬性值,這是設置 displaysAsynchronously 默認為 YES。(默認開啟異步展示效果)
- (void)setNeedsDisplay
{
///#判斷是不是主線程
ASDisplayNodeAssertMainThread();
///#線程鎖
_displaySuspendedLock.lock();
///關閉當前的繪制,已經在繪制的不能被關閉,如果還沒有開始繪制,但是在隊列里面,是能關閉的
[self cancelAsyncDisplay];
// Short circuit if display is suspended. When resumed, we will setNeedsDisplay at that time.
if (!_displaySuspended) {
[super setNeedsDisplay];
}
_displaySuspendedLock.unlock();
}
setNeedsDisplay 設置內容需要重新繪制。開始繪制之前,先關閉上次還沒有開始繪制的任務!
+ (dispatch_queue_t)displayQueue
{
static dispatch_queue_t displayQueue = NULL;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
displayQueue = dispatch_queue_create("org.AsyncDisplayKit.ASDisplayLayer.displayQueue", DISPATCH_QUEUE_CONCURRENT);
// we use the highpri queue to prioritize UI rendering over other async operations
dispatch_set_target_queue(displayQueue, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0));
});
return displayQueue;
}
displayQueue 異步繪制的隊列。注意,隊列是 DISPATCH_QUEUE_CONCURRENT 并行的。并且 dispatch_set_target_queue 設置的 DISPATCH_QUEUE_PRIORITY_HIGH。跟UI繪制相關的隊列,優先級最高。
- (void)display
{
///#此方法只能在主線程調用
ASDisplayNodeAssertMainThread();
[self _hackResetNeedsDisplay];
///如果已經掛起了,返回
if (self.isDisplaySuspended) {
return;
}
///開始調用繪制
[self display:self.displaysAsynchronously];
}
- (void)display:(BOOL)asynchronously
{
///如果大小為0 的時候是否繪制
if (CGRectIsEmpty(self.bounds)) {
_attemptedDisplayWhileZeroSized = YES;
}
id<_ASDisplayLayerDelegate> NS_VALID_UNTIL_END_OF_SCOPE strongAsyncDelegate;
{
_asyncDelegateLock.lock();
strongAsyncDelegate = _asyncDelegate;
_asyncDelegateLock.unlock();
}
///開始調用代理的方法,進行繪制
[strongAsyncDelegate displayAsyncLayer:self asynchronously:asynchronously];
}
so,繪制部分交給了代理,也就是Node....ok,我們看一下node的繪制部分。。。
相關代碼在ASDisplayNode+AsyncDisplay.mm
文件中
- (void)displayAsyncLayer:(_ASDisplayLayer *)asyncLayer asynchronously:(BOOL)asynchronously
{
///必須在主線程進行
ASDisplayNodeAssertMainThread();
///上鎖
ASDN::MutexLocker l(__instanceLock__);
if (_hierarchyState & ASHierarchyStateRasterized) {
return;
}
///判斷有沒有取消當前繪制的block
ASSentinel *displaySentinel = (asynchronously ? _displaySentinel : nil);
int32_t displaySentinelValue = [displaySentinel increment];
asdisplaynode_iscancelled_block_t isCancelledBlock = ^{
return BOOL(displaySentinelValue != displaySentinel.value);
};
///生成繪制的block 返回一個image 直接設置在layer上
asyncdisplaykit_async_transaction_operation_block_t displayBlock = [self _displayBlockWithAsynchronous:asynchronously isCancelledBlock:isCancelledBlock rasterizing:NO];
///#如果沒有繪制的block,那就沒有必要進行了
if (!displayBlock) {
return;
}
///插入runloop的block
ASDisplayNodeAssert(_layer, @"Expect _layer to be not nil");
asyncdisplaykit_async_transaction_operation_completion_block_t completionBlock = ^(id<NSObject> value, BOOL canceled){
ASDisplayNodeCAssertMainThread();
if (!canceled && !isCancelledBlock()) {
UIImage *image = (UIImage *)value;
BOOL stretchable = (NO == UIEdgeInsetsEqualToEdgeInsets(image.capInsets, UIEdgeInsetsZero));
if (stretchable) {
ASDisplayNodeSetupLayerContentsWithResizableImage(_layer, image);
} else {
_layer.contentsScale = self.contentsScale;
_layer.contents = (id)image.CGImage;
}
[self didDisplayAsyncLayer:self.asyncLayer];
}
};
///調用即將渲染layer的函數
[self willDisplayAsyncLayer:self.asyncLayer];
if (asynchronously) {///如果是異步的,加入到runloop中
// Async rendering operations are contained by a transaction, which allows them to proceed and concurrently
// while synchronizing the final application of the results to the layer's contents property (completionBlock).
// First, look to see if we are expected to join a parent's transaction container.
CALayer *containerLayer = _layer.asyncdisplaykit_parentTransactionContainer ? : _layer;
// In the case that a transaction does not yet exist (such as for an individual node outside of a container),
// this call will allocate the transaction and add it to _ASAsyncTransactionGroup.
// It will automatically commit the transaction at the end of the runloop.
_ASAsyncTransaction *transaction = containerLayer.asyncdisplaykit_asyncTransaction;
// Adding this displayBlock operation to the transaction will start it IMMEDIATELY.
// The only function of the transaction commit is to gate the calling of the completionBlock.
[transaction addOperationWithBlock:displayBlock priority:self.drawingPriority queue:[_ASDisplayLayer displayQueue] completion:completionBlock];
} else {
///不是異步,直接把image,設置在layer上!!
UIImage *contents = (UIImage *)displayBlock();
completionBlock(contents, NO);
}
}
此處,相關技術可以通過YY大神博客了解 保持界面流暢技巧