Created
February 2, 2018 05:36
-
-
Save swillits/042655d560b3591400e6c768b7bf6471 to your computer and use it in GitHub Desktop.
EnforceSubclassImplementations – Used to enforce that your class hierarchy overrides required methods
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
// | |
// EnforceSubclassImplementations.h | |
// | |
// Created by Seth Willits on 2/9/15. | |
// Copyright (c) 2015. All rights reserved. | |
// | |
#import <Foundation/Foundation.h> | |
#ifdef __cplusplus | |
extern "C" { | |
#endif | |
void EnforceSubclassesImplement(Class klass, Protocol * protocol, BOOL deeply); | |
void EnforceSubclassImplementationsNow(void); | |
#ifdef __cplusplus | |
} | |
#endif |
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
// | |
// EnforceSubclassImplementations.m | |
// | |
// Created by Seth Willits on 2/9/15. | |
// Copyright (c) 2015. All rights reserved. | |
// | |
#import <objc/runtime.h> | |
#import "EnforceSubclassImplementations.h" | |
#import <AraeliumLogging.h> | |
static void EnforceSubclassImplementationInitialize(void); | |
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 BOOL EnforceSubclassImplementsProtocol(Class superclass, Class subclass, Protocol * protocol); | |
static NSArray * SubclassesOfClass(Class theClass, BOOL deeply); | |
static dispatch_semaphore_t sSemaphore = nil; | |
static NSMutableSet * sEnforcements = nil; | |
#if DEBUG | |
#if MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_7 | |
static __attribute__((constructor)) void DoEnforceSubclassImplementations() | |
{ | |
EnforceSubclassImplementationsNow(); | |
} | |
#endif | |
#endif | |
void EnforceSubclassesImplement(Class klass, Protocol * protocol, BOOL deeply) | |
{ | |
#if DEBUG | |
EnforceSubclassImplementationInitialize(); | |
dispatch_semaphore_wait(sSemaphore, DISPATCH_TIME_FOREVER); | |
[sEnforcements addObject:@[klass, protocol, @(deeply)]]; | |
dispatch_semaphore_signal(sSemaphore); | |
dispatch_async(dispatch_get_main_queue(), ^{ | |
EnforceSubclassImplementationsNow(); | |
}); | |
#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 = ({ | |
// This logic is a little weird, because it goes from leaves upwards, but roll with it. | |
unsigned int numClasses = 0; | |
Class * classes = objc_copyClassList(&numClasses); | |
NSMutableDictionary * subclassesByParentClass = [NSMutableDictionary dictionary]; | |
for (NSArray * pair in classesAndProtocols) { | |
Class klass = pair[0]; | |
BOOL deeply = [pair[2] boolValue]; | |
NSMutableSet * set = [NSMutableSet set]; | |
[subclassesByParentClass setObject:set forKey:NSStringFromClass(klass)]; | |
if (deeply) { | |
for (unsigned int i = 0; i < numClasses; i++) { | |
Class subclass = classes[i]; | |
Class ancestor = class_getSuperclass(subclass); | |
while (ancestor) { | |
if (ancestor == klass) { | |
if (![NSStringFromClass(subclass) hasPrefix:@"NSKVONotifying_"]) { | |
[set addObject:subclass]; | |
} | |
} | |
ancestor = class_getSuperclass(ancestor); | |
} | |
} | |
} | |
} | |
for (unsigned int i = 0; i < numClasses; i++) { | |
Class subclass = classes[i]; | |
Class superclass = class_getSuperclass(subclass); | |
// If this class is a direct subclass of a class that needs Enforcement... | |
while (superclass) { | |
NSMutableSet * setOfDirectSubclasses = [subclassesByParentClass objectForKey:NSStringFromClass(superclass)]; | |
if (setOfDirectSubclasses) { | |
// ... then add it to the list | |
if (![NSStringFromClass(subclass) hasPrefix:@"NSKVONotifying_"]) { | |
[setOfDirectSubclasses addObject:subclass]; | |
} | |
} | |
subclass = superclass; | |
superclass = class_getSuperclass(subclass); | |
} | |
} | |
free(classes); | |
subclassesByParentClass; | |
}); | |
// ---------------- | |
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) { | |
if (!EnforceSubclassImplementsProtocol(superclass, subclass, protocol)) { | |
everythingAlright = NO; | |
} | |
} | |
} | |
if (!everythingAlright) { | |
fflush(stdout); | |
assert(false && "Failed EnforceSubclassesImplement"); | |
} | |
} | |
#endif // DEBUG | |
} | |
BOOL EnforceSubclassImplementsProtocol(Class superclass, Class subclass, Protocol * protocol) | |
{ | |
__block BOOL everythingAlright = YES; | |
// 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)); | |
fflush(stdout); | |
everythingAlright = NO; | |
} | |
}); | |
} | |
return everythingAlright; | |
} | |
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
Create a protocol with the methods that are required (typically overrides of abstract or no-op implementations in super class), then enforce:
+ (void)load { EnforceSubclassesImplement([self class], @protocol(RequiredMethods), NO); }