├── .swift-version ├── FishBind.podspec ├── FishBind ├── IIFishBind.h └── IIFishBind.m ├── FishBindDemo-iOS ├── AppDelegate.h ├── AppDelegate.m ├── Assets.xcassets │ └── AppIcon.appiconset │ │ └── Contents.json ├── Base.lproj │ ├── LaunchScreen.storyboard │ └── Main.storyboard ├── Info.plist ├── TestA.h ├── TestA.m ├── TestB.h ├── TestB.m ├── TestC.h ├── TestC.m ├── TestD.h ├── TestD.m ├── ViewController.h ├── ViewController.m └── main.m ├── FishBindDemo.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcuserdata │ │ └── WELCommand.xcuserdatad │ │ └── UserInterfaceState.xcuserstate └── xcuserdata │ ├── WELCommand.xcuserdatad │ └── xcschemes │ │ └── FishBindDemo.xcscheme │ └── welcommand.xcuserdatad │ ├── xcdebugger │ └── Breakpoints_v2.xcbkptlist │ └── xcschemes │ ├── FishBindDemo-iOS.xcscheme │ └── xcschememanagement.plist ├── FishBindIcon.png ├── LICENSE └── README.md /.swift-version: -------------------------------------------------------------------------------- 1 | 2.3 2 | -------------------------------------------------------------------------------- /FishBind.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | s.name = "FishBind" 3 | s.version = "0.2.4" 4 | s.summary = "FishBind supports a simple way to bind messages between objects." 5 | s.description = "Support binding properties, methods, block. Supports one-way binding & bidirectional binding." 6 | s.homepage = "https://github.com/welcommand/FishBind" 7 | s.license = { :type => 'MIT', :file => 'LICENSE' } 8 | s.author = { "WELCommand" => "ios_programming@163.com" } 9 | s.ios.deployment_target = "7.0" 10 | s.osx.deployment_target = "10.9" 11 | s.watchos.deployment_target = "2.0" 12 | s.tvos.deployment_target = "9.0" 13 | s.source = { :git => "https://github.com/welcommand/FishBind.git", :tag => s.version } 14 | s.source_files = "FishBind/*" 15 | s.framework = "Foundation" 16 | s.library = "objc" 17 | end 18 | -------------------------------------------------------------------------------- /FishBind/IIFishBind.h: -------------------------------------------------------------------------------- 1 | // 2 | // IIFishBind.h 3 | // FishBindDemo 4 | // 5 | // Created by WELCommand on 2017/9/4. 6 | // Copyright © 2017年 WELCommand. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface IIFishCallBack : NSObject 12 | @property (nonatomic, weak) id tager; 13 | @property (nonatomic, copy) NSString *selector; 14 | @property (nonatomic, strong) NSArray *args; 15 | @property (nonatomic, assign) id resule; 16 | @end 17 | 18 | typedef void(^IIFishCallBackBlock) (IIFishCallBack *callBack, id deadFish); 19 | 20 | @interface IIFish : NSObject 21 | // property bind 22 | + (instancetype)post:(id)object property:(NSString *)property; 23 | + (instancetype)observer:(id)object property:(NSString *)property; 24 | 25 | // method bind 26 | + (instancetype)post:(id)object selector:(SEL)selector; 27 | + (instancetype)observer:(id)object callBack:(IIFishCallBackBlock)callBack; 28 | 29 | // bind a block, using observer:callBack: to observe 30 | + (instancetype)postBlock:(id)blockObject; 31 | 32 | // bilateral bind 33 | + (instancetype)both:(id)object property:(NSString *)property callBack:(IIFishCallBackBlock)callBack; 34 | + (instancetype)both:(id)object selector:(SEL)selector callBack:(IIFishCallBackBlock)callBack; 35 | 36 | @end 37 | 38 | @interface IIFishBind : NSObject 39 | 40 | + (void)bindFishes:(NSArray *)fishes; 41 | 42 | @end 43 | 44 | @interface NSObject (IIFishBind) 45 | 46 | - (NSArray *)iifish_allKeys; 47 | - (NSArray *)iifish_observersWithKey:(NSString *)key; 48 | - (NSArray *)iifish_observersWithProperty:(NSString *)property; 49 | 50 | - (void)iifish_removeObserverWithKey:(NSString *)key; 51 | - (void)iifish_removeObserverWithkey:(NSString *)key andObject:(id)object; 52 | 53 | - (void)iifish_removeObserverWithProperty:(NSString *)property; 54 | - (void)iifish_removeObserverWithProperty:(NSString *)property andObject:(id)object; 55 | 56 | - (void)iifish_removeObserverWithObject:(id)object; 57 | 58 | - (void)iifish_removeAllObserver; 59 | 60 | @end 61 | 62 | 63 | //##### 64 | //##### hook all methods 65 | //##### 66 | 67 | 68 | typedef NS_OPTIONS(NSUInteger, IIFishWatchOptions) { 69 | IIFishWatchOptionsInstanceMethod = (1 << 0), 70 | IIFishWatchOptionsClassMethod = (1 << 1), 71 | IIFishWatchOptionsWithoutNSObject = (1 << 2) 72 | }; 73 | 74 | 75 | 76 | typedef void(^IIFishWatchCallBackBlock) (IIFishCallBack *callBack); 77 | 78 | @interface NSObject (IIFishWatch) 79 | + (void)iifish_watchMethodsOptions:(IIFishWatchOptions)options callback:(IIFishWatchCallBackBlock)callback; 80 | - (void)iifish_watchMethodsOptions:(IIFishWatchOptions)options callback:(IIFishWatchCallBackBlock)callback; 81 | @end 82 | 83 | -------------------------------------------------------------------------------- /FishBind/IIFishBind.m: -------------------------------------------------------------------------------- 1 | 2 | // 3 | // IIFishBind.m 4 | // FishBindDemo 5 | // 6 | // Created by WELCommand on 2017/9/4. 7 | // Copyright © 2017年 WELCommand. All rights reserved. 8 | // 9 | 10 | #import "IIFishBind.h" 11 | #import 12 | #import 13 | #import 14 | 15 | static char const* IIFish_Prefix = "IIFish_"; 16 | static char const* IIFishWatchCallbackKey = "IIFishWatchCallbackKey"; 17 | 18 | typedef NS_OPTIONS(NSUInteger, IIFishFlage) { 19 | IIFish_IsBlock = (1 << 0), 20 | IIFish_Post = (1 << 1), 21 | IIFish_Observer = (1 << 2), 22 | IIFish_Property = (1 << 3), 23 | IIFish_Seletor = (1 << 4) 24 | }; 25 | 26 | 27 | @implementation IIFishCallBack 28 | - (NSString *)description { 29 | return [NSString stringWithFormat:@"\ntager = %@\nselector = %@\nargs = %@\nresule = %@", _tager, _selector, _args, _resule]; 30 | } 31 | @end 32 | 33 | 34 | #pragma mark- 35 | #pragma mark- IIFish 36 | 37 | @interface IIFish () 38 | @property (nonatomic, assign) IIFishFlage flag; 39 | @property (nonatomic, weak) id object; 40 | @property (nonatomic, copy) NSString *property; 41 | @property (nonatomic, assign) SEL selector; 42 | @property (nonatomic, copy) IIFishCallBackBlock callBack; 43 | @end 44 | 45 | @implementation IIFish 46 | 47 | + (instancetype)fish:(id)object property:(NSString*)property selector:(SEL)selector callBack:(IIFishCallBackBlock)callBack flag:(NSInteger)flag { 48 | IIFish *fish = [[self alloc] init]; 49 | fish.object = object; 50 | fish.selector = selector; 51 | fish.property = property; 52 | fish.callBack = callBack; 53 | fish.flag = flag; 54 | return fish; 55 | } 56 | 57 | + (instancetype)both:(id)object selector:(SEL)selector callBack:(IIFishCallBackBlock)callBack { 58 | return [self fish:object property:nil selector:selector callBack:callBack flag:IIFish_Seletor]; 59 | } 60 | + (instancetype)both:(id)object property:(NSString*)property callBack:(IIFishCallBackBlock)callBack { 61 | return [self fish:object property:property selector:nil callBack:callBack flag:IIFish_Property]; 62 | } 63 | + (instancetype)postBlock:(id)blockObject { 64 | return [self fish:blockObject property:nil selector:nil callBack:nil flag:IIFish_Post | IIFish_IsBlock]; 65 | } 66 | + (instancetype)post:(id)object property:(NSString *)property { 67 | return [self fish:object property:property selector:nil callBack:nil flag:IIFish_Post | IIFish_Property]; 68 | } 69 | + (instancetype)post:(id)object selector:(SEL)selector { 70 | return [self fish:object property:nil selector:selector callBack:nil flag:IIFish_Post | IIFish_Seletor]; 71 | } 72 | + (instancetype)observer:(id)object property:(NSString *)property { 73 | return [self fish:object property:property selector:nil callBack:nil flag:IIFish_Observer | IIFish_Property]; 74 | } 75 | + (instancetype)observer:(id)object callBack:(IIFishCallBackBlock)callBack { 76 | return [self fish:object property:nil selector:nil callBack:callBack flag:IIFish_Observer | IIFish_Seletor]; 77 | } 78 | @end 79 | 80 | 81 | #pragma mark- 82 | #pragma mark- Dead Fish 83 | 84 | @interface IIDeadFish : NSProxy 85 | @property (nonatomic, weak, readonly) id target; 86 | - (instancetype)initWithTarget:(id)target; 87 | @end 88 | 89 | @implementation IIDeadFish 90 | 91 | - (instancetype)initWithTarget:(id)target { 92 | _target = target; 93 | return self; 94 | } 95 | 96 | - (BOOL)isEqual:(id)object { 97 | return [_target isEqual:object]; 98 | } 99 | 100 | - (NSUInteger)hash { 101 | return [_target hash]; 102 | } 103 | 104 | - (Class)superclass { 105 | return [_target superclass]; 106 | } 107 | 108 | - (Class)class { 109 | return [_target class]; 110 | } 111 | 112 | - (BOOL)isKindOfClass:(Class)aClass { 113 | return [_target isKindOfClass:aClass]; 114 | } 115 | 116 | - (BOOL)isMemberOfClass:(Class)aClass { 117 | return [_target isMemberOfClass:aClass]; 118 | } 119 | 120 | - (BOOL)conformsToProtocol:(Protocol *)aProtocol { 121 | return [_target conformsToProtocol:aProtocol]; 122 | } 123 | 124 | - (BOOL)respondsToSelector:(SEL)aSelector { 125 | return [_target respondsToSelector:aSelector]; 126 | } 127 | 128 | - (BOOL)isProxy { 129 | return YES; 130 | } 131 | 132 | - (NSString *)description { 133 | return [_target description]; 134 | } 135 | 136 | - (NSString *)debugDescription { 137 | return [_target debugDescription]; 138 | } 139 | 140 | - (nullable NSMethodSignature *)methodSignatureForSelector:(SEL)sel { 141 | return [_target methodSignatureForSelector:sel]; 142 | } 143 | 144 | - (void)forwardInvocation:(NSInvocation *)invocation { 145 | invocation.target = _target; 146 | 147 | SEL orgSel =NSSelectorFromString( [NSString stringWithFormat:@"%s%s",IIFish_Prefix, sel_getName(invocation.selector)]); 148 | Method method = class_getInstanceMethod(object_getClass(_target), orgSel); 149 | if (method) { 150 | invocation.selector = orgSel; 151 | } 152 | [invocation invoke]; 153 | } 154 | @end 155 | 156 | 157 | @interface NSObject(IIFishBind_DeadFish) 158 | @property (nonatomic, readonly) id iiDeadFish; 159 | @end 160 | 161 | @implementation NSObject (IIFishBind_DeadFish) 162 | 163 | - (id)iiDeadFish { 164 | Class cls = object_getClass(self); 165 | 166 | if (strncmp(class_getName(cls),IIFish_Prefix,strlen(IIFish_Prefix))) { 167 | return self; 168 | } 169 | IIDeadFish *deadFish = objc_getAssociatedObject(self, _cmd); 170 | if (!deadFish) { 171 | deadFish = [[IIDeadFish alloc] initWithTarget:self]; 172 | objc_setAssociatedObject(self, _cmd, deadFish, OBJC_ASSOCIATION_RETAIN_NONATOMIC); 173 | } 174 | return deadFish; 175 | } 176 | @end 177 | 178 | 179 | #pragma mark- 180 | #pragma mark- ObserverAsset 181 | 182 | @interface IIObserverAsset : NSObject 183 | 184 | // key : orgSelector value : info 185 | @property (nonatomic, strong) NSMutableDictionary *methodAsset; 186 | //key : orgSelector value : observer 187 | @property (nonatomic, strong) NSMutableDictionary *>*observerAsset; 188 | @property (nonatomic, strong) dispatch_queue_t rwQueue; 189 | 190 | - (void)asset:(void (^)(NSMutableDictionary * methodAsset, NSMutableDictionary *>*observerAsset))asset; 191 | 192 | @end 193 | 194 | @implementation IIObserverAsset 195 | 196 | - (id)init { 197 | if (self = [super init]) { 198 | _methodAsset = [NSMutableDictionary new]; 199 | _observerAsset = [NSMutableDictionary new]; 200 | _rwQueue = dispatch_queue_create("com.IIFishAsset.FishBind", DISPATCH_QUEUE_CONCURRENT); 201 | } 202 | return self; 203 | } 204 | 205 | - (void)asset:(void (^)(NSMutableDictionary * methodAsset, NSMutableDictionary *>*observerAsset))asset { 206 | dispatch_barrier_sync(_rwQueue, ^{ 207 | asset(_methodAsset, _observerAsset); 208 | }); 209 | } 210 | @end 211 | 212 | //#pragma mark- runtime add 213 | // 214 | //static Method IIFish_Class_getInstanceMethodWithoutSuper(Class cls, SEL sel) { 215 | // unsigned int count; 216 | // Method *methods = class_copyMethodList(cls, &count); 217 | // Method m = NULL; 218 | // for (unsigned int i = 0; i < count; i ++) { 219 | // if (method_getName(methods[i]) == sel) { 220 | // m = methods[i]; 221 | // break; 222 | // } 223 | // } 224 | // 225 | // free(methods); 226 | // return m; 227 | //} 228 | 229 | #pragma mark- Invocation 230 | // https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Articles/ocrtTypeEncodings.html 231 | 232 | static id iifish_invocation_getReturnValue(NSInvocation *invocation) { 233 | const char *argType = [invocation.methodSignature methodReturnType]; 234 | id argBox; 235 | 236 | #define IIFish_GetReturnValueInBox(coding, type) case coding : {\ 237 | type arg;\ 238 | [invocation getReturnValue:&arg];\ 239 | argBox = @(arg);\ 240 | } break; 241 | 242 | switch (argType[0]) { 243 | IIFish_GetReturnValueInBox('c', char) 244 | IIFish_GetReturnValueInBox('i', int) 245 | IIFish_GetReturnValueInBox('s', short) 246 | IIFish_GetReturnValueInBox('l', long) 247 | IIFish_GetReturnValueInBox('q', long long) 248 | IIFish_GetReturnValueInBox('^', long long) 249 | IIFish_GetReturnValueInBox('C', unsigned char) 250 | IIFish_GetReturnValueInBox('I', unsigned int) 251 | IIFish_GetReturnValueInBox('S', unsigned short) 252 | IIFish_GetReturnValueInBox('L', unsigned long) 253 | IIFish_GetReturnValueInBox('Q', unsigned long long) 254 | IIFish_GetReturnValueInBox('f', float) 255 | IIFish_GetReturnValueInBox('d', double) 256 | IIFish_GetReturnValueInBox('B', BOOL) 257 | case '*': { 258 | char *arg; 259 | [invocation getReturnValue:&arg]; 260 | argBox = [[NSString alloc] initWithUTF8String:arg]; 261 | } break; 262 | case '@': { 263 | __autoreleasing id arg; 264 | [invocation getReturnValue:&arg]; 265 | __weak id weakArg = arg; 266 | argBox = ^(){return weakArg;}; 267 | } break; 268 | case '#': { 269 | Class arg; 270 | [invocation getReturnValue:&arg]; 271 | argBox = NSStringFromClass(arg); 272 | } break; 273 | case ':': { 274 | SEL arg; 275 | [invocation getReturnValue:&arg]; 276 | argBox = NSStringFromSelector(arg); 277 | } break; 278 | case '{': { 279 | NSUInteger valueSize = 0; 280 | NSGetSizeAndAlignment(argType, &valueSize, NULL); 281 | unsigned char arg[valueSize]; 282 | [invocation getReturnValue:&arg]; 283 | argBox = [NSValue value:arg withObjCType:argType]; 284 | } break; 285 | case 'v': 286 | break; 287 | default: { 288 | void *arg; 289 | [invocation getReturnValue:&arg]; 290 | argBox = (__bridge id)arg; 291 | } 292 | } 293 | 294 | return argBox; 295 | } 296 | 297 | static NSArray *iifish_invocation_getArguments(NSInvocation *invocation, NSInteger beginIndex) { 298 | 299 | NSMutableArray *args = [NSMutableArray new]; 300 | for (NSInteger i = beginIndex; i < [invocation.methodSignature numberOfArguments]; i ++) { 301 | const char *argType = [invocation.methodSignature getArgumentTypeAtIndex:i]; 302 | id argBox; 303 | 304 | #define IIFish_GetArgumentValueInBox(coding, type) case coding : {\ 305 | type arg;\ 306 | [invocation getArgument:&arg atIndex:i];\ 307 | argBox = @(arg);\ 308 | } break; 309 | 310 | switch (argType[0]) { 311 | IIFish_GetArgumentValueInBox('c', char) 312 | IIFish_GetArgumentValueInBox('i', int) 313 | IIFish_GetArgumentValueInBox('s', short) 314 | IIFish_GetArgumentValueInBox('l', long) 315 | IIFish_GetArgumentValueInBox('q', long long) 316 | IIFish_GetArgumentValueInBox('^', long long) 317 | IIFish_GetArgumentValueInBox('C', unsigned char) 318 | IIFish_GetArgumentValueInBox('I', unsigned int) 319 | IIFish_GetArgumentValueInBox('S', unsigned short) 320 | IIFish_GetArgumentValueInBox('L', unsigned long) 321 | IIFish_GetArgumentValueInBox('Q', unsigned long long) 322 | IIFish_GetArgumentValueInBox('f', float) 323 | IIFish_GetArgumentValueInBox('d', double) 324 | IIFish_GetArgumentValueInBox('B', BOOL) 325 | case '*': { 326 | char *arg; 327 | [invocation getArgument:&arg atIndex:i]; 328 | argBox = [[NSString alloc] initWithUTF8String:arg]; 329 | } break; 330 | case '@': { 331 | __autoreleasing id arg; 332 | [invocation getArgument:&arg atIndex:i]; 333 | __weak id weakArg = arg; 334 | argBox = ^(){return weakArg;}; 335 | } break; 336 | case '#': { 337 | Class arg; 338 | [invocation getArgument:&arg atIndex:i]; 339 | argBox = NSStringFromClass(arg); 340 | } break; 341 | case ':': { 342 | SEL arg; 343 | [invocation getArgument:&arg atIndex:i]; 344 | argBox = NSStringFromSelector(arg); 345 | } break; 346 | case '{': { 347 | NSUInteger valueSize = 0; 348 | NSGetSizeAndAlignment(argType, &valueSize, NULL); 349 | unsigned char arg[valueSize]; 350 | [invocation getArgument:&arg atIndex:i]; 351 | argBox = [NSValue value:arg withObjCType:argType]; 352 | } break; 353 | default: { 354 | void *arg; 355 | [invocation getArgument:&arg atIndex:i]; 356 | argBox = (__bridge id)arg; 357 | } 358 | } 359 | if (argBox) { 360 | [args addObject:argBox]; 361 | } 362 | } 363 | 364 | return args; 365 | } 366 | 367 | 368 | static IIFishCallBack* iifish_invocation_getCallback(NSInvocation *invo) { 369 | IIFishCallBack *callBack = [[IIFishCallBack alloc] init]; 370 | callBack.tager = invo.target; 371 | 372 | if ([callBack.tager isKindOfClass:NSClassFromString(@"NSBlock")]) { 373 | callBack.args = iifish_invocation_getArguments(invo,1); 374 | } else { 375 | callBack.selector = NSStringFromSelector(invo.selector); 376 | callBack.args = iifish_invocation_getArguments(invo,2); 377 | } 378 | 379 | callBack.resule = iifish_invocation_getReturnValue(invo); 380 | return callBack; 381 | } 382 | 383 | #pragma mark- 384 | #pragma mark- msgForward 385 | 386 | // code from 387 | // https://github.com/bang590/JSPatch/blob/master/JSPatch/JPEngine.m 388 | // line 975 389 | 390 | static IMP iifish_getMsgForward(const char *methodTypes) { 391 | IMP msgForwardIMP = _objc_msgForward; 392 | #if !defined(__arm64__) 393 | if (methodTypes[0] == '{') { 394 | NSMethodSignature *methodSignature = [NSMethodSignature signatureWithObjCTypes:methodTypes]; 395 | if ([methodSignature.debugDescription rangeOfString:@"is special struct return? YES"].location != NSNotFound) { 396 | msgForwardIMP = (IMP)_objc_msgForward_stret; 397 | } 398 | } 399 | #endif 400 | return msgForwardIMP; 401 | } 402 | 403 | static BOOL iifish_isMsgForward(IMP imp) { 404 | return (imp == _objc_msgForward 405 | #if !defined(__arm64__) 406 | || imp == _objc_msgForward_stret 407 | #endif 408 | ); 409 | } 410 | 411 | #pragma mark- 412 | #pragma mark- Block Hook 413 | //code from 414 | //https://opensource.apple.com/source/libclosure/libclosure-67 415 | 416 | typedef NS_OPTIONS(int, IIFishBlockFlage) { 417 | IIFishBLOCK_HAS_COPY_DISPOSE = (1 << 25), 418 | IIFishBLOCK_HAS_SIGNATURE = (1 << 30) 419 | }; 420 | 421 | struct IIFishBlock_descriptor_1 { 422 | uintptr_t reserved; 423 | uintptr_t size; 424 | }; 425 | 426 | struct IIFishBlock_descriptor_2 { 427 | void (*copy)(void *dst, const void *src); 428 | void (*dispose)(const void *); 429 | }; 430 | 431 | struct IIFishBlock_descriptor_3 { 432 | const char *signature; 433 | const char *layout; 434 | }; 435 | 436 | struct IIFishBlock_layout { 437 | void *isa; 438 | volatile int32_t flags; 439 | int32_t reserved; 440 | void (*invoke)(void *, ...); 441 | struct IIFishBlock_descriptor_1 *descriptor; 442 | }; 443 | typedef struct IIFishBlock_layout *IIFishBlock; 444 | 445 | static struct IIFishBlock_descriptor_2 * _IIFish_Block_descriptor_2(IIFishBlock aBlock) 446 | { 447 | if (! (aBlock->flags & IIFishBLOCK_HAS_COPY_DISPOSE)) return NULL; 448 | uint8_t *desc = (uint8_t *)aBlock->descriptor; 449 | desc += sizeof(struct IIFishBlock_descriptor_1); 450 | return (struct IIFishBlock_descriptor_2 *)desc; 451 | } 452 | 453 | static struct IIFishBlock_descriptor_3 * _IIFish_Block_descriptor_3(IIFishBlock aBlock) 454 | { 455 | if (! (aBlock->flags & IIFishBLOCK_HAS_SIGNATURE)) return NULL; 456 | uint8_t *desc = (uint8_t *)aBlock->descriptor; 457 | desc += sizeof(struct IIFishBlock_descriptor_1); 458 | if (aBlock->flags & IIFishBLOCK_HAS_COPY_DISPOSE) { 459 | desc += sizeof(struct IIFishBlock_descriptor_2); 460 | } 461 | return (struct IIFishBlock_descriptor_3 *)desc; 462 | } 463 | 464 | #pragma mark- 465 | 466 | static IIObserverAsset *iifish_object_getAsset(id object); 467 | static NSString const *IIFishBlockObserverKey = @"IIFishBlockObserverKey"; 468 | 469 | static id iifish_block_getTempBlock(IIFishBlock block) { 470 | return objc_getAssociatedObject((__bridge id)block, @"IIFish_Block_TempBlock"); 471 | } 472 | 473 | static void iifish_block_setTempGlobalBlock(IIFishBlock block, struct IIFishBlock_layout tempBlockLayout) { 474 | NSValue *blockValue = [NSValue value:&tempBlockLayout withObjCType:@encode(struct IIFishBlock_layout)]; 475 | objc_setAssociatedObject((__bridge id)block, @"IIFish_Block_TempBlock", blockValue, OBJC_ASSOCIATION_RETAIN_NONATOMIC); 476 | } 477 | 478 | static void iifish_block_setTempMallocBlock(IIFishBlock block, IIFishBlock tempBlock) { 479 | objc_setAssociatedObject((__bridge id)block, @"IIFish_Block_TempBlock", (__bridge id)tempBlock, OBJC_ASSOCIATION_ASSIGN); 480 | } 481 | 482 | static long long iifish_block_getDisposeFunc(id block) { 483 | return [objc_getAssociatedObject(block, "IIFish_Block_DisposeFunc") longLongValue]; 484 | } 485 | 486 | static void iifish_block_setDisposeFunc(id block, long long disposeFuncAdders) { 487 | objc_setAssociatedObject(block, "IIFish_Block_DisposeFunc", @(disposeFuncAdders), OBJC_ASSOCIATION_RETAIN_NONATOMIC); 488 | } 489 | 490 | void iifish_block_disposeFunc(const void * block_Layout) { 491 | 492 | IIFishBlock block = (IIFishBlock)block_Layout; 493 | id tempBlock = iifish_block_getTempBlock(block); 494 | free((__bridge void *)tempBlock); 495 | 496 | long long disposeAdders = iifish_block_getDisposeFunc((__bridge id)(block_Layout)); 497 | 498 | void (*disposeFunc)(const void *) = (void (*)(const void *))disposeAdders; 499 | if (disposeFunc) { 500 | disposeFunc(block_Layout); 501 | } 502 | } 503 | 504 | static void iifish_block_hookDisposeFunc(IIFishBlock block) { 505 | if (block->flags & IIFishBLOCK_HAS_COPY_DISPOSE) { 506 | struct IIFishBlock_descriptor_2 *descriptor_2 = _IIFish_Block_descriptor_2(block); 507 | if (descriptor_2->dispose != iifish_block_disposeFunc) { 508 | long long disposeAdders = (long long)descriptor_2->dispose; 509 | 510 | iifish_block_setDisposeFunc((__bridge id)(block), disposeAdders); 511 | descriptor_2->dispose = iifish_block_disposeFunc; 512 | } 513 | } 514 | } 515 | 516 | static void iifish_block_deepCopy(IIFishBlock block) { 517 | struct IIFishBlock_descriptor_2 *descriptor_2 = _IIFish_Block_descriptor_2(block); 518 | if (descriptor_2) { 519 | IIFishBlock newBlock = malloc(block->descriptor->size); 520 | if (!newBlock) return; 521 | memmove(newBlock, block, block->descriptor->size); 522 | descriptor_2->copy(newBlock, block); 523 | iifish_block_setTempMallocBlock(block, newBlock); 524 | iifish_block_hookDisposeFunc(block); 525 | } else { 526 | struct IIFishBlock_layout block_layout; 527 | block_layout.isa = block->isa; 528 | block_layout.flags = block->flags; 529 | block_layout.invoke = block->invoke; 530 | block_layout.reserved = block->reserved; 531 | block_layout.descriptor = block->descriptor; 532 | iifish_block_setTempGlobalBlock(block, block_layout); 533 | } 534 | } 535 | 536 | #pragma mark- 537 | #pragma mark- NSBlock Hook 538 | 539 | static void iifish_block_forwardInvocation(id self, SEL _cmd, NSInvocation *invo) { 540 | 541 | IIFishBlock block = (__bridge void *)invo.target; 542 | 543 | id tempBlock = iifish_block_getTempBlock(block); 544 | if (![tempBlock isKindOfClass:NSClassFromString(@"NSBlock")]) { 545 | struct IIFishBlock_layout tb; 546 | [(NSValue *)tempBlock getValue:&tb]; 547 | tempBlock = (__bridge id)&tb; 548 | } 549 | 550 | invo.target = tempBlock; 551 | [invo invoke]; 552 | 553 | IIObserverAsset *asseet = iifish_object_getAsset((__bridge id)block); 554 | __block NSArray *observers; 555 | NSString *key = [IIFishBlockObserverKey copy]; 556 | [asseet asset:^(NSMutableDictionary *methodAsset, NSMutableDictionary *> *observerAsset) { 557 | observers = [[observerAsset objectForKey:key] allObjects]; 558 | }]; 559 | 560 | for (IIFish *fish in observers) { 561 | if (fish.callBack) { 562 | fish.callBack(iifish_invocation_getCallback(invo), [fish.object iiDeadFish]); 563 | } 564 | } 565 | } 566 | 567 | NSMethodSignature *iifish_block_methodSignatureForSelector(id self, SEL _cmd, SEL aSelector) { 568 | struct IIFishBlock_descriptor_3 *descriptor_3 = _IIFish_Block_descriptor_3((__bridge void *)self); 569 | return [NSMethodSignature signatureWithObjCTypes:descriptor_3->signature]; 570 | } 571 | 572 | static void iifish_NSBlock_hookOnces() { 573 | 574 | static dispatch_once_t onceToken; 575 | dispatch_once(&onceToken, ^{ 576 | Class cls = NSClassFromString(@"NSBlock"); 577 | 578 | #define IIFish_StrongHookMethod(selector, func) {Method method = class_getInstanceMethod([NSObject class], selector); \ 579 | BOOL success = class_addMethod(cls, selector, (IMP)func, method_getTypeEncoding(method)); \ 580 | if (!success) { class_replaceMethod(cls, selector, (IMP)func, method_getTypeEncoding(method));}} 581 | 582 | IIFish_StrongHookMethod(@selector(methodSignatureForSelector:), iifish_block_methodSignatureForSelector); 583 | IIFish_StrongHookMethod(@selector(forwardInvocation:), iifish_block_forwardInvocation); 584 | }); 585 | } 586 | 587 | static void iifish_block_hook(id obj) { 588 | iifish_NSBlock_hookOnces(); 589 | IIFishBlock block = (__bridge IIFishBlock)(obj); 590 | if (!iifish_block_getTempBlock(block)) { 591 | iifish_block_deepCopy(block); 592 | struct IIFishBlock_descriptor_3 *descriptor_3 = _IIFish_Block_descriptor_3(block); 593 | block->invoke = (void *)iifish_getMsgForward(descriptor_3->signature); 594 | } 595 | } 596 | 597 | 598 | #pragma mark- Property helper 599 | 600 | static SEL iifish_property_getSETSelector(Class cls, const char *propertyName) { 601 | 602 | objc_property_t property = class_getProperty(cls, propertyName); 603 | if (!property) return 0; 604 | 605 | SEL sel = NULL; 606 | unsigned int count; 607 | objc_property_attribute_t *attributes = property_copyAttributeList(property, &count); 608 | 609 | for (unsigned i = 0; i < count; i++) { 610 | objc_property_attribute_t att = attributes[i]; 611 | if (strcmp(att.name, "S") == 0) { 612 | sel = sel_getUid(att.value); 613 | } 614 | } 615 | free(attributes); 616 | return sel; 617 | } 618 | 619 | static SEL iifish_property_getAutoSETSelector(Class cls, const char *propertyName) { 620 | char s[strlen(propertyName) + 5]; 621 | 622 | char firstC = propertyName[0]; 623 | if (isalpha(firstC)) { 624 | snprintf(s, strlen(propertyName) + 5, "set%c%s:", toupper(firstC), propertyName + 1); 625 | } else { 626 | snprintf(s, strlen(propertyName) + 5, "set%s:", propertyName); 627 | } 628 | return sel_getUid(s); 629 | } 630 | 631 | static id iifish_property_getValueForKey(id obj, NSString *propertyName) { 632 | Class cls = object_getClass(obj); 633 | 634 | const char *propertyString = [propertyName UTF8String]; 635 | SEL propertySel = sel_getUid(propertyString); 636 | Method propertyMethod = class_getInstanceMethod(cls, propertySel); 637 | if (propertyMethod || method_getImplementation(propertyMethod) != _objc_msgForward) { 638 | return [obj valueForKey:propertyName]; 639 | } 640 | 641 | size_t len = strlen(propertyString) + strlen(IIFish_Prefix) + 1; 642 | char tempPropertyString[len]; 643 | snprintf(tempPropertyString, len, "%s%s", IIFish_Prefix, propertyString); 644 | return [obj valueForKey:[NSString stringWithUTF8String:tempPropertyString]]; 645 | } 646 | 647 | static void iifsh_property_setValueForKey(id obj, id value, NSString *propertyName) { 648 | Class cls = object_getClass(obj); 649 | 650 | const char *propertyString = [propertyName UTF8String]; 651 | SEL propertySel = iifish_property_getAutoSETSelector(cls, propertyString); 652 | Method propertyMethod = class_getInstanceMethod(cls, propertySel); 653 | if (!propertyMethod || method_getImplementation(propertyMethod) != _objc_msgForward) { 654 | return [obj setValue:value forKey:propertyName]; 655 | } 656 | 657 | size_t len = strlen(propertyString) + strlen(IIFish_Prefix) + 1; 658 | char tempPropertyString[len]; 659 | snprintf(tempPropertyString, len, "%s%s", IIFish_Prefix, propertyString); 660 | SEL tempPropertySel = iifish_property_getAutoSETSelector(cls, tempPropertyString); 661 | if (class_getInstanceMethod(cls, tempPropertySel)) { 662 | return [obj setValue:value forKey:[NSString stringWithUTF8String:tempPropertyString]]; 663 | } 664 | 665 | const char *propertySelString = sel_getName(propertySel); 666 | len = strlen(propertySelString) + strlen(IIFish_Prefix) + 1; 667 | char fakePropertyString[len]; 668 | snprintf(fakePropertyString, len, "%s%s", IIFish_Prefix, propertySelString); 669 | SEL fakePropertySel = sel_getUid(fakePropertyString); 670 | Method fakePropertyMethod = class_getInstanceMethod(cls, fakePropertySel); 671 | 672 | class_addMethod(cls, tempPropertySel, method_getImplementation(fakePropertyMethod), method_getTypeEncoding(fakePropertyMethod)); 673 | return [obj setValue:value forKey:[NSString stringWithUTF8String:tempPropertyString]]; 674 | } 675 | 676 | static void iifish_property_addAutoSETMethod(id obj, SEL setSel, SEL autoSetSel) { 677 | Class cls = object_getClass(obj); 678 | Method m1 = class_getInstanceMethod(cls, autoSetSel); 679 | Method m2 = class_getInstanceMethod(cls, setSel); 680 | if (!m2) { 681 | class_addMethod(cls, setSel, method_getImplementation(m1), method_getTypeEncoding(m1)); 682 | } 683 | } 684 | 685 | #pragma mark- Method Hook 686 | #pragma mark- 687 | 688 | static IIObserverAsset *iifish_object_getAsset(id object) { 689 | IIObserverAsset *asset = objc_getAssociatedObject(object, "IIFish_Class_Get_Asset"); 690 | if (!asset) { 691 | asset = [[IIObserverAsset alloc] init]; 692 | objc_setAssociatedObject(object, "IIFish_Class_Get_Asset", asset, OBJC_ASSOCIATION_RETAIN_NONATOMIC); 693 | } 694 | return asset; 695 | } 696 | 697 | static IIObserverAsset *iifish_object_getAssetNoInit(id object) { 698 | return objc_getAssociatedObject(object, "IIFish_Class_Get_Asset"); 699 | } 700 | 701 | static void iifish_object_clearAsset(id object) { 702 | objc_setAssociatedObject(object, "IIFish_Class_Get_Asset", nil, OBJC_ASSOCIATION_RETAIN_NONATOMIC); 703 | } 704 | 705 | static IIFishWatchCallBackBlock iifish_object_getCallBack(id tager) { 706 | IIFishWatchCallBackBlock callbackBlock = objc_getAssociatedObject(tager, IIFishWatchCallbackKey); 707 | if (callbackBlock) return callbackBlock; 708 | 709 | Class cls = object_isClass(tager) ? tager : object_getClass(tager); 710 | do { 711 | callbackBlock = objc_getAssociatedObject(cls, IIFishWatchCallbackKey); 712 | if (callbackBlock) return callbackBlock; 713 | }while ((cls = class_getSuperclass(cls))); 714 | 715 | return nil; 716 | } 717 | 718 | static void iifish_object_setCallBack(id tager, IIFishWatchCallBackBlock callbackBlock) { 719 | objc_setAssociatedObject(tager, IIFishWatchCallbackKey, callbackBlock, OBJC_ASSOCIATION_COPY_NONATOMIC); 720 | } 721 | 722 | 723 | static void IIFishForwardInvocation(id self, SEL _cmd, NSInvocation *anInvocation) { 724 | SEL fakeSel = anInvocation.selector; 725 | const char *fakeSelString = sel_getName(fakeSel); 726 | char orgSelString[strlen(fakeSelString) + strlen(IIFish_Prefix) + 1]; 727 | snprintf(orgSelString, strlen(fakeSelString) + strlen(IIFish_Prefix) + 1, "%s%s",IIFish_Prefix, fakeSelString); 728 | 729 | anInvocation.selector = sel_getUid(orgSelString); 730 | [anInvocation invoke]; 731 | 732 | IIFishWatchCallBackBlock callback = iifish_object_getCallBack(self); 733 | if (callback) { 734 | callback(iifish_invocation_getCallback(anInvocation)); 735 | } 736 | 737 | // asset 738 | IIObserverAsset *asseet = iifish_object_getAssetNoInit(self); 739 | if (!asseet) { 740 | return; 741 | } 742 | 743 | __block NSArray *observers; 744 | __block NSString *info; 745 | NSString *key = NSStringFromSelector(fakeSel); 746 | [asseet asset:^(NSMutableDictionary *methodAsset, NSMutableDictionary *> *observerAsset) { 747 | info = [methodAsset objectForKey:key]; 748 | if (!info) { 749 | observers = [[observerAsset objectForKey:key] allObjects]; 750 | } else { 751 | NSMutableArray *array = [NSMutableArray new]; 752 | for(NSString *k in [methodAsset allKeysForObject:info]) { 753 | NSArray *a = [[observerAsset objectForKey:k] allObjects]; 754 | if(a) { 755 | [array addObjectsFromArray:a]; 756 | } 757 | } 758 | observers = array; 759 | } 760 | }]; 761 | // 762 | 763 | id propertyValue = nil; 764 | if (info.length > 0) { 765 | propertyValue = iifish_property_getValueForKey(self, info); 766 | } 767 | for (IIFish *fish in observers) { 768 | if (info.length > 0 && fish.flag & IIFish_Property && propertyValue) { 769 | iifsh_property_setValueForKey(fish.object, propertyValue, fish.property); 770 | } else if (fish.callBack) { 771 | IIFishCallBack *callBack = iifish_invocation_getCallback(anInvocation); 772 | fish.callBack(callBack, [fish.object iiDeadFish]); 773 | } 774 | } 775 | } 776 | 777 | static BOOL iifish_object_ssSafeObject(id object) { 778 | 779 | return [object class] == object_getClass(object) || strncmp(object_getClassName(object),IIFish_Prefix,strlen(IIFish_Prefix)); 780 | } 781 | 782 | static Class iifish_class_createFakeSubClass(id object, const char *classPrefix) { 783 | Class orgCls = object_getClass(object); 784 | 785 | const char *orgClassName = class_getName(orgCls); 786 | char *fakeClassName = malloc(strlen(orgClassName) + strlen(classPrefix)); 787 | strcpy(fakeClassName, classPrefix); 788 | strcat(fakeClassName, orgClassName); 789 | 790 | Class fakeCls = objc_lookUpClass(fakeClassName); 791 | if (fakeCls) { 792 | free(fakeClassName); 793 | return fakeCls; 794 | } 795 | 796 | fakeCls = objc_allocateClassPair(orgCls, fakeClassName, 0); 797 | free(fakeClassName); 798 | 799 | if (fakeCls == Nil) { 800 | return Nil; 801 | } 802 | 803 | IMP imp_class = imp_implementationWithBlock(^(){ 804 | return orgCls; 805 | }); 806 | IMP imp_superClass = imp_implementationWithBlock(^(){ 807 | return class_getSuperclass(orgCls); 808 | }); 809 | 810 | #define IIFish_Method_Type(aSelector) method_getTypeEncoding( class_getInstanceMethod([NSObject class], (aSelector))) 811 | 812 | class_addMethod(fakeCls, @selector(class), imp_class, IIFish_Method_Type(@selector(class))); 813 | class_addMethod(fakeCls, @selector(superclass), imp_superClass, IIFish_Method_Type(@selector(superclass))); 814 | class_addMethod(fakeCls, @selector(forwardInvocation:), (IMP)IIFishForwardInvocation, IIFish_Method_Type(@selector(forwardInvocation:))); 815 | 816 | objc_registerClassPair(fakeCls); 817 | 818 | return fakeCls; 819 | } 820 | 821 | static void iifish_class_hook(id object) { 822 | if (!iifish_object_ssSafeObject(object)) { 823 | //maybe KVO object 824 | return; 825 | } 826 | 827 | Class cls = iifish_class_createFakeSubClass(object, IIFish_Prefix); 828 | object_setClass(object, cls); 829 | } 830 | 831 | static void iifish_method_hook(id object, SEL cmd) { 832 | 833 | Class cls = object_getClass(object); 834 | 835 | Method m = class_getInstanceMethod(cls, cmd); 836 | if (iifish_isMsgForward(method_getImplementation(m))) { 837 | return; 838 | } 839 | 840 | Method orgMethod = class_getInstanceMethod(cls, cmd); 841 | NSString *fakeSelStr = [NSString stringWithFormat:@"%s%s", IIFish_Prefix, sel_getName(cmd)]; 842 | SEL fakeSel = NSSelectorFromString(fakeSelStr); 843 | const char *methodType = method_getTypeEncoding(orgMethod); 844 | 845 | class_addMethod(cls, fakeSel, method_getImplementation(orgMethod), methodType); 846 | class_addMethod(cls, cmd, iifish_getMsgForward(methodType),methodType); 847 | } 848 | 849 | static pthread_mutex_t mutex; 850 | 851 | @implementation IIFishBind 852 | 853 | + (void)bindFishes:(NSArray *)fishes { 854 | 855 | pthread_mutex_init(&mutex, NULL); 856 | pthread_mutex_lock(&mutex); 857 | 858 | 859 | for (IIFish *fish in fishes) { 860 | if (fish.flag & IIFish_Observer) continue; 861 | 862 | NSString *autoSeletor; 863 | if (fish.flag & IIFish_Property) {// property 864 | Class cls = [fish.object class]; 865 | 866 | SEL SETSelector = iifish_property_getSETSelector(cls, [fish.property UTF8String]); 867 | SEL autoSETSelector = iifish_property_getAutoSETSelector(cls, [fish.property UTF8String]); 868 | iifish_class_hook(fish.object); 869 | if (SETSelector) { 870 | fish.selector = SETSelector; 871 | iifish_method_hook(fish.object, SETSelector); 872 | iifish_property_addAutoSETMethod(fish.object, SETSelector, autoSETSelector); 873 | iifish_method_hook(fish.object, autoSETSelector); 874 | autoSeletor = NSStringFromSelector(autoSETSelector); 875 | } else { 876 | fish.selector = autoSETSelector; 877 | iifish_method_hook(fish.object, autoSETSelector); 878 | } 879 | } else if (fish.flag & IIFish_Seletor) {// method 880 | iifish_class_hook(fish.object); 881 | iifish_method_hook(fish.object, fish.selector); 882 | } else if (fish.flag & IIFish_IsBlock) { // block 883 | iifish_block_hook(fish.object); 884 | } 885 | 886 | pthread_mutex_unlock(&mutex); 887 | 888 | NSString *key = fish.flag & IIFish_IsBlock ? IIFishBlockObserverKey : NSStringFromSelector(fish.selector); 889 | 890 | IIObserverAsset *asset = iifish_object_getAsset(fish.object); 891 | [asset asset:^(NSMutableDictionary *methodAsset, NSMutableDictionary *> *observerAsset) { 892 | if( fish.flag & IIFish_Property) { 893 | [methodAsset addEntriesFromDictionary:@{key : fish.property}]; 894 | if (autoSeletor.length) { 895 | [methodAsset addEntriesFromDictionary:@{autoSeletor : fish.property}]; 896 | } 897 | } 898 | 899 | NSMutableSet *observerFishes = (NSMutableSet *)[observerAsset objectForKey:key]; 900 | if (!observerFishes) { 901 | observerFishes = [NSMutableSet new]; 902 | } 903 | for (IIFish *f in fishes) { 904 | if (f.flag & IIFish_Post || f == fish) continue; 905 | [observerFishes addObject:f]; 906 | } 907 | [observerAsset setObject:observerFishes forKey:key]; 908 | }]; 909 | } 910 | } 911 | @end 912 | 913 | @implementation NSObject (IIFishBind) 914 | 915 | - (NSArray *)iifish_allKeys { 916 | IIObserverAsset *asset = iifish_object_getAssetNoInit(self); 917 | if (!asset) return nil; 918 | 919 | __block NSArray *array = nil; 920 | [asset asset:^(NSMutableDictionary *methodAsset, NSMutableDictionary *> *observerAsset) { 921 | array = [methodAsset allKeys]; 922 | }]; 923 | return array; 924 | } 925 | 926 | - (NSArray *)iifish_observersWithKey:(NSString *)key { 927 | IIObserverAsset *asset = iifish_object_getAssetNoInit(self); 928 | if (!asset) return nil; 929 | 930 | __block NSArray *array = nil; 931 | [asset asset:^(NSMutableDictionary *methodAsset, NSMutableDictionary *> *observerAsset) { 932 | array = [[observerAsset objectForKey:key] allObjects]; 933 | }]; 934 | return array; 935 | } 936 | 937 | - (NSArray *)iifish_observersWithProperty:(NSString *)property { 938 | IIObserverAsset *asset = iifish_object_getAssetNoInit(self); 939 | if (!asset) return nil; 940 | 941 | SEL SETSelector = iifish_property_getSETSelector([self class], [property UTF8String]); 942 | SEL autoSETSelector = iifish_property_getAutoSETSelector([self class], [property UTF8String]); 943 | 944 | __block NSArray *a1,*a2; 945 | [asset asset:^(NSMutableDictionary *methodAsset, NSMutableDictionary *> *observerAsset) { 946 | 947 | if (SETSelector) { 948 | a1 = [[observerAsset objectForKey:NSStringFromSelector(SETSelector)] allObjects]; 949 | } 950 | if (autoSETSelector) { 951 | a2 = [[observerAsset objectForKey:NSStringFromSelector(autoSETSelector)] allObjects]; 952 | } 953 | }]; 954 | 955 | NSMutableArray *array = [NSMutableArray new]; 956 | if (a1) [array addObjectsFromArray:a1]; 957 | if (a2) [array addObjectsFromArray:a2]; 958 | 959 | return array.count > 0 ? array : nil; 960 | } 961 | 962 | - (void)iifish_removeObserverWithKey:(NSString *)key { 963 | IIObserverAsset *asset = iifish_object_getAssetNoInit(self); 964 | if (!asset) return; 965 | 966 | __block BOOL shouldClearAsset = NO; 967 | [asset asset:^(NSMutableDictionary *methodAsset, NSMutableDictionary *> *observerAsset) { 968 | [observerAsset removeObjectForKey:key]; 969 | if (observerAsset.count == 0) { 970 | shouldClearAsset = YES; 971 | } 972 | }]; 973 | 974 | if (shouldClearAsset) { 975 | iifish_object_clearAsset(self); 976 | } 977 | } 978 | 979 | - (void)iifish_removeObserverWithObject:(id)object { 980 | IIObserverAsset *asset = iifish_object_getAssetNoInit(self); 981 | if (!asset) return; 982 | 983 | __block BOOL shouldClearAsset = NO; 984 | [asset asset:^(NSMutableDictionary *methodAsset, NSMutableDictionary *> *observerAsset) { 985 | [observerAsset enumerateKeysAndObjectsWithOptions:NSEnumerationReverse usingBlock:^(NSString * _Nonnull key, NSMutableSet * _Nonnull value, BOOL * _Nonnull stop) { 986 | [value enumerateObjectsUsingBlock:^(IIFish * _Nonnull obj, BOOL * _Nonnull stop) { 987 | if (obj.object == object) { 988 | [value removeObject:obj]; 989 | *stop = YES; 990 | } 991 | }]; 992 | if (value.count == 0) { 993 | [observerAsset removeObjectForKey:key]; 994 | } 995 | if (observerAsset.count == 0) { 996 | shouldClearAsset = YES; 997 | } 998 | }]; 999 | }]; 1000 | 1001 | if (shouldClearAsset) { 1002 | iifish_object_clearAsset(self); 1003 | } 1004 | } 1005 | 1006 | - (void)iifish_removeObserverWithkey:(NSString *)key andObject:(id)object { 1007 | IIObserverAsset *asset = iifish_object_getAssetNoInit(self); 1008 | if (!asset) return; 1009 | 1010 | __block BOOL shouldClearAsset = NO; 1011 | [asset asset:^(NSMutableDictionary *methodAsset, NSMutableDictionary *> *observerAsset) { 1012 | NSMutableSet *value = [observerAsset valueForKey:key]; 1013 | [value enumerateObjectsUsingBlock:^(IIFish * _Nonnull obj, BOOL * _Nonnull stop) { 1014 | if (obj.object == object) { 1015 | [value removeObject:obj]; 1016 | *stop = YES; 1017 | } 1018 | }]; 1019 | if (value.count == 0) { 1020 | [observerAsset removeObjectForKey:key]; 1021 | } 1022 | if (observerAsset.count == 0) { 1023 | shouldClearAsset = YES; 1024 | } 1025 | }]; 1026 | 1027 | if (shouldClearAsset) { 1028 | iifish_object_clearAsset(self); 1029 | } 1030 | } 1031 | 1032 | - (void)iifish_removeObserverWithProperty:(NSString *)property { 1033 | IIObserverAsset *asset = iifish_object_getAssetNoInit(self); 1034 | if (!asset) return; 1035 | 1036 | SEL SETSelector = iifish_property_getSETSelector([self class], [property UTF8String]); 1037 | SEL autoSETSelector = iifish_property_getAutoSETSelector([self class], [property UTF8String]); 1038 | 1039 | if (SETSelector) { 1040 | [self iifish_removeObserverWithKey:NSStringFromSelector(SETSelector)]; 1041 | } 1042 | if (autoSETSelector) { 1043 | [self iifish_removeObserverWithKey:NSStringFromSelector(autoSETSelector)]; 1044 | } 1045 | 1046 | } 1047 | 1048 | - (void)iifish_removeObserverWithProperty:(NSString *)property andObject:(id)object { 1049 | IIObserverAsset *asset = iifish_object_getAssetNoInit(self); 1050 | if (!asset) return; 1051 | 1052 | SEL SETSelector = iifish_property_getSETSelector([self class], [property UTF8String]); 1053 | SEL autoSETSelector = iifish_property_getAutoSETSelector([self class], [property UTF8String]); 1054 | 1055 | if (SETSelector) { 1056 | [self iifish_removeObserverWithkey:NSStringFromSelector(SETSelector) andObject:object]; 1057 | } 1058 | if (autoSETSelector) { 1059 | [self iifish_removeObserverWithkey:NSStringFromSelector(autoSETSelector) andObject:object]; 1060 | } 1061 | } 1062 | 1063 | - (void)iifish_removeAllObserver { 1064 | IIObserverAsset *asset = iifish_object_getAssetNoInit(self); 1065 | if (!asset) return; 1066 | 1067 | [asset asset:^(NSMutableDictionary *methodAsset, NSMutableDictionary *> *observerAsset) { 1068 | [[observerAsset allValues] enumerateObjectsUsingBlock:^(NSMutableSet * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { 1069 | [obj removeAllObjects]; 1070 | }]; 1071 | [observerAsset removeAllObjects]; 1072 | }]; 1073 | iifish_object_clearAsset(self); 1074 | } 1075 | @end 1076 | 1077 | @implementation NSObject (IIFishWatch) 1078 | 1079 | static NSSet *IIFish_MethodBlackList() { 1080 | static NSSet *blackLlist = nil; 1081 | static dispatch_once_t onceToken; 1082 | dispatch_once(&onceToken, ^{ 1083 | blackLlist = [NSSet setWithArray:@[@"retain", 1084 | @"release", 1085 | @"autorelease", 1086 | @"forwardInvocation:", 1087 | @"methodSignatureForSelector:", 1088 | @"forwardingTargetForSelector:", 1089 | @"respondsToSelector:", 1090 | @".cxx_destruct", 1091 | @"dealloc", 1092 | @"_tryRetain", 1093 | @"_isDeallocating"]]; 1094 | }); 1095 | return blackLlist; 1096 | } 1097 | 1098 | static void iifish_hookAllMethods(Class targetClass, Method *methods, unsigned int count, BOOL withoutNSObject) { 1099 | NSSet *blackList = IIFish_MethodBlackList(); 1100 | for (unsigned int i = 0; i < count; i++) { 1101 | Method m = methods[i]; 1102 | SEL selector = method_getName(m); 1103 | const char *selectorSrting = sel_getName(selector); 1104 | if (strncmp(selectorSrting, IIFish_Prefix, strlen(IIFish_Prefix)) == 0) continue; 1105 | 1106 | if ([blackList containsObject:NSStringFromSelector(selector)]) continue; 1107 | if (withoutNSObject && class_getInstanceMethod([NSObject class], selector)) continue; 1108 | 1109 | char hookSelectorString[strlen(selectorSrting) + strlen(IIFish_Prefix) + 1]; 1110 | snprintf(hookSelectorString, strlen(selectorSrting) + strlen(IIFish_Prefix) + 1, "%s%s",IIFish_Prefix, selectorSrting); 1111 | SEL hookSelector = sel_getUid(hookSelectorString); 1112 | if (class_getInstanceMethod(targetClass, hookSelector)) continue; 1113 | 1114 | class_addMethod(targetClass, hookSelector, method_getImplementation(m), method_getTypeEncoding(m)); 1115 | method_setImplementation(m, iifish_getMsgForward(method_getTypeEncoding(m))); 1116 | } 1117 | 1118 | Method t = class_getInstanceMethod([NSObject class], @selector(forwardInvocation:)); 1119 | 1120 | BOOL success = class_addMethod(targetClass, @selector(forwardInvocation:), (IMP)IIFishForwardInvocation, method_getTypeEncoding(t)); 1121 | if (!success) { 1122 | class_replaceMethod(targetClass, @selector(forwardInvocation:), (IMP)IIFishForwardInvocation, method_getTypeEncoding(t)); 1123 | } 1124 | 1125 | } 1126 | 1127 | + (void)iifish_watchMethodsOptions:(IIFishWatchOptions)options callback:(IIFishWatchCallBackBlock)callback { 1128 | 1129 | NSParameterAssert(callback); 1130 | 1131 | if (strncmp(class_getName(self), IIFish_Prefix, strlen(IIFish_Prefix)) == 0) return; 1132 | iifish_object_setCallBack(self, callback); 1133 | 1134 | BOOL withoutNSObject = options & IIFishWatchOptionsWithoutNSObject; 1135 | 1136 | if (options & IIFishWatchOptionsInstanceMethod) { 1137 | unsigned int count; 1138 | Method *methods = class_copyMethodList(self, &count); 1139 | iifish_hookAllMethods(self, methods, count, withoutNSObject); 1140 | free(methods); 1141 | } 1142 | 1143 | if (options & IIFishWatchOptionsClassMethod) { 1144 | Class cls = object_getClass(self); 1145 | unsigned int count; 1146 | Method *methods = class_copyMethodList(cls, &count); 1147 | iifish_hookAllMethods(cls, methods, count, withoutNSObject); 1148 | free(methods); 1149 | } 1150 | } 1151 | 1152 | - (void)iifish_watchMethodsOptions:(IIFishWatchOptions)options callback:(IIFishWatchCallBackBlock)callback { 1153 | NSParameterAssert(callback); 1154 | 1155 | iifish_class_hook(self); 1156 | iifish_object_setCallBack(self, callback); 1157 | 1158 | unsigned int count; 1159 | Method *methods = class_copyMethodList([self class], &count); 1160 | iifish_hookAllMethods(object_getClass(self), methods, count, options & IIFishWatchOptionsWithoutNSObject); 1161 | free(methods); 1162 | } 1163 | 1164 | @end 1165 | 1166 | -------------------------------------------------------------------------------- /FishBindDemo-iOS/AppDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.h 3 | // FishBindDemo-iOS 4 | // 5 | // Created by WELCommand on 2017/10/16. 6 | // Copyright © 2017年 WELCommand. 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 | -------------------------------------------------------------------------------- /FishBindDemo-iOS/AppDelegate.m: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.m 3 | // FishBindDemo-iOS 4 | // 5 | // Created by WELCommand on 2017/10/16. 6 | // Copyright © 2017年 WELCommand. All rights reserved. 7 | // 8 | 9 | #import "AppDelegate.h" 10 | 11 | @interface AppDelegate () 12 | @end 13 | 14 | @implementation AppDelegate 15 | @end 16 | -------------------------------------------------------------------------------- /FishBindDemo-iOS/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 | } -------------------------------------------------------------------------------- /FishBindDemo-iOS/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 | -------------------------------------------------------------------------------- /FishBindDemo-iOS/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 | -------------------------------------------------------------------------------- /FishBindDemo-iOS/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 | -------------------------------------------------------------------------------- /FishBindDemo-iOS/TestA.h: -------------------------------------------------------------------------------- 1 | // 2 | // TestA.h 3 | // FishBindDemo 4 | // 5 | // Created by WELCommand on 2017/10/23. 6 | // Copyright © 2017年 WELCommand. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface TestA : NSObject 12 | 13 | @property (nonatomic, strong, setter=setUserName:, getter=userName) NSString *name; 14 | 15 | @property (nonatomic, assign) double ageA; 16 | 17 | @end 18 | -------------------------------------------------------------------------------- /FishBindDemo-iOS/TestA.m: -------------------------------------------------------------------------------- 1 | // 2 | // TestA.m 3 | // FishBindDemo 4 | // 5 | // Created by WELCommand on 2017/10/23. 6 | // Copyright © 2017年 WELCommand. All rights reserved. 7 | // 8 | 9 | #import "TestA.h" 10 | 11 | @implementation TestA 12 | 13 | - (void)setUserName:(NSString *)name { 14 | _name = name; 15 | } 16 | 17 | - (void)setName:(NSString *)name { 18 | _name = name; 19 | } 20 | 21 | @end 22 | -------------------------------------------------------------------------------- /FishBindDemo-iOS/TestB.h: -------------------------------------------------------------------------------- 1 | // 2 | // TestB.h 3 | // FishBindDemo 4 | // 5 | // Created by WELCommand on 2017/10/23. 6 | // Copyright © 2017年 WELCommand. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface TestB : NSObject 12 | 13 | @property (nonatomic, copy) NSString *bName; 14 | 15 | @property (nonatomic, assign) NSInteger ageB; 16 | 17 | @end 18 | -------------------------------------------------------------------------------- /FishBindDemo-iOS/TestB.m: -------------------------------------------------------------------------------- 1 | // 2 | // TestB.m 3 | // FishBindDemo 4 | // 5 | // Created by WELCommand on 2017/10/23. 6 | // Copyright © 2017年 WELCommand. All rights reserved. 7 | // 8 | 9 | #import "TestB.h" 10 | 11 | @implementation TestB 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /FishBindDemo-iOS/TestC.h: -------------------------------------------------------------------------------- 1 | // 2 | // TestC.h 3 | // FishBindDemo 4 | // 5 | // Created by WELCommand on 2017/10/23. 6 | // Copyright © 2017年 WELCommand. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface TestC : NSObject 12 | 13 | @property (nonatomic, copy) NSString *fullName; 14 | 15 | @property (nonatomic, assign) NSInteger ageC; 16 | 17 | @end 18 | -------------------------------------------------------------------------------- /FishBindDemo-iOS/TestC.m: -------------------------------------------------------------------------------- 1 | // 2 | // TestC.m 3 | // FishBindDemo 4 | // 5 | // Created by WELCommand on 2017/10/23. 6 | // Copyright © 2017年 WELCommand. All rights reserved. 7 | // 8 | 9 | #import "TestC.h" 10 | 11 | @implementation TestC 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /FishBindDemo-iOS/TestD.h: -------------------------------------------------------------------------------- 1 | // 2 | // TestD.h 3 | // FishBindDemo 4 | // 5 | // Created by WELCommand on 2017/10/23. 6 | // Copyright © 2017年 WELCommand. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface TestD : NSObject 12 | 13 | @property (nonatomic, copy) NSString *DK_Name; 14 | 15 | @end 16 | -------------------------------------------------------------------------------- /FishBindDemo-iOS/TestD.m: -------------------------------------------------------------------------------- 1 | // 2 | // TestD.m 3 | // FishBindDemo 4 | // 5 | // Created by WELCommand on 2017/10/23. 6 | // Copyright © 2017年 WELCommand. All rights reserved. 7 | // 8 | 9 | #import "TestD.h" 10 | 11 | @implementation TestD 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /FishBindDemo-iOS/ViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.h 3 | // FishBindDemo-iOS 4 | // 5 | // Created by WELCommand on 2017/10/16. 6 | // Copyright © 2017年 WELCommand. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface ViewController : UIViewController 12 | 13 | 14 | @end 15 | 16 | -------------------------------------------------------------------------------- /FishBindDemo-iOS/ViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.m 3 | // FishBindDemo-iOS 4 | // 5 | // Created by WELCommand on 2017/10/16. 6 | // Copyright © 2017年 WELCommand. All rights reserved. 7 | // 8 | 9 | #import "ViewController.h" 10 | #import "IIFishBind.h" 11 | #import "TestA.h" 12 | #import "TestB.h" 13 | #import "TestC.h" 14 | #import "TestD.h" 15 | 16 | @interface ViewController () 17 | 18 | @end 19 | 20 | 21 | @implementation ViewController 22 | 23 | 24 | - (void)viewDidLoad { 25 | [super viewDidLoad]; 26 | 27 | //双向绑定 28 | 29 | TestA *objA = [TestA new]; 30 | TestB *objB = [TestB new]; 31 | TestC *objC = [TestC new]; 32 | TestD *objD = [TestD new]; 33 | 34 | [IIFishBind bindFishes:@[ 35 | [IIFish both:objA property:@"name" 36 | callBack:^(IIFishCallBack *callBack, id deadFish) { 37 | [deadFish setUserName:callBack.args[0]]; 38 | }], 39 | [IIFish both:objB property:@"bName" 40 | callBack:^(IIFishCallBack *callBack, id deadFish) { 41 | [deadFish setBName:callBack.args[0]]; 42 | }], 43 | [IIFish both:objD 44 | selector:@selector(setDK_Name:) 45 | callBack:^(IIFishCallBack *callBack, id deadFish) { 46 | [deadFish setDK_Name:[NSString stringWithFormat:@"DK_%@",callBack.args[0]]]; 47 | }] 48 | ]]; 49 | 50 | [objA setValue:@"json" forKey:@"name"]; 51 | NSLog(@"%@", [NSString stringWithFormat:@"\nTestA : name = %@\nTestB : bName = %@\nTestD : DK_Name = %@",objA.userName, objB.bName, objD.DK_Name]); 52 | /* 53 | TestA : name = json 54 | TestB : bName = json 55 | TestD : DK_Name = DK_json 56 | */ 57 | 58 | objB.bName = @"GCD"; 59 | NSLog(@"%@", [NSString stringWithFormat:@"\nTestA : name = %@\nTestB : bName = %@\nTestD : DK_Name = %@",objA.userName, objB.bName, objD.DK_Name]); 60 | /* 61 | TestA : name = GCD 62 | TestB : bName = GCD 63 | TestD : DK_Name = DK_GCD 64 | */ 65 | 66 | objD.DK_Name = @"apple"; 67 | NSLog(@"%@", [NSString stringWithFormat:@"\nTestA : name = %@\nTestB : bName = %@\nTestD : DK_Name = %@",objA.userName, objB.bName, objD.DK_Name]); 68 | /* 69 | TestA : name = apple 70 | TestB : bName = apple 71 | TestD : DK_Name = apple 72 | */ 73 | 74 | 75 | //类型自动转换 基于KVC 76 | // 77 | [IIFishBind bindFishes:@[ 78 | [IIFish both:objA property:@"ageA" callBack:nil], 79 | [IIFish both:objB property:@"ageB" callBack:nil], 80 | ]]; 81 | // 82 | objA.ageA = 3.3; 83 | 84 | NSLog(@"a = %@ b = %@ ", @(objA.ageA),@( objB.ageB)); 85 | 86 | NSLog(@"%@",[objA iifish_allKeys]); 87 | NSLog(@"%@",[objA iifish_observersWithKey:@"setAgeA:"]); 88 | 89 | // 绑定block 90 | // 91 | // CGFloat (^testBlock)(CGFloat i, CGFloat j) = ^(CGFloat i, CGFloat j) { 92 | // return i + j; 93 | // }; 94 | // 95 | // [IIFishBind bindFishes:@[ 96 | // [IIFish postBlock:testBlock], 97 | // [IIFish observer:self 98 | // callBack:^(IIFishCallBack *callBack, id deadFish) { 99 | // NSLog(@"%@ + %@ = %@", callBack.args[0], callBack.args[1], callBack.resule); 100 | // // 3.1 + 4.1 = 7.199999999999999 101 | // }] 102 | // ]]; 103 | // 104 | // 105 | // CGFloat value = testBlock (3.1, 4.1); 106 | // 107 | // NSLog(@"value = %@", @(value)); 108 | // value = 7.199999999999999 109 | 110 | // 单向绑定 111 | 112 | // [IIFishBind bindFishes:@[ 113 | // [IIFish post:self selector:@selector(viewDidAppear:)], 114 | // [IIFish observer:self 115 | // callBack:^(IIFishCallBack *callBack, id deadFish) { 116 | // NSLog(@"======== 4 ==========="); 117 | // }] 118 | // ]]; 119 | // 120 | // [IIFishBind bindFishes:@[ 121 | // [IIFish post:self selector:@selector(viewWillAppear:)], 122 | // [IIFish observer:self 123 | // callBack:^(IIFishCallBack *callBack, id deadFish) { 124 | // NSLog(@"======== 2 ==========="); 125 | // }] 126 | // ]]; 127 | 128 | //方法调用观察 129 | 130 | 131 | __unused NSString *s = [objC fullName]; 132 | } 133 | 134 | - (void)viewWillAppear:(BOOL)animated { 135 | [super viewWillAppear:animated]; 136 | NSLog(@"\n\n======== 1 ==========="); 137 | } 138 | 139 | 140 | - (void)viewDidAppear:(BOOL)animated { 141 | [super viewDidAppear:animated]; 142 | NSLog(@"======== 3 ==========="); 143 | } 144 | 145 | 146 | @end 147 | -------------------------------------------------------------------------------- /FishBindDemo-iOS/main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // FishBindDemo-iOS 4 | // 5 | // Created by WELCommand on 2017/10/16. 6 | // Copyright © 2017年 WELCommand. 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 | -------------------------------------------------------------------------------- /FishBindDemo.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 2D774A2F1F9D9C650078C6B5 /* TestB.m in Sources */ = {isa = PBXBuildFile; fileRef = 2D774A271F9D9B460078C6B5 /* TestB.m */; }; 11 | 2D774A301F9D9C650078C6B5 /* TestD.m in Sources */ = {isa = PBXBuildFile; fileRef = 2D774A2D1F9D9BC00078C6B5 /* TestD.m */; }; 12 | 2D774A311F9D9C650078C6B5 /* TestC.m in Sources */ = {isa = PBXBuildFile; fileRef = 2D774A2A1F9D9B510078C6B5 /* TestC.m */; }; 13 | 2D774A321F9D9C650078C6B5 /* TestA.m in Sources */ = {isa = PBXBuildFile; fileRef = 2D774A241F9D9B3B0078C6B5 /* TestA.m */; }; 14 | 2DBA234D1F94409E005F1FE2 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 2DBA234C1F94409E005F1FE2 /* AppDelegate.m */; }; 15 | 2DBA23501F94409E005F1FE2 /* ViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 2DBA234F1F94409E005F1FE2 /* ViewController.m */; }; 16 | 2DBA23531F94409E005F1FE2 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 2DBA23511F94409E005F1FE2 /* Main.storyboard */; }; 17 | 2DBA23551F94409E005F1FE2 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 2DBA23541F94409E005F1FE2 /* Assets.xcassets */; }; 18 | 2DBA23581F94409E005F1FE2 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 2DBA23561F94409E005F1FE2 /* LaunchScreen.storyboard */; }; 19 | 2DBA235B1F94409E005F1FE2 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 2DBA235A1F94409E005F1FE2 /* main.m */; }; 20 | 2DBA235F1F9441D8005F1FE2 /* IIFishBind.m in Sources */ = {isa = PBXBuildFile; fileRef = 2D9D7D9B1F7787C300C82428 /* IIFishBind.m */; }; 21 | /* End PBXBuildFile section */ 22 | 23 | /* Begin PBXFileReference section */ 24 | 2D774A231F9D9B3B0078C6B5 /* TestA.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = TestA.h; sourceTree = ""; }; 25 | 2D774A241F9D9B3B0078C6B5 /* TestA.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = TestA.m; sourceTree = ""; }; 26 | 2D774A261F9D9B460078C6B5 /* TestB.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = TestB.h; sourceTree = ""; }; 27 | 2D774A271F9D9B460078C6B5 /* TestB.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = TestB.m; sourceTree = ""; }; 28 | 2D774A291F9D9B510078C6B5 /* TestC.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = TestC.h; sourceTree = ""; }; 29 | 2D774A2A1F9D9B510078C6B5 /* TestC.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = TestC.m; sourceTree = ""; }; 30 | 2D774A2C1F9D9BC00078C6B5 /* TestD.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = TestD.h; sourceTree = ""; }; 31 | 2D774A2D1F9D9BC00078C6B5 /* TestD.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = TestD.m; sourceTree = ""; }; 32 | 2D9D7D9A1F7787C300C82428 /* IIFishBind.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = IIFishBind.h; sourceTree = ""; }; 33 | 2D9D7D9B1F7787C300C82428 /* IIFishBind.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = IIFishBind.m; sourceTree = ""; }; 34 | 2DBA23491F94409E005F1FE2 /* FishBindDemo-iOS.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "FishBindDemo-iOS.app"; sourceTree = BUILT_PRODUCTS_DIR; }; 35 | 2DBA234B1F94409E005F1FE2 /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; 36 | 2DBA234C1F94409E005F1FE2 /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; 37 | 2DBA234E1F94409E005F1FE2 /* ViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ViewController.h; sourceTree = ""; }; 38 | 2DBA234F1F94409E005F1FE2 /* ViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ViewController.m; sourceTree = ""; }; 39 | 2DBA23521F94409E005F1FE2 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 40 | 2DBA23541F94409E005F1FE2 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 41 | 2DBA23571F94409E005F1FE2 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 42 | 2DBA23591F94409E005F1FE2 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 43 | 2DBA235A1F94409E005F1FE2 /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; 44 | /* End PBXFileReference section */ 45 | 46 | /* Begin PBXFrameworksBuildPhase section */ 47 | 2DBA23461F94409E005F1FE2 /* Frameworks */ = { 48 | isa = PBXFrameworksBuildPhase; 49 | buildActionMask = 2147483647; 50 | files = ( 51 | ); 52 | runOnlyForDeploymentPostprocessing = 0; 53 | }; 54 | /* End PBXFrameworksBuildPhase section */ 55 | 56 | /* Begin PBXGroup section */ 57 | 2D9D7D861F77878B00C82428 = { 58 | isa = PBXGroup; 59 | children = ( 60 | 2D9D7D991F7787C300C82428 /* FishBind */, 61 | 2DBA234A1F94409E005F1FE2 /* FishBindDemo-iOS */, 62 | 2D9D7D901F77878B00C82428 /* Products */, 63 | ); 64 | sourceTree = ""; 65 | }; 66 | 2D9D7D901F77878B00C82428 /* Products */ = { 67 | isa = PBXGroup; 68 | children = ( 69 | 2DBA23491F94409E005F1FE2 /* FishBindDemo-iOS.app */, 70 | ); 71 | name = Products; 72 | sourceTree = ""; 73 | }; 74 | 2D9D7D991F7787C300C82428 /* FishBind */ = { 75 | isa = PBXGroup; 76 | children = ( 77 | 2D9D7D9A1F7787C300C82428 /* IIFishBind.h */, 78 | 2D9D7D9B1F7787C300C82428 /* IIFishBind.m */, 79 | ); 80 | path = FishBind; 81 | sourceTree = ""; 82 | }; 83 | 2DBA234A1F94409E005F1FE2 /* FishBindDemo-iOS */ = { 84 | isa = PBXGroup; 85 | children = ( 86 | 2D774A231F9D9B3B0078C6B5 /* TestA.h */, 87 | 2D774A241F9D9B3B0078C6B5 /* TestA.m */, 88 | 2D774A261F9D9B460078C6B5 /* TestB.h */, 89 | 2D774A271F9D9B460078C6B5 /* TestB.m */, 90 | 2D774A291F9D9B510078C6B5 /* TestC.h */, 91 | 2D774A2A1F9D9B510078C6B5 /* TestC.m */, 92 | 2D774A2C1F9D9BC00078C6B5 /* TestD.h */, 93 | 2D774A2D1F9D9BC00078C6B5 /* TestD.m */, 94 | 2DBA234B1F94409E005F1FE2 /* AppDelegate.h */, 95 | 2DBA234C1F94409E005F1FE2 /* AppDelegate.m */, 96 | 2DBA234E1F94409E005F1FE2 /* ViewController.h */, 97 | 2DBA234F1F94409E005F1FE2 /* ViewController.m */, 98 | 2DBA23511F94409E005F1FE2 /* Main.storyboard */, 99 | 2DBA23541F94409E005F1FE2 /* Assets.xcassets */, 100 | 2DBA23561F94409E005F1FE2 /* LaunchScreen.storyboard */, 101 | 2DBA23591F94409E005F1FE2 /* Info.plist */, 102 | 2DBA235A1F94409E005F1FE2 /* main.m */, 103 | ); 104 | path = "FishBindDemo-iOS"; 105 | sourceTree = ""; 106 | }; 107 | /* End PBXGroup section */ 108 | 109 | /* Begin PBXNativeTarget section */ 110 | 2DBA23481F94409E005F1FE2 /* FishBindDemo-iOS */ = { 111 | isa = PBXNativeTarget; 112 | buildConfigurationList = 2DBA235E1F94409E005F1FE2 /* Build configuration list for PBXNativeTarget "FishBindDemo-iOS" */; 113 | buildPhases = ( 114 | 2DBA23451F94409E005F1FE2 /* Sources */, 115 | 2DBA23461F94409E005F1FE2 /* Frameworks */, 116 | 2DBA23471F94409E005F1FE2 /* Resources */, 117 | ); 118 | buildRules = ( 119 | ); 120 | dependencies = ( 121 | ); 122 | name = "FishBindDemo-iOS"; 123 | productName = "FishBindDemo-iOS"; 124 | productReference = 2DBA23491F94409E005F1FE2 /* FishBindDemo-iOS.app */; 125 | productType = "com.apple.product-type.application"; 126 | }; 127 | /* End PBXNativeTarget section */ 128 | 129 | /* Begin PBXProject section */ 130 | 2D9D7D871F77878B00C82428 /* Project object */ = { 131 | isa = PBXProject; 132 | attributes = { 133 | LastUpgradeCheck = 0900; 134 | ORGANIZATIONNAME = WELCommand; 135 | TargetAttributes = { 136 | 2DBA23481F94409E005F1FE2 = { 137 | CreatedOnToolsVersion = 9.0; 138 | DevelopmentTeam = 42Q4XDWY3Y; 139 | ProvisioningStyle = Automatic; 140 | }; 141 | }; 142 | }; 143 | buildConfigurationList = 2D9D7D8A1F77878B00C82428 /* Build configuration list for PBXProject "FishBindDemo" */; 144 | compatibilityVersion = "Xcode 3.2"; 145 | developmentRegion = English; 146 | hasScannedForEncodings = 0; 147 | knownRegions = ( 148 | en, 149 | Base, 150 | ); 151 | mainGroup = 2D9D7D861F77878B00C82428; 152 | productRefGroup = 2D9D7D901F77878B00C82428 /* Products */; 153 | projectDirPath = ""; 154 | projectRoot = ""; 155 | targets = ( 156 | 2DBA23481F94409E005F1FE2 /* FishBindDemo-iOS */, 157 | ); 158 | }; 159 | /* End PBXProject section */ 160 | 161 | /* Begin PBXResourcesBuildPhase section */ 162 | 2DBA23471F94409E005F1FE2 /* Resources */ = { 163 | isa = PBXResourcesBuildPhase; 164 | buildActionMask = 2147483647; 165 | files = ( 166 | 2DBA23581F94409E005F1FE2 /* LaunchScreen.storyboard in Resources */, 167 | 2DBA23551F94409E005F1FE2 /* Assets.xcassets in Resources */, 168 | 2DBA23531F94409E005F1FE2 /* Main.storyboard in Resources */, 169 | ); 170 | runOnlyForDeploymentPostprocessing = 0; 171 | }; 172 | /* End PBXResourcesBuildPhase section */ 173 | 174 | /* Begin PBXSourcesBuildPhase section */ 175 | 2DBA23451F94409E005F1FE2 /* Sources */ = { 176 | isa = PBXSourcesBuildPhase; 177 | buildActionMask = 2147483647; 178 | files = ( 179 | 2D774A301F9D9C650078C6B5 /* TestD.m in Sources */, 180 | 2DBA235F1F9441D8005F1FE2 /* IIFishBind.m in Sources */, 181 | 2DBA23501F94409E005F1FE2 /* ViewController.m in Sources */, 182 | 2D774A321F9D9C650078C6B5 /* TestA.m in Sources */, 183 | 2DBA235B1F94409E005F1FE2 /* main.m in Sources */, 184 | 2DBA234D1F94409E005F1FE2 /* AppDelegate.m in Sources */, 185 | 2D774A2F1F9D9C650078C6B5 /* TestB.m in Sources */, 186 | 2D774A311F9D9C650078C6B5 /* TestC.m in Sources */, 187 | ); 188 | runOnlyForDeploymentPostprocessing = 0; 189 | }; 190 | /* End PBXSourcesBuildPhase section */ 191 | 192 | /* Begin PBXVariantGroup section */ 193 | 2DBA23511F94409E005F1FE2 /* Main.storyboard */ = { 194 | isa = PBXVariantGroup; 195 | children = ( 196 | 2DBA23521F94409E005F1FE2 /* Base */, 197 | ); 198 | name = Main.storyboard; 199 | sourceTree = ""; 200 | }; 201 | 2DBA23561F94409E005F1FE2 /* LaunchScreen.storyboard */ = { 202 | isa = PBXVariantGroup; 203 | children = ( 204 | 2DBA23571F94409E005F1FE2 /* Base */, 205 | ); 206 | name = LaunchScreen.storyboard; 207 | sourceTree = ""; 208 | }; 209 | /* End PBXVariantGroup section */ 210 | 211 | /* Begin XCBuildConfiguration section */ 212 | 2D9D7D941F77878B00C82428 /* Debug */ = { 213 | isa = XCBuildConfiguration; 214 | buildSettings = { 215 | ALWAYS_SEARCH_USER_PATHS = NO; 216 | CLANG_ANALYZER_NONNULL = YES; 217 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 218 | CLANG_CXX_LIBRARY = "libc++"; 219 | CLANG_ENABLE_MODULES = YES; 220 | CLANG_ENABLE_OBJC_ARC = YES; 221 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 222 | CLANG_WARN_BOOL_CONVERSION = YES; 223 | CLANG_WARN_COMMA = YES; 224 | CLANG_WARN_CONSTANT_CONVERSION = YES; 225 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 226 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 227 | CLANG_WARN_EMPTY_BODY = YES; 228 | CLANG_WARN_ENUM_CONVERSION = YES; 229 | CLANG_WARN_INFINITE_RECURSION = YES; 230 | CLANG_WARN_INT_CONVERSION = YES; 231 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 232 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 233 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 234 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 235 | CLANG_WARN_STRICT_PROTOTYPES = YES; 236 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 237 | CLANG_WARN_UNREACHABLE_CODE = YES; 238 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 239 | CODE_SIGN_IDENTITY = "-"; 240 | COPY_PHASE_STRIP = NO; 241 | DEBUG_INFORMATION_FORMAT = dwarf; 242 | ENABLE_STRICT_OBJC_MSGSEND = YES; 243 | ENABLE_TESTABILITY = YES; 244 | GCC_C_LANGUAGE_STANDARD = gnu99; 245 | GCC_DYNAMIC_NO_PIC = NO; 246 | GCC_NO_COMMON_BLOCKS = YES; 247 | GCC_OPTIMIZATION_LEVEL = 0; 248 | GCC_PREPROCESSOR_DEFINITIONS = ( 249 | "DEBUG=1", 250 | "$(inherited)", 251 | ); 252 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 253 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 254 | GCC_WARN_UNDECLARED_SELECTOR = YES; 255 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 256 | GCC_WARN_UNUSED_FUNCTION = YES; 257 | GCC_WARN_UNUSED_VARIABLE = YES; 258 | MACOSX_DEPLOYMENT_TARGET = 10.12; 259 | MTL_ENABLE_DEBUG_INFO = YES; 260 | ONLY_ACTIVE_ARCH = YES; 261 | SDKROOT = macosx; 262 | }; 263 | name = Debug; 264 | }; 265 | 2D9D7D951F77878B00C82428 /* Release */ = { 266 | isa = XCBuildConfiguration; 267 | buildSettings = { 268 | ALWAYS_SEARCH_USER_PATHS = NO; 269 | CLANG_ANALYZER_NONNULL = YES; 270 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 271 | CLANG_CXX_LIBRARY = "libc++"; 272 | CLANG_ENABLE_MODULES = YES; 273 | CLANG_ENABLE_OBJC_ARC = YES; 274 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 275 | CLANG_WARN_BOOL_CONVERSION = YES; 276 | CLANG_WARN_COMMA = YES; 277 | CLANG_WARN_CONSTANT_CONVERSION = YES; 278 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 279 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 280 | CLANG_WARN_EMPTY_BODY = YES; 281 | CLANG_WARN_ENUM_CONVERSION = YES; 282 | CLANG_WARN_INFINITE_RECURSION = YES; 283 | CLANG_WARN_INT_CONVERSION = YES; 284 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 285 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 286 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 287 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 288 | CLANG_WARN_STRICT_PROTOTYPES = YES; 289 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 290 | CLANG_WARN_UNREACHABLE_CODE = YES; 291 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 292 | CODE_SIGN_IDENTITY = "-"; 293 | COPY_PHASE_STRIP = NO; 294 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 295 | ENABLE_NS_ASSERTIONS = NO; 296 | ENABLE_STRICT_OBJC_MSGSEND = YES; 297 | GCC_C_LANGUAGE_STANDARD = gnu99; 298 | GCC_NO_COMMON_BLOCKS = YES; 299 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 300 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 301 | GCC_WARN_UNDECLARED_SELECTOR = YES; 302 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 303 | GCC_WARN_UNUSED_FUNCTION = YES; 304 | GCC_WARN_UNUSED_VARIABLE = YES; 305 | MACOSX_DEPLOYMENT_TARGET = 10.12; 306 | MTL_ENABLE_DEBUG_INFO = NO; 307 | SDKROOT = macosx; 308 | }; 309 | name = Release; 310 | }; 311 | 2DBA235C1F94409E005F1FE2 /* Debug */ = { 312 | isa = XCBuildConfiguration; 313 | buildSettings = { 314 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 315 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 316 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 317 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 318 | CLANG_WARN_COMMA = YES; 319 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 320 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 321 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 322 | CLANG_WARN_STRICT_PROTOTYPES = YES; 323 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 324 | CODE_SIGN_IDENTITY = "iPhone Developer"; 325 | CODE_SIGN_STYLE = Automatic; 326 | DEVELOPMENT_TEAM = 42Q4XDWY3Y; 327 | GCC_C_LANGUAGE_STANDARD = gnu11; 328 | INFOPLIST_FILE = "FishBindDemo-iOS/Info.plist"; 329 | IPHONEOS_DEPLOYMENT_TARGET = 9.0; 330 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 331 | PRODUCT_BUNDLE_IDENTIFIER = "WELCommand.FishBindDemo-iOS"; 332 | PRODUCT_NAME = "$(TARGET_NAME)"; 333 | SDKROOT = iphoneos; 334 | TARGETED_DEVICE_FAMILY = "1,2"; 335 | }; 336 | name = Debug; 337 | }; 338 | 2DBA235D1F94409E005F1FE2 /* Release */ = { 339 | isa = XCBuildConfiguration; 340 | buildSettings = { 341 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 342 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 343 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 344 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 345 | CLANG_WARN_COMMA = YES; 346 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 347 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 348 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 349 | CLANG_WARN_STRICT_PROTOTYPES = YES; 350 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 351 | CODE_SIGN_IDENTITY = "iPhone Developer"; 352 | CODE_SIGN_STYLE = Automatic; 353 | DEVELOPMENT_TEAM = 42Q4XDWY3Y; 354 | GCC_C_LANGUAGE_STANDARD = gnu11; 355 | INFOPLIST_FILE = "FishBindDemo-iOS/Info.plist"; 356 | IPHONEOS_DEPLOYMENT_TARGET = 9.0; 357 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 358 | PRODUCT_BUNDLE_IDENTIFIER = "WELCommand.FishBindDemo-iOS"; 359 | PRODUCT_NAME = "$(TARGET_NAME)"; 360 | SDKROOT = iphoneos; 361 | TARGETED_DEVICE_FAMILY = "1,2"; 362 | VALIDATE_PRODUCT = YES; 363 | }; 364 | name = Release; 365 | }; 366 | /* End XCBuildConfiguration section */ 367 | 368 | /* Begin XCConfigurationList section */ 369 | 2D9D7D8A1F77878B00C82428 /* Build configuration list for PBXProject "FishBindDemo" */ = { 370 | isa = XCConfigurationList; 371 | buildConfigurations = ( 372 | 2D9D7D941F77878B00C82428 /* Debug */, 373 | 2D9D7D951F77878B00C82428 /* Release */, 374 | ); 375 | defaultConfigurationIsVisible = 0; 376 | defaultConfigurationName = Release; 377 | }; 378 | 2DBA235E1F94409E005F1FE2 /* Build configuration list for PBXNativeTarget "FishBindDemo-iOS" */ = { 379 | isa = XCConfigurationList; 380 | buildConfigurations = ( 381 | 2DBA235C1F94409E005F1FE2 /* Debug */, 382 | 2DBA235D1F94409E005F1FE2 /* Release */, 383 | ); 384 | defaultConfigurationIsVisible = 0; 385 | defaultConfigurationName = Release; 386 | }; 387 | /* End XCConfigurationList section */ 388 | }; 389 | rootObject = 2D9D7D871F77878B00C82428 /* Project object */; 390 | } 391 | -------------------------------------------------------------------------------- /FishBindDemo.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /FishBindDemo.xcodeproj/project.xcworkspace/xcuserdata/WELCommand.xcuserdatad/UserInterfaceState.xcuserstate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/welcommand/FishBind/800d8c96ffb6b0e83e26c8ca44cf811414f6ad71/FishBindDemo.xcodeproj/project.xcworkspace/xcuserdata/WELCommand.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /FishBindDemo.xcodeproj/xcuserdata/WELCommand.xcuserdatad/xcschemes/FishBindDemo.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 31 | 32 | 33 | 34 | 40 | 41 | 42 | 43 | 44 | 45 | 56 | 58 | 64 | 65 | 66 | 67 | 68 | 69 | 75 | 77 | 83 | 84 | 85 | 86 | 88 | 89 | 92 | 93 | 94 | -------------------------------------------------------------------------------- /FishBindDemo.xcodeproj/xcuserdata/welcommand.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 8 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /FishBindDemo.xcodeproj/xcuserdata/welcommand.xcuserdatad/xcschemes/FishBindDemo-iOS.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 31 | 32 | 33 | 34 | 40 | 41 | 42 | 43 | 44 | 45 | 56 | 58 | 64 | 65 | 66 | 67 | 68 | 69 | 75 | 77 | 83 | 84 | 85 | 86 | 88 | 89 | 92 | 93 | 94 | -------------------------------------------------------------------------------- /FishBindDemo.xcodeproj/xcuserdata/welcommand.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | FishBindDemo-iOS.xcscheme 8 | 9 | orderHint 10 | 1 11 | 12 | FishBindDemo.xcscheme 13 | 14 | orderHint 15 | 0 16 | 17 | 18 | SuppressBuildableAutocreation 19 | 20 | 2D9D7D8E1F77878B00C82428 21 | 22 | primary 23 | 24 | 25 | 2DBA23481F94409E005F1FE2 26 | 27 | primary 28 | 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /FishBindIcon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/welcommand/FishBind/800d8c96ffb6b0e83e26c8ca44cf811414f6ad71/FishBindIcon.png -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Permission is hereby granted, free of charge, to any person obtaining a copy 2 | of this software and associated documentation files (the "Software"), to deal 3 | in the Software without restriction, including without limitation the rights 4 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 5 | copies of the Software, and to permit persons to whom the Software is 6 | furnished to do so, subject to the following conditions: 7 | 8 | The above copyright notice and this permission notice shall be included in 9 | all copies or substantial portions of the Software. 10 | 11 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 12 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 13 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 14 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 15 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 16 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 17 | THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # FishBind 2 | 3 |

