Types

The type system in Objective-C is a little bit more disparate than Swift. This is because the structures and enumerations in Objective-C come from C. Only classes and categories come from the Objective-C extension.

Structures

In Swift, structures are very similar to classes, but in Objective-C, they are much more different. Structures in Objective-C are essentially just a way of giving a name to a collection of individual types. They cannot contain methods. Even more restrictive than that, structures can't contain Objective-C types. This leaves us with only basic possibilities:

struct Cylinder {
    var radius: Int
    var height: Int
}
var c = Cylinder(radius: 10, height: 10)
typedef struct {
    int radius;
    int height;
} Cylinder;
Cylinder c;
c.radius = 10;
c.height = 5;

Structures in Objective-C start with the keyword typedef, which is short for type definition. This is then followed by the struct keyword and the different components of the structure contained within curly brackets. Finally, after the curly brackets is the name of the structure.

Advanced C programmers will do a lot more with structures. There are ways to simulate some features of inheritance with structures and to do other more advanced things, but that is beyond the scope of this book and not very relevant in most modern programming projects. There are some types in Apple's APIs that are structures like CGRect so you should know how to interact with them, but you most likely won't have to deal with custom structure definitions when looking at Objective-C resources.

Enumerations

Enumerations are also much more restrictive in Objective-C. They are really just a simple mechanism to represent a finite list of related possible values. This allows us to still represent possible primary colors:

enum PrimaryColor {
    case Red
    case Green
    case Blue
} 
var color = PrimaryColor.Blue

typedef enum {
    PrimaryColorRed,
    PrimaryColorGreen,
    PrimaryColorBlue,
} PrimaryColor;
PrimaryColor color = PrimaryColorBlue;

Just like with structures, Objective-C enumerations start with the keyword typedef followed by enum with the name at the end of the definition. Each case is contained within the curly brackets and separated by a comma.

Notice that every case of the enumeration starts with the name of the enumeration. This is a very common convention, to make it easy for code completion and to show all possible values of an enumeration. This is because in Objective-C, you cannot specify a specific enumeration value through the name of the enumeration itself. Instead, every case is its own keyword. This is why when we are assigning our color variable to blue; we use the case name by itself.

Enumerations in Objective-C cannot have methods, associated values, or represent any other values except for integers. In fact, in Objective-C enumerations, every case has a numeric value. If you don't specify any, they start at 0 and go up by 1 for each case. If you want, you can manually specify a value for one or more of the cases:

typedef enum {
    PrimaryColorRed,
    PrimaryColorGreen = 10,
    PrimaryColorBlue,
} PrimaryColor;

Each case after a manually specified case will continue to increase by one. This means that in the preceding code PrimaryColorRed is still 0 but PrimaryColorBlue is 11.

Classes

Unlike Objective-C structures and enumerations, classes are very similar to their Swift counterparts. Objective-C classes can contain methods and properties, use inheritance, and get initialized. However, they look pretty different. Most notably, a class in Objective-C is split into two parts: its interface and its implementation. The interface is intended to be the public interface to the class, while the implementation includes the implementation of that interface in addition to any other private methods.

Basic class

Let's start by looking again at our contact class from Chapter 3, One Piece at a Time – Types, Scopes, and Projects and what it looks like in Objective-C:

class Contact {
    var firstName: String = "First"
    var lastName: String = "Last"
}
@interface Contact : NSObject {
    NSString *firstName;
    NSString *lastName;
}
@end

@implementation Contact
@end

Already Objective-C is taking a lot more lines of code. First, we have the interface declaration. This begins with the @interface keyword and ends with the @end keyword. Within the square brackets is a list of attributes. These are essentially the same as the attributes of a structure, except that you can include Objective-C objects in the attributes. These attributes are not commonly written like this because using the properties will create these automatically, as we will see later.

