From f3bc0241caef3588aebd1abcf98578e09a7d0db5 Mon Sep 17 00:00:00 2001 From: Anton Bukov Date: Thu, 6 Jul 2017 14:09:47 +0300 Subject: [PATCH 1/2] Add KVOController for self observing: KVOControllerForSelf --- FBKVOController/FBKVOController.h | 8 +++++++- FBKVOController/FBKVOController.m | 17 ++++++++++++++--- FBKVOController/NSObject+FBKVOController.h | 9 +++++++++ FBKVOController/NSObject+FBKVOController.m | 20 +++++++++++++++++++- 4 files changed, 49 insertions(+), 5 deletions(-) diff --git a/FBKVOController/FBKVOController.h b/FBKVOController/FBKVOController.h index 64a030d..7a98a80 100644 --- a/FBKVOController/FBKVOController.h +++ b/FBKVOController/FBKVOController.h @@ -52,6 +52,12 @@ extern NSString *const FBKVONotificationKeyPathKey; */ typedef void (^FBKVONotificationBlock)(id _Nullable observer, id object, NSDictionary *change); +typedef NS_ENUM(NSUInteger, FBKVOControllerObjectStoreType) { + FBKVOControllerObjectStoreTypeStrong, + FBKVOControllerObjectStoreTypeWeak, + FBKVOControllerObjectStoreTypeAssign, +}; + /** @abstract FBKVOController makes Key-Value Observing simpler and safer. @discussion FBKVOController adds support for handling key-value changes with blocks and custom actions, as well as the NSKeyValueObserving callback. Notification will never message a deallocated observer. Observer removal never throws exceptions, and observers are removed implicitly on controller deallocation. FBKVOController is also thread safe. When used in a concurrent environment, it protects observers from possible resurrection and avoids ensuing crash. By default, the controller maintains a strong reference to objects observed. @@ -76,7 +82,7 @@ typedef void (^FBKVONotificationBlock)(id _Nullable observer, id object, NSDicti @return The initialized KVO controller instance. @discussion Use retainObserved = NO when a strong reference between controller and observee would create a retain loop. When not retaining observees, special care must be taken to remove observation info prior to observee dealloc. */ -- (instancetype)initWithObserver:(nullable id)observer retainObserved:(BOOL)retainObserved NS_DESIGNATED_INITIALIZER; +- (instancetype)initWithObserver:(nullable id)observer storeType:(FBKVOControllerObjectStoreType)storeType NS_DESIGNATED_INITIALIZER; /** @abstract Convenience initializer. diff --git a/FBKVOController/FBKVOController.m b/FBKVOController/FBKVOController.m index e0b6b3e..c7a54ee 100644 --- a/FBKVOController/FBKVOController.m +++ b/FBKVOController/FBKVOController.m @@ -412,12 +412,23 @@ + (instancetype)controllerWithObserver:(nullable id)observer return [[self alloc] initWithObserver:observer]; } -- (instancetype)initWithObserver:(nullable id)observer retainObserved:(BOOL)retainObserved +- (instancetype)initWithObserver:(nullable id)observer storeType:(FBKVOControllerObjectStoreType)storeType { self = [super init]; if (nil != self) { _observer = observer; - NSPointerFunctionsOptions keyOptions = retainObserved ? NSPointerFunctionsStrongMemory|NSPointerFunctionsObjectPointerPersonality : NSPointerFunctionsWeakMemory|NSPointerFunctionsObjectPointerPersonality; + NSPointerFunctionsOptions keyOptions = NSPointerFunctionsObjectPointerPersonality; + switch (storeType) { + case FBKVOControllerObjectStoreTypeStrong: + keyOptions |= NSPointerFunctionsStrongMemory; + break; + case FBKVOControllerObjectStoreTypeWeak: + keyOptions |= NSPointerFunctionsWeakMemory; + break; + case FBKVOControllerObjectStoreTypeAssign: + keyOptions |= NSPointerFunctionsOpaqueMemory; + break; + } _objectInfosMap = [[NSMapTable alloc] initWithKeyOptions:keyOptions valueOptions:NSPointerFunctionsStrongMemory|NSPointerFunctionsObjectPersonality capacity:0]; pthread_mutex_init(&_lock, NULL); } @@ -426,7 +437,7 @@ - (instancetype)initWithObserver:(nullable id)observer retainObserved:(BOOL)reta - (instancetype)initWithObserver:(nullable id)observer { - return [self initWithObserver:observer retainObserved:YES]; + return [self initWithObserver:observer storeType:FBKVOControllerObjectStoreTypeStrong]; } - (void)dealloc diff --git a/FBKVOController/NSObject+FBKVOController.h b/FBKVOController/NSObject+FBKVOController.h index db38f92..6c31752 100644 --- a/FBKVOController/NSObject+FBKVOController.h +++ b/FBKVOController/NSObject+FBKVOController.h @@ -37,6 +37,15 @@ NS_ASSUME_NONNULL_BEGIN */ @property (nonatomic, strong) FBKVOController *KVOControllerNonRetaining; +/** + @abstract Lazy-loaded FBKVOController for use with self + @return FBKVOController associated with this object, creating one if necessary + @discussion This makes it convenient to simply create and forget a FBKVOController. + Use this version when a strong reference between controller and observed object would create a retain cycle. + When not retaining observed objects, special care must be taken to remove observation info prior to deallocation of the observed object. + */ +@property (nonatomic, strong) FBKVOController *KVOControllerForSelf; + @end NS_ASSUME_NONNULL_END diff --git a/FBKVOController/NSObject+FBKVOController.m b/FBKVOController/NSObject+FBKVOController.m index af60102..57d6dd0 100644 --- a/FBKVOController/NSObject+FBKVOController.m +++ b/FBKVOController/NSObject+FBKVOController.m @@ -21,6 +21,7 @@ static void *NSObjectKVOControllerKey = &NSObjectKVOControllerKey; static void *NSObjectKVOControllerNonRetainingKey = &NSObjectKVOControllerNonRetainingKey; +static void *NSObjectKVOControllerForSelfKey = &NSObjectKVOControllerForSelfKey; @implementation NSObject (FBKVOController) @@ -47,7 +48,7 @@ - (FBKVOController *)KVOControllerNonRetaining id controller = objc_getAssociatedObject(self, NSObjectKVOControllerNonRetainingKey); if (nil == controller) { - controller = [[FBKVOController alloc] initWithObserver:self retainObserved:NO]; + controller = [[FBKVOController alloc] initWithObserver:self storeType:FBKVOControllerObjectStoreTypeWeak]; self.KVOControllerNonRetaining = controller; } @@ -59,6 +60,23 @@ - (void)setKVOControllerNonRetaining:(FBKVOController *)KVOControllerNonRetainin objc_setAssociatedObject(self, NSObjectKVOControllerNonRetainingKey, KVOControllerNonRetaining, OBJC_ASSOCIATION_RETAIN_NONATOMIC); } +- (FBKVOController *)KVOControllerForSelf +{ + id controller = objc_getAssociatedObject(self, NSObjectKVOControllerForSelfKey); + + if (nil == controller) { + controller = [[FBKVOController alloc] initWithObserver:self storeType:FBKVOControllerObjectStoreTypeAssign]; + self.KVOControllerForSelf = controller; + } + + return controller; +} + +- (void)setKVOControllerForSelf:(FBKVOController *)KVOControllerForSelf +{ + objc_setAssociatedObject(self, NSObjectKVOControllerForSelfKey, KVOControllerForSelf, OBJC_ASSOCIATION_RETAIN_NONATOMIC); +} + @end From ca15f6786319d015330d1c86039cd90b39d32099 Mon Sep 17 00:00:00 2001 From: Anton Bukov Date: Thu, 10 Aug 2017 22:38:14 +0300 Subject: [PATCH 2/2] Some fixes related to PR comments --- FBKVOController/FBKVOController.h | 22 +++++++++++++++++++--- FBKVOController/FBKVOController.m | 9 +++++++++ 2 files changed, 28 insertions(+), 3 deletions(-) diff --git a/FBKVOController/FBKVOController.h b/FBKVOController/FBKVOController.h index 7a98a80..13dcf5e 100644 --- a/FBKVOController/FBKVOController.h +++ b/FBKVOController/FBKVOController.h @@ -52,10 +52,17 @@ extern NSString *const FBKVONotificationKeyPathKey; */ typedef void (^FBKVONotificationBlock)(id _Nullable observer, id object, NSDictionary *change); +/** + * @typedef FBKVOControllerObjectStoreType + * @brief The KVOController observable object association type + * @constant FBKVOControllerObjectStoreTypeStrong store observable object as strong reference + * @constant FBKVOControllerObjectStoreTypeWeak store observable object as weak reference + * @constant FBKVOControllerObjectStoreTypeAssign store observable object as opaque pointer + */ typedef NS_ENUM(NSUInteger, FBKVOControllerObjectStoreType) { - FBKVOControllerObjectStoreTypeStrong, - FBKVOControllerObjectStoreTypeWeak, - FBKVOControllerObjectStoreTypeAssign, + FBKVOControllerObjectStoreTypeStrong, + FBKVOControllerObjectStoreTypeWeak, + FBKVOControllerObjectStoreTypeAssign, }; /** @@ -82,6 +89,15 @@ typedef NS_ENUM(NSUInteger, FBKVOControllerObjectStoreType) { @return The initialized KVO controller instance. @discussion Use retainObserved = NO when a strong reference between controller and observee would create a retain loop. When not retaining observees, special care must be taken to remove observation info prior to observee dealloc. */ +- (instancetype)initWithObserver:(nullable id)observer retainObserved:(BOOL)retainObserved __attribute__((deprecated)); + +/** + @abstract The designated initializer. + @param observer The object notified on key-value change. The specified observer must support weak references. + @param storeType Enum specifying how observed object references should be stored. + @return The initialized KVO controller instance. + @discussion Use retainObserved = NO when a strong reference between controller and observee would create a retain loop. When not retaining observees, special care must be taken to remove observation info prior to observee dealloc. + */ - (instancetype)initWithObserver:(nullable id)observer storeType:(FBKVOControllerObjectStoreType)storeType NS_DESIGNATED_INITIALIZER; /** diff --git a/FBKVOController/FBKVOController.m b/FBKVOController/FBKVOController.m index c7a54ee..4349839 100644 --- a/FBKVOController/FBKVOController.m +++ b/FBKVOController/FBKVOController.m @@ -435,6 +435,15 @@ - (instancetype)initWithObserver:(nullable id)observer storeType:(FBKVOControlle return self; } +- (instancetype)initWithObserver:(nullable id)observer retainObserved:(BOOL)retainObserved +{ + if (retainObserved) { + return [self initWithObserver:observer storeType:FBKVOControllerObjectStoreTypeStrong]; + } + return [self initWithObserver:observer storeType:FBKVOControllerObjectStoreTypeWeak]; +} + + - (instancetype)initWithObserver:(nullable id)observer { return [self initWithObserver:observer storeType:FBKVOControllerObjectStoreTypeStrong];