If you’ve been programming in one of the more popular object oriented programming languages such as C++ or Java, Objective-C may be a bit confusing at first. With Objective-C, you send objects messages opposed to calling methods. With C++ and Java, calling a method name means that, in most cases, it is bound to a section of code in the target class by the compiler. However, with Objective-C, the target of the message is resolved at runtime.
At the cost of speed and multiple inheritance, Objective-C is able to support dynamic binding by default. This means that messages can go unimplemented or be defined at runtime. However, implementation is still required for the method to be called in the derived object.
Creating a class is typically done using two files. The first being the header file (*.h). Within this file, is where you declare your class with its instance variables and methods/messages. To indicate that you are declaring a class, you use the @interface keyword to begin the class declaration and the @end keyword to end the declaration.
@interface Event : NSObject { // Instance variables @public NSString* public_var; @protected NSString* protected_var; @private NSString* private_var; } @property NSInteger* type; @property NSString* name; @property NSDate* startTime; @property NSDate* endTime; @property NSString* description; // Class methods + (id) newInstance; // Instance methods - (id) initWithName:(NSString *)name withType:(NSInteger *)type withStartTime:(NSDate *)start withEndTime:(NSDate *)end withDescription:(NSString *)description; @end
The second file that is typically used is a ‘.m’ which originally meant “messages.” The .m file is similar to a .c or .cpp file which where you implement your code.
#import "Event.h" @implementation Event { // Instance variables can also be declared here. } // The plus (+) indicates that this is a class message. + (id) newInstance { return [[Event alloc] init]; } - (id) initWithName:(NSString *)name withType:(NSInteger *)type withStartTime:(NSDate *)start withEndTime:(NSDate *)end withDescription:(NSString *)description { self = [super init]; if(self) { _name = name; _type = type; _startTime = start; _endTime = end; _description = description; } return self; } @end
In Objective-C there are two types of keywords, the standard C/C++ keywords, such as int, break, continue, etc. The second set starts with a ‘@‘ (at) symbol. The latter is used to differentiate Objective-C keywords from C/C++ keywords. It’s important to note that the ‘@‘ has multiple meanings based on the context it is used. This symbol is also used for starting strings:
NSString* str = @“Hello World!”
It is also used as literals:
NSArray* array = @[ obj1, obj2, obj3 ]; NSDictionary* dictionary = @{ key1 : obj1, key2 : obj2, key3 : obj3 }; NSNumber* number = @(2 + 2);
Instance Variables
Instance variables can be defined in either the @interface or @implementation as seen in the example code above. When declaring instance variables, the variables are private opposed to properties, which are public. However, you can change the scope by using @public, @private, or @protected.
@interface Event : NSObject { @public NSString* name; @protected NSDate* time; @private NSString* description; } // properties and messages @end @implementation Event { @public NSString* name; @protected NSDate* time; @private NSString* description; //message declarations @end
Properties
When declaring properties in an Objective-C class, you are declaring variable as public, giving external classes access to that property. It is possible to limit the access to read only by declaring the property as readonly. Additionally, properties can be declared as:
- atomic - By default all properties are atomic, which means they are locked when they are accessed or set. This is not the same as thread safety. This means that if a value is being set, the getter must wait for the value to fully be set before the value can be retrieved.
- nonatomic - Prevents the property from being locked.
- readonly - Declaring a property as readonly may be provided with storage semantics such as assign, copy, or retain.
- assign - Used to set the property’s pointer to the address of the object without retaining it. This is used with scalar types of data (I.e., int, float, char, etc.)
- copy - the property will maintain a copy of the original value. When specifying a property to keep a copy of the value, it must support NSCopying.
- retain - Default, system will manage the retain count on its own.
- getter = getterName: and setter = setterName: - By specifying a getterName and setterName name, you override the getter and setter messages generated when synthesizing properties. If you do not specify getter and setter names, the compiler automatically generates these for you. The getter and setter messages follow a specific naming convention:
- The getter message is the same property name. If you declare a property of name, you would use the getter as such: [object name];
- The setter used to set the property’s value pre-appends the word ‘set’ and capitalizes the first character of the property if it’s not already capitalized. If we have a property of firstName, the setter would be setFirstName. [object setFirstName:@“Paulus”];
- strong- By default properties are implicitly declared as having a strong relationship. Meaning that the property will remain in memory until the object is set to nil and it’s not owned by anything else.
- weak - Used if you don’t want control over the object’s life. The most frequently use cases are when there is a two way relationship between objects. Using a weak reference avoids the possibility of retain cycles.
When a property is declared, an instance variable is created and identified by the property’s name with a pre-appended underscore (_). In the above example, name and age all have an instance variable called _name and _age. However, We defined a different instance variable for address. Instead of _address, the instance variable is called instanceAddress.
@interface Person : NSObject @property(copy) NSString* name; @property(readonly) NSInteger* age; @property(setter = move:) NSString* address; // .. messages @end @implementation Person @synthesize name; @synthesize age; @synthesize address = instanceAddress; // .. message @end
Messages
The syntax for messages are a lot different than methods in C/C++, Java, and PHP. With methods and functions, the object needs to respond or have code to execute. Essentially, you’re jumping to a location in memory to execute code. However, Objective-C objects may choose to not respond or forward the message. This is what makes Objective-C a more dynamic language.
You define messages using labels, argument types, and argument names. If the message does not require parameters, then the message only needs one label is the name of the message:
+ (id) newInstance
Messages with a single parameter are pretty straight forward:
- (void) setName:(NSString *)name;
setName is the name of the message, and name is the parameter you are passing. When dealing with messages that take multiple arguments, the message, in my opinion, becomes difficult to read.
- (id) initWithName:(NSString *)name withType:(NSInteger *)type withStartTime:(NSDate *)start withEndTime:(NSDate *)end withDescription:(NSString *)description
Again, initWithName is the name of the message and also the label for the first parameter. (NSString *) is the type of parameter we’re going to be passing. withType, withStartTime, withEndTime, and withDescription are all labels for the parameters that immediately follow; start, end, and description, respectively.
Categories & Class Extensions
Categories allow you to add messages to an existing class, even if you do not have the original source. For example, if you wanted to add a helper message to the NSObject class, you can.
Categories are typically declared and implemented in two files; a header file (.h) and a messages file (.m). To differentiate the class and category files, a different naming convention is employed by categories. These files are named by taking the name of the class, category, and concatenating them with a plus sign (+):
MyClass+MyCategory.h
MyClass+MyCategory.m
NSObject+MyCategory.h
NSObject+MyCategory.m
To add an existing class to a category you would do the following:
#import "Event.h" @interface Event (ChildrensEvent) - (NSInteger) totalHeadCount; @end
Note the (ChildrensEvent) — This means that you are adding the event class to the ChildrensEvent category. If a category doesn’t exist already, it will be created automatically. In order to use the messages created in the category, you will need to include the header file where ever you wish to use the messages of the category.
#import #import "Event.h" #import "Event+ChildrensEvent.h" int main(int argc, const char * argv[]) { @autoreleasepool { Event* event = [Event newInstance]; [event setName:@"New Event Name"]; NSLog(@"\n%@", [event name]); [event setChildCount:2]; NSLog(@"\nNumber of Children: %d", [event totalHeadCount]); } return 0; }
Categorizing your own classes to add new messages won’t be a problem. When adding existing classes from another framework to a category you may run into name clashes, so it’s advised to add a three letter prefix to the message names. If there is another message with the same name, it’s impossible to know which one will be called.
Categories will not allow you to add instance variables and properties to existing classes. However, you can add instance variables and properties by using class extensions, which are also known as anonymous categories. Unlike categories, where you don’t need the source to add messages, class extensions require that you have the original source. If you add any messages to a class extension, you must implement them.
A useful trick you can do with class extensions is making messages and properties private, meaning that they are only accessible to the class itself. You could only declare a property as readwrite or readonly, which is an all or nothing situation. To get around this, you would declare the property as readonly and create a class extension that has the same property name, but as readwrite:
@interface Event : NSObject @property (readonly) NSInteger eventId; @end #import "Event.h" @interface Event () { // Instance variables } @property NSInteger eventId; @property NSInteger childrenThreeToFive; @property NSInteger childrenSixToEight; @end
Like classes and categories, class instances are declared and implemented in two different files. The naming convention for class instances is the class name you wish to extend and the name of the class extension concatenated by an underscore (_):
MyClass_MyClassExtension.h
MyClass_MyClassExtension.m
Event_ChildrensEvent.h
Event_ChildrensEvent.m
Protocols
Protocols are used to declare instance and class messages as well as properties that are independent of any specific class, unlike class interfaces. To declare a protocol, use the Objective-C protocol keyword in place of the interface or implementation keywords. Like classes, protocols can inherit from other protocols. The EventDataSource example below shows that the protocol is inheriting from NSObject. If you would like to inherit from another protocol, substitute NSObject with the protocol of your choosing.
@protocol EventDataSource - (NSUInteger) totalHeadCount; - (NSUInteger) numberOfChildren; - (NSUInteger) numberOfAdults; @end
When working with data source objects, it’s important to declare the property as weak to avoid strong reference cycles:
@property (weak) id dataSource;
Using the basic type of id, will allow the class handle any object type as long as it conforms to the protocol. The protocol that we expect the object to conform to is specified between the less than (<) and greater than (>) signs. If you were to try to assign a different object that doesn’t follow the required protocol, the compiler will produce an error.
Protocols can have optional messages which can be specified by using the optional key word:
@protocol EventDataSource // The following three messages are required. - (NSUInteger) totalHeadCount; - (NSUInteger) numberOfChildren; - (NSUInteger) numberOfAdults; @optional - (void) optionalMessage:(NSString*) message; // Every thing that follows is optional until the @required keyword is used. @required - (void) requiredMessage:(NSString*) message; // Every thing that follows is now required. @end
Methods/messages that are marked as optional must be checked to see if an object implements it before calling it:
if([self.dataSource respondsToSelector:@selector(optionalMessage:)]) { [self optionalMessage:@"ponies"]; }
Following protocols is very straight forward, which uses the angle brackets. Multiple protocols can be listed within the brackets but are delimited by a coma.
@interface MyClass : NSObject <eventdatasource, anotherprotocol=""> … @end