While I'm more than familiar with C#, I'm totally new at Objective C and iOS development. So I'm learning the language. What I don't understand is why the following code throws a compiler error (and yes, this is from the exercises at Programming with Objective C:
SNDPerson:
#interface SNDPerson : NSObject
#property NSString *first;
#property NSString *last;
+ (SNDPerson *)person;
#end
#implementation SNDPerson
+ (SNDPerson *)person
{
SNDPerson *retVal = [[self alloc] init];
retVal.first = #"Ari";
retVal.last = #"Roth";
return retVal;
}
#end
SNDShoutingPerson:
#import "SNDPerson.h"
#interface SNDShoutingPerson : SNDPerson
#end
#implementation SNDShoutingPerson
// Implementation is irrelevant here; all it does is override a method that prints a string
// in all caps. This works; I've tested it. However, if necessary I can provide more code.
// The goal here was for a concise repro.
#end
Main method:
- int main(int argc, const char * argv[])
{
SNDShoutingPerson *person = [[SNDShoutingPerson alloc] person]; // Error
...
}
The error is "No visible #interface for "SNDShoutingPerson" declares the selector "person".
Shouldn't this work? SNDShoutingPerson inherits from SNDPerson, so I would have assumed it got access to SNDPerson's class factory methods. Did I do something wrong here, or do I have to declare the method on SNDShoutingPerson's interface as well? The exercise text implies that what I did should Just Work.
Omit the +alloc when calling the class method:
SNDShoutingPerson *person = [SNDShoutingPerson person];
Briefly:
+ (id)foo denotes a class method. This takes the form:
[MONObject method];
- (id)foo denotes an instance method. This takes the form:
MONObject * object = ...; // << instance required
[object method];
Also, you can declare + (instancetype)person in this case, rather than + (SNDPerson *)person;.
change the line SNDShoutingPerson *person = [[SNDShoutingPerson alloc] person]; // Error
to
SNDShoutingPerson *person = [[SNDShoutingPerson alloc] init];
Cheers.
If you want to call class method:
SNDPerson person = [SNDPerson person];
person is a class method, but you're trying to call it with the incompletely constructed instance returned by alloc. Kill the alloc and just do [SNDShoutingPerson person].
This has nothing to do with subclasses, by the way. You would get the same error if you had written [[SNDPerson alloc] person].
Related
Is it possible at Objective C at init method to return an instance of different classes?
I'm having a Class called: MyCustomClass. I also have two other different classes called Class 1 and Class2. What I'm trying to implement is: When I call [[MyCustomClass alloc] initWithSomeParameters to create instance of Class1 or Class2 depending on some condition.
MyCustomClass.m:
#import "MyCustomClass.h"
#import "Class1.h"
#import "Class2.h"
-(id) initWithSomeParameters: (id) params{
id myClass;
if (someCondition){
myClass = [[Class1 alloc] initWithSomeParameters:(id) params];
[myClass setSomething:something];
}else{
myClass = [[Class2 alloc] initWithSomeParameters:(id) params];
[myClass setSomething:something];
}
return myClass;
}
...and later I call
id myCustomClass = [[MyCustomClass alloc] initWithSomeParameters:(id) params];
Is this a wrong approach? If so, what would be the correct one?
Several others have mentioned this, but the result of calling [[MyClass alloc] init] must always be nil or a kind of MyClass. It doesn't have to specifically be an instance of MyClass; one of its descendants is possible, as with NSArray or NSString. In code, this requirement would look like:
MyClass *a = [[MyClass alloc] init];
NSAssert((a==nil) || [a isKindOfClass:[MyClass class]], #"This must always hold true.");
I've never attempted to implement this, but it would probably have to look something like this:
- (id)initAsSubclass:(NSString *)sublcassName
{
Class c = NSClassFromString(subclassName);
self = [[c alloc] init];
if (self) {
// Do Custom Init Here
}
return self;
}
The keys would be:
DO NOT perform [super init].
Create a completely new object with +alloc.
Assign the newly created object to self.
If not using ARC, perform [self autorelease], before replacing the value. (If the object that is currently executing code becomes deallocated, it can cause issues. -autorelease will defer that until this section is complete.)
You should make some kind of controller, which initializes correct classes. You can also achieve same that using class methods.
ANd in genreal this given implementation is bad, because you alloc memory once [MyCustomClass alloc] and then in -(id)initWithSomeParameters:(id)params you are allocating memory again. So, even different address will be retruned, that isn't agains apple guidelines, some apple classes also have such behavior, but they do it because of optimizations. But here it is wrong.
Its not a good approach. Its better use some helper class or us factory pattern and provide parameters to method. Then depending on parameters create an object of class and return.
Its not good approach to create object of different class in init method of different class.
Edit:
if You want to show UIView or UIAlertView depending on iOS version do like this.
#interface AlertHelper : NSObject
+ (id)getAlert;
#end
///
#implementation AlertHelper
+(id)getAlert{
NSString *version = [[UIDevice currentDevice] systemVersion];
int ver = [version intValue];
if (ver < 7){
//For iOS 6
return something;
}
else{
//for ios 7
return something
}
}
#end
The way to do it is like this:
Create Base class like:
#import "Base.h"
#import "Class1.h"
#import "Class2.h"
#implementation Base
+ (id)classWithParams:(id)params
{
id retVal = nil;
if (condition_based_on_params_means_creating_class1)
{
retVal = [[Class1 alloc] initWithSomeParameters:params];
}
else
{
retVal = [[Class2 alloc] initWithSomeParameters:params]
}
return retVal;
}
#end
Class1 inherits from Base:
#interface Class1 : Base
{
}
- (id)initWithSomeParameters:(id)parameters;
#end
Class2 inherits from Base:
#interface Class2 : Base
{
}
- (id)initWithSomeParameters:(id)parameters;
#end
Ultimately you will have:
Base* a = [Base classWithParams:yourParams];
Closed. This question needs details or clarity. It is not currently accepting answers.
Want to improve this question? Add details and clarify the problem by editing this post.
Closed 9 years ago.
Improve this question
I'm unsure how I should initialise the various properties in an objective-C class. Please assume I'm a very new user to Objective-C in your answers...
I have the following classes:
Test class
#interface Test : NSObject
#property (nonatomic, strong) NSString *name;
#end
TestManager class
#interface TestManager : NSObject
#property (nonatomic, strong) NSMutableArray *tests; // array of Test objects (array size unknown until runtime)
#end
Controller class
#interface TestController : NSObject
#property (nonatomic, strong) TestManager *aManager;
-(void)initManager;
-(void)doSomething;
#end
I want to have an method like initManager called:
-(void)initManager
{
// how can I init the aManager which will have an array of Test objects
}
which will automatically allocate an array of objects to be stored inside the manager class so I can do things like:
-(void)doSomething
{
NSString *name = ((Test *)[self.aManager.tests objectAtIndex:0]).name;
}
I'm not even sure that initManager is the correct method to use - is there something built in that always gets called?
Firstly, let's look at the way we can initialize your Test class objects.
You can also write some initialization method for your Test class so instead of this:
Test example = [[Test alloc] init];
example.name = #"s";
you can write something like this:
Test example = [[Test alloc] initWithName:#"s"];
Please note that this is very common for initialization method to return newly created object, hence the initialization method usually returns 'id' type (not void).
This is the implementation for your test class which will be used in examples below.
.h file:
- (id)initWithName:(NSString *)aName;
.m file:
- (id)initWithName:(NSString *)aName
{
self = [super init];
if (self) {
_name = aName;
}
return self;
}
You can initialize your TestController class this way:
.h file:
- (id)initManager;
.m file:
- (id)initManager
{
self = [super init]; //always call the superclass init method when your class inherit from other class
if (self) { // checking if the superclass initialization was fine
_tests = [NSMutableArray array];
[_tests addObject:[[Test alloc] initWithName:#"s"]];
[_tests addObject:[[Test alloc] initWithName:#"l"]];
}
return self;
}
Or something like this:
- (id)initManager
{
self = [super init]; //always call the superclass init method when your class inherit from other class
if (self) { // checking if the superclass initialization was fine
_tests = [NSArray arrayWithObjects:[[Test alloc] initWithName:#"s"], [[Test alloc] initWithName:#"l"]];
}
return self;
}
Like the #Andrew said it is better to use alloc + init. Here are some examples of this syntax:
CGRect rect = CGRectMake(0, 0, 100, 100);
[[UIView alloc] initWithFrame:rect];
[[NSArray alloc] init]
This is the common way to initialize objects. Despite having this mechanism there are also some additional methods (which are in fact static functions) which give the programmer the nice way to initialize objects. Using them u don't have to write keyword 'alloc' so that the code is shorter and easier to read.
[NSArray array] //creates and returns empty array
[NSMutableArray array] //creates and return empty mutable array
[UIButton buttonWithType:UIButtonTypeContactAdd]; //creates and return button
first import header files of test, and test manager class, into controller class
#import Test.h
#import TestManager.h
then in controller class
-(void)initManager
{
TestManager *aTestManager = [TestManager new];
Test *test1 = [Test new];
Test *test2 = [Test new];
[aTestManager.tests addObject:test1];
[aTestManager.tests addObject:test2];
}
Let's start at the top. You probably can and should make the name readonly.
(Demos assume ARC is enabled)
#interface Test : NSObject
#property (nonatomic, readonly) NSString *name;
// and then simply initialize name:
- (instancetype)initWithName:(NSString *)pName;
#end
NSString properties should be copied:
#implementation Test
- (instancetype)initWithName:(NSString *)pName
{
self = [super init];
if (nil == self) return nil;
// copy the NSString:
// don't use getters/setters in initializers or -dealloc
_name = pName.copy;
return self;
}
#end
Similarly readonly:
#interface TestManager : NSObject
#property (nonatomic, strong, readonly) NSMutableArray *tests; // array of Test objects (array size unknown until runtime)
#end
#implementation TestManager
- (id)init
{
self = [super init];
if (nil == self) return nil;
// just initialize readonly tests:
_tests = NSMutableArray.new;
return self;
}
#end
Then TestController could probably use a readonly TestManager and borrow the form used above. Otherwise, it can be readwrite, if needed.
// don't declare/implement an instance method
// which has the prefix -init*, like initManager. renamed.
- (void)resetManager
{
// if readonly is ok, then just create it in the initializer.
// otherwise, if you need the ability to set the manager in the controller,
// then declare the property readwrite and:
self.testManager = TestManager.new;
// note: aManager is not a good name. renamed to testManager.
}
- (void)doSomething
{
assert(self.testManager && "did you forget to init the manager?");
Test * test = [self.testManager.tests objectAtIndex:0];
NSString * name = test.name;
...
}
This is far from covering all initialization cases in ObjC, but it is a start.
I've tried the following sample code:
#import "Foundation/Foundation.h"
#interface example
{
#public NSString* name;
}
#end
#implementation example #end
int main()
{
example* me;
me->name = #"World";
}
And it appears my code hates me at this point. I do understand how much of a bad idea it is to make a field public, but I'm not sure why I'm getting an error at that last line in main().
There is a lot wrong here
You class should subclass NSObject so it should be declared as
#interface example : NSObject
{
#public NSString* name;
}
#end
Next you actually need an instance of the class to get at it's values e.g.
example *me = [[example alloc] init];
NSLog(#"%#", me->name);
Next classes are named starting with an uppercase letter and normally have a prefix e.g. I would use
PSExample
Next please don't access instance variables like this, you should make your objects state available through accessors rather than give direct access.
You're not allocating or initializing your me variable. You probably want to inherit from NSObject and then use this:
example *me = [[example alloc] init]
At the very least you need to alloc it.
You need to initialize your variable before you can access it. Also you should derive your class from NSObject.
#import "Foundation/Foundation.h"
#interface example : NSObject
{
#public NSString* name;
}
#end
#implementation example #end
int main()
{
example* me = [[example alloc] init];
me->name = #"World";
}
Given the following class,
#interface MyBaseClass : NSObject
-(void)printName
+(NSString*)printableName
#end
how can I call the class method +printableName from within the instance method -printName without explicitly referring to MyBaseClass? [[self class] printableName] doesn't compile.
The idea is that subclasses will override +printableName so -printName should polymorphically invoke the appropriate +printableName for its class.
Declare MyBaseClass as
#interface MyBaseClass : NSObject
and your [[self class] name] should compile.
This compiles for me:
#interface MyBaseClass : NSObject
-(void)printName;
+(NSString*)printableName;
#end
#implementation MyBaseClass
-(void)printName
{
[[self class] printableName];
}
+(NSString*)printableName {
return #"hello";
}
#end
Have you tried [object_getClass(this) printableName]?
(But you realize, of course, that you could also just create a version of
-(NSString*)printableName2 {
return [MyBaseClass printableName];
}
in each of your classes and call [self printableName2]?)
From what you are describing, it should work. As you didn't offer your implementation, we won't be able to tell, what is wrong.
Based on this question, I wrote an example code for polymorphism in Objective-C.
It contains inheritance-based polymorphism, but also polymorphism based on formal and informal protocols. May you want check your code against it.
here an excerpt:
#import <Foundation/Foundation.h>
/*
* 1.: Polymorphism via subclassing
*/
#interface MyBaseClass : NSObject
-(void)printName;
+(NSString*)printableName;
#end
#implementation MyBaseClass
+(NSString *)printableName
{
return NSStringFromClass(self);
}
-(void)printName
{
NSLog(#"%#", [[self class] printableName]);
}
#end
#interface MySubBaseClass : MyBaseClass
#end
#implementation MySubBaseClass
#end
//...
int main (int argc, const char * argv[])
{
/*
* 1.: Polymorphism via subclassing. As seen in any Class-aware OO-language
*/
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
MyBaseClass *myBaseObject = [[MyBaseClass alloc] init];
[myBaseObject printName];
[myBaseObject release];
MySubBaseClass *mySubBaseObject = [[MySubBaseClass alloc] init];
[mySubBaseObject printName];
[mySubBaseObject release];
//...
[pool drain];
return 0;
}
Having some issues with code not executing within the classes I created and thought I initialized and implemented correctly here are all the files. There is a class with an array of another class. Then implemented in the code finally but for some reason none of the NSLog calls seem to execute except the one immediately before [mobdefs createTable] in the main code. All help appreciated...
// Mobdefs.h
#interface Mobdefs : NSObject {
#public NSMutableArray *mobInfo;
}
#property(retain) NSMutableArray *mobInfo;
-(void) createTable;
#end
// Mobdefs.m
#import "Mobdefs.h"
#import "Mobrec.h"
#implementation Mobdefs
#synthesize mobInfo;
- (id) init
{
mobInfo = [[NSMutableArray alloc] init];
return self;
}
-(void) addmobrec
{
MobRec *aNewMobRec = [[MobRec alloc] init];
aNewMobRec.mName=#"newbie";
[mobInfo addObject:aNewMobRec];
[aNewMobRec release];
NSLog(#"MobRec Added\n");
}
-(void) createTable
{
NSLog(#"Populating mob table.\n"); // *** THIS CODE NEVER SEEMS TO GET EXECUTED
}
#end
//main.h
Mobdefs *mobdef;
//main.m
NSLog(#"just before createTable call\n");
[mobdef createTable];
although the createTable code is called in the main the only NSLog output I get is the 'just before createtable...'
It doesn't seem that you have initialized mobdef. Add the following:
mobdef = [[Mobdefs alloc] init];
to your main.m before you invoke the method on it.
Objective-C silently ignore calls on nil, as mobdef would be initialized to initially.
are you allocating and initializing mobdef in main.m?