NSObject+A2BlockDelegate.m 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363
  1. //
  2. // NSObject+A2BlockDelegate.m
  3. // BlocksKit
  4. //
  5. #import <objc/message.h>
  6. #import "NSObject+A2BlockDelegate.h"
  7. #import "NSObject+A2DynamicDelegate.h"
  8. #pragma mark - Declarations and macros
  9. extern Protocol *a2_dataSourceProtocol(Class cls);
  10. extern Protocol *a2_delegateProtocol(Class cls);
  11. #pragma mark - Functions
  12. static BOOL bk_object_isKindOfClass(id obj, Class testClass)
  13. {
  14. BOOL isKindOfClass = NO;
  15. Class cls = object_getClass(obj);
  16. while (cls && !isKindOfClass) {
  17. isKindOfClass = (cls == testClass);
  18. cls = class_getSuperclass(cls);
  19. }
  20. return isKindOfClass;
  21. }
  22. static Protocol *a2_protocolForDelegatingObject(id obj, Protocol *protocol)
  23. {
  24. NSString *protocolName = NSStringFromProtocol(protocol);
  25. if ([protocolName hasSuffix:@"Delegate"]) {
  26. Protocol *p = a2_delegateProtocol([obj class]);
  27. if (p) return p;
  28. } else if ([protocolName hasSuffix:@"DataSource"]) {
  29. Protocol *p = a2_dataSourceProtocol([obj class]);
  30. if (p) return p;
  31. }
  32. return protocol;
  33. }
  34. static inline BOOL isValidIMP(IMP impl) {
  35. #if defined(__arm64__)
  36. if (impl == NULL || impl == _objc_msgForward) return NO;
  37. #else
  38. if (impl == NULL || impl == _objc_msgForward || impl == (IMP)_objc_msgForward_stret) return NO;
  39. #endif
  40. return YES;
  41. }
  42. static BOOL addMethodWithIMP(Class cls, SEL oldSel, SEL newSel, IMP newIMP, const char *types, BOOL aggressive) {
  43. if (!class_addMethod(cls, oldSel, newIMP, types)) {
  44. return NO;
  45. }
  46. // We just ended up implementing a method that doesn't exist
  47. // (-[NSURLConnection setDelegate:]) or overrode a superclass
  48. // version (-[UIImagePickerController setDelegate:]).
  49. IMP parentIMP = NULL;
  50. Class superclass = class_getSuperclass(cls);
  51. while (superclass && !isValidIMP(parentIMP)) {
  52. parentIMP = class_getMethodImplementation(superclass, oldSel);
  53. if (isValidIMP(parentIMP)) {
  54. break;
  55. } else {
  56. parentIMP = NULL;
  57. }
  58. superclass = class_getSuperclass(superclass);
  59. }
  60. if (parentIMP) {
  61. if (aggressive) {
  62. return class_addMethod(cls, newSel, parentIMP, types);
  63. }
  64. class_replaceMethod(cls, newSel, newIMP, types);
  65. class_replaceMethod(cls, oldSel, parentIMP, types);
  66. }
  67. return YES;
  68. }
  69. static BOOL swizzleWithIMP(Class cls, SEL oldSel, SEL newSel, IMP newIMP, const char *types, BOOL aggressive) {
  70. Method origMethod = class_getInstanceMethod(cls, oldSel);
  71. if (addMethodWithIMP(cls, oldSel, newSel, newIMP, types, aggressive)) {
  72. return YES;
  73. }
  74. // common case, actual swap
  75. BOOL ret = class_addMethod(cls, newSel, newIMP, types);
  76. Method newMethod = class_getInstanceMethod(cls, newSel);
  77. method_exchangeImplementations(origMethod, newMethod);
  78. return ret;
  79. }
  80. static SEL selectorWithPattern(const char *prefix, const char *key, const char *suffix) {
  81. size_t prefixLength = prefix ? strlen(prefix) : 0;
  82. size_t suffixLength = suffix ? strlen(suffix) : 0;
  83. char initial = key[0];
  84. if (prefixLength) initial = (char)toupper(initial);
  85. size_t initialLength = 1;
  86. const char *rest = key + initialLength;
  87. size_t restLength = strlen(rest);
  88. char selector[prefixLength + initialLength + restLength + suffixLength + 1];
  89. memcpy(selector, prefix, prefixLength);
  90. selector[prefixLength] = initial;
  91. memcpy(selector + prefixLength + initialLength, rest, restLength);
  92. memcpy(selector + prefixLength + initialLength + restLength, suffix, suffixLength);
  93. selector[prefixLength + initialLength + restLength + suffixLength] = '\0';
  94. return sel_registerName(selector);
  95. }
  96. static SEL getterForProperty(objc_property_t property, const char *name)
  97. {
  98. if (property) {
  99. char *getterName = property_copyAttributeValue(property, "G");
  100. if (getterName) {
  101. SEL getter = sel_getUid(getterName);
  102. free(getterName);
  103. if (getter) return getter;
  104. }
  105. }
  106. const char *propertyName = property ? property_getName(property) : name;
  107. return sel_registerName(propertyName);
  108. }
  109. static SEL setterForProperty(objc_property_t property, const char *name)
  110. {
  111. if (property) {
  112. char *setterName = property_copyAttributeValue(property, "S");
  113. if (setterName) {
  114. SEL setter = sel_getUid(setterName);
  115. free(setterName);
  116. if (setter) return setter;
  117. }
  118. }
  119. const char *propertyName = property ? property_getName(property) : name;
  120. return selectorWithPattern("set", propertyName, ":");
  121. }
  122. static inline SEL prefixedSelector(SEL original) {
  123. return selectorWithPattern("a2_", sel_getName(original), NULL);
  124. }
  125. #pragma mark -
  126. typedef struct {
  127. SEL setter;
  128. SEL a2_setter;
  129. SEL getter;
  130. } A2BlockDelegateInfo;
  131. static NSUInteger A2BlockDelegateInfoSize(const void *__unused item) {
  132. return sizeof(A2BlockDelegateInfo);
  133. }
  134. static NSString *A2BlockDelegateInfoDescribe(const void *__unused item) {
  135. if (!item) { return nil; }
  136. const A2BlockDelegateInfo *info = item;
  137. return [NSString stringWithFormat:@"(setter: %s, getter: %s)", sel_getName(info->setter), sel_getName(info->getter)];
  138. }
  139. static inline A2DynamicDelegate *getDynamicDelegate(NSObject *delegatingObject, Protocol *protocol, const A2BlockDelegateInfo *info, BOOL ensuring) {
  140. A2DynamicDelegate *dynamicDelegate = [delegatingObject bk_dynamicDelegateForProtocol:a2_protocolForDelegatingObject(delegatingObject, protocol)];
  141. if (!info || !info->setter || !info->getter) {
  142. return dynamicDelegate;
  143. }
  144. if (!info->a2_setter && !info->setter) { return dynamicDelegate; }
  145. id (*getterDispatch)(id, SEL) = (id (*)(id, SEL)) objc_msgSend;
  146. id originalDelegate = getterDispatch(delegatingObject, info->getter);
  147. if (bk_object_isKindOfClass(originalDelegate, A2DynamicDelegate.class)) { return dynamicDelegate; }
  148. void (*setterDispatch)(id, SEL, id) = (void (*)(id, SEL, id)) objc_msgSend;
  149. setterDispatch(delegatingObject, info->a2_setter ?: info->setter, dynamicDelegate);
  150. return dynamicDelegate;
  151. }
  152. typedef A2DynamicDelegate *(^A2GetDynamicDelegateBlock)(NSObject *, BOOL);
  153. @interface A2DynamicDelegate ()
  154. @property (nonatomic, weak, readwrite) id realDelegate;
  155. @end
  156. #pragma mark -
  157. @implementation NSObject (A2BlockDelegate)
  158. #pragma mark Helpers
  159. + (NSMapTable *)bk_delegateInfoByProtocol:(BOOL)createIfNeeded
  160. {
  161. NSMapTable *delegateInfo = objc_getAssociatedObject(self, _cmd);
  162. if (delegateInfo || !createIfNeeded) { return delegateInfo; }
  163. NSPointerFunctions *protocols = [NSPointerFunctions pointerFunctionsWithOptions:NSPointerFunctionsOpaqueMemory|NSPointerFunctionsObjectPointerPersonality];
  164. NSPointerFunctions *infoStruct = [NSPointerFunctions pointerFunctionsWithOptions:NSPointerFunctionsMallocMemory|NSPointerFunctionsStructPersonality|NSPointerFunctionsCopyIn];
  165. infoStruct.sizeFunction = A2BlockDelegateInfoSize;
  166. infoStruct.descriptionFunction = A2BlockDelegateInfoDescribe;
  167. delegateInfo = [[NSMapTable alloc] initWithKeyPointerFunctions:protocols valuePointerFunctions:infoStruct capacity:0];
  168. objc_setAssociatedObject(self, _cmd, delegateInfo, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
  169. return delegateInfo;
  170. }
  171. + (const A2BlockDelegateInfo *)bk_delegateInfoForProtocol:(Protocol *)protocol
  172. {
  173. A2BlockDelegateInfo *infoAsPtr = NULL;
  174. Class cls = self;
  175. while ((infoAsPtr == NULL || infoAsPtr->getter == NULL) && cls != nil && cls != NSObject.class) {
  176. NSMapTable *map = [cls bk_delegateInfoByProtocol:NO];
  177. infoAsPtr = (__bridge void *)[map objectForKey:protocol];
  178. cls = [cls superclass];
  179. }
  180. NSCAssert(infoAsPtr != NULL, @"Class %@ not assigned dynamic delegate for protocol %@", NSStringFromClass(self), NSStringFromProtocol(protocol));
  181. return infoAsPtr;
  182. }
  183. #pragma mark Linking block properties
  184. + (void)bk_linkDataSourceMethods:(NSDictionary *)dictionary
  185. {
  186. [self bk_linkProtocol:a2_dataSourceProtocol(self) methods:dictionary];
  187. }
  188. + (void)bk_linkDelegateMethods:(NSDictionary *)dictionary
  189. {
  190. [self bk_linkProtocol:a2_delegateProtocol(self) methods:dictionary];
  191. }
  192. + (void)bk_linkProtocol:(Protocol *)protocol methods:(NSDictionary *)dictionary
  193. {
  194. [dictionary enumerateKeysAndObjectsUsingBlock:^(NSString *propertyName, NSString *selectorName, BOOL *stop) {
  195. const char *name = propertyName.UTF8String;
  196. objc_property_t property = class_getProperty(self, name);
  197. NSCAssert(property, @"Property \"%@\" does not exist on class %s", propertyName, class_getName(self));
  198. char *dynamic = property_copyAttributeValue(property, "D");
  199. NSCAssert2(dynamic, @"Property \"%@\" on class %s must be backed with \"@dynamic\"", propertyName, class_getName(self));
  200. free(dynamic);
  201. char *copy = property_copyAttributeValue(property, "C");
  202. NSCAssert2(copy, @"Property \"%@\" on class %s must be defined with the \"copy\" attribute", propertyName, class_getName(self));
  203. free(copy);
  204. SEL selector = NSSelectorFromString(selectorName);
  205. SEL getter = getterForProperty(property, name);
  206. SEL setter = setterForProperty(property, name);
  207. if (class_respondsToSelector(self, setter) || class_respondsToSelector(self, getter)) { return; }
  208. const A2BlockDelegateInfo *info = [self bk_delegateInfoForProtocol:protocol];
  209. IMP getterImplementation = imp_implementationWithBlock(^(NSObject *delegatingObject) {
  210. A2DynamicDelegate *delegate = getDynamicDelegate(delegatingObject, protocol, info, NO);
  211. return [delegate blockImplementationForMethod:selector];
  212. });
  213. if (!class_addMethod(self, getter, getterImplementation, "@@:")) {
  214. NSCAssert(NO, @"Could not implement getter for \"%@\" property.", propertyName);
  215. }
  216. IMP setterImplementation = imp_implementationWithBlock(^(NSObject *delegatingObject, id block) {
  217. A2DynamicDelegate *delegate = getDynamicDelegate(delegatingObject, protocol, info, YES);
  218. [delegate implementMethod:selector withBlock:block];
  219. });
  220. if (!class_addMethod(self, setter, setterImplementation, "v@:@")) {
  221. NSCAssert(NO, @"Could not implement setter for \"%@\" property.", propertyName);
  222. }
  223. }];
  224. }
  225. #pragma mark Dynamic Delegate Replacement
  226. + (void)bk_registerDynamicDataSource
  227. {
  228. [self bk_registerDynamicDelegateNamed:@"dataSource" forProtocol:a2_dataSourceProtocol(self)];
  229. }
  230. + (void)bk_registerDynamicDelegate
  231. {
  232. [self bk_registerDynamicDelegateNamed:@"delegate" forProtocol:a2_delegateProtocol(self)];
  233. }
  234. + (void)bk_registerDynamicDataSourceNamed:(NSString *)dataSourceName
  235. {
  236. [self bk_registerDynamicDelegateNamed:dataSourceName forProtocol:a2_dataSourceProtocol(self)];
  237. }
  238. + (void)bk_registerDynamicDelegateNamed:(NSString *)delegateName
  239. {
  240. [self bk_registerDynamicDelegateNamed:delegateName forProtocol:a2_delegateProtocol(self)];
  241. }
  242. + (void)bk_registerDynamicDelegateNamed:(NSString *)delegateName forProtocol:(Protocol *)protocol
  243. {
  244. NSMapTable *propertyMap = [self bk_delegateInfoByProtocol:YES];
  245. A2BlockDelegateInfo *infoAsPtr = (__bridge void *)[propertyMap objectForKey:protocol];
  246. if (infoAsPtr != NULL) { return; }
  247. const char *name = delegateName.UTF8String;
  248. objc_property_t property = class_getProperty(self, name);
  249. SEL setter = setterForProperty(property, name);
  250. SEL a2_setter = prefixedSelector(setter);
  251. SEL getter = getterForProperty(property, name);
  252. A2BlockDelegateInfo info = {
  253. setter, a2_setter, getter
  254. };
  255. [propertyMap setObject:(__bridge id)&info forKey:protocol];
  256. infoAsPtr = (__bridge void *)[propertyMap objectForKey:protocol];
  257. IMP setterImplementation = imp_implementationWithBlock(^(NSObject *delegatingObject, id delegate) {
  258. A2DynamicDelegate *dynamicDelegate = getDynamicDelegate(delegatingObject, protocol, infoAsPtr, YES);
  259. if ([delegate isEqual:dynamicDelegate]) {
  260. delegate = nil;
  261. }
  262. dynamicDelegate.realDelegate = delegate;
  263. });
  264. if (!swizzleWithIMP(self, setter, a2_setter, setterImplementation, "v@:@", YES)) {
  265. bzero(infoAsPtr, sizeof(A2BlockDelegateInfo));
  266. return;
  267. }
  268. if (![self instancesRespondToSelector:getter]) {
  269. IMP getterImplementation = imp_implementationWithBlock(^(NSObject *delegatingObject) {
  270. return [delegatingObject bk_dynamicDelegateForProtocol:a2_protocolForDelegatingObject(delegatingObject, protocol)];
  271. });
  272. addMethodWithIMP(self, getter, NULL, getterImplementation, "@@:", NO);
  273. }
  274. }
  275. - (id)bk_ensuredDynamicDelegate
  276. {
  277. Protocol *protocol = a2_delegateProtocol(self.class);
  278. return [self bk_ensuredDynamicDelegateForProtocol:protocol];
  279. }
  280. - (id)bk_ensuredDynamicDelegateForProtocol:(Protocol *)protocol
  281. {
  282. const A2BlockDelegateInfo *info = [self.class bk_delegateInfoForProtocol:protocol];
  283. return getDynamicDelegate(self, protocol, info, YES);
  284. }
  285. @end