You will also notice that our class is inheriting from a class called NSObject, as indicated by : NSObject. This is because every class in Objective-C must inherit from NSObject, which makes NSObject the most basic form of class. However, don't let the term "basic" fool you; NSObject provides a lot of functionality. We won't really get into that here, but you should at least be aware of it.

The other part of the class is the implementation. It starts with the @implementation keyword followed by the name of the class we are implementing and then ends again with the @end keyword. Here, we have not actually added any extra functionality to our contact class. However, you may notice that our class is missing something that the Swift version has.

Initializers

Objective-C does not allow specifying default values for any attributes or properties. This means that we have to implement an initializer that sets the default values:

@implementation Contact
- (id)init {
    self = [super init];
    if (self) {
        firstName = @"First";
        lastName = @"Last";
    }
    return self;
}
@end

In Objective-C, initializers are the exact same as a method, except that by convention they start with the name init. This is actually just a convention but it is important, as it will cause problems down the line with memory management and interacting with the code from Swift.

The minus sign at the beginning indicates that this is a method. Next, the return type is specified within parentheses, which is then followed by the name of the method: in this case init. The body of the method is contained in curly brackets just like a function.

The return type for all initializers is going to be id by convention. This allows us to easily override initializers of subclasses.

Virtually all initializers will follow this same pattern. Just like in Swift, self references the instance that this method is being called on. The first line assigns the self reference to the result by calling the superclass's initializer with [super init]. We then allow for the possibility that the initializer fails and returns nil by testing it for nil in the if (self) statement. The if statement will fail if self is nil. If it is not nil, we assign the default values. Finally, we return self, so that calling code can maintain a reference to the newly initialized object. However, this is just a convention and Objective-C does not have any protection around properly initializing properties.

Properties

The Objective-C version of the contact class still isn't exactly like the Swift version because the firstName and lastName attributes are not accessible from outside the class. To make them accessible we need to define them as public properties and we can drop them from being explicit attributes:

@interface Contact : NSObject {
}
@property NSString *firstName;
@property NSString *lastName;
@end

Note that the properties are defined outside of the curly brackets but still within the @interface. In fact, you can leave off the curly brackets altogether if you have nothing to define in it. Properties automatically generate attributes by the same name except with an underscore at the beginning:

@implementation Contact
- (id)init {
    self = [super init];
    if (self) {
        _firstName = @"First";
        _lastName = @"Last";
    }
    return self;
}
@end

Alternatively, you can just set the values using self:

@implementation Contact
- (id)init {
    self = [super init];
    if (self) {
        self.firstName = @"First";
        self.lastName = @"Last";
    }
    return self;
}
@end

There are nuances to each approach but for just general reading purposes, it doesn't matter which one is used.

Also, just as you can define weak references in Swift, you can do so in Objective-C:

@interface SteeringWheel : NSObject
@property (weak) Car *car;
@end

If you want, you can replace weak with strong, but just like Swift, all properties are strong by default. Weak references in Objective-C will automatically be set to nil if the referenced object gets deallocated. You can also use the unsafe_unretained keyword, which is equivalent to unowned in Swift. However, this is rarely used as the only difference between the two in Objective-C is that unsafe_unretained does not reset the value to nil; instead, it will reference an invalid object if the object gets deallocated causing confusing crashes if used.

In addition to weak or strong, you can also specify that a property is readonly or readwrite:

@interface SteeringWheel : NSObject
@property (weak, readonly) Car *car;
@end

Each property attribute should be written inside the parentheses separated by a comma. As the readonly name implies, this makes it so that the property can be read but not written to. Every property is read-write by default, so normally it is not necessary to include it.

Note that you may also see the keyword nonatomic in the parentheses. This is a more advanced topic that is beyond the scope of this book.

Methods

We have already seen an example of a method in the form of an initializer, but let's take a look at some methods that take parameters:

