iOS AsyncDisplayKit 코드 읽기 노트

18390 단어
인터페이스 유창성을 유지하는 기교를 봤다면 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의 호출 시기는 ASDisplayNodeload 방법에서
+ (void)load
{
  // Ensure this value is cached on the main thread before needed in the background.
  ASScreenScale();
}


scale의 값이 이미 계산되었고 주 라인에서 계산되었는지 확인하십시오.UIKit 관련 코드를 사용했기 때문에 반드시 메인 라인에 있어야 합니다.
5: ASSentinel ASSentinel의 역할은 레이어가 반복적으로 렌더링되는 것을 방지하는 것이다. 예를 들어 레이어가 화면을 넘어섰거나 다음에 렌더링된 데이터가 왔으면 표시가 필요하고 이번에는 렌더링을 하지 않아도 된다.
@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; ///)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];
   }

이전의 번거로운 판단을 생략했다.
화려한 분할선!!!!
노드가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가 이미 표시되면 만듭니다.
뷰의 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 만들기 절차:
- (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를 만들 때 ASDisplay Node AssertMainThread () 를 사용합니다.매크로는 주 라인에서만 만들 수 있습니다.마지막으로 _loadViewOrLayerIsLayerBacked 함수를 호출할 것입니다.YES 가져오기 layer 만들기 뷰 가져오기 NO
- (void)_loadViewOrLayerIsLayerBacked:(BOOL)isLayerBacked
{
  ///   node        ,   
  if (self._isDeallocating) {
    return;
  }

  ///        ,    
  if (![self __shouldLoadViewOrLayer]) {
    return;
  }

  ///    layer
  if (isLayerBacked) {
    _layer = [self _layerToLoad];
    _layer.delegate = (id)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;
}


초기화할 때 제공된다면view Block, 바로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];
  }
}

터치 시리즈의 함수 재작성은 현재 node가 대응하는 방법을 실현했는지 판단하고 실현되면 이벤트를 node에 전달합니다.ASControlNode의 실현은 터치 함수의 전달 덕분이다.

분할선


두 개의 obj가 같은지 아닌지를 판단하다

ASDISPLAYNODE_INLINE BOOL ASObjectIsEqual(id obj, id 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의 기본 속성 값을 설정합니다. 이것은 디스플레이 Asynchronously를 설정하는 데 기본적으로 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 병행입니다.그리고 디스패치set_target_queue 설정된 DISPATCHQUEUE_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, 그림 부분은 에이전트, 즉 노드에 맡겼는데...ok, 우리 노드의 그리는 부분을 보자...관련 코드는 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 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대신블로그를 통해 인터페이스를 원활하게 유지하는 기교를 이해할 수 있다

좋은 웹페이지 즐겨찾기