Git Product home page Git Product logo

block-kvo's People

Contributors

hwaxxer avatar jurajhomola avatar niklasberglund avatar rmatta avatar ryanbertrand avatar stevemckenzie avatar tonyarnold avatar tricertops avatar tspacek avatar tupakapoor avatar vilinskiy-playdayteam avatar yanjunz avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

block-kvo's Issues

Not compiling with Xcode 6.3, Swift 1.2

Hi!

Thanks for a great library! I used it heavily in an earlier project!

However, in this new project I am experiencing some difficulties due to what I believe to be Swift 1.2.

I am including Block-KVO in my podfile and importing Block-KVO into my Bridging-header file:
'#import <Block-KVO/MTKObserving.h>'

But the project won't compile due to objc_msgsend has too many arguments. This is caused by ENABLE_STRICT_OBJC_MSGSEND being set to YES by default by cocoapods. I have successfully fixed that by adding the followint post_installer script setting said flag to NO.

post_install do |installer|
    installer.project.targets.each do |target|
        target.build_configurations.each do |config|
            config.build_settings['ENABLE_STRICT_OBJC_MSGSEND'] = "NO"
        end
    end
end

I am getting warning 'Block-KVO/MTKObserving.h' file not found.

Just for debugging purposes I than changed that line in the Bridging-Header to use the absolut path to said file, which takes me one step further (which is strange?!?!), and yields this error in the file MTKObserving.h:
"'keypath.h' file not found"

What to do? Any idea?

Thanks a bunch!

Initial value detection

In continuing to use Block-KVO, the issue of trying to determine if the observer block is being invoked with the initial value or an actual change keeps coming up. This is particularly problematic with property values that can be nil.

It suddenly occurred to me that there's an unambiguous fix for this. Instead of invoking the observer blocks with an old value of nil block(self,nil,initialValue) during setup, invoke it with the same value for both parameters: block(self,initialValue,initialValue).

The block can then easily, cheaply, and definitively, determine if the block is being invoked with the initial value:

[self observeProperty:@"representedObject.schedule"
			withBlock:^(typeof(self) self, id oldValue, id newValue) {
	if (oldValue==newValue) {
		// iff initial value
	} else {
		// iff value change
		}
	}
}];

The bad news is that this breaks code that is assuming oldValue will be nil on the initial call.

can you delete "block(self, nil);"

  • (void)observeNotification:(NSString *)name fromObject:(id)object withBlock:(MTKBlockNotify)block {

    // Invoke manually for the first time.
    block(self, nil);

    __weak typeof(self) weakSelf = self;

    id internalObserver = [[NSNotificationCenter defaultCenter] addObserverForName:name
    object:object
    queue:[NSOperationQueue currentQueue]
    usingBlock:^(NSNotification *notification) {
    if (notification!=nil) {
    block(weakSelf, notification);
    }
    }];

    [[self mtk_notificationBlockObservers] addObject:internalObserver];

}

we don't need Invoking manually for the first time.

Block-KVO logic should follow the same approach as original KVO

I found this issue while I was expecting KVO to be called for property change (because of pointer value change), however it wasn't.

If I'd use normal apple's KVO I'd get notification in observeValueForKeypath, but since my objects were equal Block-KVO didn't call my blocks.

Here is the code I'm refering to in MTKObserver.m:

  • (void)executeAfterSettingBlocksOld:(id)old new:(id)new {
    // Here we check for equality. Two values are equal when they have equal pointers (e.g. nils) or they respond to -isEqual: with YES.
    if (old == new || (old && [new isEqual:old])) return;

    for (MTKBlockChange block in self.afterSettingBlocks) {
    block(self.target, old, new);
    }
    }

PS. Additionaly I think that whole old == new will always be false, since original KVO will rule out this scenario.

Declaration shadows a local variable

When observing self clang warns that the declaration of weak self shadows the local variable of self.

I haven't figured out a clean way to fix this without the use of the macros.

I suggested changing MTKObservePropertySelf to

