Nick Harris

Archive for the ‘Code’ Category

Bitcoin Exchange Rates – A Swift 2.0 Sample App

leave a comment »

Introduction

A few days ago I read this post about Massive View Controllers by Marcus Zarra. Marcus and I have been friends for years. I’ve probably learned more about iOS development from him and my friend and old co-worker Brent then anyone else.

The post talks about using NSOperation subclasses for networking. This struck a chord with me since I love NSOperation for networking myself, particularly after spending the last 1.5 years working on a project that used a facade pattern approach. I believe that pattern is popular in Android but I had never used it in iOS and wasn’t particularly fond of it.

I decided I would write a quick blog post on how to use NSOperation to call a web service in Swift. As I got started I realized that another pattern that I had learned and used quite successfully in past projects from Marcus around Core Data (updated and described nicely in this post) was also missing in my last project. Instead it used a third party framework that seemed to cause more confusion and crashes then the simple approaches I had used successfully in the past.

So instead of just a blog post, I ended up writing a sample app to teach myself more Swift 2.0 that combines both the NSOperation networking pattern and Core Data stack in Marcus’s posts. 

Bitcoin Exchange Rate App

Bitcoin Exchange Rate is a very simple app that fetches the current exchange rate for Bitcoin in Dollars, Pounds and Euros then displays them. Its powered by an open web service from CoinDesk. Its a simple idea that demonstrates not only NSOperation and Core Data but also Storyboards, Size Classes and Adaptive Layout that works on all iOS 8 and up devices including multitasking on iPad in iOS 9. It’s written completely in Swift 2.0.

The whole project is less then 600 lines of code.

You can find the repository here on BitBucket.

DISCLAIMER
I am in no way an expert on any of the topics this demo app uses nor is this demo app polished. The code and this post is meant to show how I approached the app and implemented it’s core functionality while learning Swift 2.0. I’m sure there are mistakes since I’ve been coding in Swift for all of one month now. I’d love to hear any feedback either in the comments, on twitter or if you want you can email me.

Web Service 

In order to demonstrate a web service driven app I obviously needed a web service to connect to. I’ve been a bit fascinated with Bitcoin lately and after a quick search I found a nice web service API from CoinDesk. It uses HTTPS so I can use it out of the box with iOS 9 and the JSON is fairly simple:

{

bpi:
{
EUR:
{
code:
EUR,
description:
Euro,
rate:
208.2519,
rate_float:
208.2519,
symbol:
€

},

GBP:
{
code:
GBP,
description:
British Pound Sterling,
rate:
153.4957,
rate_float:
153.4957,
symbol:
£

},

USD:
{
code:
USD,
description:
United States Dollar,
rate:
233.2992,
rate_float:
233.2992,
symbol:
$

}

},

disclaimer:
This data was produced from the CoinDesk Bitcoin Price Index (USD). Non-USD currency data converted using hourly conversion rate from openexchangerates.org,
time:
{
updated:
Sep 28, 2015 01:23:00 UTC,
updatedISO:
2015-09-28T01:23:00+00:00,
updateduk:
Sep 28, 2015 at 02:23 BST

}

}

Adaptive Layout

I tend to be a “paint the canvas” type of developer before diving into the data layer of an app. There’s no point in parsing and saving data that’s never used. With the response from my web service in hand, I created my one scene in my main storyboard using wAny hAny:

Screen Shot 2015 09 27 at 7 39 00 PM

Its probably easier to pick through the view by downloading the project yourself, but here’s a quick overview:

– a UIView anchored to the top that has the headline, disclaimer text and last updated label
– a UITableView with a prototype cell that lists the name of the currency, its symbol and current bitcoin exchange rate
– a UIToolbar with a single “Refresh” UIBarButtonItem

One of the tricks I’ve learned with Adaptive Layout is setting font sizes big then using autoshrink with either a minimum font size or minimum font scale. For the large “Bitcoin Exchange Rates” header I went with minimum font size of 12. 

Screen Shot 2015 09 27 at 7 53 26 PM

I’ll fully admit that the simple layout of this app is not very eye pleasing but it did make getting it usable on any iOS 8 supported iPhone or iPad incredibly easy. I didn’t have to change anything for full screen iPad support though I probably could have. For iPad multitasking all I needed to do was set a smaller font for the disclaimer text on the wCompact hRegular size class:

Screen Shot 2015 09 27 at 8 09 57 PM

As you can see in the previous screen shot, I also needed to adjust the font for the wCompact hCompact size class which is used for iPhone 6/6S and under in landscape (iPhone 6/6S+ uses wCompact hRegular but I didn’t optimize the UI for those devices). I also changed the layout so that the header view is off to the side while the table view takes up the rest of the screen:

Screen Shot 2015 09 27 at 8 14 22 PM

One other simple trick I used was two views as a divider line for either iPhone in landscape on the side or all other orientations at the bottom of the header view. For the wCompact hCompact size class I set the vertical separator lines width to 1 and the horizontal lines height to 0. For all other size classes those values are swapped. Pretty nifty that those constrains can be set per size class in IB.

Preview mode for different device sizes and orientations is incredibly handy when working on your UI:

Screen Shot 2015 09 27 at 8 22 13 PM

That was pretty much it for Adaptive Layout. It took me a few hours to get right but the result is a pretty simple storyboard with only a few size class differences. I particularly like how easy this makes multitasking!

