Getting nil values when `map`-ing over array of entities - swift

I'm using Core Data in an iOS app and executing the following Swift code which results in an error at the second line:
let movies = (try? container.newBackgroundContext().fetch(request)) ?? []
return movies.map { $0.name! } // error: unexpectedly found nil while unwrapping...
Note that in the example above it's 100% certain that there is no entity in movies with nil in name. The corresponding attribute in the Core Data model is set to not optional.
When I change the code as shown below (i.e. not inlining newBackgroundContext()) there is no error:
let context = container.newBackgroundContext()
let movies = (try? context.fetch(request)) ?? []
return movies.map { $0.name! } // no error this time
I'm quite new to Swift and assume it has something to do with memory management (e.g. context is deinited prematurely) but I would appreciate an actual explanation of why the error occurs in the first code listing.

Whenever you run map function on your array it returns an optional value. seems like there is no value in name and you are doing force unwrapping. try to use compactMap.
movies.compactMap { $0.name }

Related

No exact matches in call to initializer when initializing Data in AppStorage

I'm learning how to store custom types in AppStorage, and came across an issue. In this simplified example, I'm trying to save an empty Int array to AppStorage once the view is created.
The following code gives me the error, No exact matches in call to initializer . I know that this error usually means there are mismatching types somewhere, but I'm not sure what the types should be, or how to fix it.
struct test: View {
init() {
let emptyList = [Int]()
guard let encodedList = try? JSONEncoder().encode(emptyList) else { return }
self.storedList = encodedList
}
#AppStorage("stored_list") var storedList: Data //NO EXACT MATCHES TO CALL IN INITIALIZER
//"body" implementation not shown
}
Why is this error occurring, and how can I fix it?
It should be either with default value or optional, so correct variants are
#AppStorage("stored_list") var storedList: Data = Data()
or
#AppStorage("stored_list") var storedList: Data?

How to get value of a NSSingleObjectArrayI

func responseDataHandler(data: NSDictionary) {
let temperature_c = data.value(forKeyPath: "data.current_condition.temp_C")
DispatchQueue.main.async{
self.Temperature.text = temperature_c as? String
}
}
I have the above code where I am accessing a weather API which returns data in the form of an NSDictionary to this function. I need to access the value in temperature_c which when I try to print it, it says that it is: Optional(<__NSSingleObjectArrayI 0x600002147fd0>(
25
)
). Temperature is the outlet for label on my storyboard which I want to take on the value of 25 however as written now, it doesn't work and I have tried everything to try and access the value in the Single Object Array but nothing is working. I found this stack overflow question that was similar but it doesn't work for my situation because I keep getting the error that temperature_c is of type any and doesn't have subscripts.
The issue is that you can't cast to String an array, you should try to convert it to [String]. So could change your code to:
self.Temperature.text = (temperature_c as? [String])?.first ?? "Not available"
Let's go step by step:
temperature_c as? [String] tries to convert the NSDictionary to a String array which is the expectable type.
Since the previous step may return nil we have to use optional chaining ?. If we got a valid array using first return the the arrays first element.
Since both previous steps can return nil we can use nil coalescing operator to return a default value. In this case I use "Not available" but you can set any value.
You could write it in a more verbose way like this:
var text2Display2 = "Not available"
if let theArray = temperature_c as? [String] {
if let element = theArray.first {
text2Display2 = element
}
}
self.Temperature.text = text2Display2

ReactiveKit Bond KVO observe UserDefaults

