Skip to content

Instantly share code, notes, and snippets.

@swillits
Last active September 13, 2015 17:15
Show Gist options
  • Save swillits/3a23829b205b83e98136 to your computer and use it in GitHub Desktop.
Save swillits/3a23829b205b83e98136 to your computer and use it in GitHub Desktop.
// 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