Chapter    7

Constructing Classes, Structures, and Enumerations

As alluded to in previous chapters, and revealed fully in this one, Swift breaks new ground with class types, and upgrades structures and enumerations to first-class status. In fact, there is less to say about the differences than similarities between classes and structures in Swift, and enumerations are not far off the mark, either. This chapter will introduce each of these constructs, first at a high level and then followed by deeper analysis of major considerations such as initialization, definition of properties and methods, and selection guidelines.

Naming Conventions

Swift follows the same naming conventions as Objective-C with regard to naming classes, structures, enumerations, properties, and methods. Classes, structures, and enumerations are all formal types, and thus their names should begin with a capital letter and use camel casing. Properties and methods are not types, so, in order to differentiate them from types, their names should begin with a lowercase letter and also use camel casing.

Classes and Structures

Objective-C classes are the workhorses of the language. From defining properties and methods to declaring protocols and calling on delegates, classes can satisfy a wide variety of needs. Objective-C is a superset of C and utilizes basic C structures as an alternative to classes. C structures are lighter-weight, although they are limited to storing scalar values (i.e., they cannot hold references to objects), and they cannot define methods.

Swift balances the workload between classes and structures, and as mentioned in Chapter 2, all of Swift’s basic data types are implemented as structures (except Character, which is implemented as an enumeration). Table 7-1 compares classes and structures in Objective-C and Swift. Attributes are aligned horizontally to aid in cross-referencing.

Table 7-1. Comparison of classes and structures in Objective-C and Swift

 

Objective-C

Swift

Class

Defines initializers

Can define properties

Can define instance variables

Can define static variables

Can implement custom deallocation logic

Defines initializers

Can define stored instance properties

Can define a deinitializer

 

Can use lazy instantiation of properties

Can define instance methods

Can define class methods

Almost always subclasses

Can be subclassed

Can have extensions and categories

Can define lazy stored properties

Can define instance methods

Can define type methods

Can subclass

Can be subclassed

Can have extensions

 

Can conform to protocols

Can be type checked and casted

Can define computed-property-like instance methods

Can define computed-property-like class methods

Can override property accessors

Can conform to protocols

Can be type checked and casted

Can define computed instance properties

Can define computed type properties

Can define property observers

Can define subscripts

 

Can have multiple references

Passed by reference

Can have multiple references

Passed by reference

Structure

Defines members

Defines stored instance properties

 

Can define initializing functions

Passed by copy

Has automatic memberwise initializers

Passed by copy

   

Can define stored type properties

Can define computed instance properties

Can define computed type properties

Can define instance methods

Can define type methods

Can define subscripts

Can have extensions

Can conform to protocols

Although there seems to be general feature parity between Objective-C and Swift classes, it can be quickly deduced just by glancing over Table 7-1 that structures in Swift have many more capabilities. All of these similarities, differences, and new capabilities will be covered in the forthcoming sections.

Classes

There are three standout differences between classes in Objective-C and Swift:

  1. Swift does not require creating separate interface and implementation files
  2. Swift custom classes are not reliant on inheriting from a base class
  3. Access control in Swift is entirely different

This chapter will cover the first two of these variances. Chapter 9 will deal with subclassing-specific topics, and Chapter 10 will analyze access control.

Objective-C’s use of separate interface and implementation files has evolved over time, to the extent that many in the community have questioned the continued necessity of even having separate files. Swift answered that question: no, it’s not necessary. Whereas class source files in Objective-C have a file extension of either .h or .m—for declaration and implementation code, respectively—all production source code files in Swift reside in a .swift file. In the case of Swift playgrounds, a .playground file is used. Deferring the broader topic of access control for now, it is sufficient to understand here that the external interface for each Swift class (or structure) is made available to all other code within the module. Swift recognizes each build target in an Xcode project as a separate module, and modules can be imported. Table 7-2 compares the basic syntax of a class definition in Objective-C and Swift, absent of details for properties and methods, which will be covered later in this chapter. Optional components are italicized, and in order to keep these comparisons straightforward, accoutrements such as constants, macros, and extensions in Objective-C are omitted.

Table 7-2. Basic syntax of class definitions in Objective-C and Swift

Objective-C

// In .h interface file

importStatements

@interface ClassName : ParentClassName <ProtocolName, ...>

publicPropertyDeclarations

publicMethodDeclarations

@end

// In .m implementation file

importStatements

@implementation ClassName <ProtocolName, ...>

{

   instanceVariables

}

privatePropertyDeclarations

methodImplementations

@end

Swift

// In .swift file

importStatements

class ClassName: ParentClassName, ProtocolName, ... {

   propertyDefinitions

   methodDefinitions

}

Notice in the Swift example that defining a parent class is optional. Although it is technically also optional in Objective-C, most every class in Objective-C is a subclass of NSObject, for at least one reason: to inherit the +[NSObject alloc] method. Without that method, a class would have to implement its own memory allocation process and return an instance of the class to then be initialized. Classes in Swift are self-efficient with regard to the whole instantiation, initialization, and deinitialization process, inclusive of memory allocation. Also notice that a class in Swift may adopt one or more protocols, and protocols should be listed after the parent class (if there is one).

Structures

C structures in Objective-C facilitate storing scalar values, aggregates, or other structures as its members, and are passed by copy. Member values are normally retrieved and set using dot notation syntax, and although they can also be instantiated and referenced via structure pointers, this is less common in Objective-C programming where a class is typically used for anything but simple pass-by-copy data structures.

Table 7-3 compares the basic syntax of structure definitions in Objective-C and Swift; properties (and methods in Swift, which are optional) will be covered shortly.

Table 7-3. Basic syntax of structure definitions in Objective-C and Swift

Objective-C

typedef struct {

   memberDeclarations

} StructName;

Swift

struct StructName {

   propertyDefinitions

   methodDefinitions

}

Structures in Swift have been elevated to be nearly as capable as classes, with a few distinguishing differences that also help with determining whether to choose a class or structure for a particular need (selection guidelines are discussed later in this chapter in the section Selection Guidelines). The most notable differences between Swift structures and classes are:

  • Classes can inherit from other classes. Structures cannot inherit from other structures (although they can conform to protocols)
  • Structures have an automatically-generated memberwise initializer (covered later in this chapter); classes do not
  • Structures can define both computed and stored type properties; classes can only define computed type properties
  • Structures are value types, which are passed by copy; classes are reference types, which are passed by reference, and thus a single class instance can have multiple references
  • Classes can be type checked and casted; because structures cannot inherit, they also cannot be type casted (and therefore, type checked)
  • Classes can implement deinitializers to free up resources; structures cannot

Enumerations

Enumerations represent another chasm between Objective-C and Swift. Enumerations enable grouping related values together as a specific type of value. An enumeration can be iterated over (such as in a switch statement), and enumeration members can be passed as parameters to a function. Additionally, member names receive the benefit of code-completion, thus eliminating typo-related bugs that can be common when using raw strings. Enumerations are also commonly used with bitwise shift operators to combine multiple members into a single bitmask value, for example, for use in setting options in a method.

