Generic function with structs - ios

I try to create reusable function. It select data only for current week. But I need use this function few times in my app, so I've decided to do it generic.
I have two structs
struct BodyWeightCalendarModel {
let weight: Float
let date: Date
}
struct RetrievedWorkoutsByExercise {
let exerciseTitle: String
let sets: Int
let maxWeight: Int
let maxReps: Int
let date: Date
let volume: Int
}
func loopForWeek<T> (data: [T], completionHandler: ([T]) -> Void) {
let arrayForPeriod2: [T] = []
for value in data where value.date >= (calendar.currentWeekBoundary()?.startOfWeek)! && value.date <= (calendar.currentWeekBoundary()?.endOfWeek)! {
arrayForPeriod.append(value)
}
completionHandler(arrayForPeriod2)
}
How to get access to data values? I can't get access through "value.data".
So I want to use this function for different struct (but all this structs needs to have field "date").

As mentioned in other answers using a protocol with date property makes most sense in your case. However theoretically you could also use keypaths to achieve that. You could make a function that takes any instances and gets dates from them in a similar way as below:
func printDates(data: [Any], keyPath:AnyKeyPath) {
for value in data {
if let date = value[keyPath: keyPath] as? Date {
print("date = \(date)")
}
}
}
and then pass for example your BodyWeightCalendarModel instances as below:
let one = BodyWeightCalendarModel(date: Date(), weight: 1)
let two = BodyWeightCalendarModel(date: Date(), weight: 2)
printDates(data: [one, two], keyPath: \BodyWeightCalendarModel.date)
But still a protocol makes more sense in your case.

