Whether using the mvc or mvvm architecture, we need to ensure that model changes can be synchronized to relevant components in a timely manner. Similar to the monthly Observer Model, kvo can be used in ios to accomplish such a thing, but every time this method is used, it will confuse the code. Here THBinder can be used to complete this task on github. At the same time, I have done some processing on this code, so that we can use a simple macro to complete the process. Do not save the THBinder instance.
#import "THBinder.h"#import "THObserver.h"#import
#import
#define TMBindDictoryKey "__TMBindDictoryKey"#define BindKey(target,keyPath) [NSString stringWithFormat:@"__binder__%@",keyPath]static NSMutableSet *swizzledClasses() {static dispatch_once_t onceToken;static NSMutableSet *swizzledClasses = nil;dispatch_once(&onceToken, ^{swizzledClasses = [[NSMutableSet alloc] init];});return swizzledClasses;}static void swizzleDeallocIfNeeded(Class classToSwizzle) {@synchronized (swizzledClasses()) {NSString *className = NSStringFromClass(classToSwizzle);if ([swizzledClasses() containsObject:className]) return; SEL deallocSelector = sel_registerName("dealloc");SEL swizzleDeallocSelector = sel_registerName("swizzleDelloc");__block void (*originalDealloc)(__unsafe_unretained id, SEL) = NULL; id newDealloc = ^(__unsafe_unretained id self) { if(class_respondsToSelector(classToSwizzle,swizzleDeallocSelector)) objc_msgSend(self,swizzleDeallocSelector);if (originalDealloc == NULL) {struct objc_super superInfo = {.receiver = self,.super_class = class_getSuperclass(classToSwizzle)};objc_msgSendSuper(&superInfo, deallocSelector);} else {originalDealloc(self, deallocSelector);}};IMP newDeallocIMP = imp_implementationWithBlock(newDealloc);if (!class_addMethod(classToSwizzle, deallocSelector, newDeallocIMP, "v@:")) {// The class already contains a method implementation.Method deallocMethod = class_getInstanceMethod(classToSwizzle, deallocSelector);// We need to store original implementation before setting new implementation// in case method is called at the time of setting.originalDealloc = (__typeof__(originalDealloc))method_getImplementation(deallocMethod);// We need to store original implementation again, in case it just changed.originalDealloc = (__typeof__(originalDealloc))method_setImplementation(deallocMethod, newDeallocIMP);} [swizzledClasses() addObject:className];}}@interface NSObject (SupportBinding)- (void)setBinder:(id)binder keyPath:(NSString*)keyPath;@end@implementation NSObject (SupportBinding)- (void)swizzleDelloc{ NSMutableDictionary* bindDict = objc_getAssociatedObject(self,TMBindDictoryKey); [bindDict enumerateKeysAndObjectsUsingBlock:^(id key, NSArray *obj, BOOL *stop) { [obj enumerateObjectsUsingBlock:^(THBinder* binder, NSUInteger idx, BOOL *stop) { [binder stopBinding]; }]; }]; objc_setAssociatedObject(self, TMBindDictoryKey, nil, OBJC_ASSOCIATION_RETAIN_NONATOMIC);}- (void)setBinder:(id)binder keyPath:(NSString*)keyPath{ NSMutableDictionary* bindDict = objc_getAssociatedObject(self,TMBindDictoryKey); if(!bindDict){ bindDict = [NSMutableDictionary new]; objc_setAssociatedObject(self, TMBindDictoryKey, bindDict, OBJC_ASSOCIATION_RETAIN_NONATOMIC); } NSString* keyName = BindKey(self, keyPath); id object = [bindDict valueForKey:keyName]; if([object containsObject:binder]){ return; } if(!object){ object = [NSMutableArray new]; } [object addObject:binder]; [bindDict setValue:object forKey:keyName]; swizzleDeallocIfNeeded(self.class);}@end
At the same time, you need to add a method to the THBinder so that the binder can be saved to the instance to be observed.
- (id)initForBindingFromObject:(id)fromObject keyPath:(NSString *)fromKeyPath toObject:(id)toObject keyPath:(NSString *)toKeyPath transformationBlock:(THBinderTransformationBlock)transformationBlock{ if((self = [super init])) { __weak id wToObject = toObject; NSString *myToKeyPath = [toKeyPath copy]; THObserverBlockWithChangeDictionary changeBlock; if(transformationBlock) { changeBlock = [^(NSDictionary *change) { [wToObject setValue:transformationBlock(change[NSKeyValueChangeNewKey]) forKeyPath:myToKeyPath]; } copy]; } else { changeBlock = [^(NSDictionary *change) { [wToObject setValue:change[NSKeyValueChangeNewKey] forKeyPath:myToKeyPath]; } copy]; } _observer = [THObserver observerForObject:fromObject keyPath:fromKeyPath options:NSKeyValueObservingOptionNew changeBlock:changeBlock]; [fromObject setBinder:self keyPath:fromKeyPath]; } return self;}
Here, I used the macro in reactivecocoa to organize the TMBIND macro. In this way, the macro is OK. In THBinder, the generated binder is automatically placed in the observed instance.
//// Binder. h // KVODemo /// Created by Tommy on 14-6-13. // Copyright (c) 2014 com. taobao. all rights reserved. // # ifndef KVODemo_Binder_h # define KVODemo_Binder_h # import "EXTKeyPathCoding. h "# import" THObserver. h "# import" THBinder. h "// one-way bind # define TMBIND (_ fromObject _, _ fromKeyPath _, _ toObject _, _ toKeyPath _) \ [THBinder binderFromObject: _ fromObject _ keyPath: @ keypath (_ fromObject _, _ fromKeyPath _) toObject: _ toObject _ keyPath: @ keypath (_ toObject _, _ toKeyPath _)] # define TMBIND_WITH_TRANSFORMBLOCK (_ fromObject _, _ fromKeyPath _, _ toObject _, _ toKeyPath _, _ transfromBlock _) \ [THBinder binderFromObject: _ fromObject _ keyPath: @ keypath (_ fromObject _, _ fromKeyPath _) toObject: _ toObject _ keyPath: @ keypath (_ toObject _, _ toKeyPath _) transformationBlock: _ transfromBlock _]; # endif