Wednesday, August 8, 2018

Core Data: Fetch Requests

If you are interested in more iOS tutorials, please visit my portfolio site at https://suzhoupanda.github.io/. Some of my games and apps haven’t been updated for a while and may be a little buggy, but they are free, so feel free to download and enjoy.

This tutorial is a continuation of series in which we develop an API client class for connecting to the Quandl REST API endpoint (click here) and subsequently convert the returned JSON data to NSManagedObjects that can then be persisted via CoreData (click here). Hence, the following tutorial assumes that we have already populated our CoreData persistent store (i.e. SQLite backing store) with stock quote data, enabling us to make fetch requests with a DataFetcher class.
In this tutorial, we will continue to extend the DataFetcher class that will be used to fetch stock quote data from our persistent store. This stock quote data is first downloaded in JSON format from the Quandl api. The JSON data is parsed and used to create NSManagedObjects (here subclassed with the arbitrary name DailyQuoteSummary) which will encapsulate the stock quote data and persist the data in the SQLite Persistent store managed by our CoreData stack.

For review, the we define a class DataFetcher, which has stored property coreDataStack and a computed property managedContext, the former being a wrapper class that we defined previously for our NSPersistent store and the latter being a computed property by which we access the NSManagedObject context, which we use to save and fetch data to said persistent store. In addition, we define two initializers, one taking the model name for .xcdatamodeld file, the other being a default initializer which automatically provides the name of our .momd file (in this case, “EDGAR_Financials_Helper”, where EDGAR refers to the SEC reporting system for company financial statements. You can use our own name as you please when defining your own data models.).

import Foundation
import CoreData

class DataFetcher{

    var coreDataStack: CoreDataStack

    var managedContext: NSManagedObjectContext{
        return coreDataStack.managedContext
    }
    
    init(withModelName modelName: String){
        self.coreDataStack = CoreDataStack(withModelName: modelName)
    }
    
    init(){
        self.coreDataStack = CoreDataStack(withModelName: "EDGAR_Financials_Helper")
    }

}
This DateFetcher class will house all the methods that we will use to make customized fetch requests for getting data from our persistent store, which contains the daily stock quote data for different companies.
Previously, we defined two subclasses of NSManaged object (named StockQuotePeriod and DailyQuoteSummary) whose attributes are shown below:





As you can see, the StockQuotePeriod entity has a to-many relationship with the DailyQuoteSummary entity, since the StockQuotePeriod is able to contain multiple DailyQuoteSummary objects, each of which represents the stock quote data for a given day. The StockQuotePeriod entity is intended to represent a period of time defined by a start date and end date for a specific company name, which is a String corresponding to the ticker symbol of the company whose data is being fetched.
In order to make it easier to use ticker symbols as arguments for our functions, we will define an enum TickerSymbol with a String-type raw value. We’ll use the full name of the company for the enum case, and the abbreviated ticker symbol for the raw value. This way, it will be easy to look up the name of the company using Xcode’s autocomplete feature, and we won’t have to worry about how to enter the abbreviated ticker symbol in our url query strings, since our class methods will take care of this for us.
enum TickerSymbol: String{
    
 
    
    case Agilent_Technologies = "A"
    case Aloca_Corporation = "AA"
    case AAC_Holdings = "AAC"
    case Aarons_Incorporated = "AAN"
    case American_Assets_Trust = "AAT"
    case Advantage_Oil_And_Gas = "AAV"
    case Alliancebernstein_Holding_LP = "AB"
    case Abbot_Laboratories = "ABT"
    case Ameren_Corporation = "AEE"
    case Alexanders = "ALX"
    case American_Express = "AXP"
    case Avnet = "AVT"
    case Arrow_Electronics = "ARW"
    case American_Tower = "AMT"
    case AutoZone = "AZO"
    
    
    case Barnes_Group = "B"
    case Big_Lots = "BIG"
    case Boeing_Company = "BA"
    case Bunge_Ltd = "BG"
    case Blackrock_Global = "BGT"
    case Barnes_And_Noble = "BKS"
    case Buckle_Inc = "BKE"
    case Burlington_Stores_Inc = "BURL"
    case Peabody_Energy_Corp = "BTU"
    
