Last active
September 13, 2015 17:15
-
-
Save swillits/3a23829b205b83e98136 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// In a class hierarchy, it's common for superclasses to require that their subclasses | |
// override certain methods. Unfortunately the compiler is of no help in enforcing | |
// this, as there is no annotation to produce a warning if a subclass does not. | |
// | |
// Call EnforceSubclassesImplement(class, protocol) to register that direct subclasses | |
// of the given class must implement the required methods of the given protocol. | |
// At runtime in debug (#define DEBUG 1) builds before main() is called, checking is | |
// done and will log each missing method implementation and abort() early. | |
// | |
// By placing this call in +load of a parent class, no other work ever need be done. | |
// | |
// Ex: | |
// These methods are required to be implemented by direct subclasses | |
@protocol ParentFooSubclassesProtocol | |
+ (NSString *)someClassMethod; | |
- (void)someMethod; | |
@end | |
@implementation ParentFoo | |
+ (void)load | |
{ | |
if ([self class] == [ParentFoo class]) { | |
EnforceSubclassesImplement([self class], @protocol(ParentFooSubclassesProtocol)); | |
} | |
} | |
@end | |
@interface SubFoo : ParentFoo | |
@end | |
@implementation SubFoo | |
@end | |
// ---- Result upon running: ---- | |
// SubFoo does not implement +someClassMethod from ParentFoo | |
// SubFoo does not implement -someMethod from ParentFoo | |
// (exit) | |
// ============================================================================ | |
// ============================================================================ | |
// | |
// EnforceSubclassImplementations.h | |
// | |
// Created by Seth Willits on 2/9/15. | |
// Copyright (c) 2015. All rights reserved. | |
// | |
#import <Foundation/Foundation.h> | |
static void EnforceSubclassImplementationInitialize(void); | |
void EnforceSubclassesImplement(Class class, Protocol * protocol); | |
void EnforceSubclassImplementationsNow(void); | |
// ============================================================================ | |
// ============================================================================ | |
// | |
// EnforceSubclassImplementations.m | |
// | |
// Created by Seth Willits on 2/9/15. | |
// Copyright (c) 2015. All rights reserved. | |
// | |
#import <objc/runtime.h> | |
#import "EnforceSubclassImplementations.h" | |
static void EnumerateMethodsInProtocol(Protocol * protocol, BOOL requiredMethods, BOOL instanceMethods, void (^block)(struct objc_method_description method, BOOL * stop)); | |
static void EnumerateClassInstanceMethods(Class klass, void (^block)(Method method, BOOL *stop)); | |
static dispatch_semaphore_t sSemaphore = nil; | |
static NSMutableSet * sEnforcements = nil; | |
#if DEBUG | |
static __attribute__((constructor)) void DoEnforceSubclassImplementations() | |
{ | |
EnforceSubclassImplementationsNow(); | |
} | |
#endif | |
void EnforceSubclassesImplement(Class klass, Protocol * protocol) | |
{ | |
#if DEBUG | |
EnforceSubclassImplementationInitialize(); | |
dispatch_semaphore_wait(sSemaphore, DISPATCH_TIME_FOREVER); | |
[sEnforcements addObject:@[klass, protocol]]; | |
dispatch_semaphore_signal(sSemaphore); | |
#endif | |
} | |
static void EnforceSubclassImplementationInitialize() | |
{ | |
static dispatch_once_t onceToken; | |
dispatch_once(&onceToken, ^{ | |
sSemaphore = dispatch_semaphore_create(1); | |
sEnforcements = [[NSMutableSet alloc] init]; | |
}); | |
} | |
void EnforceSubclassImplementationsNow(void) | |
{ | |
#if DEBUG | |
EnforceSubclassImplementationInitialize(); | |
@autoreleasepool { | |
NSSet * classesAndProtocols = ({ | |
dispatch_semaphore_wait(sSemaphore, DISPATCH_TIME_FOREVER); | |
NSSet * s = [sEnforcements copy]; | |
#if !__has_feature(objc_arc) | |
[s autorelease]; | |
#endif | |
dispatch_semaphore_signal(sSemaphore); | |
s; | |
}); | |
NSDictionary * subclassesByParentClass = ({ | |
NSMutableDictionary * subclassesByParentClass = [NSMutableDictionary dictionary]; | |
for (NSArray * pair in classesAndProtocols) { | |
Class klass = pair[0]; | |
[subclassesByParentClass setObject:[NSMutableSet set] forKey:NSStringFromClass(klass)]; | |
} | |
unsigned int numClasses = 0; | |
Class * classes = objc_copyClassList(&numClasses); | |
for (unsigned int i = 0; i < numClasses; i++) { | |
Class subclass = classes[i]; | |
Class superclass = class_getSuperclass(subclass); | |
while (superclass) { | |
NSMutableSet * setOfDirectSubclasses = [subclassesByParentClass objectForKey:NSStringFromClass(superclass)]; | |
if (setOfDirectSubclasses) { | |
[setOfDirectSubclasses addObject:subclass]; | |
} | |
subclass = superclass; | |
superclass = class_getSuperclass(subclass); | |
} | |
} | |
free(classes); | |
subclassesByParentClass; | |
}); | |
// ---------------- | |
__block BOOL everythingAlright = YES; | |
for (NSArray * pair in classesAndProtocols) { | |
Class superclass = pair[0]; | |
Protocol * protocol = pair[1]; | |
NSMutableSet * setOfDirectSubclasses = [subclassesByParentClass objectForKey:NSStringFromClass(superclass)]; | |
for (Class subclass in setOfDirectSubclasses) { | |
// Determine if this direct subclass has implementations for the required instance and class methods in protocol | |
for (int checkingClassMethods = 0; checkingClassMethods <= 1; checkingClassMethods++) { | |
EnumerateMethodsInProtocol(protocol, YES, !checkingClassMethods, ^(struct objc_method_description protoMethod, BOOL *stop) { | |
__block BOOL foundMethod = NO; | |
EnumerateClassInstanceMethods(subclass, ^(Method method, BOOL *stop){ | |
if (method_getName(method) == protoMethod.name) { | |
if (strcmp(method_getTypeEncoding(method), protoMethod.types) == 0) { | |
foundMethod = YES; | |
*stop = YES; | |
} | |
} | |
}); | |
if (!foundMethod) { | |
printf("%s does not implement %c%s from %s\n", class_getName(subclass), (checkingClassMethods ? '+' : '-'), sel_getName(protoMethod.name), class_getName(superclass)); | |
everythingAlright = NO; | |
} | |
}); | |
} | |
} | |
} | |
if (!everythingAlright) { | |
assert(false && "Failed EnforceSubclassesImplement"); | |
} | |
} | |
#endif // DEBUG | |
} | |
void EnumerateMethodsInProtocol(Protocol * protocol, BOOL requiredMethods, BOOL instanceMethods, void (^block)(struct objc_method_description method, BOOL * stop)) | |
{ | |
unsigned int methodsCount = 0; | |
struct objc_method_description * methods = protocol_copyMethodDescriptionList(protocol, requiredMethods, instanceMethods, &methodsCount); | |
if (methods) { | |
for (unsigned int i = 0; i < methodsCount; i++) { | |
BOOL stop = NO; | |
block(methods[i], &stop); | |
if (stop) break; | |
} | |
free(methods); | |
} | |
} | |
void EnumerateClassInstanceMethods(Class klass, void (^block)(Method method, BOOL *stop)) | |
{ | |
unsigned int methodsCount = 0; | |
Method * methods = class_copyMethodList(klass, &methodsCount); | |
if (methods) { | |
for (unsigned int i = 0; i < methodsCount; i++) { | |
BOOL stop = NO; | |
block(methods[i], &stop); | |
if (stop) break; | |
} | |
free(methods); | |
} | |
} | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment