Object–Oriented Programming with Swift 2
上QQ阅读APP看书,第一时间看更新

Working with API objects in the Xcode Playground

Now, let's forget a bit about geometry, shapes, polygons, perimeters, and areas. We will interact with API objects in the Xcode Playground. You still need to learn many things before we can start creating object-oriented code. However, we will write some code in the Playground to interact with an existing API before we move forward with our journey into the object-oriented programming world.

Object-oriented programming is extremely useful when you have to interact with API objects. When Apple launched iOS 8, it introduced a Health app that provided iPhone users access to a dashboard of health and fitness data. The HealthKit framework introduced in the iOS SDK 8 allows app developers to request permissions from the users themselves to read and write specific types of health and fitness data. The framework makes it possible to ask for, create, and save health and fitness data that the users will see summarized in the Health app.

When we store and query health and fitness data, we have to use the framework to work with the units in which the values are expressed, their conversions, and localizations. For example, let's imagine an app that stores body temperature data without considering units and their conversions. A value of 39 degrees Celsius (which is equivalent to 102.2 degrees Fahrenheit) in an adult would mean that his/her body temperature is higher than normal (that is, he/she may have a fever). However, a value of 39 degrees Fahrenheit (equivalent to 3.88 degrees Celsius) would mean that his/her body is close to its freezing point. If our app just stores values without considering the related units and user preferences, we can have huge mistakes. If the app just saves 39 degrees and thinks that the user will always display Celsius, it will still display 39 degrees to a user whose settings use Fahrenheit as the default temperature unit. Thus, the app will provide wrong information to the user.

The data in HealthKit is always represented by a double value with an associated simple or complex unit. The units are classified into types, and it is possible to check the compatibility between units before performing conversions. We can work with HealthKit quantities and units in the Swift interactive Playground and understand how simple it is to work with an object-oriented framework. It is important to note that the Playground doesn't allow us to interact with the HealthKit data store. However, we will just play with quantities and units with a few object-oriented snippets.

Start Xcode, navigate to File | New | Playground…, enter a name for the Playground, select iOS as the desired platform, click on Next, select the desired location for the Playground file, and click on Create. Xcode will display a Playground window with a line that imports UIKit and creates a string variable. You just need to add the following line to be able to work with quantities and units from the HealthKit framework, as shown in the subsequent screenshot:

import HealthKit

All HealthKit types start with the HK prefix. HKUnit represents a particular unit that can be either simple or complex. Simple units for Temperature are degrees Celsius and degrees Fahrenheit. A complex unit for Mass/Volume is ounces per liter (oz/L). HKUnit supports many standard SI units (Système Internationale d'Unités in French, International System of Units in English) and nonSI units.

Add the following two lines to the Swift Playground and check the results on the right-hand side of the window; you will notice that they generate instances of HKTemperatureUnit. Thus, you created two objects that represent temperature units, as follows:

let degCUnit = HKUnit.degreeCelsiusUnit()
let degFUnit = HKUnit.degreeFahrenheitUnit()

However, there are other ways to create objects that represent temperature units. It is also possible to use the HKUnit initializer, which returns the appropriate unit instance from its string representation. For example, the following lines also generate instances of HKTemperatureUnit for degrees in Celsius and Fahrenheit:

let degCUnitFromStr = HKUnit(fromString: "degC")
let degFUnitFromStr = HKUnit(fromString: "degF")

The following lines generate two instances of HKEnergyUnit—one for kilocalories and the other for kilojoules:

let kiloCaloriesUnit = HKUnit(fromString: "kcal")
let joulesUnit = HKUnit(fromString: "kJ")

The next two lines generate two instances of HKMassUnit—one for kilograms and the other for pounds:

let kiloGramsUnit = HKUnit.gramUnitWithMetricPrefix(HKMetricPrefix.Kilo)
let poundsUnit = HKUnit.poundUnit()

The next line generates an instance of _HKCompoundUnit because the string specifies a complex unit for Mass/Volume: ounces per liter (oz/L). The subsequent screenshot shows the results displayed in the Playground:

let ouncesPerLiter = HKUnit(fromString: "oz/L")

HKQuantity encapsulates a quantity value (Double) and the unit of measurement (HKUnit). This class doesn't provide all the operations you might expect to work with quantities and their units of measure, but it allows you to perform some useful compatibility checks and conversions.

The following lines create two HKQuantity instances with temperature units named bodyTemperature1 and bodyTemperature2. The former uses degrees Celsius (degCUnit) and the latter degrees Fahrenheit (degFUnit). Then, the code calls the isCompatibleWithUnit method to make sure that each HKQuantity instance can be converted to degrees Fahrenheit (degFUnit). If isCompatibleWithUnit returns true, it means that you can convert to HKUnit, which is specified as an argument. We always have to call this method before calling the doubleValueForUnit method. This way, we will avoid errors when the units aren't compatible.

The doubleValueForUnit method returns the quantity value converted to the unit specified as an argument. In this case, the two calls make sure that the value is expressed in degrees Fahrenheit no matter the temperature unit specified in each HKQuantity instance. The screenshot that follows the given code shows the results displayed in the Playground:

let bodyTemperature1 = HKQuantity(unit: degCUnit, doubleValue: 35.2)
let bodyTemperature2 = HKQuantity(unit: degFUnit, doubleValue: 95)
print(bodyTemperature1.description)
print(bodyTemperature2.description)
 
if bodyTemperature1.isCompatibleWithUnit(degFUnit) {
    print("Temperature #1 in Fahrenheit degrees:
\(bodyTemperature1.doubleValueForUnit(degFUnit))")
}
 
if bodyTemperature2.isCompatibleWithUnit(degFUnit) {
    print("Temperature #2 in Fahrenheit degrees: \(bodyTemperature2.doubleValueForUnit(degFUnit))")
}

The following line shows an example of the code that creates a new HKQuantity instance with a quantity and temperature unit converted from degrees Fahrenheit to degrees Celsius. There is no convert method that acts as a shortcut, so we have to call doubleValueForUnit and use it in the HKQuantity initializer, as follows:

let bodyTemperature2InDegC = HKQuantity(unit: degCUnit, doubleValue: bodyTemperature2.doubleValueForUnit(degCUnit))

The compare method returns an NSComparisonResult value that indicates whether the receiver is greater than, equal to, or less than the compatible HKQuantity value specified as an argument. For example, the following lines compare bodyTemperature1 with bodyTemperature2 and print the results of the comparison. Note that it isn't necessary to convert both the HKQuantity instances to the same unit; they just need to be compatible, and the compare method will be able to perform the comparison by making the necessary conversions under the hood. In this case, one of the temperatures is in degrees Celsius, and the other is in degrees Fahrenheit. The screenshot that follows the given code shows the results displayed in the Playground:

let comparisonResult = bodyTemperature1.compare(bodyTemperature2)
switch comparisonResult {
    case NSComparisonResult.OrderedDescending:
        print("Temperature #1 is greater than #2")
    case NSComparisonResult.OrderedAscending:
        print("Temperature #2 is greater than #1")
    case NSComparisonResult.OrderedSame:
        print("Temperature #1 is equal to Temperature #2")
}