I was previously using RxSwift and I decided I did not want to use it anymore and was able to convert everything over to Bond which I am much more familiar with. Since the new changes though to Bond v5, I cannot seem to figure out how to observe values in UserDefaults. The following code ends up giving me a fatal error.
userDefaults.reactive
.keyPath(LocationManager.HomeLocationKey, ofType: String.self, context: .immediateOnMain)
.map(self.initLocation(from:))
.bind(to: self.homeLocation)
userDefaults is a reference to UserDefaults.standard and LocationManager.HomeLocationKey is a string. I am providing the initLocation function below as I know it will be asked for. Below that function I will post the error that I am receiving after the app starts up.
func initLocation(from string: String?) -> Location?
{
guard let dataString = string
else { log.warning("Location data did not exist, returning nil"); return nil }
let json = JSON.parse(dataString)
return Location(from: json)
}
Error:
fatal error: Could not convert nil to String. Maybe `dynamic(keyPath:ofExpectedType:)` method might be of help?): file /Users/sam/Documents/iOS Apps/Drizzle/Pods/Bond/Sources/Shared/NSObject+KVO.swift, line 58
It might not be obvious, but if the observed value can be nil, the ofType argument must be an Optional type. In your case, that would be:
userDefaults.reactive
.keyPath(LocationManager.HomeLocationKey, ofType: Optional<String>.self, context: .immediateOnMain)
...

How to fetch core data objects into Dictionary in Swift?

I've saved objects in core data, and I am looking how to fetch those objects as a Dictionary
Here is an example of my code where sections is keys for the dictionary and Company as an array of core data objects.
private var companies = Dictionary<String, Array<Company>>()
private var sections: [String] = ["Pending", "Active", "Pending"]
override func viewWillAppear(_ animated: Bool) {
let fetchRequest : NSFetchRequest<Company> = Company.fetchRequest()
let moc = DatabaseController.getContext()
do {
let request = try moc.fetch(fetchRequest)
for case let (index, object) in request.enumerated() {
companies[sections[index]]!.append(object)
}
} catch let error as NSError {
print("Could not fetch. \(error), \(error.userInfo)")
}}
When I am trying to execute my code, I have an error:
fatal error: unexpectedly found nil while unwrapping an Optional value
Could anyone help me with that issue?
That error message means that you're force unwrapping an optional that doesn't have a value. In other words you're using ! where you shouldn't. (You should basically never use the force unwrap operator (!).)
Let's look at the line where you do that:
companies[sections[index]]!.append(object)
If we break this down and add the inferred types we have:
let section: String? = sections[index]
let companyArray: Array<Company> = companies[section]!
You're crashing because companies starts off empty, so asking for any of the arrays will return nil. (Actually, I'm not sure how your code is compiling, since you can't subscript into the dictionary with an optional.)
However, if you fix that, we still have a problem because you're using the index of the fetched array to look up the section. If you have more than three companies, that will start to fail.
I suspect you want something like:
for let company in result {
if var companyArray = companies[company.status] {
companyArray.append(company)
} else {
companies[company.status] = [company]
}
}
Where status is an invented property on Company that returns a String like "Pending" or "Active".
I've found the solution, just need to use NSFetchResultController in order to display data in TableView by different sections.

Foundation._GenericObjCError.NilError from core data batch delete

I am trying to use batch delete feature of core data. I have an entity named Car. That entity has a column name modelNumber as Int. I want to delete all cars which has modelNumber older than 2000. Here is my code:
func deleteCarsOlderThan(modelNumber: Int) {
let predicate = NSPredicate(format: "modelNumber <= %#", NSNumber(int: modelNumber))
let fetchRequest = NSFetchRequest(entityName: "Car")
fetchRequest.predicate = predicate
let deleteRequest = NSBatchDeleteRequest(fetchRequest: fetchRequest)
deleteRequest.resultType = .ResultTypeCount
do {
let result = try self.fhirManagedObjectContext.executeRequest(deleteRequest)
try self.fhirManagedObjectContext.save()
}
catch {
print(error)
}
}
While executing this code, control goes to catch block and it gives an error says: Foundation._GenericObjCError.NilError. My fetch request is working well as if I use:
let olderCars = self.executeFetchRequest(fetchRequest)
it returns me an array of older cars. I don't know where I am doing wrong.
I am using iOS9 for this purpose.
TL;DR: While self.fhirManagedObjectContext is non-optional, it's probably returning nil from Objective-C.
The error you observed is generated by Swift's Foundation bridging runtime. (See the source code here.) This occurs when an Objective-C method with an error pointer returns a failure value (NO or nil), but no actual error was passed back via the NSError pointer. This could either be the result of a bug in Core Data or, more likely, a nil managed object context that when using Objective-C method dispatch causes the method to appear to return NO.