    case Citigroup_Incorporated = "C"
    case Comcast_Corp = "CCV"
    case Coeur_Mining_Inc = "CDE"
    case Celanese_Corp = "CE"
    case China_Mobile_Hong_Kong_Ltd = "CHL"
    case Chesapeake_Energy_Corp = "CHK"
    case Chegg_Inc = "CHGG"
    case Celadon_Group = "CGI"
    case Cloudera_Inc = "CLDR"
    case Circor_International = "CIR"
    
    case Dominion_Resources = "D"
    case Delta_Airlines = "DAL"
    case Tableau_Software_Inc = "DATA"
    case Donaldson_Company = "DCI"
    case Dean_Foods_Companu = "DF"
    case Dillards = "DDS"
    case Walt_Disney_Company = "DIS"
    case Dolby_Laboratories = "DLB"
    case Physicians_Realty_Trust = "DOC"
    case Dycom_Industries = "DY"
    
    case Ellie_Mae_Inc = "ELLI"
    case Estee_Lauder_Companies = "EL"
    case Eastgroup_Properties = "EGP"
    case Energen_Corp = "EGN"
    
    case Ford_Motor_Company = "F"
    case Flagstar_Bancorp = "FBC"
    case Franklin_Covey_Company = "FC"
    case Fedex_Corp = "FDX"
    case Futurefuel_Corp = "FF"
    case First_Data_Corp = "FDC"
    case Ferro_Corp = "FOE"
    
    case General_Dynamics = "GD"
    case Godaddy_Inc = "GDDY"
    case Genesco_Inc = "GCO"
    case Guess_Inc = "GES"
    case Goldman_Sachs_MLP_Energy = "GER"
    case General_Mills = "GIS"
    case CGI_Group = "GIB"
    
    case Microsoft = "MSFT"
    case Oracle = "ORCL"
    case Hewlett_Packard = "HPQ"


