Class Method Exposure and Property Definition - objective-c

I have an academic question about Class Method exposure. There is something that I obviously don't understand about this and would like some clarification from those in the know.
Background:
I have a simple example of two classes named ViewController and ClassB. Class B contains an array with a method named returnArray. The ViewController accesses the array's data. I have exposed the returnArray method in the ClassB.h file.
Question:
Why is it that I can access the array's data in ViewController without having to define a property? I thought that the property would create a getter to allow access to the array. My example (only exposing the method) allows me to access the data without the creation of the #property.
Class Method:
ClassB.h
#interface ClassB : UIViewController
+(NSArray *) returnArray;
//#property (nonatomic, strong) NSArray *returnArray;
ClassB.m
#implementation ClassB
+(NSArray *) returnArray
{
NSArray *locationArray = #[#"Place1", #"Place2"];
return locationArray;
}
ViewController.m
- (void)viewDidLoad
{
NSArray *location = [ClassB returnArray];
NSLog (#"The count of the location is %d", [location count]);
NSLog (#"The second item in testArray is %#", location[1]);
}
Instance method: After reviewing answers
ClassB.h
*/
{
#private
NSArray *returnArray;
}
- (void)setReturnArray:(NSArray*)returnArray;
-(NSArray *) returnArray;
*/
#property (nonatomic, strong) NSArray *returnArray;
#end
ClassB.m - no change
ViewController.h - no change
ViewController.m
- (void)viewDidLoad
{
[super viewDidLoad];
//Create instance of ClassB
ClassB *classB = [ClassB new];
//Access the instance of returnArray
NSArray *location = [classB returnArray];
NSLog (#"The count of the location is %d", [location count]);
NSLog (#"The second item in testArray is %#", location[1]);
}

#property is a shorthand notation for creating an instance variable and associated accessor methods (with defined access / modification criteria).
What you have is a class method, which internally creates an array and returns it.
That's why you call [ClassB returnArray]; instead of [instanceOfB array];.
These are completely different things. If you wanted to use a property then you would need to create an instance of ClassB and then access the property. This would work, assuming that the array was created when the instance of ClassB was created.

Wain's answer addresses the difference between #property and Class methods, so it's worth a read. My answer assumes you know the difference between class and instance methods, and focuses on the difference between creating a #property versus creating an instance variable with an associate setter and getter.
The reason is because returnArray is a public method that returns an NSArray object on your ClassB.
A #property is merely a convenient way of creating three things at the same time: an instance variable, a setter, and a getter. It has the added bonus of allowing dot-syntax.
But at the end of the day, dot-syntax aside, all you're doing by declaring a #property is equivalently equal to this:
#interface ClassB : NSObject {
#private
NSArray *returnArray;
}
- (void)setReturnArray:(NSArray*)returnArray;
- (NSArray*)returnArray;
This is the same as this:
#property NSArray *returnArray;
Except of course, the dot syntax.
When you do:
NSArray *myArray = classB.returnArray;
You're not actually directly accessing the array you created when you declared the #property.
What you're doing is calling the getter method that was automatically generated when you declared the #property.

Related

I do not understand ways of declaring instance variable in the code

I do not quite understand the way of declaring instance variable and property. Can someone explain in detail the difference of the two codes below? In the second method, if I use _name for instance variable, is it the same function as the way declaring name in first code? Thanks!
First Code:
// OrderItem.h
#import <Foundation/Foundation.h>
#interface OrderItem : NSObject
{
#public NSString *name;
}
-(id) initWithItemName: (NSString *) itemName;
#end
// OrderItem.m
#import "OrderItem.h"
#implementation OrderItem
-(id) initWithItemName: (NSString *) itemName {
self = [super init];
if (self) {
name = itemName;
NSLog(#"Initializing OrderItem");
}
return self;
}
#end
Second Code:
// OrderItem.h
#import <Foundation/Foundation.h>
#interface OrderItem : NSObject
#property (strong,nonatomic) NSString *name;
-(id) initWithItemName: (NSString *) itemName;
#end
// OrderItem.m
#import "OrderItem.h"
#implementation OrderItem
-(id) initWithItemName: (NSString *) itemName {
self = [super init];
if (self) {
_name = itemName;
NSLog(#"Initializing OrderItem");
}
return self;
}
#end
In the first case you have declared an instance variable (usually called an ivar in Objective-C).
In the second case you have declared a property. A property is a set of two methods, a getter and a setter, usually accessed using dot notation, e.g. self.name. However, an ivar is automatically synthesized for the property with the name _name. That instance variable is what you are accessing in your init.
You can actually change the name of the ivar using #synthesize name = _myName or not have it at all (if you declare the getter and setter manually, no ivar will be synthesized).
Objective-C properties are a rather complicated topic so don't worry if you don't understand it immediately.
Properties are public which means that other classes can read and write them (even classes that aren't subclasses of the class that declares the property). In addition to that, properties provide a getter and a setter method (mutator methods). The getter of a property gets called every time you access the property
NSString *aName = self.name;
Whereas the setter is accessed every time you write or assign to a property.
self.name = #"Some name";
Instance variables (or ivars) are, by default, only visible for the class that declares it and its subclasses (also known as being encapsulated by their class). You can change this default behavior when you add the keyword #public to your ivar declaration though.

Variable losing value in delegation pattern

I'm trying to learn about delegation in Objective-C, and am having a minor issue with a variable losing it's data in the transfer process. I have Class1 that contains an NSMutableArray. The array gets populated, then I would like to transfer the array's values to Class2, and display it. Here is the relevant code in Class1:
//Class1.h
#class Class1;
// define the protocol for the delegate
#protocol Class1Delegate
#required
-(void)sayHello:(Class1 *)customClass withAntArray:(NSMutableArray *)antArray;
#end
#interface Class1 : MySuperClassName
#property (nonatomic, assign) id delegate;
-(void)helloDelegate;
#end
//Class1.m:
#interface Class1 ()
#property (nonatomic, strong) NSMutableArray *antOccurenceTimes;
#end
#implementation Class1
#synthesize antOccurenceTimes;
-(void)helloDelegate
{
// send the message to the delegate
[_delegate sayHello:self withAntArray:self.antOccurenceTimes];
}
Now, this is what I have in Class2:
#import "Class1.h"
#interface Class2 : UIView <Class1Delegate>
#end
// Class2.m:
- (void)appropriateTimeToCallMethod {
Class1 *initAntMarks = [[Class1 alloc] init];
initAntMarks.delegate = self;
[initAntMarks helloDelegate];
}
-(void)sayHello:(Class1 *)customClass withAntArray:(NSMutableArray *)antArray {
NSLog(#"Hello! %#", antArray.description);
}
The antArray.description reads as "NULL". Now, I figured that obviously it will be null, because I just created an entirely new instance of the class right before calling upon the needed method. I feel like I may have something mixed up, and being so new to delegation, I'm not sure exactly what. Does anyone know what I need to tweak to utilize Delegation?
I forgot to add that I did initialize it in Class1, and it gets populated just fine. It's only in class2 that this is occurring.
I initalize antOccuranceTimes in a separate method in ClassA in the snippet below, and the NSLog fires twice...
NSLog(#"Array initalized in class A");
antOccurenceTimes = [NSMutableArray new];
Change this line:
#property (nonatomic, assign) id delegate;
to:
#property (nonatomic, weak) id <Class1Delegate> delegate;
assign should only be used for C primitives, not Objective-c object references. You should also be checking if your object actually conforms to the delegate before messaging the delegate.
Edit:
I think you may be confused about the purpose of delegation.
Class1 *initAntMarks = [[Class1 alloc] init];
initAntMarks.delegate = self;
[initAntMarks helloDelegate];
Why are you calling a method on an object which in turn calls a delegate method when you could simply create a method that returns the NSMutableArray? The way you have your code currently set up requires that before the call to -helloDelegate you have to have filled the array with the appropriate objects. The purpose of delegation in MVC is to inform an object about an event that took place inside of another object. You are "delegating" the task off to another object, or you could say, that another object if responsible for the fulfillment of the task. Read the Apple Docs on Delegation. Delegation in your code is not the correct pattern to implement, as I stated you can simply return that array with a method call.
Edit 2:
There are two ways you can achieve this, through property methods or through an explicit method that returns your array. If you choose to use property methods, the property declaration must be in the public interface i.e. the .h file so that your class can all the accessors when the object is being implemented.
//Inside the .h
#property (nonatomic, strong) NSMutableArray *antOccurenceTimes;
This will automatically provide you with two accessor methods for the antOccurenceTimes property. These are the getter -antOccurenceTimes and setter -setAntOccurenceTimes: methods. Now after you initialize the class and fill your array you can call -antOccurenceTimes to return the array.
You can also create an explicit method that return the array:
- (NSMutableArray *)hello{
//Do something here
return _antOccurenceTimes;
}
You have not yet initialized the antOccurenceTimes. Of cause it is nil. There are many options depending on what you need. You can, for example, initialize it in a init function:
- (instancetype)init {
self = [super init];
if( self ) {
antOccurenceTimes = [NSMutableArray array];
[antOccurenceTimes addObject:#"Hello World"];
}
}
Or maybe initialize it before you call the delegate the function.
-(void)helloDelegate
{
// send the message to the delegate
self.antOccurenceTimes = [NSMutableArray array];
[self.antOccurenceTimes addObject:#"Hello World"];
[_delegate sayHello:self withAntArray:self.antOccurenceTimes];
}
I think you get my point.

Get pointer to array held in super class

Studying polymorphism in objective c . I'm trying to do some things using an NSMutableArray that is held by my super class, so I need a pointer to it from the sub class.
SuperViewController.h
#interface SuperViewController : UIViewController
- (NSMutableArray *) myArray;
#end
SuperViewController.m
#interface SuperViewController()
#property (strong, nonatomic) NSMutableArray *myArray;
#end
#implementation SuperViewController
- (NSMutableArray *) myArray {return self.myArray;}
#end
Then, in my subclass, I have this:
SubViewController.h
#import SuperViewController.h
#interface SubViewController : SuperViewController
- (void) updateUI;
#end
SubViewController.m
#implementation
- (void) updateUI
{
(NSMutableArray *) myArray = [super myArray];
}
#end
It crashes the App . I've tried quite a few different approaches, but I always wind up crashing. Can anyone help me out? Thanks!
The reason is, that you have a recursion which blows the stack:
- (NSMutableArray *) myArray
{
return self.myArray;
}
This method calls itself. The dot notation is a short form of method invocation:
- (NSMutableArray *) myArray
{
return [self myArray];
}
It is not an access to the object's context (ivar) as you probably expected:
- (NSMutableArray *) myArray
{
return self->_myArray;
}
The right way is to use the ivar directly:
- (NSMutableArray *) myArray
{
return _myArray;
}
BTW:
Properties can have the readonly attribute.
Using super in the derived class is useless and dangerous. It does not use the super's context (which does not exist, because every instance object has only one context built from the whole class hierarchy), but uses the super implementation of the method. You did not overwrite this implementation in the derived class. self would work, too, and gives you the ability to overwrite the getter.
You've written self.myArray to access an instance variable. The dot notation is a property access, and will invoke a property-like method even if not declared as a property. In your case this means your attempt to access the instance variable is in fact a self-recursive call, which will result in stack overflow.
To access an instance variable either use just it's name or the -> member access operator, e.g. _myArray or self->_myArray.

Is there a pattern to override a property?

The Objective-C runtime keeps a list of declared properties as meta-data with a Class object. The meta-data includes property name, type, and attributes. The runtime library also provides a couple of functions to retrieve these information. It means a declared property is more than a pair of accessor methods (getter/setter). My first question is: Why we (or the runtime) need the meta-data?
As is well known, a declared property cannot be overridden in subclasses (except readwrite vs. readonly). But I have a scenario that guarantees that needs:
#interface MyClass : MySuperClass <NSCopying, NSMutableCopying>
#property (nonatomic, copy, readonly) NSString *string;
- (id)initWithString:(NSString *)aString;
#end
#interface MyMutableClass : MyClass
#property (nonatomic, strong, readwrite) NSMutableString *string;
- (id)initWithString:(NSString *)aString;
#end
Of course, the compiler won't let the above code pass through. My solution is to substitute the declared property with a pair of accessor methods (with the readonly case, just the getter):
#interface MyClass : MySuperClass <NSCopying, NSMutableCopying> {
NSString *_string;
}
- (id)initWithString:(NSString *)aString;
- (NSString *)string;
#end
#implementation MyClass
- (id)initWithString:(NSString *)aString {
self = [super init...];
if (self) {
_string = [aString copy];
}
return self;
}
- (NSString *)string {
return _string;
}
- (id)copyWithZone:(NSZone *)zone {
return self;
}
- (id)mutableCopyWithZone:(NSZone *)zone {
return [[MyMutableClass alloc] initWithString:self.string];
}
#end
#interface MyMutableClass : MyClass
- (id)initWithString:(NSString *)aString;
- (NSMutableString *)string;
- (void)setString:(NSMutableString *)aMutableString;
- (void)didMutateString;
#end
#implementation MyMutableClass
- (id)initWithString:(NSString *)aString {
self = [super init...];
if (self) {
_string = [aString mutableCopy];
}
return self;
}
- (NSMutableString *)string {
return (NSMutableString *)_string;
}
- (void)setString:(NSMutableString *)aMutableString {
_string = aMutableString;
// Inform other parts that `string` has been changed (as a whole).
// ...
}
- (void)didMutateString {
// The content of `string` has been changed through the interface of
// NSMutableString, beneath the accessor method.
// ...
}
- (id)copyWithZone:(NSZone *)zone {
return [[MyClass alloc] initWithString:self.string];
}
#end
Property string needs to be mutable because it is modified incrementally and potentially frequently. I know the constraint that methods with the same selector should share the same return and parameter types. But I think the above solution is appropriate both semantically and technically. For the semantic aspect, a mutable object is a immutable object. For the technical aspect, the compiler encodes all objects as id's. My second question is: Does the above solution make sense? Or it's just odd?
I can also take a hybrid approach, as follows:
#interface MyClass : MySuperClass <NSCopying, NSMutableCopying> {
NSString *_string;
}
#property (nonatomic, copy, readonly) NSString *string;
- (id)initWithString:(NSString *)aString;
#end
#interface MyMutableClass: MyClass
- (id)initWithString:(NSString *)aString;
- (NSMutableString *)string;
- (void)setString:(NSMutableString *)aMutableString;
- (void)didMutateString;
#end
However, when I access the property using the dot syntax like myMutableObject.string, the compiler warns that the return type of the accessor method does not match the type of the declared property. It's OK to use the message form as [myMutableObject string]. That suggests another aspect where a declared property is more than a pair of accessor methods, that is, more static type checking, although it is undesirable here. My third question is: Is it common to use getter/setter pair instead of declared property when it is intended to be overridden in subclasses?
My take on this would be slightly different. In the case of the #interface of an Objective-C class, you are declaring the API that class uses with all classes that communicate with it. By replacing the NSString* copy property with an NSMutableString* strong property, you are creating a situation where unexpected side-effects are likely to occur.
In particular, an NSString* copy property is expected to return an immutable object, which would be safe for using in many situations that an NSMutableString* object would not be (keys in dictionaries, element names in an NSXMLElement). As such, you really don't want to replace these in this fashion.
If you need an underlying NSMutableString, I would suggest the following:
Add an NSMutableString* property in addition to the string property, and name it -mutableString
Override the -setString: method to create an NSMutableString and store it
Override the -string method to return an immutable copy of your mutable string
Carefully evaluate whether you can replace the internal ivar with an NSMutableString or not. This might be a problem if you don't have access to the original class and you aren't certain whether assumptions are made about the mutability of the string inside of the class
If you do this, you will maintain the current interface without disrupting existing users of the class, while extending the behavior to accommodate your new paradigm.
In the case of changing between a mutable object and an immutable one, you really need to be careful that you don't break the API contract for the object.

referencing an Mutable array from ClassA to Class B

could someone help me reference an array from a different class.
here is ClassA.h
#interface ClassA : UIViewController <MKMapViewDelegate> {
NSMutableArray *theArray;
}
#property (nonatomic, retain) NSMutableArray *theArray;
-(NSMutableArray *) pleaseReturnTheArray;
#end
ClassA.m
#import "ClassA.h"
#implementation ClassA
#synthesize theArray;
...
-(NSMutableArray *) pleaseReturnTheArray {
return theArray;
}
and here is ClassB.m (i want to reference ClassA array, i have #import "MapView.h" into this class, this is a different view that gets pushed onto the screen)
- (void)viewDidLoad {
[super viewDidLoad];
//first test
ClassA *firstTest = [[ClassA alloc] init];
NSMutableArray *firstTestArray = [firstTest.theArray mutableCopy];
NSLog(#"first test - %#", firstTestArray);
//second test
NSMutableArray *secondTestArray = [firstTest pleaseReturnTheArray];
NSLog(#"second Test - %#", secondTestArray);
...
the result in NSLog i get is ...
first test - (null)
second Test - (null)
I am sure i have used one of these methods before and it has worked fine. Is it something to do with the fact that ClassB is a different view pushed by a tab bar controller?
any help would be appreciated.
Chris
My initial guess here is that you are not initializing theArray to a real array in your init method of ClassA. By default it will be nil. So the mutableCopy will also be nil as is shown in your output. Or perhaps adding this line of code after allocating firstTest would help:
firstTest.theArray = [NSArray arrayWithObjects:#"A",#"B",nil];
Also the pleaseReturnTheArray method is redundant with the theArray getter. You might as well write the following line of code instead:
NSMutableArray *secondTestArray = firstTest.theArray;
Note that because you are taking a mutable copy for firstTestArray, firstTestArray and secondTestArray will be equal but they will not be the same arrays. That means that if you add something in firstTestArray it won't show up in secondTestArray and vice versa.