Objective-C

Although Swift has been released, there is still a considerable iOS/Cocoa ecosystem which is built on Objective-C.

Types

The BOOL type is an alias for an integer with variants YES and NO set to 1 and 0 respectively. Since these are integers, it’s not advised to explicitly compare to YES because an affirmative value can be any non-zero value.

Explicit integer types can be used such as UInt32 for an unsigned 32-bit integer or SInt16 for a signed 16-bit integer.

The NSInteger and NSUInteger types are aliases for the integer width of the system, e.g. 32-bit on 32-bit systems.

It’s possible to create a boxed expression using the @(…) literal syntax.

Objects

Instead of invoking a method on an object, a message is sent to it:

NSDate *now = [NSDate date];
double seconds = [now timeIntervalSince1970];

Naturally, sent messages are expressions which can be embedded where expressions of that type are expected:

double seconds = [[NSDate date] timeIntervalSince1970];

Class methods are when a message is sent to a class such as to NSDate, whereas instance methods are sent to a particular instance, such as to now.

Method arguments are named when passed to a method.

[now dateByAddingTimeInterval:100000]

A method’s name is often described by the concatenated names of its parameters:

// ordinalityOfUnit:inUnit:forDate:
[cal ordinalityOfUnit:NSDayCalendarUnit
               inUnit:NSMonthCalendarUnit
              forDate:now]

A method’s declaration starts with a - for instance methods and + for class methods, followed by the parameters and their types, e.g.

- (BOOL)isEqualToString:(NSString *)other

The alloc class method handles the allocation of the memory for an object of the class type. It returns a pointer to the allocated memory, but it is not initialized. The memory must be initialized with the init instance method.

NSDate *now = [[NSDate alloc] init];

Convenience methods are those which perform an allocation-initialization sequence in a conveniently expected manner. For example, since NSDate’s init method initializes the NSDate to the current date and time, the date class method is considered a convenience method since it’s more convenient than an explicit alloc-init chain.

Objective-C has nil to represent a pointer to no object. Sending a message to nil has no effect and it is completely legal. However, sending a message to an object that doesn’t implement that method does crash the program.

The id type is a pointer to an Objective-C object:

id delegate = [some delegate];

Automatic Reference Counting

Previously it was necessary to perform manual reference counting by invoking reference-manipulating methods such as release or retain. Automatic Reference Counting (ARC) injects similar constructs automatically into the code.

NSString

NSString is a high-level immutable string type. NSString literals are string literals prefixed with the at sign @.

NSString *name = @"John";

It’s also possible to box named C strings using boxed expressions.

NSStrings can contain Unicode characters by escaping them with \u.

It’s possible to create an NSString from a given format:

NSString *dateString = [NSString stringWithFormat:@"Today is %@", [NSDate date]];

The length method is used to retrieve the string length. Strings can be compared with isEqualToString:.

if ([name isEqualToString:@"John"]) {
  NSLog(@"They're equal");
}

NSArray

An NSArray is an immutable array that holds pointers to other objects. NSArray literals can be created with the syntax @[…].

NSArray *names = @[@"John", @"Jane"];

Before NSArray literal syntax was introduced, the class method arrayWithObjects: was used to specify the list of objects with which to initialize the array, terminated by nil.

Elements of the array are accessed as with any other array, using subscript notation:

NSString *john = names[0];

This syntax is desugared into a call to objectAtIndexedSubscript:. When the subscript notation is used to set an element, it’s desugared into a call to setObject:atIndexedSubscript:.

Before subscripting was introduced, the method objectAtIndex: was used to access a particular element.

The size of the array can be obtained with the count method.

NSArrays can be iterated over using the for-in syntax known as fast enumeration.

for (NSString *name in names) {
  NSLog(@"%@ is in the array");
}

NSMutableArray

NSMutableArray is a subclass of NSArray that is mutable. The addObject: method can be used to push an object onto the array, whereas insertObject:atIndex: can insert an object at a particular location in the array.

The convenience class method array creates an empty array, just like an alloc-init chain would.

