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.
Tip
The following example interacts with an iOS API, and therefore, you cannot run it in Ubuntu or in the web-based IBM Swift Sandbox. However, you will be able to run most of the examples that don't interact with iOS APIs in the forthcoming chapters.
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. This app is still a very important app in iOS 10, and the Apple Watch device in its two versions can generate very useful data for this 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 the units and their conversions. A value of 39 degrees Celsius (which is equivalent to 102.2 degrees Fahrenheit) in an adult would means that the person's body temperature is higher than normal (that is, they may have a fever). However, a value of 39 degrees Fahrenheit (equivalent to 3.88 degrees Celsius) would mean that the person's 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 in a few object-oriented snippets.
Start Xcode, navigate to File | New | Playground..., enter a name for 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
Tip
Xcode allows us to create playgrounds for any of the following platforms: iOS, Mac OS, and tvOS.
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 non-SI 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. The code file for the sample is included in the swift_3_oop_chapter_01_01
folder:
let degCUnit = HKUnit.degreeCelsius()
let degFUnit = HKUnit.degreeFahrenheit()
Tip
In Swift 2.x, in order to work with the APIs, it was necessary to repeat information many times. Swift 3 reduced the need to repeat information that was obvious, and therefore, we have to write less code to achieve the same goal compared with Swift 2.x. For example, in Swift 2.x, it was necessary to write HKUnit.degreeCelsiusUnit()
and HKUnit.degreeFahrenheitUnit()
. The HKUnit
prefix makes it clear that we are talking about a unit, and therefore, Swift 3 removed the Unit
word as a suffix of both HKUnit.degreeCelsiusUnit()
and HKUnit.degreeFahrenheitUnit()
. As a result, we can write the previously shown code that uses HKUnit.degreeCelsius()
and HKUnit.degreeFahrenheit()
.
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. The code file for the sample is included in the swift_3_oop_chapter_01_01
folder:
let degCUnitFromStr = HKUnit(from: "degC")
let degFUnitFromStr = HKUnit(from: "degF")
Tip
In Swift 2.x, it was necessary to use fromString
instead of from
to achieve the same goal shown in the previous lines. Swift 3 reduced the code that it is necessary to write to make API calls.
The following lines generate two instances of HKEnergyUnit
-one for kilocalories and the other for kilojoules. The code file for the sample is included in the swift_3_oop_chapter_01_01
folder:
let kiloCaloriesUnit = HKUnit(from: "kcal")
let joulesUnit = HKUnit(from: "kJ")
The next two lines generate two instances of HKMassUnit
-one for kilograms and the other for pounds. The code file for the sample is included in the swift_3_oop_chapter_01_01
folder:
let kiloGramsUnit = HKUnit.gramUnit(with:
HKMetricPrefix.kilo)
let poundsUnit = HKUnit.pound()
The next line generates an instance of _HKCompoundUnit
because the string specifies a complex unit for mass/volume: ounces per liter (oz/L). The code file for the sample is included in the swift_3_oop_chapter_01_01
folder. The subsequent screenshot shows the results displayed in the Playground:
let ouncesPerLiter = HKUnit(from: "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; we name the instances bodyTemperature1
and bodyTemperature2
. The former uses degrees Celsius (degCUnit
) and the latter degrees Fahrenheit (degFUnit
). Then, the code calls the is
method with the compatibleWith
argument to make sure that each HKQuantity
instance can be converted to degrees Fahrenheit (degFUnit
). If is
returns true
, it means that you can convert to HKUnit
, which is specified as the compatibleWith
argument. We always have to call this method before calling the doubleValue
method. This way, we will avoid errors when the units aren't compatible.
The doubleValue
method returns the quantity value converted to the unit specified as the for
argument. In this case, the two calls make sure that the value is expressed in degrees Fahrenheit, no matter what the temperature unit specified in each HKQuantity
instance is. The code file for the sample is included in the swift_3_oop_chapter_01_01
folder. 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.is(compatibleWith: degFUnit) {
print("Temperature #1 in Fahrenheit degrees: \
(bodyTemperature1.doubleValue(for: degFUnit))")
}
if bodyTemperature2.is(compatibleWith: degFUnit) {
print("Temperature #2 in Fahrenheit degrees: \
(bodyTemperature2.doubleValue(for: 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 doubleValue
and use it in the HKQuantity
initializer, as follows. The code file for the sample is included in the swift_3_oop_chapter_01_01
folder:
let bodyTemperature2InDegC = HKQuantity(unit:
degCUnit, doubleValue:
bodyTemperature2.doubleValue(for: degCUnit))
The compare
method returns a ComparisonResult
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 bodyTemperature2InDegC = HKQuantity(unit:
degCUnit, doubleValue:
bodyTemperature2.doubleValue(for: degCUnit))
let comparisonResult =
bodyTemperature1.compare(bodyTemperature2)
switch comparisonResult {
case ComparisonResult.orderedDescending:
print("Temperature #1 is greater than #2")
case ComparisonResult.orderedAscending:
print("Temperature #2 is greater than #1")
case ComparisonResult.orderedSame:
print("Temperature #1 is equal to Temperature #2")
}
Tip
In many cases, the APIs removed the NS
prefix in Swift 3. In Swift 2.3, the compare
method returned an NSComparisonResult
value. In Swift 3, the compare method returns a ComparisonResult
value. In addition, the APIs in Swift 3 use lowerCamelCase for enumeration values. Therefore, the NSComparisonResult.OrderedDescending
value in Swift 2.3 is ComparisonResult.orderedDescending
in Swift 3.