Static initializer in Objective-C upon Class Loading - objective-c

I am trying to build something to dynamically instantiate an object from class-name similar to how Java's Class.forName method works, e.g.
Class klass = Class.forName("MyClass");
Object obj = klass.instantiate(...
I didn't see any such behavior in Objective-C so I would like to call a method to register Class when an Objective-C class is loaded. Basically, I would like to call a method that registers my class, e.g.
+ (void)mystatic {
[NSKeyedUnarchiver setClass:[self class] forClassName:"MyClass"]
}
Is there a way to do this in Objective-C on OS X platform?
Thanks.

You want to use NSClassFromString, like this:
Class klass = NSClassFromString(#"MyClass");
id obj = [[klass alloc] init];

First, there is indeed such an equivalent in Objective-C — as #Louis suggested, use NSClassFromString().
Second, if you want a static constructor like in Java, you can do that as well with the +initialize method. See this related SO question.

You can also say
Class myClass = [[NSBundle mainBundle] classNamed: #"MyClassName];
id myInstance = [[myClass alloc] init];
This frequently helps with cases when the runtime may not have come across your class yet.

Related

Call class method of Objective C category

AFNetworking has a class method called + af_sharedImageCache that I want to access from my own category, but I can't figure out the syntax to reference this. Internally it is doing [[self class] af_sharedImageCache] but obviously that's not gonna work for me. :)
That's because + af_sharedImageCache is a private method, not exposed on UIImageView+AFNetworking.h. You can call it, though, using Obj-C runtime.
if ([[self class] respondsToSelector:#sel(af_sharedImageCache)]) {
NSCache *cache = [[self class] performSelector:#sel(af_sharedImageCache)];
}
However, AFImageCache is a private class, and you would have to do the same hack to use its methods. If I were you, I'd create my own cache, as it's clear that AFNetworking doesn't want that you mess with its cache implementation.

Casting down with Objective C

Say I have a subclass of NSManagedObject called MBManagedSquare and MBManagedCircle. An MBManagedSquare and MBManagedCircle define a method prepareFromDictionary:(NSDictionary*)dic, and both of their implementations are different.
Say I have this code:
NSString *type = // could be #"MBManagedSquare" or #"MBManagedCircle"
NSEntityDescription *desc = [NSEntityDescription entityForName:type inManagedObjectContext:_context];
NSManagedObject *object = [[NSManagedObject alloc] initWithEntity:desc insertIntoManagedObjectContext:_context];
So the type of entity it will be with Core Data is determined dynamically via a type string. So all I know is that it is an NSManagedObject.
What I want to do is call the prepareFromDictionary: method for the right class.
So if the type is "MBManagedSquare", I want to cast the object to an MBManagedSquare, and then call
[castedObject prepareFromDictionary:dic];
What I tried doing is:
Class class = NSClassFromString(type);
class *castedObject = (class*)object;
but I get an expected expression error. I'm not sure if this is even possible. How would I do this?
You don't need to worry about calling the right class if the selectors and their parameters match -- ObjC has plenty of dynamic dispatch powers.
As far as an implementation, it's pretty common to either:
create a common base with the interface you want
or create a protocol which both classes adopt:
MONProtocol.h
#protocol MONManagedShapeProtocol < NSObject >
- (void)prepareFromDictionary:(NSDictionary *)pDictionary;
#end
then (since you know it is one of the two types, MBManagedSquare or MBManagedCircle) either derive from the base or adopt the protocol and declare your variable like:
// if subclass
MBManagedShape * castedObject = (MBManagedShape*)object;
or
// if protocol
NSManagedObject<MONManagedShapeProtocol>* castedObject =
(NSManagedObject <MONManagedShapeProtocol>*)object;
no need for a cast there. the object can be either or and the function is only there once.
checking if it is there is good: respondsToSelecctor:#selector(prepareFromDictionary:)

Objective-C subclass and base class casting

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
}

Objective C : Given a Class id, can I check if this class implements a certain protocol? Or has a certain selector?

I want to use this for an object factory: Given a string, create a Class, and if
this Class supports a protocol (with a Create() method) then alloc the class and call
Create.
NSString *className; //assume this exists
Class class = NSClassFromString(className);
if ([class conformsToProtocol:#protocol(SomeProtocol)]) {
id instance = [[class alloc] init];
[instance create];
}
Class klass = NSClassFromString(classname);
if ([klass instancesRespondToSelector:#selector(create)]) {
[[klass alloc] create];
}
May I, however, point out just how many awful Objective-C rules you're breaking by doing the above? For example, you should never be calling methods on an allocated-but-not-initialized instance. The Xcode Static Analyzer will give you all sorts of warnings about memory leaks.
A better option would be this:
[[[klass alloc] init] create];
But you seem to imply that you don't want to call init.
You could consider a class method: [klass create], which would return a non-owned instance of klass. Then you'd just check [klass respondsToSelector:#selector(create)] before calling it.

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