It’s not possible to add or remove elements within a fast enumeration loop because the iterators would become invalidated. Instead use a regular for loop.

Classes

By convention classes are defined in pairs of header (.h) and implementation (.m) files. The header contains the interface that begins with the @interface keyword which states the class name and its base classes, as well as its member variables. Method declarations are placed after and outside of the braces. The end of the interface is marked with @end.

Due to the lack of namespaces, by convention classes and types are prefixed by some namespace initials preferably 3 or more letters in length, for example the NS in NSString stands for NeXTSTEP.

By convention member variables are prefixed with an underscore _, and getters take on the name of the variable without the underscore prefix and setters are prefixed with set.

@interface MYPerson : NSObject
{
  int _age;
  NSString *_name;
}

- (int)age;
- (void)setAge:(int)a;

@end

By convention, accessor methods are used within instance methods rather than accessing the instance variables directly.

The implementation file (.m) includes the header file by using the #import directive which among other things prevents double inclusions. The beginning of the method implementations is marked with @implementation and the end of the implementation is marked with @end.

#import "MYPerson.h"

@implementation MYPerson

- (int)age
{
  return _age;
}

- (void)setAge:(int)a
{
  _age = a;
}

@end

Like this in C++, the self keyword is an implicit local variable in instance methods which points to the object running the method.

Rather than manually defining accessor methods, it’s possible to define properties for which the compiler automatically defines accessor methods. A property is marked with @property and it takes an argument list specifying the property’s attributes (e.g. atomic or nonatomic), followed by the type and name of the property. Properties are declared within the class’ @interface.

@interface MYPerson : NSObject

@property (nonatomic) int age;
@property (nonatomic, copy) NSString *name;

{
  
}

@end

Property attributes can specify whether the property is atomic or nonatomic, or whether to only generate a getter but not a setter via readonly. For example, marking it readonly will cause the compiler to generate a getter method but no setter method. The readwrite permission is the default. The copy attribute specifies whether the property should be copied when set or retrieved via a getter. This should be used whenever declaring a property of a pointer to an object.

Previously it was then necessary to place a corresponding @synthesize directive in the @implementation to actually direct the compiler to generate the accessor methods. The @synthesize directive specified the name that would be publicly available in the accessor methods and the name to use internally.

@implementation MYPerson

@synthesize age = _age;
@synthesize name = _name;

@end

However, as of Xcode 4.4 this is no longer necessary as the compiler does this for you. It’s still necessary to do if the compiler ends up not generating any of the methods, for example if a property is marked readonly but the getter is manually overridden, then the _variable won’t be defined, in which case it’s necessary to explicitly @synthesize to define it or declare it manually in the @interface.

Properties are also usable using dot notation as in C++ and Java. However, rather than simply accessing a field in a struct as in C++, dot notation translates to a message sent to the corresponding accessor method.

NSString *theName = person.name;

It’s possible to define a property with a different type than a manually defined member variable of the same name. For example, the class below defines and uses a mutable array internally, but publicly exposes an immutable array.

@interface MYPerson : NSObject
{
  NSMutableArray *_names;
}

@property (nonatomic, copy) NSArray *names;

- (void)addName:(NSString *)n;

@end

Notice that the accessor methods are overridden to handle the translation between mutable and immutable arrays. Specifically, the setter sets the internal variable to a mutable copy of the passed immutable array, while the getter returns an immutable copy of the internal immutable array.

@implementation MYPerson

- (void)setNames:(NSArray *)n
{
  _names = [n mutableCopy];
}

- (NSArray *)names
{
  return [_names copy];
}

- (void)addName:(NSString *)n
{
  if (!_names) {
    _names = [[NSMutableArray alloc] init];
  }

  [_names addObject:n];
}

@end

Inheritance

The superclass that a class inherits from is specified in the @interface line and it doesn’t need to be repeated in the @implementation line.

A method is overridden by simply redefining its implementation; it’s not possible to redefine its name, return type, or argument types.

The super keyword is an implicit local variable within instance methods that refers to the object of the superclass from which the class inherits.

