1

I've created a macro that, if compiling in debug mode, will check to make sure the method has been called by a subclass with the same name (to stop external classes from calling it). If that is not the case, it will throw an exception:

#ifdef DEBUG

#define CHECK_INHERITANCE() \
do { \
    NSString *this = [NSThread callStackSymbols][0]; \
    NSString *parent = [NSThread callStackSymbols][1]; \
    NSError *error = NULL; \
    NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:@"\\[\\w+ ((\\w+:)+)\\]" options:NSRegularExpressionCaseInsensitive error:&error]; \
    NSTextCheckingResult *resultThis = [regex firstMatchInString:this options:0 range:NSMakeRange(0, this.length)]; \
    NSString *strThis = [this substringWithRange:[resultThis rangeAtIndex:1]]; \
    NSTextCheckingResult *resultParent = [regex firstMatchInString:parent options:0 range:NSMakeRange(0, parent.length)]; \
    NSString *strParent = [parent substringWithRange:[resultParent rangeAtIndex:1]]; \
    if(![strThis isEqualToString:strParent]) [NSException raise:NSGenericException format:@"Must be called from a subclass"]; \
} while (0);

#else

#define CHECK_INHERITANCE() //

#endif

I tested it and it works, but the NSException it throws doesn't give me any stack trace that would hint at why it crashed, nor does it show the message "Must be called from a subclass". I just get "0 __kill" on the main thread and a bunch of lines of assembler. How can I get it to throw me a more useful exception? Am I doing it wrong?

rmaddy
  • 314,917
  • 42
  • 532
  • 579
jowie
  • 8,028
  • 8
  • 55
  • 94
  • Have you considered using an assertion instead of raise? – Wain Jun 24 '13 at 12:12
  • Not used NSAssert before. I just tried `NSAssert(NO, @"Must be called from a subclass");', and although I get an Assertion failure that tells me which line in code, I still don't get the message coming through. – jowie Jun 24 '13 at 12:30
  • Where + how are you trying to pick up the message / trace? – Wain Jun 24 '13 at 12:35
  • I would just like a runtime message to tell me what has happened, and for the program to abort. This is to stop people from calling the method directly from a class other than the subclass. – jowie Jun 24 '13 at 12:44
  • So the assertion should print the message you provide to the console. – Wain Jun 24 '13 at 12:46
  • It doesn't appear to for me. I just get `*** Assertion failure in -[ClassName functionName:], /Workspace/objc-project/Classes/View/Game/Slots/Base/ClassName.m:52` – jowie Jun 24 '13 at 12:48
  • Do you have an all exception breakpoint? Continue past it an you'll see: `*** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Must be called from a subclass'` – Wain Jun 24 '13 at 12:55
  • I do have an all exception breakpoint, but it is switched off. However, I discovered that if I pressed the 'Continue program execution' button after it gets to the Assertion failure line, I get the `Terminating app` message you mentioned. Why does it break on the Assertion failure and not the Terminating app message? – jowie Jun 24 '13 at 13:32
  • Also, if I switch it back to an `NSException`, I realise I get the same thing... If I press the continue button, it prints the exception comment. I wonder why I have to press the continue button...? – jowie Jun 24 '13 at 13:34
  • To let lldb print it. BTW: Your check with callstack symbols is no good idea, because it is fragile. Why do you have to check this? You can control the publicity of methods with Interfaces. – Amin Negm-Awad Jun 24 '13 at 14:32
  • Just something I was experimenting with. An internal API that would force other devs to only call those methods from a subclass and nowhere else. Not sure if I'm going to keep it, but just wanted to work out how best to use NSException (or NSAssert). – jowie Jun 24 '13 at 15:47

0 Answers0