object content different from within method - swift

I am encountering a strange behavior with an App I am building.
I have a struct Record and I create an instance of it from a NavigationViewController's child ViewController.
We can see it like this:
NavigationController (called TaskTabsController)
|
ViewController (called TaskFromController)
If I put a breakpoint, I can inspect and check the content of the object is what it should (conform to interface input data).
But when I call a method on that instance (the very next line), the different members have different or missing values.
The object instance is created from within TaskTabsController with something like so:
if let formVC = (viewControllers?[1] as? TaskFormViewController) {
let rec: TaskRecord? = formVC.makeTaskRecord()
// here the rec data are correct when I inspect the instance
rec?.prepareData()
// from the prepareData function, the properties are different of can't be accessed...
}
Ex: From controller, I can see my instance rec having a member task instance (of Task type) with a name property.
But from within the prepareData method, that task member can't display the name attached to it. (debugger says value unreadable or something like that)
Also, I can see a list of other objects, but in the method, their count is different...
Here is how makeTaskRecord method works: (from my TaskFormViewController)
In TaskFormFiewController I have a private property like so:
private var stepsSelected: [StepRecord] = []
That property is updated whit user actions.
The StepRecord is a struct. (so should be passed by value I think)
Next is the makeTaskRecord method in the same controller (gathering details from form elements)
func makeTaskRecord() -> TaskRecord? {
guard let current_task = task
else {
print("TaskForm.makeTaskRecord(): Can't create a record without a Task")
return nil
}
// check data validity
var errors:[String] = []
// generate StepRecords >>>
if stepsSelected.count == 0 {
errors.append("Error, no step selected")
}
// <<<
// DATA OK
if errors.isEmpty {
let taskrecord = TaskRecord(
id: record?.id,
taskId: task!.id,
userCode: Config.getUser()!.code, // global object containing login state
pictures: [],
memo: memo.text,
task: current_task,
stepRecords: stepsSelected // data here is correctly set
)
return taskrecord // but here, I can't retreive the stepRecords! (1)
}
else {
// display errors in an alert box
let errorVC = MyHelpers.makeAlert("Error with data", errors.joined(separator: "\n"))
self.present(errorVC, animated: true)
return nil
}
}
Here is how the prepareData() method looks like: (method of Record class)
func prepareData() -> [String: Any]? {
// no steps in record, data is invalid
guard
let stepRecordsJson = try? JSONEncoder().encode(self.stepRecords)
// This is where I constated that stepRecords is different (set but empty)
else {
return nil
}
// other data
var params = [
"task_id": taskId,
"user_code": userCode,
"step_records": stepRecordsJson, // this is what doesn't work well, but it's certainly related to (1) above
"memo": memo ?? ""
] as [String : Any]
// when editing, we set again the record ID
if let edit_id = self.id {
params["id"] = edit_id
}
return params
}
Note: All the data structures and Codable struct
In the sample above, at the (1), I put a breakpoint on the return line.
When checking data, I see that the "sub-struct" do not have good values:
(lldb) print current_task.name
(String) $R0 = "屋根工事"
(lldb) print taskrecord.task.name
error: Execution was interrupted, reason: EXC_BAD_ACCESS (code=1, address=0x6f).
The process has been returned to the state before expression evaluation.
The task is a struct so there should not be problems about it, but the debugger can't read the task I assigned to my taskrecord.
Is there something I am missing? Like a pointer reference of some sort?

Related

Core Data Object was written to, but never read

As I try to update an existing entry in my Core Data DB, I fetch the desired item by id, change it to a new item and save in context.
However, when I fetch the object and replace it, I get the warning "Core Data Object was written to, but never read." It does make sense since I'm not really using that object, but as I understand it, just giving it a value saves it in Core Data.
static var current: User? {
didSet {
if var userInCoreData = User.get(with: current?.id), let current = current { //userInCoreData is the value with the warning
userInCoreData = current
}
CoreDataManager.saveInContext()
}
}
static func get(with id: String?) -> User? {
guard let id = id else { return nil }
let request: NSFetchRequest = User.fetchRequest()
let predicate = NSPredicate(format: "id = %#", id)
request.predicate = predicate
do {
let users = try CoreDataManager.managedContext.fetch(request)
return users.first
} catch let error {
print(error.localizedDescription)
return nil
}
}
I want to make sure, is this the recommended process to overwrite a value in Core Data, or am I doing something wrong?
This section
if var userInCoreData = User.get(with: current?.id), let current = current { //userInCoreData is the value with the warning
userInCoreData = current
}
seems just updating local variable userInCoreData, not User object in Core Data.
So the warning says "you fetched data from core data and set to a variable, but you set another value to the variable soon, never use the first value from core data. Is it OK?"
What you really want to do is something like this?
if var userInCoreData = User.get(with: current?.id), let current = current {
userInCoreData.someValue = current.someValue
userInCoreData.anotherValue = current.anotherValue
}

getting a fully populated array outside the trailing closure after asynchronous function finishes executing in Swift