NSObject contains an instance variable named isa which points at the class that created it. For example, MYPerson’s isa variable would point to MYPerson. When a message is sent to an object, it checks the isa pointer to see if the class it points to defines such a method, if not, the search continues up the inheritance hierarchy. If the search reaches the top (NSObject) and the method is still not found, an error is emitted specifying that an “unrecognized selector sent to instance.” This method lookup is what facilitates method overriding.

The %@ token in format strings passed to NSLog for example cause a description message to be sent to the target object. NSObject defines this method which simply returns the object’s address formatted as a string. It can be overridden in a subclass to provide more useful information.

@implementation MYPerson

- (NSString *)description
{
  return [NSString stringWithFormat:@"<Person %@>", self.name];
}

@end

Forward declarations are possible with the @class keyword.

When a @property is defined on a class without a corresponding manually-defined instance variable, subclasses aren’t able to access the synthesized instance variable directly; they must do so via the accessors. For example, given:

@interface MYPerson : NSObject
{
}

@property (nonatomic) NSMutableArray *friends;
@end

@interface MYEmployee : MYPerson
{
  
}
@end

Then a subclass MYEmployee cannot access _friends directly; it must do so via an explicit accessor or dot notation.

@implementation MYEmployee



[_friends addObject:@"Bob"];     // Error

[self.friends addObject:@"Bob"]; // Ok

Ownership

An object that contains a pointer to another object is said to own that object. Due to reference counting, the owned object knows how many owners it has through its reference count.

The dealloc method is run when an instance of a class is deallocated because it has no owners.

Class Extensions

Private internal methods, instance variables, and properties should be defined in a class extension, which is a set of private declarations that only the class or instances of it can use. A class interface is denoted by a typical @interface block with an empty parentheses pair at the end. By convention class extensions are declared in the implementation file, before the @implementation.

#import "MYPerson.h"

@interface MYPerson ()

@property (nonatomic) int somePrivateVariable;

@end

@implementation MYPerson



@end

An earlier example demonstrated that it’s possible to have a manually-defined instance variable differ in the type of a separate property of the same name. However, doing that can be confusing, and instead it’s recommended to use a private class extension to define the manually-defined instance variable.

Since the class extension is defined in the implementation file, and subclasses #import the header file, subclasses won’t have access to the superclass’ class extensions.

Reference Counting

A Strong reference cycle represents a potential for a memory leak, because the garbage collector cannot deallocate either side of the cycle. A strong reference cycle can be weakened with a weak reference which is a pointer that does not imply ownership. This is useful in a parent-child relationship, in which case the child should hold a weak reference to the parent, since the parent is what owns the child.

@interface TreeNode : NSObject

@property (nonatomic, weak) TreeNode *parent;

@end

When the targets of weak pointers are deallocated, the weak pointer is set to nil.

Weak points can be explicitly created with the __weak keyword:

__weak MYPerson *parent;

Before ARC, manual reference counting was necessary using the retain and release methods. For example, in a setter, the passed object was retained to increment its reference count and the previously-held object was released to decrease its reference count, then the pointer was set to the new object:

- (void)setPerson:(MYPerson *)newPerson
{
  [newPerson retain];
  [_person release];
  _person = newPerson;
}

Furthermore, the dealloc call had the responsibility of releaseing all held objects and deallocating the immediate superclass.

- (void)dealloc
{
  [_holder release];
  [super dealloc];
}

Newly created and returned objects would be marked as autorelease, i.e. release sometime in the future. For example, the description method creates and returns a new NSString, so it was marked for autorelease:

- (NSString *)description
{
  NSString *result = [[NSString alloc] initWithFormat:@"Person: %@", [self name]];

  [result autorelease];
  return result;
}

Specifically the object was sent the release message when the current autorelease pool was drained:

NSAutoreleasePool *arp = [[NSAutoreleasePool alloc] init];

NSString *desc = [[[MyPerson alloc] init] description];

[arp drain]; // `desc` sent `release` message

The syntax sugar @autoreleasepool can be used to automatically create an autorelease pool and drain it at the end of the provided block.