@implementation Contact
- (NSArray *)addToInviteeList:(NSArray *)invitees includeLastName:(BOOL)include {
    NSMutableArray *copy = [invitees mutableCopy];
    if (include) {
        NSString *newString = [self.firstName
           stringByAppendingFormat:@" %@", self.lastName
        ];
        [copy addObject:newString];
    }
    else {
        [copy addObject:self.firstName];
    }
    return copy;
}
@end

Each parameter is defined with a public label followed by a colon, its type in parentheses, and an internal name. Then, each parameter is separated by a space or new line.

You can also see an example way to format a long method call with the creation of the newString instance. Similar to Swift, any space can be converted to a new line instead. This allows us to convert a single long line into multiple lines, as long as we don't put semicolons after the partial lines.

Like Swift, Objective-C also has the idea of class methods. Class methods are indicated with a plus sign instead of a minus sign:

@implementation Contact
+ (void)printAvailablePhonePrefixes {
    NSLog(@"+1");
}
@end

So now you can call the method directly on the class:

[Contact printAvailablePhonePrefixes];

Inheritance

Just as all of our classes so far have inherited from NSObject, any class can inherit from any other class just like in Swift and all the same rules apply. Methods and properties are inherited from their superclass and you can choose to override methods in subclasses. However, the compiler enforces the rules much less. The compiler does not force you to specify that you intend your method to override another. The compiler does not enforce any rules around initializers and whom they call. However, all the conventions exist because those conventions were the inspiration for the Swift requirements.

Categories

Categories in Objective-C are just like Swift extensions. They allow you to add new methods to existing classes. They look very similar to plain classes:

extension Contact {
    func fullName() -> String {
        return "(self.firstName) (self.lastName)"
    }
}
@interface Contact (Helpers)
- (NSString *)fullName;
@end

@implementation Contact (Helpers)
- (NSString *)fullName {
    return [self.firstName stringByAppendingFormat:@" %@", self.lastName];
}
@end

We know that this is a category instead of a normal class because we added a name within parentheses after the class name. Every category on a class must have a unique name. In this case, we are calling it Helpers and we are adding a method to return the contact's full name.

Here, for the first time, we are declaring a method inside the interface. This is also possible with classes. A method definition looks exactly like an implementation except that it ends in a semicolon instead of the code inside the curly brackets. This will allow us to call the method from outside the current file, as we will see in more detail in the upcoming projects section.

Categories can also add properties, but you will have to define your own getter and setter methods because just like Swift extensions can't add stored properties, Objective-C categories can't add attributes:

@interface Contact (Helpers)
@property NSString *fullName;
@end

@implementation Contact (Helpers)
- (NSString *)fullName {
    return [self.firstName stringByAppendingFormat: @" %@",
        self.lastName
    ];
}
- (void)setFullName:(NSString *)fullName {
    NSArray *components = [fullName
        componentsSeperatedByString:@" "];
    ];
    if (components.count > 0) {
        self.firstName = components[0];
    }
    if (components.count > 1) {
        self.lastName = components[1];
    }
}
@end

These types of properties are very similar to calculated properties. If you need to allow reading from a property, you must implement a method with the exact same name that takes no parameters and returns the same type. If you want to be able to write to the property you will have to implement a method that starts with set, followed by the same property name with a capital first letter, that takes the property type as a parameter and returns nothing. This allows outside classes to interact with the property as if it were an attribute, when in fact it is just another set of methods. Again, this is possible within a class or a category.

Protocols

Like Swift, Objective-C has the idea of protocols. Their definition looks similar to this:

protocol StringContainer {
    var count: Int {get}
    func addString(string: String)
    func enumerateStrings(handler: () -> ())
}
@protocol StringContainer
@property (readonly) NSInteger count;
- (void)addString:(NSString *)string;
- (void)enumerateStrings:(void(^)(NSString *))handler;
@end

Here, we are using the @protocol keyword instead of @interface and it still ends with the @end keyword. We can define any properties or methods that we want. We can then say that a class implements the protocol similar to this:

@interface StringList : NSObject <StringContainer>
@property NSMutableArray *contents;
@end

The list of protocols that a class implements should be listed within angled brackets after the class it inherits from separated by commas. In this case we are only implementing a single protocol so we don't need any commas. This code also declares a contents property, so that we can implement the protocol as shown:

@implementation StringList

- (NSInteger)count {
    return [self.contents count];
}

- (void)addString:(NSString *)string {
    if (self.contents == nil) {
        self.contents = [NSMutableArray new];
    }
    [self.contents addObject:string];
}

- (void)enumerateStrings:(void (^)(NSString *))handler {
    for (NSString *string in self.contents) {
        handler(string);
    }
}

@end

Note that we don't do anything special in the implementation to implement a protocol; we just need to make sure the proper methods and computed properties are implemented.

The other thing you should be aware of is that protocols in Objective-C are not used in the same way as classes. You can't just define a variable to be a protocol; instead, you must give it a type and require that it implement the protocol. Most commonly, this is done with the id type:

    id<StringContainer> container = [StringList new];

Any variable declaration can require that it not only inherits from a specific type, but also implements certain protocols.

Blocks

Lastly, blocks are the Objective-C alternative to closures in Swift. They are actually a late addition to Objective-C so their syntax is somewhat complex:

int (^doubleClosure)(int) = ^(int input){
    return input * 2;
};
doubleClosure(2);

Let's break this down. We start like any other variable with the variable's name and type before the equals sign. The name starts with a carrot symbol (^) inside the first set of parentheses. In this case, we are calling it doubleClosure. The actual type of the closure surrounds that. The type it starts with is the type the closure returns, which in this case is an int. The second set of parentheses lists the types of the parameters the closure accepts. In total, this means that we are defining a closure called doubleClosure that accepts int and returns int.

Then, we move on to the business of implementing the closure. All closure implementations start with a carrot symbol followed by any arguments in parentheses and curly brackets with the actual implementation. Once a closure is defined, it can be called similar to any other function. However, you should always be aware that it is possible for a closure to be nil, in which calling it will cause the program to crash.

It is also possible to define a function or method that accepts a closure as a parameter. First, a function:

id firstInArrayPassingTest(NSArray *array, BOOL(^test)(id)) {
    for (id element in array) {
        if (test(element)) {
            return element;
        }
    }
    return nil;
}
firstInArrayPassingTest(array, ^BOOL(id test) {
   return false;
});

Note that the type id signifies any Objective-C object and even though it doesn't have an asterisk, it is a reference type. The usage above looks exactly like a standalone block usage. However, the syntax looks somewhat different in a method:

- (id)firstInArray:(NSArray *)array
    passingTest:(BOOL(^)(id test))test
{
    for (id element in array) {
        if (test(element)) {
            return element;
        }
    }
    return nil;
}
[self firstInArray:array passingTest:^BOOL(id test) {
    return false;
}];

This is because a method's parameter name is separated by parentheses. This causes the name of the parameter to be moved from being with the carrot to after the parentheses. In the end, we can say that the nuances of the syntax aren't too important when reading Objective-C code and translating to Swift, as long as you recognize that a carrot symbol indicates a block. Many Objective-C programmers look up the syntax of a block on a regular basis.

All of the same memory concerns exist in Objective-C with blocks. By default, all arguments are captured strongly and the syntax to capture them weakly is much more convoluted. Instead of including the weak captures in the block itself, you must create weak variables outside of the block and use them:

@interface Ball : NSObject
@property int xLocation;
@property (strong) void (^onBounce)();
@end
@implementation Ball
@end

Ball *ball = [Ball new];
__weak Ball *weakBall = ball;
ball.onBounce = ^{
    NSLog(@"%d", weakBall.xLocation);
};

Here we use the keyword __weak (that has two underscores) to indicate that the weakBall variable should only have a weak reference to ball. We can then safely reference the weakBall variable within the block and not create a circular reference.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset