Skip to content

Commit 04f6d81

Browse files
Merge pull request #3 from SomeRandomiOSDev/1.0.2
Release 1.0.2
2 parents e6158c2 + 4079f83 commit 04f6d81

14 files changed

+396
-7
lines changed

KeyValueObservation.podspec

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
Pod::Spec.new do |s|
22

33
s.name = "KeyValueObservation"
4-
s.version = "1.0.1"
4+
s.version = "1.0.2"
55
s.summary = "A small KVO helper library"
66
s.description = <<-DESC
77
A small KVO helper library that provides a NSObject and a NSArray category for observing key value changes using blocks
@@ -18,7 +18,7 @@ Pod::Spec.new do |s|
1818

1919
s.source = { :git => "https://github.com/SomeRandomiOSDev/KeyValueObservation.git", :tag => s.version.to_s }
2020

21-
s.public_header_files = 'KeyValueObservation/NSObject+KeyValueObservation.h', 'KeyValueObservation/NSArray+KeyValueObservation.h', 'KeyValueObservation/SRDKeyValueObservation.h', 'KeyValueObservation/SRDKeyValueObservedChange.h'
21+
s.public_header_files = 'KeyValueObservation/NSObject+KeyValueObservation.h', 'KeyValueObservation/NSArray+KeyValueObservation.h', 'KeyValueObservation/SRDKeyValueObservation.h', 'KeyValueObservation/SRDKeyValueObservedChange.h', 'KeyValueObservation/SRDKVOInfo.h'
2222
s.source_files = 'KeyValueObservation/**/*.{h,m}'
2323
s.frameworks = 'Foundation'
2424
s.requires_arc = true

KeyValueObservation.xcodeproj/project.pbxproj

+20
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,14 @@
5151
DDF7571622AA8688002E11D4 /* SRDKeyValueObservedChange.m in Sources */ = {isa = PBXBuildFile; fileRef = DDF7569122AA77B1002E11D4 /* SRDKeyValueObservedChange.m */; };
5252
DDF7571722AA8693002E11D4 /* KeyValueObservationTests.m in Sources */ = {isa = PBXBuildFile; fileRef = DDF7568122AA773D002E11D4 /* KeyValueObservationTests.m */; };
5353
DDF7571822AA8694002E11D4 /* KeyValueObservationTests.m in Sources */ = {isa = PBXBuildFile; fileRef = DDF7568122AA773D002E11D4 /* KeyValueObservationTests.m */; };
54+
DDF7572622AA8F27002E11D4 /* SRDKVOInfo.h in Headers */ = {isa = PBXBuildFile; fileRef = DDF7572422AA8F27002E11D4 /* SRDKVOInfo.h */; settings = {ATTRIBUTES = (Public, ); }; };
55+
DDF7572722AA8F27002E11D4 /* SRDKVOInfo.m in Sources */ = {isa = PBXBuildFile; fileRef = DDF7572522AA8F27002E11D4 /* SRDKVOInfo.m */; };
56+
DDF7572822AA8F2B002E11D4 /* SRDKVOInfo.h in Headers */ = {isa = PBXBuildFile; fileRef = DDF7572422AA8F27002E11D4 /* SRDKVOInfo.h */; settings = {ATTRIBUTES = (Public, ); }; };
57+
DDF7572922AA8F2D002E11D4 /* SRDKVOInfo.h in Headers */ = {isa = PBXBuildFile; fileRef = DDF7572422AA8F27002E11D4 /* SRDKVOInfo.h */; settings = {ATTRIBUTES = (Public, ); }; };
58+
DDF7572A22AA8F2F002E11D4 /* SRDKVOInfo.h in Headers */ = {isa = PBXBuildFile; fileRef = DDF7572422AA8F27002E11D4 /* SRDKVOInfo.h */; settings = {ATTRIBUTES = (Public, ); }; };
59+
DDF7572B22AA8F33002E11D4 /* SRDKVOInfo.m in Sources */ = {isa = PBXBuildFile; fileRef = DDF7572522AA8F27002E11D4 /* SRDKVOInfo.m */; };
60+
DDF7572C22AA8F34002E11D4 /* SRDKVOInfo.m in Sources */ = {isa = PBXBuildFile; fileRef = DDF7572522AA8F27002E11D4 /* SRDKVOInfo.m */; };
61+
DDF7572D22AA8F35002E11D4 /* SRDKVOInfo.m in Sources */ = {isa = PBXBuildFile; fileRef = DDF7572522AA8F27002E11D4 /* SRDKVOInfo.m */; };
5462
/* End PBXBuildFile section */
5563