Screen Shot 2015 09 27 at 8 31 42 PM

Screen Shot 2015 09 27 at 8 31 59 PM

(A major oversight I realized while proofreading is that this does not support Dynamic Type at all – perhaps in a future update)

Core Data and the DataController

I am one of the weird iOS devs that likes Core Data. If you’re doing consulting work I believe it makes sense to know and use Core Data instead of something like Realm (though I do like Realm too). The reason is that Core Data is an Apple technology stack so you’re pretty much guaranteed that it will be updated and/or deprecated as time moves on and needs of the system change. There are third party frameworks you can use with Core Data but I believe its best to write the code yourself. Its not that difficult to implement.

I recommend starting with Marcus’s post I mentioned in the introduction

In my past projects I’ve used the boilerplate Core Data code Xcode will throw into your AppDelegate. I’ve also partitioned a good portion of the actual working code into its own singleton manager class. It worked well but the idea of using dependency injection instead of a singleton looked better to me. I have another side project app that uses the DataController approach with a tabbed layout that injects the DataController into each. It was easy so I went with that approach again.

The complete Swift code for my DataController:

import CoreData
 
typealias networkOperationResult = (success: Bool, errorMessage: String?) -> Void
 
enum DataControllerUserDefaultKeys: String {
    case lastUpdatedKey = "BitcoinExchangeLastUpdatedKey"
}
 
class DataController {
    
    var managedObjectContext: NSManagedObjectContext
    internal var privateObjectContext: NSManagedObjectContext
    
    let networkQueue = NSOperationQueue()
    
    init(closure:()->()) {
        
        guard let modelURL = NSBundle.mainBundle().URLForResource("BitcoinExchangeRateDataModel", withExtension: "momd"),
              let managedObjectModel = NSManagedObjectModel.init(contentsOfURL: modelURL)
        else {
            fatalError("DataController - COULD NOT INIT MANAGED OBJECT MODEL")
        }
        
        let coordinator = NSPersistentStoreCoordinator.init(managedObjectModel: managedObjectModel)
            
        self.managedObjectContext = NSManagedObjectContext.init(concurrencyType: NSManagedObjectContextConcurrencyType.MainQueueConcurrencyType)
        self.privateObjectContext = NSManagedObjectContext.init(concurrencyType: NSManagedObjectContextConcurrencyType.PrivateQueueConcurrencyType)
            
        self.privateObjectContext.persistentStoreCoordinator = coordinator
        self.managedObjectContext.parentContext = self.privateObjectContext
        
        dispatch_async(dispatch_get_global_queue(Int(QOS_CLASS_USER_INITIATED.rawValue), 0)) {
            
            let options = [
                NSMigratePersistentStoresAutomaticallyOption: true,
                NSInferMappingModelAutomaticallyOption: true,
                NSSQLitePragmasOption: ["journal_mode": "DELETE"]
            ]
            
            let documentsURL = NSFileManager.defaultManager().URLsForDirectory(.DocumentDirectory, inDomains: .UserDomainMask).last
            let storeURL = NSURL.init(string: "bitcoinExchangeRate.sqlite", relativeToURL: documentsURL)
            
            do {
                try coordinator.addPersistentStoreWithType(NSSQLiteStoreType, configuration: nil, URL: storeURL, options: options)
                
                dispatch_async(dispatch_get_main_queue()) {
                    closure()
                }
            }
            catch let error as NSError {
                fatalError("DataController - COULD NOT INIT SQLITE STORE: \(error.localizedDescription)")
            }
        }
    }
    
    func save() {
        
        if self.privateObjectContext.hasChanges || self.managedObjectContext.hasChanges {
            
            self.managedObjectContext.performBlockAndWait {
                
                do {
                    try self.managedObjectContext.save()
                                        
                    self.privateObjectContext.performBlock {
                        
                        do {
                            try self.privateObjectContext.save()
                        }
                        catch let error as NSError {
                            fatalError("DataController - SAVE PRIVATEOBJECTCONTEXT FAILED: \(error.localizedDescription)")
                        }
                    }
                }
                catch let error as NSError {
                    fatalError("DataController - SAVE MANAGEDOBJECTCONTEXT FAILED: \(error.localizedDescription)")
                }
            }
        }
    }
    
    func fetchExchangeRateData(completion: networkOperationResult) {
        
        let fetchExchangeRateDataOperation = FetchExchangeRateDataOperation(dataController: self, completion: completion)
        self.networkQueue.addOperation(fetchExchangeRateDataOperation)
    }
}

The class differs slightly from Marcus’s post in that it does not inherit from NSObject. I didn’t see the need for anything in NSObject so I didn’t use it. Perhaps I’m missing something lower level but this worked for me.

The init is the workhorse. It takes a closure as a parameter that gets called after the boilerplate Core Data initialization happens. I think Marcus covers this well so I won’t dwell too much on its implementation other then to note the use of fatalError instead of NSAssert. I also like the use of guard to make sure the model exists in the app bundle.

The reason for the closure after a successful init is to inject the DataController into the apps view controller(s) from the AppDelegate:

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
 
    var window: UIWindow?
    var dataController: DataController?
 
    func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
        
        self.dataController = DataController.init() {
            
            if let bitcoinExchangeRateViewController = self.window?.rootViewController as? BitcoinExchangeRateViewController {
                bitcoinExchangeRateViewController.configureWithDataController(self.dataController!)
            }
        }
        
        return true
    }
}

