I have two similar classes, MultiSlotBlock and SingleSlotBlock. They have started to share a lot of common code so I have decided to do some refactoring and pull some of the methods up to a new superclass, let's call it Block.
Now one of the methods that I pull up, simplified for the example, looks like this:
// (Block.mm)
- (void)doACommonBehaviour
{
// .. does some stuff
[self doAUniqueBehaviour];
}
The problem here is that [self doAUniqueBehaviour] is showing a warning because of course my superclass doesn't implement this method anywhere.
The two solutions I thought of don't sound great to me. One is to use a protocol (the way I am currently doing it) like so:
// (Block.mm)
- (void)doACommonBehaviour
{
// .. does some stuff
if ([self conformsToProtocol:#protocol(UniqueBehaviourProtocol)])
{
id<UniqueBehaviourProtocol> block = (id<UniqueBehaviourProtocol>)self;
[block doAUniqueBehaviour];
}
}
The other is to have a blank method body in my superclass (in this case there would be a lot) and just return doesNotRespondToSelector.
Something is tingling at the back of my mind that I should be using the Strategy Pattern, but I might be way off, and I haven't thought through how that would be implemented.
Any ideas? Thanks.
EDIT: I know for a fact that doAUniqueBehaviour will be implemented in all subclasses, it is just the implementation that will differ.
The superclass should not know about its subclasses. You should implement the
- (void)doACommonBehaviour method in every subclass and there:
- (void)doACommonBehaviour
{
[super doACommonBehaviour];
[self doAUniqueBehaviour];
}
EDIT - clarification:
If all the subclasses are going to implement -doAUniqueBehaviour then it should be implemented in the superclass (even empty) and each subclass will override it to its needs.
If subclass1 implements -doAUniqueBehaviour1, subclass2 implements -doAUniqueBehaviour2 etc then do what I propose above; eg. in subclass1:
- (void)doACommonBehaviour
{
[super doACommonBehaviour];
[self doAUniqueBehaviour1];
}
There is not such concept as abstract class in Objective-C. In order to avoid the warning, you have to provide a default implementation in your base class. Usually, this implementation will throw a doesNotRespondToSelector error at runtime:
- (id)someMethod:(SomeObject*)blah
[self doesNotRecognizeSelector:_cmd];
return nil;
}
Note: the _cmd argument is the invoked selector.
#Dimitri's suggestion will work, but instead of forcing each subclass to implement the same method, you can declare it once in Block, and just above that method (in the implementation file, not header) declare the unique method like so:
- (void) doUniqueBehaviour { }
- (void) doCommonBehaviour {
// any common code you need
[self doUniqueBehaviour];
}
This will prevent any compiler warnings, and you can override -doUniqueBehaviour in subclasses as you like. It also avoids code duplication and reduces the potential for changing the code in one subclass but not another. Plus, you don't need a separate protocol, and dynamic typing is preserved.
Related
I have the following method:
- (FDModel *)_modelForClass: (Class)modelClass
withIdentifier: (NSString *)identifier
which should take in a Class and a identifier, create an instance of modelClass, assign the identifier and do some other work based on the fact that it assumed modelClass is a subclass of FDModel.
I can put in a check that raises some error or exception if [modelClass isSubclassOfClass: [FDModel class]] == NO but I was trying to see if there was a way to enforce this at compile time.
EDIT: I understand that some people see this as a obvious factory method but the modelClass parameter is actually passed in by the user of my library through a delegate callback - (Class<FDModel>)modelClassForDictionary: (NSDictionary *)dictionary;. This question was more aimed at making the user of my library return a Class that has a specific subclass.
I would consider the plain answer to your question being no; there is no way of checking if a class passed as a parameter is of a certain kind.
But I'd like to argue that the essence of your question primarily points to a design issue, i.e. can't your instance-generating method be expressed as a factory method? Like so:
#interface FDModel
+ (instancetype)modelWithIdentifier:(NSString *)identifier;
#end
In the above case you would simply do:
[FDModel modelWithIdentifier:anIdentifier];
The actual class returned (and the initialisation logic) being specified by the factory method implementation through subclassing of the FDModel class:
#implementation FDModelSubclass
+ (instancetype)modelWithIdentifier:(NSString *)identifier
{
FDModel *model = [super modelWithIdentifier:identifier];
if (model)
{
// do additional init stuff
}
return model;
}
#end
Nothing to check, no chance to go wrong.
After some research I don't think you can do it at compile time - you have to do it at runtime as you expected.
BOOL classConformsToProtocol = [class conformsToProtocol:#protocol(OKAProtocol)];
OR
BOOL classConformsToProtocol = [self class:[OKAClass class] conformsToProtocol:#"OKAProtocol"];
------
- (BOOL)class:(Class)class conformsToProtocol:(NSString *)protocol;
{
return [class conformsToProtocol:NSProtocolFromString(protocol)];
}
Suppose I have a class BasicDate, and a subclass of BasicDate called EuroDate. The difference between the classes is month-day-year versus day-month-year. I know it'd probably be better to just have methods on the same class to output them differently... but that's not the point of this question.
BasicDate has the following init method:
-(id)initWithMonth:(int)m andDay:(int)d andYear:(int)y {
if(self = [super init]) { /*initialize*/ } return self;
}
And the matching factory method then looks like this:
+(BasicDate)dateWithMonth:(int)m andDay:(int)d andYear:(int)y {
return [[BasicDate alloc] initWithMonth: m andDay: d andYear: y];
}
But if my subclass, EuroDate which would use a factory method more like this:
+(EuroDate)dateWithDay:(int)d andMonth:(int)m andYear:(int)y {
return [[EuroDate alloc] initWithDay: d andMonth: m andYear: y];
} //we can assume that EuroDate includes this init method...
This is all fine. Now, we assume that both classes have their own description method, which will print MMDDYYYY for BasicDate, but DDMMYYYY with EuroDate. This is still all fine.
But if I do this:
EuroDate today = [EuroDate dateWithMonth:10 andDay:18 andYear:2013];
This will call the BasicDate factory method that EuroDate has inherited. The problem is, remember how BasicDate's factory method looks? return [[BasicDate alloc] ...]
So today polymorphs into a BasicDate despite me wanting to store it as a EuroDate, so if I call the description method, it will print 10182013 rather than 18102013.
There are two solutions to this problem I have found.
Solution 1: Change BasicDate's factory method. Rather than return [[BasicDate alloc] ..., I can instead do return [[[self class] alloc] ...] This works and will allow me to use this method for BasicDate or any of BasicDate's subclasses and it will return the right object type.
Solution 2: Override the factory method. Whether I override it to throw an exception or override it to do return [[EuroDate alloc] ...]. The problem with overriding it is that I have to override every factory method for every subclass.
Which is better? What are some downsides to the two possible solutions that I may be missing? What is considered the standard way of handling this issue in Objective C?
You should generally use [[[self class] alloc] init...] in factory methods to ensure that they create instances of the correct class. Note that class isn't a property (and in fact, there's no such thing as a 'class property') so the use of dot syntax there is inappropriate.
Edit
And as pointed out by #ArkadiuszHolko (and Rob, thanks), you should now use instancetype rather than id for the return value, to get the benefits of strong typing while maintaining type flexibility for subclasses. And by the way, Apple's naming conventions suggest avoiding using the word 'and' in method names. So consider rewriting your convenience method like so:
+ (instancetype)dateWithMonth:(int)month day:(int)day year:(int)year
{
return [[self alloc] initWithMonth:month day:day year:year];
}
Problem
I'm including the managed object context as a parameter of a method when I work with Core Data.
Although this makes the code easier to test, it's messy.
Questions
Is this good or bad practice?
Is there a neater, easier way of doing this that keeps methods testable?
Background
The example below is a background operation that has it's own context.
Any advice from more experienced coders would be much appreciated!
Code
#interface JGTrainingGroupCleanupOperation : JGCoreDataOperation {
NSManagedObjectContext *imoc;
}
...
#implementation JGTrainingGroupCleanupOperation
-(void)main {
[self startOperation]; // Setting up the local context etc
[self cleanupTrainingGroupsInMOC:imoc];
[self finishOperation];
}
-(void)cleanupTrainingGroupsInMOC:(NSManagedObjectContext *)moc {
NSSet *trainedGroups = [self fetchAllTrainedGroupsInMOC:moc];
[self deleteDescendantsOfGroups:trainedGroups fromMOC:moc];
[self removeStubAncestorsOfGroups:trainedGroups fromMOC:moc];
}
-(NSSet *)fetchAllTrainedGroupsInMOC:(NSManagedObjectContext *)moc_ {
return [moc_ fetchObjectsForEntityName:kTrainingGroup withPredicate:[NSPredicate predicateWithFormat:#"projectEditedAtTopLevel == nil"]];
}
-(void)deleteDescendantsOfGroups:(NSSet *)trainedGroups fromMOC:(NSManagedObjectContext *)moc_ {
// More code here
}
-(void)deleteDescendantsOfGroup:(JGTrainingGroup *)trainedGroup fromMOC:(NSManagedObjectContext *)moc_ {
// More code here
}
In my (not so humble) opinion I'd say it's mostly a matter of style. You can do it this way or you can #synthesize the moc and call [self moc] or self.moc.
Me? I'd go the accessor route personally, mostly because class members shouldn't have to be told where to find an object dereferenced by an iVar anyway. If you're accessing something that's an iVar within the same class, I'd use the iVar directly or an accessor.
I believe the difference in performance would be negligible, so I wouldn't really bother much on that front (even though you didn't ask).
Just discovered MacRuby this afternoon; man is that ever COOL! However, I've run into some difficulties while attempting to extend an old project with some MacRuby-fu. Here's the deal:
So I have a superclass in Objective-C that looks like this:
#implementation Foo
- (id) init {
if (self = [super init]) {
//Do nothing, don't have enough data...
}
return self;
}
- (id) initWithName:(NSString*)n andLocation:(NSString*)loc andSomethingElse:(Bar*)b {
if (self = [super init]) {
//Set a LOT of internal state...
}
return self;
}
#end
So, in a ruby file, we'll call it Mung.rb that looks like this:
class Mung < Foo
def initWithSomethingElse(else, andEvenMore:more)
super.initWithName("Moop", andLocation:else, andSomethingElse:more.addVal(42))
self
end
end
When I go to instantiate a Mung (myObj = Mung.alloc.initWithSomethingElse("Boo", andEvenMore:"US"), the runtime explodes telling me there is no method defined in Mung's super called 'initWithSomethingElse'. This is true, but it means that I cannot define custom initializers in ruby files. My current workaround is to provide a homogenous initializer that takes a hash, and then the individual subclasses parse the hash as needed. I don't like this approach and would like: A. An explanation of why 'initWithSomethingElse' is ever called on super, and B. If no direct solution can be applied, an alternative workaround. Thanks guys!
You can't call the super version of a different method from a method in MacRuby. The super keyword respects the Ruby semantics and will only dispatch a call to the super version of the current method.
In your case, you may want to send initWithName:andLocation:andSomethingElse: to self directly, and if needed, you can re-define this selector on the class and call super appropriately.
I have a number of Objective-C classes organized in an inheritance hierarchy. They all share a common parent which implements all the behaviors shared among the children. Each child class defines a few methods that make it work, and the parent class raises an exception for the methods designed to be implemented/overridden by its children. This effectively makes the parent a pseudo-abstract class (since it's useless on its own) even though Objective-C doesn't explicitly support abstract classes.
The crux of this problem is that I'm unit testing this class hierarchy using OCUnit, and the tests are structured similarly: one test class that exercises the common behavior, with a subclass corresponding to each of the child classes under test. However, running the test cases on the (effectively abstract) parent class is problematic, since the unit tests will fail in spectacular fashion without the key methods. (The alternative of repeating the common tests across 5 test classes is not really an acceptable option.)
The non-ideal solution I've been using is to check (in each test method) whether the instance is the parent test class, and bail out if it is. This leads to repeated code in every test method, a problem that becomes increasingly annoying if one's unit tests are highly granular. In addition, all such tests are still executed and reported as successes, skewing the number of meaningful tests that were actually run.
What I'd prefer is a way to signal to OCUnit "Don't run any tests in this class, only run them in its child classes." To my knowledge, there isn't (yet) a way to do that, something similar to a +(BOOL)isAbstractTest method I can implement/override. Any ideas on a better way to solve this problem with minimal repetition? Does OCUnit have any ability to flag a test class in this way, or is it time to file a Radar?
Edit: Here's a link to the test code in question. Notice the frequent repetition of if (...) return; to start a method, including use of the NonConcreteClass() macro for brevity.
Here's a simple strategy that worked for me. Just override invokeTest in your AbstractTestCase as follows:
- (void) invokeTest {
BOOL abstractTest = [self isMemberOfClass:[AbstractTestCase class]];
if(!abstractTest) [super invokeTest];
}
You could also override + (id)defaultTestSuite method in your abstract TestCase class.
+ (id)defaultTestSuite {
if ([self isEqual:[AbstractTestCase class]]) {
return nil;
}
return [super defaultTestSuite];
}
It sounds like you want a parameterized test.
Parameterized tests are great whenever you want to have a large number of tests with the same logic but different variables. In this case, the parameter to your test would be the concrete tested class, or possibly a block that will create a new instance of it.
There's an article about implementing parameterized testing in OCUnit here. Here's an example of applying it to testing a class hierarchy:
#implementation MyTestCase {
RPValue*(^_createInstance)(void);
MyClass *_instance;
}
+ (id)defaultTestSuite
{
SenTestSuite *testSuite = [[SenTestSuite alloc] initWithName:NSStringFromClass(self)];
[self suite:testSuite addTestWithBlock:^id{
return [[MyClass1 alloc] initWithAnArgument:someArgument];
}];
[self suite:testSuite addTestWithBlock:^id{
return [[MyClass2 alloc] initWithAnotherArgument:someOtherArgument];
}];
return testSuite;
}
+ (void)suite:(SenTestSuite *)testSuite addTestWithBlock:(id(^)(void))block
{
for (NSInvocation *testInvocation in [self testInvocations]) {
[testSuite addTest:[[self alloc] initWithInvocation:testInvocation block:block]];
}
}
- (id)initWithInvocation:(NSInvocation *)anInvocation block:(id(^)(void))block
{
self = [super initWithInvocation:anInvocation];
if (!self)
return nil;
_createInstance = block;
return self;
}
- (void)setUp
{
_value = _createInstance();
}
- (void)tearDown
{
_value = nil;
}
The simplest way:
- (void)invokeTest {
[self isMemberOfClass:[AbstractClass class]] ?: [super invokeTest];
}
Copy, paste and replace AbstractClass.
I don't see a way to improve on the way you're currently doing things without digging into OCUnit itself, specifically the SenTestCase implementation of -performTest:. You'd be set if it called invoked a method to determine "Should I run this test?" The default implementation would return YES, while your version would be like your if-statement.
I'd file a Radar. The worst that could happen is your code stays the way it is now.