Skip to content

Instantly share code, notes, and snippets.

@swillits
Created February 2, 2018 05:36
Show Gist options
  • Save swillits/042655d560b3591400e6c768b7bf6471 to your computer and use it in GitHub Desktop.
Save swillits/042655d560b3591400e6c768b7bf6471 to your computer and use it in GitHub Desktop.
EnforceSubclassImplementations – Used to enforce that your class hierarchy overrides required methods
//
// 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
//
// 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);
}
}
@swillits
Copy link
Author

swillits commented Feb 2, 2018

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); }

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment