Yee’s Blog

Practicing Thinking Learning Sharing .

AsyncDisplayKit 源码分析

ASDisplayNode

ASDisplayNode 是该Kit的所有public class 中的base class。 ASDisplayNode 提供了与UIView 几乎同样的api接口,其又有两个辅助类。 ASDisplayNode 类中有一个用来在主线程中执行block

1
2
3
4
5
6
7
8
9
10
void ASDisplayNodePerformBlockOnMainThread(void (^block)())
{
  if ([NSThread isMainThread]) {
    block();
  } else {
    dispatch_async(dispatch_get_main_queue(), ^{
      block();
    });
  }
}

这里没有粗暴调用NSObject 执行主线程的方法。

1
2
ASDisplayNode (Subclassing)
ASDisplayNodeInternal.h //定义ASDisplayNode内部使用的方法和property。这里竟然把这些定义一个单独的头文件里。

ASDisplayNode 是异步UI绘制,对于异步并发程序,关键在于是如何设计锁,以及如何进行加锁。 ASDisplayNode 使用以下加锁的方法,那么其加锁是怎么实现的呢? ASDN::MutexLocker l(_propertyLock); 还发现编码习惯,在这些方法体中,都先对参数进行判断,如果不满足条件直接调用return,非常严谨。而且大量使用宏和断言。

1
  ASDisplayNodeAssertThreadAffinity(self);

这行代码不知其意思?????

ASDisplayNode(SubClassing)

提供 subClases of ASDisplayNode 必须后者可以被overriden 的方法。这些方法不可以用于直接调用。 通过实现+displayWithParameters:isCancelled:或者drawRect:withParameters:isCancelled 提供绘制UI;

1
-drawParametersForAsyncLayer

将涉及drawing 的所有property复制到用于display queue(异步)的immutable object。

ASControlNode

在这些方法里,都运用到了断言对参数进行判断NSParameterAssert(action) 这种编码习惯值得借鉴!

1
NSMapTable

发现一个全新的数据集合类型,之前一直没怎么用到过。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
- (void)addTarget:(id)target action:(SEL)action forControlEvents:(ASControlNodeEvent)controlEventMask
{
  NSParameterAssert(action);
  NSParameterAssert(controlEventMask != 0);
  
  // Convert nil to [NSNull null] so that it can be used as a key for NSMapTable.
  if (!target)
    target = [NSNull null];

  // Enumerate the events in the mask, adding the target-action pair for each control event included in controlEventMask
  _ASEnumerateControlEventsIncludedInMaskWithBlock(controlEventMask, ^
    (ASControlNodeEvent controlEvent)
    {
      // Do we already have an event table for this control event?
      id<NSCopying> eventKey = _ASControlNodeEventKeyForControlEvent(controlEvent);
      NSMapTable *eventDispatchTable = [_controlEventDispatchTable objectForKey:eventKey];
      // Create it if necessary.
      if (!eventDispatchTable)
      {
        // Create the dispatch table for this event.
        eventDispatchTable = [NSMapTable weakToStrongObjectsMapTable];
        [_controlEventDispatchTable setObject:eventDispatchTable forKey:eventKey];
      }

      // Have we seen this target before for this event?
      NSMutableArray *targetActions = [eventDispatchTable objectForKey:target];
      if (!targetActions)
      {
        // Nope. Create an actions array for it.
        targetActions = [[NSMutableArray alloc] initWithCapacity:kASControlNodeActionDispatchTableInitialCapacity]; // enough to handle common types without re-hashing the dictionary when adding entries.
        [eventDispatchTable setObject:targetActions forKey:target];
      }

      // Add the action message.
      // Note that bizarrely enough UIControl (at least according to the docs) supports duplicate target-action pairs for a particular control event, so we replicate that behavior.
      [targetActions addObject:NSStringFromSelector(action)];
    });

  self.userInteractionEnabled = YES;
}

下面这个方法是C 风格的方法,为什么要这么写呢?

1
2
3
4
5
6
7
8
9
10
void _ASEnumerateControlEventsIncludedInMaskWithBlock(ASControlNodeEvent mask, void (^block)(ASControlNodeEvent anEvent))
{
  // Start with our first event (touch down) and work our way up to the last event (touch cancel)
  for (ASControlNodeEvent thisEvent = ASControlNodeEventTouchDown; thisEvent <= ASControlNodeEventTouchCancel; thisEvent <<= 1)
  {
    // If it's included in the mask, invoke the block.
    if ((mask & thisEvent) == thisEvent)
      block(thisEvent);
  }
}

ASTableView

对该初始化方法进行分析,发现该类由三个主要组件构成 ASFlowLayoutController, ASRangeController, ASDataController。 这三个controller 也被用来实现ASCollectionView

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
- (instancetype)initWithFrame:(CGRect)frame style:(UITableViewStyle)style
{
  if (!(self = [super initWithFrame:frame style:style]))
    return nil;

  _layoutController = [[ASFlowLayoutController alloc] initWithScrollOption:ASFlowLayoutDirectionVertical];

  _rangeController = [[ASRangeController alloc] init];
  _rangeController.layoutController = _layoutController;
  _rangeController.delegate = self;

  _dataController = [[ASDataController alloc] init];
  _dataController.dataSource = self;
  _dataController.delegate = _rangeController;

  return self;
}

ASNetworkImageNode

可以通过学习这个类,来很好的研究网络下载图片的最佳实践。

1
2
3
4
5
6
7
8
9
10
11
- (instancetype)initWithCache:(id<ASImageCacheProtocol>)cache downloader:(id<ASImageDownloaderProtocol>)downloader
{
  if (!(self = [super init]))
    return nil;

  _cache = cache;
  _downloader = downloader;
  _shouldCacheImage = YES;

  return self;
}

包含一个cache, downloader, 以及一个状态标志flag。 这里需要学习的是如何实现cache,以及 downloader呢? DownLoader 是ASNetworkImageNode 里面的一个property ,也就是组合模式。 取消下载方法:

1
2
3
4
5
6
7
8
9
10
11
- (void)_cancelImageDownload
{
  if (!_imageDownload) {
    return;
  }

  [_downloader cancelImageDownloadForIdentifier:_imageDownload];
  _imageDownload = nil;

  _cacheUUID = nil;
}
1
2
3
4
5
6
7
8
9
10
11
- (void)_downloadImageWithCompletion:(void (^)(CGImageRef))finished
{
  _imageDownload = [_downloader downloadImageWithURL:_URL
                                       callbackQueue:dispatch_get_main_queue()
                               downloadProgressBlock:NULL
                                          completion:^(CGImageRef responseImage, NSError *error) {
                                            if (finished != NULL) {
                                              finished(responseImage);
                                            }
                                          }];
}

在给类NSURLRequest 利用runtime 添加属性,这里使用了static const char修饰符。 因为ASBasicImageDownloader 是 NSURLSession backed,所以只支持iOS7 以上的版本。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@interface NSURLRequest (ASBasicImageDownloader)
@property (nonatomic, strong) ASBasicImageDownloaderMetadata *asyncdisplaykit_metadata;
@end

@implementation NSURLRequest (ASBasicImageDownloader)
static const char *kMetadataKey = NSStringFromClass(ASBasicImageDownloaderMetadata.class).UTF8String;
- (void)setAsyncdisplaykit_metadata:(ASBasicImageDownloaderMetadata *)asyncdisplaykit_metadata
{
  objc_setAssociatedObject(self, kMetadataKey, asyncdisplaykit_metadata, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
- (ASBasicImageDownloader *)asyncdisplaykit_metadata
{
  return objc_getAssociatedObject(self, kMetadataKey);
}
@end