how to create a instance of objective-c class by name? - ios

I want get something like:
#define weaken(object) ...
----
ClassABCD * abcd = [ClassABCD new];
weaken(abcd);
weakAbcd.tag = 0;
----
I have some code below:
#define weaken(x) __weak typeof(x) weak##x = x
But it only can use "weakabcd", and not "weakAbcd". Any idea why, and how to fix it?

Based on the question title, NSClassFromString() will do it. Based on the code you offer, I'm not so sure the two match.
This is a line from one of my projects where I am creating a UITableViewCell subclass instance based on the string variable prefCellClassName that I define elsewhere. (For completeness, prefCellStyle and prefCellReuseIdentifier are also variables that I define elsewhere.)
cell = [[NSClassFromString(prefCellClassName) alloc] initWithStyle:prefCellStyle reuseIdentifier:prefCellReuseIdentifier];

To make a class from a string:
id someObject = NSClassFromString("MyClassName");
When you do something like this, it's always good to check if your object responds to the method you're trying to pass it like so:
if ([someObject respondsToSelector:#selector(someMethod)])
{
[someObject someMethod];
}
It's features like this that make objective-c the dynamic language that it is

Related

Calling a instance method while using childNodeWithName

Is it possible to call an instance method without using an instance variable or #property?
Here is how I create an instance of a class. Within the method, I try to call on the class's instance movement method to force the instance to move:
-(void)createCharacterNPC
{
int randomness = [self getRandomNumberBetweenMin:1 andMax:20];
for (int i = 0; i < randomness; i += 1)
{
NSString *npcName = [NSString stringWithFormat:#"anNPC%i", randomness];
NPCclass *NPC = [[NPCclass alloc] initWithName:npcName];
NPC.position = CGPointMake(self.size.width/2, self.size.height/2);
NPC.zPosition = 1.0;
[_worldNode addChild:NPC];
// THIS OBVIOUSLY WORKS. But I can't use this technique outside this method.
[NPC beginMovement];
// THIS IS WHAT I WANT, BUT XCODE DOESN'T ALLOW ME TO WRITE CODE THIS WAY.
[[_worldNode childNodeWithName:#"anNPC1"] beginMovement];
}
}
Is there a way to allow [[_worldNode childNodeWithName:#"anNPC1"] beginMovement]; to work? Or some way similar to this so I wouldn't have to have create an instance variable of NPC (like so: _NPC)?
I'm asking because all of this is happening inside a mini-game scene and NPCclass will be initialized a random number amount of times (with arc4random() method). NPCclass moves on its own using vector (physics in a platformer) movement but I need to initialize its movement method right after creation then I need to periodically access each individually created instance of NPCclass using its name in other methods of the scene. Since I don't know how many NPCclass instances will be created each time the mini-game is played, I CAN'T use IVAR's or something like #property NPCclass *anNPC;
Please help.
Xcode complains about
[[_worldNode childNodeWithName:#"anNPC1"] beginMovement];
because the method -childNodeWithName returns an SKNode object. Instances of the SKNode class do not respond to the selector -beginMovement (or as Xcode puts it, no visible #interface declares the selector -beginMovement). Xcode shows this to you to force you to make sure you wrote what you wanted to write. Since you are sure, you can tell Xcode that the returned object is of the type NPCclass.
(NPCclass *)[_worldNode childNodeWithName:#"anNPC1"]
Now you can expand the statement to call -beginMovement.
[(NPCclass *)[_worldNode childNodeWithName:#"anNPC1"] beginMovement];
Note
There are a few concepts which you might be confusing. NPCclass is a class. +node is a class method of SKNode, which you can call with [NPCclass node];. -beginMovement is an instance method, called with:
NPCclass *npc = [NPCclass node];
[npc beginMovement];
Or:
[(NPCclass *)anyObject beginMovement];
// make sure anyObject responds to this selector though, or you app will crash.
Class methods are prefixed with a +, instance methods with -.
Class methods do not use an instance, just the class name.
As an example consider the
NSString` class method: `+ (id nullable)stringWithContentsOfFile:(NSString * nonnull)path
and a usage:
NSString *fileData = [NSString stringWithContentsOfFile:filePath];

Access int from Another Objective-C Class

I've trying to get an int from another class and it just says that the number is 0 (but it's not).
This is in Class2.h:
#interface Class2 : SKScene <SKPhysicsContactDelegate>{
Class1 * class;
}
This is in Class2.m:
NSLog(#"%i", class.thisint);
This is in Class1.h:
#property (assign, nonatomic) int thisint;
This is in Class1.m (in viewDidLoad):
thisint = 5;
The NSLog is being called well after the viewDidLoad method but it just keeps saying 0. How do I get this int from Class1? I don't know if the fact that Class2 is an SKScene affects this...
class is an existing method on NSObject and I would hope that you've just used that name for the purposes of this question - if not, please change the name of the variable, it will only lead to confusion.
What do you see if you log class:
NSLog(#"%#",class);
(Put that next to where your existing log is)
How and where are you assigning to the class variable?
A value of 0 probably means one of two things:
class is nil. This means you haven't assigned it anywhere. You don't magically get a value in a property just because you've declared one
class is a different instance to the one you think it is. This is a common beginner mistake, where you've done something like
class = [[Class2 alloc] init];
Which creates a new instance. You need to get a reference to the existing instance, which I can't tell you how to do without seeing more code.

Minimizing header import

I just thought up a simple scenario for myself to play around with NSClassFromString and the Objective-C runtime.
Background:
Imagine I had a class method in which I create an instance from a class based on some condition, let's say:
Class class;
id object;
if(classNumber == 1)
{
//create ClassA
class = NSClassFromString(#"classA");
object = [[class alloc] init];
}
else if(classNumber == 2)
{
//create ClassB
class = NSClassFromString(#"classB");
object = [[class alloc] init];
}
else if(classNumber == 3)
{
//create ClassC
class = NSClassFromString(#"classC");
object = [[class alloc] init];
}
else
{
object = nil;
}
return object;
For the runtime version, I would replace the above and use within the above if-elseif-else respectively:
Class class = objc_allocateClassPair([classA class], "mySubClass", 0);
...
Class class = objc_allocateClassPair([classB class], "mySubClass", 0);
...
Class class = objc_allocateClassPair([classC class], "mySubClass", 0);
...
Both versions go well. But I have to #import nonetheless all these classX.
And given this is a class method and will be called only once, I wonder if there exist some clever way of creating a class without having to import all these headers.
I understand that the answer will most likely be "No", because the compiler needs to know the class and its method signature at compile time.
But my experience is that whenever I say "Ok, I think I am pushing a bit here. Let's settle what seems to work now", someone would come along and would refute my satisfaction by saying "Hey, look here, there is a better/more clever way to do this. It's possible.". So I thought I would ask.
To sum up the question: is it possible to minimize #importing as much as possible, if we only needed to create a class from one of the files that need to be imported? Or is there some clever way where we could dynamically import a header file?
First off, I really don't see the point of this. For one thing, you still need to link the object files for all those classes into the final binary. So you can't minimize the binary size by using reflection and manipulating your includes.
So you can only try to optimize the number of include statements. Is it really worth it, just to remove a couple of lines of code? Of course, you could include the required headers into a new header file, and just include that one. That would cut down the number of includes to 1. Is it worth the trouble? I don't know.

How can I define a variable which could initialise from two different classes?

Sorry about the slightly vague question title but I found it very difficult to get it straight in my head myself.
The issue here is that I have two different dataSources I might be initialising and loading data from. Depending on the data changes which dataSource I need.
The problem I am having is how to define a variable of that dataSource when it could come from two different classes.
If I define them in my interface:
BColumnChartDataSource * chartDatasource = [[BColumnChartDataSource alloc] initWithExercise:_exercise];
BDoubleColumnChartDataSource * chartDatasource = [[BDoubleColumnChartDataSource alloc] initWithExercise:_exercise];
Then it obviously doesn't like them being called the same thing.
If I try to put them in an if statement then they aren't available outside the logic statement
if (_exercise.unitTypeLinks.count < 2) {
BColumnChartDataSource * chartDatasource = [[BColumnChartDataSource alloc] initWithExercise:_exercise];
}
else {
BDoubleColumnChartDataSource * chartDatasource = [[BDoubleColumnChartDataSource alloc] initWithExercise:_exercise];
}
Eventually I want to put them into a statement like this so I could put an if statement into every one of these but it is an awfully verbose way which might take more time if I add more dataSources.
// Get the exercise event list for our clicked exercise
_exerciseEventList = [chartDatasource getExerciseEventList];
I think I must be missing something obvious here so thank you for any help you can give
You should create create a base class, and inherit both of those classes with the base class.
#interface BDataSource : NSObject
#end
#interface BColumnChartDataSource : BDataSource
//your custom implementation here
#end
#interface BDoubleColumnChartDataSource : BDataSource
//your custom implementation here
#end
After that you can initialise your datasource like this
BDataSource *dataSource = nil;
if (_exercise.unitTypeLinks.count < 2) {
dataSource = [[BColumnChartDataSource alloc] initWithExercise:_exercise];
}
else {
dataSource = [[BDoubleColumnChartDataSource alloc] initWithExercise:_exercise];
}
The easiest way is to make sure that BColumnChartDataSource and BDoubleColumnChartDataSource have a common super class. For example, write a super class called BDataSource and make sure that both of the other class are subclass of this.
If that is too difficult to do, the easiest thing to do (which I don't recommend) is to make sure the property is id or NSObject, then do the type checking every time you access the property. This is definitely not ideal and you shouldn't do this. The right thing to do is the previous paragraph.
NSObject *chartDataSource;
if (_exercise.unitTypeLinks.count < 2) {
chartDataSource = [[BColumnChartDataSource alloc] initWithExercise:_exercise];
}
else {
chartDataSource = [[BDoubleColumnChartDataSource alloc] initWithExercise:_exercise];
}
//Now do something with chartDataSource
that handles the exact example you describe, although it leaves quite a bit to be desired as downstream consumers of chartDataSource will probably have to do their own conditioning upon the result of ([chartDataSource isKindOfClass:[BColumnChartDataSource class]])
Better patterns are likely to be found somewhere in the idea of "inheritance", depending on what the actual differences of your two dataSource classes actually are.

Is it possible to declare a variable type from string?

I don't know the technical term for this.I am wondering in Objective C, if it is possible to declare a variable like this:
NSClassFromString(aClassName) *var;
or
[NSClassFromString(aClassName) class] *var;
Apparently, the above two are not correct. What I want is to dynamically declare a variable.
Thanks.
You should make the ivar of id type, and then make it dynamically typed. For example, if you want to dynamically type it NSString, you can do like this :
id ivar;
Class myClass = NSClassFromString(#"NSString");
ivar = [[myClass alloc] initWithString:#"abc"];
You'll have to declare var as an id, and then instantiate it like:
var = [[NSClassFromString(aClassName) alloc] init];
The only point of declaring a type is compile-time type-checking, so there shouldn't be a problem as long as you only throw messages at the object that it can handle.
id is they type of a dynamically typed variable. If you need to check to see if a variable conforms to a particular class you can use -isKindOfClass:.
You can dynamically test if a class is some type with [var isKinkdOfClass:NSClassFromString(aClassName)]

Resources