@autoreleasepool {
  NSString *desc = [[[MyPerson alloc] init] description];
} // drained here

There are a couple of rules of thumb for manual reference counting:

  • Creating an object using a method starting with alloc, new, or containing copy gives you ownership of it. Assume refcount = 1, not in autorelease pool.

  • Objects created by any other means are not owned by you. Assume refcount = 1, in autorelease pool.

  • Take ownership by retaining it.

  • Relinquish ownership by using release or autorelease.

  • Objects exist as long as they have an owner.

This explains why the NSString returned by description is autoreleased: because although it created the object via alloc-init and thus gained ownership of it, it is giving it away by returning it. Sending it a release message would immediately decrement its refcount, thereby deallocating it, so instead it is autoreleased.

Collections

Sets are represented by NSSet and NSMutableSet.

Collection methods which test for equality contain variants containing the word Identical which test if the objects are the same object by testing the pointers for equality, e.g. indexOfObject: vs indexOfObjectIdenticalTo:.

Dictionaries are represented by NSDictionary and NSMutableDictionary. Dictionaries can be created from literal syntax @{…}. A dictionary can be keyed using subscript notation, for example:

NSDictionary *ages = @{
  @"John": @20,
  @"Jane": @21,
};

NSNumber johnAge = ages[@"John"];

This syntax is desugared into objectForKeyedSubscript:. When an entry is being set, it’s desugared into setObject:forKeyedSubscript:.

Mutable arrays can be sorted using:

- (void)sortUsingDescriptors:(NSArray *)sortDescriptors;

A sort descriptor is an object of type NSSortDescriptor which specifies a property of the sorted element—any instance variable or the result of any method of the object—and whether to sort it in ascending or descending order. sortUsingDescriptors: takes an array of sort descriptors so that in the event of equality, the next descriptor is used. For example, to sort by the property lastName in ascending order, the following descriptor may be used:

NSSortDescriptor *lastAscending = [NSSortDescriptor sortDescriptorWithKey:@"lastName"
                                                                ascending:YES];

Collections can be filtered given a predicate of type NSPredicate. The filtering is done in-place on an NSMutableArray via filterUsingPredicate: whereas a copy is created for NSArray via filteredArrayUsingPredicate:. The predicate can be constructed from a string representing the condition:

NSPredicate *pred = [NSPredicate predicateWithFormat:@"person.age > 18"];
NSArray *adults = [people filteredArrayUsingPredicate:pred];

An NSNumber is essentially a boxed number type which is used to wrap numbers so that they can be stored in collections such as NSDictionary. They can be constructed using NSNumber literals such as @2.

The NSValue type can be used to box/wrap arbitrary types such as structs. C structures with the objc_boxable attribute can be boxed into NSValue via boxed expressions. Structures without this attribute can add the attribute via a typedef:

typedef struct __attribute__((objc_boxable)) _OldThing Thing;

Thing thing;
NSValue *myThing = @(thing);

It’s not possible to insert nil into a collection. In order to represent a “hole” in a collection, the NSNull class can be used.

Enumerations

The NS_ENUM() preprocessor macro can be used to specify the enumeration’s backing data type and name.

typedef NS_ENUM(int, Color) {
  ColorRed,
  ColorBlue,
  ColorGreen
};

Enumerations can be boxed using boxed expressions. For example, @(AVAudioQualityMax) converts the enumeration to an integer type and boxes the value.

NSError

Some methods may fail for any reason. By convention, the error is returned through a parameter which is a pointer to a pointer to an NSError. That is, the parameter type is NSError **, but the caller can simply create a NSError * and return a pointer to it via &error. Methods that can take an NSError pointer parameter always return a value indicating whether or not there was an error.

NSError *error = nil;

BOOL success = [obj someArgument:@"test" error:&error];

if (!success) {
  NSLog(@"failed: %@", [error localizedDescription]);
}

NSData

The NSData class represents a buffer of bytes. The data can be written to a file using writeToFile:options:error. The option NSDataWritingAtomic ensures an atomic write operation.

