├── .gitignore ├── Example Project ├── DGKVOBlocks.xcodeproj │ └── project.pbxproj └── DGKVOBlocks │ ├── DGAppDelegate.h │ ├── DGAppDelegate.m │ ├── DGKVOBlocks-Info.plist │ ├── DGKVOBlocks-Prefix.pch │ ├── en.lproj │ ├── Credits.rtf │ ├── InfoPlist.strings │ └── MainMenu.xib │ └── main.m ├── NSObject+DGKVOBlocks.h ├── NSObject+DGKVOBlocks.m └── ReadMe.md /.gitignore: -------------------------------------------------------------------------------- 1 | # Mac OS X 2 | *.DS_Store 3 | 4 | # Xcode 5 | *.pbxuser 6 | *.mode1v3 7 | *.mode2v3 8 | *.perspectivev3 9 | *.xcuserstate 10 | project.xcworkspace/ 11 | xcuserdata/ 12 | 13 | # Generated files 14 | *.[oa] 15 | *.pyc 16 | *.6 17 | 6.out 18 | 19 | #Python modules 20 | MANIFEST 21 | dist/ 22 | build/ 23 | 24 | # Backup files 25 | *~.nib -------------------------------------------------------------------------------- /Example Project/DGKVOBlocks.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 308679161439E8EF004A54A0 /* NSObject+DGKVOBlocks.m in Sources */ = {isa = PBXBuildFile; fileRef = 308679151439E8EF004A54A0 /* NSObject+DGKVOBlocks.m */; }; 11 | 30C994521438716C00966C25 /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 30C994511438716C00966C25 /* Cocoa.framework */; }; 12 | 30C9945C1438716C00966C25 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 30C9945A1438716C00966C25 /* InfoPlist.strings */; }; 13 | 30C9945E1438716C00966C25 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 30C9945D1438716C00966C25 /* main.m */; }; 14 | 30C994621438716C00966C25 /* Credits.rtf in Resources */ = {isa = PBXBuildFile; fileRef = 30C994601438716C00966C25 /* Credits.rtf */; }; 15 | 30C994651438716C00966C25 /* DGAppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 30C994641438716C00966C25 /* DGAppDelegate.m */; }; 16 | 30C994681438716D00966C25 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 30C994661438716D00966C25 /* MainMenu.xib */; }; 17 | /* End PBXBuildFile section */ 18 | 19 | /* Begin PBXFileReference section */ 20 | 308679141439E8EF004A54A0 /* NSObject+DGKVOBlocks.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "NSObject+DGKVOBlocks.h"; path = "../../NSObject+DGKVOBlocks.h"; sourceTree = ""; }; 21 | 308679151439E8EF004A54A0 /* NSObject+DGKVOBlocks.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = "NSObject+DGKVOBlocks.m"; path = "../../NSObject+DGKVOBlocks.m"; sourceTree = ""; }; 22 | 30C9944D1438716C00966C25 /* DGKVOBlocks.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = DGKVOBlocks.app; sourceTree = BUILT_PRODUCTS_DIR; }; 23 | 30C994511438716C00966C25 /* Cocoa.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Cocoa.framework; path = System/Library/Frameworks/Cocoa.framework; sourceTree = SDKROOT; }; 24 | 30C994541438716C00966C25 /* AppKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AppKit.framework; path = System/Library/Frameworks/AppKit.framework; sourceTree = SDKROOT; }; 25 | 30C994551438716C00966C25 /* CoreData.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreData.framework; path = System/Library/Frameworks/CoreData.framework; sourceTree = SDKROOT; }; 26 | 30C994561438716C00966C25 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; }; 27 | 30C994591438716C00966C25 /* DGKVOBlocks-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "DGKVOBlocks-Info.plist"; sourceTree = ""; }; 28 | 30C9945B1438716C00966C25 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = ""; }; 29 | 30C9945D1438716C00966C25 /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; 30 | 30C9945F1438716C00966C25 /* DGKVOBlocks-Prefix.pch */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "DGKVOBlocks-Prefix.pch"; sourceTree = ""; }; 31 | 30C994611438716C00966C25 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.rtf; name = en; path = en.lproj/Credits.rtf; sourceTree = ""; }; 32 | 30C994631438716C00966C25 /* DGAppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DGAppDelegate.h; sourceTree = ""; }; 33 | 30C994641438716C00966C25 /* DGAppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DGAppDelegate.m; sourceTree = ""; }; 34 | 30C994671438716D00966C25 /* en */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = en; path = en.lproj/MainMenu.xib; sourceTree = ""; }; 35 | /* End PBXFileReference section */ 36 | 37 | /* Begin PBXFrameworksBuildPhase section */ 38 | 30C9944A1438716C00966C25 /* Frameworks */ = { 39 | isa = PBXFrameworksBuildPhase; 40 | buildActionMask = 2147483647; 41 | files = ( 42 | 30C994521438716C00966C25 /* Cocoa.framework in Frameworks */, 43 | ); 44 | runOnlyForDeploymentPostprocessing = 0; 45 | }; 46 | /* End PBXFrameworksBuildPhase section */ 47 | 48 | /* Begin PBXGroup section */ 49 | 308679131439E8D5004A54A0 /* Example */ = { 50 | isa = PBXGroup; 51 | children = ( 52 | 30C994631438716C00966C25 /* DGAppDelegate.h */, 53 | 30C994641438716C00966C25 /* DGAppDelegate.m */, 54 | 30C994661438716D00966C25 /* MainMenu.xib */, 55 | 30C994581438716C00966C25 /* Supporting Files */, 56 | ); 57 | name = Example; 58 | sourceTree = ""; 59 | }; 60 | 30C994421438716C00966C25 = { 61 | isa = PBXGroup; 62 | children = ( 63 | 30C994571438716C00966C25 /* DGKVOBlocks */, 64 | 30C994501438716C00966C25 /* Frameworks */, 65 | 30C9944E1438716C00966C25 /* Products */, 66 | ); 67 | sourceTree = ""; 68 | }; 69 | 30C9944E1438716C00966C25 /* Products */ = { 70 | isa = PBXGroup; 71 | children = ( 72 | 30C9944D1438716C00966C25 /* DGKVOBlocks.app */, 73 | ); 74 | name = Products; 75 | sourceTree = ""; 76 | }; 77 | 30C994501438716C00966C25 /* Frameworks */ = { 78 | isa = PBXGroup; 79 | children = ( 80 | 30C994511438716C00966C25 /* Cocoa.framework */, 81 | 30C994531438716C00966C25 /* Other Frameworks */, 82 | ); 83 | name = Frameworks; 84 | sourceTree = ""; 85 | }; 86 | 30C994531438716C00966C25 /* Other Frameworks */ = { 87 | isa = PBXGroup; 88 | children = ( 89 | 30C994541438716C00966C25 /* AppKit.framework */, 90 | 30C994551438716C00966C25 /* CoreData.framework */, 91 | 30C994561438716C00966C25 /* Foundation.framework */, 92 | ); 93 | name = "Other Frameworks"; 94 | sourceTree = ""; 95 | }; 96 | 30C994571438716C00966C25 /* DGKVOBlocks */ = { 97 | isa = PBXGroup; 98 | children = ( 99 | 308679141439E8EF004A54A0 /* NSObject+DGKVOBlocks.h */, 100 | 308679151439E8EF004A54A0 /* NSObject+DGKVOBlocks.m */, 101 | 308679131439E8D5004A54A0 /* Example */, 102 | ); 103 | path = DGKVOBlocks; 104 | sourceTree = ""; 105 | }; 106 | 30C994581438716C00966C25 /* Supporting Files */ = { 107 | isa = PBXGroup; 108 | children = ( 109 | 30C994591438716C00966C25 /* DGKVOBlocks-Info.plist */, 110 | 30C9945A1438716C00966C25 /* InfoPlist.strings */, 111 | 30C9945D1438716C00966C25 /* main.m */, 112 | 30C9945F1438716C00966C25 /* DGKVOBlocks-Prefix.pch */, 113 | 30C994601438716C00966C25 /* Credits.rtf */, 114 | ); 115 | name = "Supporting Files"; 116 | sourceTree = ""; 117 | }; 118 | /* End PBXGroup section */ 119 | 120 | /* Begin PBXNativeTarget section */ 121 | 30C9944C1438716C00966C25 /* DGKVOBlocks */ = { 122 | isa = PBXNativeTarget; 123 | buildConfigurationList = 30C9946B1438716D00966C25 /* Build configuration list for PBXNativeTarget "DGKVOBlocks" */; 124 | buildPhases = ( 125 | 30C994491438716C00966C25 /* Sources */, 126 | 30C9944A1438716C00966C25 /* Frameworks */, 127 | 30C9944B1438716C00966C25 /* Resources */, 128 | ); 129 | buildRules = ( 130 | ); 131 | dependencies = ( 132 | ); 133 | name = DGKVOBlocks; 134 | productName = DGKVOBlocks; 135 | productReference = 30C9944D1438716C00966C25 /* DGKVOBlocks.app */; 136 | productType = "com.apple.product-type.application"; 137 | }; 138 | /* End PBXNativeTarget section */ 139 | 140 | /* Begin PBXProject section */ 141 | 30C994441438716C00966C25 /* Project object */ = { 142 | isa = PBXProject; 143 | attributes = { 144 | LastUpgradeCheck = 0420; 145 | ORGANIZATIONNAME = "No Thirst Software"; 146 | }; 147 | buildConfigurationList = 30C994471438716C00966C25 /* Build configuration list for PBXProject "DGKVOBlocks" */; 148 | compatibilityVersion = "Xcode 3.2"; 149 | developmentRegion = English; 150 | hasScannedForEncodings = 0; 151 | knownRegions = ( 152 | en, 153 | ); 154 | mainGroup = 30C994421438716C00966C25; 155 | productRefGroup = 30C9944E1438716C00966C25 /* Products */; 156 | projectDirPath = ""; 157 | projectRoot = ""; 158 | targets = ( 159 | 30C9944C1438716C00966C25 /* DGKVOBlocks */, 160 | ); 161 | }; 162 | /* End PBXProject section */ 163 | 164 | /* Begin PBXResourcesBuildPhase section */ 165 | 30C9944B1438716C00966C25 /* Resources */ = { 166 | isa = PBXResourcesBuildPhase; 167 | buildActionMask = 2147483647; 168 | files = ( 169 | 30C9945C1438716C00966C25 /* InfoPlist.strings in Resources */, 170 | 30C994621438716C00966C25 /* Credits.rtf in Resources */, 171 | 30C994681438716D00966C25 /* MainMenu.xib in Resources */, 172 | ); 173 | runOnlyForDeploymentPostprocessing = 0; 174 | }; 175 | /* End PBXResourcesBuildPhase section */ 176 | 177 | /* Begin PBXSourcesBuildPhase section */ 178 | 30C994491438716C00966C25 /* Sources */ = { 179 | isa = PBXSourcesBuildPhase; 180 | buildActionMask = 2147483647; 181 | files = ( 182 | 30C9945E1438716C00966C25 /* main.m in Sources */, 183 | 30C994651438716C00966C25 /* DGAppDelegate.m in Sources */, 184 | 308679161439E8EF004A54A0 /* NSObject+DGKVOBlocks.m in Sources */, 185 | ); 186 | runOnlyForDeploymentPostprocessing = 0; 187 | }; 188 | /* End PBXSourcesBuildPhase section */ 189 | 190 | /* Begin PBXVariantGroup section */ 191 | 30C9945A1438716C00966C25 /* InfoPlist.strings */ = { 192 | isa = PBXVariantGroup; 193 | children = ( 194 | 30C9945B1438716C00966C25 /* en */, 195 | ); 196 | name = InfoPlist.strings; 197 | sourceTree = ""; 198 | }; 199 | 30C994601438716C00966C25 /* Credits.rtf */ = { 200 | isa = PBXVariantGroup; 201 | children = ( 202 | 30C994611438716C00966C25 /* en */, 203 | ); 204 | name = Credits.rtf; 205 | sourceTree = ""; 206 | }; 207 | 30C994661438716D00966C25 /* MainMenu.xib */ = { 208 | isa = PBXVariantGroup; 209 | children = ( 210 | 30C994671438716D00966C25 /* en */, 211 | ); 212 | name = MainMenu.xib; 213 | sourceTree = ""; 214 | }; 215 | /* End PBXVariantGroup section */ 216 | 217 | /* Begin XCBuildConfiguration section */ 218 | 30C994691438716D00966C25 /* Debug */ = { 219 | isa = XCBuildConfiguration; 220 | buildSettings = { 221 | ALWAYS_SEARCH_USER_PATHS = NO; 222 | ARCHS = "$(ARCHS_STANDARD_64_BIT)"; 223 | CLANG_ENABLE_OBJC_ARC = YES; 224 | COPY_PHASE_STRIP = NO; 225 | GCC_C_LANGUAGE_STANDARD = gnu99; 226 | GCC_DYNAMIC_NO_PIC = NO; 227 | GCC_ENABLE_OBJC_EXCEPTIONS = YES; 228 | GCC_ENABLE_OBJC_GC = supported; 229 | GCC_OPTIMIZATION_LEVEL = 0; 230 | GCC_PREPROCESSOR_DEFINITIONS = ( 231 | "DEBUG=1", 232 | "$(inherited)", 233 | ); 234 | GCC_SYMBOLS_PRIVATE_EXTERN = NO; 235 | GCC_VERSION = com.apple.compilers.llvm.clang.1_0; 236 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 237 | GCC_WARN_ABOUT_MISSING_PROTOTYPES = YES; 238 | GCC_WARN_ABOUT_RETURN_TYPE = YES; 239 | GCC_WARN_UNUSED_VARIABLE = YES; 240 | ONLY_ACTIVE_ARCH = YES; 241 | SDKROOT = macosx; 242 | }; 243 | name = Debug; 244 | }; 245 | 30C9946A1438716D00966C25 /* Release */ = { 246 | isa = XCBuildConfiguration; 247 | buildSettings = { 248 | ALWAYS_SEARCH_USER_PATHS = NO; 249 | ARCHS = "$(ARCHS_STANDARD_64_BIT)"; 250 | CLANG_ENABLE_OBJC_ARC = YES; 251 | COPY_PHASE_STRIP = YES; 252 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 253 | GCC_C_LANGUAGE_STANDARD = gnu99; 254 | GCC_ENABLE_OBJC_EXCEPTIONS = YES; 255 | GCC_ENABLE_OBJC_GC = supported; 256 | GCC_VERSION = com.apple.compilers.llvm.clang.1_0; 257 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 258 | GCC_WARN_ABOUT_MISSING_PROTOTYPES = YES; 259 | GCC_WARN_ABOUT_RETURN_TYPE = YES; 260 | GCC_WARN_UNUSED_VARIABLE = YES; 261 | SDKROOT = macosx; 262 | }; 263 | name = Release; 264 | }; 265 | 30C9946C1438716D00966C25 /* Debug */ = { 266 | isa = XCBuildConfiguration; 267 | buildSettings = { 268 | CLANG_ENABLE_OBJC_ARC = YES; 269 | GCC_PRECOMPILE_PREFIX_HEADER = YES; 270 | GCC_PREFIX_HEADER = "DGKVOBlocks/DGKVOBlocks-Prefix.pch"; 271 | INFOPLIST_FILE = "DGKVOBlocks/DGKVOBlocks-Info.plist"; 272 | PRODUCT_NAME = "$(TARGET_NAME)"; 273 | WRAPPER_EXTENSION = app; 274 | }; 275 | name = Debug; 276 | }; 277 | 30C9946D1438716D00966C25 /* Release */ = { 278 | isa = XCBuildConfiguration; 279 | buildSettings = { 280 | CLANG_ENABLE_OBJC_ARC = YES; 281 | GCC_PRECOMPILE_PREFIX_HEADER = YES; 282 | GCC_PREFIX_HEADER = "DGKVOBlocks/DGKVOBlocks-Prefix.pch"; 283 | INFOPLIST_FILE = "DGKVOBlocks/DGKVOBlocks-Info.plist"; 284 | PRODUCT_NAME = "$(TARGET_NAME)"; 285 | WRAPPER_EXTENSION = app; 286 | }; 287 | name = Release; 288 | }; 289 | /* End XCBuildConfiguration section */ 290 | 291 | /* Begin XCConfigurationList section */ 292 | 30C994471438716C00966C25 /* Build configuration list for PBXProject "DGKVOBlocks" */ = { 293 | isa = XCConfigurationList; 294 | buildConfigurations = ( 295 | 30C994691438716D00966C25 /* Debug */, 296 | 30C9946A1438716D00966C25 /* Release */, 297 | ); 298 | defaultConfigurationIsVisible = 0; 299 | defaultConfigurationName = Release; 300 | }; 301 | 30C9946B1438716D00966C25 /* Build configuration list for PBXNativeTarget "DGKVOBlocks" */ = { 302 | isa = XCConfigurationList; 303 | buildConfigurations = ( 304 | 30C9946C1438716D00966C25 /* Debug */, 305 | 30C9946D1438716D00966C25 /* Release */, 306 | ); 307 | defaultConfigurationIsVisible = 0; 308 | defaultConfigurationName = Release; 309 | }; 310 | /* End XCConfigurationList section */ 311 | }; 312 | rootObject = 30C994441438716C00966C25 /* Project object */; 313 | } 314 | -------------------------------------------------------------------------------- /Example Project/DGKVOBlocks/DGAppDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // DGAppDelegate.h 3 | // DGKVOBlocks 4 | // 5 | // Created by Danny Greg on 02/10/2011. 6 | // Copyright (c) 2011 No Thirst Software. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface DGAppDelegate : NSObject { 12 | @private 13 | id stringObserver; 14 | id numberObserver; 15 | } 16 | 17 | @property (assign) IBOutlet NSWindow *window; 18 | @property (nonatomic, copy) NSString *string; 19 | @property (assign) IBOutlet NSTextField *fibonacciField; 20 | @property (assign) NSUInteger number; 21 | 22 | @end 23 | -------------------------------------------------------------------------------- /Example Project/DGKVOBlocks/DGAppDelegate.m: -------------------------------------------------------------------------------- 1 | // 2 | // DGAppDelegate.m 3 | // DGKVOBlocks 4 | // 5 | // Created by Danny Greg on 02/10/2011. 6 | // Copyright (c) 2011 No Thirst Software. All rights reserved. 7 | // 8 | 9 | #import "DGAppDelegate.h" 10 | #import "NSObject+DGKVOBlocks.h" 11 | 12 | @interface DGAppDelegate () 13 | 14 | - (NSUInteger)fibonacciNumber:(NSUInteger)aNumber; 15 | @end 16 | 17 | @implementation DGAppDelegate 18 | 19 | @synthesize window =_window; 20 | @synthesize string; 21 | @synthesize fibonacciField; 22 | @synthesize number; 23 | 24 | - (id)init 25 | { 26 | if (!(self = [super init])) { 27 | return nil; 28 | } 29 | number = 1; 30 | 31 | return self; 32 | } 33 | 34 | - (void)dealloc { 35 | [self dgkvo_removeObserverWithIdentifier:numberObserver]; 36 | } 37 | 38 | 39 | - (void)applicationDidFinishLaunching:(NSNotification *)aNotification 40 | { 41 | 42 | stringObserver = [self dgkvo_addObserverForKeyPath:@"string" options:NSKeyValueObservingOptionNew queue:nil usingBlock:^(NSDictionary *change) { 43 | NSLog(@"%@", change); 44 | }]; 45 | 46 | self.string = @"ONE"; 47 | self.string = @"TWO"; 48 | self.string = @"THREE"; 49 | 50 | [self dgkvo_removeObserverWithIdentifier:stringObserver]; 51 | 52 | self.string = @"This should not appear."; 53 | 54 | // Use this with a nil queue to see the main thread blocking. 55 | NSOperationQueue *queue = [[NSOperationQueue alloc] init]; 56 | numberObserver = [self dgkvo_addObserverForKeyPath:@"number" options:NSKeyValueObservingOptionNew queue:queue usingBlock:^(NSDictionary *change) { 57 | NSLog(@"%@", change); 58 | self.fibonacciField.integerValue = [self fibonacciNumber:number]; 59 | }]; 60 | 61 | } 62 | 63 | 64 | // Don't laugh. I know it's terrible - that's the whole point. 65 | - (NSUInteger)fibonacciNumber:(NSUInteger)aNumber { 66 | if (aNumber < 2) { 67 | return aNumber; 68 | } 69 | return [self fibonacciNumber:aNumber - 2] + [self fibonacciNumber:aNumber -1]; 70 | } 71 | 72 | @end 73 | -------------------------------------------------------------------------------- /Example Project/DGKVOBlocks/DGKVOBlocks-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | ${EXECUTABLE_NAME} 9 | CFBundleIconFile 10 | 11 | CFBundleIdentifier 12 | com.dannygreg.${PRODUCT_NAME:rfc1034identifier} 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | ${PRODUCT_NAME} 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | 1.0 21 | CFBundleSignature 22 | ???? 23 | CFBundleVersion 24 | 1 25 | LSMinimumSystemVersion 26 | ${MACOSX_DEPLOYMENT_TARGET} 27 | NSHumanReadableCopyright 28 | Copyright © 2011 No Thirst Software. All rights reserved. 29 | NSMainNibFile 30 | MainMenu 31 | NSPrincipalClass 32 | NSApplication 33 | 34 | 35 | -------------------------------------------------------------------------------- /Example Project/DGKVOBlocks/DGKVOBlocks-Prefix.pch: -------------------------------------------------------------------------------- 1 | // 2 | // Prefix header for all source files of the 'DGKVOBlocks' target in the 'DGKVOBlocks' project 3 | // 4 | 5 | #ifdef __OBJC__ 6 | #import 7 | #endif 8 | -------------------------------------------------------------------------------- /Example Project/DGKVOBlocks/en.lproj/Credits.rtf: -------------------------------------------------------------------------------- 1 | {\rtf0\ansi{\fonttbl\f0\fswiss Helvetica;} 2 | {\colortbl;\red255\green255\blue255;} 3 | \paperw9840\paperh8400 4 | \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\ql\qnatural 5 | 6 | \f0\b\fs24 \cf0 Engineering: 7 | \b0 \ 8 | Some people\ 9 | \ 10 | 11 | \b Human Interface Design: 12 | \b0 \ 13 | Some other people\ 14 | \ 15 | 16 | \b Testing: 17 | \b0 \ 18 | Hopefully not nobody\ 19 | \ 20 | 21 | \b Documentation: 22 | \b0 \ 23 | Whoever\ 24 | \ 25 | 26 | \b With special thanks to: 27 | \b0 \ 28 | Mom\ 29 | } 30 | -------------------------------------------------------------------------------- /Example Project/DGKVOBlocks/en.lproj/InfoPlist.strings: -------------------------------------------------------------------------------- 1 | /* Localized versions of Info.plist keys */ 2 | 3 | -------------------------------------------------------------------------------- /Example Project/DGKVOBlocks/main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // DGKVOBlocks 4 | // 5 | // Created by Danny Greg on 02/10/2011. 6 | // Copyright (c) 2011 No Thirst Software. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | int main(int argc, char *argv[]) 12 | { 13 | return NSApplicationMain(argc, (const char **)argv); 14 | } 15 | -------------------------------------------------------------------------------- /NSObject+DGKVOBlocks.h: -------------------------------------------------------------------------------- 1 | //******************************************************************************* 2 | 3 | // Copyright (c) 2011 Danny Greg 4 | 5 | // Permission is hereby granted, free of charge, to any person obtaining a copy 6 | // of this software and associated documentation files (the "Software"), to deal 7 | // in the Software without restriction, including without limitation the rights 8 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | // copies of the Software, and to permit persons to whom the Software is 10 | // furnished to do so, subject to the following conditions: 11 | 12 | // The above copyright notice and this permission notice shall be included in 13 | // all copies or substantial portions of the Software. 14 | 15 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | // THE SOFTWARE. 22 | 23 | // Created by Danny Greg on 02/10/2011 24 | 25 | //******************************************************************************* 26 | 27 | #import 28 | 29 | typedef void (^DGKVOObserverBlock)(NSDictionary *change); 30 | 31 | @interface NSObject (DGKVOBlocks) 32 | 33 | - (id)dgkvo_addObserverForKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options queue:(NSOperationQueue *)queue usingBlock:(DGKVOObserverBlock)block; 34 | - (void)dgkvo_removeObserverWithIdentifier:(id)identifier; 35 | - (void)dgkvo_removeAllObservers; 36 | 37 | @end 38 | -------------------------------------------------------------------------------- /NSObject+DGKVOBlocks.m: -------------------------------------------------------------------------------- 1 | //******************************************************************************* 2 | 3 | // Copyright (c) 2011 Danny Greg 4 | 5 | // Permission is hereby granted, free of charge, to any person obtaining a copy 6 | // of this software and associated documentation files (the "Software"), to deal 7 | // in the Software without restriction, including without limitation the rights 8 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | // copies of the Software, and to permit persons to whom the Software is 10 | // furnished to do so, subject to the following conditions: 11 | 12 | // The above copyright notice and this permission notice shall be included in 13 | // all copies or substantial portions of the Software. 14 | 15 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | // THE SOFTWARE. 22 | 23 | // Created by Danny Greg on 02/10/2011 24 | 25 | //******************************************************************************* 26 | 27 | #import "NSObject+DGKVOBlocks.h" 28 | 29 | #import 30 | 31 | //*************************************************************************** 32 | 33 | NSString *DGKVOBlocksObservationContext = @"DGKVOBlocksObservationContext"; 34 | 35 | NSString *const DGKVOBlocksObserversAssociatedObjectsKey = @"DGKVOBlocksObserversAssociatedObjectsKey"; 36 | 37 | #if __has_feature(objc_arc) 38 | #define DGKVOBlocksObserversAssociatedObjectsKey (__bridge const void *)DGKVOBlocksObserversAssociatedObjectsKey 39 | #endif 40 | 41 | //*************************************************************************** 42 | 43 | @interface DGKVOBlocksObserver : NSObject { 44 | DGKVOObserverBlock _block; 45 | NSString *_keyPath; 46 | NSOperationQueue *_queue; 47 | } 48 | 49 | @property (copy) DGKVOObserverBlock block; 50 | @property (copy) NSString *keyPath; 51 | @property (retain) NSOperationQueue *queue; 52 | 53 | @end 54 | 55 | @implementation DGKVOBlocksObserver 56 | 57 | @synthesize block = _block; 58 | @synthesize keyPath = _keyPath; 59 | @synthesize queue = _queue; 60 | 61 | #if !__has_feature(objc_arc) 62 | - (void)dealloc 63 | { 64 | [_keyPath release]; 65 | [_queue release]; 66 | [_block release]; 67 | 68 | [super dealloc]; 69 | } 70 | #endif 71 | 72 | - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context 73 | { 74 | if (context == &DGKVOBlocksObservationContext) { 75 | if (self.queue == nil) { 76 | self.block(change); 77 | return; 78 | } 79 | 80 | NSDictionary *copiedChange = [change copy]; 81 | [self.queue addOperationWithBlock: ^ { 82 | self.block(copiedChange); 83 | }]; 84 | 85 | #if !__has_feature(objc_arc) 86 | [copiedChange release]; 87 | #endif 88 | 89 | } else { 90 | [super observeValueForKeyPath:keyPath ofObject:object change:change context:context]; 91 | } 92 | } 93 | 94 | @end 95 | 96 | //*************************************************************************** 97 | 98 | @interface NSObject (DGKVOBlocksProperties) 99 | 100 | @property (readonly) NSMutableArray *dgkvo_blockObservers; 101 | 102 | @end 103 | 104 | @implementation NSObject (DGKVOBlocksProperties) 105 | 106 | - (NSMutableArray *)dgkvo_blockObservers 107 | { 108 | @synchronized (self) { 109 | 110 | NSMutableArray *setDict = objc_getAssociatedObject(self, DGKVOBlocksObserversAssociatedObjectsKey); 111 | 112 | if (setDict == nil) { 113 | NSMutableArray *newSetDict = [NSMutableArray array]; 114 | 115 | objc_setAssociatedObject(self, DGKVOBlocksObserversAssociatedObjectsKey, newSetDict, OBJC_ASSOCIATION_RETAIN); 116 | 117 | return newSetDict; 118 | } 119 | 120 | return setDict; 121 | } 122 | } 123 | 124 | @end 125 | 126 | //*************************************************************************** 127 | 128 | @implementation NSObject (DGKVOBlocks) 129 | 130 | - (id)dgkvo_addObserverForKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options queue:(NSOperationQueue *)queue usingBlock:(DGKVOObserverBlock)block 131 | { 132 | NSAssert(block != nil, @"You cannot add a block observer without a block."); 133 | 134 | DGKVOBlocksObserver *newBlocksObserver = [[DGKVOBlocksObserver alloc] init]; 135 | newBlocksObserver.block = block; 136 | newBlocksObserver.keyPath = keyPath; 137 | newBlocksObserver.queue = queue; 138 | 139 | [self addObserver:newBlocksObserver forKeyPath:keyPath options:options context:&DGKVOBlocksObservationContext]; 140 | 141 | @synchronized (self.dgkvo_blockObservers) { 142 | [self.dgkvo_blockObservers addObject:newBlocksObserver]; 143 | } 144 | 145 | #if !__has_feature(objc_arc) 146 | [newBlocksObserver release]; 147 | #endif 148 | 149 | return newBlocksObserver; 150 | } 151 | 152 | - (void)dgkvo_removeObserverWithIdentifier:(id)identifier 153 | { 154 | [self removeObserver:identifier forKeyPath:[identifier keyPath]]; 155 | 156 | @synchronized (self.dgkvo_blockObservers) { 157 | [self.dgkvo_blockObservers removeObjectIdenticalTo:identifier]; 158 | } 159 | } 160 | 161 | - (void)dgkvo_removeAllObservers { 162 | @synchronized (self.dgkvo_blockObservers) { 163 | [self.dgkvo_blockObservers enumerateObjectsUsingBlock:^(id observer, NSUInteger idx, BOOL *stop) { 164 | [self removeObserver:observer forKeyPath:[observer keyPath]]; 165 | }]; 166 | [self.dgkvo_blockObservers removeAllObjects]; 167 | } 168 | } 169 | 170 | @end 171 | -------------------------------------------------------------------------------- /ReadMe.md: -------------------------------------------------------------------------------- 1 | #DGKVOBlocks 2 | 3 | A simple extension onto NSObject which allows you to observe a key path using a block rather than the, frankly dreadful, KVO API. 4 | 5 | ##Compatibility 6 | 7 | Compatible with ARC, retain/release or garbage collected code. --------------------------------------------------------------------------------