swift 3.1 How to sum async fetch properties - ios

I cannot mace a count of a single property in an asynchronous fetch, I'd like to summ all expenseAmount for given async fetch but cannot apply for in pattern
my entity:
extension Expense {
#nonobjc public class func fetchRequest() -> NSFetchRequest<Expense> {
return NSFetchRequest<Expense>(entityName: "Expense");
}
#NSManaged public var expenseAmount: Double
#NSManaged public var expenseDate: NSDate?
#NSManaged public var expenseOwner: String?
#NSManaged public var expenseTag: String?
}
in my view controller I call this func in my viewDidLoad, how could I select and summ the expenseAmount values for all fetched entities?
func makeAsyncFetch() {
guard let appDelegate = UIApplication.shared.delegate as? AppDelegate else {
return
}
managedContext = appDelegate.persistentContainer.viewContext
// MARK: - async fetch request 2
let expenseFetch = NSFetchRequest<Expense>(entityName: kExpenseEntityName)
asyncFetchRequest = NSAsynchronousFetchRequest<Expense>(fetchRequest: expenseFetch) {
[unowned self] (result: NSAsynchronousFetchResult) in
guard let Expenses = result.finalResult else {
return
}
self.asyncExpensesArray = Expenses
self.expenseTableView.reloadData()
// self.testPrintForMyArray(arrayToCheck: self.asyncExpensesArray)
// self.batchUpdate()
}
// MARK: - async fetch request 3
do {
try managedContext.execute(asyncFetchRequest)
} catch let error as NSError {
print("Could not fetch \(error), \(error.userInfo)")
}
}

Map the Expenses array to the expenseAmount values and sum them up.
let sum = Expenses.map({$0.expenseAmount}).reduce(0, {$0 + $1})
PS: According to the Swift 3 naming philosophy I recommend to name the Expense properties just amount, date, owner and tag and remove the redundant parts since it's clear that the properties belong to Expense .

Related

Fetch CoreData using Generic Model and privateQueueConcurrencyType