Objective-C utilizes straight C enumerations, adding helpful macros such as NS_ENUM and NS_OPTIONS that aid in defining new typedef’d enumerations. Members are named and represent differing, usually incremental, integer values (bitmasks in the case of NS_OPTIONS). Swift takes enumerations to a whole new level. One of the biggest differences is that Swift enumerations do not automatically assign a default integer value; member values are, by default, fully-fledged values of the enumeration type. Table 7-4 compares enumerations between Objective-C and Swift. Attributes are aligned horizontally to aid in cross-referencing.

Table 7-4. Comparison of enumerations in Objective-C and Swift

Objective-C

Swift

Defines members via comma-separated list

Defines members via case statements

Can define multiple members in single case statement (comma-separated list)

Members defined with default integer value

Members are not automatically assigned a raw value

Member default values are fully-fledged values of the enumeration type

Member raw values can be a string, character, integer, or floating-point number

Member values auto-increment if not explicitly assigned an integer value

If a raw integer value is assigned, subsequent member values auto-increment if not explicitly assigned an integer value

Can define associated member values of any type and/or multiple types

Can define initializers

Can define computed instance properties

Can define computed type properties

Can define stored type properties

Can define instance methods

Can define type methods

Can define subscripts

Can have extensions

Can conform to protocols

Passed by copy

Table 7-5 compares the basic syntax of an enumeration definition in Objective-C and Swift; optional components are italicized.

Table 7-5. Basic syntax of enumeration definitions in Objective-C and Swift

Objective-C

typedef NS_ENUM(NSInteger, EnumName) {
   value1 = 1,
   value2,
   ...
};

Swift

enum Enum1Name {
   case value1, value2, ...
   init() { ... }
}
enum Enum2Name: Int {
   case value1 = 1
   case value2
   ...
   init() { ... }
}

Two versions of a Swift enumeration are included in Table 7-5, as individual enum members in Swift can be specified in individual case statements or in a single, comma-separated listed in one case statement.

Initialization

As already noted, Objective-C uses C-based structures and enumerations that do not have formal initializers (although initializer functions or factory methods could be created to vend initialized instances). Objective-C classes, along with Swift classes, structures, and enumerations, all define or inherit initializers that prepare an instance for use. The process of instantiation—which includes allocating the memory for an instance and then initializing it—is similar in outcome but significantly different in implementation between the two languages.

Class Initialization

Objective-C classes can define one or more initializers, typically with one designated initializer, that can optionally set default property values and perform other initialization tasks. Instantiating an Objective-C class instance involves first calling +[<NSObject subclass> alloc] to obtain an instance of the receiving class (memory allocated), and then passing that instance to an -[<NSObject subclass> init] method to complete initialization, or by calling a convenience class method such as +[<NSObject subclass> new] (which in turn simply calls [[<NSObject subclass> alloc] init]), or by calling any number of additional convenience constructors that may be available to receive a fully set up object back from a single method call that abstracts the alloc/init calls. This is an oversimplification of an elaborate process that has evolved over many years (Automatic Reference Counting, a.k.a. ARC, is one of the most profound improvements). Yet a comparison can now be made with how Swift handles class instantiation. Swift, also using ARC, handles all memory-management responsibilities associated with instantiating a class instance.

Swift classes must ensure that all nonoptional property values are set on initialization, and this is carried out by either setting default values in property declarations and/or in an initializer. Unlike Objective-C initializers, which return an initialized instance of the class, a Swift initializer’s sole responsibility is to ensure that a new instance is properly set up before use.

Tip  A general best practice is to prefer setting a property’s initial value in its declaration versus setting it in an initializer, whenever possible. This makes for more concise code, streamlines initializers, and benefits initializer inheritance (Chapter 9 will cover initializer inheritance in detail).

For base classes (i.e., that do not inherit from another class) wherein all nonoptional properties are assigned a default value during declaration, Swift automatically provides a default initializer that will set all the properties to their default values.

Initialization in Objective-C is typically carried out by assigning the response of a call to –[super init] (or a variation) to self, followed by (optionally) assigning initial values directly to the backing instance variables, and performing any other initialization tasks.

Swift defines two kinds of initializers: designated and convenience. Designated initializers are primarily responsible for ensuring that all of the class’ properties are initialized. Every class must have at least one designated initializer, which can be inherited from a superclass (i.e., if the class has a superclass). Additionally, in the case in which there is a parent class, a designated initializer must also call a designated initializer in the immediate superclass; see Chapter 9 for details. Convenience intitializers are just that, initializers that conveniently abstract some of the work associated with initializing an instance of the class. They are optional, and marked with a convenience modifier before the init keyword in the function definition. A convenience initializer may call another convenience initializer or a designated initializer, for example, self.init(parameterName: parameterValue). However, all convenience initializers must eventually point to a designated initializer. Apple’s Swift language guide summarizes how designated and convenience initializers should be chained together, “Designated initializers must always delegate up. Convenience initializers must always delegate across.”

Initialization in Swift is a two-phase process that is similar to initialization in Objective-C, except that Swift allows setting custom initial values in phase 1 versus in Objective-C, every property is initially assigned 0 or nil. In phase 1, memory is allocated, all nonoptional properties of the class are assigned an initial value, and the superclass (if one exists) is given the opportunity to assign values to all its nonoptional values (this repeats all the way up the inheritance chain). Essentially, phase 1 establishes self, which can then be accessed in phase 2 in order to further customize the instance. Table 7-6 provides examples of a basic class being defined and instantiated in Objective-C and Swift.

Table 7-6. Comparing definition and instantiation of a class in Objective-C and Swift

Objective-C

// In MyCustomClass.h
@import Foundation;
static NSString *defaultTitle = @"A Worthy Title";
@interface MyCustomClass : NSObject
@property (copy, nonatomic) NSString *title;
+ (instancetype)instanceWithDefaultTitle;
- (instancetype)initWithTitle:(NSString *)title;
@end
// In MyCustomClass.m
#import "MyCustomClass.h"
@implementation MyCustomClass
+ (instancetype)instanceWithDefaultTitle
{
   return [[MyCustomClass alloc] initWithTitle:nil];
}
- (instancetype)initWithTitle:(NSString *)title
{
   if (self = [super init]) {
      _title = title ?: defaultTitle;
   }
   return self;
}
@end
// In –[SomeOtherClass someMethod] in SomeOtherClass.m
MyCustomClass *myCustomClass1 = [MyCustomClass instanceWithDefaultTitle]; // myCustomClass1.title = "A Worthy Title"
MyCustomClass *myCustomClass2 = [[MyCustomClass alloc] initWithTitle:@"A Great Title"]; // myCustomClass2.title = "A Great Title"

Swift

// In .swift file
class MyCustomClass {
class var defaultTitle: String {
   return "A Worthy Title"
}
var title: String!
init(title: String) {
   self.title = title
}
convenience init() {
   self.init(title: MyCustomClass.defaultTitle)
  }
}
let myCustomClass1 = MyCustomClass() // myCustomClass1.title = "A Worthy Title"
let myCustomClass2 = MyCustomClass(title: "A Great Title") // myCustomClass2.title = "A Great Title"