It’s possible to obtain the standard path for a given task by using the NSSearchPathForDirectoriesInDomains method. For example, to get the desktop directory:

NSArray *desktops =
  NSSearchPathForDirectoriesInDomains(NSDesktopDirectory, NSUserDomainMask, YES);

NSString *desktopPath = desktops[0];

Callbacks

Callbacks in Objective-C can take on four forms:

  1. Target-action: Specify an object (target) and a message to send it (action).
  2. Helper objects: Specify objects which do the required work. These objects are also known as delegates or data sources.
  3. Notifications: An object subscribes to the notification center for a particular kind of notification.
  4. Blocks: Essentially a lambda meant to run when the event is triggered.

Events happen within the context of a run loop of type NSRunLoop.

The potential for strong reference cycles is high in most callback schemes. For example, an object may have a pointer to the object that will call it back, and that object contains a pointer to the object so that it can call it back. To mitigate this:

  • Notifications don’t own their observers. Observers remove themselves from the notification center in their dealloc method via removeObserver:.

    - (void)dealloc {
      [[NSNotificationCenter defaultCenter] removeObserver:self];
    }
    
  • Objects don’t own their delegates/data sources. Delegates/data sources remove themselves in their dealloc method.

    - (void)dealloc {
      [delegator setDelegate:nil];
    }
    
  • Objects don’t own their targets. Target objects should remove themselves in their dealloc method.

    - (void)dealloc {
      [button setTarget:nil];
    }
    

Selectors

Each method name that the compiler encounters is given a unique number known as a selector which is used to perform method lookup. The @selector(…) directive is replaced by the compiler with the selector for the given method.

Target-action

Action methods are the methods invoked by a target-action combination, and they always take a single argument consisting of the object that sent the message.

Timers of type NSTimer use the target-action pattern to specify what message to send to what object every time the timer triggers. For example, the timer below will send the message actionMethod to someObject every 2 seconds.

NSTimer *timer =
  [NSTimer scheduledTimerWithTimeInterval:2.0
                                   target:someObject
                                 selector:@selector(actionMethod:)
                                 userInfo:nil
                                  repeats:YES];

Unused variable warnings can be explicitly silenced by using the __unused keyword as a prefix to the type:

__unused NSString *name = @"John";

When sending one callback to one object, Apple uses target-action.

Helper Objects

Helper objects implement methods used to do different kinds of work. For example, asynchronous usage of NSURLConnection requires a helper object which defines methods to do work in response to new data, authentication, handle failure, etc.

For example, in the code below, the connection is configured to use object logger as its delegate, which defines methods which are invoked in response to specific events, such as connectionDidFinishLoading:.

NSURLConnection *conn =
  [[NSURLConnection alloc] initWithRequest:request
                                  delegate:logger
                          startImmediately:YES];

The methods expected by an NSURLConnection are defined in a protocol—a list of method declarations. A protocol is implemented by specifying its name in angle brackets following the superclass in the @interface line:

@interface MYLogger : NSObject <NSURLConnectionDelegate, NSURLConnectionDataDelegate>
{
  
}

When sending various callbacks to one object, Apple uses a helper object with a protocol.

Notifications

Various objects can subscribe to certain notifications using the notification center NSNotificationCenter. For example, the code below subscribes the logger object to a notification for when the time zone is changed, and it’s configured to invoke the zoneChange: method. It’s also possible to specify that only notifications sent from a particular object are to be considered.

[[NSNotificationCenter defaultCenter]
  addObserver:logger
     selector:@selector(zoneChange:)
         name:NSSystemTimeZoneDidChangeNotification
       object:nil];

Blocks

Blocks are essentially lambdas. They are prefixed by a caret ^ followed by optional parameter list and, followed by braces surrounding the body.

^{
  NSLog(@"No parameters.");
}

^(int a, int b){
 return a + b;
}

Blocks can be stored in a variable, in which case their type must be explicitly typed. Block types look like function pointer types except that they use ^ to denote a block rather than * to denote a pointer.

void (^devowelizer)(id, NSUInteger, BOOL *);