#define MTKObservePropertySelf(KEYPATH, TYPE, CODE) \
_Pragma("clang diagnostic push") \
_Pragma("clang diagnostic ignored \"-Wshadow\"") \
[self observeProperty:@(((void)(NO && ((void)self.KEYPATH, NO)), # KEYPATH)) withBlock:^(__weak typeof(self) self, TYPE old, TYPE new) CODE ]; \
_Pragma("clang diagnostic pop")

Obscure retain issue in -removeAllObservationsOfObject:

First let me say that I recently integrated Block-KVO into my project and been very happy with it. I researched a number of block-based KVO solutions, and this one was one of the few that takes retain cycles seriously. There are a number of design issues I might quibble with, but I can't fault its performance.

Now onto the problem.

I discovered a rather obscure memory management issue with -removeAllObservationsOfObject:. Here's the scenario that causes the problem:

Object D: Data model object
Object C1: A controller
Object C2: Another controller

C1 and C2 both register "foreign" observations blocks with the data model object, like this:

[self observeObject:D property:@"repositoryReference" withBlock: ... ];

I have cases in my code where the data model object might get replaced, so I have methods that encapsulate the releasing and setting of said object in the C2 view controller. When adopting Block-KVO, I added code to detach all observations, thusly:

- (void)forgetDataModel {
    [self removeAllObservationsOfObject:self.representedObject];
    self.representedObject = nil;
    ...

Here's where it goes south.

  1. During a window close, the code to forget the data model in C2 gets called.
  2. C2 sends [C2 removeAllObservationsOfObject:D].
  3. C2 then sends [D mtk_removeAllObservationsForOwner:C2]
  4. D iterates through its observer dictionary (by keyPath) and sends itself -mtk_removeObservationsForOwner:keyPath: for each key.
  5. In -mtk_removeObservationsForOwner:keyPath: D finds the observer for C2, detaches it, and removes it from the set of observers for that keyPath.
  6. D returns to the loop and iterates to the next collection of observers.
  7. In the next -mtk_removeObservationsForOwner:forKeyPath: call, a "sent -retain message to zombie object" exception gets thrown.

What happened was this: the observer block for C2 just happened to contain the last strong reference to the controller C1. When C2's observer was released, C1 got deallocated. Afterwards, the loop in -mtk_removeAllObservationsForOwner: continues, but it soon iterates through to the (now detached) observer for C1. The owner of the observer no longer exists, but ARC sends it a -retain when it calls the property getter in the if (observer.owner == owner) conditional, and then bad things happen.

It took me awhile to figure how why this is happening. It's an impedance mismatch between the way -removeAllObservationsOfObject: works and how most of the internal, swizzled-dealloc-triggered, methods work. The high-level -removeAllObservationsOfObject: detaches the observers and removes them from the collection of observers. The -dealloc-triggered methods simply -detach the observer, and expect the observer objects to get released en mass along with the object.

Pondering the problem, I think that -removeAllObservationsOfObject: (and related methods) needs to deal with the possibility of detached-and-released owners in the observer pool that are still associated with the object. I considered other solutions, but I think this is most direct fix, albeit not the cleanest.

The problem is that (apparently) all property getters under ARC, synthesized or otherwise, get a -retain & -autorelease before returning. So it's impossible for the category to get the observer's owner property without indirectly retaining it. My simple solution was to add an ownership test method that avoids the gratuitous -retain:

@implementation MTKObserver
...
- (BOOL)isOwnedBy:(__unsafe_unretained id)someObject
{
	return _owner == someObject;
}

Now it's safe to write:

- (void)mtk_removeObservationsForOwner:(id)owner keyPath:(NSString *)keyPath {
    NSMutableSet *observersForKeyPath = [self mtk_keyPathBlockObservers][keyPath];
    for (MTKObserver *observer in [observersForKeyPath copy]) {
        if ([observer isOwnedBy:owner]) {
            [observer detach];
            [observersForKeyPath removeObject:observer];
...

If you think this all makes sense, I'll implement it for real and send a pull request.

Problem observing UITextField

When observing text value in UITextField, observation block gets called when text is changed programmatically. But if text is changed by user typing in, observation block is not called.

Integration failing, can't find libBlockObserving.a

I haven't managed to integrate Blocks-KVO into my project by following the integration steps from the README. Not sure if I did step 5 correctly. Added the entire Block-KVO folder recursively, this path: $(SRCROOT)/../Block-KVO

But when compiling libBlockObserving.a can't be found. I have added the Block-KVO project under my project, added libBlockObserving.a under Build Phases and followed all the other steps.

The error I'm getting:
clang: error: no such file or directory: '/Users/niklas/Library/Developer/Xcode/DerivedData/SpotDaDaemon-gcfsyiwqwawhaqepkmbuhvcfdldi/Build/Products/Debug/libBlockObserving.a'

PodSpec Version

The podspec in the git repository says version 3.0 but the one in coccoapods is still 2.2.1
https://cocoapods.org/pods/Block-KVO

In version 2.2.1 I am having issues building with line 196 of NSObject+MTKObserving.m
I notice in version 3.0 (when downloaded from github) it is building. Are you able to check the version in cocoapods is up to date?

Thanks

New version API proposal

This library is in use for enough time to be able to tell, what features I don't use and what features I would like to have. For anyone reading this, please tell me your opinion on proposed changes. All changes will be backwards compatible.

New features

  • Unwrap primitive values out of NSValue or NSNumber instances. I won't support all primitive types, but only those from Foundation, CoreGraphics and UIKit. Support for other may be added using category.
NSInteger, NSUInteger, CGFloat, CGPoint, CGSize, CGRect, CGAffineTransform UIEdgeInsets, UIOffset
  • Invoke observation block when value didn't change.
  • Supress observation for a while.
  • Behavior options. Optionally skip initial observation or skip equality check.
  • Automatic removal of observations, but I have to check available solutions for this.

Removed, deprecated or more difficult to access

  • Use selector observation methods. I will keep them because of backward compatibility, maybe as deprecated.
  • Use block arguments while observing many properties (keyPath, old, new). These may be sometimes needed, but mostly not.
  • Use previous value in any of the blocks (old).

The new API

Main idea is to separate creation from the action. In one call, you create observation object and in second one you tell it to invoke block, post a notification or something.

Usage:

[[self observeProperty:@keypath(self, title)] usingObjectBlock:^(typeof(self) self, NSString *title) {
    // ...
}];

Methods that create observation object:

- (id)observeProperty:(NSString *)keypath;
- (id)observeObject:(NSObject *)foreignObject property:(NSString *)keypath;
- (id)observeProperty:(NSString *)keypath initial:(BOOL)wantsInitial equals:(BOOL)doEqualityCheck;
- (id)observeObject:(NSObject *)foreignObject property:(NSString *)keypath initial:(BOOL)wantsInitial equals:(BOOL)doEqualityCheck;

Ignore or supress observations by boolean switch:

- (instancetype)enabledProperty:(NSString *)keypathToBooleanProperty;
- (instancetype)disabledProperty:(NSString *)keypathToBooleanProperty;

[[[self observeProperty:@keypath(self, title)]
  disabledProperty:@keypath(self, ignoreTitleChanges)]
 usingObjectBlock:^(typeof(self) self, NSString *title) {
    // ...
}];
self.ignoreTitleChanges = YES;

Block observation methods:

- (void)usingObjectBlock:(void (^)(typeof(self) self, id value))block;
- (void)usingIntegerBlock:(void (^)(typeof(self) self, NSInteger value))block;
- (void)usingUnsignedIntegerBlock:(void (^)(typeof(self) self, NSUInteger value))block;
- (void)usingFloatBlock:(void (^)(typeof(self) self, CGFloat value))block;
- (void)usingPointBlock:(void (^)(typeof(self) self, CGPoint point))block;
- (void)usingSizeBlock:(void (^)(typeof(self) self, CGSize size))block;
- (void)usingRectBlock:(void (^)(typeof(self) self, CGRect rect))block;
// ...

General observation method. From this object you can get any KVO info:

- (void)usingChangeBlock:(void (^)(typeof(self) self, MTKKVOChange *change))block;

Other actions (instead of block observation):

  • (void)postNotificationName:(NSString *)name;
  • (void)setProperty:(NSString *) transform:(id (^)(id input))transformBlock;
  • (void)setProperty:(NSString *) null:(id)replacement;

Manual trigger:

- (void)triggerObservation;

Ignoration:

- (void)ignoreInBlock:(void (^)(void))block;

[self.titleObservation ignoreInBlock:^{
    self.title = @"Top Secret";
}];

Remove single observation:

- (void)remove;

Compile error when included in .mm file

Looks like the keyword 'new' used MTKObserver.h is causing compile time error when included in .mm file.

/MyProjectPath/Pods/Headers/Public/Block-KVO/MTKObserver.h:15:84: error: expected ')'
typedef void(^MTKBlockGeneric) (__weak id self, id new);
^
/MyProjectPath/Pods/Headers/Public/Block-KVO/MTKObserver.h:15:37: note: to match this '('
typedef void(^MTKBlockGeneric) (__weak id self, id new);
^
/MyProjectPath/Pods/Headers/Public/Block-KVO/MTKObserver.h:16:84: error: expected ')'
typedef void(^MTKBlockChange) (__weak id self, id old, id new);
^
/MyProjectPath/Pods/Headers/Public/Block-KVO/MTKObserver.h:16:37: note: to match this '('
typedef void(^MTKBlockChange) (__weak id self, id old, id new);
^

Observation of other objects

It whould be great to directly observe properties of other objects.

Now (v2.0) it is only possible to observe self, that means all key-paths have to be relative to the caller. Workaround for this is to store observed object in weak property and observe it this way.

Two-way binding causing infinite loop

Hi,

The recursion-safe for two-way binding caught my attention, but unfortunately it's not working. My code runs in loop when the second binding is reached.

[self map:@keypath(self.operationType) to:@keypath(self.headerView.operationType) null:nil];
[self map:@keypath(self.headerView.operationType) to:@keypath(self.operationType) null:nil];

Any advice?

Regards,

MTKObserver should removeObserver in dealloc

As a associated object, dealloc() of MTKObserver is a excellent place to removeObserver to target. That also make MTKObserver self managed, and there is no need to call removeAllObservations outside.

Observing a readonly property cause runtime error (crash).

I have property like that:

@interface MyClass : NSObject
@Property (readonly, nonatomic) id myProp;
@EnD

@interface MyClass ()
@Property (strong, nonatomic) id myProp;
@EnD

If I try to observe that property this way:

MyClass *myClassInstance = ...
[self
observeProperty:@keypath(myClassInstance.myProp)
withBlock:^(__weak typeof(self) self, id oldValue, id newValue) {

NSLog(@"Changed!");

}];

I'm getting runtime error like that:

Terminating app due to uncaught exception 'NSUnknownKeyException', reason:
"addObserver: <...> forKeyPath:@"myProp" options:7 context:0x0] was sent to an object that is not KVC-compliant for the "myProp" property"

Registering change observer without calling block with initial value

I have a use pattern where I need to observe changes in a property and trigger special housekeeping and UI related code (like animations).

I do not, however, want to perform these actions for the initial value. As designed, Block-KVO always calls the observation code block with the initial value. I'd like the option of suppressing that behavior.

To that end, I forked the project and added a parallel set of registration methods that take a new observeInitialValue:(BOOL) parameter. So for every method like this

- (void)observeProperty:(NSString *)keyPath withBlock:(MTKBlockChange)observationBlock;

there is now a sister method named

- (void)observeProperty:(NSString *)keyPath observingInitialValue:(BOOL)initial withBlock:(MTKBlockChange)observationBlock;

The implementation is trivial. Each original registration method had the new parameter added. The implementation of the original method is now a single call to its new sister method, passing YES for the observingInitialValue: parameter:

- (void)observeProperty:(NSString *)keyPath withBlock:(MTKBlockChange)observationBlock {
	[self observeProperty:keyPath observingInitialValue:YES withBlock:observationBlock];
}

- (void)observeProperty:(NSString *)keyPath observingInitialValue:(BOOL)initial withBlock:(MTKBlockChange)observationBlock {
    ...
}

The initial call to the change block was removed from -addSettingObservationBlock: and is now the responsibility of the sender—which I think is cleaner architecturally. The initial call is now performed, or skipped, as requested in -observeObject:property:observingInitialValue:withBlock: and -observeRelationship:observingInitialValue:changeBlock:insertionBlock:removalBlock:replacementBlock:.

If this seems like a welcome addition to the project, let me know and I'll make a pull request. If not, just close the issue.

Expose change dictionary for observing collections

Currently the KVO block doesn't pass in the change dictionary making it hard to disambiguate if a change on a collection was caused by an insertion, deletion, change, etc.

(Thank you for this library!)

Observe `NSNotifications` with blocks

Would be great to be able to observe NSNotifications with blocks. There is support in NSNotificationCentre for GCD and blocks, but it is not very easy to use, since you have to store some strange observer and then remove it yourself.

Despite of the fact this project is primary focused on KVO, this would be nice addition and quite easy to implement using similar mechanics.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.