4 | 5 |

6 | 7 |

8 | 9 | 10 | 11 | 12 |

13 | 14 | 15 | # 介绍 16 | 17 | FishBind可以轻松的实现对象间消息的绑定。支持绑定属性、方法、block。支持单向绑定&双向绑定。 18 | 19 | 用这个做MVVM应该很愉快。 20 | 21 | 项目处于早期版本,仍在持续开发,如果喜欢这个lun zi,赶快一起来加功能、杀bug ー( ´ ▽ ` )ノ 22 | 23 | ## 例子 24 | 25 | ``` 26 | - (void)viewDidLoad { 27 | [super viewDidLoad]; 28 | 29 | //双向绑定 30 | 31 | TestA *objA = [TestA new]; 32 | TestB *objB = [TestB new]; 33 | TestD *objD = [TestD new]; 34 | 35 | [IIFishBind bindFishes:@[ 36 | [IIFish both:objA property:@"name" 37 | callBack:^(IIFishCallBack *callBack, id deadFish) { 38 | [deadFish setUserName:callBack.args[0]]; 39 | }], 40 | [IIFish both:objB property:@"bName" 41 | callBack:^(IIFishCallBack *callBack, id deadFish) { 42 | [deadFish setBName:callBack.args[0]]; 43 | }], 44 | [IIFish both:objD 45 | selector:@selector(setDK_Name:) 46 | callBack:^(IIFishCallBack *callBack, id deadFish) { 47 | [deadFish setDK_Name:[NSString stringWithFormat:@"DK_%@",callBack.args[0]]]; 48 | }] 49 | ]]; 50 | 51 | objA.name = @"json"; 52 | NSLog(@"%@", [NSString stringWithFormat:@"\nTestA : name = %@\nTestB : bName = %@\nTestD : DK_Name = %@",objA.userName, objB.bName, objD.DK_Name]); 53 | /* 54 | TestA : name = json 55 | TestB : bName = json 56 | TestD : DK_Name = DK_json 57 | */ 58 | 59 | objB.bName = @"GCD"; 60 | NSLog(@"%@", [NSString stringWithFormat:@"\nTestA : name = %@\nTestB : bName = %@\nTestD : DK_Name = %@",objA.userName, objB.bName, objD.DK_Name]); 61 | /* 62 | TestA : name = GCD 63 | TestB : bName = GCD 64 | TestD : DK_Name = DK_GCD 65 | */ 66 | 67 | objD.DK_Name = @"apple"; 68 | NSLog(@"%@", [NSString stringWithFormat:@"\nTestA : name = %@\nTestB : bName = %@\nTestD : DK_Name = %@",objA.userName, objB.bName, objD.DK_Name]); 69 | /* 70 | TestA : name = apple 71 | TestB : bName = apple 72 | TestD : DK_Name = apple 73 | */ 74 | 75 | // 绑定block 76 | 77 | CGFloat (^testBlock)(CGFloat i, CGFloat j) = ^(CGFloat i, CGFloat j) { 78 | return i + j; 79 | }; 80 | 81 | [IIFishBind bindFishes:@[ 82 | [IIFish postBlock:testBlock], 83 | [IIFish observer:self 84 | callBack:^(IIFishCallBack *callBack, id deadFish) { 85 | NSLog(@"%@ + %@ = %@", callBack.args[0], callBack.args[1], callBack.resule); 86 | // 3.1 + 4.1 = 7.199999999999999 87 | }] 88 | ]]; 89 | 90 | 91 | CGFloat value = testBlock (3.1, 4.1); 92 | 93 | NSLog(@"value = %@", @(value)); 94 | // value = 7.199999999999999 95 | 96 | // 单向绑定 97 | 98 | [IIFishBind bindFishes:@[ 99 | [IIFish post:self selector:@selector(viewDidAppear:)], 100 | [IIFish observer:self 101 | callBack:^(IIFishCallBack *callBack, id deadFish) { 102 | NSLog(@"======== 4 ==========="); 103 | }] 104 | ]]; 105 | 106 | [IIFishBind bindFishes:@[ 107 | [IIFish post:self selector:@selector(viewWillAppear:)], 108 | [IIFish observer:self 109 | callBack:^(IIFishCallBack *callBack, id deadFish) { 110 | NSLog(@"======== 2 ==========="); 111 | }] 112 | ]]; 113 | 114 | } 115 | 116 | - (void)viewWillAppear:(BOOL)animated { 117 | [super viewWillAppear:animated]; 118 | NSLog(@"======== 1 ==========="); 119 | } 120 | 121 | 122 | - (void)viewDidAppear:(BOOL)animated { 123 | [super viewDidAppear:animated]; 124 | NSLog(@"======== 3 ==========="); 125 | } 126 | 127 | ``` 128 | 129 | 130 | 131 | ## 安装 132 | 133 | ``` 134 | pod 'FishBind' 135 | ``` 136 | 137 | 138 | ## 使用 139 | 140 | 现阶段,基本上就是搞几个IIFish,然后丢给IIFishBind。根据IIFish的语意不同,实现不同的逻辑。 141 | 142 | 下面是几种IIFish的初始化方式 143 | 144 | ``` 145 | // property bind 146 | + (instancetype)post:(id)object property:(NSString *)property; 147 | + (instancetype)observer:(id)object property:(NSString *)property; 148 | 149 | // method bind 150 | + (instancetype)post:(id)object selector:(SEL)selector; 151 | + (instancetype)observer:(id)object callBack:(IIFishCallBackBlock)callBack; 152 | 153 | // bind a block, using observer:callBack: to observe 154 | + (instancetype)postBlock:(id)blockObject; 155 | 156 | // bilateral bind 157 | + (instancetype)both:(id)object selector:(SEL)selector callBack:(IIFishCallBackBlock)callBack; 158 | + (instancetype)both:(id)object property:(NSString *)property callBack:(IIFishCallBackBlock)callBack;; 159 | ``` 160 | 161 | 162 | 163 | ## 单向绑定 164 | 165 | ### 绑定属性 166 | 167 | 比如,有对象A,希望监控其属性a的变化,则使用 168 | 169 | ``` 170 | IIFish *fish1 = [IIFish post:A property:@"a"]; 171 | ``` 172 | 173 | 然后,如果希望在a变化后,把a的值直接传给对象B的属性b, 则使用 174 | 175 | ``` 176 | IIFish *fish2 = [IIFish observer:B property:@"b"]; 177 | ``` 178 | 179 | 如果,只是希望像KVO一样观察事件,然后做一些复杂的操作,则使用 180 | 181 | ``` 182 | IIFish *fish2 = [IIFish observer:B callBack:^(IIFishCallBack *callBack, id deadFish) { 183 | 184 | }]; 185 | ``` 186 | 187 | IIFishCallBack 会把完整的信息交给你。IIFishCallBack结构如下 188 | 189 | ``` 190 | @interface IIFishCallBack : NSObject 191 | @property (nonatomic, weak) id tager; //被观察者 192 | @property (nonatomic, copy) NSString *selector; //被调用的方法 193 | @property (nonatomic, strong) NSArray *args; // 该方法的参数的值 194 | @property (nonatomic, strong) id resule; // 方法的返回值 195 | @end 196 | ``` 197 | 198 | 而deadFish是什么呢?在本例中,deadFish可以理解为对象B,即观察者。假设B有一个方法test被C监控,调用[B test],C会收到回调。调用[deadFish test],C不会收到回掉(为什么要设计这个,在下文有介绍)。 199 | 200 | 最后把两个IIFish对象交给IIFishBind,就完成了绑定,代码如下。 201 | 202 | ``` 203 | [IIFishBind bindFishes:[fish1,fish2]]; 204 | ``` 205 | 206 | 207 | 208 | ### 绑定方法 209 | 210 | 绑定属性其实就是绑定方法。本质上没区别。 211 | 212 | 在使用上,被监控的对象需要使用 213 | 214 | ``` 215 | + (instancetype)post:(id)object selector:(SEL)selector; 216 | ``` 217 | 218 | 观察者也必须使用 219 | 220 | ``` 221 | + (instancetype)observer:(id)object callBack:(IIFishCallBackBlock)callBack; 222 | ``` 223 | 224 | 225 | 226 | ### 绑定Block 227 | 228 | 其实就是钩一个block。在block执行后加一些自己的代码。(感觉上可以做block队列,或者block事件发给多个接收者。不过实际上没遇见这种需求,纯粹是为了好玩才写的.....) 229 | 230 | 流程上和上面一样。使用下面的方法初始化IIFish。 231 | 232 | ``` 233 | + (instancetype)postBlock:(id)blockObject; 234 | + (instancetype)observer:(id)object callBack:(IIFishCallBackBlock)callBack; 235 | ``` 236 | 237 | 最后绑定一下就好了。 238 | 239 | ``` 240 | [IIFishBind bindFishes:XXXXX]; 241 | ``` 242 | 243 | 244 | 245 | ## 双向绑定 246 | 247 | 单向绑定的话,属性可以用KVO,方法可以用Aspects,基本都是现成的方案。写这个的目的主要是为了实现双向绑定。 248 | 249 | 比如,有对象A、B、C,分别有属性a、b、c。三个属性内容一样,希望这三个属性有一个被更改,其它两个跟着被更改。则代码如下 250 | 251 | ``` 252 | [IIFishBind bindFishes:@[ 253 | [IIFish both:A property:@"a" callBack:nil], 254 | [IIFish both:B property:@"b" callBack:nil], 255 | [IIFish both:C property:@"c" callBack:nil] 256 | ]]; 257 | ``` 258 | 259 | 如果,对象B的属性b,需要特殊处理,比如是a c的100倍,则如下。 260 | 261 | ``` 262 | [IIFishBind bindFishes:@[ 263 | [IIFish both:A property:@"a" callBack:^(IIFishCallBack *callBack, id deadFish) { 264 |      deadFish.a = [callback.arg[0] intValue] / 100; 265 | }], 266 | [IIFish both:C property:@"c" callBack:^(IIFishCallBack *callBack, id deadFish) { 267 | deadFish.c = [callback.arg[0] intValue] / 100; 268 | }], 269 | [IIFish both:B selector:@selector(setB:) callBack:^(IIFishCallBack *callBack, id deadFish) { 270 | deadFish.b = 100 * [callback.arg[0] intValue]; 271 | }] 272 | ]]; 273 | ``` 274 | 275 | 这里,如果调用 B.b则回会造成死循环,所以需要使用deadFish。 276 | 277 | 278 | 279 | ## 双向绑定API的选择问题 280 | 281 | ``` 282 | + (instancetype)both:(id)object selector:(SEL)selector callBack:(IIFishCallBackBlock)callBack; 283 | 284 | + (instancetype)both:(id)object property:(NSString *)property callBack:(IIFishCallBackBlock)callBack; 285 | ``` 286 | 287 | 使用Both开头的API初始化的IIFish,表示既可以发送改变也可以接受改变。 288 | 289 | 一组中, 如果有使用both:selector:callBack:,则这一组都需要实现callBack,来实现回调行为。 290 | 291 | ## 绑定的相关注意事项 292 | 293 | 目前callBack没有优先级,调用顺序不确定,不应该在callBack中直接获取这一组的其他值。 294 | 295 | 296 | ## todo 297 | 298 | - [ ] 兼容KVO 299 | --------------------------------------------------------------------------------