Generic FetchRequest - swift

I have a static function that extends NSManagedObject to get an object like so...
NSManagedObject.get(type: MYUser.self, with: ("id", "SomeUserId"), in: context)
extension NSManagedObject {
static func get<M: NSManagedObject>(type: M.Type, with kvp: (String, CVarArg), in context: NSManagedObjectContext) -> M? {
guard let name = entity().name else { return nil }
guard M.entity().propertiesByName[kvp.0] != nil else { Assert("\(name) does not have \(kvp.0)"); return nil }
let fetchRequest = NSFetchRequest<M>(entityName: name)
fetchRequest.predicate = NSPredicate(format: "\(kvp.0) == %#", kvp.1)
do {
let object = try context.fetch(fetchRequest)
if let foundObject = object.first { return foundObject }
return nil
} catch {
return nil
}
}
}
The syntax I would like is
MYUser.get(with: ("id", "SomeUserId"), in: context)
and to infer the Type from the class that made the call... but I'm unsure what to put in place of the generic here
NSFetchRequest<M>(entityName: name)
NSFetchRequest<???>(entityName: name)
Thanks in advance

If you don't mind writing MYUser twice, you can remove the type parameter and specify the type so that Swift can infer M:
extension NSManagedObject {
static func get<M: NSManagedObject>(with kvp: (String, CVarArg), in context: NSManagedObjectContext) -> M? {
guard let name = entity().name else { return nil }
guard M.entity().propertiesByName[kvp.0] != nil else {
print("\(name) does not have \(kvp.0)")
return nil
}
let fetchRequest = NSFetchRequest<M>(entityName: name)
fetchRequest.predicate = NSPredicate(format: "\(kvp.0) == %#", kvp.1)
do {
let object = try context.fetch(fetchRequest)
if let foundObject = object.first { return foundObject }
return nil
} catch {
return nil
}
}
}
// usage:
let user: MYUser? = MYUser.get(with: ("id", "SomeUserId"), in: context)
If you don't want to write MYUser twice, then I can't think of any solutions. If NSManagedObject were a protocol, you could have used Self in there.

Based on the link Passing generic Class as argument to function in swift suggested by Martin R
protocol Managed where Self: NSManagedObject { }
extension Managed where Self: NSManagedObject {
static func get(with kvp: (String, CVarArg), in context: NSManagedObjectContext) -> Self? {
guard let name = entity().name else { return nil }
guard entity().propertiesByName[kvp.0] != nil else { Assert("\(name) does not have \(kvp.0)"); return nil }
let fetchRequest = NSFetchRequest<Self>(entityName: name)
fetchRequest.predicate = NSPredicate(format: "\(kvp.0) == %#", kvp.1)
do {
let object = try context.fetch(fetchRequest)
if let foundObject = object.first { return foundObject }
return nil
} catch {
return nil
}
}

Related

save string over saved string in core data

In my swift code below the code saves an item in core data. The goal is to overwrite that item. I am getting a runtime error at
CoreDataHandler.changeName(user: fetchUser!\[indexNumber\], jessica: "jo")
Thread 1: Fatal error: Unexpectedly found nil while unwrapping an Optional value
I don't know how to wrap in the index number. The goal is it to print judo then jo
import UIKit;import CoreData
class ViewController: UIViewController {
var fetchUser: [UserInfo]? = nil
var indexNumber : Int = 0
override func viewDidLoad() {
super.viewDidLoad()
CoreDataHandler.saveObject2( name: "judo")
getText(textNo: indexNumber)
saveTheItem()
}
#objc func saveTheItem(){
CoreDataHandler.changeName(user: fetchUser![indexNumber], jessica: "jo")
}
func getText(textNo:Int) {
// first check the array bounds
let info = helpText.shareInstance.fetchText()
if info.count > textNo {
if let imageData = info[textNo].name
{
print(imageData)
} else {
// no data
print("data is empty Textss")
}
} else {
// image number is greater than array bounds
print("you are asking out of bounds")
}
}
}
class CoreDataHandler : NSManagedObject {
class func saveObject2( name: String) -> Bool {
let context = getContext()
let entity = NSEntityDescription.entity(forEntityName: "UserInfo", in: context)
let managedObject = NSManagedObject(entity: entity!, insertInto: context)
managedObject.setValue(name, forKey: "name")
do{
try context.save()
return true
}
catch {
return false
}
}
private class func getContext() -> NSManagedObjectContext{
let appD = UIApplication.shared.delegate as! AppDelegate
return appD.persistentContainer.viewContext
}
class func changeName(user: UserInfo,jessica : String) -> Bool
{
let context = getContext()
user.name = jessica
print(jessica)
do{
try context.save()
return true
}
catch{
return false
}
}
}
class helpText: UIViewController{
private class func getContext() -> NSManagedObjectContext {
let appDelegate = UIApplication.shared.delegate as! AppDelegate
return appDelegate.persistentContainer.viewContext
}
static let shareInstance = helpText()
let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
func saveName(data: String) {
let imageInstance = UserInfo(context: context)
imageInstance.name = data
do {
try context.save()
} catch {
print(error.localizedDescription)
}
}
func fetchText() -> [UserInfo] {
var fetchingImage = [UserInfo]()
let fetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: "UserInfo")
do {
fetchingImage = try context.fetch(fetchRequest) as! [UserInfo]
} catch {
print("Error while fetching the image")
}
return fetchingImage
}
}
No offense but your code is a mess.
And there is a big misunderstanding. Core Data records are unordered, there is no index. To update a record you have to fetch it by a known attribute, in your example by name, update it and save it back.
This is a simple method to do that. It searches for a record with the given name. If there is one, update the attribute with newName and save the record.
The code assumes that there is a NSManagedObject subclass UserInfo with implemented class method fetchRequest.
func changeName(_ name: String, to newName: String) {
let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
let request : NSFetchRequest<UserInfo> = UserInfo.fetchRequest()
request.predicate = NSPredicate(format: "name == %#", name)
do {
let records = try context.fetch(request)
guard let foundRecord = records.first else { return }
foundRecord.name = newName
try context.save()
} catch {
print(error)
}
}
Regarding your confusing code:
Create CoreDataHandler as singleton (and it must not be a subclass of NSManagedObject). Move the Core Data related code from AppDelegate and the methods to read and write in this class.

