├── .gitignore ├── IQDataBinding.podspec ├── IQDataBinding.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist └── xcshareddata │ └── xcschemes │ └── IQDataBindingDemo.xcscheme ├── IQDataBinding ├── IQDataBinding.h ├── Info.plist ├── NSObject+IQDataBinding.h └── NSObject+IQDataBinding.m ├── IQDataBindingDemo ├── AppDelegate.h ├── AppDelegate.m ├── Assets.xcassets │ ├── AppIcon.appiconset │ │ └── Contents.json │ └── Contents.json ├── Base.lproj │ ├── LaunchScreen.storyboard │ └── Main.storyboard ├── ContentModel.h ├── ContentModel.m ├── ContentView.h ├── ContentView.m ├── Info.plist ├── ViewController.h ├── ViewController.m └── main.m ├── LICENSE └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | # 3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 4 | 5 | ## Build generated 6 | build/ 7 | DerivedData/ 8 | 9 | ## Various settings 10 | *.pbxuser 11 | !default.pbxuser 12 | *.mode1v3 13 | !default.mode1v3 14 | *.mode2v3 15 | !default.mode2v3 16 | *.perspectivev3 17 | !default.perspectivev3 18 | xcuserdata/ 19 | 20 | ## Other 21 | *.moved-aside 22 | *.xccheckout 23 | *.xcscmblueprint 24 | 25 | ## Obj-C/Swift specific 26 | *.hmap 27 | *.ipa 28 | *.dSYM.zip 29 | *.dSYM 30 | 31 | # CocoaPods 32 | # 33 | # We recommend against adding the Pods directory to your .gitignore. However 34 | # you should judge for yourself, the pros and cons are mentioned at: 35 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 36 | # 37 | # Pods/ 38 | 39 | # Carthage 40 | # 41 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 42 | # Carthage/Checkouts 43 | 44 | Carthage/Build 45 | 46 | # fastlane 47 | # 48 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the 49 | # screenshots whenever they are needed. 50 | # For more information about the recommended setup visit: 51 | # https://docs.fastlane.tools/best-practices/source-control/#source-control 52 | 53 | fastlane/report.xml 54 | fastlane/Preview.html 55 | fastlane/screenshots/**/*.png 56 | fastlane/test_output 57 | 58 | # Code Injection 59 | # 60 | # After new code Injection tools there's a generated folder /iOSInjectionProject 61 | # https://github.com/johnno1962/injectionforxcode 62 | 63 | iOSInjectionProject/ 64 | -------------------------------------------------------------------------------- /IQDataBinding.podspec: -------------------------------------------------------------------------------- 1 | # 2 | # Be sure to run `pod spec lint IQDataBinding.podspec' to ensure this is a 3 | # valid spec and to remove all comments including this before submitting the spec. 4 | # 5 | # To learn more about Podspec attributes see https://docs.cocoapods.org/specification.html 6 | # To see working Podspecs in the CocoaPods repo see https://github.com/CocoaPods/Specs/ 7 | # 8 | 9 | Pod::Spec.new do |spec| 10 | 11 | spec.name = "IQDataBinding" 12 | spec.version = "0.0.2" 13 | spec.summary = "iOS端View和ViewModel数据绑定框架。基于KVO监听所绑定ViewModel的属性,支持链式调用。" 14 | spec.description = <<-DESC 15 | A solution for decoupling modules in iOS platform. 16 | DESC 17 | spec.homepage = "https://github.com/Lobster-King/IQDataBinding" 18 | spec.license = "MIT" 19 | spec.author = { "Lobster-King" => "zhiwei.geek@gmail.com" } 20 | spec.ios.deployment_target = '7.0' 21 | spec.source = { :git => "https://github.com/Lobster-King/IQDataBinding.git", :tag => "#{spec.version}" } 22 | spec.source_files = "IQDataBinding", "IQDataBinding/**/*.{h,m}" 23 | spec.exclude_files = "IQDataBinding/Exclude" 24 | 25 | end 26 | -------------------------------------------------------------------------------- /IQDataBinding.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 50; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | BA4129C2227BC50500EA8099 /* NSObject+IQDataBinding.m in Sources */ = {isa = PBXBuildFile; fileRef = BAFD163C227B3CB8002D98EB /* NSObject+IQDataBinding.m */; }; 11 | BA68B9A4227DD4F1005F4819 /* ContentView.m in Sources */ = {isa = PBXBuildFile; fileRef = BA68B9A3227DD4F1005F4819 /* ContentView.m */; }; 12 | BAFD161B227B39DF002D98EB /* IQDataBinding.h in Headers */ = {isa = PBXBuildFile; fileRef = BAFD1619227B39DF002D98EB /* IQDataBinding.h */; settings = {ATTRIBUTES = (Public, ); }; }; 13 | BAFD1629227B3A85002D98EB /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = BAFD1628227B3A85002D98EB /* AppDelegate.m */; }; 14 | BAFD162C227B3A85002D98EB /* ViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = BAFD162B227B3A85002D98EB /* ViewController.m */; }; 15 | BAFD162F227B3A85002D98EB /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = BAFD162D227B3A85002D98EB /* Main.storyboard */; }; 16 | BAFD1631227B3A86002D98EB /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = BAFD1630227B3A86002D98EB /* Assets.xcassets */; }; 17 | BAFD1634227B3A86002D98EB /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = BAFD1632227B3A86002D98EB /* LaunchScreen.storyboard */; }; 18 | BAFD1637227B3A86002D98EB /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = BAFD1636227B3A86002D98EB /* main.m */; }; 19 | BAFD163D227B3CB8002D98EB /* NSObject+IQDataBinding.h in Headers */ = {isa = PBXBuildFile; fileRef = BAFD163B227B3CB8002D98EB /* NSObject+IQDataBinding.h */; }; 20 | BAFD163E227B3CB8002D98EB /* NSObject+IQDataBinding.m in Sources */ = {isa = PBXBuildFile; fileRef = BAFD163C227B3CB8002D98EB /* NSObject+IQDataBinding.m */; }; 21 | BAFD1645227B3EB5002D98EB /* ContentModel.m in Sources */ = {isa = PBXBuildFile; fileRef = BAFD1644227B3EB5002D98EB /* ContentModel.m */; }; 22 | /* End PBXBuildFile section */ 23 | 24 | /* Begin PBXFileReference section */ 25 | BA68B9A2227DD4F1005F4819 /* ContentView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ContentView.h; sourceTree = ""; }; 26 | BA68B9A3227DD4F1005F4819 /* ContentView.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ContentView.m; sourceTree = ""; }; 27 | BAFD1616227B39DF002D98EB /* IQDataBinding.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = IQDataBinding.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 28 | BAFD1619227B39DF002D98EB /* IQDataBinding.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = IQDataBinding.h; sourceTree = ""; }; 29 | BAFD161A227B39DF002D98EB /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 30 | BAFD1625227B3A85002D98EB /* IQDataBindingDemo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = IQDataBindingDemo.app; sourceTree = BUILT_PRODUCTS_DIR; }; 31 | BAFD1627227B3A85002D98EB /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; 32 | BAFD1628227B3A85002D98EB /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; 33 | BAFD162A227B3A85002D98EB /* ViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ViewController.h; sourceTree = ""; }; 34 | BAFD162B227B3A85002D98EB /* ViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ViewController.m; sourceTree = ""; }; 35 | BAFD162E227B3A85002D98EB /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 36 | BAFD1630227B3A86002D98EB /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 37 | BAFD1633227B3A86002D98EB /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 38 | BAFD1635227B3A86002D98EB /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 39 | BAFD1636227B3A86002D98EB /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; 40 | BAFD163B227B3CB8002D98EB /* NSObject+IQDataBinding.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "NSObject+IQDataBinding.h"; sourceTree = ""; }; 41 | BAFD163C227B3CB8002D98EB /* NSObject+IQDataBinding.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "NSObject+IQDataBinding.m"; sourceTree = ""; }; 42 | BAFD1643227B3EB5002D98EB /* ContentModel.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ContentModel.h; sourceTree = ""; }; 43 | BAFD1644227B3EB5002D98EB /* ContentModel.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ContentModel.m; sourceTree = ""; }; 44 | /* End PBXFileReference section */ 45 | 46 | /* Begin PBXFrameworksBuildPhase section */ 47 | BAFD1613227B39DF002D98EB /* Frameworks */ = { 48 | isa = PBXFrameworksBuildPhase; 49 | buildActionMask = 2147483647; 50 | files = ( 51 | ); 52 | runOnlyForDeploymentPostprocessing = 0; 53 | }; 54 | BAFD1622227B3A85002D98EB /* Frameworks */ = { 55 | isa = PBXFrameworksBuildPhase; 56 | buildActionMask = 2147483647; 57 | files = ( 58 | ); 59 | runOnlyForDeploymentPostprocessing = 0; 60 | }; 61 | /* End PBXFrameworksBuildPhase section */ 62 | 63 | /* Begin PBXGroup section */ 64 | BAFD160C227B39DF002D98EB = { 65 | isa = PBXGroup; 66 | children = ( 67 | BAFD1618227B39DF002D98EB /* IQDataBinding */, 68 | BAFD1626227B3A85002D98EB /* IQDataBindingDemo */, 69 | BAFD1617227B39DF002D98EB /* Products */, 70 | BAFD1648227B4ED2002D98EB /* Frameworks */, 71 | ); 72 | sourceTree = ""; 73 | }; 74 | BAFD1617227B39DF002D98EB /* Products */ = { 75 | isa = PBXGroup; 76 | children = ( 77 | BAFD1616227B39DF002D98EB /* IQDataBinding.framework */, 78 | BAFD1625227B3A85002D98EB /* IQDataBindingDemo.app */, 79 | ); 80 | name = Products; 81 | sourceTree = ""; 82 | }; 83 | BAFD1618227B39DF002D98EB /* IQDataBinding */ = { 84 | isa = PBXGroup; 85 | children = ( 86 | BAFD1619227B39DF002D98EB /* IQDataBinding.h */, 87 | BAFD163B227B3CB8002D98EB /* NSObject+IQDataBinding.h */, 88 | BAFD163C227B3CB8002D98EB /* NSObject+IQDataBinding.m */, 89 | BAFD161A227B39DF002D98EB /* Info.plist */, 90 | ); 91 | path = IQDataBinding; 92 | sourceTree = ""; 93 | }; 94 | BAFD1626227B3A85002D98EB /* IQDataBindingDemo */ = { 95 | isa = PBXGroup; 96 | children = ( 97 | BAFD1627227B3A85002D98EB /* AppDelegate.h */, 98 | BAFD1628227B3A85002D98EB /* AppDelegate.m */, 99 | BAFD162A227B3A85002D98EB /* ViewController.h */, 100 | BAFD162B227B3A85002D98EB /* ViewController.m */, 101 | BA68B9A2227DD4F1005F4819 /* ContentView.h */, 102 | BA68B9A3227DD4F1005F4819 /* ContentView.m */, 103 | BAFD1643227B3EB5002D98EB /* ContentModel.h */, 104 | BAFD1644227B3EB5002D98EB /* ContentModel.m */, 105 | BAFD162D227B3A85002D98EB /* Main.storyboard */, 106 | BAFD1630227B3A86002D98EB /* Assets.xcassets */, 107 | BAFD1632227B3A86002D98EB /* LaunchScreen.storyboard */, 108 | BAFD1635227B3A86002D98EB /* Info.plist */, 109 | BAFD1636227B3A86002D98EB /* main.m */, 110 | ); 111 | path = IQDataBindingDemo; 112 | sourceTree = ""; 113 | }; 114 | BAFD1648227B4ED2002D98EB /* Frameworks */ = { 115 | isa = PBXGroup; 116 | children = ( 117 | ); 118 | name = Frameworks; 119 | sourceTree = ""; 120 | }; 121 | /* End PBXGroup section */ 122 | 123 | /* Begin PBXHeadersBuildPhase section */ 124 | BAFD1611227B39DF002D98EB /* Headers */ = { 125 | isa = PBXHeadersBuildPhase; 126 | buildActionMask = 2147483647; 127 | files = ( 128 | BAFD163D227B3CB8002D98EB /* NSObject+IQDataBinding.h in Headers */, 129 | BAFD161B227B39DF002D98EB /* IQDataBinding.h in Headers */, 130 | ); 131 | runOnlyForDeploymentPostprocessing = 0; 132 | }; 133 | /* End PBXHeadersBuildPhase section */ 134 | 135 | /* Begin PBXNativeTarget section */ 136 | BAFD1615227B39DF002D98EB /* IQDataBinding */ = { 137 | isa = PBXNativeTarget; 138 | buildConfigurationList = BAFD161E227B39DF002D98EB /* Build configuration list for PBXNativeTarget "IQDataBinding" */; 139 | buildPhases = ( 140 | BAFD1611227B39DF002D98EB /* Headers */, 141 | BAFD1612227B39DF002D98EB /* Sources */, 142 | BAFD1613227B39DF002D98EB /* Frameworks */, 143 | BAFD1614227B39DF002D98EB /* Resources */, 144 | ); 145 | buildRules = ( 146 | ); 147 | dependencies = ( 148 | ); 149 | name = IQDataBinding; 150 | productName = IQDataBinding; 151 | productReference = BAFD1616227B39DF002D98EB /* IQDataBinding.framework */; 152 | productType = "com.apple.product-type.framework"; 153 | }; 154 | BAFD1624227B3A85002D98EB /* IQDataBindingDemo */ = { 155 | isa = PBXNativeTarget; 156 | buildConfigurationList = BAFD163A227B3A86002D98EB /* Build configuration list for PBXNativeTarget "IQDataBindingDemo" */; 157 | buildPhases = ( 158 | BAFD1621227B3A85002D98EB /* Sources */, 159 | BAFD1622227B3A85002D98EB /* Frameworks */, 160 | BAFD1623227B3A85002D98EB /* Resources */, 161 | ); 162 | buildRules = ( 163 | ); 164 | dependencies = ( 165 | ); 166 | name = IQDataBindingDemo; 167 | productName = IQDataBindingDemo; 168 | productReference = BAFD1625227B3A85002D98EB /* IQDataBindingDemo.app */; 169 | productType = "com.apple.product-type.application"; 170 | }; 171 | /* End PBXNativeTarget section */ 172 | 173 | /* Begin PBXProject section */ 174 | BAFD160D227B39DF002D98EB /* Project object */ = { 175 | isa = PBXProject; 176 | attributes = { 177 | LastUpgradeCheck = 1020; 178 | ORGANIZATIONNAME = lobster; 179 | TargetAttributes = { 180 | BAFD1615227B39DF002D98EB = { 181 | CreatedOnToolsVersion = 10.2.1; 182 | }; 183 | BAFD1624227B3A85002D98EB = { 184 | CreatedOnToolsVersion = 10.2.1; 185 | }; 186 | }; 187 | }; 188 | buildConfigurationList = BAFD1610227B39DF002D98EB /* Build configuration list for PBXProject "IQDataBinding" */; 189 | compatibilityVersion = "Xcode 9.3"; 190 | developmentRegion = en; 191 | hasScannedForEncodings = 0; 192 | knownRegions = ( 193 | en, 194 | Base, 195 | ); 196 | mainGroup = BAFD160C227B39DF002D98EB; 197 | productRefGroup = BAFD1617227B39DF002D98EB /* Products */; 198 | projectDirPath = ""; 199 | projectRoot = ""; 200 | targets = ( 201 | BAFD1615227B39DF002D98EB /* IQDataBinding */, 202 | BAFD1624227B3A85002D98EB /* IQDataBindingDemo */, 203 | ); 204 | }; 205 | /* End PBXProject section */ 206 | 207 | /* Begin PBXResourcesBuildPhase section */ 208 | BAFD1614227B39DF002D98EB /* Resources */ = { 209 | isa = PBXResourcesBuildPhase; 210 | buildActionMask = 2147483647; 211 | files = ( 212 | ); 213 | runOnlyForDeploymentPostprocessing = 0; 214 | }; 215 | BAFD1623227B3A85002D98EB /* Resources */ = { 216 | isa = PBXResourcesBuildPhase; 217 | buildActionMask = 2147483647; 218 | files = ( 219 | BAFD1634227B3A86002D98EB /* LaunchScreen.storyboard in Resources */, 220 | BAFD1631227B3A86002D98EB /* Assets.xcassets in Resources */, 221 | BAFD162F227B3A85002D98EB /* Main.storyboard in Resources */, 222 | ); 223 | runOnlyForDeploymentPostprocessing = 0; 224 | }; 225 | /* End PBXResourcesBuildPhase section */ 226 | 227 | /* Begin PBXSourcesBuildPhase section */ 228 | BAFD1612227B39DF002D98EB /* Sources */ = { 229 | isa = PBXSourcesBuildPhase; 230 | buildActionMask = 2147483647; 231 | files = ( 232 | BAFD163E227B3CB8002D98EB /* NSObject+IQDataBinding.m in Sources */, 233 | ); 234 | runOnlyForDeploymentPostprocessing = 0; 235 | }; 236 | BAFD1621227B3A85002D98EB /* Sources */ = { 237 | isa = PBXSourcesBuildPhase; 238 | buildActionMask = 2147483647; 239 | files = ( 240 | BA4129C2227BC50500EA8099 /* NSObject+IQDataBinding.m in Sources */, 241 | BAFD162C227B3A85002D98EB /* ViewController.m in Sources */, 242 | BA68B9A4227DD4F1005F4819 /* ContentView.m in Sources */, 243 | BAFD1645227B3EB5002D98EB /* ContentModel.m in Sources */, 244 | BAFD1637227B3A86002D98EB /* main.m in Sources */, 245 | BAFD1629227B3A85002D98EB /* AppDelegate.m in Sources */, 246 | ); 247 | runOnlyForDeploymentPostprocessing = 0; 248 | }; 249 | /* End PBXSourcesBuildPhase section */ 250 | 251 | /* Begin PBXVariantGroup section */ 252 | BAFD162D227B3A85002D98EB /* Main.storyboard */ = { 253 | isa = PBXVariantGroup; 254 | children = ( 255 | BAFD162E227B3A85002D98EB /* Base */, 256 | ); 257 | name = Main.storyboard; 258 | sourceTree = ""; 259 | }; 260 | BAFD1632227B3A86002D98EB /* LaunchScreen.storyboard */ = { 261 | isa = PBXVariantGroup; 262 | children = ( 263 | BAFD1633227B3A86002D98EB /* Base */, 264 | ); 265 | name = LaunchScreen.storyboard; 266 | sourceTree = ""; 267 | }; 268 | /* End PBXVariantGroup section */ 269 | 270 | /* Begin XCBuildConfiguration section */ 271 | BAFD161C227B39DF002D98EB /* Debug */ = { 272 | isa = XCBuildConfiguration; 273 | buildSettings = { 274 | ALWAYS_SEARCH_USER_PATHS = NO; 275 | CLANG_ANALYZER_NONNULL = YES; 276 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 277 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 278 | CLANG_CXX_LIBRARY = "libc++"; 279 | CLANG_ENABLE_MODULES = YES; 280 | CLANG_ENABLE_OBJC_ARC = YES; 281 | CLANG_ENABLE_OBJC_WEAK = YES; 282 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 283 | CLANG_WARN_BOOL_CONVERSION = YES; 284 | CLANG_WARN_COMMA = YES; 285 | CLANG_WARN_CONSTANT_CONVERSION = YES; 286 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 287 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 288 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 289 | CLANG_WARN_EMPTY_BODY = YES; 290 | CLANG_WARN_ENUM_CONVERSION = YES; 291 | CLANG_WARN_INFINITE_RECURSION = YES; 292 | CLANG_WARN_INT_CONVERSION = YES; 293 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 294 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 295 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 296 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 297 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 298 | CLANG_WARN_STRICT_PROTOTYPES = YES; 299 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 300 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 301 | CLANG_WARN_UNREACHABLE_CODE = YES; 302 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 303 | CODE_SIGN_IDENTITY = "iPhone Developer"; 304 | COPY_PHASE_STRIP = NO; 305 | CURRENT_PROJECT_VERSION = 1; 306 | DEBUG_INFORMATION_FORMAT = dwarf; 307 | ENABLE_STRICT_OBJC_MSGSEND = YES; 308 | ENABLE_TESTABILITY = YES; 309 | GCC_C_LANGUAGE_STANDARD = gnu11; 310 | GCC_DYNAMIC_NO_PIC = NO; 311 | GCC_NO_COMMON_BLOCKS = YES; 312 | GCC_OPTIMIZATION_LEVEL = 0; 313 | GCC_PREPROCESSOR_DEFINITIONS = ( 314 | "DEBUG=1", 315 | "$(inherited)", 316 | ); 317 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 318 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 319 | GCC_WARN_UNDECLARED_SELECTOR = YES; 320 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 321 | GCC_WARN_UNUSED_FUNCTION = YES; 322 | GCC_WARN_UNUSED_VARIABLE = YES; 323 | IPHONEOS_DEPLOYMENT_TARGET = 12.2; 324 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; 325 | MTL_FAST_MATH = YES; 326 | ONLY_ACTIVE_ARCH = YES; 327 | SDKROOT = iphoneos; 328 | VERSIONING_SYSTEM = "apple-generic"; 329 | VERSION_INFO_PREFIX = ""; 330 | }; 331 | name = Debug; 332 | }; 333 | BAFD161D227B39DF002D98EB /* Release */ = { 334 | isa = XCBuildConfiguration; 335 | buildSettings = { 336 | ALWAYS_SEARCH_USER_PATHS = NO; 337 | CLANG_ANALYZER_NONNULL = YES; 338 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 339 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 340 | CLANG_CXX_LIBRARY = "libc++"; 341 | CLANG_ENABLE_MODULES = YES; 342 | CLANG_ENABLE_OBJC_ARC = YES; 343 | CLANG_ENABLE_OBJC_WEAK = YES; 344 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 345 | CLANG_WARN_BOOL_CONVERSION = YES; 346 | CLANG_WARN_COMMA = YES; 347 | CLANG_WARN_CONSTANT_CONVERSION = YES; 348 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 349 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 350 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 351 | CLANG_WARN_EMPTY_BODY = YES; 352 | CLANG_WARN_ENUM_CONVERSION = YES; 353 | CLANG_WARN_INFINITE_RECURSION = YES; 354 | CLANG_WARN_INT_CONVERSION = YES; 355 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 356 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 357 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 358 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 359 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 360 | CLANG_WARN_STRICT_PROTOTYPES = YES; 361 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 362 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 363 | CLANG_WARN_UNREACHABLE_CODE = YES; 364 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 365 | CODE_SIGN_IDENTITY = "iPhone Developer"; 366 | COPY_PHASE_STRIP = NO; 367 | CURRENT_PROJECT_VERSION = 1; 368 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 369 | ENABLE_NS_ASSERTIONS = NO; 370 | ENABLE_STRICT_OBJC_MSGSEND = YES; 371 | GCC_C_LANGUAGE_STANDARD = gnu11; 372 | GCC_NO_COMMON_BLOCKS = YES; 373 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 374 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 375 | GCC_WARN_UNDECLARED_SELECTOR = YES; 376 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 377 | GCC_WARN_UNUSED_FUNCTION = YES; 378 | GCC_WARN_UNUSED_VARIABLE = YES; 379 | IPHONEOS_DEPLOYMENT_TARGET = 12.2; 380 | MTL_ENABLE_DEBUG_INFO = NO; 381 | MTL_FAST_MATH = YES; 382 | SDKROOT = iphoneos; 383 | VALIDATE_PRODUCT = YES; 384 | VERSIONING_SYSTEM = "apple-generic"; 385 | VERSION_INFO_PREFIX = ""; 386 | }; 387 | name = Release; 388 | }; 389 | BAFD161F227B39DF002D98EB /* Debug */ = { 390 | isa = XCBuildConfiguration; 391 | buildSettings = { 392 | CODE_SIGN_IDENTITY = ""; 393 | CODE_SIGN_STYLE = Automatic; 394 | DEFINES_MODULE = YES; 395 | DYLIB_COMPATIBILITY_VERSION = 1; 396 | DYLIB_CURRENT_VERSION = 1; 397 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 398 | INFOPLIST_FILE = IQDataBinding/Info.plist; 399 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 400 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 401 | LD_RUNPATH_SEARCH_PATHS = ( 402 | "$(inherited)", 403 | "@executable_path/Frameworks", 404 | "@loader_path/Frameworks", 405 | ); 406 | OTHER_LDFLAGS = ""; 407 | PRODUCT_BUNDLE_IDENTIFIER = com.lobster.IQDataBinding; 408 | PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; 409 | SKIP_INSTALL = YES; 410 | TARGETED_DEVICE_FAMILY = "1,2"; 411 | }; 412 | name = Debug; 413 | }; 414 | BAFD1620227B39DF002D98EB /* Release */ = { 415 | isa = XCBuildConfiguration; 416 | buildSettings = { 417 | CODE_SIGN_IDENTITY = ""; 418 | CODE_SIGN_STYLE = Automatic; 419 | DEFINES_MODULE = YES; 420 | DYLIB_COMPATIBILITY_VERSION = 1; 421 | DYLIB_CURRENT_VERSION = 1; 422 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 423 | INFOPLIST_FILE = IQDataBinding/Info.plist; 424 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 425 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 426 | LD_RUNPATH_SEARCH_PATHS = ( 427 | "$(inherited)", 428 | "@executable_path/Frameworks", 429 | "@loader_path/Frameworks", 430 | ); 431 | OTHER_LDFLAGS = ""; 432 | PRODUCT_BUNDLE_IDENTIFIER = com.lobster.IQDataBinding; 433 | PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; 434 | SKIP_INSTALL = YES; 435 | TARGETED_DEVICE_FAMILY = "1,2"; 436 | }; 437 | name = Release; 438 | }; 439 | BAFD1638227B3A86002D98EB /* Debug */ = { 440 | isa = XCBuildConfiguration; 441 | buildSettings = { 442 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 443 | CODE_SIGN_STYLE = Automatic; 444 | INFOPLIST_FILE = IQDataBindingDemo/Info.plist; 445 | LD_RUNPATH_SEARCH_PATHS = ( 446 | "$(inherited)", 447 | "@executable_path/Frameworks", 448 | ); 449 | OTHER_LDFLAGS = ""; 450 | OTHER_LIBTOOLFLAGS = "-ObjC"; 451 | PRODUCT_BUNDLE_IDENTIFIER = com.lobster.IQDataBindingDemo; 452 | PRODUCT_NAME = "$(TARGET_NAME)"; 453 | TARGETED_DEVICE_FAMILY = "1,2"; 454 | }; 455 | name = Debug; 456 | }; 457 | BAFD1639227B3A86002D98EB /* Release */ = { 458 | isa = XCBuildConfiguration; 459 | buildSettings = { 460 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 461 | CODE_SIGN_STYLE = Automatic; 462 | INFOPLIST_FILE = IQDataBindingDemo/Info.plist; 463 | LD_RUNPATH_SEARCH_PATHS = ( 464 | "$(inherited)", 465 | "@executable_path/Frameworks", 466 | ); 467 | OTHER_LDFLAGS = ""; 468 | OTHER_LIBTOOLFLAGS = "-ObjC"; 469 | PRODUCT_BUNDLE_IDENTIFIER = com.lobster.IQDataBindingDemo; 470 | PRODUCT_NAME = "$(TARGET_NAME)"; 471 | TARGETED_DEVICE_FAMILY = "1,2"; 472 | }; 473 | name = Release; 474 | }; 475 | /* End XCBuildConfiguration section */ 476 | 477 | /* Begin XCConfigurationList section */ 478 | BAFD1610227B39DF002D98EB /* Build configuration list for PBXProject "IQDataBinding" */ = { 479 | isa = XCConfigurationList; 480 | buildConfigurations = ( 481 | BAFD161C227B39DF002D98EB /* Debug */, 482 | BAFD161D227B39DF002D98EB /* Release */, 483 | ); 484 | defaultConfigurationIsVisible = 0; 485 | defaultConfigurationName = Release; 486 | }; 487 | BAFD161E227B39DF002D98EB /* Build configuration list for PBXNativeTarget "IQDataBinding" */ = { 488 | isa = XCConfigurationList; 489 | buildConfigurations = ( 490 | BAFD161F227B39DF002D98EB /* Debug */, 491 | BAFD1620227B39DF002D98EB /* Release */, 492 | ); 493 | defaultConfigurationIsVisible = 0; 494 | defaultConfigurationName = Release; 495 | }; 496 | BAFD163A227B3A86002D98EB /* Build configuration list for PBXNativeTarget "IQDataBindingDemo" */ = { 497 | isa = XCConfigurationList; 498 | buildConfigurations = ( 499 | BAFD1638227B3A86002D98EB /* Debug */, 500 | BAFD1639227B3A86002D98EB /* Release */, 501 | ); 502 | defaultConfigurationIsVisible = 0; 503 | defaultConfigurationName = Release; 504 | }; 505 | /* End XCConfigurationList section */ 506 | }; 507 | rootObject = BAFD160D227B39DF002D98EB /* Project object */; 508 | } 509 | -------------------------------------------------------------------------------- /IQDataBinding.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /IQDataBinding.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /IQDataBinding.xcodeproj/xcshareddata/xcschemes/IQDataBindingDemo.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 39 | 40 | 41 | 42 | 43 | 44 | 54 | 56 | 62 | 63 | 64 | 65 | 66 | 67 | 73 | 75 | 81 | 82 | 83 | 84 | 86 | 87 | 90 | 91 | 92 | -------------------------------------------------------------------------------- /IQDataBinding/IQDataBinding.h: -------------------------------------------------------------------------------- 1 | // 2 | // IQDataBinding.h 3 | // IQDataBinding 4 | // 5 | // Created by lobster on 2019/5/2. 6 | // Copyright © 2019 lobster. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "NSObject+IQDataBinding.h" 11 | 12 | //! Project version number for IQDataBinding. 13 | FOUNDATION_EXPORT double IQDataBindingVersionNumber; 14 | 15 | //! Project version string for IQDataBinding. 16 | FOUNDATION_EXPORT const unsigned char IQDataBindingVersionString[]; 17 | 18 | // In this header, you should import all the public headers of your framework using statements like #import 19 | /** 20 | TODO LIST: 21 | 1.同一view绑定多个model的情况。 22 | 2.view变动->更新viewModel。 23 | */ 24 | 25 | -------------------------------------------------------------------------------- /IQDataBinding/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 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | $(CURRENT_PROJECT_VERSION) 21 | 22 | 23 | -------------------------------------------------------------------------------- /IQDataBinding/NSObject+IQDataBinding.h: -------------------------------------------------------------------------------- 1 | // 2 | // NSObject+IQDataBinding.h 3 | // IQDataBinding 4 | // 5 | // Created by lobster on 2019/5/2. 6 | // Copyright © 2019 lobster. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | 12 | NS_ASSUME_NONNULL_BEGIN 13 | 14 | #define update(key,...) update(IQBoxValue(key,(__VA_ARGS__))) 15 | #define IQBoxValue(key,value) autoboxing(@encode(__typeof__((value))),self,key,(value)) 16 | 17 | id autoboxing(const char *type, ...); 18 | 19 | typedef void(^observerCallBack)(id changedValue); 20 | 21 | @interface NSObject (IQDataBinding) 22 | 23 | - (void)bindModel:(id)model; 24 | 25 | - (NSObject *(^)(NSString *keyPath,observerCallBack observer))observe; 26 | - (NSObject * (^)(id,...))update; 27 | 28 | @end 29 | 30 | NS_ASSUME_NONNULL_END 31 | -------------------------------------------------------------------------------- /IQDataBinding/NSObject+IQDataBinding.m: -------------------------------------------------------------------------------- 1 | // 2 | // NSObject+IQDataBinding.m 3 | // IQDataBinding 4 | // 5 | // Created by lobster on 2019/5/2. 6 | // Copyright © 2019 lobster. All rights reserved. 7 | // 8 | 9 | #import "NSObject+IQDataBinding.h" 10 | #import 11 | 12 | static NSString *kViewAssociatedModelKey = @"kViewAssociatedModelKey"; 13 | static NSMutableDictionary *stashedObserver = nil; 14 | 15 | @interface IQWatchDog : NSObject 16 | 17 | @property (nonatomic, weak) id target; 18 | @property (nonatomic, strong) NSMutableDictionary *keyPathsAndCallBacks; 19 | 20 | @end 21 | 22 | @implementation IQWatchDog 23 | 24 | - (void)dealloc { 25 | [self.keyPathsAndCallBacks enumerateKeysAndObjectsUsingBlock:^(id _Nonnull key, id _Nonnull obj, BOOL * _Nonnull stop) { 26 | [self.target removeObserver:self forKeyPath:key]; 27 | }]; 28 | } 29 | 30 | - (void)observeKeyPath:(NSString *)keyPath callBack:(observerCallBack)callBack { 31 | NSAssert(keyPath.length, @"keyPath不合法"); 32 | /*加载默认值*/ 33 | id value = [self.target valueForKeyPath:keyPath]; 34 | if (value) { 35 | callBack(value); 36 | } 37 | /*添加观察者*/ 38 | [self.keyPathsAndCallBacks setObject:callBack forKey:keyPath]; 39 | [self.target addObserver:self forKeyPath:keyPath options:NSKeyValueObservingOptionNew context:NULL]; 40 | } 41 | 42 | - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context { 43 | observerCallBack callBack = self.keyPathsAndCallBacks[keyPath]; 44 | if (callBack) { 45 | callBack(change[NSKeyValueChangeNewKey]); 46 | } 47 | } 48 | 49 | - (void)removeAllObservers { 50 | [self.keyPathsAndCallBacks enumerateKeysAndObjectsUsingBlock:^(id _Nonnull key, id _Nonnull obj, BOOL * _Nonnull stop) { 51 | [self.target removeObserver:self forKeyPath:key]; 52 | }]; 53 | } 54 | 55 | - (NSMutableDictionary *)keyPathsAndCallBacks { 56 | if (!_keyPathsAndCallBacks) { 57 | _keyPathsAndCallBacks = [NSMutableDictionary dictionary]; 58 | } 59 | return _keyPathsAndCallBacks; 60 | } 61 | 62 | @end 63 | 64 | @implementation NSObject (IQDataBinding) 65 | 66 | - (void)bindModel:(id)model { 67 | /*给view添加一个关联对象IQWatchDog,IQWatchDog职责如下 68 | 1.存储@{绑定的Key,回调Block}对应关系。 69 | 2.根据@{绑定的Key,回调Block}中的Key,进行KVO监听。 70 | 3.监听view Dealloc事件,自动移除KVO监听。 71 | */ 72 | IQWatchDog *viewAssociatedModel = objc_getAssociatedObject(self, &kViewAssociatedModelKey); 73 | if (viewAssociatedModel) { 74 | objc_setAssociatedObject(self, &kViewAssociatedModelKey, nil, OBJC_ASSOCIATION_RETAIN_NONATOMIC); 75 | } 76 | 77 | viewAssociatedModel = [[IQWatchDog alloc]init]; 78 | viewAssociatedModel.target = model; 79 | objc_setAssociatedObject(self, &kViewAssociatedModelKey, viewAssociatedModel, OBJC_ASSOCIATION_RETAIN_NONATOMIC); 80 | 81 | /*借鉴Git stash暂存命令理念,stashedObserver职责如下 82 | 1.如果bindModel调用在绑定keyPath之后调用,会自动把当前@{绑定的Key,回调Block}结构保存到暂存区。 83 | 2.调用bindModel的时候先根据当前view的地址指针去stashedObserver取暂存的数据。 84 | 3.如果暂存区有数据则调用IQWatchDog注册方法进行自动注册。 85 | 4.注册完成进行stash pop操作。 86 | */ 87 | NSString *viewP = [NSString stringWithFormat:@"%p",self]; 88 | 89 | NSDictionary *viewStashMap = stashedObserver[viewP]; 90 | 91 | if (viewStashMap) { 92 | [viewStashMap enumerateKeysAndObjectsUsingBlock:^(id _Nonnull key, id _Nonnull obj, BOOL * _Nonnull stop) { 93 | [viewAssociatedModel observeKeyPath:key callBack:obj]; 94 | }]; 95 | /*stash pop*/ 96 | [stashedObserver removeObjectForKey:viewP]; 97 | } 98 | } 99 | 100 | - (NSObject *(^)(NSString *keyPath,observerCallBack observer))observe { 101 | if (!stashedObserver) { 102 | stashedObserver = [NSMutableDictionary dictionary]; 103 | } 104 | 105 | IQWatchDog *viewAssociatedModel = objc_getAssociatedObject(self, &kViewAssociatedModelKey); 106 | 107 | return ^(NSString *keyPath,observerCallBack observer){ 108 | /*viewAssociatedModel为空,说明在绑定属性前没有绑定model,此处进行stash暂存*/ 109 | if (!viewAssociatedModel) { 110 | /*stash push*/ 111 | NSString *viewP = [NSString stringWithFormat:@"%p",self]; 112 | NSMutableDictionary *viewStashMap = [NSMutableDictionary dictionaryWithDictionary:stashedObserver[viewP]]; 113 | 114 | if (!viewStashMap) { 115 | viewStashMap = [NSMutableDictionary new]; 116 | } 117 | 118 | [viewStashMap setObject:observer forKey:keyPath]; 119 | 120 | [stashedObserver setObject:viewStashMap forKey:viewP]; 121 | return self; 122 | } 123 | [viewAssociatedModel observeKeyPath:keyPath callBack:observer]; 124 | return self; 125 | }; 126 | } 127 | 128 | - (NSObject * (^)(id,...))update { 129 | return ^id(id attribute,...) { 130 | return self; 131 | }; 132 | } 133 | 134 | id autoboxing(const char *type, ...) { 135 | 136 | va_list v; 137 | va_start(v, type); 138 | 139 | NSObject *pointer = va_arg(v, id); 140 | NSString *key = va_arg(v, NSString *); 141 | 142 | IQWatchDog *watchDog = objc_getAssociatedObject(pointer, &kViewAssociatedModelKey); 143 | Ivar ivar = class_getInstanceVariable([watchDog.target class], [[NSString stringWithFormat:@"_%@",key] UTF8String]); 144 | 145 | id obj = nil; 146 | if (strcmp(type, @encode(id)) == 0) { 147 | id actual = va_arg(v, id); 148 | object_setIvar(watchDog.target,ivar,actual); 149 | } else if (strcmp(type, @encode(CGPoint)) == 0) { 150 | #warning fix me!!! 151 | CGPoint actual = (CGPoint)va_arg(v, CGPoint); 152 | void (*fcgpoint)(id, Ivar, CGPoint) = (void (*)(id, Ivar, CGPoint))object_setIvar; 153 | fcgpoint(watchDog.target,ivar,actual); 154 | } else if (strcmp(type, @encode(CGSize)) == 0) { 155 | #warning fix me!!! 156 | CGSize actual = (CGSize)va_arg(v, CGSize); 157 | void (*fcgsize)(id, Ivar, CGSize) = (void (*)(id, Ivar, CGSize))object_setIvar; 158 | fcgsize(watchDog.target,ivar,actual); 159 | } else if (strcmp(type, @encode(double)) == 0) { 160 | double actual = (double)va_arg(v, double); 161 | void (*fdouble)(id, Ivar, double) = (void (*)(id, Ivar, double))object_setIvar; 162 | fdouble(watchDog.target,ivar,actual); 163 | } else if (strcmp(type, @encode(float)) == 0) { 164 | float actual = (float)va_arg(v, double); 165 | void (*ffloat)(id, Ivar, float) = (void (*)(id, Ivar, float))object_setIvar; 166 | ffloat(watchDog.target,ivar,actual); 167 | } else if (strcmp(type, @encode(int)) == 0) { 168 | int actual = (int)va_arg(v, int); 169 | void (*fint)(id, Ivar, int) = (void (*)(id, Ivar, int))object_setIvar; 170 | fint(watchDog.target,ivar,actual); 171 | } else if (strcmp(type, @encode(long)) == 0) { 172 | long actual = (long)va_arg(v, long); 173 | void (*flong)(id, Ivar, long) = (void (*)(id, Ivar, long))object_setIvar; 174 | flong(watchDog.target,ivar,actual); 175 | } else if (strcmp(type, @encode(long long)) == 0) { 176 | long long actual = (long long)va_arg(v, long long); 177 | void (*flonglong)(id, Ivar, long long) = (void (*)(id, Ivar, long long))object_setIvar; 178 | flonglong(watchDog.target,ivar,actual); 179 | } else if (strcmp(type, @encode(short)) == 0) { 180 | short actual = (short)va_arg(v, int); 181 | void (*fshort)(id, Ivar, short) = (void (*)(id, Ivar, short))object_setIvar; 182 | fshort(watchDog.target,ivar,actual); 183 | } else if (strcmp(type, @encode(char)) == 0) { 184 | char actual = (char)va_arg(v, int); 185 | void (*fchar)(id, Ivar, char) = (void (*)(id, Ivar, char))object_setIvar; 186 | fchar(watchDog.target,ivar,actual); 187 | } else if (strcmp(type, @encode(bool)) == 0) { 188 | bool actual = (bool)va_arg(v, int); 189 | void (*fbool)(id, Ivar, bool) = (void (*)(id, Ivar, bool))object_setIvar; 190 | fbool(watchDog.target,ivar,actual); 191 | } else if (strcmp(type, @encode(unsigned char)) == 0) { 192 | unsigned char actual = (unsigned char)va_arg(v, unsigned int); 193 | void (*funsignedchar)(id, Ivar, unsigned char) = (void (*)(id, Ivar, unsigned char))object_setIvar; 194 | funsignedchar(watchDog.target,ivar,actual); 195 | } else if (strcmp(type, @encode(unsigned int)) == 0) { 196 | unsigned int actual = (unsigned int)va_arg(v, unsigned int); 197 | void (*funsignedint)(id, Ivar, unsigned int) = (void (*)(id, Ivar, unsigned int))object_setIvar; 198 | funsignedint(watchDog.target,ivar,actual); 199 | } else if (strcmp(type, @encode(unsigned long)) == 0) { 200 | unsigned long actual = (unsigned long)va_arg(v, unsigned long); 201 | void (*funsignedlong)(id, Ivar, unsigned long) = (void (*)(id, Ivar, unsigned long))object_setIvar; 202 | funsignedlong(watchDog.target,ivar,actual); 203 | } else if (strcmp(type, @encode(unsigned long long)) == 0) { 204 | unsigned long long actual = (unsigned long long)va_arg(v, unsigned long long); 205 | void (*funsignedlonglong)(id, Ivar, unsigned long long) = (void (*)(id, Ivar, unsigned long long))object_setIvar; 206 | funsignedlonglong(watchDog.target,ivar,actual); 207 | } else if (strcmp(type, @encode(unsigned short)) == 0) { 208 | unsigned short actual = (unsigned short)va_arg(v, unsigned int); 209 | void (*funsignedshort)(id, Ivar, unsigned short) = (void (*)(id, Ivar, unsigned short))object_setIvar; 210 | funsignedshort(watchDog.target,ivar,actual); 211 | } 212 | va_end(v); 213 | return obj; 214 | } 215 | 216 | @end 217 | -------------------------------------------------------------------------------- /IQDataBindingDemo/AppDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.h 3 | // IQDataBindingDemo 4 | // 5 | // Created by lobster on 2019/5/2. 6 | // Copyright © 2019 lobster. 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 | -------------------------------------------------------------------------------- /IQDataBindingDemo/AppDelegate.m: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.m 3 | // IQDataBindingDemo 4 | // 5 | // Created by lobster on 2019/5/2. 6 | // Copyright © 2019 lobster. All rights reserved. 7 | // 8 | 9 | #import "AppDelegate.h" 10 | #import 11 | 12 | @interface AppDelegate () 13 | 14 | @end 15 | 16 | @implementation AppDelegate 17 | 18 | 19 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { 20 | // Override point for customization after application launch. 21 | return YES; 22 | } 23 | 24 | 25 | - (void)applicationWillResignActive:(UIApplication *)application { 26 | // 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. 27 | // Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game. 28 | } 29 | 30 | 31 | - (void)applicationDidEnterBackground:(UIApplication *)application { 32 | // 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. 33 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 34 | } 35 | 36 | 37 | - (void)applicationWillEnterForeground:(UIApplication *)application { 38 | // 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. 39 | } 40 | 41 | 42 | - (void)applicationDidBecomeActive:(UIApplication *)application { 43 | // 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. 44 | } 45 | 46 | 47 | - (void)applicationWillTerminate:(UIApplication *)application { 48 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 49 | } 50 | 51 | 52 | @end 53 | -------------------------------------------------------------------------------- /IQDataBindingDemo/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 | "idiom" : "ios-marketing", 90 | "size" : "1024x1024", 91 | "scale" : "1x" 92 | } 93 | ], 94 | "info" : { 95 | "version" : 1, 96 | "author" : "xcode" 97 | } 98 | } -------------------------------------------------------------------------------- /IQDataBindingDemo/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /IQDataBindingDemo/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 | -------------------------------------------------------------------------------- /IQDataBindingDemo/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 | -------------------------------------------------------------------------------- /IQDataBindingDemo/ContentModel.h: -------------------------------------------------------------------------------- 1 | // 2 | // ContentModel.h 3 | // IQDataBindingDemo 4 | // 5 | // Created by lobster on 2019/5/2. 6 | // Copyright © 2019 lobster. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | 12 | NS_ASSUME_NONNULL_BEGIN 13 | 14 | @interface ContentModel : NSObject 15 | 16 | @property (nonatomic, copy) NSString *title; 17 | @property (nonatomic, copy) NSString *content; 18 | @property (nonatomic, assign) CGPoint point; 19 | @property (nonatomic, assign) CGSize size; 20 | @property (nonatomic, assign) double db; 21 | @property (nonatomic, assign) float fl; 22 | @property (nonatomic, assign) long lg; 23 | @property (nonatomic, assign) long long llg; 24 | @property (nonatomic, assign) short st; 25 | @property (nonatomic, assign) char ch; 26 | @property (nonatomic, assign) bool bl; 27 | @property (nonatomic, assign) unsigned char ucha; 28 | @property (nonatomic, assign) unsigned long ulg; 29 | @property (nonatomic, assign) unsigned int uit; 30 | @property (nonatomic, assign) unsigned long long ullg; 31 | @property (nonatomic, assign) unsigned short ust; 32 | 33 | @end 34 | 35 | NS_ASSUME_NONNULL_END 36 | -------------------------------------------------------------------------------- /IQDataBindingDemo/ContentModel.m: -------------------------------------------------------------------------------- 1 | // 2 | // ContentModel.m 3 | // IQDataBindingDemo 4 | // 5 | // Created by lobster on 2019/5/2. 6 | // Copyright © 2019 lobster. All rights reserved. 7 | // 8 | 9 | #import "ContentModel.h" 10 | 11 | @implementation ContentModel 12 | 13 | - (void)dealloc { 14 | NSLog(@"%s",__func__); 15 | } 16 | 17 | @end 18 | -------------------------------------------------------------------------------- /IQDataBindingDemo/ContentView.h: -------------------------------------------------------------------------------- 1 | // 2 | // ContentView.h 3 | // IQDataBindingDemo 4 | // 5 | // Created by lobster on 2019/5/4. 6 | // Copyright © 2019 lobster. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | NS_ASSUME_NONNULL_BEGIN 12 | 13 | @interface ContentView : UIView 14 | 15 | 16 | @end 17 | 18 | NS_ASSUME_NONNULL_END 19 | -------------------------------------------------------------------------------- /IQDataBindingDemo/ContentView.m: -------------------------------------------------------------------------------- 1 | // 2 | // ContentView.m 3 | // IQDataBindingDemo 4 | // 5 | // Created by lobster on 2019/5/4. 6 | // Copyright © 2019 lobster. All rights reserved. 7 | // 8 | 9 | #import "ContentView.h" 10 | #import 11 | 12 | @interface ContentView () 13 | 14 | @property (nonatomic, strong) UITextField *loginTextField; 15 | @property (nonatomic, strong) UITextField *pwdTextField; 16 | 17 | @end 18 | 19 | @implementation ContentView 20 | 21 | - (void)dealloc { 22 | NSLog(@"%s",__func__); 23 | } 24 | 25 | - (id)initWithFrame:(CGRect)frame { 26 | if (self = [super initWithFrame:frame]) { 27 | [self setUpSubviews]; 28 | } 29 | return self; 30 | } 31 | 32 | - (void)setUpSubviews { 33 | 34 | [self addSubview:self.loginTextField]; 35 | [self addSubview:self.pwdTextField]; 36 | 37 | self.loginTextField.frame = CGRectMake(0, 0, self.bounds.size.width, 30); 38 | self.pwdTextField.frame = CGRectMake(0, 40, self.bounds.size.width, 30); 39 | 40 | __weak typeof(self)weakSelf = self; 41 | self.observe(@"title",^(id value){ 42 | weakSelf.loginTextField.text = value; 43 | }).observe(@"content",^(id value){ 44 | weakSelf.pwdTextField.text = value; 45 | }); 46 | 47 | } 48 | /* 49 | @property (nonatomic, assign) CGPoint point; 50 | @property (nonatomic, assign) CGSize size; 51 | @property (nonatomic, assign) double db; 52 | @property (nonatomic, assign) float fl; 53 | @property (nonatomic, assign) long lg; 54 | @property (nonatomic, assign) long long llg; 55 | @property (nonatomic, assign) short st; 56 | @property (nonatomic, assign) char ch; 57 | @property (nonatomic, assign) bool bl; 58 | @property (nonatomic, assign) unsigned char ucha; 59 | @property (nonatomic, assign) unsigned long ulg; 60 | @property (nonatomic, assign) unsigned int uit; 61 | @property (nonatomic, assign) unsigned long long ullg; 62 | @property (nonatomic, assign) unsigned short ust; 63 | */ 64 | 65 | - (BOOL)textFieldShouldReturn:(UITextField *)textField { 66 | [textField resignFirstResponder]; 67 | if (textField.text) { 68 | self.update(@"content",textField.text).update(@"point",CGPointMake(10, 20)).update(@"size",CGSizeMake(100, 200)).update(@"db",300).update(@"fl",400.0).update(@"lg",500.0).update(@"llg",70000000).update(@"st",700).update(@"ch","char").update(@"bl",true).update(@"ucha","lobster"); 69 | } 70 | return YES; 71 | } 72 | 73 | - (UITextField *)loginTextField { 74 | if (!_loginTextField) { 75 | _loginTextField = [[UITextField alloc]init]; 76 | _loginTextField.borderStyle = UITextBorderStyleRoundedRect; 77 | _loginTextField.backgroundColor = [UIColor greenColor]; 78 | _loginTextField.placeholder = @"请输入用户名"; 79 | _loginTextField.delegate = self; 80 | } 81 | return _loginTextField; 82 | } 83 | 84 | - (UITextField *)pwdTextField { 85 | if (!_pwdTextField) { 86 | _pwdTextField = [[UITextField alloc]init]; 87 | _pwdTextField.borderStyle = UITextBorderStyleRoundedRect; 88 | _pwdTextField.backgroundColor = [UIColor redColor]; 89 | _pwdTextField.placeholder = @"请输入密码"; 90 | _pwdTextField.delegate = self; 91 | } 92 | return _pwdTextField; 93 | } 94 | /* 95 | // Only override drawRect: if you perform custom drawing. 96 | // An empty implementation adversely affects performance during animation. 97 | - (void)drawRect:(CGRect)rect { 98 | // Drawing code 99 | } 100 | */ 101 | 102 | @end 103 | -------------------------------------------------------------------------------- /IQDataBindingDemo/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 | -------------------------------------------------------------------------------- /IQDataBindingDemo/ViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.h 3 | // IQDataBindingDemo 4 | // 5 | // Created by lobster on 2019/5/2. 6 | // Copyright © 2019 lobster. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface ViewController : UIViewController 12 | 13 | 14 | @end 15 | 16 | -------------------------------------------------------------------------------- /IQDataBindingDemo/ViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.m 3 | // IQDataBindingDemo 4 | // 5 | // Created by lobster on 2019/5/2. 6 | // Copyright © 2019 lobster. All rights reserved. 7 | // 8 | 9 | #import "ViewController.h" 10 | #import 11 | #import "ContentView.h" 12 | #import "ContentModel.h" 13 | 14 | @interface ViewController () 15 | 16 | @property (nonatomic, strong) ContentView *contentView; 17 | @property (nonatomic, strong) UIButton *changeButton; 18 | @property (nonatomic, strong) ContentModel *contentModel; 19 | 20 | @end 21 | 22 | @implementation ViewController 23 | 24 | - (void)viewDidLoad { 25 | [super viewDidLoad]; 26 | [self setUpSubviews]; 27 | [self configData]; 28 | 29 | // [self testModelDealloc]; 30 | // [self testViewDealloc]; 31 | } 32 | 33 | - (void)changeButtonClicked { 34 | self.contentModel.title = @"lobster"; 35 | self.contentModel.content = @"654321"; 36 | } 37 | 38 | - (void)setUpSubviews { 39 | self.contentView = [[ContentView alloc]initWithFrame:CGRectMake(100, 80, 200, 100)]; 40 | [self.view addSubview:self.contentView]; 41 | self.contentView.center = self.view.center; 42 | 43 | self.changeButton = [UIButton buttonWithType:UIButtonTypeSystem]; 44 | self.changeButton.frame = CGRectMake(self.contentView.frame.origin.x, self.contentView.frame.origin.y + 100, 200, 40); 45 | 46 | [self.changeButton setTitle:@"加载默认用户名和密码" forState:UIControlStateNormal]; 47 | [self.changeButton addTarget:self action:@selector(changeButtonClicked) forControlEvents:UIControlEventTouchUpInside]; 48 | [self.view addSubview:self.changeButton]; 49 | 50 | } 51 | 52 | - (void)configData { 53 | self.contentModel = [[ContentModel alloc]init]; 54 | self.contentModel.title = @"lobster"; 55 | self.contentModel.content = @"123456"; 56 | 57 | /*view和viewModel之间绑定*/ 58 | [self.contentView bindModel:self.contentModel]; 59 | 60 | } 61 | 62 | - (void)testViewDealloc { 63 | [self.contentView removeFromSuperview]; 64 | self.contentView = nil; 65 | } 66 | 67 | - (void)testModelDealloc { 68 | self.contentModel = nil; 69 | } 70 | 71 | @end 72 | -------------------------------------------------------------------------------- /IQDataBindingDemo/main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // IQDataBindingDemo 4 | // 5 | // Created by lobster on 2019/5/2. 6 | // Copyright © 2019 lobster. 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 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 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 all 13 | 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 THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # IQDataBinding 2 | iOS端View和ViewModel数据绑定框架,实现了自动移除,且支持函数式、链式调用,使用姿势比较优雅。 3 | 4 | ## 使用方式 5 | **一、通过Cocoapods引入工程。** 6 | 7 | ``` 8 | pod 'IQDataBinding' 9 | ``` 10 | 11 | **二、Controller** 12 | 13 | ``` 14 | /*引入NSObject+IQDataBinding头文件*/ 15 | - (void)configData { 16 | self.contentModel = [[ContentModel alloc]init]; 17 | self.contentModel.title = @"lobster"; 18 | self.contentModel.content = @"123456"; 19 | 20 | /*View和ViewModel之间绑定*/ 21 | [self.contentView bindModel:self.contentModel]; 22 | 23 | } 24 | 25 | ``` 26 | 27 | **三、View** 28 | 29 | ``` 30 | /*ViewModel >>> View*/ 31 | - (void)setUpSubviews { 32 | 33 | 34 | [self addSubview:self.loginTextField]; 35 | [self addSubview:self.pwdTextField]; 36 | 37 | self.loginTextField.frame = CGRectMake(0, 0, self.bounds.size.width, 30); 38 | self.pwdTextField.frame = CGRectMake(0, 40, self.bounds.size.width, 30); 39 | 40 | /*绑定ViewModel中title和content属性,发生改变自动触发View更新操作*/ 41 | __weak typeof(self)weakSelf = self; 42 | self.bind(@"title",^(id value){ 43 | weakSelf.loginTextField.text = value; 44 | }).bind(@"content",^(id value){ 45 | weakSelf.pwdTextField.text = value; 46 | }); 47 | 48 | } 49 | 50 | ``` 51 | ``` 52 | /*View >>> ViewModel*/ 53 | - (BOOL)textFieldShouldReturn:(UITextField *)textField { 54 | [textField resignFirstResponder]; 55 | if (textField.text) { 56 | /*函数式调用*/ 57 | self.update(@"content",textField.text).update(@"title",@"lobster"); 58 | } 59 | return YES; 60 | } 61 | ``` 62 | 63 | ## IQDataBinding踩坑记 64 | 65 | * View更新ViewModel属性时,如何让一个函数支持传输不同的参数类型? 66 | * View更新ViewModel时,如何避免触发KVO而导致死循环? 67 | * 如何自动移除KVO? 68 | 69 | **View更新ViewModel属性时,如何让一个函数支持传输不同的参数类型?** 70 | 71 | 笔者借鉴了Masonry框架的解决方案,通过宏定义+不定参数解决了传输不同的参数类型的问题。感兴趣的可以了解下Masonry中_MASBoxValue这个函数。 72 | 73 | **View更新ViewModel时,如何避免触发KVO而导致死循环?** 74 | 75 | 很显然,通过setValue:forKey:函数会触发KVO回调,所以我的解决方案是获取到IVar,直接设置实例变量的值。但是object_setIvar(id _Nullable obj, Ivar _Nonnull ivar, id _Nullable value) 函数,只接收id类型的值。Stack Overflow查询之后,发现可以通过函数类型强转的方式来解决。 76 | 77 | **如何自动移除KVO?** 78 | 79 | 这个问题就比较简单了,为了监控View的dealloc函数调用时机,我们可以通过Hook的方式,但是Hook不太推荐。尤其使用类似于Aspects(通过消息转发来实现,代价很高)进行Hook时,对于那种一秒钟调用超过1000次的业务场景会严重影响性能。所以我采用的方案是,通过给View添加一个关联对象来解决。因为我们知道对象释放时会先释放成员变量,然后再释放关联对象,所以我们可以在关联对象的dealloc方法里对观察者进行自动移除。 80 | 81 | ``` 82 | /*给view添加一个关联对象IQWatchDog,IQWatchDog职责如下 83 | 1.存储@{绑定的Key,回调Block}对应关系。 84 | 2.根据@{绑定的Key,回调Block}中的Key,进行KVO监听。 85 | 3.监听view Dealloc事件,自动移除KVO监听。 86 | */ 87 | IQWatchDog *viewAssociatedModel = objc_getAssociatedObject(self, &kViewAssociatedModelKey); 88 | if (!viewAssociatedModel) { 89 | viewAssociatedModel = [[IQWatchDog alloc]init]; 90 | viewAssociatedModel.target = model; 91 | objc_setAssociatedObject(self, &kViewAssociatedModelKey, viewAssociatedModel, OBJC_ASSOCIATION_RETAIN_NONATOMIC); 92 | } 93 | ``` 94 | ``` 95 | @interface IQWatchDog : NSObject 96 | 97 | @property (nonatomic, weak) id target; 98 | @property (nonatomic, strong) NSMutableDictionary *keyPathsAndCallBacks; 99 | 100 | @end 101 | 102 | @implementation IQWatchDog 103 | 104 | - (void)dealloc { 105 | [self.keyPathsAndCallBacks enumerateKeysAndObjectsUsingBlock:^(id _Nonnull key, id _Nonnull obj, BOOL * _Nonnull stop) { 106 | [self.target removeObserver:self forKeyPath:key]; 107 | }]; 108 | } 109 | 110 | - (void)observeKeyPath:(NSString *)keyPath callBack:(observerCallBack)callBack { 111 | NSAssert(keyPath.length, @"keyPath不合法"); 112 | /*加载默认值*/ 113 | id value = [self.target valueForKeyPath:keyPath]; 114 | if (value) { 115 | callBack(value); 116 | } 117 | /*添加观察者*/ 118 | [self.keyPathsAndCallBacks setObject:callBack forKey:keyPath]; 119 | [self.target addObserver:self forKeyPath:keyPath options:NSKeyValueObservingOptionNew context:NULL]; 120 | } 121 | 122 | - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context { 123 | observerCallBack callBack = self.keyPathsAndCallBacks[keyPath]; 124 | if (callBack) { 125 | callBack(change[NSKeyValueChangeNewKey]); 126 | } 127 | } 128 | 129 | - (void)removeAllObservers { 130 | [self.keyPathsAndCallBacks enumerateKeysAndObjectsUsingBlock:^(id _Nonnull key, id _Nonnull obj, BOOL * _Nonnull stop) { 131 | [self.target removeObserver:self forKeyPath:key]; 132 | }]; 133 | } 134 | 135 | - (NSMutableDictionary *)keyPathsAndCallBacks { 136 | if (!_keyPathsAndCallBacks) { 137 | _keyPathsAndCallBacks = [NSMutableDictionary dictionary]; 138 | } 139 | return _keyPathsAndCallBacks; 140 | } 141 | 142 | @end 143 | 144 | ``` 145 | 146 | ## 联系我 147 | PRs or Issues. 148 | Email:[zhiwei.geek@gmail.com](mailto:zhiwei.geek@gmail.com) 149 | --------------------------------------------------------------------------------