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.
Related
Wow, great issue I have found for myself.
What is it? The candy or the garlic?
something about Objective-C:
Are there any issues not to use 'self' in (+) - class methods as class?
in the deep of a class...
+(NSDate*)dateWithTimeInterval:(NSTimeInterval)interval {
return [self dateWithTimeIntervalSince1970:interval];
}
Ruby here:
For example, in Ruby everything is object and class is object of class Class and there is a good practice to rely on self:
class DateClass
# self is DateClass here, inside of class definition, uh
self.dateWithTimeInterval(interval)
self.dateWithTimeIntervalSince1970(interval)
end
end
Perl here:
Another example was found in perl oop deep: (thanks for this thread)
sub new {
my $proto = shift || die "Must pass a class or object into new()";
my $class = ref($proto) || $proto;
bless {}, $class;
}
So, in Perl and in Ruby guys always rely on $class refs
Maybe example with Perl code not obvious, but it happens all time. Programmers rely on $class reference and take class name with it. also, they can invoke some methods with it:
my $class = 'Class';
$class->new();
or
Class::->new()
After all...
Which pitfalls or caveats could you provide against usage self as class in objective-c?
Usually you use self whenever you can but of course, there are situations when referencing the class by [MyClass class] is desired. Almost all of the scenarios are related to inheritance.
For example, a creator method for a class A.
#implementation A
+ (id)createInstanceWithParam:(NSInteger)param {
return [[self alloc] initWithParam:param];
}
#end
Will work correctly even if we create a subclass B. However, if we decide to implement a class cluster, then we have to reference classes by names:
#implementation SomeDataStructure
+ (id)createInstanceWithType:(NSInteger)type {
if (type == 0) {
return [[DataStructureImpl1 alloc] init];
}
else if (type == 1) {
return [[DataStructureImpl2 alloc] init];
}
}
#end
Another example is the common example of +initialize
+ (void)initialize {
if (self == [MyClass class]) {
...perform initialization...
}
}
And of course, if you are overriding a method, then using self or using [MySelf class] can be a distinction between your overriden implementation and the original implementation. Although super could be used there, too.
TLDR:
self is preferred but be careful with subclasses/superclasses.
For understanding pros and cons of using self vs. class name let's consider one situation:
Class A is subclass of NSDate and implements method +(NSDate*)dateWithTimeInterval:(NSTimeInterval)interval.
Class B is subclass of A and overrides implementation of +dateWithTimeIntervalSince1970:(NSTimeInterval)interval method that declared in NSDate.
Now let's consider two possible implementations of +(NSDate*)dateWithTimeInterval:(NSTimeInterval)interval method in A:
1. Using self
+(NSDate*)dateWithTimeInterval:(NSTimeInterval)interval {
return [self dateWithTimeIntervalSince1970:interval];
}
if run [B dateWithTimeInterval:interval]; then self in above code is kind of B class and as expected custom implementation (in class B) for +(NSDate*)dateWithTimeIntervalSince1970:(NSTimeInterval)interval method would be called.
2. Using directly NSDate
+(NSDate*)dateWithTimeInterval:(NSTimeInterval)interval {
return [NSDate dateWithTimeIntervalSince1970:interval];
}
if run [B dateWithTimeInterval:interval]; then overridden implementation (in class B) would be ignored and instead of it: original implementation (in class NSDate) for +(NSDate*)dateWithTimeIntervalSince1970:(NSTimeInterval)interval method would be called. It's so because we directly send message to NSDate: [NSDate dateWithTimeIntervalSince1970:interval];.
This behavior is unexpected for developer.
For the same reason declare methods in such way:
+(instancetype)dateWithTimeInterval:(NSTimeInterval)interval {
return [self dateWithTimeIntervalSince1970:interval];
}
By using instancetype compiler will know what kind of object is returned by method-initializer. When you call [B dateWithTimeInterval:interval] it returns object of kind B but not NSDate.
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)];
}
I did try to google this, but actually found nothing. Coming from a strong Smalltalk background, I thought the following would be fine:
#import "ValveTargetState.h"
- (id) targetStateClass {
return ValveTargetState;
}
- (void) targetIsActive:(BOOL)isActive {
self.targetState = [[[self targetStateClass] alloc] initValve: self isActive: isActive];
[self.targetState push];
}
Basically, I've added a method, so that subclasses can tune what the targetStateClass is used. It was my understanding, that like Smalltalk, classes are objects too in ObjC. But Xcode tells me
Unexpected interface name 'ValveTargetState': expected expression
(for the return ValveTargetState; expression)
I think I'm missing something obvious, but what is it?
Try this:
- (Class)targetStateClass
{
return [ValveTargetState class];
}
Assuming that ValveTargetState is a class that inherits ultimately from NSObject, either
[ValveTargetState class]
or
[ValveTargetState self]
will give you the pointer to the class object for ValveTargetState.
It would be much better to use ValveTargetState directly, but unfortunately the name of a class is not a valid expression in Objective-C.
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];
}
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.