Notice in the Swift example in Table 7-6 that the property title is of type String!. Remember from Chapter 2 that an ! can be used to implicitly unwrap an optional during declaration. In this example, the instances’ title property is set during instantiation, however it can be subsequently set to nil, because it is an optional value. Observe also that the init() method automatically included the external title parameter name, even though it was not explicitly stated. As pointed out in the last chapter, Swift methods (functions defined within a type), automatically provide an external name for the second and subsequent parameters of a method. However, for init() methods, an external name is automatically provided for all parameters, including the first one.

Tip  To prevent an external name from automatically being created for an initializer parameter, write an underscore (_) before the parameter name, where an explicit external parameter name would normally be stated.

The use of self in the init() method was necessary to disambiguate the parameter from the property. Had the parameter been named aTitle, for example, the line setting the title property could have omitted using self, that is, title = aTitle. Also note that a computed type property was used, because, as of this writing, Swift does not support stored class properties; attempting to create one generates the compiler error: “class variables not yet supported.”

Structure Initialization

As in Swift classes, Swift structure initializers must ensure that all nonoptional properties are set to an initial value, unless a property is set to a default value in its declaration.

When all nonoptional properties are assigned a default value in their declaration, a default initializer is automatically provided to set those initial values. Yet even when a structure does not set all of its nonoptional properties, if no custom initializer is defined, a memberwise initializer is automatically created, which essentially results in each property receiving an external name. That said, if a custom initializer is defined, neither a default initializer nor memberwise initializer will be available.

Tip  To regain access to the default initializer for a structure that declares one or more custom initializers but also sets default values for all nonoptional properties, simply define an empty initializer:

init() {}

Table 7-7 demonstrates the definition, instantiation, and usage of a structure in Objective-C and Swift.

Table 7-7. Comparing definition, instantiation, and usage of a structure in Objective-C and Swift

Objective-C

typedef struct {
char *date;
char *message;
NSInteger value;
} MyStruct;
MyStruct myStruct1 = { "9/9/14", "Hello iPhone", 6 };
MyStruct myStruct2 = { .message = "goodbye iPhone", .value = 5 };
NSLog(@"%s: %s %li, %s %li", myStruct1.date, myStruct1.message, (long)myStruct1.value, myStruct2.message, (long)myStruct2.value); // Prints "9/9/14: Hello iPhone 6, goodbye iPhone 5"

Swift

struct MyStruct {
  static let date = "9/9/14"
  var message = "Hello iPhone"
  var value = 6
  init() {}
  init(message: String, value: Int) {
    self.message = message
    self.value = value
   }
}
let myStruct1 = MyStruct()
var myStruct2 = MyStruct(message: "goodbye iPhone", value: 5)
println("(MyStruct.date): (myStruct1.message) (myStruct1.value), (myStruct2.message) (myStruct2.value)") // Prints "9/9/14: Hello iPhone 6, goodbye iPhone 5"

Enumeration Initilialization

Enumerations are comparatively simple to define and instantiate in both Objective-C and Swift, as Table 7-8 demonstrates.

Table 7-8. Comparing definition and instantiation of an enumeration in Objective-C and Swift

Objective-C

typedef NS_ENUM(NSInteger, MyEnum) {
  value1, value2, value3
};
MyEnum myEnum = value1;
NSLog(@"%i", myEnum); // Prints 0
typedef NS_OPTIONS(NSInteger, MyOptionsEnum) {
  v1 = 1 << 0,
  v2 = 1 << 1,
  v3 = 1 << 2
};
MyOptionsEnum myOptionsEnum = v3;
NSLog(@"%i", myOptionsEnum); // Prints 4

Swift

enum MyEnum1 {
  case value1, value2, value3
}
var myEnum1 = MyEnum1.value1
println(_stdlib_getDemangledTypeName(myEnum1)) // Prints "...MyEnum1"
myEnum1 = .value2
println(_stdlib_getDemangledTypeName(myEnum1)) // Prints "...MyEnum1"
enum MyEnum2: Int {
  case value1 = 1, value2, value3
  init() {
    self = value3
  }
}
var myEnum2 = MyEnum2()
println(myEnum2.rawValue) // Prints "3"
myEnum2 = .value1
println(myEnum2.rawValue) // Prints "1"

In the Swift examples, notice that MyEnum1 does not define a type, referred to as the raw value type for enumerations, whereas MyEnum2 defines that it has a raw value of type Int. As pointed out in Table 7-4, MyEnum1 values are of type MyEnum1. Swift enumeration member raw values must be literal, which means it is not possible to use bitwise shifting to assign values (as in the NS_OPTIONS example). The raw value of a Swift enumeration that defines a raw value can be accessed via the read-only rawValue property. Also notice that enumeration values can be assigned using short dot syntax (instead of explicitly stating the type, e.g., MyEnum.value1), because the type can already be inferred.

Failable Initializers

Under certain circumstances, such as when a required external resource is not available during initialization or an invalid parameter value is passed to an initializer, initialization may fail. One or more failable initializers can be defined for a class, structure, or enumeration, in order to handle the possibility that initialization may not succeed. As previously mentioned, a Swift initializer does not return an instance of the type but, rather, it ensures that all nonoptional property values are set. A failable initializer, however, will either succeed and result in the creation of a fully-initialized optional value of the type, or it will fail and must explicitly return nil.

To define an initializer as failable, write a ? immediately after the init keyword. As with other uses of optional values, an instance initialized with a failable initializer must be unwrapped before using. That said, a failable initializer may also be defined such that it will result in the creation of an implicitly unwrapped optional instance. To define an implicitly unwrapped failable initializer, write an ! immediately after the method name.

Note  Failable or not, an initializer cannot be defined with the same parameter names and types as another existing initializer for the same type.

The Swift standard library automatically defines the failable initializer init?(rawValue:) for enumerations defined as having a raw value. Table 7-9 provides examples of failable initializers for classes, structures, and enumerations.

Table 7-9. Examples of class, structure, and enumeration failable initializers in Swift

Class

class Person {
  let firstName: String!
  let lastName: String!
init?(firstName: String, lastName: String) {
  if firstName.isEmpty || lastName.isEmpty {
    return nil
  }
  self.firstName = firstName
  self.lastName = lastName
  }
}
let noOne = Person(firstName: "", lastName: "")
println(noOne) // Prints "nil"
let vivian = Person(firstName: "Vivian", lastName: "Gardner")
println("(vivian!.firstName) (vivian!.lastName)") // Prints "Vivian Gardner"

Structure

struct Polygon {
let sides: Int!
init!() {
    sides = 5
}
init?(sides: Int) {
    if sides < 3 { return nil }
    self.sides = sides
  }
}
let pentagon = Polygon()
println(pentagon.sides) // Prints "5"
let hectogon = Polygon(sides: 100)
if let sides = hectogon?.sides {
  println(sides)
}
// Prints "100"

Enumeration