I've made a method to fetch my coredata Objects array using generics and privateQueueConcurrencyType. However, I'm doing something wrong somewhere and I'm experiencing crashes because of that.
The place where I get the crash as follow. what am I missing here ??
func fetchData<T: NSFetchRequestResult>(entity: String, model: T.Type, _ custom_predicate: NSPredicate?=nil) throws -> [T] {
let request = NSFetchRequest<T>(entityName: entity)
if custom_predicate != nil {
request.predicate = custom_predicate
}
request.returnsObjectsAsFaults = false
//Crash is the bellow line
return try privateMOC.fetch(request)//This line throws the crash
}
My privateMOC initialisation as follow.
class StorageManager: NSObject {
let privateMOC: NSManagedObjectContext!
private override init() {
privateMOC = CoreDataManager.sharedManager.updateContext
}
private static var SMInstance: StorageManager?
lazy var managedObjectContext: NSManagedObjectContext = {
return CoreDataManager.sharedManager.persistentContainer.viewContext
}()
}
My CoreData stack as follow.
class CoreDataManager {
static let sharedManager = CoreDataManager()
let persistentContainer: NSPersistentContainer!
let viewContext: NSManagedObjectContext!
let updateContext: NSManagedObjectContext!
private init() {
let container: NSPersistentContainer = {
let container = NSPersistentContainer(name: "Store")
container.loadPersistentStores(completionHandler: { (_, error) in
if let error = error as NSError? {
fatalError("Unresolved error \(error), \(error.userInfo)")
}
})
return container
}()
self.persistentContainer = container
self.viewContext = persistentContainer.viewContext
//This is where I use the privateQueueConcurrencyType formy privateMOC
let _updateContext = NSManagedObjectContext(concurrencyType: .privateQueueConcurrencyType)
_updateContext.parent = self.viewContext
self.updateContext = _updateContext
}
Stacktrace as follow.
When you are using background context (private queue), you should wrap it in perform or performAndWait. With that said the fetch method should be called like this:
context.performAndWait {
context.fetch(request)
}
Because the queue is private and internal to the NSManagedObjectContext instance, it can only be accessed through the perform(:) and the performAndWait(:) methods.
More about using core data in background read here
Edit 1:
performAndWait takes a closure as its parameter that has no return value, so you can't return result/value from it. You need to understand these concepts in Swift.
Let's take your problem for example, you have a function that you want to return array of some values after the fetch request is performed. What you did is not going to work because of what I said earlier, so we will "extract" the context outside of the function.
func fetch<T>(_ type: T.Type, in context: NSManagedObjectContext) -> [T] {
return context.fetch(request)
}
In that way we can wrap the function in whatever context we want either background or viewContext(main).
context.performAndWait {
let products = fetch(SubProduct.self, in: context)
// do some other stuff
}
I can't replicate your crash but I was getting an Error in the line you highlighted.
I didn't have info on you SubProduct so I used the generic Item that comes with Xcode Projects
There are a ton of comments that come with the code.
This is just a standard View to test functionality.
struct CoreDataBackgroundView: View {
#StateObject var vm: CoreDataBackgroundViewModel2 = CoreDataBackgroundViewModel2()
public init(){}
var body: some View {
VStack{
List(vm.items){ item in
Button(action: {
vm.fetchItem(timestamp: item.timestamp)
}, label: {
Text(item.timestamp!.description)
})
}
Text("fetched Item = \(vm.item?.timestamp?.description ?? "nil")")
Text(vm.items.count.description)
Button("fetch", action: {
vm.fetchItems()
})
Button("add", action: {
vm.addItem()
})
}
}
}
struct CoreDataBackgroundView_Previews: PreviewProvider {
static var previews: some View {
CoreDataBackgroundView()
}
}
This is how I incorporated your code
class CoreDataBackgroundViewModel2: ObservableObject{
private let manager = StorageManager.SMInstance
#Published var items: [Item] = []
#Published var item: Item? = nil
///Fetch everything
func fetchItems() {
do{
items = try manager.fetchData(entity: "Item", model: Item.self)
}catch{
print(error)
items = []
}
}
///This way you can just fetch the item(s) that you need
func fetchItem(timestamp: Date?) {
if timestamp != nil{
do{
item = try manager.fetchData(entity: "Item", model: Item.self, NSPredicate(format: "timestamp == %#", timestamp! as CVarArg)).first
}catch{
print(error)
item = nil
}
}else{
item = nil
}
}
func addItem() {
manager.addItem()
}
}
//Something had to change here there is no entry
class StorageManager: NSObject {
let manager = CoreDataManager.sharedManager
let privateMOC: NSManagedObjectContext
private override init() {
privateMOC = manager.container.newBackgroundContext()
}
//I made it a singleton
static var SMInstance: StorageManager = StorageManager()
lazy var managedObjectContext: NSManagedObjectContext = {
return manager.container.viewContext
}()
func fetchData<T: NSFetchRequestResult>(entity: String, model: T.Type, _ custom_predicate: NSPredicate?=nil) throws -> [T] {
let request = NSFetchRequest<T>(entityName: entity)
if custom_predicate != nil {
request.predicate = custom_predicate
}
request.returnsObjectsAsFaults = false
//Didn't get a crash but an Error
//Value of optional type 'NSManagedObjectContext?' must be unwrapped to refer to member 'fetch' of wrapped base type 'NSManagedObjectContext'
// It is bad practice to use ! try to minimize those as much as possible
return try privateMOC.fetch(request)
}
func addItem()
{
privateMOC.perform {
let newItem = Item(context: self.privateMOC)
newItem.timestamp = Date()
newItem.text = "sample"
do{
try self.privateMOC.save()
}catch{
print(error)
}
}
}
}
//I changed your manager because there were a bunch of nil objects that are unnecessary also, I added the ability to use an inMemory conxtext for when you are using previews/canvas
//This is basically the standard setup with comes with all New Projects in Xcode
//Some of the errors probably had to do with the let variables that were after loadPersistentStores. They were called before the store was done loading so they stayed nil. The speed of the load might have given you mixed results.
class CoreDataManager {
static let sharedManager = CoreDataManager.previewAware()
private static func previewAware() -> CoreDataManager{
if ProcessInfo.processInfo.environment["XCODE_RUNNING_FOR_PREVIEWS"] == "1"{
return CoreDataManager.preview
}else{
return CoreDataManager.shared
}
}
private static let shared = CoreDataManager()
private static var preview: CoreDataManager = {
let result = CoreDataManager(inMemory: true)
let viewContext = result.container.viewContext
for n in 0..<2 {
let newItem = Item(context: viewContext)
newItem.timestamp = Date()
}
do {
try viewContext.save()
} catch {
// Replace this implementation with code to handle the error appropriately.
// fatalError() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
let nsError = error as NSError
fatalError("Unresolved error \(nsError), \(nsError.userInfo)")
}
return result
}()
let container: NSPersistentContainer
init(inMemory: Bool = false) {
//This is usually the AppName
container = NSPersistentContainer(name: "AppName")
//If you are in Preview the setup work take place only on a device or simulator
if inMemory {
container.persistentStoreDescriptions.first!.url = URL(fileURLWithPath: "/dev/null")
}
container.loadPersistentStores(completionHandler: { (storeDescription, error) in
if let error = error as NSError? {
// Replace this implementation with code to handle the error appropriately.
// fatalError() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
/*
Typical reasons for an error here include:
* The parent directory does not exist, cannot be created, or disallows writing.
* The persistent store is not accessible, due to permissions or data protection when the device is locked.
* The device is out of space.
* The store could not be migrated to the current model version.
Check the error message to determine what the actual problem was.
*/
fatalError("Unresolved error \(error), \(error.userInfo)")
}
})
container.viewContext.automaticallyMergesChangesFromParent = false
container.viewContext.mergePolicy = NSMergePolicy.mergeByPropertyStoreTrump
}
}

fill in UITableViewCell with JSONdata

I am trying to display some json data inside my tableView cell, and parsed the json data.
But
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.contents.count
}
returning 0
and as a result, I cannot display the data inside the UITableViewCell eventhough I have all my JSON data have been parsed and ready to be displayed.. .
how to fix it?
my response Model class and the rest of implementation are as follows:
Model classes for json response
// MARK: - TradingPairElement
class TradingPair: Entity {
var id: Int?
//var name: String?
var quoteAsset: QuoteAsset?
}
enum QuoteAsset: String, Codable {
case btc = "BTC"
case krw = "KRW"
}
// MARK: - TickerByPair
class TickerByPair: Entity {
var ask: Int?
//var price: Double?
//var volume: Double?
var askVolume: Double?
var bid: Int?
var bidVolume: Double?
var time: String?
}
And a wrapper class for the above two class 's contents:
class Entity: Codable {
var name: String?
var price: Double?
var volume: Double?
}
and here is how i am getting the data from the api and assigning to my self variables:
func APIcall() {
ServerCommunicator.getPairs().done{ response -> Void in
for keyPathParam in response {
self.keyPathParam = keyPathParam.name
}
ServerCommunicator.getPair(with: self.keyPathParam).done{ ticker -> Void in
for data in self.contents {
data.name = ticker.name
data.price = ticker.price
data.volume = ticker.volume
self.contents.append(data)
}
}.catch{(err) in
print(err)
}
}.catch{(error) in
print(error)
}
}
First of all if the API sends always all keys declare the properties non-optional and as constants and most likely you don't need a class and conformance to Encodable
struct Entity: Decodable {
let name: String
let price: Double
let volume: Double
}
After getting the data from the server you have to create new instances of Entity and assign them to the data source array. Further you need DispatchGroup to handle the loop and reload the table view after the last entity has been created.
If you want to overwrite self.contents with the received data uncomment the removeAll line
func APIcall() {
ServerCommunicator.getPairs().done{ response in
// self.contents.removeAll()
let group = DispatchGroup()
for keyPathParam in response {
group.enter()
ServerCommunicator.getPair(with: keyPathParam.name).done{ ticker in
let entity = Entity(name: ticker.name, price: ticker.price, volume: ticker.volume)
self.contents.append(entity)
group.leave()
}.catch{(err) in
print(err)
group.leave()
}
}
group.notify(queue: .main) {
self.tableView.reloadData()
}
}.catch{(error) in
print(error)
}
}

