I have an iOS static library which defines an NSOperation
base class that clients should subclass to add their own logic to: @interface BaseClass : NSOperation
Clients register their subclasses with a manager: -[OperationManagerClass registerClass:forType:]
In the manager, I want to enforce that you must register a subclass of BaseClass
not just NSOperation
Okay, so seems like asserting +isSubclassOfClass:
should get the job done. But... it doesn't.
@implementation OperationManagerClass
- (void)registerClass:(Class)aClass forType:(NSString *)type
{
NSAssert([aClass isSubclassOfClass:[BaseClass class]);
self.registeredClasses[type] = aClass;
}
@end
The assert is always NO
, even when passed BaseClass
.
What about traveling higher up the class hierarchy? NSOperation
and NSObject
both respond YES
!
(lldb) p (BOOL)[aClass isSubclassOfClass:[BaseClass class]]
(BOOL) $0 = NO
(lldb) po aClass
BaseClass
(lldb) p (BOOL)[aClass isSubclassOfClass:[NSOperation class]]
(BOOL) $2 = YES
(lldb) p (BOOL)[aClass isSubclassOfClass:[NSObject class]]
(BOOL) $3 = YES
Mind you, the consumers of the base operation class are subclassing in the iOS application project, and OperationManagerClass
and BaseClass
are in an included static library. Why do I think this might have something to do with isSubclassOfClass:
being wrong? Because of the following...
Still in libSharedStuff.a
@implementation OperationManagerClass
- (void)registerClass:(Class)aClass forType:(NSString *)type
{
// Obviously OperationManagerClass.m cannot #import "OutsideClass.h"
NSAssert([aClass isSubclassOfClass:NSClassFromString(@"OutsideClass");
self.registeredClasses[type] = aClass;
}
@end
In application target
@interface OutsideClass : NSOperation
@end
@interface OutsideClassSubA : OutsideClass
@end
... yields the following results when OutsideClassSubA
is passed in:
(lldb) p (BOOL)[aClass isSubclassOfClass:[OutsideClass class]]
(BOOL) $0 = YES
(lldb) po aClass
OutsideClassSubA
(lldb) p (BOOL)[aClass isSubclassOfClass:[NSOperation class]]
(BOOL) $2 = YES
(lldb) p (BOOL)[aClass isSubclassOfClass:[NSObject class]]
(BOOL) $3 = YES
What's going on here? Why does +isSubclassOfClass:
give the wrong answer? How can I enforce that the aClass
parameter must be a subclass of my BaseClass
?
I actually have two static libraries (libSharedStuff.a
and libHelperSharedStuff.a
). The application target links with libSharedStuff.a
, and the unit test target depends on the application target as well as links libHelperSharedStuff.a
. When BaseClass.m
is a member of both static library targets, then +isSubclassOfClass:
will fail in unit test assertions. Specifically, it fails when I pass MockBaseClass
, which is a subclass of BaseClass
that is part of the unit test target.
All of this is illustrated in the project linked above.
As I explain in the edit of my question, I'm including BaseClass.m
in both static libraries libSharedStuff.a
and libHelperSharedStuff.a
. The main application target links against libSharedStuff.a
and the unit test target links with libHelperSharedStuff.a
. This means that when the .xctest
bundle is injected into my application at runtime, the BaseClass
symbol is defined twice
(Maybe? Is that possible? What actually happens in the runtime?).
I seems that in my unit test target MockSubclassOfBaseClass
inherits from the BaseClass
symbol in libHelperSharedStuff.a
, whereas in my application target SubclassOfBaseClass
inherits from the BaseClass
symbol in libSharedStuff.a
. If that's the case, then it makes sense why +isSublcassOfClass:
responds NO
when MockSubclassOfBaseClass
is passed in!
Any answers that clarifies, confirms, or gives better insight that I've got the right idea here are greatly appreciated.
Make sure that the static library is actually causing the symbols to be linked in correctly. My immediate guess would be that the static lib–provided classes passed as aClass are actually Nil at runtime, and therefore returning 0 to any messages sent it.
One way to ensure this is to make a constructor function which uses them:
__attribute__((constructor)) static void EnsureClassesAreLoaded(void) {
[BaseClass self];
}
Weak-linked classes can be a bit tricksy.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With