123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363 |
- //
- // NSObject+A2BlockDelegate.m
- // BlocksKit
- //
- #import <objc/message.h>
- #import "NSObject+A2BlockDelegate.h"
- #import "NSObject+A2DynamicDelegate.h"
- #pragma mark - Declarations and macros
- extern Protocol *a2_dataSourceProtocol(Class cls);
- extern Protocol *a2_delegateProtocol(Class cls);
- #pragma mark - Functions
- static BOOL bk_object_isKindOfClass(id obj, Class testClass)
- {
- BOOL isKindOfClass = NO;
- Class cls = object_getClass(obj);
- while (cls && !isKindOfClass) {
- isKindOfClass = (cls == testClass);
- cls = class_getSuperclass(cls);
- }
- return isKindOfClass;
- }
- static Protocol *a2_protocolForDelegatingObject(id obj, Protocol *protocol)
- {
- NSString *protocolName = NSStringFromProtocol(protocol);
- if ([protocolName hasSuffix:@"Delegate"]) {
- Protocol *p = a2_delegateProtocol([obj class]);
- if (p) return p;
- } else if ([protocolName hasSuffix:@"DataSource"]) {
- Protocol *p = a2_dataSourceProtocol([obj class]);
- if (p) return p;
- }
- return protocol;
- }
- static inline BOOL isValidIMP(IMP impl) {
- #if defined(__arm64__)
- if (impl == NULL || impl == _objc_msgForward) return NO;
- #else
- if (impl == NULL || impl == _objc_msgForward || impl == (IMP)_objc_msgForward_stret) return NO;
- #endif
- return YES;
- }
- static BOOL addMethodWithIMP(Class cls, SEL oldSel, SEL newSel, IMP newIMP, const char *types, BOOL aggressive) {
- if (!class_addMethod(cls, oldSel, newIMP, types)) {
- return NO;
- }
- // We just ended up implementing a method that doesn't exist
- // (-[NSURLConnection setDelegate:]) or overrode a superclass
- // version (-[UIImagePickerController setDelegate:]).
- IMP parentIMP = NULL;
- Class superclass = class_getSuperclass(cls);
- while (superclass && !isValidIMP(parentIMP)) {
- parentIMP = class_getMethodImplementation(superclass, oldSel);
- if (isValidIMP(parentIMP)) {
- break;
- } else {
- parentIMP = NULL;
- }
- superclass = class_getSuperclass(superclass);
- }
- if (parentIMP) {
- if (aggressive) {
- return class_addMethod(cls, newSel, parentIMP, types);
- }
- class_replaceMethod(cls, newSel, newIMP, types);
- class_replaceMethod(cls, oldSel, parentIMP, types);
- }
- return YES;
- }
- static BOOL swizzleWithIMP(Class cls, SEL oldSel, SEL newSel, IMP newIMP, const char *types, BOOL aggressive) {
- Method origMethod = class_getInstanceMethod(cls, oldSel);
- if (addMethodWithIMP(cls, oldSel, newSel, newIMP, types, aggressive)) {
- return YES;
- }
- // common case, actual swap
- BOOL ret = class_addMethod(cls, newSel, newIMP, types);
- Method newMethod = class_getInstanceMethod(cls, newSel);
- method_exchangeImplementations(origMethod, newMethod);
- return ret;
- }
- static SEL selectorWithPattern(const char *prefix, const char *key, const char *suffix) {
- size_t prefixLength = prefix ? strlen(prefix) : 0;
- size_t suffixLength = suffix ? strlen(suffix) : 0;
- char initial = key[0];
- if (prefixLength) initial = (char)toupper(initial);
- size_t initialLength = 1;
- const char *rest = key + initialLength;
- size_t restLength = strlen(rest);
- char selector[prefixLength + initialLength + restLength + suffixLength + 1];
- memcpy(selector, prefix, prefixLength);
- selector[prefixLength] = initial;
- memcpy(selector + prefixLength + initialLength, rest, restLength);
- memcpy(selector + prefixLength + initialLength + restLength, suffix, suffixLength);
- selector[prefixLength + initialLength + restLength + suffixLength] = '\0';
- return sel_registerName(selector);
- }
- static SEL getterForProperty(objc_property_t property, const char *name)
- {
- if (property) {
- char *getterName = property_copyAttributeValue(property, "G");
- if (getterName) {
- SEL getter = sel_getUid(getterName);
- free(getterName);
- if (getter) return getter;
- }
- }
- const char *propertyName = property ? property_getName(property) : name;
- return sel_registerName(propertyName);
- }
- static SEL setterForProperty(objc_property_t property, const char *name)
- {
- if (property) {
- char *setterName = property_copyAttributeValue(property, "S");
- if (setterName) {
- SEL setter = sel_getUid(setterName);
- free(setterName);
- if (setter) return setter;
- }
- }
- const char *propertyName = property ? property_getName(property) : name;
- return selectorWithPattern("set", propertyName, ":");
- }
- static inline SEL prefixedSelector(SEL original) {
- return selectorWithPattern("a2_", sel_getName(original), NULL);
- }
- #pragma mark -
- typedef struct {
- SEL setter;
- SEL a2_setter;
- SEL getter;
- } A2BlockDelegateInfo;
- static NSUInteger A2BlockDelegateInfoSize(const void *__unused item) {
- return sizeof(A2BlockDelegateInfo);
- }
- static NSString *A2BlockDelegateInfoDescribe(const void *__unused item) {
- if (!item) { return nil; }
- const A2BlockDelegateInfo *info = item;
- return [NSString stringWithFormat:@"(setter: %s, getter: %s)", sel_getName(info->setter), sel_getName(info->getter)];
- }
- static inline A2DynamicDelegate *getDynamicDelegate(NSObject *delegatingObject, Protocol *protocol, const A2BlockDelegateInfo *info, BOOL ensuring) {
- A2DynamicDelegate *dynamicDelegate = [delegatingObject bk_dynamicDelegateForProtocol:a2_protocolForDelegatingObject(delegatingObject, protocol)];
- if (!info || !info->setter || !info->getter) {
- return dynamicDelegate;
- }
- if (!info->a2_setter && !info->setter) { return dynamicDelegate; }
- id (*getterDispatch)(id, SEL) = (id (*)(id, SEL)) objc_msgSend;
- id originalDelegate = getterDispatch(delegatingObject, info->getter);
- if (bk_object_isKindOfClass(originalDelegate, A2DynamicDelegate.class)) { return dynamicDelegate; }
- void (*setterDispatch)(id, SEL, id) = (void (*)(id, SEL, id)) objc_msgSend;
- setterDispatch(delegatingObject, info->a2_setter ?: info->setter, dynamicDelegate);
- return dynamicDelegate;
- }
- typedef A2DynamicDelegate *(^A2GetDynamicDelegateBlock)(NSObject *, BOOL);
- @interface A2DynamicDelegate ()
- @property (nonatomic, weak, readwrite) id realDelegate;
- @end
- #pragma mark -
- @implementation NSObject (A2BlockDelegate)
- #pragma mark Helpers
- + (NSMapTable *)bk_delegateInfoByProtocol:(BOOL)createIfNeeded
- {
- NSMapTable *delegateInfo = objc_getAssociatedObject(self, _cmd);
- if (delegateInfo || !createIfNeeded) { return delegateInfo; }
- NSPointerFunctions *protocols = [NSPointerFunctions pointerFunctionsWithOptions:NSPointerFunctionsOpaqueMemory|NSPointerFunctionsObjectPointerPersonality];
- NSPointerFunctions *infoStruct = [NSPointerFunctions pointerFunctionsWithOptions:NSPointerFunctionsMallocMemory|NSPointerFunctionsStructPersonality|NSPointerFunctionsCopyIn];
- infoStruct.sizeFunction = A2BlockDelegateInfoSize;
- infoStruct.descriptionFunction = A2BlockDelegateInfoDescribe;
- delegateInfo = [[NSMapTable alloc] initWithKeyPointerFunctions:protocols valuePointerFunctions:infoStruct capacity:0];
- objc_setAssociatedObject(self, _cmd, delegateInfo, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
- return delegateInfo;
- }
- + (const A2BlockDelegateInfo *)bk_delegateInfoForProtocol:(Protocol *)protocol
- {
- A2BlockDelegateInfo *infoAsPtr = NULL;
- Class cls = self;
- while ((infoAsPtr == NULL || infoAsPtr->getter == NULL) && cls != nil && cls != NSObject.class) {
- NSMapTable *map = [cls bk_delegateInfoByProtocol:NO];
- infoAsPtr = (__bridge void *)[map objectForKey:protocol];
- cls = [cls superclass];
- }
- NSCAssert(infoAsPtr != NULL, @"Class %@ not assigned dynamic delegate for protocol %@", NSStringFromClass(self), NSStringFromProtocol(protocol));
- return infoAsPtr;
- }
- #pragma mark Linking block properties
- + (void)bk_linkDataSourceMethods:(NSDictionary *)dictionary
- {
- [self bk_linkProtocol:a2_dataSourceProtocol(self) methods:dictionary];
- }
- + (void)bk_linkDelegateMethods:(NSDictionary *)dictionary
- {
- [self bk_linkProtocol:a2_delegateProtocol(self) methods:dictionary];
- }
- + (void)bk_linkProtocol:(Protocol *)protocol methods:(NSDictionary *)dictionary
- {
- [dictionary enumerateKeysAndObjectsUsingBlock:^(NSString *propertyName, NSString *selectorName, BOOL *stop) {
- const char *name = propertyName.UTF8String;
- objc_property_t property = class_getProperty(self, name);
- NSCAssert(property, @"Property \"%@\" does not exist on class %s", propertyName, class_getName(self));
- char *dynamic = property_copyAttributeValue(property, "D");
- NSCAssert2(dynamic, @"Property \"%@\" on class %s must be backed with \"@dynamic\"", propertyName, class_getName(self));
- free(dynamic);
- char *copy = property_copyAttributeValue(property, "C");
- NSCAssert2(copy, @"Property \"%@\" on class %s must be defined with the \"copy\" attribute", propertyName, class_getName(self));
- free(copy);
- SEL selector = NSSelectorFromString(selectorName);
- SEL getter = getterForProperty(property, name);
- SEL setter = setterForProperty(property, name);
- if (class_respondsToSelector(self, setter) || class_respondsToSelector(self, getter)) { return; }
- const A2BlockDelegateInfo *info = [self bk_delegateInfoForProtocol:protocol];
- IMP getterImplementation = imp_implementationWithBlock(^(NSObject *delegatingObject) {
- A2DynamicDelegate *delegate = getDynamicDelegate(delegatingObject, protocol, info, NO);
- return [delegate blockImplementationForMethod:selector];
- });
- if (!class_addMethod(self, getter, getterImplementation, "@@:")) {
- NSCAssert(NO, @"Could not implement getter for \"%@\" property.", propertyName);
- }
- IMP setterImplementation = imp_implementationWithBlock(^(NSObject *delegatingObject, id block) {
- A2DynamicDelegate *delegate = getDynamicDelegate(delegatingObject, protocol, info, YES);
- [delegate implementMethod:selector withBlock:block];
- });
- if (!class_addMethod(self, setter, setterImplementation, "v@:@")) {
- NSCAssert(NO, @"Could not implement setter for \"%@\" property.", propertyName);
- }
- }];
- }
- #pragma mark Dynamic Delegate Replacement
- + (void)bk_registerDynamicDataSource
- {
- [self bk_registerDynamicDelegateNamed:@"dataSource" forProtocol:a2_dataSourceProtocol(self)];
- }
- + (void)bk_registerDynamicDelegate
- {
- [self bk_registerDynamicDelegateNamed:@"delegate" forProtocol:a2_delegateProtocol(self)];
- }
- + (void)bk_registerDynamicDataSourceNamed:(NSString *)dataSourceName
- {
- [self bk_registerDynamicDelegateNamed:dataSourceName forProtocol:a2_dataSourceProtocol(self)];
- }
- + (void)bk_registerDynamicDelegateNamed:(NSString *)delegateName
- {
- [self bk_registerDynamicDelegateNamed:delegateName forProtocol:a2_delegateProtocol(self)];
- }
- + (void)bk_registerDynamicDelegateNamed:(NSString *)delegateName forProtocol:(Protocol *)protocol
- {
- NSMapTable *propertyMap = [self bk_delegateInfoByProtocol:YES];
- A2BlockDelegateInfo *infoAsPtr = (__bridge void *)[propertyMap objectForKey:protocol];
- if (infoAsPtr != NULL) { return; }
- const char *name = delegateName.UTF8String;
- objc_property_t property = class_getProperty(self, name);
- SEL setter = setterForProperty(property, name);
- SEL a2_setter = prefixedSelector(setter);
- SEL getter = getterForProperty(property, name);
- A2BlockDelegateInfo info = {
- setter, a2_setter, getter
- };
- [propertyMap setObject:(__bridge id)&info forKey:protocol];
- infoAsPtr = (__bridge void *)[propertyMap objectForKey:protocol];
- IMP setterImplementation = imp_implementationWithBlock(^(NSObject *delegatingObject, id delegate) {
- A2DynamicDelegate *dynamicDelegate = getDynamicDelegate(delegatingObject, protocol, infoAsPtr, YES);
- if ([delegate isEqual:dynamicDelegate]) {
- delegate = nil;
- }
- dynamicDelegate.realDelegate = delegate;
- });
- if (!swizzleWithIMP(self, setter, a2_setter, setterImplementation, "v@:@", YES)) {
- bzero(infoAsPtr, sizeof(A2BlockDelegateInfo));
- return;
- }
- if (![self instancesRespondToSelector:getter]) {
- IMP getterImplementation = imp_implementationWithBlock(^(NSObject *delegatingObject) {
- return [delegatingObject bk_dynamicDelegateForProtocol:a2_protocolForDelegatingObject(delegatingObject, protocol)];
- });
- addMethodWithIMP(self, getter, NULL, getterImplementation, "@@:", NO);
- }
- }
- - (id)bk_ensuredDynamicDelegate
- {
- Protocol *protocol = a2_delegateProtocol(self.class);
- return [self bk_ensuredDynamicDelegateForProtocol:protocol];
- }
- - (id)bk_ensuredDynamicDelegateForProtocol:(Protocol *)protocol
- {
- const A2BlockDelegateInfo *info = [self.class bk_delegateInfoForProtocol:protocol];
- return getDynamicDelegate(self, protocol, info, YES);
- }
- @end
|