My BitcoinExchangeRateViewController can then set itself up knowing full well that the Core Data layer is initiated and ready to use. I’ll show that in the Updating the UI section below.

Fetching Data with NSOperation

 In the code for my DataController you’ll see the fetchExchangeRateData function:

func fetchExchangeRateData(completion: networkOperationResult) {
        
    let fetchExchangeRateDataOperation = FetchExchangeRateDataOperation(dataController: self, completion: completion)
    self.networkQueue.addOperation(fetchExchangeRateDataOperation)
}

FetchExchangeRateDataOperation is my NSOperation subclass that could easily be initiated by the view controller that wants the data instead of within the DataController. I’ve done that in the past and used execute to start the operation. What I like about this approach though is that the DataController owns the NSOperationQueue for working background networking tasks. It also means that my NSOperation is going to be called on a background thread instead of the main thread. I get both background threading and a simpler dependency graph in a few easy to read lines of code.

Next lets take a look at the NSOperation subclass itself:

import UIKit
 
class FetchExchangeRateDataOperation: NSOperation {
    
    let currencyDataUrlString = "https://api.coindesk.com/v1/bpi/currentprice.json"
    var dataController: DataController
    var completion: networkOperationResult
    
    init(dataController: DataController, completion: networkOperationResult) {
        self.dataController = dataController
        self.completion = completion
        super.init()
    }
    
    override func start() {
        
        if let currencyExchangeRateURL = NSURL(string: currencyDataUrlString) {
            
            let session = NSURLSession.sharedSession()
            let request = NSMutableURLRequest(URL: currencyExchangeRateURL)
         
            let task = session.dataTaskWithRequest(request, completionHandler: {
                [unowned self]
                data, response, error -> Void in
                
                if let safeError = error {
                    self.completion(success: false, errorMessage: safeError.localizedDescription)
                    return
                }
                
                guard let safeData = data else {
                    self.completion(success: false, errorMessage: "No data was recieved")
                    return
                }
                
                do {
                    
                    if let json = try NSJSONSerialization.JSONObjectWithData(safeData, options: .MutableLeaves) as? NSDictionary
                    {
                        let bitcoinExchangeRateJSON = try BitcoinCurrentPriceJSON(json: json)
                        
                        NSUserDefaults.standardUserDefaults().setObject(bitcoinExchangeRateJSON.lastUpdate, forKey: DataControllerUserDefaultKeys.lastUpdatedKey.rawValue)
                        
                        for currencyExchangeData in bitcoinExchangeRateJSON.currencies! {
                            ExchangeCurrency.saveCurrencyExchangeData(currencyExchangeData, managedObjectContext: self.dataController.managedObjectContext)
                        }
                        
                        self.dataController.save()
                        self.completion(success: true, errorMessage: nil)
                    }
                    
                    
                }
                catch JSONParseError.MissingRequiredField(let fieldName) {
                    self.completion(success: false, errorMessage: "Response is missing an expected field: \(fieldName)")
                }
                catch let error as NSError {
                    self.completion(success: false, errorMessage: "Parsing the response failed: \(error.localizedDescription)")
                }
                
            })
            
            task.resume()
        }
    }
}

I’ve become a big fan of NSURLSession and its counterparts. Networking back in the bad old days prior to its introduction were ugly and very error prone. My apps powered through it but I’ll admit that the first time I saw AFNetworking in action I couldn’t wait to use it. In fact I use to tout it as the one third party framework I would never argue against using. I don’t feel that way anymore with AlamoFire. The tools we have in the foundation are more then enough for most developers now a day.

Side Track – Third Party Code
You may have caught on in this post or knowing me that I’m not a fan of third party code. That doesn’t mean I’m against open source code. I think sharing code openly is amazing. You can learn so much from reading other code and seeing how problems were approached. I just caution anyone depending on third party code to know what you’re “buying” in the trade-off for not writing it yourself. Check out this post and think about it when debating third party frameworks. 

Lets look at what this code does. First it makes sure the system can convert the web service URL string to an actual NSURL used by NSURLSession:

if let currencyExchangeRateURL = NSURL(string: currencyDataUrlString)

Next it gets the default NSURLSession and creates an NSURLRequest

let session = NSURLSession.sharedSession()
let request = NSMutableURLRequest(URL: currencyExchangeRateURL)

The creation of the data task is next.

let task = session.dataTaskWithRequest(request, completionHandler: {...

By using dataTaskWithResult we know we’re making a GET HTTP call. The rest is just a completion handler which gets called after the system makes the networking call for us.

It has a signature that returns the data, the NSURLResponse object if we want it, along with any generated NSError’s. You might also notice that it declares self in the closure as unowned. I’ll be honest in that I don’t have the best knowledge of how ARC, weak, strong and unowned work in Swift yet. I can tell you that this code doesn’t crash and has no memory leaks.

The closure first looks for any reported errors. If there is an error it calls the supplied closure with success: false and the errorMessage set to the NSError.localizedDescription.

if let safeError = error {
    self.completion(success: false, errorMessage: safeError.localizedDescription)
    return
}

Next it uses a guard statement to make sure there is actual data to parse:

guard let safeData = data else {
    self.completion(success: false, errorMessage: "No data was recieved")
    return
}

Guard vs. let if has been my biggest struggle with Swift so far. I think they both have their place but I wonder if I’m utilizing them to their maximum potential.

The rest of the closure uses a do/catch statement.

Long, long ago I wrote about exceptions in Objective-C vs what I was doing with platform code I was writing for Glassboard in C#. I even had a special explanation in my book about how exceptions and error handling in Objective-C were completely different then any other language. Swift 2.0 makes this so much better.

The first try attempts to parse the NSData into a generic JSON object using NSJSONSerialization.JSONObjectWithData. If that succeeds it then uses my own BitcoinCurrentPriceRateJSON struct to parse it down into usable objects. Either of these calls can throw exceptions which can then be properly assessed and passed back up to the UI layer using the networkOperationResult closure. If all goes well then everything gets parsed and saved to Core Data which in turn lets the UI know there’s new data to display.

The reason I really like do/catch is that is much more in line with languages I’ve used in the past. Objective-C and the very clear mandate to never use NSException in the iOS developer community was very strong. But it made handling error conditions in code clunky. Swift and all its type safety and error security is much different. I believe it will make creating apps that handle unexpected situations much easier then in Objective-C.

Parsing the JSON

JSON parsing in Swift is odd. JSON is not strongly typed by definition. Swift however is. This makes for a bit of a stand off.

When I first got to the part of parsing the JSON from the CoinDesk web service I initially threw my hands in the air and decided that Swift sucks for JSON while simultaneously searching for a third party framework to do the hard work for me. After about 2 hours though I gave up on any third party code yet again (see my note above) and settled into writing my own parser. 

I did like what I saw in SwiftyJSON and even Argo. But the idea of adding more lines of code to my project through a third party framework then actual lines of JSON I needed to parse seemed up-side-down. How hard could a Swift JSON parser be when I know exactly what the JSON looks like.

Turns out its not hard at all:

import UIKit
 
enum JSONParseError: ErrorType {
    case MissingRequiredField(fieldName: String)
}
 
struct CurrencyExchangeData {
    var code: String?
    var name: String?
    var rate: String?
    var symbol: String?
}
 
struct BitcoinCurrentPriceJSON {
    
    enum BitcoinCurrentPriceJSONKeys: String {
        case time = "time"
        case updated = "updated"
        case bpi = "bpi"
        case code = "code"
        case description = "description"
        case rate = "rate"
        case symbol = "symbol"
    }
    
    var lastUpdate: String?
    var currencies: [CurrencyExchangeData]?
    
    init(json: NSDictionary) throws {
        self.currencies = []
        try parseExchangeRateJSON(json)
    }
    
    internal mutating func parseExchangeRateJSON(json: NSDictionary) throws -> BitcoinCurrentPriceJSON {
        
        guard let timeDictionary = json[BitcoinCurrentPriceJSONKeys.time.rawValue] as? NSDictionary,
              let timeStamp = timeDictionary[BitcoinCurrentPriceJSONKeys.updated.rawValue] as? String else
        {
            throw JSONParseError.MissingRequiredField(fieldName: BitcoinCurrentPriceJSONKeys.updated.rawValue)
        }
        
        self.lastUpdate = timeStamp
        
        guard let bpiDictionary = json[BitcoinCurrentPriceJSONKeys.bpi.rawValue] as? NSDictionary,
              let currencies = bpiDictionary.allValues as? [NSDictionary] else
        {
            throw JSONParseError.MissingRequiredField(fieldName: BitcoinCurrentPriceJSONKeys.bpi.rawValue)
        }
        
        for currencyDictionary in currencies {
            let currencyExchangeData = try parseCurrencyDictionary(currencyDictionary as NSDictionary)
            self.currencies?.append(currencyExchangeData)
        }
        
        return self
    }
    
    internal func parseCurrencyDictionary(currencyDictionary: NSDictionary) throws -> CurrencyExchangeData {
        
        guard let currencyCode = currencyDictionary[BitcoinCurrentPriceJSONKeys.code.rawValue] as? String else
        {
            throw JSONParseError.MissingRequiredField(fieldName: BitcoinCurrentPriceJSONKeys.code.rawValue)
        }
        
        guard let currencyDescription = currencyDictionary[BitcoinCurrentPriceJSONKeys.description.rawValue] as? String else
        {
            throw JSONParseError.MissingRequiredField(fieldName: BitcoinCurrentPriceJSONKeys.description.rawValue)
        }
        
        guard let currencyRate = currencyDictionary[BitcoinCurrentPriceJSONKeys.rate.rawValue] as? String else
        {
            throw JSONParseError.MissingRequiredField(fieldName: BitcoinCurrentPriceJSONKeys.rate.rawValue)
        }
        
        guard let currencySymbol = currencyDictionary[BitcoinCurrentPriceJSONKeys.symbol.rawValue] as? String else
        {
            throw JSONParseError.MissingRequiredField(fieldName: BitcoinCurrentPriceJSONKeys.symbol.rawValue)
        }
        
        var currencyExchangeData = CurrencyExchangeData()
        currencyExchangeData.code = currencyCode
        currencyExchangeData.name = currencyDescription
        currencyExchangeData.rate = currencyRate
        currencyExchangeData.symbol = decodeSymbol(currencySymbol)
        
        return currencyExchangeData
    }
    
    internal func decodeSymbol(encodedSymbolString: String) -> String {
        let encodedData = encodedSymbolString.dataUsingEncoding(NSUTF8StringEncoding)!
        let attributedOptions : [String: AnyObject] = [
            NSDocumentTypeDocumentAttribute: NSHTMLTextDocumentType,
            NSCharacterEncodingDocumentAttribute: NSUTF8StringEncoding
        ]
        
        do {
            let attributedString = try NSAttributedString(data: encodedData, options: attributedOptions, documentAttributes: nil)
            return attributedString.string
            
        }
        catch {
            return ""
        }
    }
}

What I like about this code is that its not only strongly typed but its also strongly structured. I love that JSON is open type-wise, but when it comes down to consuming data offered in JSON you kind of need to know what you’re getting and how to get to it. The third party frameworks mentioned earlier can do this for you but again at what cost? If my JSON response changes, I can be pretty certain my data layer needs updating as well.

This code also gave me the chance to group the JSON keys into their own String enumeration and use rawValue in the code. I ended up making that change after fixing a misspelling in one of the throws but not in the guard statement its self. Its the old problem of magical strings but solved in a Swift-type way.

One last note on this code – the decodeSymbol function could easily have been written as an extension to String. Perhaps I’ll do that in a future update.

Updating the UI

After my NSOperation parses the JSON into a BitcoinCurrentPriceJSON object it uses that to parse and save the information to Core Data. There’s only one Core Data NSManagedObject in the project called ExchangeCurrency. Here’s the ExchangeCurrency+CoreDataProperties:

import Foundation
import CoreData
 
extensionExchangeCurrency {
 
    @NSManaged var code: String?
    @NSManaged var name: String?
    @NSManaged var rate: String?
    @NSManaged var symbol: String?
 
}

I then added two functions to the ExchangeCurrency.swift file. In the past I would have done this using Mogenerator but you can do this now in Xcode without any helpers. I think this code is fairly straight forward. The internal function gets the correct object from the context if it can find one or it creates a new one. The public function uses the found or created object and sets the correct properties.

import Foundation
import CoreData
 
@objc(ExchangeCurrency)
 
class ExchangeCurrency: NSManagedObject {
 
    class func saveCurrencyExchangeData(currencyExchangeData: CurrencyExchangeData, managedObjectContext: NSManagedObjectContext) {
        
        guard let exchangeCurrency = getExchangeCurrencyObject(currencyExchangeData.code!, managedObjectContext: managedObjectContext) else {
            fatalError("ExchangeCurrency.saveCurrencyExchangeData - NO EXCHANGECURRENCY OBJECT")
        }
        
        exchangeCurrency.code = currencyExchangeData.code
        exchangeCurrency.name = currencyExchangeData.name
        exchangeCurrency.rate = currencyExchangeData.rate
        exchangeCurrency.symbol = currencyExchangeData.symbol
    }
 
    internal class func getExchangeCurrencyObject(code: String, managedObjectContext: NSManagedObjectContext) -> ExchangeCurrency? {
        
        let fetchRequest = NSFetchRequest(entityName: "ExchangeCurrency")
        fetchRequest.predicate = NSPredicate(format: "code = %@", code)
        
        do {
            guard let fetchResults = try managedObjectContext.executeFetchRequest(fetchRequest) as? [ExchangeCurrency] else {
                fatalError("ExchangeCurrency.getExchangeCurrencyObject - NO FETCH RESULTS")
            }
            
            if fetchResults.count == 1 {
                return fetchResults.last
            }
            else if fetchResults.count == 0 {
                
                guard let exchangeCurrency = NSEntityDescription.insertNewObjectForEntityForName("ExchangeCurrency", inManagedObjectContext: managedObjectContext) as? ExchangeCurrency else {
                    fatalError("ExchangeCurrency.getExchangeCurrencyObject - COULD NOT CREATE EXCHANGECURRENCY OBJECT")
                }
                
                return exchangeCurrency
            }
            else {
                fatalError("ExchangeCurrency.getExchangeCurrencyObject - MULTIPLE FETCH RESULTS FOR CODE")
            }
        }
        catch let error as NSError {
            fatalError("ExchangeCurrency.getExchangeCurrencyObject - ERROR FETCHING FROM CORE DATA: \(error.localizedDescription)")
        }
        
        return nil
    }
}

Once all the data is parsed into NSManagaedObjectContext the FetchExchangeRateDataOperation calls save on the DataController. It should be noted that I’m doing the Core Data work on the context meant for the main thread. In this small of an app it works fine. If I were doing something more involved I would probably have created a child context from the main thread context and used that in the FetchExchangeRateDataOperation. There really isn’t much difference code wise in the NSOperation. The only big difference would be in the DataController and how the save merges things back into the main thread context.

Finally lets take a look at the BitcoinExchangeRateViewController:

import UIKit
import CoreData
 
class BitcoinExchangeRateViewController: UIViewController, UITableViewDataSource, NSFetchedResultsControllerDelegate {
    
    @IBOutlet weak var tableView: UITableView!
    @IBOutlet weak var lastUpdatedLabel: UILabel!
    @IBOutlet weak var refreshButton: UIBarButtonItem!
    var dataController: DataController?
    var fetchedResultsController: NSFetchedResultsController?
    
    override func viewWillAppear(animated: Bool) {
        super.viewWillAppear(animated)
        
        setLastUpdatedLabel()
    }
    
    @IBAction func updateExchangeRateData() {
        fetchExchangeRateData()
    }
    
    func configureWithDataController(dataController: DataController) {
        
        self.dataController = dataController
        
        let currencyFetchRequest = NSFetchRequest(entityName: "ExchangeCurrency")
        let sortDescriptor = NSSortDescriptor(key: "code", ascending: false)
        
        currencyFetchRequest.sortDescriptors = [sortDescriptor]
        
        if let managedObjectContext = self.dataController?.managedObjectContext {
            self.fetchedResultsController = NSFetchedResultsController(
                fetchRequest: currencyFetchRequest,
                managedObjectContext: managedObjectContext,
                sectionNameKeyPath: nil,
                cacheName: nil)
        }
        
        self.fetchedResultsController?.delegate = self
        
        do {
            tryself.fetchedResultsController?.performFetch()
        }
        catch let error as NSError {
            fatalError("BitcoinExchangeRateViewController - NSFETCHEDRESULTSCONTROLLER FETCH FAILED: \(error.localizedDescription)")
        }
        
        self.tableView.reloadData()
        fetchExchangeRateData()
    }
    
    internal func fetchExchangeRateData() {
        self.refreshButton.enabled = false
        dataController?.fetchExchangeRateData({
            (success: Bool, errorMessage: String?) in
            
            dispatch_async(dispatch_get_main_queue()) {
                self.refreshButton.enabled = true
                if success {
                    self.setLastUpdatedLabel()
                }
                else {
                    var errorMsg = ""
                    if let safeErrorMessage = errorMessage {
                        errorMsg = safeErrorMessage
                    }
                    
                    let alertController = UIAlertController(title: "Update Failed", message: errorMsg, preferredStyle: .Alert)
                    let cancelAction = UIAlertAction(title: "OK", style: .Cancel, handler: nil)
                    alertController.addAction(cancelAction)
                    self.presentViewController(alertController, animated: true, completion: nil)
                }
            }
        })
    }
    
    // MARK: UITableViewDataSource
    func numberOfSectionsInTableView(tableView: UITableView) -> Int {
        
        return 1
    }
    
    func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        
        if let currencyCount = fetchedResultsController?.fetchedObjects?.count {
            return currencyCount
        }
        
        return 0
    }
    
    func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
        
        let currencyCell: CurrencyTableViewCell = tableView.dequeueReusableCellWithIdentifier(CurrencyTableViewCell.ReuseIdentifier) as! CurrencyTableViewCell
        
        if let exchangeCurrency = self.fetchedResultsController?.objectAtIndexPath(indexPath) as? ExchangeCurrency {
                currencyCell.configureCell(exchangeCurrency)
        }
        
        return currencyCell
    }
    
    func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
        
        return 60.0
    }
 
    // MARK: NSFetchedResultsControllerDelegate
    func controllerWillChangeContent(controller: NSFetchedResultsController) {
        self.tableView.beginUpdates()
    }
    
    func controller(controller: NSFetchedResultsController, didChangeObject anObject: AnyObject, atIndexPath indexPath: NSIndexPath?, forChangeType type: NSFetchedResultsChangeType, newIndexPath: NSIndexPath?) {
        switch type {
        case .Insert:
            guard let newIndexPath = newIndexPath else {
                fatalError("BitcoinExchangeRateViewController - CONTROLLERDIDCHANGEOBJECT CALLED WITH NIL INDEXPATH")
            }
            tableView.insertRowsAtIndexPaths([newIndexPath], withRowAnimation: UITableViewRowAnimation.Fade)
            
        case .Update:
            guard let indexPath = indexPath else {
                fatalError("BitcoinExchangeRateViewController - CONTROLLERDIDCHANGEOBJECT CALLED WITH NIL INDEXPATH")
            }
            guard let currencyCell = tableView.cellForRowAtIndexPath(indexPath) as? CurrencyTableViewCell else {
                fatalError("BitcoinExchangeRateViewController - COULD NOT GET CELL FOR INDEX PATH")
            }
            guard let exchangeCurrency = anObject as? ExchangeCurrency else {
                fatalError("BitcoinExchangeRateViewController - CONTROLLERDIDCHANGEOBJECT CALLED WITH ANOBJECT NOT AN EXCHANGECURRENCY OBJECT")
            }
            currencyCell.configureCell(exchangeCurrency)
            
        case .Delete:
            break// this tableview does not enable deleting cells
            
        case .Move:
            break// this tableview does not enable moving cells
        }
    }
    
    func controllerDidChangeContent(controller: NSFetchedResultsController) {
        self.tableView.endUpdates()
    }
    
    func setLastUpdatedLabel() {
        if let lastUpdatedString = NSUserDefaults.standardUserDefaults().objectForKey(DataControllerUserDefaultKeys.lastUpdatedKey.rawValue) as? String {
            lastUpdatedLabel.text = lastUpdatedString
        }
    }
}