The reason you can't access value.date is that your function knows nothing about T. Your function declares T but it doesn't constrain it. To the compiler, T can be anything.
You need to create a protocol that tells your function what to expect, and make your structs conform to it:
protocol Timed { // You might find a better name
var date: Date { get }
}
struct BodyWeightCalendarModel: Timed {
let date: Date
...
}
struct RetrievedWorkoutsByExercise: Timed {
let date: Date
...
}
Now you can constrain T to Timed and your function will know that value has a date property.
func loopForWeek<T: Timed> (data: [T], completionHandler: ([T]) -> Void) {

What you're trying to do is close. A Generic value doesn't necessarily have a value on it called date so this won't work. What you can do instead is create a protocol that has a variable of date on it. With that protocol you can do something neat with an extension.
protocol Dated {
var date: Date { get set }
}
extension Dated {
func loopForWeek() -> [Dated] {
let arrayForPeriod2: [Dated] = []
for value in date >= (calendar.currentWeekBoundary()?.startOfWeek)! && value.date <= (calendar.currentWeekBoundary()?.endOfWeek)! {
arrayForPeriod.append(value)
}
return arrayForPeriod2
}
}
Then we can make your two structs use that protocol like:
struct BodyWeightCalendarModel: Dated {
let date: Date
}
struct RetrievedWorkoutsByExercise: Dated {
let date: Date
}
And they can call the function like:
let workouts = RetrievedWorkoutsByExercise()
let result = workouts.loopForWeek()

Related

Swift workaround for protocols comparison

I know comparing protocols doesn't make any sense but my situation is dictated by choices and decisions taken before me.
A table view's data source is an array of RowViewModel.
protocol RowViewModel {}
there's nothing in there (yet) to make it even conform to Equatable.
Then my table has different cells, all of which implement that protocol:
func getCells() -> [RowViewModel] {
var rows = [RowViewModel]()
rows.append(Cell1ViewModel())
rows.append(Cell2ViewModel())
rows.append(Cell3ViewModel())
return rows
}
Cell's view model:
class Cell1ViewModel: RowViewModel {
var cellTitle: String
...
}
This structure is convenient but it now shoots me in the back because I now need to calculate delta to send specific tableView indexes to insert / delete rows. To calculate delta I need RowViewModel to conform to Equatable, which is possible but seems like a workaround that defies the initial point of using this approach. I'd like to do something like this:
let oldList = rows
let newList = getCells()
let deltaAdded = newList.filter { !oldList.contains($0) }.compactMap { newList.firstIndex(of: $0) }
let deltaRemoved = oldList.filter { !newList.contains($0) }.compactMap { oldList.firstIndex(of: $0) }
What is the best practice here? Is there a way to write a comparison function for concrete types conforming to the RowViewModel?
As I told in comment you would have something like:
class CellViewModel1: Equatable {
// classes need explicit equatable conformance.
static func == (lhs: CellViewModel1, rhs: CellViewModel1) -> Bool {
// your implementation
return true
}
}
enum RowViewModel: Equatable {
// enum automatically is Equatable as long as all cases have Equatable associated types
case cell1(CellViewModel1)
}
func test() {
let oldList = [RowViewModel]()
let newList = [RowViewModel]()
let deltaAdded = newList.filter { !oldList.contains($0) }.compactMap { newList.firstIndex(of: $0) }
let deltaRemoved = oldList.filter { !newList.contains($0) }.compactMap { oldList.firstIndex(of: $0) }
}
Notice that both enum and ViewModels must conform to Equatable.
Still not 100% sure if this fits your necessities.

How to design a Settings model to generically get + set values for all UserDefaults keys

I’m setting up my Settings class which gets/sets values from UserDefaults. I wish for as much of the code to be generic to minimise effort involved whenever a new setting is introduced (I have many as it is, and I expect many more in the future too), thereby reducing the probability of any human errors/bugs.
I came across this answer to create a wrapper for UserDefaults:
struct UserDefaultsManager {
static var userDefaults: UserDefaults = .standard
static func set<T>(_ value: T, forKey: String) where T: Encodable {
if let encoded = try? JSONEncoder().encode(value) {
userDefaults.set(encoded, forKey: forKey)
}
}
static func get<T>(forKey: String) -> T? where T: Decodable {
guard let data = userDefaults.value(forKey: forKey) as? Data,
let decodedData = try? JSONDecoder().decode(T.self, from: data)
else { return nil }
return decodedData
}
}
I’ve created an enum to store all the setting keys:
enum SettingKeys: String {
case TimeFormat = "TimeFormat"
// and many more
}
And each setting has their own enum:
enum TimeFormat: String, Codable {
case ampm = "12"
case mili = "24"
}
In this simplified Settings class example, you can see that when it’s instantiated I initialise the value of every setting defined. I check if its setting key was found in UserDefaults: if yes, I use the value found, otherwise I set it a default and save it for the first time to UserDefaults.
class Settings {
var timeFormat: TimeFormat!
init() {
self.initTimeFormat()
}
func initTimeFormat() {
guard let format: TimeFormat = UserDefaultsManager.get(forKey: SettingKeys.TimeFormat.rawValue) else {
self.setTimeFormat(to: .ampm)
return
}
self.timeFormat = format
}
func setTimeFormat(to format: TimeFormat) {
UserDefaultsManager.set(format, forKey: SettingKeys.TimeFormat.rawValue)
self.timeFormat = format
}
}
This is working fine, and pretty straightforward. However, thinking ahead, this will be tedious (and therefore error-prone) to replicate for every setting in this app (and every other I look to do in the future). Is there a way for the init<name of setting>() and set<name of setting>() to be generalised, whereby I pass it a key for a setting (and nothing more), and it handles everything else?
I’ve identified every setting to have these shared elements:
settings key (e.g. SettingsKey.TimeFormat in my example)
unique type (could be AnyObject, String, Int, Bool etc. e.g. enum TimeFormat in my example)
unique property (e.g. timeFormat in my example)
default value (e.g. TimeFormat.ampm in my example)
Thanks as always!
UPDATE:
This may or may not make a difference, but considering all settings will have a default value, I realised they don’t need to be a non-optional optional but can have the default set at initialisation.
That is, change:
var timeFormat: TimeFormat!
func initTimeFormat() {
guard let format: TimeFormat = UserDefaultsManager.get(forKey: SettingKeys.TimeFormat.rawValue) else {
self.setTimeFormat(to: .ampm)
return
}
self.timeFormat = format
}
To:
var timeFormat: TimeFormat = .ampm
func initTimeFormat() {
guard let format: TimeFormat = UserDefaultsManager.get(forKey: SettingKeys.TimeFormat.rawValue) else {
UserDefaultsManager.set(self.timeFormat, forKey: SettingKeys.TimeFormat.rawValue)
return
}
self.timeFormat = format
}
The setTimeFormat() function is still needed when its value is changed by the user in-app.
Note that your settings don't have to be stored properties. They can be computed properties:
var timeFormat: TimeFormat {
get {
UserDefaultsManager.get(forKey: SettingKeys.TimeFormat.rawValue) ?? .ampm
}
set {
UserDefaultsManager.set(newValue, forKey: SettingKeys.TimeFormat.rawValue)
}
}
You don't have to write as much code this way, when you want to add a new setting. Since you will just be adding a new setting key enum case, and a computed property.
Further more, this computed property can be wrapped into a property wrapper:
#propertyWrapper
struct FromUserDefaults<T: Codable> {
let key: SettingKeys
let defaultValue: T
init(key: SettingKeys, defaultValue: T) {
self.key = key
self.defaultValue = defaultValue
}
var wrappedValue: T {
get {
UserDefaultsManager.get(forKey: key.rawValue) ?? defaultValue
}
set {
UserDefaultsManager.set(newValue, forKey: key.rawValue)
}
}
}
Usage:
class Settings {
#FromUserDefaults(key: .TimeFormat, defaultValue: .ampm)
var timeFormat: TimeFormat
}
Now to add a new setting, you just need to write a new enum case for the key, and two more lines of code.

Is there performance/compiler benefits when using tuple vs struct whenever possible?

Is there performance/compiler benefits when using tuple vs struct whenever possible?
For example in this case where
you don't need protocol conformance,
you don't need functions,
all variables are readonly.
.
typealias SomeModel = (
name: String,
id: String
)
vs
struct SomeModel {
let name: String
let id: String
}
I don't have a formal answer to your question but here is a very basic test comparing creation times.
import Foundation
func duration(_ block: () -> ()) -> TimeInterval {
let start = CFAbsoluteTimeGetCurrent()
block()
let end = CFAbsoluteTimeGetCurrent()
return end - start
}
struct Person {
let first: String
let last: String
}
let total = 99999
let structTest = duration {
for _ in (0...total) {
let person = Person(first: "", last: "")
}
}
let tupleTest = duration {
for _ in (0...total) {
let person = (first: "", last: "")
}
}
print(structTest, tupleTest)
The results were:
1.3234739303588867 1.1551849842071533
Create following function to calculate a time for operations. Put blocks of your code as its argument and try to see a difference by yourself:
func duration(_ block: () -> ()) -> TimeInterval {
let startTime = Date()
block()
return Date().timeIntervalSince(startTime)
}

what is the best way to reuse API manager class and store data so that it is reusable

I have a PageViewController which creates instances of DataViewController, and this dataViewController needs to do some API called (from manager Class) then use that data to populate its UI. I.E. a page is added, the dataViewController gets its datasource and uses that to tell the API what to get.
The problem is, as I can add multiple pages, and I need for example data to form a graph for each page, having to do the API call every time the view controller loads I feel is unness esary, and that there must be a better way to store the data for the life of the session once it have been retrieved. as each instance of dataViewController uses the same API Managers, I refresh them clearing any data they have, and start the API call again for the page that loaded, this can sometimes get data mixed up, displaying a mixture of information from two pages or displaying the wrong data on the wrong page. Here is an example of one of my API managers which uses the dataObject of the dataViewController to get chart data:
import Foundation
struct Root: Codable {
let prices: [Price]
}
struct Price: Codable {
let date: Date
let price: Double
}
class CGCharts {
var priceData = [Price]()
var graphPrices: [Double] = []
var graphTimes: [String] = []
static let shared = CGCharts()
var defaultCurrency = UserDefaults.standard.string(forKey: "DefaultCurrency")!
var coin = ""
var currency = ""
var days = ""
enum GraphStatus {
case day, week, month
}
var graphSetup = GraphStatus.day
func getData(arr: Bool, completion: #escaping (Bool) -> ()) {
switch graphSetup {
case .day:
days = "1"
case .week:
days = "14"
case .month:
days = "30"
}
let urlJSON = "https://api.coingecko.com/api/v3/coins/\(coin)/market_chart?vs_currency=\(defaultCurrency)&days=\(days)"
guard let url = URL(string: urlJSON) else { return }
URLSession.shared.dataTask(with: url) { (data, response, err) in
guard let data = data else { return }
do {
let prices = try JSONDecoder().decode(Root.self, from: data).prices
print(prices.first!.date.description(with:.current)) // "Saturday, September 1, 2018 at 6:25:38 PM Brasilia Standard Time\n"
print(prices[0].price)
self.priceData = prices
for element in prices {
self.graphPrices.append(element.price)
}
for element in prices {
self.graphTimes.append(element.date.description(with: nil))
}
completion(arr)
} catch {
print(error)
}
}.resume()
}
func refresh(arr: Bool, completion: #escaping (Bool) -> ()) {
defaultCurrency = UserDefaults.standard.string(forKey: "DefaultCurrency")!
graphPrices = []
graphTimes = []
completion(arr)
}
}
Here as you can see I am getting the data, using codable to parse and in this case the best way I could find to seperate the data was to use the two arrays, I basically want to have thoes 2 arrays stored in memory for that specific dataObject, so that when a page with the same dataObject loads in the pageView it will first check if that data already exists, and use that, instead of getting new information everytime and me having to clear the class. Hope I am making sense, Thank you.
After researching more about NSCache, I learned you can save data to the disk or cache and access it again easily. I found this excellent storage class made by Saoud M. Rizwan here
which makes it super easy to store structs:
var messages = [Message]()
for i in 1...4 {
let newMessage = Message(title: "Message \(i)", body: "...")
messages.append(newMessage)
}
Storage.store(messages, to: .documents, as: "messages.json")
and retrieve them:
let messagesFromDisk = Storage.retrieve("messages.json", from: .documents, as: [Message].self)
Perfect for storing data from an API so that you don't have to make so many API calls, which is especially useful if your requests are limited. In my case, I use the method for checking if storage exists, and if it does I use that to load my data, and if not then I do an api call, store the data using this class, and then run the check again to see that the data is there and use that, It may not be the most efficient but it works.
my example:
var prices: [Double] = []
var days: [String] = []
var priceData = [Price]()
var graphDataExists = false
func checkGraphStorage() {
if Storage.fileExists("\(dataObject)GraphPrices", in:
Storage.Directory.caches) {
print("UsingSavedData")
self.priceData = Storage.retrieve("\(dataObject)GraphPrices", from:
Storage.Directory.caches, as: [Price].self)
if self.prices.isEmpty {
for element in self.priceData {
self.prices.append(element.price)
}
for element in self.priceData {
self.days.append(element.date.description(with: nil))
graphDataExists = true
}
} else {
}
DispatchQueue.main.async {
self.chartView.animate(xAxisDuration: 4.0)
self.lineChartUpdate(dataPoints: self.days, values: self.prices)
}
} else {
self.prepareGraph(arr: true) { (success) in
}
}
}

How can I return all values as NSArray? from query in completion block

In QueryHK I run a HealthKit query for steps and the corresponding date. I return the values in a completion handler. In ViewController I declare the completion. My problem is that the method only returns the last value from the iteration sample in samples.
Question: I want all of the data returned in the completion, not just the last value.. How can I return all the data from the query in an NSArray ?
QueryHK.swift:
import UIKit
import HealthKit
class QueryHK: NSObject {
var steps = Double()
var date = NSDate()
func performHKQuery (completion: (steps: Double, date: NSDate) -> Void){
let healthKitManager = HealthKitManager.sharedInstance
let stepsSample = HKQuantityType.quantityTypeForIdentifier(HKQuantityTypeIdentifierStepCount)
let stepsUnit = HKUnit.countUnit()
let sampleQuery = HKSampleQuery(
sampleType: stepsSample,
predicate: nil,
limit: 0,
sortDescriptors: nil)
{
(sampleQuery, samples, error) in
for sample in samples as [HKQuantitySample]
{
self.steps = sample.quantity.doubleValueForUnit(stepsUnit)
self.date = sample.startDate
}
// Calling the completion handler with the results here
completion(steps: self.steps, date: self.date)
}
healthKitManager.healthStore.executeQuery(sampleQuery)
}
}
ViewController:
import UIKit
class ViewController: UIViewController {
var dt = NSDate()
var stp = Double()
var query = QueryHK()
override func viewDidLoad() {
super.viewDidLoad()
printStepsAndDate()
}
func printStepsAndDate() {
query.performHKQuery() {
(steps, date) in
self.stp = steps
self.dt = date
println(self.stp)
println(self.dt)
}
}
}
Have your completion handler receive an array of steps/date pairs:
completion: ([(steps: Double, date: NSDate)]) -> Void
(you could pass two arrays, one of steps and one of dates, but I feel like it’s clearer to pass an array of pairs since the two are tied together)
Then build an array of pairs of step counts and dates:
if let samples = samples as? [HKQuantitySample] {
let steps = samples.map { (sample: HKQuantitySample)->(steps: Double, date: NSDate) in
let stepCount = sample.quantity.doubleValueForUnit(stepsUnit)
let date = sample.startDate
return (steps: stepCount, date: date)
}
completion(steps)
}
If you want the query class to retain this information as well, make the member variable an array of the same type and store the result in that as well as pass it to the callback.

Resources