Anonymous blocks are ones that are passed directly to a method without first giving them a name by storing them in a variable.

External variables are those that are captured by the block from the outer scopes. Primitive variables are copied as local variables within the block. Pointers are kept as strong references to ensure that they live at least as long as the block itself.

Since pointers are captured as strong references, it’s easy to inadvertently create a strong reference cycle. This can happen implicitly when an instance variable is used directly within a block, because directly accessing an instance variable _var gets translated to self->_var by the compiler.

The cycle can be broken by first creating a __weak pointer to self, then using that within the block. However, this would mean that self could be deallocated while the block is executing. To prevent that, a strong reference to the __weak pointer can be created:

__weak MYPerson *weakSelf = self;

block = ^{
  MYPerson *innerSelf = weakSelf;
  NSLog(@"Person: %@", innerSelf);
};

Instance variables should be accessed through the innerSelf to avoid implicitly and inadvertently capturing self.

Variables captured by a block are constant within the block. In order to modify an external variable within a block, it must be declared as an external variable by using the __block keyword prefix:

__block int count = 0;

void (^incrementBlock)() = ^{ count++ };

Protocols

A protocol is a list of method declarations which may be required or optional. Any object that wants to conform to a protocol must implement the required methods.

@protocol UITableViewDataSource <NSObject>

@required

- (NSInteger)tableView:(UITableView *)tv numberOfRowsInSection:(NSInteger)section;



@optional

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tv;



@end

It’s then possible to specify the type of any object that conforms to a particular protocol:

@property (nonatomic, assign) id<UITableViewDataSource> dataSource;

In order to make a class conform to a protocol, the protocol name must be mentioned in the @interface:

@interface SomeViewController : UIViewController <UITableViewDataSource>



@end

To avoid crashing the program when attempting to call an optional method that an object didn’t implement, it’s necessary to first check if the object did implement the method by using NSObject’s respondsToSelector: which returns a BOOL indicating if the object does respond to that message.

if ([_dataSource respondsToSelector:@selector(numberOfSectionsInTableView:)]) {
  // call it here
} else {
  // don't call it; use some default value
}

Property Lists

A property list (aka P-list) is an XML file format which is used to serialize Objective-C objects such as NSArray, NSDictionary, NSString, NSData, NSDate, and NSNumber.

Writing a property list can be achieved by using NSMutableArray’s writeToFile:atomically:. It can then be read in by using arrayWithContentsOfFile.

iOS Applications

GUI applications start a run loop which listens for events.

The company identifier is used to generate a bundle identifier which is used to uniquely identify each app in the App Store.

Every iOS application has an app delegate which is a subclass of UIResponder which conforms to UIApplicationDelegate. The app delegate has a property of type UIWindow which fills the screen of the iOS application. Other controls can be added onto the window.

When an iOS application launches, an instance of UIApplication is created to control the application state and liaison with the operating system. An instance of the application delegate serves as the UIApplication’s delegate. For example, the application:didFinishLaunchingWithOptions method is invoked when the application becomes ready.

A control is created by specifying the a CGRect to specify its frame (positions and dimensions) and then it’s added to the UIWindow as a sub-view.

- (BOOL)application:(UIApplication *)application
        didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
  // CGRect is (x, y, width, height)

  CGRect winFrame [[UIScreen mainScreen] bounds];
  UIWindow *theWindow = [[UIWindow alloc] initWithFrame:winFrame];
  self.window = theWindow;

  CGRect buttonFrame = CGRectMake(228, 40, 72, 31);
  self.button = [UIButton buttonWithType:UIButtonTypeRoundedRect];
  self.button.frame = buttonFrame;

  [self.button setTitle:@"Click me" forState:UIControlStateNormal];

  [self.window addSubview:self.button];
  [self.window makeKeyAndVisible];

  return YES;
}

A UIButton uses the target-action callback pattern to send a message when it receives certain events. For example, to send the addTask: message to the application delegate:

[self.button addTarget:self
                action:@selector(addTask:)
      forControlEvents:UIControlEvenTouchUpInside];