Minus the comment header the entire view controller is ~150 lines of code which consists mostly of the UITableViewDataSource and NSFetchedResultsControllerDelegate methods. I didn’t need anything in the UITableViewDelegate so its not included at all.

The approach of using a DataController that initiates on a background thread does add just a little complexity. The view will be loaded and displayed most likely before the AppDelegate has had a chance to call configureWithDataController. That means the first calls to the UITableViewDataSource functions will not have the NSFetchedResultsController available. Swift makes this incredibly easy to handle by simply checking that the NSFetchedResultsController is ready otherwise return 0 for the number of rows (the app only has one section so that can always just return 1).

func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        
    if let currencyCount = fetchedResultsController?.fetchedObjects?.count {
        return currencyCount
    }
    
    return 0
}

The configureWithDataController function gets called when Core Data is ready to be used. All it really does is setup the NSFetchedResultsController:

func configureWithDataController(dataController: DataController) {
        
    self.dataController = dataController
        
    let currencyFetchRequest = NSFetchRequest(entityName: "ExchangeCurrency")
    let sortDescriptor = NSSortDescriptor(key: "code", ascending: false)
        
    currencyFetchRequest.sortDescriptors = [sortDescriptor]
        
    if let managedObjectContext = self.dataController?.managedObjectContext {
        self.fetchedResultsController = NSFetchedResultsController(
            fetchRequest: currencyFetchRequest,
            managedObjectContext: managedObjectContext,
            sectionNameKeyPath: nil,
            cacheName: nil)
    }
        
    self.fetchedResultsController?.delegate = self
        
    do {
        try self.fetchedResultsController?.performFetch()
    }
    catch let error as NSError {
        fatalError("BitcoinExchangeRateViewController - NSFETCHEDRESULTSCONTROLLER FETCH FAILED: \(error.localizedDescription)")
    }
        
    self.tableView.reloadData()
    fetchExchangeRateData()
}

