├── CMKVODemo.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcuserdata │ │ └── ChenMan.xcuserdatad │ │ └── IDEFindNavigatorScopes.plist └── xcuserdata │ └── ChenMan.xcuserdatad │ ├── xcdebugger │ └── Breakpoints_v2.xcbkptlist │ └── xcschemes │ └── xcschememanagement.plist ├── CMKVODemo ├── AppDelegate.h ├── AppDelegate.m ├── Assets.xcassets │ └── AppIcon.appiconset │ │ └── Contents.json ├── Base.lproj │ ├── LaunchScreen.storyboard │ └── Main.storyboard ├── CMKVOKit │ ├── NSObject+Block_KVO.h │ ├── NSObject+Block_KVO.m │ ├── NSObject+Delegate_KVO.h │ └── NSObject+Delegate_KVO.m ├── Info.plist ├── ObservedObject.h ├── ObservedObject.m ├── ViewController.h ├── ViewController.m └── main.m ├── CMKVODemoTests ├── CMKVODemoTests.m └── Info.plist ├── CMKVODemoUITests ├── CMKVODemoUITests.m └── Info.plist └── README.md /CMKVODemo.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 48; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | F7405ECD209C3C47002682F1 /* NSObject+Block_KVO.m in Sources */ = {isa = PBXBuildFile; fileRef = F7405ECC209C3C47002682F1 /* NSObject+Block_KVO.m */; }; 11 | F7B41E852088A19600D6C2CE /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = F7B41E842088A19600D6C2CE /* AppDelegate.m */; }; 12 | F7B41E882088A19600D6C2CE /* ViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = F7B41E872088A19600D6C2CE /* ViewController.m */; }; 13 | F7B41E8B2088A19600D6C2CE /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = F7B41E892088A19600D6C2CE /* Main.storyboard */; }; 14 | F7B41E8D2088A19600D6C2CE /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = F7B41E8C2088A19600D6C2CE /* Assets.xcassets */; }; 15 | F7B41E902088A19600D6C2CE /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = F7B41E8E2088A19600D6C2CE /* LaunchScreen.storyboard */; }; 16 | F7B41E932088A19600D6C2CE /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = F7B41E922088A19600D6C2CE /* main.m */; }; 17 | F7B41E9D2088A19600D6C2CE /* CMKVODemoTests.m in Sources */ = {isa = PBXBuildFile; fileRef = F7B41E9C2088A19600D6C2CE /* CMKVODemoTests.m */; }; 18 | F7B41EA82088A19600D6C2CE /* CMKVODemoUITests.m in Sources */ = {isa = PBXBuildFile; fileRef = F7B41EA72088A19600D6C2CE /* CMKVODemoUITests.m */; }; 19 | F7B41EB82088A1C000D6C2CE /* NSObject+Delegate_KVO.m in Sources */ = {isa = PBXBuildFile; fileRef = F7B41EB62088A1C000D6C2CE /* NSObject+Delegate_KVO.m */; }; 20 | F7B41EBB2088A1D200D6C2CE /* ObservedObject.m in Sources */ = {isa = PBXBuildFile; fileRef = F7B41EBA2088A1D200D6C2CE /* ObservedObject.m */; }; 21 | /* End PBXBuildFile section */ 22 | 23 | /* Begin PBXContainerItemProxy section */ 24 | F7B41E992088A19600D6C2CE /* PBXContainerItemProxy */ = { 25 | isa = PBXContainerItemProxy; 26 | containerPortal = F7B41E782088A19600D6C2CE /* Project object */; 27 | proxyType = 1; 28 | remoteGlobalIDString = F7B41E7F2088A19600D6C2CE; 29 | remoteInfo = CMKVODemo; 30 | }; 31 | F7B41EA42088A19600D6C2CE /* PBXContainerItemProxy */ = { 32 | isa = PBXContainerItemProxy; 33 | containerPortal = F7B41E782088A19600D6C2CE /* Project object */; 34 | proxyType = 1; 35 | remoteGlobalIDString = F7B41E7F2088A19600D6C2CE; 36 | remoteInfo = CMKVODemo; 37 | }; 38 | /* End PBXContainerItemProxy section */ 39 | 40 | /* Begin PBXFileReference section */ 41 | F7405ECB209C3C47002682F1 /* NSObject+Block_KVO.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "NSObject+Block_KVO.h"; sourceTree = ""; }; 42 | F7405ECC209C3C47002682F1 /* NSObject+Block_KVO.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "NSObject+Block_KVO.m"; sourceTree = ""; }; 43 | F7B41E802088A19600D6C2CE /* CMKVODemo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = CMKVODemo.app; sourceTree = BUILT_PRODUCTS_DIR; }; 44 | F7B41E832088A19600D6C2CE /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; 45 | F7B41E842088A19600D6C2CE /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; 46 | F7B41E862088A19600D6C2CE /* ViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ViewController.h; sourceTree = ""; }; 47 | F7B41E872088A19600D6C2CE /* ViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ViewController.m; sourceTree = ""; }; 48 | F7B41E8A2088A19600D6C2CE /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 49 | F7B41E8C2088A19600D6C2CE /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 50 | F7B41E8F2088A19600D6C2CE /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 51 | F7B41E912088A19600D6C2CE /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 52 | F7B41E922088A19600D6C2CE /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; 53 | F7B41E982088A19600D6C2CE /* CMKVODemoTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = CMKVODemoTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 54 | F7B41E9C2088A19600D6C2CE /* CMKVODemoTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = CMKVODemoTests.m; sourceTree = ""; }; 55 | F7B41E9E2088A19600D6C2CE /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 56 | F7B41EA32088A19600D6C2CE /* CMKVODemoUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = CMKVODemoUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 57 | F7B41EA72088A19600D6C2CE /* CMKVODemoUITests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = CMKVODemoUITests.m; sourceTree = ""; }; 58 | F7B41EA92088A19600D6C2CE /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 59 | F7B41EB62088A1C000D6C2CE /* NSObject+Delegate_KVO.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSObject+Delegate_KVO.m"; sourceTree = ""; }; 60 | F7B41EB72088A1C000D6C2CE /* NSObject+Delegate_KVO.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSObject+Delegate_KVO.h"; sourceTree = ""; }; 61 | F7B41EB92088A1D200D6C2CE /* ObservedObject.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ObservedObject.h; sourceTree = ""; }; 62 | F7B41EBA2088A1D200D6C2CE /* ObservedObject.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ObservedObject.m; sourceTree = ""; }; 63 | /* End PBXFileReference section */ 64 | 65 | /* Begin PBXFrameworksBuildPhase section */ 66 | F7B41E7D2088A19600D6C2CE /* Frameworks */ = { 67 | isa = PBXFrameworksBuildPhase; 68 | buildActionMask = 2147483647; 69 | files = ( 70 | ); 71 | runOnlyForDeploymentPostprocessing = 0; 72 | }; 73 | F7B41E952088A19600D6C2CE /* Frameworks */ = { 74 | isa = PBXFrameworksBuildPhase; 75 | buildActionMask = 2147483647; 76 | files = ( 77 | ); 78 | runOnlyForDeploymentPostprocessing = 0; 79 | }; 80 | F7B41EA02088A19600D6C2CE /* Frameworks */ = { 81 | isa = PBXFrameworksBuildPhase; 82 | buildActionMask = 2147483647; 83 | files = ( 84 | ); 85 | runOnlyForDeploymentPostprocessing = 0; 86 | }; 87 | /* End PBXFrameworksBuildPhase section */ 88 | 89 | /* Begin PBXGroup section */ 90 | F7B41E772088A19600D6C2CE = { 91 | isa = PBXGroup; 92 | children = ( 93 | F7B41E822088A19600D6C2CE /* CMKVODemo */, 94 | F7B41E9B2088A19600D6C2CE /* CMKVODemoTests */, 95 | F7B41EA62088A19600D6C2CE /* CMKVODemoUITests */, 96 | F7B41E812088A19600D6C2CE /* Products */, 97 | ); 98 | sourceTree = ""; 99 | }; 100 | F7B41E812088A19600D6C2CE /* Products */ = { 101 | isa = PBXGroup; 102 | children = ( 103 | F7B41E802088A19600D6C2CE /* CMKVODemo.app */, 104 | F7B41E982088A19600D6C2CE /* CMKVODemoTests.xctest */, 105 | F7B41EA32088A19600D6C2CE /* CMKVODemoUITests.xctest */, 106 | ); 107 | name = Products; 108 | sourceTree = ""; 109 | }; 110 | F7B41E822088A19600D6C2CE /* CMKVODemo */ = { 111 | isa = PBXGroup; 112 | children = ( 113 | F7B41EB52088A1A300D6C2CE /* CMKVOKit */, 114 | F7B41E832088A19600D6C2CE /* AppDelegate.h */, 115 | F7B41E842088A19600D6C2CE /* AppDelegate.m */, 116 | F7B41E862088A19600D6C2CE /* ViewController.h */, 117 | F7B41E872088A19600D6C2CE /* ViewController.m */, 118 | F7B41EB92088A1D200D6C2CE /* ObservedObject.h */, 119 | F7B41EBA2088A1D200D6C2CE /* ObservedObject.m */, 120 | F7B41E892088A19600D6C2CE /* Main.storyboard */, 121 | F7B41E8C2088A19600D6C2CE /* Assets.xcassets */, 122 | F7B41E8E2088A19600D6C2CE /* LaunchScreen.storyboard */, 123 | F7B41E912088A19600D6C2CE /* Info.plist */, 124 | F7B41E922088A19600D6C2CE /* main.m */, 125 | ); 126 | path = CMKVODemo; 127 | sourceTree = ""; 128 | }; 129 | F7B41E9B2088A19600D6C2CE /* CMKVODemoTests */ = { 130 | isa = PBXGroup; 131 | children = ( 132 | F7B41E9C2088A19600D6C2CE /* CMKVODemoTests.m */, 133 | F7B41E9E2088A19600D6C2CE /* Info.plist */, 134 | ); 135 | path = CMKVODemoTests; 136 | sourceTree = ""; 137 | }; 138 | F7B41EA62088A19600D6C2CE /* CMKVODemoUITests */ = { 139 | isa = PBXGroup; 140 | children = ( 141 | F7B41EA72088A19600D6C2CE /* CMKVODemoUITests.m */, 142 | F7B41EA92088A19600D6C2CE /* Info.plist */, 143 | ); 144 | path = CMKVODemoUITests; 145 | sourceTree = ""; 146 | }; 147 | F7B41EB52088A1A300D6C2CE /* CMKVOKit */ = { 148 | isa = PBXGroup; 149 | children = ( 150 | F7B41EB72088A1C000D6C2CE /* NSObject+Delegate_KVO.h */, 151 | F7B41EB62088A1C000D6C2CE /* NSObject+Delegate_KVO.m */, 152 | F7405ECB209C3C47002682F1 /* NSObject+Block_KVO.h */, 153 | F7405ECC209C3C47002682F1 /* NSObject+Block_KVO.m */, 154 | ); 155 | path = CMKVOKit; 156 | sourceTree = ""; 157 | }; 158 | /* End PBXGroup section */ 159 | 160 | /* Begin PBXNativeTarget section */ 161 | F7B41E7F2088A19600D6C2CE /* CMKVODemo */ = { 162 | isa = PBXNativeTarget; 163 | buildConfigurationList = F7B41EAC2088A19600D6C2CE /* Build configuration list for PBXNativeTarget "CMKVODemo" */; 164 | buildPhases = ( 165 | F7B41E7C2088A19600D6C2CE /* Sources */, 166 | F7B41E7D2088A19600D6C2CE /* Frameworks */, 167 | F7B41E7E2088A19600D6C2CE /* Resources */, 168 | ); 169 | buildRules = ( 170 | ); 171 | dependencies = ( 172 | ); 173 | name = CMKVODemo; 174 | productName = CMKVODemo; 175 | productReference = F7B41E802088A19600D6C2CE /* CMKVODemo.app */; 176 | productType = "com.apple.product-type.application"; 177 | }; 178 | F7B41E972088A19600D6C2CE /* CMKVODemoTests */ = { 179 | isa = PBXNativeTarget; 180 | buildConfigurationList = F7B41EAF2088A19600D6C2CE /* Build configuration list for PBXNativeTarget "CMKVODemoTests" */; 181 | buildPhases = ( 182 | F7B41E942088A19600D6C2CE /* Sources */, 183 | F7B41E952088A19600D6C2CE /* Frameworks */, 184 | F7B41E962088A19600D6C2CE /* Resources */, 185 | ); 186 | buildRules = ( 187 | ); 188 | dependencies = ( 189 | F7B41E9A2088A19600D6C2CE /* PBXTargetDependency */, 190 | ); 191 | name = CMKVODemoTests; 192 | productName = CMKVODemoTests; 193 | productReference = F7B41E982088A19600D6C2CE /* CMKVODemoTests.xctest */; 194 | productType = "com.apple.product-type.bundle.unit-test"; 195 | }; 196 | F7B41EA22088A19600D6C2CE /* CMKVODemoUITests */ = { 197 | isa = PBXNativeTarget; 198 | buildConfigurationList = F7B41EB22088A19600D6C2CE /* Build configuration list for PBXNativeTarget "CMKVODemoUITests" */; 199 | buildPhases = ( 200 | F7B41E9F2088A19600D6C2CE /* Sources */, 201 | F7B41EA02088A19600D6C2CE /* Frameworks */, 202 | F7B41EA12088A19600D6C2CE /* Resources */, 203 | ); 204 | buildRules = ( 205 | ); 206 | dependencies = ( 207 | F7B41EA52088A19600D6C2CE /* PBXTargetDependency */, 208 | ); 209 | name = CMKVODemoUITests; 210 | productName = CMKVODemoUITests; 211 | productReference = F7B41EA32088A19600D6C2CE /* CMKVODemoUITests.xctest */; 212 | productType = "com.apple.product-type.bundle.ui-testing"; 213 | }; 214 | /* End PBXNativeTarget section */ 215 | 216 | /* Begin PBXProject section */ 217 | F7B41E782088A19600D6C2CE /* Project object */ = { 218 | isa = PBXProject; 219 | attributes = { 220 | LastUpgradeCheck = 0920; 221 | ORGANIZATIONNAME = cimain; 222 | TargetAttributes = { 223 | F7B41E7F2088A19600D6C2CE = { 224 | CreatedOnToolsVersion = 9.2; 225 | ProvisioningStyle = Automatic; 226 | }; 227 | F7B41E972088A19600D6C2CE = { 228 | CreatedOnToolsVersion = 9.2; 229 | ProvisioningStyle = Automatic; 230 | TestTargetID = F7B41E7F2088A19600D6C2CE; 231 | }; 232 | F7B41EA22088A19600D6C2CE = { 233 | CreatedOnToolsVersion = 9.2; 234 | ProvisioningStyle = Automatic; 235 | TestTargetID = F7B41E7F2088A19600D6C2CE; 236 | }; 237 | }; 238 | }; 239 | buildConfigurationList = F7B41E7B2088A19600D6C2CE /* Build configuration list for PBXProject "CMKVODemo" */; 240 | compatibilityVersion = "Xcode 8.0"; 241 | developmentRegion = en; 242 | hasScannedForEncodings = 0; 243 | knownRegions = ( 244 | en, 245 | Base, 246 | ); 247 | mainGroup = F7B41E772088A19600D6C2CE; 248 | productRefGroup = F7B41E812088A19600D6C2CE /* Products */; 249 | projectDirPath = ""; 250 | projectRoot = ""; 251 | targets = ( 252 | F7B41E7F2088A19600D6C2CE /* CMKVODemo */, 253 | F7B41E972088A19600D6C2CE /* CMKVODemoTests */, 254 | F7B41EA22088A19600D6C2CE /* CMKVODemoUITests */, 255 | ); 256 | }; 257 | /* End PBXProject section */ 258 | 259 | /* Begin PBXResourcesBuildPhase section */ 260 | F7B41E7E2088A19600D6C2CE /* Resources */ = { 261 | isa = PBXResourcesBuildPhase; 262 | buildActionMask = 2147483647; 263 | files = ( 264 | F7B41E902088A19600D6C2CE /* LaunchScreen.storyboard in Resources */, 265 | F7B41E8D2088A19600D6C2CE /* Assets.xcassets in Resources */, 266 | F7B41E8B2088A19600D6C2CE /* Main.storyboard in Resources */, 267 | ); 268 | runOnlyForDeploymentPostprocessing = 0; 269 | }; 270 | F7B41E962088A19600D6C2CE /* Resources */ = { 271 | isa = PBXResourcesBuildPhase; 272 | buildActionMask = 2147483647; 273 | files = ( 274 | ); 275 | runOnlyForDeploymentPostprocessing = 0; 276 | }; 277 | F7B41EA12088A19600D6C2CE /* Resources */ = { 278 | isa = PBXResourcesBuildPhase; 279 | buildActionMask = 2147483647; 280 | files = ( 281 | ); 282 | runOnlyForDeploymentPostprocessing = 0; 283 | }; 284 | /* End PBXResourcesBuildPhase section */ 285 | 286 | /* Begin PBXSourcesBuildPhase section */ 287 | F7B41E7C2088A19600D6C2CE /* Sources */ = { 288 | isa = PBXSourcesBuildPhase; 289 | buildActionMask = 2147483647; 290 | files = ( 291 | F7B41E882088A19600D6C2CE /* ViewController.m in Sources */, 292 | F7B41E932088A19600D6C2CE /* main.m in Sources */, 293 | F7405ECD209C3C47002682F1 /* NSObject+Block_KVO.m in Sources */, 294 | F7B41EBB2088A1D200D6C2CE /* ObservedObject.m in Sources */, 295 | F7B41EB82088A1C000D6C2CE /* NSObject+Delegate_KVO.m in Sources */, 296 | F7B41E852088A19600D6C2CE /* AppDelegate.m in Sources */, 297 | ); 298 | runOnlyForDeploymentPostprocessing = 0; 299 | }; 300 | F7B41E942088A19600D6C2CE /* Sources */ = { 301 | isa = PBXSourcesBuildPhase; 302 | buildActionMask = 2147483647; 303 | files = ( 304 | F7B41E9D2088A19600D6C2CE /* CMKVODemoTests.m in Sources */, 305 | ); 306 | runOnlyForDeploymentPostprocessing = 0; 307 | }; 308 | F7B41E9F2088A19600D6C2CE /* Sources */ = { 309 | isa = PBXSourcesBuildPhase; 310 | buildActionMask = 2147483647; 311 | files = ( 312 | F7B41EA82088A19600D6C2CE /* CMKVODemoUITests.m in Sources */, 313 | ); 314 | runOnlyForDeploymentPostprocessing = 0; 315 | }; 316 | /* End PBXSourcesBuildPhase section */ 317 | 318 | /* Begin PBXTargetDependency section */ 319 | F7B41E9A2088A19600D6C2CE /* PBXTargetDependency */ = { 320 | isa = PBXTargetDependency; 321 | target = F7B41E7F2088A19600D6C2CE /* CMKVODemo */; 322 | targetProxy = F7B41E992088A19600D6C2CE /* PBXContainerItemProxy */; 323 | }; 324 | F7B41EA52088A19600D6C2CE /* PBXTargetDependency */ = { 325 | isa = PBXTargetDependency; 326 | target = F7B41E7F2088A19600D6C2CE /* CMKVODemo */; 327 | targetProxy = F7B41EA42088A19600D6C2CE /* PBXContainerItemProxy */; 328 | }; 329 | /* End PBXTargetDependency section */ 330 | 331 | /* Begin PBXVariantGroup section */ 332 | F7B41E892088A19600D6C2CE /* Main.storyboard */ = { 333 | isa = PBXVariantGroup; 334 | children = ( 335 | F7B41E8A2088A19600D6C2CE /* Base */, 336 | ); 337 | name = Main.storyboard; 338 | sourceTree = ""; 339 | }; 340 | F7B41E8E2088A19600D6C2CE /* LaunchScreen.storyboard */ = { 341 | isa = PBXVariantGroup; 342 | children = ( 343 | F7B41E8F2088A19600D6C2CE /* Base */, 344 | ); 345 | name = LaunchScreen.storyboard; 346 | sourceTree = ""; 347 | }; 348 | /* End PBXVariantGroup section */ 349 | 350 | /* Begin XCBuildConfiguration section */ 351 | F7B41EAA2088A19600D6C2CE /* Debug */ = { 352 | isa = XCBuildConfiguration; 353 | buildSettings = { 354 | ALWAYS_SEARCH_USER_PATHS = NO; 355 | CLANG_ANALYZER_NONNULL = YES; 356 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 357 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 358 | CLANG_CXX_LIBRARY = "libc++"; 359 | CLANG_ENABLE_MODULES = YES; 360 | CLANG_ENABLE_OBJC_ARC = YES; 361 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 362 | CLANG_WARN_BOOL_CONVERSION = YES; 363 | CLANG_WARN_COMMA = YES; 364 | CLANG_WARN_CONSTANT_CONVERSION = YES; 365 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 366 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 367 | CLANG_WARN_EMPTY_BODY = YES; 368 | CLANG_WARN_ENUM_CONVERSION = YES; 369 | CLANG_WARN_INFINITE_RECURSION = YES; 370 | CLANG_WARN_INT_CONVERSION = YES; 371 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 372 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 373 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 374 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 375 | CLANG_WARN_STRICT_PROTOTYPES = YES; 376 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 377 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 378 | CLANG_WARN_UNREACHABLE_CODE = YES; 379 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 380 | CODE_SIGN_IDENTITY = "iPhone Developer"; 381 | COPY_PHASE_STRIP = NO; 382 | DEBUG_INFORMATION_FORMAT = dwarf; 383 | ENABLE_STRICT_OBJC_MSGSEND = YES; 384 | ENABLE_TESTABILITY = YES; 385 | GCC_C_LANGUAGE_STANDARD = gnu11; 386 | GCC_DYNAMIC_NO_PIC = NO; 387 | GCC_NO_COMMON_BLOCKS = YES; 388 | GCC_OPTIMIZATION_LEVEL = 0; 389 | GCC_PREPROCESSOR_DEFINITIONS = ( 390 | "DEBUG=1", 391 | "$(inherited)", 392 | ); 393 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 394 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 395 | GCC_WARN_UNDECLARED_SELECTOR = YES; 396 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 397 | GCC_WARN_UNUSED_FUNCTION = YES; 398 | GCC_WARN_UNUSED_VARIABLE = YES; 399 | IPHONEOS_DEPLOYMENT_TARGET = 11.2; 400 | MTL_ENABLE_DEBUG_INFO = YES; 401 | ONLY_ACTIVE_ARCH = YES; 402 | SDKROOT = iphoneos; 403 | }; 404 | name = Debug; 405 | }; 406 | F7B41EAB2088A19600D6C2CE /* Release */ = { 407 | isa = XCBuildConfiguration; 408 | buildSettings = { 409 | ALWAYS_SEARCH_USER_PATHS = NO; 410 | CLANG_ANALYZER_NONNULL = YES; 411 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 412 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 413 | CLANG_CXX_LIBRARY = "libc++"; 414 | CLANG_ENABLE_MODULES = YES; 415 | CLANG_ENABLE_OBJC_ARC = YES; 416 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 417 | CLANG_WARN_BOOL_CONVERSION = YES; 418 | CLANG_WARN_COMMA = YES; 419 | CLANG_WARN_CONSTANT_CONVERSION = YES; 420 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 421 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 422 | CLANG_WARN_EMPTY_BODY = YES; 423 | CLANG_WARN_ENUM_CONVERSION = YES; 424 | CLANG_WARN_INFINITE_RECURSION = YES; 425 | CLANG_WARN_INT_CONVERSION = YES; 426 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 427 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 428 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 429 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 430 | CLANG_WARN_STRICT_PROTOTYPES = YES; 431 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 432 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 433 | CLANG_WARN_UNREACHABLE_CODE = YES; 434 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 435 | CODE_SIGN_IDENTITY = "iPhone Developer"; 436 | COPY_PHASE_STRIP = NO; 437 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 438 | ENABLE_NS_ASSERTIONS = NO; 439 | ENABLE_STRICT_OBJC_MSGSEND = YES; 440 | GCC_C_LANGUAGE_STANDARD = gnu11; 441 | GCC_NO_COMMON_BLOCKS = YES; 442 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 443 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 444 | GCC_WARN_UNDECLARED_SELECTOR = YES; 445 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 446 | GCC_WARN_UNUSED_FUNCTION = YES; 447 | GCC_WARN_UNUSED_VARIABLE = YES; 448 | IPHONEOS_DEPLOYMENT_TARGET = 11.2; 449 | MTL_ENABLE_DEBUG_INFO = NO; 450 | SDKROOT = iphoneos; 451 | VALIDATE_PRODUCT = YES; 452 | }; 453 | name = Release; 454 | }; 455 | F7B41EAD2088A19600D6C2CE /* Debug */ = { 456 | isa = XCBuildConfiguration; 457 | buildSettings = { 458 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 459 | CODE_SIGN_STYLE = Automatic; 460 | DEVELOPMENT_TEAM = G9A57BVG7J; 461 | INFOPLIST_FILE = CMKVODemo/Info.plist; 462 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 463 | PRODUCT_BUNDLE_IDENTIFIER = com.limbank.ddd.CMKVODemo; 464 | PRODUCT_NAME = "$(TARGET_NAME)"; 465 | TARGETED_DEVICE_FAMILY = "1,2"; 466 | }; 467 | name = Debug; 468 | }; 469 | F7B41EAE2088A19600D6C2CE /* Release */ = { 470 | isa = XCBuildConfiguration; 471 | buildSettings = { 472 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 473 | CODE_SIGN_STYLE = Automatic; 474 | DEVELOPMENT_TEAM = G9A57BVG7J; 475 | INFOPLIST_FILE = CMKVODemo/Info.plist; 476 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 477 | PRODUCT_BUNDLE_IDENTIFIER = com.limbank.ddd.CMKVODemo; 478 | PRODUCT_NAME = "$(TARGET_NAME)"; 479 | TARGETED_DEVICE_FAMILY = "1,2"; 480 | }; 481 | name = Release; 482 | }; 483 | F7B41EB02088A19600D6C2CE /* Debug */ = { 484 | isa = XCBuildConfiguration; 485 | buildSettings = { 486 | BUNDLE_LOADER = "$(TEST_HOST)"; 487 | CODE_SIGN_STYLE = Automatic; 488 | DEVELOPMENT_TEAM = G9A57BVG7J; 489 | INFOPLIST_FILE = CMKVODemoTests/Info.plist; 490 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 491 | PRODUCT_BUNDLE_IDENTIFIER = com.limbank.ddd.CMKVODemoTests; 492 | PRODUCT_NAME = "$(TARGET_NAME)"; 493 | TARGETED_DEVICE_FAMILY = "1,2"; 494 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/CMKVODemo.app/CMKVODemo"; 495 | }; 496 | name = Debug; 497 | }; 498 | F7B41EB12088A19600D6C2CE /* Release */ = { 499 | isa = XCBuildConfiguration; 500 | buildSettings = { 501 | BUNDLE_LOADER = "$(TEST_HOST)"; 502 | CODE_SIGN_STYLE = Automatic; 503 | DEVELOPMENT_TEAM = G9A57BVG7J; 504 | INFOPLIST_FILE = CMKVODemoTests/Info.plist; 505 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 506 | PRODUCT_BUNDLE_IDENTIFIER = com.limbank.ddd.CMKVODemoTests; 507 | PRODUCT_NAME = "$(TARGET_NAME)"; 508 | TARGETED_DEVICE_FAMILY = "1,2"; 509 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/CMKVODemo.app/CMKVODemo"; 510 | }; 511 | name = Release; 512 | }; 513 | F7B41EB32088A19600D6C2CE /* Debug */ = { 514 | isa = XCBuildConfiguration; 515 | buildSettings = { 516 | CODE_SIGN_STYLE = Automatic; 517 | DEVELOPMENT_TEAM = G9A57BVG7J; 518 | INFOPLIST_FILE = CMKVODemoUITests/Info.plist; 519 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 520 | PRODUCT_BUNDLE_IDENTIFIER = com.limbank.ddd.CMKVODemoUITests; 521 | PRODUCT_NAME = "$(TARGET_NAME)"; 522 | TARGETED_DEVICE_FAMILY = "1,2"; 523 | TEST_TARGET_NAME = CMKVODemo; 524 | }; 525 | name = Debug; 526 | }; 527 | F7B41EB42088A19600D6C2CE /* Release */ = { 528 | isa = XCBuildConfiguration; 529 | buildSettings = { 530 | CODE_SIGN_STYLE = Automatic; 531 | DEVELOPMENT_TEAM = G9A57BVG7J; 532 | INFOPLIST_FILE = CMKVODemoUITests/Info.plist; 533 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 534 | PRODUCT_BUNDLE_IDENTIFIER = com.limbank.ddd.CMKVODemoUITests; 535 | PRODUCT_NAME = "$(TARGET_NAME)"; 536 | TARGETED_DEVICE_FAMILY = "1,2"; 537 | TEST_TARGET_NAME = CMKVODemo; 538 | }; 539 | name = Release; 540 | }; 541 | /* End XCBuildConfiguration section */ 542 | 543 | /* Begin XCConfigurationList section */ 544 | F7B41E7B2088A19600D6C2CE /* Build configuration list for PBXProject "CMKVODemo" */ = { 545 | isa = XCConfigurationList; 546 | buildConfigurations = ( 547 | F7B41EAA2088A19600D6C2CE /* Debug */, 548 | F7B41EAB2088A19600D6C2CE /* Release */, 549 | ); 550 | defaultConfigurationIsVisible = 0; 551 | defaultConfigurationName = Release; 552 | }; 553 | F7B41EAC2088A19600D6C2CE /* Build configuration list for PBXNativeTarget "CMKVODemo" */ = { 554 | isa = XCConfigurationList; 555 | buildConfigurations = ( 556 | F7B41EAD2088A19600D6C2CE /* Debug */, 557 | F7B41EAE2088A19600D6C2CE /* Release */, 558 | ); 559 | defaultConfigurationIsVisible = 0; 560 | defaultConfigurationName = Release; 561 | }; 562 | F7B41EAF2088A19600D6C2CE /* Build configuration list for PBXNativeTarget "CMKVODemoTests" */ = { 563 | isa = XCConfigurationList; 564 | buildConfigurations = ( 565 | F7B41EB02088A19600D6C2CE /* Debug */, 566 | F7B41EB12088A19600D6C2CE /* Release */, 567 | ); 568 | defaultConfigurationIsVisible = 0; 569 | defaultConfigurationName = Release; 570 | }; 571 | F7B41EB22088A19600D6C2CE /* Build configuration list for PBXNativeTarget "CMKVODemoUITests" */ = { 572 | isa = XCConfigurationList; 573 | buildConfigurations = ( 574 | F7B41EB32088A19600D6C2CE /* Debug */, 575 | F7B41EB42088A19600D6C2CE /* Release */, 576 | ); 577 | defaultConfigurationIsVisible = 0; 578 | defaultConfigurationName = Release; 579 | }; 580 | /* End XCConfigurationList section */ 581 | }; 582 | rootObject = F7B41E782088A19600D6C2CE /* Project object */; 583 | } 584 | -------------------------------------------------------------------------------- /CMKVODemo.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /CMKVODemo.xcodeproj/project.xcworkspace/xcuserdata/ChenMan.xcuserdatad/IDEFindNavigatorScopes.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /CMKVODemo.xcodeproj/xcuserdata/ChenMan.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 8 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /CMKVODemo.xcodeproj/xcuserdata/ChenMan.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | CMKVODemo.xcscheme 8 | 9 | orderHint 10 | 0 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /CMKVODemo/AppDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.h 3 | // CMKVODemo 4 | // 5 | // Created by ChenMan on 2018/4/19. 6 | // Copyright © 2018年 cimain. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface AppDelegate : UIResponder 12 | 13 | @property (strong, nonatomic) UIWindow *window; 14 | 15 | 16 | @end 17 | 18 | -------------------------------------------------------------------------------- /CMKVODemo/AppDelegate.m: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.m 3 | // CMKVODemo 4 | // 5 | // Created by ChenMan on 2018/4/19. 6 | // Copyright © 2018年 cimain. All rights reserved. 7 | // 8 | 9 | #import "AppDelegate.h" 10 | 11 | @interface AppDelegate () 12 | 13 | @end 14 | 15 | @implementation AppDelegate 16 | 17 | 18 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { 19 | // Override point for customization after application launch. 20 | return YES; 21 | } 22 | 23 | 24 | - (void)applicationWillResignActive:(UIApplication *)application { 25 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. 26 | // Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game. 27 | } 28 | 29 | 30 | - (void)applicationDidEnterBackground:(UIApplication *)application { 31 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. 32 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 33 | } 34 | 35 | 36 | - (void)applicationWillEnterForeground:(UIApplication *)application { 37 | // Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background. 38 | } 39 | 40 | 41 | - (void)applicationDidBecomeActive:(UIApplication *)application { 42 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. 43 | } 44 | 45 | 46 | - (void)applicationWillTerminate:(UIApplication *)application { 47 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 48 | } 49 | 50 | 51 | @end 52 | -------------------------------------------------------------------------------- /CMKVODemo/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "20x20", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "20x20", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "29x29", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "29x29", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "40x40", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "40x40", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "size" : "60x60", 36 | "scale" : "2x" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "size" : "60x60", 41 | "scale" : "3x" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "size" : "20x20", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "size" : "20x20", 51 | "scale" : "2x" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "size" : "29x29", 56 | "scale" : "1x" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "size" : "29x29", 61 | "scale" : "2x" 62 | }, 63 | { 64 | "idiom" : "ipad", 65 | "size" : "40x40", 66 | "scale" : "1x" 67 | }, 68 | { 69 | "idiom" : "ipad", 70 | "size" : "40x40", 71 | "scale" : "2x" 72 | }, 73 | { 74 | "idiom" : "ipad", 75 | "size" : "76x76", 76 | "scale" : "1x" 77 | }, 78 | { 79 | "idiom" : "ipad", 80 | "size" : "76x76", 81 | "scale" : "2x" 82 | }, 83 | { 84 | "idiom" : "ipad", 85 | "size" : "83.5x83.5", 86 | "scale" : "2x" 87 | } 88 | ], 89 | "info" : { 90 | "version" : 1, 91 | "author" : "xcode" 92 | } 93 | } -------------------------------------------------------------------------------- /CMKVODemo/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /CMKVODemo/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /CMKVODemo/CMKVOKit/NSObject+Block_KVO.h: -------------------------------------------------------------------------------- 1 | // 2 | // NSObject+Block_KVO.h 3 | // CMKVODemo 4 | // 5 | // Created by ChenMan on 2018/5/4. 6 | // Copyright © 2018年 cimain. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | typedef void (^CM_ObservingHandler) (id observedObject, NSString * observedKey, id oldValue, id newValue); 12 | 13 | @interface NSObject (Block_KVO) 14 | 15 | /** 16 | * method stead of traditional addObserver API 17 | * 18 | * @param object object as observer 19 | * @param key attribute of object to be observed 20 | * @param observedHandler method to be invoked when notification be observed has changed 21 | */ 22 | - (void)CM_addObserver: (NSObject *)object forKey: (NSString *)key withBlock: (CM_ObservingHandler)observedHandler; 23 | 24 | 25 | /** 26 | * remove the observe 27 | * 28 | * @param object object as observer 29 | * @param key attribute observed will remove the observe 30 | */ 31 | - (void)CM_removeObserver: (NSObject *)object forKey: (NSString *)key; 32 | 33 | @end 34 | -------------------------------------------------------------------------------- /CMKVODemo/CMKVOKit/NSObject+Block_KVO.m: -------------------------------------------------------------------------------- 1 | // 2 | // NSObject+Block_KVO.m 3 | // CMKVODemo 4 | // 5 | // Created by ChenMan on 2018/5/4. 6 | // Copyright © 2018年 cimain. All rights reserved. 7 | // 8 | 9 | #import "NSObject+Block_KVO.h" 10 | #import 11 | #import 12 | 13 | //as prefix string of kvo class 14 | static NSString * const kCMkvoClassPrefix_for_Block = @"CMObserver_"; 15 | static NSString * const kCMkvoAssiociateObserver_for_Block = @"CMAssiociateObserver"; 16 | 17 | @interface CM_ObserverInfo_for_Block : NSObject 18 | 19 | @property (nonatomic, weak) NSObject * observer; 20 | @property (nonatomic, copy) NSString * key; 21 | @property (nonatomic, copy) CM_ObservingHandler handler; 22 | 23 | @end 24 | 25 | 26 | @implementation CM_ObserverInfo_for_Block 27 | 28 | - (instancetype)initWithObserver: (NSObject *)observer forKey: (NSString *)key observeHandler: (CM_ObservingHandler)handler 29 | { 30 | if (self = [super init]) { 31 | 32 | _observer = observer; 33 | self.key = key; 34 | self.handler = handler; 35 | } 36 | return self; 37 | } 38 | 39 | @end 40 | 41 | 42 | #pragma mark -- Transform setter or getter to each other Methods 43 | static NSString * setterForGetter(NSString * getter) 44 | { 45 | if (getter.length <= 0) { return nil; } 46 | NSString * firstString = [[getter substringToIndex: 1] uppercaseString]; 47 | NSString * leaveString = [getter substringFromIndex: 1]; 48 | 49 | return [NSString stringWithFormat: @"set%@%@:", firstString, leaveString]; 50 | } 51 | 52 | 53 | static NSString * getterForSetter(NSString * setter) 54 | { 55 | if (setter.length <= 0 || ![setter hasPrefix: @"set"] || ![setter hasSuffix: @":"]) { 56 | 57 | return nil; 58 | } 59 | 60 | NSRange range = NSMakeRange(3, setter.length - 4); 61 | NSString * getter = [setter substringWithRange: range]; 62 | 63 | NSString * firstString = [[getter substringToIndex: 1] lowercaseString]; 64 | getter = [getter stringByReplacingCharactersInRange: NSMakeRange(0, 1) withString: firstString]; 65 | 66 | return getter; 67 | } 68 | 69 | 70 | #pragma mark -- Override setter and getter Methods 71 | static void KVO_setter(id self, SEL _cmd, id newValue) 72 | { 73 | NSString * setterName = NSStringFromSelector(_cmd); 74 | NSString * getterName = getterForSetter(setterName); 75 | if (!getterName) { 76 | @throw [NSException exceptionWithName: NSInvalidArgumentException reason: [NSString stringWithFormat: @"unrecognized selector sent to instance %p", self] userInfo: nil]; 77 | return; 78 | } 79 | 80 | id oldValue = [self valueForKey: getterName]; 81 | struct objc_super superClass = { 82 | .receiver = self, 83 | .super_class = class_getSuperclass(object_getClass(self)) 84 | }; 85 | 86 | [self willChangeValueForKey: getterName]; 87 | void (*objc_msgSendSuperKVO)(void *, SEL, id) = (void *)objc_msgSendSuper; 88 | objc_msgSendSuperKVO(&superClass, _cmd, newValue); 89 | [self didChangeValueForKey: getterName]; 90 | 91 | //获取所有监听回调对象进行回调 92 | NSMutableArray * observers = objc_getAssociatedObject(self, (__bridge const void *)kCMkvoAssiociateObserver_for_Block); 93 | for (CM_ObserverInfo_for_Block * info in observers) { 94 | if ([info.key isEqualToString: getterName]) { 95 | dispatch_async(dispatch_queue_create(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ 96 | info.handler(self, getterName, oldValue, newValue); 97 | }); 98 | } 99 | } 100 | } 101 | 102 | 103 | static Class kvo_Class(id self) 104 | { 105 | return class_getSuperclass(object_getClass(self)); 106 | } 107 | 108 | 109 | 110 | #pragma mark -- NSObject Category(KVO Reconstruct) 111 | @implementation NSObject (Block_KVO) 112 | 113 | - (void)CM_addObserver:(NSObject *)observer forKey:(NSString *)key withBlock:(CM_ObservingHandler)observedHandler 114 | { 115 | //step 1 get setter method, if not, throw exception 116 | SEL setterSelector = NSSelectorFromString(setterForGetter(key)); 117 | Method setterMethod = class_getInstanceMethod([self class], setterSelector); 118 | if (!setterMethod) { 119 | @throw [NSException exceptionWithName: NSInvalidArgumentException reason: [NSString stringWithFormat: @"unrecognized selector sent to instance %@", self] userInfo: nil]; 120 | return; 121 | } 122 | 123 | //自己的类作为被观察者类 124 | Class observedClass = object_getClass(self); 125 | NSString * className = NSStringFromClass(observedClass); 126 | 127 | //如果被监听者没有CMObserver_,那么判断是否需要创建新类 128 | if (![className hasPrefix: kCMkvoClassPrefix_for_Block]) { 129 | //【代码①】 130 | observedClass = [self createKVOClassWithOriginalClassName: className]; 131 | //【API注解①】 132 | object_setClass(self, observedClass); 133 | } 134 | 135 | //add kvo setter method if its class(or superclass)hasn't implement setter 136 | if (![self hasSelector: setterSelector]) { 137 | const char * types = method_getTypeEncoding(setterMethod); 138 | //【代码②】 139 | class_addMethod(observedClass, setterSelector, (IMP)KVO_setter, types); 140 | } 141 | 142 | 143 | //add this observation info to saved new observer 144 | //【代码③】 145 | CM_ObserverInfo_for_Block * newInfo = [[CM_ObserverInfo_for_Block alloc] initWithObserver: observer forKey: key observeHandler: observedHandler]; 146 | 147 | //【代码④】【API注解③】 148 | NSMutableArray * observers = objc_getAssociatedObject(self, (__bridge void *)kCMkvoAssiociateObserver_for_Block); 149 | 150 | if (!observers) { 151 | observers = [NSMutableArray array]; 152 | objc_setAssociatedObject(self, (__bridge void *)kCMkvoAssiociateObserver_for_Block, observers, OBJC_ASSOCIATION_RETAIN_NONATOMIC); 153 | } 154 | [observers addObject: newInfo]; 155 | } 156 | 157 | 158 | - (void)CM_removeObserver:(NSObject *)object forKey:(NSString *)key 159 | { 160 | NSMutableArray * observers = objc_getAssociatedObject(self, (__bridge void *)kCMkvoAssiociateObserver_for_Block); 161 | 162 | CM_ObserverInfo_for_Block * observerRemoved = nil; 163 | for (CM_ObserverInfo_for_Block * observerInfo in observers) { 164 | 165 | if (observerInfo.observer == object && [observerInfo.key isEqualToString: key]) { 166 | 167 | observerRemoved = observerInfo; 168 | break; 169 | } 170 | } 171 | [observers removeObject: observerRemoved]; 172 | } 173 | 174 | 175 | - (Class)createKVOClassWithOriginalClassName: (NSString *)className 176 | { 177 | NSString * kvoClassName = [kCMkvoClassPrefix_for_Block stringByAppendingString: className]; 178 | Class observedClass = NSClassFromString(kvoClassName); 179 | 180 | if (observedClass) { return observedClass; } 181 | 182 | //创建新类,并且添加CMObserver_为类名新前缀 183 | Class originalClass = object_getClass(self); 184 | Class kvoClass = objc_allocateClassPair(originalClass, kvoClassName.UTF8String, 0); 185 | 186 | //获取监听对象的class方法实现代码,然后替换新建类的class实现 187 | Method classMethod = class_getInstanceMethod(originalClass, @selector(class)); 188 | const char * types = method_getTypeEncoding(classMethod); 189 | class_addMethod(kvoClass, @selector(class), (IMP)kvo_Class, types); 190 | objc_registerClassPair(kvoClass); 191 | return kvoClass; 192 | } 193 | 194 | 195 | - (BOOL)hasSelector: (SEL)selector 196 | { 197 | Class observedClass = object_getClass(self); 198 | unsigned int methodCount = 0; 199 | Method * methodList = class_copyMethodList(observedClass, &methodCount); 200 | for (int i = 0; i < methodCount; i++) { 201 | 202 | SEL thisSelector = method_getName(methodList[i]); 203 | if (thisSelector == selector) { 204 | 205 | free(methodList); 206 | return YES; 207 | } 208 | } 209 | 210 | free(methodList); 211 | return NO; 212 | } 213 | 214 | 215 | #pragma mark -- Debug Method 216 | //static NSArray * ClassMethodsName(Class class) 217 | //{ 218 | // NSMutableArray * methodsArr = [NSMutableArray array]; 219 | // 220 | // unsigned methodCount = 0; 221 | // Method * methodList = class_copyMethodList(class, &methodCount); 222 | // for (int i = 0; i < methodCount; i++) { 223 | // 224 | // [methodsArr addObject: NSStringFromSelector(method_getName(methodList[i]))]; 225 | // } 226 | // free(methodList); 227 | // 228 | // return methodsArr; 229 | //} 230 | 231 | @end 232 | -------------------------------------------------------------------------------- /CMKVODemo/CMKVOKit/NSObject+Delegate_KVO.h: -------------------------------------------------------------------------------- 1 | // 2 | // NSObject+Delegate_KVO.h 3 | // CM_KeyValueObserveDemo 4 | // 5 | 6 | // Created by ChenMan on 2018/4/19. 7 | // Copyright © 2018年 cimain. All rights reserved. 8 | // 9 | 10 | 11 | #import 12 | 13 | @protocol ObserverDelegate 14 | 15 | @optional 16 | /** 17 | 回调 18 | 19 | @param keyPath 属性名称 20 | @param object 被观察的对象 21 | */ 22 | -(void)CM_ObserveValueForKeyPath:(NSString *)keyPath ofObject:(id)object oldValue:(id) oldValue newValue:(id)newValue; 23 | 24 | 25 | @end 26 | 27 | @interface NSObject (Delegate_KVO) 28 | 29 | /** 30 | * method stead of traditional addObserver API 31 | * 32 | * @param object object as observer 33 | * @param key attribute of object to be observed 34 | */ 35 | - (void)CM_addObserver: (NSObject *)object forKey: (NSString *)key; 36 | 37 | 38 | /** 39 | * remove the observe 40 | * 41 | * @param object object as observer 42 | * @param key attribute observed will remove the observe 43 | */ 44 | - (void)CM_removeObserver: (NSObject *)object forKey: (NSString *)key; 45 | 46 | 47 | @end 48 | -------------------------------------------------------------------------------- /CMKVODemo/CMKVOKit/NSObject+Delegate_KVO.m: -------------------------------------------------------------------------------- 1 | // 2 | // NSObject+Delegate_KVO.m 3 | // CM_KeyValueObserveDemo 4 | // 5 | // Created by ChenMan on 2018/4/19. 6 | // Copyright © 2018年 cimain. All rights reserved. 7 | // 8 | 9 | #import "NSObject+Delegate_KVO.h" 10 | #import 11 | #import 12 | 13 | //as prefix string of kvo class 14 | static NSString * const kCMkvoClassPrefix = @"CMObserver_"; 15 | static NSString * const kCMkvoAssiociateObserver = @"CMAssiociateObserver"; 16 | 17 | @interface CM_ObserverInfo : NSObject 18 | 19 | @property (nonatomic, weak) NSObject * observer; 20 | @property (nonatomic, copy) NSString * key; 21 | @property (nonatomic, assign) id observerDelegate; 22 | 23 | @end 24 | 25 | 26 | @implementation CM_ObserverInfo 27 | 28 | - (instancetype)initWithObserver: (NSObject *)observer forKey: (NSString *)key 29 | { 30 | if (self = [super init]) { 31 | _observer = observer; 32 | self.key = key; 33 | self.observerDelegate = (id)observer; 34 | } 35 | return self; 36 | } 37 | 38 | @end 39 | 40 | 41 | #pragma mark -- Transform setter or getter to each other Methods 42 | static NSString * setterForGetter(NSString * getter) 43 | { 44 | if (getter.length <= 0) { return nil; } 45 | NSString * firstString = [[getter substringToIndex: 1] uppercaseString]; 46 | NSString * leaveString = [getter substringFromIndex: 1]; 47 | 48 | return [NSString stringWithFormat: @"set%@%@:", firstString, leaveString]; 49 | } 50 | 51 | 52 | static NSString * getterForSetter(NSString * setter) 53 | { 54 | if (setter.length <= 0 || ![setter hasPrefix: @"set"] || ![setter hasSuffix: @":"]) { 55 | 56 | return nil; 57 | } 58 | 59 | NSRange range = NSMakeRange(3, setter.length - 4); 60 | NSString * getter = [setter substringWithRange: range]; 61 | 62 | NSString * firstString = [[getter substringToIndex: 1] lowercaseString]; 63 | getter = [getter stringByReplacingCharactersInRange: NSMakeRange(0, 1) withString: firstString]; 64 | 65 | return getter; 66 | } 67 | 68 | 69 | #pragma mark -- Override setter and getter Methods 70 | static void KVO_setter(id self, SEL _cmd, id newValue) 71 | { 72 | NSString * setterName = NSStringFromSelector(_cmd); 73 | NSString * getterName = getterForSetter(setterName); 74 | if (!getterName) { 75 | @throw [NSException exceptionWithName: NSInvalidArgumentException reason: [NSString stringWithFormat: @"unrecognized selector sent to instance %p", self] userInfo: nil]; 76 | return; 77 | } 78 | 79 | id oldValue = [self valueForKey: getterName]; 80 | struct objc_super superClass = { 81 | .receiver = self, 82 | .super_class = class_getSuperclass(object_getClass(self)) 83 | }; 84 | 85 | [self willChangeValueForKey: getterName]; 86 | void (*objc_msgSendSuperKVO)(void *, SEL, id) = (void *)objc_msgSendSuper; 87 | objc_msgSendSuperKVO(&superClass, _cmd, newValue); 88 | [self didChangeValueForKey: getterName]; 89 | 90 | //获取所有监听回调对象进行回调 91 | NSMutableArray * observers = objc_getAssociatedObject(self, (__bridge const void *)kCMkvoAssiociateObserver); 92 | for (CM_ObserverInfo * info in observers) { 93 | if ([info.key isEqualToString: getterName]) { 94 | dispatch_async(dispatch_queue_create(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ 95 | 96 | if ([info.observerDelegate respondsToSelector:@selector(CM_ObserveValueForKeyPath: ofObject:oldValue: newValue:)]){ 97 | [info.observerDelegate CM_ObserveValueForKeyPath:getterName ofObject:self oldValue:oldValue newValue:newValue]; 98 | } 99 | }); 100 | } 101 | } 102 | } 103 | 104 | 105 | static Class kvo_Class(id self) 106 | { 107 | return class_getSuperclass(object_getClass(self)); 108 | } 109 | 110 | 111 | #pragma mark -- NSObject Category(KVO Reconstruct) 112 | @implementation NSObject (Delegate_KVO) 113 | 114 | - (void)CM_addObserver:(NSObject *)observer forKey:(NSString *)key 115 | { 116 | //step 1 get setter method, if not, throw exception 117 | SEL setterSelector = NSSelectorFromString(setterForGetter(key)); 118 | Method setterMethod = class_getInstanceMethod([self class], setterSelector); 119 | if (!setterMethod) { 120 | @throw [NSException exceptionWithName: NSInvalidArgumentException reason: [NSString stringWithFormat: @"unrecognized selector sent to instance %@", self] userInfo: nil]; 121 | return; 122 | } 123 | 124 | // 125 | Class observedClass = object_getClass(self); 126 | NSString * className = NSStringFromClass(observedClass); 127 | 128 | //如果被监听者没有CMObserver_,那么判断是否需要创建新类 129 | if (![className hasPrefix: kCMkvoClassPrefix]) { 130 | observedClass = [self createKVOClassWithOriginalClassName: className]; 131 | object_setClass(self, observedClass); 132 | } 133 | 134 | //add kvo setter method if its class(or superclass)hasn't implement setter 135 | if (![self hasSelector: setterSelector]) { 136 | const char * types = method_getTypeEncoding(setterMethod); 137 | class_addMethod(observedClass, setterSelector, (IMP)KVO_setter, types); 138 | } 139 | 140 | 141 | //add this observation info to saved new observer 142 | CM_ObserverInfo * newInfo = [[CM_ObserverInfo alloc] initWithObserver: observer forKey: key]; 143 | 144 | NSMutableArray * observers = objc_getAssociatedObject(self, (__bridge void *)kCMkvoAssiociateObserver); 145 | if (!observers) { 146 | observers = [NSMutableArray array]; 147 | objc_setAssociatedObject(self, (__bridge void *)kCMkvoAssiociateObserver, observers, OBJC_ASSOCIATION_RETAIN_NONATOMIC); 148 | } 149 | [observers addObject: newInfo]; 150 | } 151 | 152 | 153 | - (void)CM_removeObserver:(NSObject *)object forKey:(NSString *)key 154 | { 155 | NSMutableArray * observers = objc_getAssociatedObject(self, (__bridge void *)kCMkvoAssiociateObserver); 156 | 157 | CM_ObserverInfo * observerRemoved = nil; 158 | for (CM_ObserverInfo * observerInfo in observers) { 159 | 160 | if (observerInfo.observer == object && [observerInfo.key isEqualToString: key]) { 161 | 162 | observerRemoved = observerInfo; 163 | break; 164 | } 165 | } 166 | [observers removeObject: observerRemoved]; 167 | } 168 | 169 | 170 | - (Class)createKVOClassWithOriginalClassName: (NSString *)className 171 | { 172 | NSString * kvoClassName = [kCMkvoClassPrefix stringByAppendingString: className]; 173 | Class observedClass = NSClassFromString(kvoClassName); 174 | 175 | if (observedClass) { return observedClass; } 176 | 177 | //创建新类,并且添加CMObserver_为类名新前缀 178 | Class originalClass = object_getClass(self); 179 | //【API注解②】 180 | Class kvoClass = objc_allocateClassPair(originalClass, kvoClassName.UTF8String, 0); 181 | 182 | //获取监听对象的class方法实现代码,然后替换新建类的class实现 183 | Method classMethod = class_getInstanceMethod(originalClass, @selector(class)); 184 | const char * types = method_getTypeEncoding(classMethod); 185 | class_addMethod(kvoClass, @selector(class), (IMP)kvo_Class, types); 186 | objc_registerClassPair(kvoClass); 187 | return kvoClass; 188 | } 189 | 190 | 191 | - (BOOL)hasSelector: (SEL)selector 192 | { 193 | Class observedClass = object_getClass(self); 194 | unsigned int methodCount = 0; 195 | Method * methodList = class_copyMethodList(observedClass, &methodCount); 196 | for (int i = 0; i < methodCount; i++) { 197 | 198 | SEL thisSelector = method_getName(methodList[i]); 199 | if (thisSelector == selector) { 200 | 201 | free(methodList); 202 | return YES; 203 | } 204 | } 205 | 206 | free(methodList); 207 | return NO; 208 | } 209 | 210 | #pragma mark -- Debug Method 211 | //static NSArray * ClassMethodsName(Class class) 212 | //{ 213 | // NSMutableArray * methodsArr = [NSMutableArray array]; 214 | // 215 | // unsigned methodCount = 0; 216 | // Method * methodList = class_copyMethodList(class, &methodCount); 217 | // for (int i = 0; i < methodCount; i++) { 218 | // 219 | // [methodsArr addObject: NSStringFromSelector(method_getName(methodList[i]))]; 220 | // } 221 | // free(methodList); 222 | // 223 | // return methodsArr; 224 | //} 225 | 226 | 227 | @end 228 | -------------------------------------------------------------------------------- /CMKVODemo/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | LSRequiresIPhoneOS 22 | 23 | UILaunchStoryboardName 24 | LaunchScreen 25 | UIMainStoryboardFile 26 | Main 27 | UIRequiredDeviceCapabilities 28 | 29 | armv7 30 | 31 | UISupportedInterfaceOrientations 32 | 33 | UIInterfaceOrientationPortrait 34 | UIInterfaceOrientationLandscapeLeft 35 | UIInterfaceOrientationLandscapeRight 36 | 37 | UISupportedInterfaceOrientations~ipad 38 | 39 | UIInterfaceOrientationPortrait 40 | UIInterfaceOrientationPortraitUpsideDown 41 | UIInterfaceOrientationLandscapeLeft 42 | UIInterfaceOrientationLandscapeRight 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /CMKVODemo/ObservedObject.h: -------------------------------------------------------------------------------- 1 | // 2 | // ObservedObject.h 3 | // CM_KeyValueObserveDemo 4 | // 5 | // Created by linxinda on 15/3/16. 6 | // Copyright (c) 2015年 Personal. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface ObservedObject : NSObject 12 | 13 | @property (assign, nonatomic) NSNumber * observedNum; 14 | 15 | @end 16 | -------------------------------------------------------------------------------- /CMKVODemo/ObservedObject.m: -------------------------------------------------------------------------------- 1 | // 2 | // ObservedObject.m 3 | // CM_KeyValueObserveDemo 4 | // 5 | // Created by linxinda on 15/3/16. 6 | // Copyright (c) 2015年 Personal. All rights reserved. 7 | // 8 | 9 | #import "ObservedObject.h" 10 | 11 | @implementation ObservedObject 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /CMKVODemo/ViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.h 3 | // CMKVODemo 4 | // 5 | // Created by ChenMan on 2018/4/19. 6 | // Copyright © 2018年 cimain. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface ViewController : UIViewController 12 | 13 | 14 | @end 15 | 16 | -------------------------------------------------------------------------------- /CMKVODemo/ViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.m 3 | // CMKVODemo 4 | // 5 | // Created by ChenMan on 2018/4/19. 6 | // Copyright © 2018年 cimain. All rights reserved. 7 | // 8 | 9 | #import "ViewController.h" 10 | #import "ObservedObject.h" 11 | 12 | #import "NSObject+Delegate_KVO.h" 13 | #import "NSObject+Block_KVO.h" 14 | 15 | 16 | @interface ViewController () 17 | 18 | @end 19 | 20 | @implementation ViewController 21 | 22 | - (void)viewDidLoad { 23 | [super viewDidLoad]; 24 | 25 | ObservedObject * object = [ObservedObject new]; 26 | object.observedNum = @111; 27 | 28 | #pragma mark - Observed By Delegate 29 | // [object CM_addObserver: self forKey: @"observedNum"]; 30 | 31 | #pragma mark - Observed By Block 32 | [object CM_addObserver: self forKey: @"observedNum" withBlock: ^(id observedObject, NSString *observedKey, id oldValue, id newValue) { 33 | NSLog(@"Value had changed yet with observing Block"); 34 | NSLog(@"oldValue---%@",oldValue); 35 | NSLog(@"newValue---%@",newValue); 36 | }]; 37 | 38 | object.observedNum = @888; 39 | } 40 | 41 | #pragma mark - ObserverDelegate 42 | -(void)CM_ObserveValueForKeyPath:(NSString *)keyPath ofObject:(id)object oldValue:(id)oldValue newValue:(id)newValue{ 43 | NSLog(@"Value had changed yet with observing Delegate"); 44 | NSLog(@"oldValue---%@",oldValue); 45 | NSLog(@"newValue---%@",newValue); 46 | } 47 | 48 | - (void)didReceiveMemoryWarning { 49 | [super didReceiveMemoryWarning]; 50 | // Dispose of any resources that can be recreated. 51 | } 52 | 53 | @end 54 | -------------------------------------------------------------------------------- /CMKVODemo/main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // CMKVODemo 4 | // 5 | // Created by ChenMan on 2018/4/19. 6 | // Copyright © 2018年 cimain. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "AppDelegate.h" 11 | 12 | int main(int argc, char * argv[]) { 13 | @autoreleasepool { 14 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /CMKVODemoTests/CMKVODemoTests.m: -------------------------------------------------------------------------------- 1 | // 2 | // CMKVODemoTests.m 3 | // CMKVODemoTests 4 | // 5 | // Created by ChenMan on 2018/4/19. 6 | // Copyright © 2018年 cimain. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface CMKVODemoTests : XCTestCase 12 | 13 | @end 14 | 15 | @implementation CMKVODemoTests 16 | 17 | - (void)setUp { 18 | [super setUp]; 19 | // Put setup code here. This method is called before the invocation of each test method in the class. 20 | } 21 | 22 | - (void)tearDown { 23 | // Put teardown code here. This method is called after the invocation of each test method in the class. 24 | [super tearDown]; 25 | } 26 | 27 | - (void)testExample { 28 | // This is an example of a functional test case. 29 | // Use XCTAssert and related functions to verify your tests produce the correct results. 30 | } 31 | 32 | - (void)testPerformanceExample { 33 | // This is an example of a performance test case. 34 | [self measureBlock:^{ 35 | // Put the code you want to measure the time of here. 36 | }]; 37 | } 38 | 39 | @end 40 | -------------------------------------------------------------------------------- /CMKVODemoTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /CMKVODemoUITests/CMKVODemoUITests.m: -------------------------------------------------------------------------------- 1 | // 2 | // CMKVODemoUITests.m 3 | // CMKVODemoUITests 4 | // 5 | // Created by ChenMan on 2018/4/19. 6 | // Copyright © 2018年 cimain. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface CMKVODemoUITests : XCTestCase 12 | 13 | @end 14 | 15 | @implementation CMKVODemoUITests 16 | 17 | - (void)setUp { 18 | [super setUp]; 19 | 20 | // Put setup code here. This method is called before the invocation of each test method in the class. 21 | 22 | // In UI tests it is usually best to stop immediately when a failure occurs. 23 | self.continueAfterFailure = NO; 24 | // UI tests must launch the application that they test. Doing this in setup will make sure it happens for each test method. 25 | [[[XCUIApplication alloc] init] launch]; 26 | 27 | // In UI tests it’s important to set the initial state - such as interface orientation - required for your tests before they run. The setUp method is a good place to do this. 28 | } 29 | 30 | - (void)tearDown { 31 | // Put teardown code here. This method is called after the invocation of each test method in the class. 32 | [super tearDown]; 33 | } 34 | 35 | - (void)testExample { 36 | // Use recording to get started writing UI tests. 37 | // Use XCTAssert and related functions to verify your tests produce the correct results. 38 | } 39 | 40 | @end 41 | -------------------------------------------------------------------------------- /CMKVODemoUITests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # CMKVODemo 2 | iOS开发·KVO用法,原理与底层实现: runtime模拟实现KVO监听机制(Blcok及Delgate方式) 3 | 4 | # 了解更多 5 | 6 | 简书地址:[iOS开发·KVO用法,原理与底层实现: runtime模拟实现KVO监听机制(Blcok及Delgate方式)](https://www.jianshu.com/p/c1aa85779d80) 7 | --------------------------------------------------------------------------------