    static let allTickerSymbols: [String] = [
        TickerSymbol.Agilent_Technologies.rawValue,
        TickerSymbol.Avnet.rawValue,
        TickerSymbol.AAC_Holdings.rawValue,
        TickerSymbol.AutoZone.rawValue,
        TickerSymbol.Fedex_Corp.rawValue
        ]
}
With the TickerSymbol enum defined above, let’s make an instance method that will be responsible for fetching all the daily stock quote data corresponding to a specified company. We use the ticker symbol enum type raw value to get the abbreviated ticker symbol, which in turn is used to narrow down the set of results obtained from our fetch request. The method is shown below:
func fetchAllDailyQuoteSummaryData(forTicker tickerSymbol: TickerSymbol) -> [DailyQuoteSummary]?{
        

        let fetchRequest: NSFetchRequest = 
        DailyQuoteSummary.fetchRequest()
        
        fetchRequest.predicate = NSPredicate(format: "%K LIKE %@", argumentArray: [#keyPath(DailyQuoteSummary.stockQuotePeriod.companyName),tickerSymbol.rawValue])
        
        do {
            
            let allSummaryData = try self.coreDataStack.managedContext.fetch(fetchRequest)
            
            return allSummaryData
            
        } catch let error as NSError {
            
            print("Error: unable to complete fetch request with error: \(error.localizedDescription)")
            return nil
            
        }

}

Okay, now that we have a way of fetching all of the daily stock quote data for a specific company, we can continue to define methods with more targeted NSPredicate expressions, so that we can narrow down the set of possible results for our fetch requests.
With that in mind, in our DataFetcher class, let’s first define a private helper function getFilterConditions, which will return an array of Boolean values. This function is a generic type, with a generic data type T shown in the angle brackets after the function definition. This generic type will also include a type constraint Comparable so that the generic type must conform to the Comparable protocol. The parameters for this function will include a stock quote attribute (i.e. high, low, adjusted high, adjusted low, etc.) here defined as attribute. This attribute is generic because sometimes it may be used to pass in the volume of stocks (which might be represented as an integer), while other times it may be used to pass in the high, low, adjusted high, or adjusted low values for the stock on a given day (which might better be represented as Float types). In addition to the attribute parameter, we define an optional tuple, which itself contains optional versions of the generic types defined in the angle brackets. This optional tuple will be used to pass in the minimum and maximum values that will define the define the range of values for a given stock quote attribute (i.e. high, low, close, etc.) for which we want to query our NSPersistent store and fetch data. The utility of this class will become more useful with further reading.
 

private func getFilterConditions(forAttribute attribute: T, forMinMaxTuple minMaxTuple: (T?,T?)?) -> [Bool]{
        
        var filterConditions = [Bool]()
        
        if let minMaxTuple = minMaxTuple{
            if let minValue = minMaxTuple.0{
                filterConditions.append(
                    attribute > minValue
                )
            }
            
            if let maxValue = minMaxTuple.1{
                filterConditions.append(
                    attribute < maxValue
                )
            }
            
        }
        
        return filterConditions
}
To make our code more readable, in our DataFetcher class, we can define typealiases that can represent tuples whose members have data types conforming to the Comparable protocol:
 
    typealias MinMaxFloat = (Float?,Float?)
    typealias MinMaxInt64 = (Int64?,Int64?)
    typealias MinMaxInt = (Int?,Int?)
    typealias MinMaxDouble = (Double?,Double?)

Now we will define a function getStockQuoteSummariesWithAttributeRanges, which will take as parameters the ticker symbol of the stock being looked up as well as optional tuples which will represent the range of values for which we want to narrow down the range of possible results obtained from our fetch request.
 

    func getStockQuoteSummariesWithAttributeRanges(forTickerSymbol tickerSymbol: TickerSymbol, forMinMaxOpen minMaxOpen: MinMaxFloat?, forMinMaxClose minMaxClose: MinMaxFloat?, forMinMaxHigh minMaxHigh: MinMaxFloat?, forMinMaxLow minMaxLow: MinMaxFloat?, forMinMaxAdjustedOpen minMaxAdjOpen: MinMaxFloat?, forMinMaxAdjustedClose minMaxAdjClose: MinMaxFloat?, forMinMaxAdjustedHigh minMaxAdjHigh: MinMaxFloat?, forMinMaxAdjustedLow minMaxAdjLow: MinMaxFloat?, forMinMaxVolume minMaxVolume: MinMaxInt64?, forMinMaxAdjusedVolume minMaxAdjustedVolume: MinMaxInt64?, forMinMaxExDividend minMaxExDividend: MinMaxInt64?) -> [DailyQuoteSummary]?{
        
        if let stockQuoteSummaries = fetchAllDailyQuoteSummaryData(forTicker: tickerSymbol){
            
            return stockQuoteSummaries.filter({
                
                stockQuoteSummary in
                
                var filterConditions = [Bool]()
                
                filterConditions += getFilterConditions(forAttribute: stockQuoteSummary.adjustedHigh, forMinMaxTuple: minMaxAdjHigh)
                
                filterConditions += getFilterConditions(forAttribute: stockQuoteSummary.adjustedLow, forMinMaxTuple: minMaxAdjLow)
                
                filterConditions += getFilterConditions(forAttribute: stockQuoteSummary.adjustedOpen, forMinMaxTuple: minMaxAdjOpen)
                
                
                filterConditions += getFilterConditions(forAttribute: stockQuoteSummary.adjustedClose, forMinMaxTuple: minMaxAdjClose)
                
                
                filterConditions += getFilterConditions(forAttribute: stockQuoteSummary.low, forMinMaxTuple: minMaxLow)
                
                
                filterConditions += getFilterConditions(forAttribute: stockQuoteSummary.high, forMinMaxTuple: minMaxHigh)
                
                filterConditions += getFilterConditions(forAttribute: stockQuoteSummary.close, forMinMaxTuple: minMaxClose)
                
                
                filterConditions += getFilterConditions(forAttribute: stockQuoteSummary.open, forMinMaxTuple: minMaxOpen)
                
                
                filterConditions += getFilterConditions(forAttribute: stockQuoteSummary.volume, forMinMaxTuple: minMaxVolume)
                
                filterConditions += getFilterConditions(forAttribute: stockQuoteSummary.adjustedVolume, forMinMaxTuple: minMaxAdjustedVolume)
                
                filterConditions += getFilterConditions(forAttribute: stockQuoteSummary.exDividend, forMinMaxTuple: minMaxExDividend)
                
                return filterConditions.reduce(true, {$0 && $1 })
                
            })
        }
            
        
        
        return nil
}
In the body of this function, we first use conditional binding to unwrap the array of DailyQuoteSummary objects obtained from the fetch request made using the fetchAllDailyQuoteSummaryData(forTickerSymbol:) method defined earlier. This method returns all the DailyQuoteSummary objects which contain stock quote data for individual days.
Then, within the if-bloc for our conditional binding statement, we call the filter method on the array of DailyQuoteSummary objects. The filter method takes a closure whose return type must of type Bool, and in the closure body we define an empty array filterConditions, which will be used for storing the predicate conditions that will be used to filter the array of DailyQuoteSummary data. A given predicate condition may or may not be appended to this array, depending on whether or not we passed in a value for an optional tuple whose members represent the minimum and maximum values that will be used to define the upper and lower bounds for the stock quote attribute for which are narrowing down our result set. At the end of the function body, we return filterConditions.reduce(true, {$0 && $1 }) to return the result of evaluating all of the predicate conditions that we appended to the array filterConditions.
Now, in the viewDidLoad function of ViewController.swift file, lets define a local variable to store a newly instantiated DataFetcher object:
 
            let dataFetcher = DataFetcher()
Then, call the method we just created (i.e. getStockQuoteSummariesWithAttributeRanges to fetch all the daily stock quote summaries, which are basically NSManagedObject subclasses) on the dataFetcher object, and then use conditional binding to unwrap the results of the fetch requests. We print out the results of this conditional binding to the console:
 
        if let dailyQuoteSummaries = dataFetcher.getStockQuoteSummariesWithAttributeRanges(forTickerSymbol: TickerSymbol.Fedex_Corp, forMinMaxOpen: nil, forMinMaxClose: nil, forMinMaxHigh: (80.0,83.0), forMinMaxLow: (78.0,82.0), forMinMaxAdjustedOpen: nil, forMinMaxAdjustedClose: nil, forMinMaxAdjustedHigh: (75.0,85.0), forMinMaxAdjustedLow: (70.0,75.0), forMinMaxVolume: nil, forMinMaxAdjusedVolume: nil, forMinMaxExDividend: nil){
            
            dailyQuoteSummaries.forEach({
                
                let date = $0.date
                
                let adjHigh = $0.adjustedHigh
                let adjLow = $0.adjustedLow
                
                let high = $0.high
                let low = $0.low
            
                
                print("\n For stock quote summary for date: \(date!), \n the adjusted high is: \(adjHigh), \n adjusted low is: \(adjLow), \n high is: \(high), low is: \(low)")
            })

            
        }
        
    
You should see something similar to what is shown below in the console. Each of the values for high, low, adjusted high, and adjusted low should fall within the ranges bounded by the upper and lower values passed in the optional tuples for the method.


Voila! You’ve now learned how to use generics, along with array methods like filter and reduce, to define functions for making customizable and detailed fetch requests.
If you want to check out some of my free apps and games, please check out my portfolio site at https://suzhoupanda.github.io/. Please note, some of the games may be a little buggy, I haven’t updated them for a while, and they are also intended for instruction purposes. So if you decide to leave a review, please take this into account

No comments:

Post a Comment