The only other code of note in this class is the call to fetch the data. It uses the networkOperationResult closure to know if the data was successfully retrieved, parsed and stored or if it needs to show the user an error:

internal func fetchExchangeRateData() {
    self.refreshButton.enabled = false
    dataController?.fetchExchangeRateData({
        (success: Bool, errorMessage: String?) in
            
        dispatch_async(dispatch_get_main_queue()) {
            self.refreshButton.enabled = true
            if success {
                self.setLastUpdatedLabel()
            }
            else {
                var errorMsg = ""
                if let safeErrorMessage = errorMessage {
                    errorMsg = safeErrorMessage
                }
                    
                let alertController = UIAlertController(title: "Update Failed", message: errorMsg, preferredStyle: .Alert)
                let cancelAction = UIAlertAction(title: "OK", style: .Cancel, handler: nil)
                alertController.addAction(cancelAction)
                self.presentViewController(alertController, animated: true, completion: nil)

            }

        }

    })

}

The NSFetchedResultsControllerDelegate functions are pretty standard. The code to configure the labels of the cell is contained in a UITableViewCell subclass which is something else Marcus mentions but has been a habit of mine since I started writing iOS apps:

import UIKit
 
public class CurrencyTableViewCell: UITableViewCell {
 
    class var ReuseIdentifier: String { return "CurrencyCellReuseID" }
    
    @IBOutlet var currencyNameLabel: UILabel!
    @IBOutlet var currencySymbolLabel: UILabel!
    @IBOutlet var currencyRateLabel: UILabel!
 
    func configureCell(currency: ExchangeCurrency) {
        
        currencyNameLabel.text = currency.name
        currencySymbolLabel.text = currency.symbol
        currencyRateLabel.text = currency.rate
    }
}

I couldn’t find a way to have a static string like I would typically do for a cell’s reuse identifier so I did it with a class level var. Perhaps there’s a nicer way to do that.

Conclusion

This is a pretty small and hopefully easy to understand example of how to use NSOperation for networking along with a DataController setup for Core Data. One thing I’ve heard, read and been told since Swift was introduced was to try and not write Swift like you’re writing Objective-C. I know many are very excited about functional programming in Swift but I’m not quite there yet. Perhaps this code is more Objective-C style then Swift but on the other had things like optionals, guard statements and exception handling are not things we had with Objective-C. To me its easy to see how Swift can help you write apps with a lot less bugs and better tools to handle unexpected situations. I personally think I use fatalError a bit to much in this sample but I also used NSAssert quite a bit in Objective-C.

Again, any and all feedback is appreciated! Let me know what you think!

Bitcoin Exchange Rate on BitBucket

really

Written by Nick Harris

September 30, 2015 at 4:12 am

Core Data and Enterprise iPhone Applications – Protecting Your Data

with 30 comments

Introduction

NewsGator’s primary software offering is a product called Social Sites which basically adds social networking aspects to Microsoft SharePoint. My role in this product is to create the iOS device apps that interact with the server. My app is pulling in a lot of data that an enterprise customer considers *very* sensitive so data protection is the first thing many CIOs ask about when considering Social Sites. This post is to layout both how iOS protects data on the device so our prospective clients have a better idea about how it works, along with some additional steps developers can take to further protect their data.

Much of what I’ll discuss was covered in Session 209 at WWDC, but since that information is under NDA, I’ll stick to information that’s available in the public domain (including this post about iPhone 3GS and this post about iOS4). But if you have access to the WWDC videos, Session 209 is a great resource.

Data Encryption Starting with iPhone 3GS

Social Sites uses Core Data with a SQLite store type for storage and persistence. When the app first runs, it creates a Social_Sites.sqlite database file in its sand boxed documents directory on the device file system.

With the 3GS, any data written to the filesystem is encrypted using hardware encryption. By simply creating the Social_Sites.sqlite file on the file system, the data stored in it is already encrypted. This encryption also allows for the Social Sites data to be instantaneously unavailable when you use Remote Wipe. I say unavailable because the remote wipe doesn’t actually overwrite the encrypted data on the filesystem, but instead overwrites the hardware key used to encrypt/decrypt it. But still, the important thing is that the data on disk is encrypted.

