I have to wait for multiple background process before doing other, I am looking for and trying to do something with this, but I can't. Here is the code. I commented the parts to explain what I want to reach. I could do it with completion handler but this function is called for a few times, depending on number of photos.
func prepareArray(stext: NSMutableAttributedString, completionHandler: CompletionHandler) {
self.finalForUpload = [String]()
var attributedArray = [AnyObject]()
var lastRng = 0
let range:NSRange = NSMakeRange(0,stext.length)
stext.enumerateAttributesInRange(range, options: NSAttributedStringEnumerationOptions(rawValue: 0), usingBlock: {(dic: [String:AnyObject]?, range :NSRange!, stop: UnsafeMutablePointer<ObjCBool>) -> Void in
let attachement = dic as NSDictionary!
attachement.enumerateKeysAndObjectsUsingBlock({ (key:AnyObject?, obj:AnyObject?, stop:UnsafeMutablePointer<ObjCBool>) -> Void in
let stri = key as! NSString!
if stri == "NSAttachment"{
let att:NSTextAttachment = obj as! NSTextAttachment
if att.image != nil{
print("range location: \(range.location)")
if range.location != lastRng {
attributedArray.append(stext.attributedSubstringFromRange(NSMakeRange(lastRng, range.location-lastRng)))
lastRng = range.length
}
let im:UIImage = att.image!
self.httpManager.uploadPhoto(im, completionHandler: { (imgName) -> Void in
let imName = imgName.characters.dropFirst()
let imageName = String(imName.dropLast())
//WAIT FOR THIS
dispatch_async(dispatch_get_main_queue(), {
print("append")
//stext.attributedSubstringFromRange(range)
attributedArray.append(imageName)
lastRng = range.location
})
})
}
}
})
})
//BEFORE DOING THIS
dispatch_async(dispatch_get_main_queue(), {
if lastRng == 0 && attributedArray.count < 1{
attributedArray.append(stext)
} else {
attributedArray.append(stext.attributedSubstringFromRange(NSMakeRange(lastRng, stext.length-lastRng)))
}
for var i = 0; i<attributedArray.count; i++ {
print("\n \n \(i) : \(attributedArray[i])")
}
})
}
You can do this using dispatch_group methods.
However I would recommend using something like PromiseKit or similar which gives you a much nicer way of controlling dependencies of multiple async blocks.
Here is your code using dispatch_group methods:
func prepareArray(stext: NSMutableAttributedString, completionHandler: CompletionHandler) {
// create the dispatch group
dispatch_group_t aGroup = dispatch_group_create();
self.finalForUpload = [String]()
var attributedArray = [AnyObject]()
var lastRng = 0
let range:NSRange = NSMakeRange(0,stext.length)
stext.enumerateAttributesInRange(range, options: NSAttributedStringEnumerationOptions(rawValue: 0), usingBlock: {(dic: [String:AnyObject]?, range :NSRange!, stop: UnsafeMutablePointer<ObjCBool>) -> Void in
let attachement = dic as NSDictionary!
attachement.enumerateKeysAndObjectsUsingBlock({ (key:AnyObject?, obj:AnyObject?, stop:UnsafeMutablePointer<ObjCBool>) -> Void in
let stri = key as! NSString!
if stri == "NSAttachment"{
let att:NSTextAttachment = obj as! NSTextAttachment
if att.image != nil{
print("range location: \(range.location)")
if range.location != lastRng {
attributedArray.append(stext.attributedSubstringFromRange(NSMakeRange(lastRng, range.location-lastRng)))
lastRng = range.length
}
let im:UIImage = att.image!
// use dispatch_group to coordinate.
// must call before uploadPhoto as we don't know how long it will take
dispatch_group_enter(aGroup);
self.httpManager.uploadPhoto(im, completionHandler: { (imgName) -> Void in
let imName = imgName.characters.dropFirst()
let imageName = String(imName.dropLast())
dispatch_async(aGroup, dispatch_get_main_queue(), {
print("append")
//stext.attributedSubstringFromRange(range)
attributedArray.append(imageName)
lastRng = range.location
// indicate block has completed (reduced group count)
dispatch_group_leave(aGroup);
})
})
}
}
})
})
// wait for all items in group to finish before running this block
dispatch_group_notify(aGroup, dispatch_get_main_queue(), {
if lastRng == 0 && attributedArray.count < 1{
attributedArray.append(stext)
} else {
attributedArray.append(stext.attributedSubstringFromRange(NSMakeRange(lastRng, stext.length-lastRng)))
}
for var i = 0; i<attributedArray.count; i++ {
print("\n \n \(i) : \(attributedArray[i])")
}
})
}
I have to wait for multiple background process before doing other
You should really look at using NSOperationQueue for this requirement as it allows you to configure dependencies between all of the registered operations. In this way you could configure and add a number of downloads and then configure a post download action which is dependent on all of those downloads. The operation queue will deal with starting that operation when all others are complete.
The operations also give you a nice way to cancel the pending uploads / downloads if you don't need them any more.
You could alternatively look at using dispatch_groups to manage this. It's harder to ensure that what you've done is entirely correct.
The main queue is a serial queue, meaning that it executes on block at a time in first-in-first-out order. All of the dispatches in the enumerate should be done before the one at the bottom is run.
EDIT: just noticed that you async in a completion handler.
One simple thing to do is to just count down (because you know how many there are).
Just add var expectedCompletions = attachement.count before the enumerate and then count them down (expectedCompletions--) inside the block. When it gets to 0, run the code at the end.
Related
I am currently working as a 5 month junior ios developer.
The project I'm working on is an application that shows the prices of 70 cryptocurrencies realtime with websocket connection.
we used websocket connection, UItableview, UITableViewDiffableDataSource, NSDiffableDataSourceSnapshot while developing the application.
But right now there are problems such as slowdown scrolling or not stop scroling and UI locking while scrolling in the tableview because too much data is processed at the same time.
after i check cpu performance with timer profiler I came to the conclusion that updateDataSource and updateUI functions exhausting the main thread.
func updateDataSource(model: [PairModel]) {
var snapshot = DiffableDataSourceSnapshot()
let diff = model.difference(from: snapshot.itemIdentifiers)
let currentIdentifiers = snapshot.itemIdentifiers
guard let newIdentifiers = currentIdentifiers.applying(diff) else {
return
}
snapshot.appendSections([.first])
snapshot.deleteItems(currentIdentifiers)
snapshot.appendItems(newIdentifiers)
dataSource?.apply(snapshot, animatingDifferences: false, completion: nil)
}
func updateUI(data: SocketData) {
guard let newData = data.data else { return }
guard let current = data.data?.price else { return }
guard let closed = data.data?.lastDayClosePrice else { return }
let dailyChange = ((current - closed)/closed)*100
DispatchQueue.main.async { [self] in
if model.filter({ $0.symbol == newData.pairSymbol }).first != nil {
let index = model.enumerated().first(where: { $0.element.symbol == newData.pairSymbol})
guard let location = index?.offset else { return }
model[location].price = current
model[location].dailyPercent = dailyChange
if calculateLastSignalTime(alertDate: model[location].alertDate) > 0 {
//Do Nothing
} else {
model[location].alertDate = ""
model[location].alertType = ""
}
if let text = allSymbolsView.searchTextField.text {
if text != "" {
filteredModel = model.filter({ $0.name.contains(text) || $0.symbol.contains(text) })
updateDataSource(model: filteredModel)
} else {
filteredModel = model
updateDataSource(model: filteredModel)
}
}
}
delegate?.pricesChange(data: self.model)
}
}
Regards.
ALL of your code is running on the main thread. You have to wrap your entire updateUI function inside a DispatchQueue.global(qos:), and then wrap your dataSource.apply(snapshot) line inside a DispatchQueue.main.async. the dataSource.apply(snapshot) line is the only UI work you're doing in all that code you posted.
In my project concept i need a insert 10k data when user open the application. I integrate core data for storing data but its take 1 to 5 minutes.
Here is my code ?
func inserChatMessage(_ message: String, chatId: String, onCompletion completionHandler:((_ message: ChatMessage) -> Void)?) {
var objMessage: ChatMessage? = nil
if let obj = ChatMessage.createEntity() {
objMessage = obj
}
objMessage?.messageId = ""
objMessage?.message = message
objMessage?.chatId = chatId
objMessage?.senderId = AIUser.current.userId
objMessage?.createAt = Date()
objMessage?.updateAt = Date()
let cManager = CoreDataManager.sharedManager
cManager.saveContext()
if let completionHandler = completionHandler, let objMessage = objMessage {
completionHandler(objMessage)
}
}
Coredata is not a threadsafe. And as per your requirement you need to save large amount of data on app launch. So If you will save those data using main thread, your app will get hanged. So Instead on saving large amount of data on main thread you can save those data on background thread. Coredata is supporting multi threading concept by providing parent child context concept.
I have done same in one of my project and its working fine. Here i have attached code.
func savePersonalMessagesOnBackGroundThread(arrMessages:NSArray,responseData:#escaping () -> Void)
{
print(arrMessages)
let temporaryChatContext = NSManagedObjectContext(concurrencyType: NSManagedObjectContextConcurrencyType.privateQueueConcurrencyType)
temporaryChatContext.parent = self.managedObjectContext
temporaryChatContext.perform({() -> Void in
for i in 0..<arrMessages.count
{
let msgDic = arrMessages[i] as! NSDictionary
_ = self.saveMessageInLocalDB(dictMessage: msgDic, managedObjectContext: temporaryChatContext, onBackground: true)
if i == arrMessages.count - 1 {
do {
try temporaryChatContext.save()
runOnMainThreadWithoutDeadlock {
DLog(message: "Thred \(Thread.isMainThread)")
if(self.managedObjectContext.hasChanges)
{
self.saveContext()
responseData()
}
}
}
catch {
print(error)
}
}
}
})
}
func saveMessageInLocalDB(dictMessage:NSDictionary, managedObjectContext:NSManagedObjectContext,onBackground:Bool) -> Chat
{
var chatObj : Chat! = Chat()
var receiveId: Int32!
var flag:Bool = false
print(dictMessage)
// let predicate = NSPredicate(format:"uniqueId == %# and senderId = %d and receiverId = %d","\(dictMessage.value(forKey:keyuniqueId)!)",Int32(dictMessage.value(forKey:keysenderId) as! Int64),Int32(dictMessage.value(forKey:keyreceiverId) as! Int64))
let predicate = NSPredicate(format:"uniqueId == %#","\(dictMessage.value(forKey:keyuniqueId)!)")
let objContext = managedObjectContext
let fetchRequest = NSFetchRequest<Chat>(entityName: ENTITY_CHAT)
let disentity: NSEntityDescription = NSEntityDescription.entity(forEntityName: ENTITY_CHAT, in: objContext)!
fetchRequest.predicate = predicate
fetchRequest.entity = disentity
do{
let results = try managedObjectContext.fetch(fetchRequest as! NSFetchRequest<NSFetchRequestResult>) as! [Chat]
if(results.count > 0)
{
chatObj = results[0]
chatObj.messageId = Int32(dictMessage.value(forKey:keymessageId) as! Int64)
chatObj.dateOnly = dictMessage.value(forKey:keydateOnly) as! String?
}
else{
//receiveId = Int32(dictMessage.value(forKey:keyreceiverId) as! Int64)
//self.createNewChatObject(dictMessage: dictMessage, receiverId: receiveId, managedObjectContext: managedObjectContext)
chatObj = NSEntityDescription.insertNewObject(forEntityName:ENTITY_CHAT,into: managedObjectContext) as? Chat
if dictMessage[keymessageId] != nil {
chatObj.messageId = dictMessage.value(forKey:keymessageId) as! Int32
}
if(chatObj.message?.length != 0)
{
chatObj.message = dictMessage.value(forKey:keychatMessage) as? String
}
chatObj.messageType = Int32(dictMessage.value(forKey:keymessageType) as! Int64)
chatObj.senderId = Int32(dictMessage.value(forKey:keysenderId) as! Int64)
if(chatObj.senderId != Int32((APP_DELEGATE.loggedInUser?.id!)!))
{
let contactObj = self.getContactByContactId(contactId: Int32(dictMessage.value(forKey:keysenderId) as! Int64))
if(contactObj == nil)
{
_ = self.saveUnknownUserASContact(msgDict: dictMessage as! Dictionary<String, Any>)
}
}
chatObj.receiverId = Int32(dictMessage.value(forKey:keyreceiverId) as! Int64)
chatObj.uniqueId = dictMessage.value(forKey:keyuniqueId) as? String
chatObj.mediaName = dictMessage.value(forKey:keymediaName) as? String
print(NSDate())
if dictMessage[keycreatedDate] != nil {
let utcDate : NSDate = DateFormater.getUTCDateFromUTCString(givenDate: dictMessage.value(forKey:keycreatedDate) as! String)
chatObj.createdDate = utcDate
chatObj.updatedDate = utcDate
}
else
{
chatObj.createdDate = NSDate()
chatObj.updatedDate = NSDate()
}
if(chatObj.senderId == Int32((APP_DELEGATE.loggedInUser?.id)!))
{
chatObj.chatUser = chatObj.receiverId
}
else
{
chatObj.chatUser = chatObj.senderId
}
if dictMessage[keystatus] != nil {
chatObj.status = Bool((dictMessage.value(forKey:keystatus) as! Int64) as NSNumber)
}
switch Int(chatObj.messageType)
{
case MSG_TYPE.MSG_Text.rawValue:
chatObj.cellType = (chatObj.senderId != Int32((APP_DELEGATE.loggedInUser?.id!)!) ? Int32(CELL_TYPE.CELL_TEXT_RECEIVER.rawValue) : Int32(CELL_TYPE.CELL_TEXT_SENDER.rawValue))
case MSG_TYPE.MSG_Image.rawValue:
chatObj.cellType = (chatObj.senderId != Int32((APP_DELEGATE.loggedInUser?.id!)!) ? Int32(CELL_TYPE.CELL_IMAGE_RECEIVER.rawValue) : Int32(CELL_TYPE.CELL_IMAGE_SENDER.rawValue))
self.saveMedia(chatObj: chatObj)
default :
// chatObj.cellType = Int32(CELL_TYPE.CELL_LOAD_MORE.rawValue)
break
}
}
// deviceMake = 1;
if(!onBackground)
{
self.saveContext()
}
}
catch
{
}
return chatObj
}
Using the basic example below I can insert 10k records very quickly. The main thing that has changed here compared to your code is that I loop through and create the entities and then call save() at the very end. So you are performing one write call to the db instead of 10k. You are writing more information in that one call but it is still much quicker.
import UIKit
import CoreData
class ViewController: UIViewController {
lazy var sharedContext: NSManagedObjectContext? = {
return (UIApplication.shared.delegate as? AppDelegate)?.persistentContainer.viewContext
}()
override func viewDidLoad() {
super.viewDidLoad()
if let messages = getMessages(), messages.count > 0 {
printMessages(messages: messages)
} else {
loadChatMessages()
printMessages(messages: getMessages())
}
}
private func printMessages(messages: [Message]?) {
guard let messages = messages else { return }
for message in messages {
print(message.message)
}
}
private func getMessages() -> [Message]? {
let request = NSFetchRequest<Message>(entityName: "Message")
let messages = try? self.sharedContext?.fetch(request)
return messages ?? nil
}
private func loadChatMessages() {
var counter = 1
while counter <= 10000 {
let message = Message(entity: Message.entity(), insertInto: self.sharedContext)
message.message = "This is message number \(counter)"
message.read = false
message.timestamp = Date()
counter = counter + 1
}
do {
try self.sharedContext?.save()
} catch {
print(error)
}
}
}
As mentioned in my comment above, you can improve this further by doing it in the background (see Twinkle's answer for an example of how to switch to a background thread), you can also provide a pre-filled (pre-seeded) core data database that already contains the 10k records with your app. so it doesn't need to load this on initial load.
To do this you would fill the db locally on your dev machine and then copy it to the project bundle. On initial load you can check to see if your db filename exists in the documents folder or not. If it doesn't copy it over from the bundle and then use that DB for core data.
im trying to work with firebase, i made this function that is saving "Offers" to an Array and then sending this array to another viewcontroller to show them on a tableView.
i found a problem that the for loop is ending before the data is coming from the servers and the callback of firebase is coming, where u can see the line "fir (self.goodOffer...)" is the end of the firebase callback.
maybe someone know how can i make the for loop to wait for the firebase callback to end?
thx.
geocodeAddress(totalFromLocation, callback: { (location) in
if location != nil {
fromLocation = location
self.geocodeAddress(totalToLocation, callback: { (location) in
if location != nil {
toLocation = location
mainDistance = Int(self.distanceCalculate(fromLocation!, locationTwo: toLocation!))
print(mainDistance)
REF_BUSSINES_OFFERS.queryOrderedByKey().queryEqualToValue("north").observeSingleEventOfType(.Value, withBlock: { (snapshot) in
print("in snap")
if let value = snapshot.value {
if self.isAlavator {
if let area = value["north"] {
if let data = area!["withAlavator"]! {
if let bids = data["bids"] as? Int {
for i in 1..<bids+1 {
if let bid = data["\(i)"] {
let bidFromSize = bid!["fromSize"] as! Int
let bidToSize = bid!["toSize"] as! Int
let bidFromDistance = bid!["fromDistance"] as! Int
let bidToDistance = bid!["toDistance"] as! Int
if mainDistance >= bidFromDistance && mainDistance <= bidToDistance {
if Int(totalSize) >= bidFromSize && Int(totalSize) <= bidToSize {
let price = String(bid!["price"])
let name = bid!["name"] as! String
let phoneNumber = bid!["phoneNumber"] as! String
var logoImageData: NSData!
LOGOS_REF.child("\(phoneNumber)").dataWithMaxSize(3 * 1024 * 1024, completion: { (data, error) in
if error != nil {
logoImageData = UIImagePNGRepresentation(UIImage(named: "company")!)
} else {
logoImageData = data
}
let offer = Offer(logoImage: logoImageData, name: name, price: price)
self.goodOffers.append(offer)
print("fir \(self.goodOffers[0].price)")
})
}
}
}
print(i)
}//end of for
print("out")
//self.changgeViewToTable()
}
Can't help you much because your code is such a pain to read. Use ObjectMapper to reduce the level (and the effort) to map these fields. Anyhow, you can use dispatch_group to synchronize the various async calls:
let group = dispatch_group_create()
for i in 1..<bids+1 {
// ...
dispatch_group_enter(group)
LOGOS_REF.child("\(phoneNumber)").dataWithMaxSize(3 * 1024 * 1024) {
// ...
dispatch_group_leave(group)
}
}
// Wait for all async calls to complete before proceeding
dispatch_group_wait(group, DISPATCH_TIME_FOREVER)
Each dispatch_group_enter() should be balanced with a dispatch_group_leave() when the async task is complete.
I've written a method using dispatch_group :
class func loadNewEvents(inContext context:NSManagedObjectContext, completion:(() -> Void)?=nil) {
DDLogDebug("Loading New Events")
let context = CoreDataManager.backgroundContext()
let events = CoreDataManager.fetch(Event.self, inContext: context)
var earliestEvent = events.sort({$0.initialTimestamp.integerValue > $1.initialTimestamp.integerValue}).first
let group = dispatch_group_create()
var loadedEvents:[Event]?
var failure = false
while (loadedEvents == nil || loadedEvents!.count != 0) && !failure {
dispatch_group_enter(group);
if let earliestTimeStamp = earliestEvent?.initialTimestamp.longLongValue {
let url = afterUrl(earliestTimeStamp)
getEvents(url: url,
success: { events in
loadedEvents = events
earliestEvent = events.last
dispatch_group_leave(group)
},
failure: {
failure = true
dispatch_group_leave(group)
}
)
} else {
break
}
dispatch_group_wait(group, DISPATCH_TIME_FOREVER)
}
DDLogDebug("Loaded new events")
}
It works great in the iOS9 simulator and on a iPhone5S w/ iOS9.
But on an iPhone 4S with iOS8 there is a crash at the very end of the method :
EXC_BREAKPOINT (code=EXC_ARM_BREAKPOINT, subcode=0xdefe) in _dispatch_semaphore_dispose
Any idea what would cause this and how could I fix this ?
I finally found out what was the problem : I did not leave the group on the break.
To fix it I called dispatch_group_enter(group) within the if let since it is not needed when I'm not doing an async call.
I was excited to learn that Apple added tracking of deletes in HealthKit in iOS 9. So I set up a test project to try it out. Unfortunately, while I can get new data just fine, I am not getting any deleted objects in my callbacks.
I have a functioning HKAnchoredObjectQuery that tracks HKQueryAnchor and gives me new HKSamples whenever I add a BloodGlucose quantity into HealthKit via the Health app. However when I delete that same quantity and re-run the app the HKDeletedObject is always empty. Even I do an add and a delete at the same time. It seems no matter what I do, the HKDeletedObject array is always empty. But additions work fine (only getting the added samples since the last anchor).
Here is my code. It is just 2 files. To recreate the project just make a new swift project, give yourself the HealthKit Entitlement, and copy these in. (Note: When you run it, you only get one update for each run so if you make changes in HealthKit you have to stop and restart the app to test the callbacks.)
This is my HealthKit client:
//
// HKClient.swift
// HKTest
import UIKit
import HealthKit
class HKClient : NSObject {
var isSharingEnabled: Bool = false
let healthKitStore:HKHealthStore? = HKHealthStore()
let glucoseType : HKObjectType = HKObjectType.quantityTypeForIdentifier(HKQuantityTypeIdentifierBloodGlucose)!
override init(){
super.init()
}
func requestGlucosePermissions(authorizationCompleted: (success: Bool, error: NSError?)->Void) {
let dataTypesToRead : Set<HKObjectType> = [ glucoseType ]
if(!HKHealthStore.isHealthDataAvailable())
{
// let error = NSError(domain: "com.test.healthkit", code: 2, userInfo: [NSLocalizedDescriptionKey: "Healthkit is not available on this device"])
self.isSharingEnabled = false
return
}
self.healthKitStore?.requestAuthorizationToShareTypes(nil, readTypes: dataTypesToRead){(success, error) -> Void in
self.isSharingEnabled = true
authorizationCompleted(success: success, error: error)
}
}
func getGlucoseSinceAnchor(anchor:HKQueryAnchor?, maxResults:uint, callback: ((source: HKClient, added: [String]?, deleted: [String]?, newAnchor: HKQueryAnchor?, error: NSError?)->Void)!){
let queryEndDate = NSDate(timeIntervalSinceNow: NSTimeInterval(60.0 * 60.0 * 24))
let queryStartDate = NSDate.distantPast()
let sampleType: HKSampleType = glucoseType as! HKSampleType
let predicate: NSPredicate = HKAnchoredObjectQuery.predicateForSamplesWithStartDate(queryStartDate, endDate: queryEndDate, options: HKQueryOptions.None)
var hkAnchor: HKQueryAnchor;
if(anchor != nil){
hkAnchor = anchor!
} else {
hkAnchor = HKQueryAnchor(fromValue: Int(HKAnchoredObjectQueryNoAnchor))
}
let onAnchorQueryResults : ((HKAnchoredObjectQuery, [HKSample]?, [HKDeletedObject]?, HKQueryAnchor?, NSError?) -> Void)! = {
(query:HKAnchoredObjectQuery, addedObjects:[HKSample]?, deletedObjects:[HKDeletedObject]?, newAnchor:HKQueryAnchor?, nsError:NSError?) -> Void in
var added = [String]()
var deleted = [String]()
if (addedObjects?.count > 0){
for obj in addedObjects! {
let quant = obj as? HKQuantitySample
if(quant?.UUID.UUIDString != nil){
let val = Double( (quant?.quantity.doubleValueForUnit(HKUnit(fromString: "mg/dL")))! )
let msg : String = (quant?.UUID.UUIDString)! + " " + String(val)
added.append(msg)
}
}
}
if (deletedObjects?.count > 0){
for del in deletedObjects! {
let value : String = del.UUID.UUIDString
deleted.append(value)
}
}
if(callback != nil){
callback(source:self, added: added, deleted: deleted, newAnchor: newAnchor, error: nsError)
}
}
let anchoredQuery = HKAnchoredObjectQuery(type: sampleType, predicate: predicate, anchor: hkAnchor, limit: Int(maxResults), resultsHandler: onAnchorQueryResults)
healthKitStore?.executeQuery(anchoredQuery)
}
let AnchorKey = "HKClientAnchorKey"
func getAnchor() -> HKQueryAnchor? {
let encoded = NSUserDefaults.standardUserDefaults().dataForKey(AnchorKey)
if(encoded == nil){
return nil
}
let anchor = NSKeyedUnarchiver.unarchiveObjectWithData(encoded!) as? HKQueryAnchor
return anchor
}
func saveAnchor(anchor : HKQueryAnchor) {
let encoded = NSKeyedArchiver.archivedDataWithRootObject(anchor)
NSUserDefaults.standardUserDefaults().setValue(encoded, forKey: AnchorKey)
NSUserDefaults.standardUserDefaults().synchronize()
}
}
This is my View:
//
// ViewController.swift
// HKTest
import UIKit
import HealthKit
class ViewController: UIViewController {
let debugLabel = UILabel(frame: CGRect(x: 10,y: 20,width: 350,height: 600))
override func viewDidLoad() {
super.viewDidLoad()
self.view = UIView();
self.view.backgroundColor = UIColor.whiteColor()
debugLabel.textAlignment = NSTextAlignment.Center
debugLabel.textColor = UIColor.blackColor()
debugLabel.lineBreakMode = NSLineBreakMode.ByWordWrapping
debugLabel.numberOfLines = 0
self.view.addSubview(debugLabel)
let hk = HKClient()
hk.requestGlucosePermissions(){
(success, error) -> Void in
if(success){
let anchor = hk.getAnchor()
hk.getGlucoseSinceAnchor(anchor, maxResults: 0)
{ (source, added, deleted, newAnchor, error) -> Void in
var msg : String = String()
if(deleted?.count > 0){
msg += "Deleted: \n" + (deleted?[0])!
for s in deleted!{
msg += s + "\n"
}
}
if (added?.count > 0) {
msg += "Added: "
for s in added!{
msg += s + "\n"
}
}
if(error != nil) {
msg = "Error = " + (error?.description)!
}
if(msg.isEmpty)
{
msg = "No changes"
}
debugPrint(msg)
if(newAnchor != nil && newAnchor != anchor){
hk.saveAnchor(newAnchor!)
}
dispatch_async(dispatch_get_main_queue(), { () -> Void in
self.debugLabel.text = msg
})
}
}
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
Note: I know Apple recommends to set this up using an HKObserverQuery. I originally did it that way in a Xamarin project and the behavior was the same (no HKDeletedObjects were getting sent). So when trying it out with swift I left out the HKObserverQuery for simplicity.
Remove the predicate (predicate: nil) when you instantiate the query and you'll see more results, including deleted results. The first time you run this (before the HKQueryAnchor has been saved) you'll get all the results so you may want to do something to filter those; but subsequent execution of the query will use your saved anchor so you'll only see changes since that saved anchor.
You probably also want to set the updateHandler property on your query before executing. This will set the query up to run continuously in the background calling the update handler whenever there are changes (Adds or Deletes). The code near the end of getGlucoseSinceAnchor(...) looks like
...
let anchoredQuery = HKAnchoredObjectQuery(type: sampleType, predicate: nil, anchor: hkAnchor, limit: Int(maxResults), resultsHandler: onAnchorQueryResults)
anchoredQuery.updateHandler = onAnchorQueryResults
healthKitStore?.executeQuery(anchoredQuery)
...