In my iOS app users complete transactions which I need to post back to the server. I've created a function to do this:
static let configurationParam = NSURLSessionConfiguration.defaultSessionConfiguration()
static var manager = Alamofire.Manager(configuration: configurationParam)
func postItemToServer(itemToPost:DemoItem) {
let webServiceCallUrl = "..."
var itemApiModel:[String: AnyObject] = [
"ItemId": 123,
"ItemName": itemToPost.Name!,
//...
]
ApiManager.manager.request(.POST, webServiceCallUrl, parameters: itemApiModel, encoding: .JSON)
.validate()
.responseJSON { response in
switch response.result {
case .Success:
print("post success")
case .Failure:
print("SERVER RESPONSE: \(response.response?.statusCode)")
}
}
}
Currently I call this once a transaction is complete:
//...
if(transactionCompleted!) {
let apiManager = ApiManager()
apiManager.postItemToServer(self.item)
self.senderViewController!.performSegueWithIdentifier("TransactionCompletedSegue", sender: self)
}
//...
Where DemoItem is a CoreData object.
This all works as expected. However I need the ability to retry the POST request if it fails. For example if the network connection is down at the point of trying post to the server I need to automatically post the data once it becomes active again - at which point there may be several DemoItem's which need to be synced.
I'm new to Swift. In a similar Xamarin app I had a status column in my SQLite database which I set to 'AwaitingSync'. I then had an async timer that ran every 30 seconds, queried the DB for any items which had status='AwaitingSync' and then tried to post them if they existed. If it succeed it updated the status in the DB. I could implement something along the same lines here - but I was never really happy with that implementation as I had a DB query every 30 seconds even if nothing had changed.
Finally, it needs to be still work if the app is terminated. For example any items which weren't synced before the app is killed should sync once the app is resumed. What's the best way to approach this?
Edit
Based on Tom's answer I've created the following:
class SyncHelper {
let serialQueue = dispatch_queue_create("com.mycompany.syncqueue", DISPATCH_QUEUE_SERIAL)
let managedContext = (UIApplication.sharedApplication().delegate as! AppDelegate).managedObjectContext
func StartSync() {
//Run on serial queue so it can't be called twice at once
dispatch_async(serialQueue, {
//See if there are any items pending to sync
if let itemsToSync = self.GetItemsToSync() {
//Sync all pending items
for itemToSync in itemsToSync {
self.SyncItemToServer(itemToSync)
}
}
})
}
private func GetItemsToSync() -> [DemoItem]? {
var result:[DemoItem]?
do {
let fetchRequest = NSFetchRequest(entityName: "DemoItem")
fetchRequest.predicate = NSPredicate(format: "awaitingSync = true", argumentArray: nil)
result = try managedContext.executeFetchRequest(fetchRequest) as? [DemoItem]
} catch {
//Handle error...
}
return result
}
private func SyncItemToServer(itemToSync:DemoItem) {
let apiManager = ApiManager()
//Try to post to the server
apiManager.postItemToServer(itemToSync:DemoItem, completionHandler: { (error) -> Void in
if let _ = error {
//An error has occurred - nothing need to happen as it will be picked up when the network is restored
print("Sync failed")
} else {
print("Sync success")
itemToSync.awaitingSync = false
do {
try self.managedContext.save()
} catch {
//Handle error...
}
}
})
}
}
I then call this when ever a transaction is completed:
//...
if(transactionCompleted!) {
let syncHelper = SyncHelper()
syncHelper.StartSync()
}
//...
And then finally I've used Reachability.swift to start the sync every time the network connection resumes:
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
var reachability:Reachability?
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
//...
//Setup the sync for when the network connection resumes
do {
reachability = try Reachability.reachabilityForInternetConnection()
NSNotificationCenter.defaultCenter().addObserver(self,
selector: "reachabilityChanged:",
name: ReachabilityChangedNotification,
object: reachability)
try reachability!.startNotifier()
} catch {
print("Unable to create Reachability")
}
return true
}
func reachabilityChanged(note: NSNotification) {
let reachability = note.object as! Reachability
if reachability.isReachable() {
print("Network reachable")
let syncHelper = SyncHelper()
syncHelper.StartSync()
} else {
print("Not reachable")
}
}
}
This all seems to be working. Is this approach ok and have I missed anything which would improve it? The only gap I can see is if the network connectivity is active however the server throws an error for some reason - I guess I could then add a button for the user to retry any pending items.
Firstly, if your concern is whether the network connection is working, you shouldn't be polling at intervals. You should be using iOS's network reachability API to get notified when the network status changes. Apple provides a simple implementation of this and there are numerous alternative implementations online.
Since a sync status value should be a boolean flag, it's not as if a fetch request is a heavy-duty operation, especially if you use reachability. Not only should the fetch request be fast, you can update the flag after the fact in a single step-- use NSBatchUpdateRequest to set the flag to false on every instance you just sent to the server.
If you want to get the sync status out of the persistent store (not a bad idea since it's metadata), you'll need to maintain your own list of unsynced objects. The best way to do this is by tracking the objectID of the managed objects awaiting sync. That would be something like:
Get the objectID of a newly changed managed object
Convert that to an NSURL using NSManagedObjectID's URIRepresentation() method.
Put the NSURL on a list that you save somewhere, so it'll persist.
You can save the list in a file, in user defaults, or in the persistent store's own metadata.
When it's time to sync, you'd do something like:
Get an NSURL from your list
Convert that into an NSManagedObjectID using managedObjectIDForURIRepresentation(url:NSURL) (which is on NSPersistentStoreCoordinator)
Get the managed object for that ID objectWithID: on NSManagedObjectContext.
Sync that object's data.
Then on a successful sync, remove entries from the list.
Related
I wanted to fetch data from the server api.
The issues is that all networking frameworks are doing it Async.
So I have issues that return variable return empty Here is my code.
The view controller where I call the function
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
let url = "http://api.musixmatch.com/ws/1.1/track.lyrics.get?track_id=12693365&apikey=63ee7da5e2ee269067ecc42b25590922"
let musixrequest = MusicMatchRequest()
let endResults = musixrequest.gettingLyrics(url: url)
if !endResults.isEmpty{
print("The end results are \(endResults)")
}else{
print("No results found")
}
}
Here is my class where I am trying to fetch the data
public class MusicMatchRequest : NSObject{
public override init(){}
public func gettingLyrics(url : String) -> String {
var endResults = ""
DefaultProvider.request(Route(path:"\(url)")).responseJSON { (response:Response<Any>) in
switch response.result{
case .success(let json):
endResults = String(describing:json)
print(endResults)
case .failure(let error):
print("error: \(error)")
}
}
return endResults
}
}
When I am printing the endRsults from the task it is working It print the results but the var endResults return empty.
Idea how to transfer the data .
I have tried two frameworks
Alamofire
Nikka
In both frameworks it's acting the same .
Solution
I don't exactly know what happens under the hood, but as ANY network operation this also has to be asynchronous (meaning it will take a certain amount of time to fetch the data).
let endResults = musixrequest.gettingLyrics(url: url)
If it's synchronously done on the Main thread, it will block it so the user can't interact with the app, which is pretty bad. Given it's asynchronous in your code you read the value in the very next line immediately, here:
if !endResults.isEmpty {
print("The end results are \(endResults)")
} else {
print("No results found")
}
It's very unlikely that the network operation will finish in one line step time, so you won't have the data there.
What you should do is to pass a completion handler in this method:
public func gettingLyrics(url : String) -> String
and dispatch to main thread like this:
DispatchQueue.main.async {
// do you UI stuff here
}
Change you function to this:
public func gettingLyrics(url : String, completionHandler: (String) -> Void)
and call the completion handler in the success branch:
completionHandler(String(describing:json))
I'm trying to implement a contacts reader in Xamarin.iOS which tries to iterate over the iOS contacts in all CNContactStore containers. Instead of loading all contacts into memory, I need to iterate over a contacts resultset batch by batch (paging contacts). However all the examples that I saw in SO load almost all contacts into memory first.
i.e. This question has loads of similar examples that read all contacts at once. Although these examples have logic which iterates one by one, it is not evident to me how to skip N and take the next N number of contacts without iterating from the beginning on the next call (which looks sub optimal at least to me).
Apple's own documentation reads
When fetching all contacts and caching the results, first fetch all contacts identifiers, then fetch batches of detailed contacts by identifiers as required
I was able to do this easily for Android using the cursor based approach available in its SDK. Is this at all possible for iOS? If not how can we handle a large number of contacts (e.g. something above 2000, etc.). I don't mind examples in swift. I should be able to convert them to Xamarin.
Thanks in advance.
Here's the approach I took, granted my requirements did not allow persisting contacts, only holding in active memory. Not saying it's the right approach, but fetching all identifiers first, then lazily fetching all keys for a specific contact as needed, did improve performance. It also avoids performing a lookup when the contact doesn't exist.
I also tried using NSCache instead of dictionary, but ran into issue when I needed to iterate over the cache.
I truncated functions that aren't relevant to the topic but hopefully still convey the approach.
import Contacts
extension CNContactStore {
// Used to seed a Contact Cache with all identifiers
func getAllIdentifiers() -> [String: CNContact]{
// keys to fetch from store
let minimumKeys: [CNKeyDescriptor] = [
CNContactPhoneNumbersKey as CNKeyDescriptor,
CNContactIdentifierKey as CNKeyDescriptor
]
// contact request
let request = CNContactFetchRequest(keysToFetch: minimumKeys)
// dictionary to hold results, phone number as key
var results: [String: CNContact] = [:]
do {
try enumerateContacts(with: request) { contact, stop in
for phone in contact.phoneNumbers {
let phoneNumberString = phone.value.stringValue
results[phoneNumberString] = contact
}
}
} catch let enumerateError {
print(enumerateError.localizedDescription)
}
return results
}
// retreive a contact using an identifier
// fetch keys lists any CNContact Keys you need
func get(withIdentifier identifier: String, keysToFetch: [CNKeyDescriptor]) -> CNContact? {
var result: CNContact?
do {
result = try unifiedContact(withIdentifier: identifier, keysToFetch: keysToFetch)
} catch {
print(error)
}
return result
}
}
final class ContactsCache {
static let shared = ContactsCache()
private var cache : [String : ContactCacheItem] = [:]
init() {
self.initializeCache() // calls CNContactStore().getAllIdentifiers() and loads into cache
NotificationCenter.default.addObserver(self, selector: #selector(contactsAppUpdated), name: .CNContactStoreDidChange, object: nil)
}
private func initializeCache() {
DispatchQueue.global(qos: .background).async {
let seed = CNContactStore.getAllIdentifiers()
for (number, contact) in seed{
let item = ContactCacheItem.init(contact: contact, phoneNumber: number )
self.cache[number] = item
}
}
}
// if the contact is in cache, return immediately, else fetch and execute completion when finished. This is bit wonky to both return value and execute completion, but goal was to reduce visible cell async update as much as possible
public func contact(for phoneNumber: String, completion: #escaping (CNContact?) -> Void) -> CNContact?{
if !initialized { // the cache has not finished seeding, queue request
queueRequest(phoneNumber: phoneNumber, completion: completion) // save request to be executed as soon as seeding completes
return nil
}
// item is in cache
if let existingItem = getCachedContact(for: phoneNumber) {
// is it being looked up
if existingItem.lookupInProgress(){
existingItem.addCompletion(completion: completion)
}
// is it stale or has it never been looked up
else if existingItem.shouldPerformLookup(){
existingItem.addCompletion(completion: completion)
refreshCacheItem( existingItem )
}
// its current, return it
return existingItem.contact
}
// item is not in cache
completion(nil)
return nil
}
private func getCachedContact(for number: String) -> ContactCacheItem? {
return self.cache.first(where: { (key, _) in key.contains( number) })?.value
}
// during the async initialize/seeding of the cache, requests may come in from app, so they are temporarily 'queued'
private func queueRequest(phoneNumber: String, completion: #escaping (CNContact?) -> Void){..}
// upon async initialize/seeding completion, queued requests can be executed
private func executeQueuedRequests() {..}
// if app receives notification of update to user contacts, refresh cache
#objc func contactsAppUpdated(_ notification: Notification) {..}
// if a contact has gone stale or never been fetched, perform the fetch
private func refreshCacheItem(_ item: ContactCacheItem){..}
// if app receives memory warning, dump data
func clearCaches() {..}
}
class ContactCacheItem : NSObject {
var contact: CNContact? = nil
var lookupAttempted : Date? // used to determine when last lookup started
var lookupCompleted : Date? // used to determien when last successful looup completed
var phoneNumber: String //the number used to look this item up
private var callBacks = ContactLookupCompletion() //used to keep completion blocks for lookups in progress, in case multilpe callers want the same contact info
init(contact: CNContact?, phoneNumber: String){..}
func updateContact(contact: CNContact?){..} // when a contact is fetched from store, update it here
func lookupInProgress() -> Bool {..}
func shouldPerformLookup() -> Bool {..}
func hasCallBacks() -> Bool {..}
func addCompletion(completion: #escaping (CNContact?) -> Void){..}
}
I'm developing an iOS download app, that uses Alamofire 4.4 and Swift 3.0.
The app gets a url and downloads the file using Alamofire.
I'm using the background session manager so that the app can download whilst in the background and send a notification when complete.
For some reason, after adding this, the .failure response is never triggered even when putting airplane mode on.
If it turn airplane mode back off, the download weirdly continues going, but leave it long enough and it doesn't continue downloading, so you'd imagine it would have triggered the failure...
I have this code for the download request:
if let resumeData = resumeData {
request = BackendAPIManager.sharedInstance.alamoFireManager.download(resumingWith: resumeData, to: destination)
}
else {
request = BackendAPIManager.sharedInstance.alamoFireManager.download(extUrl, to: destination)
}
alamoRequest = request
.responseData { response in
switch response.result {
case .success:
//get file path of downloaded file
let filePath = response.destinationURL?.path
completeDownload(filePath: filePath!)
case .failure:
//this is what never calls on network drop out
print("Failed")
}
.downloadProgress { progress in
let progressPercent = Float(progress.fractionCompleted)
//handle other progress stuff
}
}
.success case triggers fine, and progress returns fine.
If I cancel a download with alamoRequest?.cancel() then it does trigger the .failure case.
Here's my completion handler:
class BackendAPIManager: NSObject {
static let sharedInstance = BackendAPIManager()
var alamoFireManager : Alamofire.SessionManager!
var backgroundCompletionHandler: (() -> Void)? {
get {
return alamoFireManager?.backgroundCompletionHandler
}
set {
alamoFireManager?.backgroundCompletionHandler = newValue
}
}
fileprivate override init()
{
let configuration = URLSessionConfiguration.background(withIdentifier: "com.uniqudeidexample.background")
self.alamoFireManager = Alamofire.SessionManager(configuration: configuration)
}
}
And in my AppDelegate:
func application(_ application: UIApplication, handleEventsForBackgroundURLSession identifier: String, completionHandler: #escaping () -> Void) {
BackendAPIManager.sharedInstance.backgroundCompletionHandler = completionHandler
//do other notification stuff...
}
Before I added the background session stuff it worked ok, and when I activated airplane mode it failed. So am I missing something?
Hope that all makes sense, it's my first app, so not clued up on all the technicalities,
Thanks
CloudKit manages my notifications (not my dedicated server)
My first device changes something in CloudKit Container and pushes notification.
... but on my second device my app is currently running in background mode. So, the notification arrives to device with Alert, but the app itself doesn't know about it.
What is the elegant and effective way to catch this one missed notification (or even more) when the app goes back to the foreground mode?
Suppose the change is related to my top visible controller, and I would like to apply that change without fetching anything on viewDidAppear:.
Simply you can do the following, implemented inside UIApplicationDelegate method:
func applicationWillEnterForeground(application: UIApplication) {
var queryNotifications = [CKQueryNotification]()
let operation = CKFetchNotificationChangesOperation(previousServerChangeToken: nil)
operation.notificationChangedBlock = { notification in
if let queryNotification = notification as? CKQueryNotification {
queryNotifications.append(queryNotification)
}
}
operation.fetchNotificationChangesCompletionBlock = { token, error in
var notificationIdentifiers = [CKNotificationID]()
for queryNotification in queryNotifications {
let recordID = queryNotification.recordID!
//here you can do enything you need with your recordID
container.publicCloudDatabase.fetchRecordWithID(recordID, completionHandler: { object, error in
notificationIdentifiers.append(queryNotification.notificationID!)
if queryNotifications.count == notificationIdentifiers.count {
let operationQueue = NSOperationQueue()
operationQueue.addOperation(CKMarkNotificationsReadOperation(notificationIDsToMarkRead: notificationIdentifiers))
}
})
}
}
let operationQueue = NSOperationQueue()
operationQueue.addOperation(operation)
}
I am trying to receive an array of objects (that are retrieved from Parse within the app) from a parent application to be displayed in the watch application. I have been trying a few different things but with no success.
Here is my code in the extension:
override func awakeWithContext(context: AnyObject?) {
super.awakeWithContext(context)
var parkPassed = context as! String
openParentAppWithPark(parkPassed)
}
private func openParentAppWithPark(park: String) {
WKInterfaceController.openParentApplication(["request": park], reply: { (reply, error) -> Void in
println(reply)
})
}
And the code in the parent app:
func application(application: UIApplication, handleWatchKitExtensionRequest userInfo: [NSObject : AnyObject]?, reply: (([NSObject : AnyObject]!) -> Void)!) {
println("Test")
if let userInfo = userInfo, request = userInfo["request"] as? NSArray {
if request == "Park 1" {
DataManager.sharedInstance.loadRides("Park 1")
} else if request == "Park 2" {
DataManager.sharedInstance.loadRides("Park 2")
} else if request == "Park 3" {
DataManager.sharedInstance.loadRides("Park 3")
} else {
DataManager.sharedInstance.loadRides("Park 4")
}
let rides = DataManager.sharedInstance.rideArray
println("Rides: \(rides)")
reply(["rideData": rides])
return
}
reply([:])
}
The println I have always returns nil the first time I try to load, and then [:] every other time. I assume this is because the request is timing out before the app has time to load the data from Parse? Also, the println that is supposed to print "Test" is never called.
In the extension, you're passing a String (park) to the parent application via the request key, but in the parent application, you're testing whether userInfo["request"] is an NSArray or not. You should be testing for a String, as in:
if let userInfo = userInfo, request = userInfo["request"] as? String {
First add a background task assertion to the openParentCall, you can find more context on that here: Background Task Watchkit
let backgroundTask = application.beginBackgroundTaskWithExpirationHandler { NSLog("TIME UP")}
///do code
reply(callback)
//
application.endBackgroundTask(backgroundId)
Now for the actual handleWatchKitExtensionRequest call I would change the first line to
if let request = userInfo["request"] as? String {
Now for the println("Test") not printing to console if you don't attach to process with the parentApplication then the println will not log out.
If the ride data is returning empty then I would inspect this function:
DataManager.sharedInstance.loadRides(ride: String)
make sure it is actually returning the correct data you need. Attach to process and place a breakpoint on each case and check that one of the cases is being called and also jump into the loadRides function to make sure it is coming back out from it. As a side note, the information you send back in the reply block has to be a property list or the reply block will always fail.