How to implement simple MVC design pattern in Swift?

I am new to MVC design pattern. I created "DataModel" it will make an API call, create data, and return data to the ViewController using Delegation and "DataModelItem" that will hold all data. How to call a DataModel init function in "requestData" function. Here is my code:
protocol DataModelDelegate:class {
func didRecieveDataUpdata(data:[DataModelItem])
func didFailUpdateWithError(error:Error)
}
class DataModel: NSObject {
weak var delegate : DataModelDelegate?
func requestData() {
}
private func setDataWithResponse(response:[AnyObject]){
var data = [DataModelItem]()
for item in response{
if let tableViewModel = DataModelItem(data: item as? [String : String]){
data.append(tableViewModel)
}
}
delegate?.didRecieveDataUpdata(data: data)
}
}
And for DataModelItem:
class DataModelItem{
var name:String?
var id:String?
init?(data:[String:String]?) {
if let data = data, let serviceName = data["name"] , let serviceId = data["id"] {
self.name = serviceName
self.id = serviceId
}
else{
return nil
}
}
}
Controller:
class ViewController: UIViewController {
private let dataSource = DataModel()
override func viewDidLoad() {
super.viewDidLoad()
dataSource.delegate = self
}
override func viewWillAppear(_ animated: Bool) {
dataSource.requestData()
}
}
extension ViewController : DataModelDelegate{
func didRecieveDataUpdata(data: [DataModelItem]) {
print(data)
}
func didFailUpdateWithError(error: Error) {
print("error: \(error.localizedDescription)")
}
}
How to implement simple MVC design pattern in Swift?
As a generic answer, in iOS development you're already doing this implicitly! Dealing with storyboard(s) implies the view layer and controlling the logic of how they work and how they are connected to the model is done by creating view controller, that's the default flow.
For your case, let's clarify a point which is: according to the standard MVC, by default the responsible layer for calling an api should be -logically- the view controller. However for the purpose of modularity, reusability and avoiding to create massive view controllers we can follow the approach that you are imitate, that doesn't mean that its the model responsibility, we can consider it a secondary helper layer (MVC-N for instance), which means (based on your code) is DataModel is not a model, its a "networking" layer and DataModelItem is the actual model.
How to call a DataModel init function in "requestData" function
It seems to me that it doesn't make scene. What do you need instead is an instance from DataModel therefore you could call the desired method.
In the view controller:
let object = DataModel()
object.delegate = self // if you want to handle it in the view controller itself
object.requestData()
I am just sharing my answer here and I am using a codable. It will be useful for anyone:
Model:
import Foundation
struct DataModelItem: Codable{
struct Result : Codable {
let icon : String?
let name : String?
let rating : Float?
let userRatingsTotal : Int?
let vicinity : String?
enum CodingKeys: String, CodingKey {
case icon = "icon"
case name = "name"
case rating = "rating"
case userRatingsTotal = "user_ratings_total"
case vicinity = "vicinity"
}
}
let results : [Result]?
}
NetWork Layer :
import UIKit
protocol DataModelDelegate:class {
func didRecieveDataUpdata(data:[String])
func didFailUpdateWithError(error:Error)
}
class DataModel: NSObject {
weak var delegate : DataModelDelegate?
var theatreNameArray = [String]()
var theatreVicinityArray = [String]()
var theatreiconArray = [String]()
func requestData() {
Service.sharedInstance.getClassList { (response, error) in
if error != nil {
self.delegate?.didFailUpdateWithError(error: error!)
} else if let response = response{
self.setDataWithResponse(response: response as [DataModelItem])
}
}
}
private func setDataWithResponse(response:[DataModelItem]){
for i in response[0].results!{
self.theatreNameArray.append(i.name!)
self.theatreVicinityArray.append(i.vicinity!)
self.theatreiconArray.append(i.icon!)
}
delegate?.didRecieveDataUpdata(data: theatreNameArray)
print("TheatreName------------------------->\(self.theatreNameArray)")
print("TheatreVicinity------------------------->\(self.theatreVicinityArray)")
print("Theatreicon------------------------->\(self.theatreiconArray)")
}
}
Controller :
class ViewController: UIViewController {
private let dataSource = DataModel()
override func viewDidLoad() {
super.viewDidLoad()
dataSource.delegate = self
}
override func viewWillAppear(_ animated: Bool) {
dataSource.requestData()
}
}
extension ViewController : DataModelDelegate{
func didRecieveDataUpdata(data: [DataModelItem]) {
print(data)
}
func didFailUpdateWithError(error: Error) {
print("error: \(error.localizedDescription)")
}
}
APIManager :
class Service : NSObject{
static let sharedInstance = Service()
func getClassList(completion: (([DataModelItem]?, NSError?) -> Void)?) {
guard let gitUrl = URL(string: "") else { return }
URLSession.shared.dataTask(with: gitUrl) { (data, response
, error) in
guard let data = data else { return }
do {
let decoder = JSONDecoder()
let gitData = try decoder.decode(DataModelItem.self, from: data)
completion!([gitData],nil)
} catch let err {
print("Err", err)
completion!(nil,err as NSError)
}
}.resume()
}
}
I would recommend using a singleton instance for DataModel, since this would be a class you would be invoking from many points in your application.
You may refer its documentation at :
Managing Shared resources using singleton
With this you wont need to initialise this class instance every time you need to access data.

