FBKVOController.m 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624
  1. /**
  2. Copyright (c) 2014-present, Facebook, Inc.
  3. All rights reserved.
  4. This source code is licensed under the BSD-style license found in the
  5. LICENSE file in the root directory of this source tree. An additional grant
  6. of patent rights can be found in the PATENTS file in the same directory.
  7. */
  8. #import "FBKVOController.h"
  9. #import <libkern/OSAtomic.h>
  10. #import <objc/message.h>
  11. #if !__has_feature(objc_arc)
  12. #error This file must be compiled with ARC. Convert your project to ARC or specify the -fobjc-arc flag.
  13. #endif
  14. #pragma mark Utilities -
  15. static NSString *describe_option(NSKeyValueObservingOptions option)
  16. {
  17. switch (option) {
  18. case NSKeyValueObservingOptionNew:
  19. return @"NSKeyValueObservingOptionNew";
  20. break;
  21. case NSKeyValueObservingOptionOld:
  22. return @"NSKeyValueObservingOptionOld";
  23. break;
  24. case NSKeyValueObservingOptionInitial:
  25. return @"NSKeyValueObservingOptionInitial";
  26. break;
  27. case NSKeyValueObservingOptionPrior:
  28. return @"NSKeyValueObservingOptionPrior";
  29. break;
  30. default:
  31. NSCAssert(NO, @"unexpected option %tu", option);
  32. break;
  33. }
  34. return nil;
  35. }
  36. static void append_option_description(NSMutableString *s, NSUInteger option)
  37. {
  38. if (0 == s.length) {
  39. [s appendString:describe_option(option)];
  40. } else {
  41. [s appendString:@"|"];
  42. [s appendString:describe_option(option)];
  43. }
  44. }
  45. static NSUInteger enumerate_flags(NSUInteger *ptrFlags)
  46. {
  47. NSCAssert(ptrFlags, @"expected ptrFlags");
  48. if (!ptrFlags) {
  49. return 0;
  50. }
  51. NSUInteger flags = *ptrFlags;
  52. if (!flags) {
  53. return 0;
  54. }
  55. NSUInteger flag = 1 << __builtin_ctzl(flags);
  56. flags &= ~flag;
  57. *ptrFlags = flags;
  58. return flag;
  59. }
  60. static NSString *describe_options(NSKeyValueObservingOptions options)
  61. {
  62. NSMutableString *s = [NSMutableString string];
  63. NSUInteger option;
  64. while (0 != (option = enumerate_flags(&options))) {
  65. append_option_description(s, option);
  66. }
  67. return s;
  68. }
  69. #pragma mark _FBKVOInfo -
  70. /**
  71. @abstract The key-value observation info.
  72. @discussion Object equality is only used within the scope of a controller instance. Safely omit controller from equality definition.
  73. */
  74. @interface _FBKVOInfo : NSObject
  75. @end
  76. @implementation _FBKVOInfo
  77. {
  78. @public
  79. __weak FBKVOController *_controller;
  80. NSString *_keyPath;
  81. NSKeyValueObservingOptions _options;
  82. SEL _action;
  83. void *_context;
  84. FBKVONotificationBlock _block;
  85. }
  86. - (instancetype)initWithController:(FBKVOController *)controller keyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options block:(FBKVONotificationBlock)block action:(SEL)action context:(void *)context
  87. {
  88. self = [super init];
  89. if (nil != self) {
  90. _controller = controller;
  91. _block = [block copy];
  92. _keyPath = [keyPath copy];
  93. _options = options;
  94. _action = action;
  95. _context = context;
  96. }
  97. return self;
  98. }
  99. - (instancetype)initWithController:(FBKVOController *)controller keyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options block:(FBKVONotificationBlock)block
  100. {
  101. return [self initWithController:controller keyPath:keyPath options:options block:block action:NULL context:NULL];
  102. }
  103. - (instancetype)initWithController:(FBKVOController *)controller keyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options action:(SEL)action
  104. {
  105. return [self initWithController:controller keyPath:keyPath options:options block:NULL action:action context:NULL];
  106. }
  107. - (instancetype)initWithController:(FBKVOController *)controller keyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(void *)context
  108. {
  109. return [self initWithController:controller keyPath:keyPath options:options block:NULL action:NULL context:context];
  110. }
  111. - (instancetype)initWithController:(FBKVOController *)controller keyPath:(NSString *)keyPath
  112. {
  113. return [self initWithController:controller keyPath:keyPath options:0 block:NULL action:NULL context:NULL];
  114. }
  115. - (NSUInteger)hash
  116. {
  117. return [_keyPath hash];
  118. }
  119. - (BOOL)isEqual:(id)object
  120. {
  121. if (nil == object) {
  122. return NO;
  123. }
  124. if (self == object) {
  125. return YES;
  126. }
  127. if (![object isKindOfClass:[self class]]) {
  128. return NO;
  129. }
  130. return [_keyPath isEqualToString:((_FBKVOInfo *)object)->_keyPath];
  131. }
  132. - (NSString *)debugDescription
  133. {
  134. NSMutableString *s = [NSMutableString stringWithFormat:@"<%@:%p keyPath:%@", NSStringFromClass([self class]), self, _keyPath];
  135. if (0 != _options) {
  136. [s appendFormat:@" options:%@", describe_options(_options)];
  137. }
  138. if (NULL != _action) {
  139. [s appendFormat:@" action:%@", NSStringFromSelector(_action)];
  140. }
  141. if (NULL != _context) {
  142. [s appendFormat:@" context:%p", _context];
  143. }
  144. if (NULL != _block) {
  145. [s appendFormat:@" block:%p", _block];
  146. }
  147. [s appendString:@">"];
  148. return s;
  149. }
  150. @end
  151. #pragma mark _FBKVOSharedController -
  152. /**
  153. @abstract The shared KVO controller instance.
  154. @discussion Acts as a receptionist, receiving and forwarding KVO notifications.
  155. */
  156. @interface _FBKVOSharedController : NSObject
  157. /** A shared instance that never deallocates. */
  158. + (instancetype)sharedController;
  159. /** observe an object, info pair */
  160. - (void)observe:(id)object info:(_FBKVOInfo *)info;
  161. /** unobserve an object, info pair */
  162. - (void)unobserve:(id)object info:(_FBKVOInfo *)info;
  163. /** unobserve an object with a set of infos */
  164. - (void)unobserve:(id)object infos:(NSSet *)infos;
  165. @end
  166. @implementation _FBKVOSharedController
  167. {
  168. NSHashTable *_infos;
  169. OSSpinLock _lock;
  170. }
  171. + (instancetype)sharedController
  172. {
  173. static _FBKVOSharedController *_controller = nil;
  174. static dispatch_once_t onceToken;
  175. dispatch_once(&onceToken, ^{
  176. _controller = [[_FBKVOSharedController alloc] init];
  177. });
  178. return _controller;
  179. }
  180. - (instancetype)init
  181. {
  182. self = [super init];
  183. if (nil != self) {
  184. NSHashTable *infos = [NSHashTable alloc];
  185. #ifdef __IPHONE_OS_VERSION_MIN_REQUIRED
  186. _infos = [infos initWithOptions:NSPointerFunctionsWeakMemory|NSPointerFunctionsObjectPointerPersonality capacity:0];
  187. #elif defined(__MAC_OS_X_VERSION_MIN_REQUIRED)
  188. if ([NSHashTable respondsToSelector:@selector(weakObjectsHashTable)]) {
  189. _infos = [infos initWithOptions:NSPointerFunctionsWeakMemory|NSPointerFunctionsObjectPointerPersonality capacity:0];
  190. } else {
  191. // silence deprecated warnings
  192. #pragma clang diagnostic push
  193. #pragma clang diagnostic ignored "-Wdeprecated-declarations"
  194. _infos = [infos initWithOptions:NSPointerFunctionsZeroingWeakMemory|NSPointerFunctionsObjectPointerPersonality capacity:0];
  195. #pragma clang diagnostic pop
  196. }
  197. #endif
  198. _lock = OS_SPINLOCK_INIT;
  199. }
  200. return self;
  201. }
  202. - (NSString *)debugDescription
  203. {
  204. NSMutableString *s = [NSMutableString stringWithFormat:@"<%@:%p", NSStringFromClass([self class]), self];
  205. // lock
  206. OSSpinLockLock(&_lock);
  207. NSMutableArray *infoDescriptions = [NSMutableArray arrayWithCapacity:_infos.count];
  208. for (_FBKVOInfo *info in _infos) {
  209. [infoDescriptions addObject:info.debugDescription];
  210. }
  211. [s appendFormat:@" contexts:%@", infoDescriptions];
  212. // unlock
  213. OSSpinLockUnlock(&_lock);
  214. [s appendString:@">"];
  215. return s;
  216. }
  217. - (void)observe:(id)object info:(_FBKVOInfo *)info
  218. {
  219. if (nil == info) {
  220. return;
  221. }
  222. // register info
  223. OSSpinLockLock(&_lock);
  224. [_infos addObject:info];
  225. OSSpinLockUnlock(&_lock);
  226. // add observer
  227. [object addObserver:self forKeyPath:info->_keyPath options:info->_options context:(void *)info];
  228. }
  229. - (void)unobserve:(id)object info:(_FBKVOInfo *)info
  230. {
  231. if (nil == info) {
  232. return;
  233. }
  234. // unregister info
  235. OSSpinLockLock(&_lock);
  236. [_infos removeObject:info];
  237. OSSpinLockUnlock(&_lock);
  238. // remove observer
  239. [object removeObserver:self forKeyPath:info->_keyPath context:(void *)info];
  240. }
  241. - (void)unobserve:(id)object infos:(NSSet *)infos
  242. {
  243. if (0 == infos.count) {
  244. return;
  245. }
  246. // unregister info
  247. OSSpinLockLock(&_lock);
  248. for (_FBKVOInfo *info in infos) {
  249. [_infos removeObject:info];
  250. }
  251. OSSpinLockUnlock(&_lock);
  252. // remove observer
  253. for (_FBKVOInfo *info in infos) {
  254. [object removeObserver:self forKeyPath:info->_keyPath context:(void *)info];
  255. }
  256. }
  257. - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
  258. {
  259. NSAssert(context, @"missing context keyPath:%@ object:%@ change:%@", keyPath, object, change);
  260. _FBKVOInfo *info;
  261. {
  262. // lookup context in registered infos, taking out a strong reference only if it exists
  263. OSSpinLockLock(&_lock);
  264. info = [_infos member:(__bridge id)context];
  265. OSSpinLockUnlock(&_lock);
  266. }
  267. if (nil != info) {
  268. // take strong reference to controller
  269. FBKVOController *controller = info->_controller;
  270. if (nil != controller) {
  271. // take strong reference to observer
  272. id observer = controller.observer;
  273. if (nil != observer) {
  274. // dispatch custom block or action, fall back to default action
  275. if (info->_block) {
  276. info->_block(observer, object, change);
  277. } else if (info->_action) {
  278. #pragma clang diagnostic push
  279. #pragma clang diagnostic ignored "-Warc-performSelector-leaks"
  280. [observer performSelector:info->_action withObject:change withObject:object];
  281. #pragma clang diagnostic pop
  282. } else {
  283. [observer observeValueForKeyPath:keyPath ofObject:object change:change context:info->_context];
  284. }
  285. }
  286. }
  287. }
  288. }
  289. @end
  290. #pragma mark FBKVOController -
  291. @implementation FBKVOController
  292. {
  293. NSMapTable *_objectInfosMap;
  294. OSSpinLock _lock;
  295. }
  296. #pragma mark Lifecycle -
  297. + (instancetype)controllerWithObserver:(id)observer
  298. {
  299. return [[self alloc] initWithObserver:observer];
  300. }
  301. - (instancetype)initWithObserver:(id)observer retainObserved:(BOOL)retainObserved
  302. {
  303. self = [super init];
  304. if (nil != self) {
  305. _observer = observer;
  306. NSPointerFunctionsOptions keyOptions = retainObserved ? NSPointerFunctionsStrongMemory|NSPointerFunctionsObjectPointerPersonality : NSPointerFunctionsWeakMemory|NSPointerFunctionsObjectPointerPersonality;
  307. _objectInfosMap = [[NSMapTable alloc] initWithKeyOptions:keyOptions valueOptions:NSPointerFunctionsStrongMemory|NSPointerFunctionsObjectPersonality capacity:0];
  308. _lock = OS_SPINLOCK_INIT;
  309. }
  310. return self;
  311. }
  312. - (instancetype)initWithObserver:(id)observer
  313. {
  314. return [self initWithObserver:observer retainObserved:YES];
  315. }
  316. - (void)dealloc
  317. {
  318. [self unobserveAll];
  319. }
  320. #pragma mark Properties -
  321. - (NSString *)debugDescription
  322. {
  323. NSMutableString *s = [NSMutableString stringWithFormat:@"<%@:%p", NSStringFromClass([self class]), self];
  324. [s appendFormat:@" observer:<%@:%p>", NSStringFromClass([_observer class]), _observer];
  325. // lock
  326. OSSpinLockLock(&_lock);
  327. if (0 != _objectInfosMap.count) {
  328. [s appendString:@"\n "];
  329. }
  330. for (id object in _objectInfosMap) {
  331. NSMutableSet *infos = [_objectInfosMap objectForKey:object];
  332. NSMutableArray *infoDescriptions = [NSMutableArray arrayWithCapacity:infos.count];
  333. [infos enumerateObjectsUsingBlock:^(_FBKVOInfo *info, BOOL *stop) {
  334. [infoDescriptions addObject:info.debugDescription];
  335. }];
  336. [s appendFormat:@"%@ -> %@", object, infoDescriptions];
  337. }
  338. // unlock
  339. OSSpinLockUnlock(&_lock);
  340. [s appendString:@">"];
  341. return s;
  342. }
  343. #pragma mark Utilities -
  344. - (void)_observe:(id)object info:(_FBKVOInfo *)info
  345. {
  346. // lock
  347. OSSpinLockLock(&_lock);
  348. NSMutableSet *infos = [_objectInfosMap objectForKey:object];
  349. // check for info existence
  350. _FBKVOInfo *existingInfo = [infos member:info];
  351. if (nil != existingInfo) {
  352. NSLog(@"observation info already exists %@", existingInfo);
  353. // unlock and return
  354. OSSpinLockUnlock(&_lock);
  355. return;
  356. }
  357. // lazilly create set of infos
  358. if (nil == infos) {
  359. infos = [NSMutableSet set];
  360. [_objectInfosMap setObject:infos forKey:object];
  361. }
  362. // add info and oberve
  363. [infos addObject:info];
  364. // unlock prior to callout
  365. OSSpinLockUnlock(&_lock);
  366. [[_FBKVOSharedController sharedController] observe:object info:info];
  367. }
  368. - (void)_unobserve:(id)object info:(_FBKVOInfo *)info
  369. {
  370. // lock
  371. OSSpinLockLock(&_lock);
  372. // get observation infos
  373. NSMutableSet *infos = [_objectInfosMap objectForKey:object];
  374. // lookup registered info instance
  375. _FBKVOInfo *registeredInfo = [infos member:info];
  376. if (nil != registeredInfo) {
  377. [infos removeObject:registeredInfo];
  378. // remove no longer used infos
  379. if (0 == infos.count) {
  380. [_objectInfosMap removeObjectForKey:object];
  381. }
  382. }
  383. // unlock
  384. OSSpinLockUnlock(&_lock);
  385. // unobserve
  386. [[_FBKVOSharedController sharedController] unobserve:object info:registeredInfo];
  387. }
  388. - (void)_unobserve:(id)object
  389. {
  390. // lock
  391. OSSpinLockLock(&_lock);
  392. NSMutableSet *infos = [_objectInfosMap objectForKey:object];
  393. // remove infos
  394. [_objectInfosMap removeObjectForKey:object];
  395. // unlock
  396. OSSpinLockUnlock(&_lock);
  397. // unobserve
  398. [[_FBKVOSharedController sharedController] unobserve:object infos:infos];
  399. }
  400. - (void)_unobserveAll
  401. {
  402. // lock
  403. OSSpinLockLock(&_lock);
  404. NSMapTable *objectInfoMaps = [_objectInfosMap copy];
  405. // clear table and map
  406. [_objectInfosMap removeAllObjects];
  407. // unlock
  408. OSSpinLockUnlock(&_lock);
  409. _FBKVOSharedController *shareController = [_FBKVOSharedController sharedController];
  410. for (id object in objectInfoMaps) {
  411. // unobserve each registered object and infos
  412. NSSet *infos = [objectInfoMaps objectForKey:object];
  413. [shareController unobserve:object infos:infos];
  414. }
  415. }
  416. #pragma mark API -
  417. - (void)observe:(id)object keyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options block:(FBKVONotificationBlock)block
  418. {
  419. NSAssert(0 != keyPath.length && NULL != block, @"missing required parameters observe:%@ keyPath:%@ block:%p", object, keyPath, block);
  420. if (nil == object || 0 == keyPath.length || NULL == block) {
  421. return;
  422. }
  423. // create info
  424. _FBKVOInfo *info = [[_FBKVOInfo alloc] initWithController:self keyPath:keyPath options:options block:block];
  425. // observe object with info
  426. [self _observe:object info:info];
  427. }
  428. - (void)observe:(id)object keyPaths:(NSArray *)keyPaths options:(NSKeyValueObservingOptions)options block:(FBKVONotificationBlock)block
  429. {
  430. NSAssert(0 != keyPaths.count && NULL != block, @"missing required parameters observe:%@ keyPath:%@ block:%p", object, keyPaths, block);
  431. if (nil == object || 0 == keyPaths.count || NULL == block) {
  432. return;
  433. }
  434. for (NSString *keyPath in keyPaths)
  435. {
  436. [self observe:object keyPath:keyPath options:options block:block];
  437. }
  438. }
  439. - (void)observe:(id)object keyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options action:(SEL)action
  440. {
  441. NSAssert(0 != keyPath.length && NULL != action, @"missing required parameters observe:%@ keyPath:%@ action:%@", object, keyPath, NSStringFromSelector(action));
  442. NSAssert([_observer respondsToSelector:action], @"%@ does not respond to %@", _observer, NSStringFromSelector(action));
  443. if (nil == object || 0 == keyPath.length || NULL == action) {
  444. return;
  445. }
  446. // create info
  447. _FBKVOInfo *info = [[_FBKVOInfo alloc] initWithController:self keyPath:keyPath options:options action:action];
  448. // observe object with info
  449. [self _observe:object info:info];
  450. }
  451. - (void)observe:(id)object keyPaths:(NSArray *)keyPaths options:(NSKeyValueObservingOptions)options action:(SEL)action
  452. {
  453. NSAssert(0 != keyPaths.count && NULL != action, @"missing required parameters observe:%@ keyPath:%@ action:%@", object, keyPaths, NSStringFromSelector(action));
  454. NSAssert([_observer respondsToSelector:action], @"%@ does not respond to %@", _observer, NSStringFromSelector(action));
  455. if (nil == object || 0 == keyPaths.count || NULL == action) {
  456. return;
  457. }
  458. for (NSString *keyPath in keyPaths)
  459. {
  460. [self observe:object keyPath:keyPath options:options action:action];
  461. }
  462. }
  463. - (void)observe:(id)object keyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(void *)context
  464. {
  465. NSAssert(0 != keyPath.length, @"missing required parameters observe:%@ keyPath:%@", object, keyPath);
  466. if (nil == object || 0 == keyPath.length) {
  467. return;
  468. }
  469. // create info
  470. _FBKVOInfo *info = [[_FBKVOInfo alloc] initWithController:self keyPath:keyPath options:options context:context];
  471. // observe object with info
  472. [self _observe:object info:info];
  473. }
  474. - (void)observe:(id)object keyPaths:(NSArray *)keyPaths options:(NSKeyValueObservingOptions)options context:(void *)context
  475. {
  476. NSAssert(0 != keyPaths.count, @"missing required parameters observe:%@ keyPath:%@", object, keyPaths);
  477. if (nil == object || 0 == keyPaths.count) {
  478. return;
  479. }
  480. for (NSString *keyPath in keyPaths)
  481. {
  482. [self observe:object keyPath:keyPath options:options context:context];
  483. }
  484. }
  485. - (void)unobserve:(id)object keyPath:(NSString *)keyPath
  486. {
  487. // create representative info
  488. _FBKVOInfo *info = [[_FBKVOInfo alloc] initWithController:self keyPath:keyPath];
  489. // unobserve object property
  490. [self _unobserve:object info:info];
  491. }
  492. - (void)unobserve:(id)object
  493. {
  494. if (nil == object) {
  495. return;
  496. }
  497. [self _unobserve:object];
  498. }
  499. - (void)unobserveAll
  500. {
  501. [self _unobserveAll];
  502. }
  503. @end