Swift Core Data - handling empty fetch result

I have a simple entity, a stationID and a type, both Strings
I use this method to search for a type for a given stationID,
func returnStationType(stationTypeId: String) -> PersistantStationType {
let context = container.viewContext
let request = PersistantStationType.fetchRequest() as NSFetchRequest<PersistantStationType>
request.predicate = NSPredicate(format: "%K == %#", #keyPath(PersistantStationType.stationId), stationTypeId as CVarArg)
do {
let result = try context.fetch(request)
if result.count != 0 {
let fetchedStationType = result.first!
return fetchedStationType
} else { print("4 Fetch result was empty for specified stationid: \(String(describing: stationTypeId))")
}
} catch { print("Fetch on goal id: \(String(describing: stationTypeId)) failed. \(error)") }
return PersistantStationType.init()
}
I call the method here:
let persistentStationType = persistenceController.returnStationType(stationTypeId: closestStation.id)
let stationType = persistentStationType.type?.lowercased().capitalized
let realName = helper.returnRealName(stationType: stationType ?? "None")
let imageName = helper.returnStationTypeImage(stationType: stationType ?? "None")
If a stationId is not found - so empty result I get a crash in the code(Bad Exec/ Access) where I call the method - not in the method itself. (I'm not surprised by this but i'm not sure how to handle it)
I can't use if let on the method .. .it's not an optional. Should I be returning an empty object and check for It when the fetch result is empty?
Thanks for any help.
I would declare the function to return an optional and return nil when nothing is found
func returnStationType(stationTypeId: String) -> PersistantStationType? {
let context = container.viewContext
let request: NSFetchRequest<PersistantStationType> = PersistantStationType.fetchRequest()
request.predicate = NSPredicate(format: "%K == %#", #keyPath(PersistantStationType.stationId), stationTypeId)
do {
let result = try context.fetch(request)
if result.count != 0 {
return result[0]
} else {
print("4 Fetch result was empty for specified stationid: \(String(describing: stationTypeId))")
return nil
}
} catch {
print("Fetch on goal id: \(String(describing: stationTypeId)) failed. \(error)")
return nil
}
}
If on the error hand it is considered an error if more than one objects (or none) exists for an id then it would be better to throw an error in those situations
func returnStationType(stationTypeId: String) throws -> PersistantStationType {
let context = container.viewContext
let request: NSFetchRequest<PersistantStationType> = PersistantStationType.fetchRequest()
request.predicate = NSPredicate(format: "%K == %#", #keyPath(PersistantStationType.stationId), stationTypeId)
do {
let result = try context.fetch(request)
switch result.count {
case 0:
throw FetchError("No station type found for id \(stationTypeId)")
case 1:
return result[0]
default:
throw FetchError("Multiple station types found for id \(stationTypeId)")
}
} catch let error as NSError {
throw FetchError(error.localizedDescription)
}
}
struct FetchError: LocalizedError {
private let message: String
var errorDescription: String? {
message
}
init(_ message: String) {
self.message = message
}
}

Core Data: Generic class function to return object

I have several classes of base type NSManagedObject and each of them contains a class function to return the first object of a given context. Here's an example:
public class Car: NSManagedObject {
class func first(context: NSManagedObjectContext) -> Car? {
let fetchRequest = Car.fetchRequest()
fetchRequest.fetchLimit = 1
let results = try? context.fetch(fetchRequest)
return results?.first
}
}
Instead of writing this function for every subclass I'd like to put a generic version as an extension to NSManagedObject. I've tried this:
extension NSManagedObject {
class func first(context: NSManagedObjectContext) -> Self? {
let fetchRequest = Self.fetchRequest()
fetchRequest.fetchLimit = 1
let results = try? context.fetch(fetchRequest)
return results?.first
}
}
But this gives a "Type of expression is ambigous without more context" error. How can this be done?
context.fetch() returns [Any], but you can conditionally cast it to the expected type [Self]:
extension NSManagedObject {
class func first(context: NSManagedObjectContext) -> Self? {
let fetchRequest = Self.fetchRequest()
fetchRequest.fetchLimit = 1
let results = try? context.fetch(fetchRequest) as? [Self]
return results?.first
}
}
Or with a real do/try/catch for better diagnostics in the error case:
extension NSManagedObject {
class func first(context: NSManagedObjectContext) -> Self? {
let fetchRequest = Self.fetchRequest()
fetchRequest.fetchLimit = 1
do {
let results = try context.fetch(fetchRequest) as? [Self]
return results?.first
} catch {
print(error)
return nil
}
}
}

How to encode/decode [CKRecordZone.ID: CKServerChangeToken]?

public var zonesChangeToken: [CKRecordZone.ID: CKServerChangeToken]? {
get {
if(backingPreviousZonesChangeToken == nil) {
guard let defaults: UserDefaults = UserDefaults(suiteName: CloudKitHandler.APP_GROUP_ID) else { return nil }
guard let data = defaults.data(forKey: CloudKitHandler.CK_PREVIOUS_ZONES_CHANGE_TOKEN)
else { return [CKRecordZone.ID: CKServerChangeToken]() }
do {
let unarchiver: NSKeyedUnarchiver = try NSKeyedUnarchiver(forReadingFrom: data)
unarchiver.requiresSecureCoding = true
backingPreviousZonesChangeToken = try unarchiver.decodeTopLevelObject() as? [CKRecordZone.ID: CKServerChangeToken]
} catch { }
}
return backingPreviousZonesChangeToken
}
set(value) {
backingPreviousZonesChangeToken = value
guard let value = value else { return }
guard let defaults: UserDefaults = UserDefaults(suiteName: CloudKitHandler.APP_GROUP_ID) else { return }
let archiver: NSKeyedArchiver = NSKeyedArchiver(requiringSecureCoding: true)
archiver.encode(value)
archiver.finishEncoding()
defaults.setValue(archiver.encodedData, forKey: CloudKitHandler.CK_PREVIOUS_ZONES_CHANGE_TOKEN)
}
}
I'm trying to encode/decode a dictionary of IDs and Tokens. But for some reason the decode always gives me a nil.
How to fix?
extension CKServerChangeToken {
func dataRepresentation() -> Data {
let coder = NSKeyedArchiver.init(requiringSecureCoding: true)
coder.requiresSecureCoding = true
self.encode(with: coder)
coder.finishEncoding()
return coder.encodedData
}
class func token(data: Data) -> CKServerChangeToken? {
do{
let coder = try NSKeyedUnarchiver(forReadingFrom: data)
coder.requiresSecureCoding = true
let record = CKServerChangeToken(coder: coder)
coder.finishDecoding()
return record
} catch {
print(error)
}
return nil
}
}

How to check object is nil or not in swift?

Suppose I have String like :
var abc : NSString = "ABC"
and I want to check that it is nil or not and for that I try :
if abc == nil{
//TODO:
}
But this is not working and giving me an error. Error Says :
Can not invoke '=='with an argument list of type '(#|value NSString , NilLiteralConvertible)'
Any solution for this?
If abc is an optional, then the usual way to do this would be to attempt to unwrap it in an if statement:
if let variableName = abc { // If casting, use, eg, if let var = abc as? NSString
// variableName will be abc, unwrapped
} else {
// abc is nil
}
However, to answer your actual question, your problem is that you're typing the variable such that it can never be optional.
Remember that in Swift, nil is a value which can only apply to optionals.
Since you've declared your variable as:
var abc: NSString ...
it is not optional, and cannot be nil.
Try declaring it as:
var abc: NSString? ...
or alternatively letting the compiler infer the type.
The case of if abc == nil is used when you are declaring a var and want to force unwrap and then check for null. Here you know this can be nil and you can check if != nil use the NSString functions from foundation.
In case of String? you are not aware what is wrapped at runtime and hence you have to use if-let and perform the check.
You were doing following but without "!". Hope this clears it.
From apple docs look at this:
let assumedString: String! = "An implicitly unwrapped optional string."
You can still treat an implicitly unwrapped optional like a normal optional, to check if it contains a value:
if assumedString != nil {
println(assumedString)
}
// prints "An implicitly unwrapped optional string."
The null check is really done nice with guard keyword in swift. It improves the code readability and the scope of the variables are still available after the nil checks if you want to use them.
func setXYX -> Void{
guard a != nil else {
return;
}
guard b != nil else {
return;
}
print (" a and b is not null");
}
I ended up writing utility function for nil check
func isObjectNotNil(object:AnyObject!) -> Bool
{
if let _:AnyObject = object
{
return true
}
return false
}
Does the same job & code looks clean!
Usage
var someVar:NSNumber?
if isObjectNotNil(someVar)
{
print("Object is NOT nil")
}
else
{
print("Object is nil")
}
func isObjectValid(someObject: Any?) -> Any? {
if someObject is String {
if let someObject = someObject as? String {
return someObject
}else {
return ""
}
}else if someObject is Array<Any> {
if let someObject = someObject as? Array<Any> {
return someObject
}else {
return []
}
}else if someObject is Dictionary<AnyHashable, Any> {
if let someObject = someObject as? Dictionary<String, Any> {
return someObject
}else {
return [:]
}
}else if someObject is Data {
if let someObject = someObject as? Data {
return someObject
}else {
return Data()
}
}else if someObject is NSNumber {
if let someObject = someObject as? NSNumber{
return someObject
}else {
return NSNumber.init(booleanLiteral: false)
}
}else if someObject is UIImage {
if let someObject = someObject as? UIImage {
return someObject
}else {
return UIImage()
}
}
else {
return "InValid Object"
}
}
This function checks any kind of object and return's default value of the kind of object, if object is invalid.
if (MyUnknownClassOrType is nil) {
println("No class or object to see here")
}
Apple also recommends that you use this to check for depreciated and removed classes from previous frameworks.
Here's an exact quote from a developer at Apple:
Yes. If the currently running OS doesn’t implement the class then the class method will return nil.
Hope this helps :)
Normally, I just want to know if the object is nil or not.
So i use this function that just returns true when the object entered is valid and false when its not.
func isNotNil(someObject: Any?) -> Bool {
if someObject is String {
if (someObject as? String) != nil {
return true
}else {
return false
}
}else if someObject is Array<Any> {
if (someObject as? Array<Any>) != nil {
return true
}else {
return false
}
}else if someObject is Dictionary<AnyHashable, Any> {
if (someObject as? Dictionary<String, Any>) != nil {
return true
}else {
return false
}
}else if someObject is Data {
if (someObject as? Data) != nil {
return true
}else {
return false
}
}else if someObject is NSNumber {
if (someObject as? NSNumber) != nil{
return true
}else {
return false
}
}else if someObject is UIImage {
if (someObject as? UIImage) != nil {
return true
}else {
return false
}
}
return false
}
Swift 4.2
func isValid(_ object:AnyObject!) -> Bool
{
if let _:AnyObject = object
{
return true
}
return false
}
Usage
if isValid(selectedPost)
{
savePost()
}
Swift short expression:
var abc = "string"
abc != nil ? doWork(abc) : ()
or:
abc == nil ? () : abc = "string"
or both:
abc != nil ? doWork(abc) : abc = "string"
Swift-5 Very Simple Way
//MARK:- In my case i have an array so i am checking the object in this
for object in yourArray {
if object is NSNull {
print("Hey, it's null!")
}else if object is String {
print("Hey, it's String!")
}else if object is Int {
print("Hey, it's Int!")
}else if object is yourChoice {
print("Hey, it's yourChoice!")
}
else {
print("It's not null, not String, not yourChoice it's \(object)")
}
}
Swift 4
You cannot compare Any to nil.Because an optional can be nil and hence it always succeeds to true.
The only way is to cast it to your desired object and compare it to nil.
if (someone as? String) != nil
{
//your code`enter code here`
}
Swift 5
Crash
Your app crash because parameters receive null, and broke in NSException.
if let parameters = parameters {
httpBody = try JSONSerialization.data(withJSONObject: parameters, options: .prettyPrinted)
}
Solution
if let parameters = parameters, JSONSerialization.isValidJSONObject(parameters) {
httpBody = try JSONSerialization.data(withJSONObject: parameters, options: .prettyPrinted)
}