Is it a bad practice to create a generic wrapper over Core Data models?

I'm studying Swift and Core Data and I plan to use a simple wrapper over it for my models.
At this point, protocol and extension looks like this:
protocol CRUD {
associatedtype T: NSManagedObject
var context: NSManagedObjectContext { get }
var items: [T]! { get set }
func getAll() -> [T]
mutating func addOrUpdate(_ item: T) -> T
mutating func delete(_ item: T)
}
extension CRUD where T: NSManagedObject {
var context: NSManagedObjectContext {
return (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
}
func save() {
do {
try context.save()
} catch {
fatalError("Saving of \(String(describing: self)) failed")
}
}
func getAll() -> [T] {
let fetchRequest = NSFetchRequest<T>(entityName: String(describing: T.self))
let list: [T]
do {
list = try context.fetch(fetchRequest)
} catch {
fatalError("Fetching of \(String(describing: self)) failed")
}
return list
}
mutating func delete(_ item: T) {
if let index = items.index(of: item) {
items.remove(at: index)
}
context.delete(item)
save()
}
mutating func addOrUpdate(_ item: T) -> T {
if (items.contains(item)) {
items.append(item)
}
save()
return item
}
}
And each model is declared like this:
class TaskModel : CRUD {
typealias T = Task
var items: [Task]!
init() {
self.items = getAll()
}
}
How much does this code correspond to the principles of OOP (in particular, can I call this protocol the implementation of the DAO pattern)? Are such wrappers needed? Or does Core Data imply the direct use of models in the code?
What possible problems can reveal with it in the future?
I will be very grateful for the advice from more experienced iOS developers. Thank you in advance.
A protocol might be too much for this kind of functionality, as protocols main goal is still polymorphism. You could use a generic struct instead:
struct CRUD<T: NSManagedObject> {
var context: NSManagedObjectContext {
return (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
}
var items = [T]()
// let's provide two approaches for initialization
init() {
self.init(items: getAll())
}
init(items: [T]) {
self.items = items
}
func save() {
do {
try context.save()
} catch {
fatalError("Saving of \(String(describing: self)) failed")
}
}
func getAll() -> [T] {
let fetchRequest = NSFetchRequest<T>(entityName: String(describing: T.self))
let list: [T]
do {
list = try context.fetch(fetchRequest)
} catch {
fatalError("Fetching of \(String(describing: self)) failed")
}
return list
}
mutating func delete(_ item: T) {
if let index = items.index(of: item) {
items.remove(at: index)
}
context.delete(item)
save()
}
mutating func addOrUpdate(_ item: T) -> T {
if (items.contains(item)) {
items.append(item)
}
save()
return item
}
}
You can then use it in your class:
class TaskModel {
// making sure no-one from outside can mutate our CRUD
private(set) lazy var crud = CRUD<Task>()
init() {
// nothing to do here, the items are already populated
}
}
let model = TaskModel()
// the following won't compile
model.crud.delete(someTask)
IMO, this better transmit the intent of having a Facade over CoreData.

How to write a simple generic Core Data function for all Entities

EDIT: Specifically using Swift Generics
I want a countAll() function for all of my entities and I'm hoping to achieve this by writing one generic function.
The following handles an entity called 'Event', but I want to handle an entity named 'T'.
I'm not sure how to write a generic function like this. Could someone help please?
func countAll() -> Int {
let request: NSFetchRequest<Event> = Event.fetchRequest()
do {
return try persistentContainer.viewContext.count(for: request)
} catch {
XCTAssert(false, "Core Data failed to fetch with error: " + error.localizedDescription)
return 0
}
}
This is the closest I can get before I hit an error:
func count<T: NSFetchRequestResult>(entityName: String) -> Int {
let request = NSFetchRequest<T>(entityName: entityName)
do {
return try persistentContainer.viewContext.count(for: request)
} catch {
XCTAssert(false, "Core Data failed to fetch with error: " + error.localizedDescription)
return 0
}
}
You can pass the managed object class instead of NSFetchRequestResult
to the generic function:
func countAll<T: NSManagedObject>(entity: T.Type) -> Int {
let entityName = String(describing: entity)
let request = NSFetchRequest<T>(entityName: entityName)
do {
return try thePersistentContainer.viewContext.count(for: request)
} catch {
print(error.localizedDescription)
return 0
}
}
let count = countAll(entity: YourEntity.self)
An alternative is to define an extension method on NSManagedObject
(similarly as in How can I create instances of managed object subclasses in a NSManagedObject Swift extension?):
extension NSManagedObject {
class func countAll() -> Int {
let eName = String(describing: self)
let request = NSFetchRequest<NSFetchRequestResult>(entityName: eName)
do {
return try thePersistentContainer.viewContext.count(for: request)
} catch {
print(error.localizedDescription)
return 0
}
}
}
let count = YourEntity.countAll()
A suitable alternative to a generic function is a protocol extension and class methods
protocol Fetchable
{
associatedtype FetchableType: NSManagedObject = Self
static var entityName : String { get }
static func countAll(in persistentContainer: NSPersistentContainer) throws -> Int
}
extension Fetchable where Self : NSManagedObject, FetchableType == Self
{
static var entityName : String {
return NSStringFromClass(self).components(separatedBy: ".").last!
}
static func countAll(in persistentContainer: NSPersistentContainer) throws -> Int
{
let request = NSFetchRequest<FetchableType>(entityName: entityName)
return try persistentContainer.viewContext.count(for: request)
}
}
The benefit is you can call the method on the class, just make your NSManagedObject subclasses adopt the protocol.
do {
try Event.countAll(in: persistentContainer)
} catch { print(error) }

Resources