import Foundation
enum AirPressure: String {
    case Hectopascals = "inHg"
    case Millibars = "mb"
    var value: Double! {
    // API call that gets current air pressure in hectopascals, e.g., 30 inHg
    let currentAirPressure = 30.0
    switch self {
    case .Hectopascals:
   return currentAirPressure
case .Millibars:
   return currentAirPressure * 33.8637526
  }
}
var reading: String {
   let roundedValue = String(format: "%.1f", value)
   return "(roundedValue) (self.rawValue)"
  }
init?(_ unit: String) {
   if unit.isEmpty { return nil }
   if let airPressure = AirPressure(rawValue: unit) {
   self = airPressure
} else {
   return nil
  }
}
}
var airPressure = AirPressure("mb")
println(airPressure!.reading) // Prints "1015.9 mb"
airPressure = .Hectopascals
println(airPressure!.reading) // Prints "30.0 inHg"

Properties

Both Objective-C and Swift classes can store values as properties, yet Swift structures and enumerations can also have properties. Table 7-10 itemizes the kinds of properties each Swift construct can define.

Table 7-10. Itemizing available property types for classes, structures, and enumerations in Swift

Table7-10

Stored type properties are marked with the static keyword in the definition. Computed type properties are marked with the class keyword for classes, and static keyword for structures and enumerations.

Properties in Objective-C are typically defined with explicit memory retention and atomicity rules, whereas in Swift, all value types (i.e., structures and enumerations) are copy, and all reference types (e.g., classes) are strong. There is one exception to this rule, which will be covered in the section Declaration and Type Attributes. Of atomicity, Apple states to use, “...dispatch_once to make sure that the initialization is atomic.,” (https://developer.apple.com/swift/blog/?id=7). An example of when this exception would be necessary will be demonstrated in the section Singletons.

Swift properties are regular stored or computed values of a class, structure, or enumeration, declared as variables or constants within the class, structure, or enumeration. Although Objective-C properties are automatically synthesized with backing instance variables, no such construct exists in Swift. The backing store of a Swift property is not directly accessible, and access to a property’s value is facilitated via the getter and setter of that property. A Swift property with only a getter is read-only, just like an Objective-C property with the readonly property attribute.

Stored type properties in Swift can be set (if declared as a variable) and retrieved on a structure or enumeration type itself, just like instance properties. This is similar to static variables (mutable) and constants (immutable) in Objective-C, except that static variables and constants are accessed directly instead of as properties of the class using dot notation, as they are in Swift.

Note  Stored type properties must always be given an initial value, since there is no initializer to set them.

A property can be lazily instantiated in Objective-C by overriding the getter and working directly with the backing instance variable for the property. Swift has lazy stored properties (variable stored properties marked with the lazy modifier) that achieve the same goal of delaying calculation of a property’s initial value until the first time it is retrieved.

Note  Constant properties in Swift must be set during declaration or in an initializer and cannot be marked as lazy.

Swift computed properties can be retrieved and set (if variable) like a regular stored property, but return a computed value or process a set value (such as to indirectly set other properties), respectively. A computed property can optionally have only a getter and setter, and when it has only a getter, the get keyword can be omitted. A computed setter can specify a parameter name for the new value, enclosed in parentheses after the set keyword. If no parameter name is specified and the parentheses are omitted, a default newValue parameter name is automatically created and made available for use.

A Swift property can be initialized using a closure, which is similar to using a block within a custom getter in Objective-C. Keep in mind that, during initialization, the instance has not yet been fully initialized, so other properties should not be accessed in the closure, nor should self be accessed or any other methods called. In order to execute the closure immediately, write () immediately following the closing curly brace of the closure.

Changes to a Swift property can also be observed via a property observer. Property observers respond to changes in a property’s value by calling the willSet and didSet and passing along the new or old value, accordingly. Similar to computed properties, if a parameter name is not specified in parentheses following the implementation of a willSet or didSet method, a parameter name of newValue or oldValue, respectively, is automatically created.

Note  willSet and didSet are not called during initialization, nor are they called if the property is changed within either method. However, they are called even if the property is set to a new value that is equal to the current value.

In Objective-C, public methods can be used to achieve an effect similar to Swift computed properties, and public property accessors can be overridden to provide Swift’s property-observer-like handling of changes to a property’s value.

Table 7-11 provides examples of each of the aforementioned property types in Objective-C and Swift. To keep the comparison clear-cut, the Objective-C computed instance example does not include type checking, which would be necessary for production code. An example of using a block or closure to initialize a property will be included in Chapter 9.

Table 7-11. Examples of property definitions in Objective-C and Swift

 

Objective-C

Swift

Stored instance

@property (copy, nonatomic) NSString *title;

var title: String!

Computed instance

// In HttpStatusChecker.h
@import Foundation;
@interface HttpStatusChecker : NSObject
+ (instancetype)checkerWithHttpStatus:(NSArray *)httpStatus;
- (NSArray *)status;
- (void)setStatus:(NSArray *)httpStatus;
@end
// In HttpStatusChecker.m
#import "HttpStatusChecker.h"
@interface HttpStatusChecker ()
@property (assign, nonatomic) NSInteger code;
@property (copy, nonatomic) NSString *message;
@end
@implementation HttpStatusChecker
+ (instancetype)checkerWithHttpStatus:(NSArray *)httpStatus
{
 HttpStatusChecker *status = [HttpStatusChecker new];
  [status setStatus:httpStatus];
  return status;
}
- (instancetype)init
{
  if (self = [super init]) {
    _code = 200;
    _message = @"OK";
  }
  return self;
}
- (NSArray *)status
{
  return @[@(self.code), self.message];
}
- (void)setStatus:(NSArray *)httpStatus
{
  self.code = [(NSNumber *)httpStatus[0] integerValue];
  self.message = httpStatus[1];
}
@end
// In –[SomeOtherClass someMethod] in SomeOtherClass.m
HttpStatusChecker *httpStatusChecker = [HttpStatusChecker checkerWithHttpStatus:@[@200, @"OK"]];
NSLog(@"code: %@, status: %@", httpStatusChecker.status[0], httpStatusChecker.status[1]); // Prints "code: 200, status: OK"
struct HttpStatusChecker {
  var code: Int!
  var message: String!
  var status: (Int, String) {
    get {
        return (code, message)
    }
    set {
        code = newValue.0
        message = newValue.1
    }
  }
  init(status: (Int, String)) {
    code = status.0
    message = status.1
  }
}
var httpStatusChecker = HttpStatusChecker(status: (200, "OK"))
println("code: (httpStatusChecker.status.0), status: (httpStatusChecker.status.1)") // Prints "code: 200, status: OK"

Stored class/type

static NSString *defaultTitle = @"A Worthy Title";

// In structure or enumeration
static var defaultTitle = @"A Worthy Title"

Computed class/type

// In DateChecker.h
@import Foundation;
@interface DateChecker : NSObject
+ (NSString *)dateString;
@end
// In DateChecker.m
#import "DateChecker.h"
@implementation DateChecker
+ (NSString *)dateString
{
    return [NSDateFormatter localizedStringFromDate:[NSDate date] dateStyle:NSDateFormatterShortStyle timeStyle:NSDateFormatterNoStyle];
}
@end
// In –[SomeOtherClass someMethod] in SomeOtherClass.m
NSLog(@"%@", [DateChecker dateString]); // Prints current date, e.g., "9/14/14"
class DateChecker {
  class var dateString: String {
 return NSDateFormatter.localizedStringFromDate(NSDate(), dateStyle: .ShortStyle, timeStyle: .NoStyle)
  }
}
println(DateChecker.dateString) // Prints current date, e.g., "9/14/14"

Observer

// In TitleTracker.h
@import Foundation;
@interface TitleTracker : NSObject
@property (copy, nonatomic) NSString *title;
@property (assign, nonatomic) BOOL isBeingInstantiated;
+ (instancetype)trackerWithTitle:(NSString *)title;
@end
// In TitleTracker.m
#import "TitleTracker.h"
@interface TitleTracker ()
@property (copy, nonatomic) NSString *oldTitle;
@end
@implementation TitleTracker
+ (instancetype)trackerWithTitle:(NSString *)title
{
    TitleTracker *tracker = [TitleTracker new];
    tracker.isBeingInstantiated = YES;
    tracker.title = title;
    tracker.isBeingInstantiated = NO;
    return tracker;
}
class TitleTracker {
  var title: String! {
      willSet {
          print("'(newValue)' (fka '(title)'). ")
      }
      didSet {
          println("Farewell to '(oldValue).'")
      }
  }
  init(title: String) {
    self.title = title
  }
}
let myTitleTracker = TitleTracker(title: "A Worthy Title")
myTitleTracker.title = "A Better Title" // Prints "'A Better Title' (fka 'A Worthy Title'). Farewell to 'A Worthy Title.'"
- (void)setTitle:(NSString *)title
{
    if (!self.isBeingInstantiated) {
        const char* oldTitle = [self.oldTitle UTF8String];
        printf("'%s' (fka '%s'). Farewell to '%s.' ", [title UTF8String], oldTitle, oldTitle);
        title = title;
    }
    self.oldTitle = title;
}
@end
// In –[SomeOtherClass someMethod] in SomeOtherClass.m
TitleTracker *myTitleTracker = [TitleTracker trackerWithTitle:@"A Worthy Title"];
myTitleTracker.title = @"A Better Title"; // Prints "'A Better Title' (fka 'A Worthy Title'). Farewell to 'A Worthy Title.'"
 

Enumeration Associated Values

Swift enumerations can also associate passed-in values to its members. Each enumeration case can be assigned the passed in value, and value types can be of any type and even different between cases. Associated values can also be extracted in a switch statement using value binding:

enum BookID {
  case isbn10(Int)
  case isbn13(String)
  case oclc(String)
  case lccn(String)
  case olid(String)
  var title: String! {
    var t: String!
    switch self {
      // Simulating setting t via API call or query
    case let .isbn10(id):
      t = "Transitioning to Swift"
    case let .isbn13(id):
      t = "Transitioning to Swift"
    case let .oclc(id):
      t = "Transitioning to Swift"
    case let .lccn(id):
      t = "Transitioning to Swift"
    case let .olid(id):
      t = "Transitioning to Swift"
    }
    return t
  }
}
let bookId = BookID.isbn10(1484204077)
println(bookId.title) // Prints "Transitioning to Swift"

Subscripts

Objective-C classes can add array- or dictionary-like subscripting by overriding –objectAtIndexedSubscript: and/or -setObject:forKeyedSubscript:, taking a key (and object) to get (or set) an object, respectively. In Swift, classes, structures, and enumerations can all define subscripts that can take multiple input parameters and/or a variadic parameter, be multidimensional (same as collection types), be read-write or read-only (using getters and setters, just like computed properties), return any value type (including multiple values in a tuple), and can even be overloaded. They cannot, however, use inout parameters to modify pass-by-reference parameters, nor can they define default parameter values:

class Introspector {
  var values: Any!
  subscript(theValues: Int...) -> (sum: Int, average: Double) {
    values = theValues
      var sum = 0
      for integer in theValues {
        sum += integer
      }
      let average = Double(sum) / Double(theValues.count)
      return (sum: sum, average: average)
  }
  subscript(theValues: Double...) -> (sum: Double, average: Double) {
    values = theValues
      var sum = 0.0
      for value in theValues {
        sum += Double(value)
      }
      let average = sum / Double(theValues.count)
      return (sum: sum, average: average)
  }
}
let myIntrospector = Introspector()
let result1 = myIntrospector[1, 2, 3, 4, 5]
println("For (myIntrospector.values), sum is (result1.sum) and average is (result1.average).") // Prints "For [1, 2, 3, 4, 5], sum is 15 and average is 3.0."
let result2 = myIntrospector[1.1, 2.2, 3.3, 4.4, 5.5]
println("For (myIntrospector.values), sum is (result2.sum) and average is (result2.average).") // Prints "For [1.1, 2.2, 3.3, 4.4, 5.5], sum is 16.5 and average is 3.3."

Methods

In Objective-C, only classes can define methods, which can either be instance or class methods. In Swift, classes, structures, and enumeration can all define methods, including both instance and type methods. Swift methods are simply functions that are associated to a specific type. For example, initializers are instance methods (in both Objective-C and Swift). Remember from the last chapter that parameters of functions (and thus methods) are constants by default; explicitly declare a parameter as a variable by using the var prefix. Objective-C methods are called on instances and classes using bracket notation. Swift methods use the same dot syntax to call methods as to access properties.

Write the keyword static before the method name for structure and enumeration type methods, and write the class keyword for class type methods. As mentioned previously, Swift methods automatically provide an external name for the second and subsequent parameters when calling the method. Figure 7-1 compares the same function as a global function and as a method within a class.

9781484204078_Fig07-01.jpg

Figure 7-1. Comparing a global function to method in Swift

Presumably, the intention of this additional feature of methods over regular functions is to maintain the style of Objective-C method inline parameter names and general self-documenting readability. Although the examples in Figure 7-1 demonstrate this difference, good form would actually be to provide an explicit external name for the second parameter, as demonstrated in Table 7-13.

When calling an instance method within another instance method within that same type, it is not necessary to explicitly state self. The same goes for calling a type method within another type method of the same type:

struct Incrementor {
  static var value = 0
  static func increment(_ increment: Int = 1) {
    value++
    println(value)
  }
  init() {
    printCurrentValue()
  }
  func printCurrentValue() {
    println("Current value: (Incrementor.value)")
  }
  func topLevelFunc() {
    func nestedFunc() {
      printCurrentValue()
    }
    nestedFunc()
  }
}
Incrementor.increment()   // Prints "1"
Incrementor.increment()   // Prints "2"
var typer = Incrementor() // Prints "Current value: 2"
typer.topLevelFunc()      // Prints "Current value: 2"

Notice in this example that it is not necessary to specify Incrementor for value in increment(), nor is it necessary to specify self for printCurrentValue() in init(), or in the nested function nestedFunction(), yet it is necessary to specify Incrementor.value in printCurrentValue(), because that is an instance function accessing a type property.

By default, instance properties of value types cannot be changed from within their own instance methods. Because enumerations cannot have instance properties, this rule applies only to structures. To opt in to the mutating behavior of an instance method, such that it can change instance property values and write back those changes when the method ends, write the mutating keyword before the func keyword in the definition. A mutating method in Swift can even instantiate a completely new instance of that type and assign it to self, which is not possible in Objective-C. Class instance properties, as well as type properties of value types (i.e., structures and enumerations) can be mutated within the type’s instance methods.

Table 7-12 provides examples of Objective-C methods and Swift functions, specific to the unique features of methods over functions as described in this section; the Objective-C method calls are being made within –[SomeOtherClass someMethod] in SomeOtherClass.m. Refer to the last chapter for more detailed coverage of Swift functions in general.

Table 7-12. Comparing unique features of Swift methods over functions to Objective-C methods

 

Objective-C

Swift

 
// In SalutationPrinter.h
@import Foundation;
@interface SalutationPrinter : NSObject
+ (void)print:(NSString *)salutation toRecipient:(NSString *)recipient;
- (void)print:(NSString *)salutation toRecipient:(NSString *)recipient;
@end
// In SalutationPrinter.m
#import "SalutationPrinter.h"
@implementation SalutationPrinter
+ (void)print:(NSString *)salutation toRecipient:(NSString *)recipient
{
   NSLog(@"%@ %@", salutation, recipient);
}
- (void)print:(NSString *)salutation toRecipient:(NSString *)recipient
{
   NSLog(@"%@ %@", salutation, recipient);
}
@end
class SalutationPrinter {
   class func print(salutation: String, toRecipient recipient: String) {
     println("(salutation) (recipient)")
   }
   func print(salutation: String, toRecipient recipient: String) {
     println("(salutation) (recipient)")
   }
}
struct Counter {
   var total = 0
   var reset: Bool = false
   mutating func increment() {
     if reset {
       self = Counter()
     }
     total++
     println(total)
   }
}

Instance method

SalutationPrinter *salutationPrinter = [SalutationPrinter new];
[salutationPrinter print:@"Hello" toRecipient:@"Henry"]; // Prints "Hello Henry"
let salutationPrinter = SalutationPrinter()
salutationPrinter.print("Hello", toRecipient: "Henry") // Prints "Hello Henry"

Class/type method

[SalutationPrinter print:@"Hello" toRecipient:@"Henry"]; // Prints "Hello Henry"

SalutationPrinter.print("Hello", toRecipient: "Henry") // Prints "Hello Henry"

Mutating instance method

N/A

var counter = Counter()
counter.increment() // Prints "1"
counter.increment() // Prints "2"
counter.reset = true
counter.increment() // Prints "1"

Declaration and Type Attributes

Swift classes can utilize declaration attributes just like stored values and functions to provide additional information:

@availability(iOS, obsoleted=1.0, message="Use NewIOSClass instead")
class OldIOSClass { /* ... */ }
@availability(iOS, introduced=0.8)
class NewIOSClass { /* ... */ }
// Originally implemented in release 0.1
@availability(*, introduced=0.1)enum MyStruct { /* ... */ }
// Renamed in release 1.0
@availability(*, introduced=1.0, renamed="MyRenamedStruct") enum MyRenamedStruct { /* ... */ }
typealias MyStruct = MyRenamedStruct

In these examples, OldIOSClass is an iOS-only class that was obsoleted in 1.0, and its usage will be prevented by a compiler error; it was replaced by NewIOSClass in 0.8 (and, presumably, from 0.8 to 1.0, OldIOSClass was marked deprecated). MyStruct was renamed in 1.0 to MyRenamedStruct, and a typealias is used to ensure existing code will still work with the old name.

As previously stated, reference types properties in Swift have strong memory retention by default. However, reference type properties marked with the declaration attribute @NSCopying will have their setter synthesized with a copy of the passed in value, similar to how the copy property attribute works in Objective-C. That property’s type must also conform to the NSCopying protocol; protocols are covered in the next chapter.

Although the topic of mixing Objective-C and Swift source files in the same project is beyond the scope of this book, there is one declaration attribute use case specific to classes that should be mentioned here. The @objc attribute can be used to mark a class as available to Objective-C source code within the same module. An Xcode-generated header must also be imported within an Objective-C source code file in order to expose the Swift source code files to it. The generated header file has the name ProductModuleName-Swift.h, where ProductModuleName is the name of the target. When exposing a Swift class to Objective-C code, a different name can be provided for use by the Objective-C code by placing that name in parentheses immediately after the @objc attribute. And for classes marked with the @objc attribute, individual entities within that class can have different names exposed to Objective-C by marking those entities with the @objc attribute.

Note  Swift classes that are intended to be made available to Objective-C code should subclass NSObject in order to inherit +[NSObject alloc].

// In SwiftClass.swift
import Foundation
@objc class MySwiftCustomClass: NSObject {
  var enabled: Bool
  init(enabled: Bool) {
    self.enabled = enabled
    super.init()
  }
}

// In SomeOtherClass.m
#import "Transitioning-Swift.h"
@implementation SomeOtherClass
- (void)someMethod
{
  MySwiftCustomClass *swiftClassInstance = [[MySwiftCustomClass alloc] initWithEnabled:NO];
}

Note  Apple provides a useful guide for mixing Swift and Objective-C code in the same project, which includes listing Swift-only features that are not accessible within a class or protocol marked with the @objc attribute: Using Swift with Cocoa and Objective-C (http://bit.ly/mixswiftandobjc).

See Chapter 2 and the last chapter for a list of additional declaration attributes and examples. Swift classes, structures, and enumerations can use autoclosure and noreturn type attributes in their method declarations in the same manner as described in the last chapter for using type declarations with functions.

Class Deinitialization

Deinitialization in Swift applies only to class types, and is similar to deallocation in Objective-C, except that deinitialization is what happens right before a Swift class get deallocated. Because of this, a deinitializer in Swift has full access to its instance, including all its properties. A class can have at most only a single deinitializer, which is defined as a deinit method that takes no parameters and, uniquely, is written without parentheses. deinit is automatically called right before deallocation and should not be called directly. Although the topic of subclasses is deferred until Chapter 9, it should be mentioned here that a subclass inherits its superclass’ deinitializer, and the superclass’ deinitializer is automatically called at the end of the subclass’ deinitialization, even if an explicit deinit method was not defined for the class. Swift automatically deallocates an instance when it is no longer referenced (via ARC), so explicitly defining deinit is not generally necessary unless some manual process or cleanup must be performed right before deallocation. Table 7-13 compares a common use case for –[NSObject dealloc] in Objective-C and deinit in Swift.

Table 7-13. Comparing use of –[NSObject dealloc] in Objective-C and deinit Swift

Objective-C

// In KeyboardDisplayHandler.h
@import UIKit;
@interface KeyboardDisplayHandler : NSObject
@end
// In KeyboardDisplayHandler.m
#import "KeyboardDisplayHandler.h"
@implementation KeyboardDisplayHandler
- (instancetype)init
{
  if (self = [super init]) {
    NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter];
    [notificationCenter addObserver:self selector:@selector(keyboardWillShow) name:UIKeyboardWillShowNotification object:nil];
    [notificationCenter addObserver:self selector:@selector(keyboardWillHide) name:UIKeyboardWillHideNotification object:nil];
    }
  return self;
}
- (void)keyboardWillShow
{
  // ...
}
- (void)keyboardWillHide
{
  // ...
}
@end

Swift

class KeyboardDisplayHandler {
  init() {
    NSNotificationCenter.defaultCenter().addObserver(self, selector: "keyboardWillShow", name: UIKeyboardWillShowNotification, object: nil)
    NSNotificationCenter.defaultCenter().addObserver(self, selector: "keyboardWillHide", name: UIKeyboardWillHideNotification, object: nil)
  }
  func keyboardWillShow() {
    // ...
  }
  func keyboardWillHide() {
    // ...
  }
  deinit {
    NSNotificationCenter.defaultCenter().removeObserver(self)
  }
}

Avoiding Strong Reference Cycles

Because Swift classes are reference types, and thus a single class instance can have multiple (default strong) references to it, a situation can occur where two class instances can hold a strong reference to each other, such that neither will ever be deallocated. This is a strong reference cycle, a familiar situation to Objective-C developers. And the approach to avoid a strong reference cycle between classes in Swift is similar to the approach taken in Objective-C.

In Swift, a weak reference must be declared as optional variables, as their value must be able to be set to nil during runtime. By contrast, an unowned reference in Swift always will have a value and cannot be set to nil.

Tip  If a class will always have a reference to another class, mark the property that points to an instance of that other class as unowned. Otherwise, if the class may not always have a reference to another class, mark the property as weak.

Table 7-14 provides comparative examples of avoiding strong reference cycles in Objective-C and Swift.

Tip  Swift playgrounds do not consistently call deinit. As such, while the println() statements in the example code in Tables 7-14 and 7-15 would print out if run in an Xcode project, they may not print out when run in a playground.

Table 7-14. Avoiding strong reference cycles in Objective-C and Swift

Objective-C

// In MyCustomClass.h
@import Foundation;
@class ParkingSpaceAssignment;
@interface Employee : NSObject
@property (strong, nonatomic) ParkingSpaceAssignment *parkingSpaceAssignment;
@end
@interface ParkingSpace : NSObject
@property (assign, nonatomic) NSUInteger number;
@property (weak, nonatomic) ParkingSpaceAssignment *assignment;
- (instancetype)initWithNumber:(NSUInteger)number;
@end
@interface ParkingSpaceAssignment : NSObject
@property (weak, nonatomic) ParkingSpace *parkingSpace;
@property (weak, nonatomic) Employee *employee;
- (instancetype)initWithParkingSpace:(ParkingSpace *)parkingSpace employee:(Employee *)employee;
@end
// In MyCustomClass.m
#import "MyCustomClass.h"
@implementation Employee
- (void)dealloc
{
  NSLog(@"Fired!");
}
@end
@implementation ParkingSpace
- (instancetype)initWithNumber:(NSUInteger)number
{
  if (self = [super init]) {
    _number = number;
  }
  return self;
}
- (void)dealloc
{
  NSLog(@"Parking space deleted");
}
@end
@implementation ParkingSpaceAssignment
- (instancetype)initWithParkingSpace:(ParkingSpace *)parkingSpace employee:(Employee *)employee
{
  if (self = [super init]) {
    _parkingSpace = parkingSpace;
    _employee = employee;
  }
  return self;
}
- (void)dealloc
{
  NSLog(@"Assignment deleted");
}
@end
// In –[SomeOtherClass someMethod] in SomeOtherClass.m
Employee *chris = [Employee new];
ParkingSpace *parkingSpace = [[ParkingSpace alloc] initWithNumber:8];
chris.parkingSpaceAssignment = [[ParkingSpaceAssignment alloc] initWithParkingSpace:parkingSpace employee:chris];
chris = nil;
/* Prints:
Fired!
Assignment deleted
*/

Swift

class Employee {
  weak var parkingSpaceAssignment: ParkingSpaceAssignment?
  init() { }
  deinit {
    parkingSpaceAssignment = nil
    println("Fired!")
  }
}
class ParkingSpace {
  let number: Int
  weak var assignment: ParkingSpaceAssignment?
  init(number: Int) {
    self.number = number
  }
}
class ParkingSpaceAssignment {
  unowned var parkingSpace: ParkingSpace
  unowned var employee: Employee
  init(parkingSpace: ParkingSpace, employee: Employee) {
    self.parkingSpace = parkingSpace
    self.employee = employee
  }
  deinit {
    println("Assignment deleted")
  }
}
var chris: Employee? = Employee()
var parkingSpace = ParkingSpace(number: 8)
chris!.parkingSpaceAssignment = ParkingSpaceAssignment(parkingSpace: parkingSpace, employee: chris!)
chris = nil
/* Prints:
Assignment deleted
Fired!
*/

In Objective-C, capturing self in a block will also result in a strong reference cycle, because blocks maintain a strong reference to any captured objects, including self. A similar situation can also occur in Swift that results in a strong reference cycle. Closures are reference types, just like classes. So, if a closure that is assigned to a property in a class instance calls self in any way, such as to access another property or call a method, a strong reference cycle will occur.

Note  To help avoid creating a strong refence cycle, Swift requires explicitly stating self within a closure, such as self.someProperty or self.someMethod().

The solution in Objective-C is to create a weak reference to self ahead of and for use in the block.

Swift, by contrast, employs closure capture lists. A closure can define a capture list as part of its overall definition, to specify if one or more captures should be handled as weak or unowned references. As with nonclosure properties, a weak reference must always be declared as an optional variable.

Tip  If the capture can be set to nil, such as if the instance it references gets deallocated, define the capture as weak. Otherwise, if a closure and a capture within that closure will always refer to each other and be deallocated at the same time, define the capture as unowned.

The syntax to define a capture list is to enclose a rule (or multiple rules in a comma-separated list) inside square brackets, within the closure body, after the opening curly brace and typically on the next line, before the closure’s parameter list and return value (if provided and cannot be inferred), followed by the in keyword. A rule consists of the weak or unowned keyword followed by a single capture. The basic syntax is as follows:

lazy var someClosure: (ParamType, ...) -> ReturnType = {
  [weak self] (paramOneName: ParamType, ...) -> ReturnType in
  statements
}

An arbitrary expression can also be bound to a named value in a capture list, as demonatrated in Table 7-15, which compares how to avoid strong reference cycles in Objective-C blocks and Swift closure properties of a class instance.

Table 7-15. Avoiding strong reference cycles in Objective-C blocks and Swift closure properties of a class instance

Objective-C

// In MyCustomClass.h
@import Foundation;
@interface Bravo : NSObject
@property (copy, nonatomic) NSString *value;
- (instancetype)initWithValue:(NSString *)value;
@end
@interface Charlie : NSObject
@property (copy, nonatomic) NSString *value;
- (instancetype)initWithValue:(NSString *)value;
@end
typedef void (^Printer)();
@interface Alpha : NSObject
@property (copy, nonatomic) NSString *value;
@property (strong, nonatomic) Bravo *bravo;
@property (strong, nonatomic) Charlie *charlie;
@property (copy, nonatomic) Printer printOutValues;
- (instancetype)initWithValue:(NSString *)value bravoValue:(NSString *)bravoValue charlieValue:(NSString *)charlieValue;
@end
// In MyCustomClass.m
#import "MyCustomClass.h"
@implementation Bravo
- (instancetype)initWithValue:(NSString *)value
{
  if (self = [super init]) {
    _value = value;
  }
  return self;
}
- (void)dealloc
{
  NSLog(@"Bravo deallocated");
}
@end
@implementation Charlie
- (instancetype)initWithValue:(NSString *)value
{
  if (self = [super init]) {
    _value = value;
  }
  return self;
}
- (void)dealloc
{
  NSLog(@"Charlie deallocated");
}
@end
@implementation Alpha
- (instancetype)initWithValue:(NSString *)value bravoValue:(NSString *)bravoValue charlieValue:(NSString *)charlieValue
{
  if (self = [super init]) {
    _value = value;
    _bravo = [[Bravo alloc] initWithValue:bravoValue];
    _charlie = [[Charlie alloc] initWithValue:charlieValue];
    __weak typeof(self)weakSelf = self;
      _printOutValues = ^{
        if (weakSelf.value.length && weakSelf.bravo.value.length && weakSelf.charlie.value.length) {
           NSLog(@"%@ %@ %@", weakSelf.value, weakSelf.bravo.value, weakSelf.charlie.value);
        }
      };
  }
  return self;
}
- (void)dealloc
{
    NSLog(@"Alpha deallocated");
}
@end
// In –[SomeOtherClass someMethod] in SomeOtherClass.m
Alpha *alpha = [[Alpha alloc] initWithValue:@"Alpha" bravoValue:@"Bravo" charlieValue:@"Charlie"];
alpha.printOutValues(); // Prints "Alpha Bravo Charlie"
alpha.bravo = nil;      // Prints "Bravo deallocated"
alpha.charlie = nil;    // Prints "Charlie deallocated"
alpha = nil;            // Prints "Alpha deallocated"

Swift

class Bravo {
  var value: String
  init(_ value: String) {
    self.value = value
  }
  deinit {
    println("Bravo deallocated")
  }
}
class Charlie {
  var value: String
  init(_ value: String) {
    self.value = value
  }
  deinit {
    println("Charlie deallocated")
  }
}
class Alpha {
  var value: String
  var bravo: Bravo?
  var charlie: Charlie?
  lazy var printOutValues: () -> () = {
    [unowned self, weak bravo = self.bravo, weak charlie = self.charlie] in
    if bravo != nil && charlie != nil {
      println("(self.value) (bravo!.value) (charlie!.value)")
    }
  }
  init(value: String, bravoValue: String, charlieValue: String) {
    self.value = value
    bravo = Bravo(bravoValue)
    charlie = Charlie(charlieValue)
  }
  deinit {
  println("Alpha deallocated")
  }
}
var alpha: Alpha? = Alpha(value: "Alpha", bravoValue: "Bravo", charlieValue: "Charlie")
alpha?.printOutValues() // Prints "Alpha Bravo Charlie"
alpha!.bravo = nil      // Prints "Bravo deallocated"
alpha!.charlie = nil    // Prints "Charlie deallocated"
alpha = nil             // Prints "Alpha deallocated"

Singletons

In the same blog post cited earlier referencing atomicity in Swift, Apple further states that the initializer for global variables and for static members of structures and enumerations is run on first access, using dispatch_once from GCD (Grand Central Dispatch being right up there with ARC as a major milestone addition to the platform) to ensure the initialization is atomic. With that said, Table 7-16 compares creating singletons in Objective-C and Swift (credit goes to Stack Overflow user “David” for the elegant Swift example he provided: http://stackoverflow.com/a/24073016/616764).

Table 7-16. Comparing creation of singletons in Objective-C and Swift

Objective-C

// In StoreManager.h
@import Foundation;
@interface StoreManager : NSObject
+ (instancetype)sharedManager;
@end
// In StoreManager.m
#import "StoreManager.h"
@implementation StoreManager
+ (instancetype)sharedManager
{
  static StoreManager *sharedManager = nil;
  static dispatch_once_t onceToken;
  dispatch_once(&onceToken, ^{
    sharedManager = [StoreManager new];
  });
  return sharedManager;
}
@end
// In –[SomeOtherClass someMethod] in SomeOtherClass.m
StoreManager *sharedStoreManager1 = [StoreManager sharedManager];
StoreManager *sharedStoreManager2 = [StoreManager sharedManager];
NSLog(@"%@", sharedStoreManager1 == sharedStoreManager2 ? @"YES" : @"NO"); // Prints "YES"

Swift

class StoreManager {
  class var sharedManager: StoreManager {
    struct Manager {
      static let instance = StoreManager()
    }
    return Manager.instance
  }
}
let sharedStoreManager1 = StoreManager.sharedManager
let sharedStoreManager2 = StoreManager.sharedManager
println(sharedStoreManager1 === sharedStoreManager2) // Prints "true"

Selection Guidelines

Eumerations offer many new capabilities in Swift, such that an enumeration could actually handle a need that would normally be served by a class or structure. For example, an enumeration could be used to capture state that has nothing to do with an actual sequence of related values. In the spirit of the old adage, “Just because you can does not necessarily mean you should,” an enumeration should really only be chosen when an enumeration is needed. The new capabilities afforded enumerations in Swift should be taken advantage of in conjunction with satisifying an enumerative need, not in lieu of choosing a more appropriate type when there is no such need.

The near-feature parity nature of classes and structures in Swift can make choosing one over the other more of a challenge. While there are certainly going to be use cases where it won’t really matter which type is used, Table 7-17 offers some basic guidelines to help discern when one really is a better choice over the other for a particular need.

Table 7-17. Guidelines for selecting a class or structure in Swift

 

Class

Structure

Needs to inherit certain capabilities, initially or perceivably later

 

Needs to be able to be inherited from

 

May need to be type casted

 

Needs to store type properties to be shared with all instances

 

Needs broadest range of storage options (i.e., stored and computed instance and type properties)

 

Needs to store simple values

 

Needs to store large values

 

May need to perform some final process or do manual cleanup before being deallocated

 

Will only ever need to be copied when passed around in code (e.g., to a function)

 

Should be referenced when passed (e.g., to a function)

 

Needs to be a singleton instance

 

Needs to (optionally) conform to optional protocols

 

All of these guidelines are based on topics covered thus far, except optional protocols, which will be introduced in Chapter 8.

Summary

In this chapter, the differences between Objective-C and Swift classes, structures, and enumerations—of which there are many—and the similarities between Swift classes, structures, and enumerations—of which there are also many—were examined. Copious examples and tables summarizing rules and features were provided to accompany narrative explanations in an effort to expose sometimes subtle distinctions. Additionally, selection guidelines were introduced to help choose the best tool for the task at hand.

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

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