Objective-C subclass and base class casting - objective-c

I'm going to create a base class that implements very similar functions for all of the subclasses. This was answered in a different question. But what I need to know now is if/how I can cast various functions (in the base class) to return the subclass object. This is both for a given function but also a function call in it.
(I'm working with CoreData by the way)
As a function within the base class (this is from a class that is going to become my subclass)
+(Structure *)fetchStructureByID:(NSNumber *)structureID inContext:(NSManagedObjectContext *)managedObjectContext {...}
And as a function call within a given function:
Structure *newStructure = [Structure fetchStructureByID:[currentDictionary objectForKey:#"myId"]];
inContext:managedObjectContext];
Structure is one of my subclasses, so I need to rewrite both of these so that they are "generic" and can be applied to other subclasses (whoever is calling the function).
How do I do that?
Update: I just realized that in the second part there are actually two issues. You can't change [Structure fetch...] to [self fetch...] because it is a class method, not an instance method. How do I get around that too?

If I understand your question correctly I believe the key is the [self class] idiom.
As far as your update goes requesting a way to call a class method on the current class you can use [self class]. As in:
Structure *newStructure = [[self class] fetchStructureByID:[currentDictionary
objectForKey:#"myId"]];
inContext:managedObjectContext];
EDIT: I redid this to return id per #rpetrich's comment -- much cleaner and avoids the need for -isKindOfClass: as long as you're sure of the type of the instance you're calling -createConfiguredObject on.
As for the first part, you could just return an id (pointer to any object) and document that it will return an instance of the same class it's called upon. Then in the code you need to use [self class] anywhere you instantiate a new object in a method.
e.g. if you have a -createConfiguredObject method which returns an instance of the same class it's called on, it would be implemented as follows:
// Returns an instance of the same class as the instance it was called on.
// This is true even if the method was declared in a base class.
-(id) createConfiguredObject {
Structure *newObject = [[[self class] alloc] init];
// When this method is called on a subclass newObject is actually
// an instance of that subclass
// Configure newObject
return newObject;
}
You can then use this in code as follows:
StructureSubclass *subclass = [[[StructureSubclass alloc] init] autorelease];
subclass.name = #"subclass";
// No need to cast or use isKindOfClass: here because returned object is of type id
// and documented to return instance of the same type.
StructureSubclass *configuredSubclass = [[subclass createConfiguredObject] autorelease];
configuredSubclass.name = #"configuredSubclass";
For reference, what I was referring to with -isKindOfClass: and casting to the proper subclass is as follows:
Structure *structure;
// Do stuff
// I believe structure is now pointing to an object of type StructureSubclass
// and I want to call a method only present on StructureSubclass.
if ([structure isKindOfClass:[StrucutreSubclass class]]) {
// It is indeed of type StructureSubclass (or a subclass of same)
// so cast the pointer to StructureSubclass *
StructureSubclass *subclass = (StructureSubclass *)structure;
// the name property is only available on StructureSubclass.
subclass.name = #"myname";
} else {
NSLog(#"structure was not an instance of StructureSubclass when it was expected it would be.");
// Handle error
}

Related

Besides syntax, is there any difference between writing an initialization method as an instance or class method?

Here is initialization as a class method:
+ (instancetype)imageEditorForDocument:(ImageDocument*)imageDocument
{
ImageEditorViewController* result = nil;
result = [[ImageEditorViewController alloc] initWithNibName:#"ImageEditorViewController"
bundle:[NSBundle mainBundle]];
if ( result )
{
result.imageDocument = imageDocument;
}
return result;
}
And here is an initialization as an instance method:
- (instancetype)initWithDocument:(ImageDocument *)imageDocument
{
self = [[ImageEditorViewController alloc] initWithNibName:#"ImageEditorViewController"
bundle:[NSBundle mainBundle]];
if( self )
{
self.imageDocument = imageDocument;
}
return self;
}
As far as I can tell, the only difference is that the sender doesn't need to call alloc when using a class initializer
But is there any other reason?
Also, a side question is there a name for a initializer that is a class method?
Like in NSColor
+ (NSColor *)colorWithRed:(CGFloat)red green:(CGFloat)green blue:(CGFloat)blue alpha:(CGFloat)alpha NS_AVAILABLE_MAC(10_9);
With manual reference counting (MRC), the difference was the factory method typically returned an autoreleased instance while the instance method returned an instance that the caller now owned.
With automatic reference counting (ARC), that difference is now hidden from the calling code.
So the only difference is convenience for the caller as well as slightly more work for the author of the class to write the optional factory method.
BTW - your instance method initializer is incorrect. It should be more like this (not the proper way to set self):
- (instancetype)initWithDocument:(ImageDocument *)imageDocument
{
self = [super initWithNibName:#"ImageEditorViewController"
bundle:[NSBundle mainBundle]];
if( self )
{
self.imageDocument = imageDocument;
}
return self;
}
And given this, your factory method should really be:
+ (instancetype)imageEditorForDocument:(ImageDocument*)imageDocument
{
ImageEditorViewController* result = [[[self class] alloc] initWithDocument:imageDocument];
return result;
}
Yes there is a major difference. If you implement a factory method (class method) you could for example choose to return an already existing instance of your object (e.g. from some sort of cache) instead of creating a new instance. Imagine you have a class Country which is expensive to initialize. Hence, write the following factory method that looks up the cache first and only if it does not find the country it creates a new object:
+(instancetype) countryForCountryCode: (NSString *)countryCode
{
Country *country = ... // ask our "CountryCache" if we already have a cached instance of the country
if (!country) {
country = [[Country alloc] init];
// Here you would also set up the new Country object, or even write a "private" initializer
// You would also add the new instance to the cache here
}
return country;
}
On the other hand, when you choose a "conventional" initializer the caller will always have created a fresh instance via alloc before your initializer gets called, and you'll have no way of returning a cached object instead.
Lastly I just remembered that I personally use factory methods whenever I deal with persistent objects (if not using CoreData). So, if I want to get a new object from the database I usually implement a factory method called "load". And to actually create a new record in the database I would implement another factory method named "create". If you would use initializers here it would become very confusing.

My object's isKindOfClass executed with unexpected answer

I've been trying to get this script print out the one of the two types of object that was inserted. However, it always prints both types while only one thing is inserted. There are two classes, VKJItem and VKJBox. VKJBox is a subclass of VKJItem.
This is the main.m:
VKJBox *box1 = [[VKJBox alloc] init];
VKJBox *box2 = [[VKJBox alloc] init];
[box1 addItem:box2];
and this is the implementation of my VKJBox's addItem method:
if ([item isKindOfClass:[VKJBox class]]) {
NSLog(#"BOX");
}
if ([item isKindOfClass:[VKJItem class]]) {
NSLog(#"ITEM");
}
The problem is that the script prints both BOX and ITEM to the console.
VKJBox is a subclass of of VKJItem and therefore VKJBox is a kind of VKJItem and a kind of VKJBox.
-isKindOfClass: is used to determine whether an object is an instance of a class or an instance of a class which inherits from the class.
For example:
isKindOfClass[UIView class] will be true for UIImageView, UILabel, etc.
Use the -isMemberOfClass: to check if the object is an instance of the specified class exactly.
On of your Classes must be a subclass of Other.
isKindClass:
Returns a Boolean value that indicates whether the receiver is an instance of given class or an instance of any class that inherits from that class.
isMemberOfClass:
Returns a Boolean value that indicates whether the receiver is an instance of a given class.
You need to use isMemberOfClass
Source

Class issues: should I use everywhere self as class in objective c?

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.

What should be done with inherited factory methods?

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

How can I pass a class name as an argument to an object factory in cocoa?

I am working on an object factory to keep track of a small collection of objects. The objects can be of different types, but they will all respond to createInstance and reset. The objects can not be derived from a common base class because some of them will have to derive from built-in cocoa classes like NSView and NSWindowController.
I would like to be able to create instances of any suitable object by simply passing the desired classname to my factory as follows:
myClass * variable = [factory makeObjectOfClass:myClass];
The makeObjectOfClass: method would look something like this:
- (id)makeObjectOfClass:(CLASSNAME)className
{
assert([className instancesRespondToSelector:#selector(reset)]);
id newInstance = [className createInstance];
[managedObjects addObject:newInstance];
return newInstance;
}
Is there a way to pass a class name to a method, as I have done with the (CLASSNAME)className argument to makeObjectOfClass: above?
For the sake of completeness, here is why I want to manage all of the objects. I want to be able to reset the complete set of objects in one shot, by calling [factory reset];.
- (void)reset
{
[managedObjects makeObjectsPerformSelector:#selector(reset)];
}
You can convert a string to a class using the function: NSClassFromString
Class classFromString = NSClassFromString(#"MyClass");
In your case though, you'd be better off using the Class objects directly.
MyClass * variable = [factory makeObjectOfClass:[MyClass class]];
- (id)makeObjectOfClass:(Class)aClass
{
assert([aClass instancesRespondToSelector:#selector(reset)]);
id newInstance = [aClass createInstance];
[managedObjects addObject:newInstance];
return newInstance;
}
I have right a better tutorial on that , please checkout
https://appengineer.in/2014/03/13/send-class-name-as-a-argument-in-ios/
It's pretty easy to dynamically specify a class, in fact you can just reference it by it's name:
id string = [[NSClassFromString(#"NSString") alloc] initWithString:#"Hello!"];
NSLog( #"%#", string );
One other tip, I would avoid using the nomenclature 'managed object' since most other Cocoa programmers will read that as NSManagedObject, from Core Data. You may also find it easier to use a global NSNotification (that all your reset-able objects subscribe to) instead of managing a collection of different types of objects, but you're more informed to make that decision than I am.
The bit of the answer missing from the other answers is that you could define a #protocol containing your +createInstance and +reset methods.
It sounds like you want something like:
- (id)makeObjectOfClassNamed:(NSString *)className
{
Class klass = NSClassFromString(className);
assert([klass instancesRespondToSelector:#selector(reset)]);
id newInstance = [klass createInstance];
[managedObjects addObject:newInstance];
return newInstance;
}
This would assume a class method named +createInstance. Or you could just use [[klass alloc] init].
To call it:
MyClass *variable = [factory makeObjectOfClassNamed:#"MyClass"];
Depending on what you're trying to do, it might be better to pass around class objects than strings, e.g.:
MyClass *variable = [factory makeObjectOfClass:[MyClass class]];