5664
/* Begin PBXContainerItemProxy section */
@@ -104,6 +112,8 @@
104112
DDF756EC22AA7854002E11D4 /* KeyValueObservation.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = KeyValueObservation.framework; sourceTree = BUILT_PRODUCTS_DIR; };
105113
DDF756F422AA7B6E002E11D4 /* KeyValueObservation.modulemap */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.module-map"; path = KeyValueObservation.modulemap; sourceTree = SOURCE_ROOT; };
106114
DDF7570422AA7C34002E11D4 /* KeyValueObservation.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KeyValueObservation.h; sourceTree = "<group>"; };
115+
DDF7572422AA8F27002E11D4 /* SRDKVOInfo.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SRDKVOInfo.h; sourceTree = "<group>"; };
116+
DDF7572522AA8F27002E11D4 /* SRDKVOInfo.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SRDKVOInfo.m; sourceTree = "<group>"; };
107117
/* End PBXFileReference section */
108118

109119
/* Begin PBXFrameworksBuildPhase section */
@@ -216,6 +226,8 @@
216226
DDF7569822AA77B2002E11D4 /* SRDKeyValueObservedChange.h */,
217227
DDF7568D22AA77B1002E11D4 /* SRDKeyValueObservedChange+Internal.h */,
218228
DDF7569122AA77B1002E11D4 /* SRDKeyValueObservedChange.m */,
229+
DDF7572422AA8F27002E11D4 /* SRDKVOInfo.h */,
230+
DDF7572522AA8F27002E11D4 /* SRDKVOInfo.m */,
219231
);
220232
name = Sources;
221233
sourceTree = "<group>";
@@ -248,6 +260,7 @@
248260
DDF7570522AA7C34002E11D4 /* KeyValueObservation.h in Headers */,
249261
DDF7569B22AA77B2002E11D4 /* SRDKeyValueObservation+Internal.h in Headers */,
250262
DDF7569922AA77B2002E11D4 /* SRDKeyValueObservedChange+Internal.h in Headers */,
263+
DDF7572622AA8F27002E11D4 /* SRDKVOInfo.h in Headers */,
251264
);
252265
runOnlyForDeploymentPostprocessing = 0;
253266
};
@@ -258,6 +271,7 @@
258271
DDF756F822AA7BF8002E11D4 /* SRDKeyValueObservedChange.h in Headers */,
259272
DDF756F522AA7BF8002E11D4 /* NSArray+KeyValueObservation.h in Headers */,
260273
DDF756F622AA7BF8002E11D4 /* NSObject+KeyValueObservation.h in Headers */,
274+
DDF7572822AA8F2B002E11D4 /* SRDKVOInfo.h in Headers */,
261275
DDF756F722AA7BF8002E11D4 /* SRDKeyValueObservation.h in Headers */,
262276
DDF7570622AA7C34002E11D4 /* KeyValueObservation.h in Headers */,
263277
);
@@ -270,6 +284,7 @@
270284
DDF756FD22AA7BFA002E11D4 /* SRDKeyValueObservedChange.h in Headers */,
271285
DDF756FA22AA7BFA002E11D4 /* NSArray+KeyValueObservation.h in Headers */,
272286
DDF756FB22AA7BFA002E11D4 /* NSObject+KeyValueObservation.h in Headers */,
287+
DDF7572922AA8F2D002E11D4 /* SRDKVOInfo.h in Headers */,
273288
DDF756FC22AA7BFA002E11D4 /* SRDKeyValueObservation.h in Headers */,
274289
DDF7570722AA7C34002E11D4 /* KeyValueObservation.h in Headers */,
275290
);
@@ -282,6 +297,7 @@
282297
DDF7570222AA7BFC002E11D4 /* SRDKeyValueObservedChange.h in Headers */,
283298
DDF756FF22AA7BFC002E11D4 /* NSArray+KeyValueObservation.h in Headers */,
284299
DDF7570022AA7BFC002E11D4 /* NSObject+KeyValueObservation.h in Headers */,
300+
DDF7572A22AA8F2F002E11D4 /* SRDKVOInfo.h in Headers */,
285301
DDF7570122AA7BFC002E11D4 /* SRDKeyValueObservation.h in Headers */,
286302
DDF7570822AA7C34002E11D4 /* KeyValueObservation.h in Headers */,
287303
);
@@ -529,6 +545,7 @@
529545
buildActionMask = 2147483647;
530546
files = (
531547
DDF756A322AA77B2002E11D4 /* NSObject+KeyValueObservation.m in Sources */,
548+
DDF7572722AA8F27002E11D4 /* SRDKVOInfo.m in Sources */,
532549
DDF7569A22AA77B2002E11D4 /* SRDKeyValueObservation.m in Sources */,
533550
DDF756A022AA77B2002E11D4 /* NSArray+KeyValueObservation.m in Sources */,
534551
DDF7569D22AA77B2002E11D4 /* SRDKeyValueObservedChange.m in Sources */,
@@ -548,6 +565,7 @@
548565
buildActionMask = 2147483647;
549566
files = (
550567
DDF7570D22AA8686002E11D4 /* SRDKeyValueObservation.m in Sources */,
568+
DDF7572B22AA8F33002E11D4 /* SRDKVOInfo.m in Sources */,
551569
DDF7570E22AA8686002E11D4 /* SRDKeyValueObservedChange.m in Sources */,
552570
DDF7570B22AA8686002E11D4 /* NSObject+KeyValueObservation.m in Sources */,
553571
DDF7570C22AA8686002E11D4 /* NSArray+KeyValueObservation.m in Sources */,
@@ -567,6 +585,7 @@
567585
buildActionMask = 2147483647;
568586
files = (
569587
DDF7571122AA8687002E11D4 /* SRDKeyValueObservation.m in Sources */,
588+
DDF7572C22AA8F34002E11D4 /* SRDKVOInfo.m in Sources */,
570589
DDF7571222AA8687002E11D4 /* SRDKeyValueObservedChange.m in Sources */,
571590
DDF7570F22AA8687002E11D4 /* NSObject+KeyValueObservation.m in Sources */,
572591
DDF7571022AA8687002E11D4 /* NSArray+KeyValueObservation.m in Sources */,
@@ -586,6 +605,7 @@
586605
buildActionMask = 2147483647;
587606
files = (
588607
DDF7571522AA8688002E11D4 /* SRDKeyValueObservation.m in Sources */,
608+
DDF7572D22AA8F35002E11D4 /* SRDKVOInfo.m in Sources */,
589609
DDF7571622AA8688002E11D4 /* SRDKeyValueObservedChange.m in Sources */,
590610
DDF7571322AA8688002E11D4 /* NSObject+KeyValueObservation.m in Sources */,
591611
DDF7571422AA8688002E11D4 /* NSArray+KeyValueObservation.m in Sources */,

KeyValueObservation.xcodeproj/xcshareddata/xcschemes/KeyValueObservation macOS Tests.xcscheme

+11
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,18 @@
1010
buildConfiguration = "Debug"
1111
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
1212
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
13+
codeCoverageEnabled = "YES"
14+
onlyGenerateCoverageForSpecifiedTargets = "YES"
1315
shouldUseLaunchSchemeArgsEnv = "YES">
16+
<CodeCoverageTargets>
17+
<BuildableReference
18+
BuildableIdentifier = "primary"
19+
BlueprintIdentifier = "DDF756B322AA782D002E11D4"
20+
BuildableName = "KeyValueObservation.framework"
21+
BlueprintName = "KeyValueObservation macOS"
22+
ReferencedContainer = "container:KeyValueObservation.xcodeproj">
23+
</BuildableReference>
24+
</CodeCoverageTargets>
1425
<Testables>
1526
<TestableReference
1627
skipped = "NO">

KeyValueObservation.xcodeproj/xcshareddata/xcschemes/KeyValueObservation tvOS Tests.xcscheme

+11
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,18 @@
1010
buildConfiguration = "Debug"
1111
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
1212
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
13+
codeCoverageEnabled = "YES"
14+
onlyGenerateCoverageForSpecifiedTargets = "YES"
1315
shouldUseLaunchSchemeArgsEnv = "YES">
16+
<CodeCoverageTargets>
17+
<BuildableReference
18+
BuildableIdentifier = "primary"
19+
BlueprintIdentifier = "DDF756CF22AA7841002E11D4"
20+
BuildableName = "KeyValueObservation.framework"
21+
BlueprintName = "KeyValueObservation tvOS"
22+
ReferencedContainer = "container:KeyValueObservation.xcodeproj">
23+
</BuildableReference>
24+
</CodeCoverageTargets>
1425
<Testables>
1526
<TestableReference
1627
skipped = "NO">

KeyValueObservation.xcodeproj/xcshareddata/xcschemes/KeyValueObservation watchOS.xcscheme

+11
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,18 @@
2626
buildConfiguration = "Debug"
2727
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
2828
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
29+
codeCoverageEnabled = "YES"
30+
onlyGenerateCoverageForSpecifiedTargets = "YES"
2931
shouldUseLaunchSchemeArgsEnv = "YES">
32+
<CodeCoverageTargets>
33+
<BuildableReference
34+
BuildableIdentifier = "primary"
35+
BlueprintIdentifier = "DDF756EB22AA7854002E11D4"
36+
BuildableName = "KeyValueObservation.framework"
37+
BlueprintName = "KeyValueObservation watchOS"
38+
ReferencedContainer = "container:KeyValueObservation.xcodeproj">
39+
</BuildableReference>
40+
</CodeCoverageTargets>
3041
<Testables>
3142
</Testables>
3243
<AdditionalOptions>

KeyValueObservation.xcodeproj/xcshareddata/xcschemes/KeyValueObservationTests.xcscheme

+11
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,18 @@
1010
buildConfiguration = "Debug"
1111
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
1212
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
13+
codeCoverageEnabled = "YES"
14+
onlyGenerateCoverageForSpecifiedTargets = "YES"
1315
shouldUseLaunchSchemeArgsEnv = "YES">
16+
<CodeCoverageTargets>
17+
<BuildableReference
18+
BuildableIdentifier = "primary"
19+
BlueprintIdentifier = "DDF7567222AA773D002E11D4"
20+
BuildableName = "KeyValueObservation.framework"
21+
BlueprintName = "KeyValueObservation"
22+
ReferencedContainer = "container:KeyValueObservation.xcodeproj">
23+
</BuildableReference>
24+
</CodeCoverageTargets>
1425
<Testables>
1526
<TestableReference
1627
skipped = "NO">

KeyValueObservation/KeyValueObservation.h

+1
Original file line numberDiff line numberDiff line change
@@ -10,3 +10,4 @@
1010
#import <KeyValueObservation/NSObject+KeyValueObservation.h>
1111
#import <KeyValueObservation/SRDKeyValueObservation.h>
1212
#import <KeyValueObservation/SRDKeyValueObservedChange.h>
13+
#import <KeyValueObservation/SRDKVOInfo.h>

KeyValueObservation/NSObject+KeyValueObservation.h

+7
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
#import <Foundation/Foundation.h>
1010

11+
#import <KeyValueObservation/SRDKVOInfo.h>
1112
#import <KeyValueObservation/SRDKeyValueObservation.h>
1213
#import <KeyValueObservation/SRDKeyValueObservedChange.h>
1314

@@ -24,6 +25,12 @@ NS_ASSUME_NONNULL_BEGIN
2425
/// \returns An object that keeps the observation alive. Once this object is deallocated, the block is released no longer receives KVO events.
2526
- (SRDKeyValueObservation *)observeKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options changeHandler:(void (^)(id, SRDKeyValueObservedChange *))changeHandler OBJC_SWIFT_UNAVAILABLE("use Swift's KeyPath observing instead");
2627

28+
/// Runs a given handler block synchronously while ignoring all Key-Value Observations specified by the @p observations parameter. This has the same effect as unregistering for each observer/keyPath in @p observations, running the block, then re-registering for each observation.
29+
///
30+
/// \param observations An array specifying the Key-Value Observations to ignore.
31+
/// \param handler The block to run.
32+
- (void)performWhileIgnoringObservations:(NSArray<SRDKVOInfo *> *)observations handler:(void (^NS_NOESCAPE)(void))handler;
33+
2734
@end
2835

2936
NS_ASSUME_NONNULL_END

KeyValueObservation/NSObject+KeyValueObservation.m

+66
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,10 @@
1111

1212
#import "SRDKeyValueObservation+Internal.h"
1313

14+
#pragma mark - Private Function Declarations
15+
16+
static void __observeValueForKeyPath(id self, SEL _cmd, NSString * __nullable, id __nullable, NSDictionary<NSKeyValueChangeKey, id> * __nullable, void * __nullable);
17+
1418
#pragma mark - Class Category | NSObject (KeyValueObservation)
1519

1620
@implementation NSObject (KeyValueObservation)
@@ -26,4 +30,66 @@ - (SRDKeyValueObservation *)observeKeyPath:(NSString *)keyPath options:(NSKeyVal
2630
return [[SRDKeyValueObservation alloc] initWithObject:self keyPath:keyPath options:options changeHandler:changeHandler];
2731
}
2832

33+
34+
35+
- (void)performWhileIgnoringObservations:(NSArray<SRDKVOInfo *> *)observations handler:(void (^NS_NOESCAPE)(void))handler {
36+
if (observations.count == 0)
37+
return handler();
38+
39+
observations = [[NSArray alloc] initWithArray:observations copyItems:YES];
40+
41+
Class const selfclass = object_getClass(self);
42+
Class const superclass = class_getSuperclass(selfclass);
43+
44+
Method const superObserveValueForKeyPath = superclass != Nil ? class_getInstanceMethod(superclass, @selector(observeValueForKeyPath:ofObject:change:context:)) : NULL;
45+
Method observeValueForKeyPath = class_getInstanceMethod(selfclass, method_getName(superObserveValueForKeyPath));
46+
47+
if (superObserveValueForKeyPath != NULL && observeValueForKeyPath == superObserveValueForKeyPath) {
48+
// This class doesn't override its superclass' implementation of
49+
// -[NSObject observeValueForKeyPath:ofObject:change:context:]. We should add the
50+
// method to the current class so as not to replace the superclass' implementation
51+
// with ours.
52+
53+
class_addMethod(selfclass, method_getName(superObserveValueForKeyPath), (IMP)__observeValueForKeyPath, method_getTypeEncoding(superObserveValueForKeyPath));
54+
observeValueForKeyPath = class_getInstanceMethod(selfclass, method_getName(superObserveValueForKeyPath));
55+
}
56+
57+
IMP const originalObserveValueForKeyPath = method_getImplementation(observeValueForKeyPath);
58+
59+
__typeof(self) __unsafe_unretained const unowned = self;
60+
void (^ const __observeValueForKeyPath)(id, NSString *, id, NSDictionary *, void *) = ^(id _self, NSString *_keyPath, id _object, NSDictionary *_change, void *_context) {
61+
for (SRDKVOInfo *observation in observations) {
62+
if (unowned == _self && observation.observed == _object && [observation.keyPath isEqualToString:_keyPath])
63+
return; // Ignore
64+
}
65+
66+
// Forward to original implementation
67+
((void (*)(id, SEL, NSString *, id, NSDictionary *, void *))originalObserveValueForKeyPath)(_self, @selector(observeValueForKeyPath:ofObject:change:context:), _keyPath, _object, _change, _context);
68+
};
69+
70+
IMP const replacementObserveValueForKeyPath = imp_implementationWithBlock(__observeValueForKeyPath);
71+
method_setImplementation(observeValueForKeyPath, replacementObserveValueForKeyPath);
72+
73+
@try { handler(); }
74+
@finally {
75+
// Always set back original implementation and remove block
76+
method_setImplementation(observeValueForKeyPath, originalObserveValueForKeyPath);
77+
imp_removeBlock(replacementObserveValueForKeyPath);
78+
}
79+
}
80+
2981
@end
82+
83+
#pragma mark - Private Function Definitions
84+
85+
/// Simple implementation of -[NSObject observeValueForKeyPath:ofObject:change:context:] that simply invoke its superclass' implementation
86+
static void __observeValueForKeyPath(id self, SEL _cmd, NSString * __nullable keyPath, id __nullable object, NSDictionary<NSKeyValueChangeKey, id> * __nullable change, void * __nullable context) {
87+
Class const superclass = class_getSuperclass(object_getClass(self));
88+
89+
struct objc_super super = {
90+
.receiver = self,
91+
.super_class = superclass
92+
};
93+
94+
((void (*)(struct objc_super *, SEL, NSString *, id, NSDictionary<NSKeyValueChangeKey, id> *, void *))objc_msgSendSuper)(&super, _cmd, keyPath, object, change, context);
95+
}

KeyValueObservation/SRDKVOInfo.h

+36
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
//
2+
// SRDKVOInfo.h
3+
// KeyValueObservation
4+
//
5+
// Created by Joseph Newton on 2/7/19.
6+
// Copyright © 2019 Joseph Newton. All rights reserved.
7+
//
8+
9+
#import <Foundation/Foundation.h>
10+
11+
NS_ASSUME_NONNULL_BEGIN
12+
13+
#pragma mark - Class (SRDKVOInfo) Interface
14+
15+
@interface SRDKVOInfo : NSObject <NSCopying, NSMutableCopying>
16+
17+
@property (nonatomic, strong, readonly, nullable) __kindof NSObject *observed;
18+
@property (nonatomic, copy, readonly) NSString *keyPath;
19+
20+
- (instancetype)initWithObserved:(nullable __kindof NSObject *)observed keyPath:(NSString *)keyPath;
21+
+ (SRDKVOInfo *)infoWithObserved:(nullable __kindof NSObject *)observed keyPath:(NSString *)keyPath OBJC_SWIFT_UNAVAILABLE("use object initializers instead");
22+
23+
@end
24+
25+
#pragma mark - Class (SRDMutableKVOInfo) Interface
26+
27+
@interface SRDMutableKVOInfo : SRDKVOInfo <NSCopying, NSMutableCopying>
28+
29+
@property (nonatomic, strong, nullable) __kindof NSObject *observed;
30+
@property (nonatomic, copy) NSString *keyPath;
31+
32+
+ (SRDMutableKVOInfo *)infoWithObserved:(nullable __kindof NSObject *)observed keyPath:(NSString *)keyPath OBJC_SWIFT_UNAVAILABLE("use object initializers instead");
33+
34+
@end
35+
36+
NS_ASSUME_NONNULL_END

0 commit comments

Comments
 (0)