The App Distribution Guide details Apple’s process of distributing an application.

When using the Interface Builder, the IBOutlet keyword is actually an empty #define which only serves to tell Xcode that the pointer to the object will be set by Interface Builder and not manually in code. The IBAction keyword is actually an alias for void that tells Xcode that a method is an action method that will be used in a target-action pair configured in Interface Builder and not manually in code.

Interface Builder files .xib are XML files representing the UI.

Initializers

When a class does not override the init method, NSObject’s init method will be called which zeroes out all of the subclass’ instance variables.

When overriding the init method, the first line should call super’s init, then the return value should be checked to determine if a valid object was returned.

The return type of init should be instancetype, a keyword represents the type of an instance to which the method belongs. This is preferred over returning an explicit type such as MYPerson * so that subclasses can continue to use the same initializer to initialize objects of the subclass type. Previously this was done by returning id, but instancetype is preferred because it more narrowly restricts the type to the type of the receiver.

It’s necessary to set self to the value returned by super’s init because a superclass init may have deallocated self and allocated a new object. Then it’s necessary to check that the returned value is not nil, in which case we can forego further initialization and simply return nil.

- (instancetype)init
{
  // initialize superclass object, e.g. NSObject
  self = [super init];

  // above may return nil
  if (self) {
    _instance_variable = 3;
  }

  return self;
}

It’s also common to do the three things in one step: initializing the superclass object, assigning it to self, and checking its value:

- (instancetype)init
{
  if (self = [super init]) {
    _instance_variable = 3;
  }

  return self;
}

It’s also possible to create initializers which take arguments in the same manner.

In initializers it’s common to simply use instance variables directly rather than through accessors because it is more semantically correct, since accessors may assume that the object is already initialized. However, using accessors usually works fine.

It’s often necessary to override superclass initializers to account for the subclass’ additional variables, otherwise users may initialize a subclass using a superclass initializer which will not initialize the rest of the subclass variables, leaving the object in an inconsistent state.

A designated initializer is a single initializer in a class that all other initializers in the class directly or indirectly call and rely upon for complete initialization of the object.

If the subclass’ designated initializer has a different name than the designated initializer of its superclass, the superclass’ designated initializer should be overridden to call the new subclass’ designated initializer.

When there is no good default initialization of an object, init can be overridden to raise an exception that will crash the program:

- (instancetype)init
{
  [NSException raise:@"MYNoDefaultUserPass"
              format:@"Use initWithUser:Pass:, not init"];
}

Properties

The @property directive accepts a list of attributes that affects how the accessors are created.

The readwrite and readonly attributes specify whether to create both setters and getters or just getters, respectively.

The assign attribute is the default attribute for non-object types. It simply assigns the passed value to the property.

The strong attribute is the default for object pointers. It ensures that a strong reference is kept to the passed object, releasing ownership of the old object.

The weak attribute represents a weak reference to the passed object. IF the pointed-to object is deallocated, the property is set to nil.

The unsafe_unretained attribute acts like the weak attribute except that the property is not automatically set to nil when the pointed-to object is deallocated.

The copy attribute creates a copy of an object and then makes the pointer point to the copy. This is commonly used with objects that have mutable subclasses. This ensures that if an NSMutableString is passed, an immutable NSString copy will be created and pointed to, and if an immutable NSString is passed, no copy is actually performed because NSString overrides copyWithZone:, called by copy, to just return the pointer to itself.

The atomic attribute ensures that the setters are atomic. This is the default attribute but is rarely needed, so most properties should be explicitly marked nonatomic.

Key-Value Coding

Key-Value Coding allows reading and setting a property using its name, for example the following two are equivalent:

[person setName:@"John"];
[person setValue:@"John" forKey:@"name"];

The setValue:forKey: method is defined in NSObject and looks for a setter method, property, or instance variable of the given name.

Similarly, the valueForKey: method can be used to read a value.

Key-value coding is used by general frameworks that need to read or push data to one’s own custom objects, since it provides a uniform way of accessing that information regardless of the object’s layout and capabilities.

