I'm using Parse in my iOS app.
In my app I'm using a lot of saveEventually() functions to store data in Parse without there needing to be an internet connection available.
I know that saveEventually() returns a BFTask object.
It is possible to get all the tasks created in order to check their status in any given moment? Also, could this same technique be used after an app restart?
Thanks!
Parse is using Boltz framework, which in return, you will have a BFTask object upon completion.
Example:
yourPFObject.saveEventually().continueWithBlock {
(task: BFTask!) -> BFTask in
if task.isCancelled() {
// the save was cancelled.
} else if task.error() {
// the save failed.
} else {
// the object was saved successfully.
var object = task.result() as PFObject
}
}
With this in mind, you can check their status by storing the completed task in coredata. In appDelegate applicationDidBecomeActive or didFinishLaunchingWithOptions, you can attempt to retrieve them from coredata depending on your logic.
Of course if you want to just keep track on the status, you can keep in a dictionary and stores in NSUserDefault. It is completely up to your choice and needs.
More example can be found in this link : https://github.com/BoltsFramework/Bolts-iOS
Related
This question already has answers here:
Returning data from async call in Swift function
(13 answers)
Closed last year.
I am building a mobile app with swift, and am having some syntax issues as I am not a developer. The structure and logic of the application is really rough and surely incorrect, however we just need something that functions. (It is a school project and my team got no devs).
Anyways, we have a MySQL database that will be used as a middleman between our badge server/admin app, and our mobile app. Currently when you go to https://gatekeeperapp.org/service.php , you will see the current database data, taken by a php script and hosted there as JSON. Currently in Swift I have a struct with a function that takes this JSON data, and maps it to variables. The idea is to then pass these pulled variables into a separate set of functions that will check the pulled long/lat against the mobile devices location, and then return whether they match or not. This value would be updated, re-encoded to JSON, and pushed to a web service that would go about changing the values in the database so the badge server could use them.
Where I am currently I can see that values are being pulled and mapped and I can set a variable in a separate function to the pulled value, but then I can only seem to output this value internally, rather than actually use it in the function. I get a type error saying that the pulled values are of type (). How can I properly use these values? Ultimately I think I would want to convert the () to a double, so I could properly compare it to the Long/Lat of the device, and then will need to re-encode the new values to JSON.
Swift Code -- struct function
Swift code -- JSON struct
Swift code -- using pulled data
Your closure is called asynchronously, which means that the outer function where you are expecting to use the values has already returned by the time the closure is called. Instead, you probably need to call some other function from the closure, passing the values you've received.
class MyClass {
func fetchUserData() {
UserData().fetchUser { [weak self] user, error in
DispatchQueue.main.async {
if let user = user {
self?.handleSuccess(userID: user)
} else if let error = error {
self?.handleError(error)
}
}
}
}
private func handleSuccess(userID: String) {
print(userID)
// Do something with userID. Maybe assign it to a property on the class?
}
private func handleError(_ error: Error) {
print(error)
// Handle the error. Maybe show an alert?
}
}
I need to observe changes of an Entity after import occurred.
Currently I have next logic:
Save Entity with temp identifier (NSManagedObject.objectId) to local core data storage.
Send Entity to the server via Alamofire POST request.
Server generates JSON and reply with the almost the same Entity details but with modified identifier which was NSManagedObject.objectId previously. So the local one Entity id will be updated with server id.
Now when I received new JSON I do transaction.importUniqueObjects.
At this step I want to inform my datasource about changes. And refetch data with updated identifiers.
So my DataSource has some Entities in an array, and while I use this datasource to show data it's still static information in that array which I fetched before, but as you see on the step number 4 I already updated core data storage via CoreStore import and want DataSource's array to be updated too.
I found some information regarding ListMonitor in CoreStore and tried to use it. As I can see this method works when update comes
func listMonitorDidChange(_ monitor: ListMonitor)
but I try to refetch data somehow. Looks like monitor already contains some most up to date info.
but when I do this:
func listMonitorDidChange(_ monitor: ListMonitor<MyEntity>) {
let entities = try? CoreStore.fetchAll(
From<MyEntity>()
.orderBy(.ascending(\.name))
) // THERE IS STILL old information in database, but monitor instance shows new info.
}
And then code became like this:
func listMonitorDidChange(_ monitor: ListMonitor<MyEntity>) {
var myEntitiesFromMonitor = [MyEntity]()
for index in 0...monitor.numberOfObjects() {
myEntitiesFromMonitor.append(monitor[index])
}
if myEntitiesFromMonitor.count > 0 {
// HERE we update DataSource
updateData(with: myEntitiesFromMonitor)
}
}
not sure if I am on the right way.
Please correct me if I am wrong:
As I understood each time core data gets updated with new changes, monitor gets updated as well. I have not dive deep into it how this was made, via some CoreData context notification or whatever but after you do something via CoreStore transaction, such as create or update or delete object or whatever you want, monitor gets update. Also it has callback functions that you need to implement in your class where you want to observe any changes with data model:
Your classes such as datasource or some service or even some view controller (if you don't use any MVVP or VIPER or other design patterns) need to conform to ListObserver protocol in case you want to listen not to just one object.
here are that functions:
func listMonitorDidChange(monitor: ListMonitor<MyPersonEntity>) {
// Here I reload my tableview and this monitor already has all needed info about sections and rows depend how you setup monitor.
// So you classVariableMonitor which I provide below already has up to date state after any changes with data.
}
func listMonitorDidRefetch(monitor: ListMonitor<MyPersonEntity>) {
// Not sure for which purposes it. I have not received this call yet
}
typealias ListEntityType = ExerciseEntity
let classVariableMonitor = CoreStore.monitorSectionedList(
From<ListEntityType>()
.sectionBy(#keyPath(ListEntityType.muscle.name)) { (sectionName) -> String? in
"\(String(describing: sectionName)) years old"
}
.orderBy(.ascending(\.name))
.where(
format: "%K == %#",
#keyPath(ListEntityType.name),
"Search string")
)
All other thing documented here so you can find info how to extract info from monitor in your tableview datasource function.
Thanks #MartinM for suggestion!
I'm caching data in CoreData within my app to reduce updating request when there's nothing new.
Here's my caching logic in pseudo code:
if cacheExistsInCoreData {
if cacheIsOutdated {
loadDataFromRemoteAndCacheItWithCurrentDate()
}else {
useCache()
}else {
loadDataFromRemoteAndCacheItWithCurrentDate()
}
How I check if cache is outdated:
func checkIfCacheIsOutdated {
if lastCachedDate isOrderThan selfDefinedCheckingDate {
return true // need to load new data
}else {
return false // just use cache
}
}
This mechanism works fine almost all the time.
While in rare situation I find my program caches the wrong data with a right date, which means user might see the older data and could not get update when new one is available.
If there's nothing wrong with my caching logic, I wonder if the reason could be that when the remote data is fetched by my app before it gets updated and then gets stored in core data with the latest date time.
A cache in core data includes:
data(provided by remote server) //nothing I can do with it...
date(provided by me using NSDate())
How can I make sure if the two objects are correctly connected (latest data with current time) before storing them?
Assuming you are calling a web api, most of the web apis would return cache headers. It is safe to cache the data based on cache header value, that way there wont be any stale cache.
The solution that I came across that works for me is to set the stalenessInterval on the Main Queue NSManagedObjectContext as follows inside of your extension:
objectContext.stalenessInterval = 0.0;
This tells the context in the extension to fetch new data every time and ignore the cache.
In my app, I have a screen where the user can edit an object's attributes. The screen has 2 actions: save or revert.
When revert is selected, I want to revert the object's attributes to whatever is stored on the server and discard all local changes. I do this with the the fetch method:
println("1: \(localObject)")
if localObject.isDirty() {
localObject.fetchInBackgroundWithBlock { serverObject, error in
println("2: \(localObject)")
println("3: \(serverObject)")
}
}
isDirty() works correctly and only returns true if something was locally modified and not saved.
According to the docs, fetch: fetches the PFObject with the current data from the server. But what I see in the log is that all 3 print statements show the same local attributes. I would expect serverObject to be whatever is stored on the server, but it's not, serverObject also shows the locally modified attributes.
I'm not using the Local Datastore.
What is going on? Is this a bug with the Parse SDK? Or is there a caching issue?
If you call fetch without using the local datastore, Parse will give you a error in the console.
Since you're not getting an error from Parse when you call fetchInBackgroundWithBlock, it means that at some point you've called localObject.pinInBackground() and placed that object in the local datastore.
For locally stored objects, you can call revert() before fetchInBackgroundWithBlock to discard local changes before updating data from the server.
if localObject.isDirty() {
localObject.revert()
localObject.fetchInBackgroundWithBlock { serverObject, error in
println("2: \(localObject)")
println("3: \(serverObject)")
}
}
I haven't seen the docs for fetch, but I do know that you can accomplish what you're trying just by performing a query. Doing the following will query from the server (although you can adjust the cache policy if you've already used the query to look at cache or network first):
var query = PFQuery(className:"GameScore")
query.getObjectInBackgroundWithId("xWMyZEGZ") {
(gameScore: PFObject?, error: NSError?) -> Void in
if error == nil && gameScore != nil {
println(gameScore)
}else {
println(error)
}
}
Alternatively, you could just save a copy of the local object before any changes can be made, and then if it is dirty, you can just replace it with the copy. That's probably what I'd do to reduce the number of parse API calls.
I want to backup the Data of an UITableView to Parse. First the User logs in. Now every user should have it's own cloud Storage save of their UITableView, but how to upload this UITableView or the UITableViewCell when pressing on a Button (Save)?
You can create separate Model objects for populating each cell of the UITableView . Saving objects into parse is very well in explained in the documentation:
Also take a look at this tutorial if you still need more explanation:
Tutorial
Parse is just a backend which allows you to save data by calling their functions.
The concept of parse is to show the data in your app by using their api and database.
An example to save a PFObject (A parse object) in the parse database:
let testObject = PFObject(className: "TestObject") // Create the PFObject with the classname/tablename on parse
testObject["foo"] = "bar" // set the property "foo" to "bar"
// save the object in background (not on the gui thread to prevent lags on the UI)
testObject.saveInBackgroundWithBlock { (success: Bool, error: NSError?) -> Void in
println("Object has been saved.")
}
ATTENTION: Before to be able to call these functions and create there objects you need to import the Parse Library and initialize your app to parse.