I'm trying to learn how to make a class and object and how to call methods in Objective-C. My small program creates an object of the City class, allows to name that object, set an age, population, and get these values to print. But when I call a method to set these values, I get a (null) and zeros in result. Here's my code:
City.h
#import <Foundation/Foundation.h>
#interface City : NSObject
-(void) setName:(NSString *)theName Age:(int)theAge Population:(int)thePopulation;
-(void) getName;
-(void) getAge;
-(void) getPopulation;
-(void) nextDay;
#end
City.m
#import "City.h"
#implementation City
{
NSString *name;
int age;
int population;
}
-(void) setName:(NSString *)theName Age:(int)theAge Population:(int)thePopulation
{
theName = name;
theAge = age;
thePopulation = population;
}
-(void) getName
{
NSLog(#"Name is %#", name);
}
-(void) getAge
{
NSLog(#"Age is %d", age);
}
-(void) getPopulation
{
NSLog(#"Population today is %d", population);
}
main.m
int main()
{
City *moscow = [[City alloc] init];
[moscow setName:#"Msk" Age:120 Population:1000];
[moscow getName];
[moscow getAge];
[moscow getPopulation];
}
the result of the running is:
Name is (null)
Age is 0
Population today is 0
Program ended with exit code: 0
what am I doing wrong?
The problem is that the instance variables of City is never set. The code in setName:Age:Population: assigns the values of the instance variables (name, age, and population) to the arguments variables (theName, theAge, and thePopulation). Swapping these will cause the setter to assign the arguments to the instance variables:
name = theName;
age = theAge;
population = thePopulation;
That said, it's more idiomatic Objective-C to use properties—instead of instance variables and manual getters and setters—and to use an initializer to set the initial values. With those changes the City class would look something like this:
City.h
NS_ASSUME_NONNULL_BEGIN
#interface City : NSObject
#property (copy) NSString *name;
#property (assign) NSInteger age;
#property (assign) NSInteger population;
- (instancetype)initWithName:(NSString *)name
age:(NSInteger)age
population:(NSInteger)population;
#end
NS_ASSUME_NONNULL_END
City.m
#import "City.h"
#implementation City
- (instancetype)initWithName:(NSString *)name
age:(NSInteger)age
population:(NSInteger)population
{
self = [super init];
if (self) {
_name = [name copy];
_age = age;
_population = population;
}
return self;
}
#end
Two things to note about this code:
The string is copied—both in the initializer and in the property—to protect against having a NSMutableString is passed and later be mutated (which would mutate the value of name as well. For the common case where an immutable NSString is passed, the copy is equivalent to a "retain".
The synthesized instance variables are used when assigning values in the initializer. This is to protect against having a subclass override any of these properties and have a custom setter method run before the object is fully initialized (have all its variables set to their initial values). This only applies to initializers, custom setters, and dealloc. Everything else should use the properties to access and modify these values.
Related
I would like to a create convenience initiator by cloning an existing object with a mutation, while keeping the original object intact.
For example:
Given a Person object person1 with a name (Tom) and age (10)
I would like to clone the person1 object, but with 0 age.
I have following code in Obj-C, but not sure if there's a better way to do it:
Person.h
#interface Person : NSObject
#property (nonatomic, readonly) NSString *name;
#property (nonatomic, readonly) NSUInteger age;
- (instancetype)initWithName:(NSString *)name age:(NSUInteger)age;
- (instancetype)cloneWithZeroAge;
#end
Person.m
#implementation Person
- (instancetype)initWithName:(NSString *)name age:(NSUInteger)age
{
if (self = [super init]) {
_name = name;
_age = age;
}
return self;
}
- (instancetype)cloneWithZeroAge
{
if (self) {
// mutate age to 0
return [self initWithName:_name age:0];
}
return self;
}
#end
Let's start with the constructor. For you specific scenario it doesn't make much difference, but in order to be functionally independent, you better ensure that the name gets a copy of the data passed. You also want to specify this part as memory storage modifier of the property, so the contract is apparent to the client code:
#interface Person : NSObject
#property (copy, nonatomic, readonly) NSString *name;
...
#end
#implementation Person
- (instancetype)initWithName:(NSString *)name age:(NSUInteger)age
{
if (self = [super init]) {
_name = [name copy];
_age = age;
}
return self;
}
Now for the actual "copy" method. In order to be more consistent with existing NSCopying protocol and Objective-C naming convention, and, which is more important, the memory management ownership convention, you should start the method name with the word "copy", so the calling side knows it's responsible for releasing the object. The most important part now, is that if you want to keep the original object untouched, you have to allocate and create a new object. In your implementation, however, you just change the self into the new object entirely. Here is how I would implement such a method:
- (instancetype)copyWithZeroAge {
Person *copy = [[Person alloc] initWithName:_name age:0];
return copy;
}
If you prefer to keep the name property memory modifier strong instead of copy, don't forget to copy the instance:
- (instancetype)copyWithZeroAge {
Person *copy = [[Person alloc] initWithName:[_name copy]
age:0];
return copy;
}
I'm try to grok properties declared as both copy and readonly in objective-c, and specifically, whether I have to do the copy myself. In my init methods. Evidence suggests I do:
#interface A : NSObject
#property(nonatomic, copy, readonly) NSData *test;
- (instancetype)initWithData:(NSData *)data;
#end
#implementation A
- (instancetype)initWithData:(NSData *)data {
if ((self = [super init]) != nil) {
_test = data;
}
return self;
}
#end
int main (void) {
NSData *d1 = [NSMutableData dataWithBytes:"1234" length:5];
A *a = [[A alloc] initWithData:d1];
NSLog(#"%lx", (unsigned long)d1);
NSLog(#"%lx", (unsigned long)a.test);
return 0;
}
I had thought I could do self.test = data in my init method, but that is not permitted because it's readonly (not unexpectedly). Of course, self.test = [data copy] ensures two different objects.
So: Is there a way to create a readonly property in objective-c that copies the incoming value, or is it sufficiently an edge case that the combination is pointless and I have to do any copying myself manually anyway?
A #property declaration is merely shorthand for some accessor/mutator method declarations, and (in some cases) synthesized implementations for said accessor/mutator methods.
In your case, the #property(nonatomic, copy, readonly) NSData *test declaration expands to this equivalent code:
#interface A : NSObject
{
NSData* _test;
}
- (NSData*)test;
#end
#implementation A
- (NSData*)test
{
return _test;
}
#end
There is no setTest: mutator method because the property is declared as readonly, so the copy attribute has no effect.
You can implement your own mutator method:
- (void)setTest:(NSData*)newValue
{
_test = [newValue copy];
}
Or, you can have the compiler synthesize a mutator method for you by declaring a read/write property in a private class extension in your implementation file:
// A.m:
#interface A()
#property (nonatomic, copy) NSData* test;
#end
Both cases would allow you to use the test mutator method to copy a value to the _test instance variable:
- (instancetype)initWithData:(NSData *)data {
if ((self = [super init]) != nil) {
self.test = data;
}
return self;
}
The end result is:
#interface A : NSObject
#property(nonatomic, copy, readonly) NSData* test;
- (instancetype)initWithData:(NSData*)data;
#end
#interface A()
#property (nonatomic, copy) NSData* test;
#end
#implementation A
- (instancetype)initWithData:(NSData*)data {
if ((self = [super init]) != nil) {
self.test = data;
}
return self;
}
#end
In addition to what Darren said, the copy attribute describes what semantics the properties setter has. In your initializer, you're not using the setter, you're directly assigning to the instance variable.
It's maybe a bit hard to grok, but the instance variable is not the same thing as the property. It is used to implement the property in this case. But, assigning to the instance variable is not the same as setting the property.
If you want your initializer to also have the semantics that it copies the passed-in data, that's a separate design decision (although a good idea to go with the property's semantics). You could implement that by using a private setter as Darren suggests, but you could also just do:
_test = [data copy];
in the initializer.
I'm new in Objective C and stuck on this problem already 5 days)) What i have to do is write implementation for simple task about city and metropolis. I have class City with properties and class metropolis that has an global array which adds city object through createCity method. I have implemented this task but this arrays returns nothing.
Can anybody help me?
Here is part of the task:
1. Write a “City” class, which inherits from NSObject. Your class should contain the following:
Variables:
name, age, population.
Instance methods:
setName:age:population (single method) which set city’s name, age and population. getName, getAge, getPopulation which return city’s name, age and population, respectfully.
nextDay which adds a random number to city’s population, then subtracts a random number from city’s population. Figure out a way to generate random numbers yourself.
2. Create an instance of City class, set its name, age and population as you want.
3. Write a for-‐loop (if in doubt how to do it – google or use Xcode’s help system) for 10 steps. Each step send ‘nextDay’ message to your object and print out the population.
4. Write a “Metropolis” class. It should contain the following:
Variable:
array of 10 cities.
Instance method:
createCity:atIndex:withPopulation: (single method) which creates a city with first parameter being a name at index (from the second parameter) and sets its population to that of third parameter. So, you should be able to do this:
[myMetropolis createCity: #”Almaty” atIndex: 2 withPopulation: 1500000]
5. Create an instance of Metropolis class and create all 10 cities.
Here is my implementation:
City.h
#import <Foundation/Foundation.h>
#interface City : NSObject
{
NSString* name;
int age;
int population;
}
-(void)setName: (NSString*)n age: (int)a population: (int)p;
-(NSString*)getName;
-(int)getAge;
-(int)getPopulation;
-(void)nextDay;
#end
City.m
#import "City.h"
#implementation City
-(void)setName:(NSString*)n age:(int)a population:(int)p
{
name = n;
age = a;
population = p;
}
-(NSString*)getName
{
return name;
}
-(int)getAge
{
return age;
}
-(int)getPopulation
{
return population;
}
-(void)nextDay
{
int r = arc4random() % 100;
int r2 = arc4random() % 100;
population = population + r;
population = population - r2;
}
#end
Metropolis.h
#import <Foundation/Foundation.h>
#import "City.h"
#interface Metropolis : NSObject{
NSMutableArray* myArray;
}
-(void)createCity: (NSString*)n atIndex: (int)a withPopulation: (int)p;
-(NSMutableArray*) getArray;
#end
Metropolis.m
#import "Metropolis.h"
#import "City.h"
#implementation Metropolis
NSMutableArray* myArray = nil;
- (void)initialize {
myArray = [[NSMutableArray alloc]initWithCapacity:10];
}
-(void)createCity:(NSString*)n atIndex:(int)a withPopulation:(int)p
{
City* newCity = [[City alloc]init];
[newCity setName:n age:0 population:p];
[myArray insertObject:newCity atIndex:a];
}
-(NSMutableArray*)getArray
{
return myArray;
}
#end
main.m
#import <Foundation/Foundation.h>
#import "City.h"
#import "Metropolis.h"
int main(int argc, const char * argv[])
{
#autoreleasepool {
Metropolis* myMetropolis = [[Metropolis alloc]init];
[myMetropolis createCity:#"Aktobe" atIndex:0 withPopulation:15];
[Metropolis initialize];
NSMutableArray* c = [[NSMutableArray alloc]init];
c = [myMetropolis getArray];
NSLog(#"%#", [[c objectAtIndex:0] getName]);
}
return 0;
}
The method for initialization is -(void)init; this method should be overwritten in your implementation of Metropolis.
You are calling - (void)initialize; which is wrong in this case.
So, simply change - (void)initialize { to -(void)init { in your implementation of Metropolis and delete the line: [Metropolis initialize]; in main.
After the comment below the proper init method should be:
-(id) init {
self = [super init];
if (self) {
myArray = [[NSMutableArray alloc]initWithCapacity:10];
}
return self;
}
I've rewritten my answer to make it more complete, and to incorporate some of the other ideas generated in the other answers, especially #Hannes Sverrisson
The easy way to fix your issue is to call initialize BEFORE createCity (otherwise your trying to add objects to a nil array) and to also make sure you're not calling initialize from a static context. i.e. change [Metropolis initialize]; to [myMetropolis initialize];
The better way, and by better I mean more consistent with typical objective-c design, you should override the instance method init. This is done in the Metropolis implementation and replaces your initialize method.
-(id) init {
self = [super init];
if (self) {
myArray = [[NSMutableArray alloc]initWithCapacity:10];
}
return self;
}
or to make it more fun, create a new init method that takes the number of cities as a parameter.
-(id) initWithNumberOfCities:(NSInteger)numCities {
self = [super init];
if (self) {
myArray = [[NSMutableArray alloc]initWithCapacity:numCities];
}
return self;
}
Then in your main method, remove the call to [Metropolis initialize]. The reason for this is when you say:
Metropolis* myMetropolis = [[Metropolis alloc]init];
or
Metropolis* myMetropolis = [[Metropolis alloc]initWithNumberOfCities:10];
the init method is being called inline after the allocation takes place.
You don't need to write getters or create backing instance variables. You can use Objective-C 2.0's #property syntax.
#property (strong) NSString *name;
#property (assign) NSInteger age;
#property (assign) NSInteger population;
- (void)setName:(NSString*)name age:(NSInteger)age population:(NSInteger)population;
- (void)nextDay;
Then you access the properties using self.name, self.age, self.population or if you need to access the backing variable itself, _name, _age, _population.
just wanted to ask where I define initial class properties?
From other languages I am used to define some standard properties in the head before the content of the class starts.
For example paths to files. Settings and so on.
Where I fill these initial properties with values in Objective-C?
Thanks
Generally it's something like:
MyClass.h:
extern NSString * const staticValue1;
extern NSString * const staticValue2;
#interface MyClass : NSObject
{
NSString *_strval;
int _intval;
float _fltval;
}
#property (retain, nonatomic, readwrite) NSString *strval;
#property (assign, nonatomic, readwrite) int intval;
#property (assign, nonatomic, readwrite) float fltval;
#end
MyClass.m:
NSString * const staticValue1 = #"Something";
NSString * const staticValue2 = #"Something else";
#interface MyClass
#synthesize strval = _strval;
#synthesize intval = _intval;
#synthesize fltval = _fltval;
- (id)init
{
self = [super init];
if (self != nil)
{
[self setStrval:[NSString stringWithFormat:#"This is a %#", #"string"]];
[self setIntval:10];
[self setFltval:123.45f];
}
return self;
}
- (void)dealloc
{
[self setStrval:nil];
[super dealloc];
}
#end
This demonstrates the use of synthesized properties which are being used here to manage the memory of the instance variable _strval, which requires retaining/releasing to avoid memory leaks. Note that [self setStrval] is initialised with an autoreleased object (from [NSString stringWithFormat) and will be retained by the setter method. Alternatively these methods can be called using the following syntax, if you prefer:
self.strval = [NSString stringWithFormat:#"This is a %#", #"string"];
self.intval = 10;
self.fltval = 123.45f;
Maybe some of what you are after can be implemented with class methods.
Class methods are coded with a + (instead of the instance methods' -), and can't refer to instance variables, as they are not associated with any specific instance of the class.
This is a class method to return a default string:
+ (NSString *)myDefaultString
{
return #"Some default value";
}
You call it by simply calling it with the class name at the receiver's place. Imagine you have defined the method in a class called MyClass, the you call it like this:
NSString *str = [MyClass myDefaultString];
You'll notice that there is no alloc/init calls in this.
Public property needs to be define in .h file.
#interface MyClass {
}
#property(nonatomic, reatin) NSString *a;//Define as per needs, then synthesise in .m file
#end
For private property you need define inline category in .m file-
#interface MyClass ()
#property(nonatomic, reatin) NSString *b;//Define as per needs, then synthesise in .m file
#end
#implementation MyClass
#synthesize a = _a;
#synthesize b = _b;
- (void)viewDidLoad {
//You can initialise property here or in init method
self.a = #"Demo1";
self.b = #"Demo2";
}
//Now you can have other code for this class.
#end
Given the following definition of a class with retain properties:
#interface FeedEntry : NSObject<NSCoding>
{
NSURL* url;
NSData* source;
}
#property (retain) NSURL* url;
#property (retain) NSData* source;
#end
#implementation FeedEntry
#synthesize url;
#synthesize source;
-(void)encodeWithCoder:(NSCoder*)coder
{
[coder encodeObject:url forKey:#"url"];
[coder encodeObject:source forKey:#"source"];
}
Why does the url property in initWithCoder method need the "retain":
-(id)initWithCoder:(NSCoder*)coder
{
url = [[coder decodeObjectForKey:#"url"] retain];
source = [coder decodeObjectForKey:#"source"];
NSLog(#"got url=%#\n", url);
return self;
}
Specifically, why doesn't the synthesized "get url" method retain the object? (I'm guessing the source property will need a retain as well).
Quick answer:
When you set:
url = [[coder decodeObjectForKey:#"url"] retain];
you are not using the #property. You are manually setting the value of the instance variable url. You must, therefore, also manually retain the value.
To set the variable using the synthesized properties, you would instead call:
[self setUrl:[coder decodeObjectForKey:#"url"]];
or
self.url = [coder decodeObjectForKey:#"url"];
Either of these forms would make use of the synthesized methods, and handle the retain automatically.
Details:
In Objective-C, the #property and #synthesize keywords automatically create the getter and setter methods for you:
#interface MyClass
{
id someValue;
}
#property (retain) id someValue;
#end
#implementation MyClass
#synthesize someValue;
#end
Is equivalent to:
#interface MyClass
{
id someValue;
}
- (id)someValue;
- (void)setSomeValue:(id)newValue;
#end
#implementation MyClass
- (id)someValue { return someValue; }
- (void)setSomeValue:(id)newValue
{
[newValue retain];
[someValue release];
someValue = newValue;
}
#end
This creates an important distinction between the "internal" member variable and the property having the same name. If you reference the member variable by name, you are bypassing the synthesized property methods.