It’s possible to use key-value coding to set primitive types by for example using NSNumber.

[person setAge:[NSNumber numberWithInt:18] forKey:@"age"];

A key path is a way to use dot notation to specify a property in a given object hierarchy. For example:

NSString *schoolName = [person valueForKeyPath:@"class.school.name"];

There is also setValue:forKeyPath:.

Key-Value Observing

Key-value observing (KVO) is a way of subscribing to notifications that are emitted when an object’s property changes. KVO enables Cocoa bindings and Core Data.

KVO is accomplished by using NSObject’s addObserver:forKeyPath:options:context: to specify an observer object which implements a method called when the property changes.

@implementation MYObserver

- (void)observeValueForKeyPath:(NSString *)keyPath
                      ofObject:(id)object
                        change:(NSDictionary *)change
                       context:(void *)context
{
  NSString *oldValue = [change objectForKey:NSKeyValueChangeOldKey];
  NSString *newValue = [change objectForKey:NSKeyValueChangeNewKey];

  NSLog(@"Property %@ on object %@ changed: %@ → %@",
        keyPath, object, oldValue, newValue);
}

@end
__unused MYObserver *observer = [[MYObserver alloc] init];

[person addObserver:observer
         forKeyPath:@"name"
            options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld
            context:nil];

Since a superclass may itself use KVO, it may be necessary for the subclass to determine if the notification was meant for it or its superclass, in which case it would need to be forwarded to the superclass. This differentiation can be accomplished by passing a class-unique pointer to the context: parameter. This class-unique pointer is often accomplished by creating a static variable.

// pass as context:&contextForKVO
static int contextForKVO;

@implementation MYObserver

- (void)observeValueForKeyPath:(NSString *)keyPath
                      ofObject:(id)object
                        change:(NSDictionary *)change
                       context:(void *)context
{
  if (context != &contextForKVO) {
    // notification is for superclass
    [super observeValueforkeypath:keyPath
                         ofObject:object
                           change:change
                          context:context];
  } else {
    // notification is for us
  }
}

@end

If the accessor method is not used, the notification will have to be sent explicitly by using NSObject’s willChangeValueForKey: and didChangeValueForKey:.

It’s possible to specify that a given property is changed whenever another one is changed. For example, if an ageString property is recomputed whenever the age property is changed, KVO can be configured to send notifications whenever age changes. This is accomplished by implementing a method named keyPathsForValuesAffecting concatenated with the name of the property in camel case. This method returns an NSSet of properties that, when changed, lead to a change in the named property:

+ (NSSet *)keyPathsForValuesAffectingAgeString
{
  return [NSSet setWithObject:@"age"];
}

Categories

Categories are a way of adding new methods to existing classes. By convention, categories are declared in files named as a combination of the target class and the category name. For example, NSString+MYVowelCounting.{h,m} would contain a category for NSString named MYVowelCounting:

By convention, to avoid clobbering existing methods—including private ones that we may not know about—we prefix category method names with the namespace we use and an underscore, e.g. my_.

@interface NSString (MYVowelCounting)

- (int)my_vowelCount;

@end
#import "NSString+MYVowelCounting.h"

@implementation NSString (MYVowelCounting)

- (int)my_vowelCount
{
  
}

@end
#import "NSString+MYVowelCounting.h"

NSString *string = @"testing";
int vowelCount = [string my_vowelCount];

Runtime

The respondsToSelector: method is an example of run-time introspection.

The C function objc_msgSend is the function that performs the lookup and execution of an Objective-C method.

#import <objc/message.h>

NSString *name = @"John";
int length = objc_msgSend(name, @selector(length));

KVO works by creating a subclass of the observed object at run-time and changing the object’s isa pointer to point to that new subclass (effectively changing the type of the object) and overriding the accessors for the observed properties so that the willChangeValueForKey: and didChangeValueForKey methods are invoked around the property change.

See the Objective-C Runtime Programming Guide and Objective-C Runtime Reference for more information.

May 21, 2016
329ce08 — May 23, 2024