I am trying to populate finalArray with the result of asynchronous call from my extractProperties() function.
class ViewController: UIViewController {
var finalArray: [SingleRepository] = []
let extractor = Extractor()
override func viewDidLoad() {
super.viewDidLoad()
print("Starting the program... ")
extractor.extractProperties { object, error in
guard let object = object else {
print("Extractor did not reutrn data")
return
}
self.finalArray.append(object)
print("Appended successfully --- \(self.finalArray.count) --- inside the trailing closure")
}
print("Size of the array --- \(self.finalArray) --- outside the trailing closure")
}
The issue is that I can't get the fully populated finalArray to work with outside the scope of trailing closure!
Output log:
Starting the program...
Size of the array --- [] --- outside the trailing closure
Appended successfully --- 1 --- inside the trailing closure
Appended successfully --- 2 --- inside the trailing closure
Appended successfully --- 3 --- inside the trailing closure
.
.
.
Appended successfully --- 300 --- inside the trailing closure
I know why the print statement from outside get executed first, but I never can get my fully populated array with all 300 objects in it.
Please note that the following post did NOT solve my problem:Run code only after asynchronous function finishes executing
I even tried solution in that post by writing a the following function:
func constructingFinalArray(completionBlock: #escaping ([SingleRepository]) -> Void) {
var fArrray: [SingleRepository] = []
extractor.extractProperties { data, error in
guard let data = data else {
print("Extractor did not reutrn data")
return
}
fArrray.append(data)
completionBlock(fArrray)
}
}
and called it inside viewDidLoad() as following, but confusingly I got the same result and array gets populated element by element, therefore never be able to access a fully populated array from out of trailing closures!
constructingFinalArray { array in
print("array size from constructingFinalArray function: \(array.count) ")
}
output:
Starting the program...
array size from constructingFinalArray function: 1
array size from constructingFinalArray function: 2
array size from constructingFinalArray function: 3
.
.
.
extractProperties get called 300 times exactly and sometimes it returns no date(Error).
// Class to extract the required properties and
// provide ready to use object for ViewController
class Extractor {
private let client = RepoViewerAPIClient()
private var allURLs: [RepositoryURL] = []
var allRepositories: [SingleRepository] = []
// Method to extract all the required properties
// compromising of 2 asynchrounous call, (nested asynch call)
// one to get the urls and another call within the first call to extract all the propreties
func extractProperties(completionHandler: #escaping (SingleRepository?, RepoViewerErrors?) -> Void) {
// implementation of nested asynchronous calls are deleted to shorten the question length
}
}
It seems that after you call once
extractor.extractProperties {
...
}
The closure gets called exactly 300 times but sometimes it can return no data.
In this case you can follow this approach.
extractor.extractProperties { object, error in
serialQueue.async { [weak self] in
count += 1
guard count < 300 else {
self?.didCompletePopulation()
return
}
guard let object = object else {
print("Extractor did not reutrn data")
return
}
self?.finalArray.append(object)
}
}
func didCompletePopulation() {
// be aware, this is not called on the main thread
// if you need to update the UI from here then use the main thread
print("Final array is populated \(self.finalArray)")
}
How does it work?
The body of the closure is wrapped is wrapped into another closure executed through a Serial Queue. This way we are sure the share resources (finalArray and count) are accessed safely.
serialQueue.async { [weak self] in
...
}
Next every execution of the closure increments count by 1.
Then we make sure count is minor than 300, otherwise we stop the execution of the closure an call didCompletePopulation().
guard count < 300 else {
self?.didCompletePopulation()
return
}
We check if the result contains a proper value otherwise we stop the execution of the current closure
guard let object = object else {
print("Extractor did not reutrn data")
return
}
And finally we add the new element to the array
self?.finalArray.append(object)

Result of call is unused

Right below the second comment, I receive an error of "Result of call to 'taskForDeleteMethod' is unused. Why is this when I use the results and error in the closure following the call?
func deleteSession(_ completionHandlerForDeleteSession: #escaping (_ success: Bool, _ error: NSError?) -> Void) {
/* 1. Specify parameters, method (if has {key}), and HTTP body (if POST) */
// There are none...
/* 2. Make the request */
taskForDELETEMethod { (results, error) in
/* 3. Send the desired value(s) to completion handler */
if let error = error {
print("Post error: \(error)")
completionHandlerForDeleteSession(false, error)
} else {
guard let session = results![JSONKeys.session] as? [String: AnyObject] else {
print("No key '\(JSONKeys.session)' in \(results)")
return
}
if let id = session[JSONKeys.id] as? String {
print("logout id: \(id)")
completionHandlerForDeleteSession(true, nil)
}
}
}
}
In earlier swift versions, you need not bother about the return value of a method. You may store it in any variable snd use it later or you may ignore it completely. Neither it gave any error nor a warning.
But in swift 3.0 you need to specify whether you want to ignore the returned value or use it.
1. If you want to use the returned value, you can create a variable/constant and store the value in it, i.e
let value = taskForDELETEMethod {
// Your code goes here
}
2. If you want to ignore the returned value, you can use _ ,i.e
let _ = taskForDELETEMethod {
// Your code goes here
}
You are confusing the results variable, which is, indeed, used inside the closure, and the result of the taskForDELETEMethod call itself, which is NSURLSessionDataTask object.
From the examples of using taskForDELETEMethod that I was able to find online it looks like it is perfectly OK to ignore the return value, so you can avoid this warning by assigning the result to _ variable, i.e.
let _ = taskForDELETEMethod {
... // The rest of your code goes here
}

Exception throw for null data with optional Int type in Realm

I am taking my first foray into using Realm (0.98.1 via Cocoapods, Xcode 7.2) and am running into a small problem that I am not sure how to solve.
I have a model class called Airport that declares a property
let elevationFt = RealmOptional<Int>()
I am creating a set of Airport objects and persisting them in the following way
public func cacheDataToPersistanceStore(data:NSArray) -> Bool {
var success = true
autoreleasepool {
do {
let realm = try Realm()
realm.beginWrite()
for object in data {
guard let dictionaryValues = object as? Dictionary<String, AnyObject> else {
debugPrint("Unable to convert data to correct type")
success = false
return
}
if(dictionaryValues["airportID"] as! Int == 6605) {
realm.create(Airport.self, value: dictionaryValues, update: true)
}
}
try realm.commitWrite()
}
catch(let e) {
debugPrint(e)
success = false
}
}
return success
}
For the airport entry in question, the dictionary that stores the relevant data looks to have a null value for the key "elevationFt", so I assume things will be OK for the optional Int property
Here is a string version of the dictionary:
["gps_code": 01MD, "ident": 01MD, "iata_code": , "local_code": 01MD, "keywords": , "elevationFt": , "type": seaplane_base, "municipality": Annapolis, "iso_country": US, "airportID": 6605, "longitudeDeg": -76.45600128173828, "latitudeDeg": 38.99919891357422, "iso_region": US-MD, "wikipedia_link": , "name": Annapolis Seaplane Base, "scheduled_service": no, "continent": NA, "home_link": ]
However once the create function starts for this set of data, an exception is thrown:
Terminating app due to uncaught exception 'RLMException', reason: 'Invalid value '' for property 'elevationFt''
I am guessing I have something set up incorrectly, but I am not quite sure how to fix this other than to clean my source data for that particular field.
The screenshot from the debugger shows that elevationFt is an empty string, which is not a number or null, so it is not a valid value for an optional int property.

How to return values from Haneke's async fetch method

I'm trying to parse some data that I cached using Haneke Swift. I've cached the data and have written the parser to accomplish this. This parser is in a separate class called AssembleCalendar().
Using Haneke's example code for fetching, I've tried with complete and utter failure to actually return a value from the closure.
My attempt
func getScheduledItems() -> [ScheduledItem] {
var scheduledItem = [ScheduledItem]() // initialize array
let cache = Shared.dataCache
cache.fetch(key: "CalendarData").onSuccess { data in
scheduledItem = AssembleCalendar().assimilate(data) // update array
print(scheduledItem) // array returns expected value
}
print(scheduledItem) // array returns nil
return scheduledItem // returns nil
}
What I know
I understand that this is an asynchronous issue. My code isn't waiting for my AssembleCalendar() parser to finish. It's just running each line and returns nil long before my scheduledItem receives a value. I've tried many, many solutions and read quite a few examples online but I cannot figure out how to retrieve a value from this closure in this scenario.
My question
How can I get .fetch() to return a value before my function hits nil?
update:
Here's my code in context:
class Schedule {
var items : [ScheduledItem]
init() {
items = getScheduledItems() // Schedule.getScheduledItems()
}
func getScheduledItems(completion: (items: [ScheduledItem]) -> ()) {
var scheduledItem = [ScheduledItem]() // initialize array
let cache = Shared.dataCache
cache.fetch(key: "CalendarData").onSuccess { data in
scheduledItem = AssembleCalendar().assimilate(data) // update array
print(scheduledItem) // array returns expected value
completion(items: scheduledItem)
}
}
}
Fetch() is using a completion handler, this means that the block of code called in there is actually executing AFTER your return statement has been executed. This is why it is returning nil. To get around this, you can use your own completion handler to return the information:
class Schedule {
var items = [ScheduledItem]()
init(items: [ScheduledItem]) {
self.items = items
}
class func getScheduledItems(completion: (items: [ScheduledItem]) -> ()) {
var scheduledItem = [ScheduledItem]() // initialize array
let cache = Shared.dataCache
cache.fetch(key: "CalendarData").onSuccess { data in
scheduledItem = AssembleCalendar().assimilate(data) // update array
print(scheduledItem) // array returns expected value
completion(items: scheduledItem)
}
}
}
Then initialise the class using this:
Schedule.getScheduledItems { (items) -> () in
var schedule = Schedule(items: items)
}