{"action":"created","comment":{"url":"https://api.github.com/repos/facebook/AsyncDisplayKit/pulls/comments/53562105","id":53562105,"diff_hunk":"@@ -0,0 +1,76 @@\n+//\n+//  ASWeakSet.m\n+//  AsyncDisplayKit\n+//\n+//  Created by Adlai Holler on 1/7/16.\n+//  Copyright © 2016 Facebook. All rights reserved.\n+//\n+\n+#import \"ASWeakSet.h\"\n+\n+@interface ASWeakSet<__covariant ObjectType> ()\n+@property (nonatomic, strong, readonly) NSMapTable<ObjectType, NSNull *> *mapTable;\n+@end\n+\n+@implementation ASWeakSet\n+\n+- (instancetype)init\n+{\n+  self = [super init];\n+  if (self) {\n+    _mapTable = [NSMapTable weakToStrongObjectsMapTable];\n+  }\n+  return self;\n+}\n+\n+- (void)addObject:(id)object\n+{\n+  [_mapTable setObject:[NSNull null] forKey:object];\n+}\n+\n+- (void)removeObject:(id)object\n+{\n+  [_mapTable removeObjectForKey:object];\n+}\n+\n+- (void)removeAllObjects\n+{\n+  [_mapTable removeAllObjects];\n+}\n+\n+- (BOOL)containsObject:(id)object\n+{\n+  return [_mapTable objectForKey:object] != nil;\n+}\n+\n+- (BOOL)isEmpty\n+{\n+  for (__unused id object in _mapTable) {","path":"AsyncDisplayKit/Private/ASWeakSet.m","position":48,"original_position":48,"commit_id":"e244b10e1e530e2e907af507c1e8254b069b22e1","original_commit_id":"e244b10e1e530e2e907af507c1e8254b069b22e1","user":{"login":"appleguy","id":565251,"avatar_url":"https://avatars.githubusercontent.com/u/565251?v=3","gravatar_id":"","url":"https://api.github.com/users/appleguy","html_url":"https://github.com/appleguy","followers_url":"https://api.github.com/users/appleguy/followers","following_url":"https://api.github.com/users/appleguy/following{/other_user}","gists_url":"https://api.github.com/users/appleguy/gists{/gist_id}","starred_url":"https://api.github.com/users/appleguy/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/appleguy/subscriptions","organizations_url":"https://api.github.com/users/appleguy/orgs","repos_url":"https://api.github.com/users/appleguy/repos","events_url":"https://api.github.com/users/appleguy/events{/privacy}","received_events_url":"https://api.github.com/users/appleguy/received_events","type":"User","site_admin":false},"body":"lol!!! nice one","created_at":"2016-02-21T05:59:11Z","updated_at":"2016-02-21T05:59:11Z","html_url":"https://github.com/facebook/AsyncDisplayKit/pull/1048#discussion_r53562105","pull_request_url":"https://api.github.com/repos/facebook/AsyncDisplayKit/pulls/1048","_links":{"self":{"href":"https://api.github.com/repos/facebook/AsyncDisplayKit/pulls/comments/53562105"},"html":{"href":"https://github.com/facebook/AsyncDisplayKit/pull/1048#discussion_r53562105"},"pull_request":{"href":"https://api.github.com/repos/facebook/AsyncDisplayKit/pulls/1048"}}},"pull_request":{"url":"https://api.github.com/repos/facebook/AsyncDisplayKit/pulls/1048","id":55433780,"html_url":"https://github.com/facebook/AsyncDisplayKit/pull/1048","diff_url":"https://github.com/facebook/AsyncDisplayKit/pull/1048.diff","patch_url":"https://github.com/facebook/AsyncDisplayKit/pull/1048.patch","issue_url":"https://api.github.com/repos/facebook/AsyncDisplayKit/issues/1048","number":1048,"state":"open","locked":false,"title":"[ASDisplayNode] Allow UIKit / CA Bridged Properties to Always Be Writeable On Background Threads II","user":{"login":"Adlai-Holler","id":2466893,"avatar_url":"https://avatars.githubusercontent.com/u/2466893?v=3","gravatar_id":"","url":"https://api.github.com/users/Adlai-Holler","html_url":"https://github.com/Adlai-Holler","followers_url":"https://api.github.com/users/Adlai-Holler/followers","following_url":"https://api.github.com/users/Adlai-Holler/following{/other_user}","gists_url":"https://api.github.com/users/Adlai-Holler/gists{/gist_id}","starred_url":"https://api.github.com/users/Adlai-Holler/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/Adlai-Holler/subscriptions","organizations_url":"https://api.github.com/users/Adlai-Holler/orgs","repos_url":"https://api.github.com/users/Adlai-Holler/repos","events_url":"https://api.github.com/users/Adlai-Holler/events{/privacy}","received_events_url":"https://api.github.com/users/Adlai-Holler/received_events","type":"User","site_admin":false},"body":"Yo @appleguy here's something pretty neat. Let me know what you think!\r\n\r\n- Optimize `_ASPendingState` by removing unneeded sets, and by loading all flags at once before doing batch comparisons.\r\n- Add `ASWeakSet` that is built on top of `NSMapTable`. This does not support ordering, so if it's valuable to be able to flush nodes in the order that they were dirtied, we can change the approach to be built on top of `NSMutableOrderedSet` but it'll be a little less pleasant.\r\n- Add `ASPendingStateController` singleton that keeps a weak set of dirty nodes and flushes them at the end of each turn of the run loop. You can also flush it manually at any time from the main queue (obviously if you want to flush from the background, just use `dispatch`.) It does not yet support dividing the work up into chunks. It'd be easy enough to only handle max N nodes per pass but I wanted to wait for your comments before diving into that optimization.\r\n- Modify `ASDisplayNode` to always have a pending state. It no longer discards the pending state after node is loaded; instead it just clears the pending state's flags.\r\n- Modify display node's `UIViewBridging` category to:\r\n  - Always read properties from `ASPendingState`.\r\n  - Always write properties to `ASPendingState`.\r\n  - If properties are modified on main and node is loaded, properties are immediately propagated to view/layer.\r\n  - If node is not dirty, but is loaded, and a bridged property is set off-main, we register the node with the pending state controller. Note that we register it before updating the pending state, but this is safe because we hold the node's property lock during this time and pending states can't be applied until lock is relinquished. \r\n\r\nI'm using `performSelectorOnMainThread:withObject:waitUntilDone:inModes` to schedule the run loop flush event, and it works well even when the change originated from an async dispatch onto a global concurrent queue. The docs are a little confusing here though – it mentions at one point that it dispatches onto \"the run loop of the main thread\" and then later that it registers with \"the runloop of its current context\". I can definitely swap it out with an `NSTimer` of duration 0 to act as the run loop observer but it's a bit less direct. Thoughts?\r\n\r\nTODO:\r\n- [x] Add more tests for the pending state controller.\r\n- [x] Talk about back-propagation i.e. if user sets property on view/layer, what do we do? Only API-safe way to know about it is to swizzle UIView/CALayer properties which is a non-starter.\r\n- [x] Talk about run loop amortization as an optimization.\r\n- [x] Figure out why bridging `backgroundColor` won't work. I've tried migrating ASPendingState over to `UIColor *backgroundColor` but no dice. It's probably something stupid.","created_at":"2016-01-08T07:53:22Z","updated_at":"2016-02-21T05:59:11Z","closed_at":null,"merged_at":null,"merge_commit_sha":"f05d42ebb978814af116b26e015889dc848ffe55","assignee":null,"milestone":{"url":"https://api.github.com/repos/facebook/AsyncDisplayKit/milestones/7","html_url":"https://github.com/facebook/AsyncDisplayKit/milestones/2.0","labels_url":"https://api.github.com/repos/facebook/AsyncDisplayKit/milestones/7/labels","id":1164099,"number":7,"title":"2.0","description":null,"creator":{"login":"appleguy","id":565251,"avatar_url":"https://avatars.githubusercontent.com/u/565251?v=3","gravatar_id":"","url":"https://api.github.com/users/appleguy","html_url":"https://github.com/appleguy","followers_url":"https://api.github.com/users/appleguy/followers","following_url":"https://api.github.com/users/appleguy/following{/other_user}","gists_url":"https://api.github.com/users/appleguy/gists{/gist_id}","starred_url":"https://api.github.com/users/appleguy/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/appleguy/subscriptions","organizations_url":"https://api.github.com/users/appleguy/orgs","repos_url":"https://api.github.com/users/appleguy/repos","events_url":"https://api.github.com/users/appleguy/events{/privacy}","received_events_url":"https://api.github.com/users/appleguy/received_events","type":"User","site_admin":false},"open_issues":46,"closed_issues":7,"state":"open","created_at":"2015-06-15T00:41:47Z","updated_at":"2016-02-11T08:25:00Z","due_on":null,"closed_at":null},"commits_url":"https://api.github.com/repos/facebook/AsyncDisplayKit/pulls/1048/commits","review_comments_url":"https://api.github.com/repos/facebook/AsyncDisplayKit/pulls/1048/comments","review_comment_url":"https://api.github.com/repos/facebook/AsyncDisplayKit/pulls/comments{/number}","comments_url":"https://api.github.com/repos/facebook/AsyncDisplayKit/issues/1048/comments","statuses_url":"https://api.github.com/repos/facebook/AsyncDisplayKit/statuses/e244b10e1e530e2e907af507c1e8254b069b22e1","head":{"label":"Adlai-Holler:ThreadSafeViewLayerProperties","ref":"ThreadSafeViewLayerProperties","sha":"e244b10e1e530e2e907af507c1e8254b069b22e1","user":{"login":"Adlai-Holler","id":2466893,"avatar_url":"https://avatars.githubusercontent.com/u/2466893?v=3","gravatar_id":"","url":"https://api.github.com/users/Adlai-Holler","html_url":"https://github.com/Adlai-Holler","followers_url":"https://api.github.com/users/Adlai-Holler/followers","following_url":"https://api.github.com/users/Adlai-Holler/following{/other_user}","gists_url":"https://api.github.com/users/Adlai-Holler/gists{/gist_id}","starred_url":"https://api.github.com/users/Adlai-Holler/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/Adlai-Holler/subscriptions","organizations_url":"https://api.github.com/users/Adlai-Holler/orgs","repos_url":"https://api.github.com/users/Adlai-Holler/repos","events_url":"https://api.github.com/users/Adlai-Holler/events{/privacy}","received_events_url":"https://api.github.com/users/Adlai-Holler/received_events","type":"User","site_admin":false},"repo":{"id":42613564,"name":"AsyncDisplayKit","full_name":"Adlai-Holler/AsyncDisplayKit","owner":{"login":"Adlai-Holler","id":2466893,"avatar_url":"https://avatars.githubusercontent.com/u/2466893?v=3","gravatar_id":"","url":"https://api.github.com/users/Adlai-Holler","html_url":"https://github.com/Adlai-Holler","followers_url":"https://api.github.com/users/Adlai-Holler/followers","following_url":"https://api.github.com/users/Adlai-Holler/following{/other_user}","gists_url":"https://api.github.com/users/Adlai-Holler/gists{/gist_id}","starred_url":"https://api.github.com/users/Adlai-Holler/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/Adlai-Holler/subscriptions","organizations_url":"https://api.github.com/users/Adlai-Holler/orgs","repos_url":"https://api.github.com/users/Adlai-Holler/repos","events_url":"https://api.github.com/users/Adlai-Holler/events{/privacy}","received_events_url":"https://api.github.com/users/Adlai-Holler/received_events","type":"User","site_admin":false},"private":false,"html_url":"https://github.com/Adlai-Holler/AsyncDisplayKit","description":"Smooth asynchronous user interfaces for iOS apps.","fork":true,"url":"https://api.github.com/repos/Adlai-Holler/AsyncDisplayKit","forks_url":"https://api.github.com/repos/Adlai-Holler/AsyncDisplayKit/forks","keys_url":"https://api.github.com/repos/Adlai-Holler/AsyncDisplayKit/keys{/key_id}","collaborators_url":"https://api.github.com/repos/Adlai-Holler/AsyncDisplayKit/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/Adlai-Holler/AsyncDisplayKit/teams","hooks_url":"https://api.github.com/repos/Adlai-Holler/AsyncDisplayKit/hooks","issue_events_url":"https://api.github.com/repos/Adlai-Holler/AsyncDisplayKit/issues/events{/number}","events_url":"https://api.github.com/repos/Adlai-Holler/AsyncDisplayKit/events","assignees_url":"https://api.github.com/repos/Adlai-Holler/AsyncDisplayKit/assignees{/user}","branches_url":"https://api.github.com/repos/Adlai-Holler/AsyncDisplayKit/branches{/branch}","tags_url":"https://api.github.com/repos/Adlai-Holler/AsyncDisplayKit/tags","blobs_url":"https://api.github.com/repos/Adlai-Holler/AsyncDisplayKit/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/Adlai-Holler/AsyncDisplayKit/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/Adlai-Holler/AsyncDisplayKit/git/refs{/sha}","trees_url":"https://api.github.com/repos/Adlai-Holler/AsyncDisplayKit/git/trees{/sha}","statuses_url":"https://api.github.com/repos/Adlai-Holler/AsyncDisplayKit/statuses/{sha}","languages_url":"https://api.github.com/repos/Adlai-Holler/AsyncDisplayKit/languages","stargazers_url":"https://api.github.com/repos/Adlai-Holler/AsyncDisplayKit/stargazers","contributors_url":"https://api.github.com/repos/Adlai-Holler/AsyncDisplayKit/contributors","subscribers_url":"https://api.github.com/repos/Adlai-Holler/AsyncDisplayKit/subscribers","subscription_url":"https://api.github.com/repos/Adlai-Holler/AsyncDisplayKit/subscription","commits_url":"https://api.github.com/repos/Adlai-Holler/AsyncDisplayKit/commits{/sha}","git_commits_url":"https://api.github.com/repos/Adlai-Holler/AsyncDisplayKit/git/commits{/sha}","comments_url":"https://api.github.com/repos/Adlai-Holler/AsyncDisplayKit/comments{/number}","issue_comment_url":"https://api.github.com/repos/Adlai-Holler/AsyncDisplayKit/issues/comments{/number}","contents_url":"https://api.github.com/repos/Adlai-Holler/AsyncDisplayKit/contents/{+path}","compare_url":"https://api.github.com/repos/Adlai-Holler/AsyncDisplayKit/compare/{base}...{head}","merges_url":"https://api.github.com/repos/Adlai-Holler/AsyncDisplayKit/merges","archive_url":"https://api.github.com/repos/Adlai-Holler/AsyncDisplayKit/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/Adlai-Holler/AsyncDisplayKit/downloads","issues_url":"https://api.github.com/repos/Adlai-Holler/AsyncDisplayKit/issues{/number}","pulls_url":"https://api.github.com/repos/Adlai-Holler/AsyncDisplayKit/pulls{/number}","milestones_url":"https://api.github.com/repos/Adlai-Holler/AsyncDisplayKit/milestones{/number}","notifications_url":"https://api.github.com/repos/Adlai-Holler/AsyncDisplayKit/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/Adlai-Holler/AsyncDisplayKit/labels{/name}","releases_url":"https://api.github.com/repos/Adlai-Holler/AsyncDisplayKit/releases{/id}","deployments_url":"https://api.github.com/repos/Adlai-Holler/AsyncDisplayKit/deployments","created_at":"2015-09-16T21:10:06Z","updated_at":"2015-09-16T21:10:08Z","pushed_at":"2016-02-20T23:43:47Z","git_url":"git://github.com/Adlai-Holler/AsyncDisplayKit.git","ssh_url":"git@github.com:Adlai-Holler/AsyncDisplayKit.git","clone_url":"https://github.com/Adlai-Holler/AsyncDisplayKit.git","svn_url":"https://github.com/Adlai-Holler/AsyncDisplayKit","homepage":"http://asyncdisplaykit.org","size":16281,"stargazers_count":0,"watchers_count":0,"language":"Objective-C","has_issues":false,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":0,"mirror_url":null,"open_issues_count":1,"forks":0,"open_issues":1,"watchers":0,"default_branch":"master"}},"base":{"label":"facebook:master","ref":"master","sha":"d899f12f70fe9624db126c3027c4c4cdc795e93f","user":{"login":"facebook","id":69631,"avatar_url":"https://avatars.githubusercontent.com/u/69631?v=3","gravatar_id":"","url":"https://api.github.com/users/facebook","html_url":"https://github.com/facebook","followers_url":"https://api.github.com/users/facebook/followers","following_url":"https://api.github.com/users/facebook/following{/other_user}","gists_url":"https://api.github.com/users/facebook/gists{/gist_id}","starred_url":"https://api.github.com/users/facebook/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/facebook/subscriptions","organizations_url":"https://api.github.com/users/facebook/orgs","repos_url":"https://api.github.com/users/facebook/repos","events_url":"https://api.github.com/users/facebook/events{/privacy}","received_events_url":"https://api.github.com/users/facebook/received_events","type":"Organization","site_admin":false},"repo":{"id":21265042,"name":"AsyncDisplayKit","full_name":"facebook/AsyncDisplayKit","owner":{"login":"facebook","id":69631,"avatar_url":"https://avatars.githubusercontent.com/u/69631?v=3","gravatar_id":"","url":"https://api.github.com/users/facebook","html_url":"https://github.com/facebook","followers_url":"https://api.github.com/users/facebook/followers","following_url":"https://api.github.com/users/facebook/following{/other_user}","gists_url":"https://api.github.com/users/facebook/gists{/gist_id}","starred_url":"https://api.github.com/users/facebook/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/facebook/subscriptions","organizations_url":"https://api.github.com/users/facebook/orgs","repos_url":"https://api.github.com/users/facebook/repos","events_url":"https://api.github.com/users/facebook/events{/privacy}","received_events_url":"https://api.github.com/users/facebook/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/facebook/AsyncDisplayKit","description":"Smooth asynchronous user interfaces for iOS apps.","fork":false,"url":"https://api.github.com/repos/facebook/AsyncDisplayKit","forks_url":"https://api.github.com/repos/facebook/AsyncDisplayKit/forks","keys_url":"https://api.github.com/repos/facebook/AsyncDisplayKit/keys{/key_id}","collaborators_url":"https://api.github.com/repos/facebook/AsyncDisplayKit/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/facebook/AsyncDisplayKit/teams","hooks_url":"https://api.github.com/repos/facebook/AsyncDisplayKit/hooks","issue_events_url":"https://api.github.com/repos/facebook/AsyncDisplayKit/issues/events{/number}","events_url":"https://api.github.com/repos/facebook/AsyncDisplayKit/events","assignees_url":"https://api.github.com/repos/facebook/AsyncDisplayKit/assignees{/user}","branches_url":"https://api.github.com/repos/facebook/AsyncDisplayKit/branches{/branch}","tags_url":"https://api.github.com/repos/facebook/AsyncDisplayKit/tags","blobs_url":"https://api.github.com/repos/facebook/AsyncDisplayKit/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/facebook/AsyncDisplayKit/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/facebook/AsyncDisplayKit/git/refs{/sha}","trees_url":"https://api.github.com/repos/facebook/AsyncDisplayKit/git/trees{/sha}","statuses_url":"https://api.github.com/repos/facebook/AsyncDisplayKit/statuses/{sha}","languages_url":"https://api.github.com/repos/facebook/AsyncDisplayKit/languages","stargazers_url":"https://api.github.com/repos/facebook/AsyncDisplayKit/stargazers","contributors_url":"https://api.github.com/repos/facebook/AsyncDisplayKit/contributors","subscribers_url":"https://api.github.com/repos/facebook/AsyncDisplayKit/subscribers","subscription_url":"https://api.github.com/repos/facebook/AsyncDisplayKit/subscription","commits_url":"https://api.github.com/repos/facebook/AsyncDisplayKit/commits{/sha}","git_commits_url":"https://api.github.com/repos/facebook/AsyncDisplayKit/git/commits{/sha}","comments_url":"https://api.github.com/repos/facebook/AsyncDisplayKit/comments{/number}","issue_comment_url":"https://api.github.com/repos/facebook/AsyncDisplayKit/issues/comments{/number}","contents_url":"https://api.github.com/repos/facebook/AsyncDisplayKit/contents/{+path}","compare_url":"https://api.github.com/repos/facebook/AsyncDisplayKit/compare/{base}...{head}","merges_url":"https://api.github.com/repos/facebook/AsyncDisplayKit/merges","archive_url":"https://api.github.com/repos/facebook/AsyncDisplayKit/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/facebook/AsyncDisplayKit/downloads","issues_url":"https://api.github.com/repos/facebook/AsyncDisplayKit/issues{/number}","pulls_url":"https://api.github.com/repos/facebook/AsyncDisplayKit/pulls{/number}","milestones_url":"https://api.github.com/repos/facebook/AsyncDisplayKit/milestones{/number}","notifications_url":"https://api.github.com/repos/facebook/AsyncDisplayKit/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/facebook/AsyncDisplayKit/labels{/name}","releases_url":"https://api.github.com/repos/facebook/AsyncDisplayKit/releases{/id}","deployments_url":"https://api.github.com/repos/facebook/AsyncDisplayKit/deployments","created_at":"2014-06-27T05:29:02Z","updated_at":"2016-02-21T05:51:32Z","pushed_at":"2016-02-21T01:51:05Z","git_url":"git://github.com/facebook/AsyncDisplayKit.git","ssh_url":"git@github.com:facebook/AsyncDisplayKit.git","clone_url":"https://github.com/facebook/AsyncDisplayKit.git","svn_url":"https://github.com/facebook/AsyncDisplayKit","homepage":"http://asyncdisplaykit.org","size":18031,"stargazers_count":7313,"watchers_count":7313,"language":"Objective-C","has_issues":true,"has_downloads":true,"has_wiki":true,"has_pages":true,"forks_count":1127,"mirror_url":null,"open_issues_count":182,"forks":1127,"open_issues":182,"watchers":7313,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/facebook/AsyncDisplayKit/pulls/1048"},"html":{"href":"https://github.com/facebook/AsyncDisplayKit/pull/1048"},"issue":{"href":"https://api.github.com/repos/facebook/AsyncDisplayKit/issues/1048"},"comments":{"href":"https://api.github.com/repos/facebook/AsyncDisplayKit/issues/1048/comments"},"review_comments":{"href":"https://api.github.com/repos/facebook/AsyncDisplayKit/pulls/1048/comments"},"review_comment":{"href":"https://api.github.com/repos/facebook/AsyncDisplayKit/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/facebook/AsyncDisplayKit/pulls/1048/commits"},"statuses":{"href":"https://api.github.com/repos/facebook/AsyncDisplayKit/statuses/e244b10e1e530e2e907af507c1e8254b069b22e1"}}}}