This type of data encryption is also used on the current iPads.

Data Encryption on iOS 4

The way hardware encryption works on iOS4 has been greatly improved. I can’t find any public document from Apple that explains the improvements in detail, but their iOS 4: Understanding data protection support page gives a hint with “Data protection enhances the built-in hardware encryption by protecting the hardware encryption keys with your passcode.” I’ll just say that hardware data encryption on the 3GS and iPad use a single hardware encryption key. The WWDC session goes into great detail about the under the hood improvements. The main takeaway for a CIO though is that the Social Sites sqlite datafile on iOS4 has stronger encryption than it did in the past.

Further Protection – NSFileProtectionComplete

In iOS4, Apple introduced data protection. It gives you the ability to encrypt the hardware keys used to encrypt your files and to erase those keys when the system is locked – leaving your file unreadable until the phone is unlocked and the keys are regenerated.

This level of protection is not built in by default though. The user has to enable it (or an IT department can enable it and force users to have it on) and your code must set an attribute on your Core Data sqlite store as well.

Steps for the user to enable data protection are outlined on the iOS 4: Understanding data protection support page. You need to have the passcode turned on since this is what the system will use to generate the encryption for the keys.

As a developer, you need to set the NSFileProtection level of your sqlite store to NSFileProtectionComplete (by default it will be NSFileProtectionNone). You can do this right after you instantiate your persistent store coordinator:

- (NSPersistentStoreCoordinator *)persistentStoreCoordinator {
    if (persistentStoreCoordinator != nil) {
        return persistentStoreCoordinator;
    }

    NSString *storePath = [[self applicationDocumentsDirectory] stringByAppendingPathComponent: @"Social_Sites.sqlite"];
    NSURL *storeUrl = [NSURL fileURLWithPath:storePath ];

    NSError *error = nil;
    persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel: [self managedObjectModel]];
    if (![persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeUrl options:nil error:&error]) {
        // Handle error
    }

    if(RSRunningOnOS4OrBetter())
    {
        NSDictionary *fileAttributes = [NSDictionary dictionaryWithObject:NSFileProtectionComplete forKey:NSFileProtectionKey];
        if (![[NSFileManager defaultManager] setAttributes:fileAttributes ofItemAtPath:storePath error:&error]) {
            // Handle error
        }
    }

    return persistentStoreCoordinator;
}

BOOL RSRunningOnOS4OrBetter(void) {
    static BOOL didCheckIfOnOS4 = NO;
    static BOOL runningOnOS4OrBetter = NO;

    if (!didCheckIfOnOS4) {
        NSString *systemVersion = [UIDevice currentDevice].systemVersion;
        NSInteger majorSystemVersion = 3;

        if (systemVersion != nil && [systemVersion length] > 0) { //Can't imagine it would be empty, but.
            NSString *firstCharacter = [systemVersion substringToIndex:1];
            majorSystemVersion = [firstCharacter integerValue];			
        }

        runningOnOS4OrBetter = (majorSystemVersion >= 4);
        didCheckIfOnOS4 = YES;
    }
    return runningOnOS4OrBetter;
}

Considerations

If your application needs your Core Data store in any background processing, then you cannot use data protection. Any attempt to access files that are NSFileProtectionComplete will cause an exception.

There are application delegates applicationProtectedDataWillBecomeUnavailable and applicationProtectedDataDidBecomeAvailable as well as notifications UIApplicationProtectedDataWillBecomeUnavailable and UIApplicationProtectedDataWillBecomeAvailable that you can use to determine what state your protected data is in.

You could then in theory keep unprotected data in memory in your background processing then write it into your Core Data store when protected data becomes available again.

You can find more information in the iPhone Application Programming Guide.

Conclusions

My answer to any CIO asking would be to ensure any device using Social Sites be a 3GS, an iPad or an iPhone 4. I would also encourage enforcing passcode and data protection for any device running iOS4. These steps ensure that the SQLite data store for Social Sites is hardware encrypted with remote wipe capabilities and further protected with iOS4 and data protection.

UPDATE:
Here’s the link to the iPhone Configuration Utility 3.0 which can be used to “create, maintain, encrypt, and install configuration profiles, track and install provisioning profiles and authorized applications, and capture device information including console logs.”

Written by Nick Harris

July 14, 2010 at 8:23 pm

Posted in Code, Core Data

Tagged with ,

UIAlertView with an NSObject

with 3 comments

Here’s my situation… I have an operation that gets performed in the background. If that operation fails I need to alert the user and see what they would like to do about it. If they want to retry it, I just add the operation back onto the queue.

To do this, I’m sending a notification that the operation failed and am including the operation as the notifications object. I then set up a responder on my view controller for that notification. It creates a UIAlertView to ask the user what to do.

If the user selects “Retry”, it would be nice to have the operation right there on the alert view in alertView:clickedButtonAtIndex: so that I can just add it back to the operation queue. But there doesn’t seem to be any properties on UIAlertView for an arbitrary object. Instead I’ve subclassed UIAlertView and simply added a NSObject property.

Pretty easy code wise, just wondering if I’m missing something on UIAlertView that would handle the same thing.

Written by Nick Harris

March 26, 2010 at 5:00 pm

Posted in Code

Tagged with