Creating a Key Path from a computed var in Swift 4 - ios

I have a fetchedResultsController where I'd like to add a keypath for Date so I don't have a section for every second of the day. Simple enough, I thought...I'll create an extension for MyEntity
extension MyEntity {
var dateForSection: String {
get {
let dateFormatter = DateFormatter()
dateFormatter.dateStyle = .long
if let time = self.time {
return dateFormatter.string(from: time)
}
return "Unavailable Date"
}
}
}
Then, on MyViewController in the fetchedResultsController lazy var declaration, I declare the frc as follows:
let frc = NSFetchedResultsController(fetchRequest: fetchRequest, managedObjectContext: managedObjectContext, sectionNameKeyPath: #keyPath(MyEntity.dateForSection), cacheName: nil)
I get this compiler error:
Argument of '#keyPath' refers to non-'#objc' property 'dateForSection'
In Googling the issue saw some Swift bug reports from 2016, but I haven't seen any solutions to this. How would I get around this?
Addendum
I've tried adding #objc in front of the var declaration. I've also tried adding #objc dynamic, closing Xcode, nuking derived data from the command line, rebooting Xcode, rebooting my machine, etc.

I can't reproduce the problem. I whipped out a Core Data project with a Group class auto-generated from my entity, and added a computed variable:
extension Group {
#objc var yoho : String {
return "yoho"
}
}
Then I modified that same line in my lazy fetched results controller initializer:
let frc = NSFetchedResultsController(
fetchRequest:req,
managedObjectContext:self.managedObjectContext,
sectionNameKeyPath:#keyPath(Group.yoho), cacheName:nil)
It compiles. I don't know why yours doesn't.
On the other hand, a #keyPath is nothing but a string generated for you, and what's expected here is a string, so if you really can't get it to compile, just put "dateForSection" and be done with it. Of course that doesn't mean it will run without crashing, but at least it will compile and you can move on.

Related

best practice handling NSManagedObjects and CoreData in multiple VC's

i'd could use your advice on this one
i restructured my whole app, to use coreData so i'd be able to save the Main Class of my app: "FooBar"
i also transfered my complete business logic into that NSManagedObject subclass FooBar()
that's why i needed to to use some kind of convenience init, to start my own methods for calculating values and so on
here's my class definition:
Import Foundation
import CoreData
import UIKit
#objc(FooBar)
public class FooBar: NSManagedObject {
var context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext // used for other CD interactions inside this class
var member1 = Int()
var member2 = Double()
var member3:Double = 0
var fooPack:[FooPacks] = []
...
convenience init( param1: Int16
, param2: String?
, param3: Float
, param4: String?
, param5: Float
, fooPack: NSSet
, entity: NSEntityDescription
, context: NSManagedObjectContext?
) {
self.init(entity: entity, insertInto: context)
self.param1 = param1
self.param2 = param2
self.param3 = param3
self.param4 = param4
self.param5 = param5
self.addToUsedfooBarPacks(fooBarPack)
self.build()
}
func build() {
// i do something
}
func method1() {
// i do something
}
when i initialize my FooBar class inside a viewController i do this by:
self.fooBar = FooBar.init(param1: var1,
param2: var2,
iparam3: var3,
param4: var4,
param5: var5,
fooPack: var6,
entity: FooEntity,
context: context)
when ever i modify any instance of this class i do this by:
self.fooBar.member2 = newValue
self.fooBar.build()
and later at a specific point (in multiple VC's) i then call to save that Instance to CoreData:
let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
do {
try context.save()
}catch{
print("\(error)")
}
with this approach i often have to deal with bugs and unexpected behaviours like:
the do block gets executed successfully, but the object has NOT been saved!?
i often hand over FooBar instances to a different ViewController and i try to save them there,
but this does not seem to be the right way to do update previous fetched objects
in my opinion, this has to do with the way i handle the context variables..
and exactly this is why i am reaching out for your advice..
What is the optimal way to handle such a situation, where i want to modify and save instances within multiple different ViewControllers?
i already learned a lot new stuff about CoreData in the last months, but i seem to miss something important here..
please help me out guys :)
You're using persistentContainer, it helps you setup coredata stack. persistentContainer has two types of context: viewContext and background contexts
With this setup we always use viewContext to fetch data to display on UI.
And use a background context to make changes, update storage...
What is the kind of context you passed in init method?
You should execute context.save() on exactly the context that you used to make changes (you passed to init). In this case you shouldn't pass viewContext to init and use viewContext to call save()
How to update and sync changes between multiple ViewControllers?
The view controller that makes changes should call save as well. And you can use delegation pattern to delegate/broadcast changes to other view controllers...

Anonymous-ish classes (closure?) in Swift for NSFetchedResultsController

Some context:
I've been away from iOS programming for 5+ years and boy howdy have things changed. Just to make it more exciting, I'm trying Swift at the same time.
I'm trying to build a relatively simple iOS app using the Master-Detail App template and am at the point where I'm trying to add sections to the data. My (Core Data) data model is pretty simple at this point - some Locations (these are the sections) each of which contain some Containers. These are linked in the obvious way (a Location have a one-to-many reference to Containers, and Containers have a one-to-one reference to its Location).
I suspect that I'm heading to a place where I'm going to want the fetchedResultsController.object(at: indexPath) to return the Container corresponding with the indexPath but I'm also going to want the fetchedResultsController.sections[section] to return a Location.
The code
This is pretty much the code that comes from the app template (with minor modifications to use my Location type as the generic ResultType for NSFetchedResultsController - that might might be a mistake; maybe it should be a Container or even a NSManagedObject - we'll get to that in a minute).
var fetchedResultsController: NSFetchedResultsController<Location> {
if _fetchedResultsController != nil {
return _fetchedResultsController!
}
let fetchRequest: NSFetchRequest<Location> = Location.fetchRequest()
// Set the batch size to a suitable number.
fetchRequest.fetchBatchSize = 20
// Edit the sort key as appropriate.
let sortDescriptor = NSSortDescriptor(key: "name", ascending: false)
fetchRequest.sortDescriptors = [sortDescriptor]
// Edit the section name key path and cache name if appropriate.
// nil for section name key path means "no sections".
let aFetchedResultsController = NSFetchedResultsController(fetchRequest: fetchRequest, managedObjectContext: self.managedObjectContext!, sectionNameKeyPath: nil, cacheName: "Master")
aFetchedResultsController.delegate = self
_fetchedResultsController = aFetchedResultsController
do {
try _fetchedResultsController!.performFetch()
} 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 _fetchedResultsController!
}
My questions
I'm new to Swift and I'm struggling to understand what the code that the Xcode template created for me. So I have a few questions:
What do we call this pattern? Anonymous classes (apparently not)? Closures? Knowing what it's called will help me do a better job of searching for related answers instead of wasting your time on these noob questions.
What is it actually defining? What I suspect it's doing is defining an implementation for init and returning an instance of NSFetchedResultsController that has all the default implementations. If that's not completely right, help me understand it a little better (pointing at something to read is also helpful).
How should I go about overriding methods of NSFetchedResultsController when using this pattern? Or is that something where I need to create a real subclass.
What type should I be using as the generic ResultType and why? This is a little off topic, but what the heck, maybe you'll take pity on me and bump me along another step of the journey.
It's basically called a computed property.
It checks if an instance of NSFetchedResultsController exists. If not it creates one and configures it.
There is nothing to override. However you should implement the delegate methods.
The generic type is correct. It avoids a lot of type casting.
I doubt this is the code which comes from the app template. The Swift equivalent of the objective-c-ish pattern with the instance variable and the nil-check is a lazy instantiated property. The body is called once the first time the property is accessed.
lazy var fetchedResultsController : NSFetchedResultsController<Location> = {
let fetchRequest: NSFetchRequest<Location> = Location.fetchRequest()
// Set the batch size to a suitable number.
fetchRequest.fetchBatchSize = 20
// Edit the sort key as appropriate.
let sortDescriptor = NSSortDescriptor(key: "name", ascending: false)
fetchRequest.sortDescriptors = [sortDescriptor]
// Edit the section name key path and cache name if appropriate.
// nil for section name key path means "no sections".
let controller = NSFetchedResultsController(fetchRequest: fetchRequest, managedObjectContext: self.managedObjectContext!, sectionNameKeyPath: nil, cacheName: "Master")
controller.delegate = self
do {
try controller.performFetch()
} 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 controller
}()
To add sections you have to specify the sectionNameKeyPath parameter and add an appropriate second sort descriptor.
The table view data source methods should look like
func numberOfSections(in tableView: UITableView) -> Int {
return self.fetchedResultsController.sections?.count ?? 0
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.fetchedResultsController.sections?[section].numberOfObjects ?? 0
}

Swift2.0 CoreData issue on NSFetchRequest

This is a code snippet of me trying to fetch data from coredata. I tried to fetch data and checked using breakpoint and there was NO data fethed! The code is in swift2. Any suggestions would be appreciated. I defined a struct constants where I defined all the constants. gate is a string type defined in nsmanagedobject class. The snippet is a part of viewDidLoad() method.
let context: NSManagedObjectContext? = (UIApplication.sharedApplication().delegate as? AppDelegate)?.managedObjectContext!
let TPTodayFetchRequest = NSFetchRequest(entityName: Constants.CoreDataEntities.TPTodayCoreDataEntity)
do {
let patroDaily = try context!.executeFetchRequest(TPTodayFetchRequest) as! [TPToday]
for patroEntity in patroDaily {
print(patroEntity.gate)
}
}
catch { print("error") }
Except for cumbersome variable naming, and unfortunate indentation, your code is fine and should work.
You will have to investigate if you have indeed saved the data you are expecting to find. For example, you could have the program log the documents directory URL after which you could examine the sqlite file with the sqlite3 command line tool.

Swift Lazy Variables that Load more than Once (Computed Properties?)

I'm trying to translate some Objective-C code that was essentially lazy loading a variable multiple times. The code was similar to the following:
-(NSFetchedResultsController *)fetchedResultsController {
if (_fetchedResultsController != nil) {
return _fetchedResultsController;
}
//...code to build the fetchedResultsController with a new predicate
Whenever they wanted to rebuild the fetchedResultsController to use a new predicate, they would just set it to "nil" and call it, and it would rebuild it with a new predicate.
I'm struggling to do this same task in Swift. As far as I can tell, Swift lazy variables become normal variables after they are called for the first time. This is causing issues for me because if I try to set my swift variable back to nil, and recall it, it doesn't rebuild but remains nil.
The working code to load my fetchedResultsController as a lazy varaible is below. I've tried changing it to a computed property by adding a check if its nil and have it within a get block, but that hasn't worked. Any ideas?
lazy var taskController : NSFetchedResultsController? = {
var subtaskRequest = NSFetchRequest(entityName: "Subtasks")
var segIndex = self.segmentedControl.selectedSegmentIndex
subtaskRequest.predicate = NSPredicate(format: "task.category.name == %#", self.segmentedControl.titleForSegmentAtIndex(segIndex)!)
subtaskRequest.sortDescriptors = [NSSortDescriptor(key: "task.englishTitle", ascending: true), NSSortDescriptor(key: "sortOrder", ascending: true)]
let controller = NSFetchedResultsController(fetchRequest: subtaskRequest, managedObjectContext:
self.managedObjectContext!, sectionNameKeyPath: "task.englishTitle", cacheName: nil)
controller.delegate = self
return controller
}()
You can create something similar to the Objective-C method using a computed property backed by an optional variable.
var _fetchedResultsController: NSFetchedResultsController?
var fetchedResultsController: NSFetchedResultsController {
get {
if _fetchedResultsController != nil {
return _fetchedResultsController!
}
//create the fetched results controller...
return _fetchedResultsController!
}
}
lazy just implements a very specific memoization pattern. It's not as magical as you'd sometimes like it to be. You can implement your own pattern to match your ObjC code pretty easily.
Just make a second private optional property that holds the real value. Make a standard (non-lazy) computed property that checks the private property for nil and updates it if it's nil.
This is pretty much identical to the ObjC system. In ObjC you had two "things," one called _fetchedResultsController and the other called self.fetchedResultsController. In Swift you'll have two things, one called self.fetchedResultsController and the other called self._cachedFetchedResultsController (or whatever).

updateValue not working for Dictionary

I'm creating a test app using Swift in Xcode, and I've run into an annoying issue. I'm writing a simple class that will act as a cache using a Dictionary object. My implementation is below:
import Foundation
import UIKit
class ImageCache {
var dict:Dictionary<String,NSData>?;
init() {
dict = Dictionary<String,NSData>();
}
func exists(id:String) -> Bool {
return dict!.indexForKey(id)!==nil;
}
func getImage(id:String) -> UIImage? {
if(!exists(id)) {
return nil;
}
return UIImage(data: (dict!)[id]);
}
func setData(id:String, data:NSData) {
dict!.updateValue(data, forKey: id);
}
}
The issue is in the last method, with Xcode stating "Could not find member 'UpdateValue'". This is weird, because the code hint seems to show it just fine:
But when I try to compile:
Could this potentially be a bug in Xcode? Or am I missing something super-obvious?
this is not a bug or a quirk in the compiler.
it is how Optional implemented (which may be flawed or not)
what happened is that the Optional store the Dictionary as immutable object (with let perhaps). So even Optional it is mutable, you can't modify the underlying Dictionaryobject directly (without reassign the Optional object).
updateValue(forKey:) is mutating method, you can't call it on immutable object and hence the error.
you can workaround it by doing
var d = dict!
d.updateValue(data, forKey: id)
because you copy the dictionary to another mutable variable, which then is mutable and able to call mutating method on it
but without dict = d, your change won't be applied on dict because Dictionary is value type, it makes copy on every assignment
related answer
Smells like a bug or a quirk in the compiler.
I just tried something like
var d = dict!
d.updateValue(data, forKey: id)
and it works as expected.

Resources