Arhn - архитектура программирования

Push-уведомление от CloudKit не синхронизируется должным образом

Я пытаюсь создать простой чат в IOS/Swift с помощью iCloudKit. Я моделирую на основе этого примера: Создайте приложение, такое как Twitter: Push-уведомления с помощью CloudKit но изменив его на чат вместо Sweets.

Баннер и значок кода в некоторой степени работают хорошо, а отправка данных в CloudDashboard выполняется быстро и без проблем.

Но синхронизация из cloudKit с устройствами в большинстве случаев не работает. Иногда одно устройство видит больше другого, иногда меньше, просто не слишком надежно. Я использую среду РАЗРАБОТКИ в CloudKit.

В чем проблема? Вот мой код реализованных методов в appDelegate и viewController:

func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
    // Override point for customization after application launch.
    let notificationSettings = UIUserNotificationSettings(forTypes: [.Alert, .Badge, .Sound], categories: nil)
    UIApplication.sharedApplication().registerUserNotificationSettings(notificationSettings)
    UIApplication.sharedApplication().registerForRemoteNotifications()
    return true
}

func application(application: UIApplication, didReceiveRemoteNotification userInfo: [NSObject : AnyObject]) {
    let cloudKitNotification = CKNotification(fromRemoteNotificationDictionary: userInfo as! [String:NSObject])

    if cloudKitNotification.notificationType == CKNotificationType.Query {
        dispatch_async(dispatch_get_main_queue(), { () -> Void in
            NSNotificationCenter.defaultCenter().postNotificationName("performReload", object: nil)
        })
    }
}

func resetBadge () {
    let badgeReset = CKModifyBadgeOperation(badgeValue: 0)
    badgeReset.modifyBadgeCompletionBlock = { (error) -> Void in
        if error == nil {
            UIApplication.sharedApplication().applicationIconBadgeNumber = 0
        }
    }
    CKContainer.defaultContainer().addOperation(badgeReset)
}
func applicationWillResignActive(application: UIApplication) {

}

func applicationDidEnterBackground(application: UIApplication) {
    resetBadge()
}

func applicationWillEnterForeground(application: UIApplication) {
    dispatch_async(dispatch_get_main_queue(), { () -> Void in
        NSNotificationCenter.defaultCenter().postNotificationName("performReload", object: nil)
    })

}

func applicationDidBecomeActive(application: UIApplication) {
    resetBadge()
}

и это viewController

import UIKit
import CloudKit

class ChatViewController: UIViewController, UITableViewDelegate, UITableViewDataSource, UITextFieldDelegate {

    @IBOutlet weak var dockViewHeightConstraint: NSLayoutConstraint!
    @IBOutlet weak var messageTextField: UITextField!
    @IBOutlet weak var sendButton: UIButton!
    @IBOutlet weak var messageTableView: UITableView!

    var chatMessagesArray = [CKRecord]()
    var messagesArray: [String] = [String]()

    override func viewDidLoad() {
        super.viewDidLoad()

        // Do any additional setup after loading the view.
        self.messageTableView.delegate = self
        self.messageTableView.dataSource = self
        // set self as the delegate for the textfield
        self.messageTextField.delegate = self

        // add a tap gesture recognizer to the tableview
        let tapGesture:UITapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(ChatViewController.tableViewTapped))
        self.messageTableView.addGestureRecognizer(tapGesture)

        setupCloudKitSubscription()

        dispatch_async(dispatch_get_main_queue(), { () -> Void in
            NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(ChatViewController.retrieveMessages), name: "performReload", object: nil)
        })

        // retrieve messages form iCloud
        self.retrieveMessages()
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }

    @IBAction func sendButtonTapped(sender: UIButton) {

        // Call the end editing method for the text field
        self.messageTextField.endEditing(true)

        // Disable the send button and textfield
        self.messageTextField.enabled = false
        self.sendButton.enabled = false

        // create a cloud object
        //var newMessageObject
        // set the text key to the text of the messageTextField

        // save the object
        if messageTextField.text != "" {
            let newChat = CKRecord(recordType: "Chat")
            newChat["content"] = messageTextField.text
            newChat["user1"] = "john"
            newChat["user2"] = "mark"

            let publicData = CKContainer.defaultContainer().publicCloudDatabase
            //TODO investigate if we want to do public or private

            publicData.saveRecord(newChat, completionHandler: { (record:CKRecord?, error:NSError?) in
                if error == nil {
                    dispatch_async(dispatch_get_main_queue(), {() -> Void in
                        print("chat saved")
                        self.retrieveMessages()
                    })
                }
            })
        }

        dispatch_async(dispatch_get_main_queue()) {
            // Enable the send button and textfield
            self.messageTextField.enabled = true
            self.sendButton.enabled = true
            self.messageTextField.text = ""
        }
    }

    func retrieveMessages() {
        print("inside retrieve messages")
        // create a new cloud query
        let publicData = CKContainer.defaultContainer().publicCloudDatabase

        // TODO: we should use this
        let predicate = NSPredicate(format: "user1 in %@ AND user2 in %@", ["john", "mark"], ["john", "mark"])
        let query = CKQuery(recordType: "Chat", predicate: predicate)

        //let query = CKQuery(recordType: "Chat", predicate: NSPredicate(format: "TRUEPREDICATE", argumentArray: nil))

        query.sortDescriptors = [NSSortDescriptor(key:"creationDate", ascending: true)]
        publicData.performQuery(query, inZoneWithID: nil) { (results: [CKRecord]?, error:NSError?) in
            if let chats = results {
                dispatch_async(dispatch_get_main_queue(), {() -> Void in
                    self.chatMessagesArray = chats
                    print("count is: \(self.chatMessagesArray.count)")
                    self.messageTableView.reloadData()
                })
            }
        }
    }

    func tableViewTapped () {
        // Force the textfied to end editing
        self.messageTextField.endEditing(true)
    }

    // MARK: TextField Delegate Methods
    func textFieldDidBeginEditing(textField: UITextField) {
        // perform an animation to grow the dockview
        self.view.layoutIfNeeded()
        UIView.animateWithDuration(0.5, animations: {
            self.dockViewHeightConstraint.constant = 350
            self.view.layoutIfNeeded()
            }, completion: nil)
    }

    func textFieldDidEndEditing(textField: UITextField) {

        // perform an animation to grow the dockview
        self.view.layoutIfNeeded()
        UIView.animateWithDuration(0.5, animations: {
            self.dockViewHeightConstraint.constant = 60
            self.view.layoutIfNeeded()
            }, completion: nil)
    }

    // MARK: TableView Delegate Methods

    func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {

        // Create a table cell
        let cell = self.messageTableView.dequeueReusableCellWithIdentifier("MessageCell")! as UITableViewCell

        // customize the cell
        let chat = self.chatMessagesArray[indexPath.row]
        if let chatContent = chat["content"] as? String {
            let dateFormat = NSDateFormatter()
            dateFormat.dateFormat = "MM/dd/yyyy"
            let dateString = dateFormat.stringFromDate(chat.creationDate!)
            cell.textLabel?.text = chatContent
            //cell.detailTextLabel?.text = dateString
        }
        //cell.textLabel?.text = self.messagesArray[indexPath.row]

        // return the cell
        return cell
    }

    func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        //print(tableView.frame.size)
        //print("count: \(self.chatMessagesArray.count)")
        return self.chatMessagesArray.count
    }
    /*
    // MARK: - Navigation

    // In a storyboard-based application, you will often want to do a little preparation before navigation
    override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
        // Get the new view controller using segue.destinationViewController.
        // Pass the selected object to the new view controller.
    }
    */

    // MARK: Push Notifications

    func setupCloudKitSubscription() {
        let userDefaults = NSUserDefaults.standardUserDefaults()
        print("the value of the bool is: ")
        print(userDefaults.boolForKey("subscribed"))
        print("print is above")
        if userDefaults.boolForKey("subscribed") == false { // TODO: maybe here we do multiple types of subscriptions

            let predicate = NSPredicate(format: "user1 in %@ AND user2 in %@", ["john", "mark"], ["john", "mark"])
            //let predicate = NSPredicate(format: "TRUEPREDICATE", argumentArray: nil)
            let subscription = CKSubscription(recordType: "Chat", predicate: predicate, options: CKSubscriptionOptions.FiresOnRecordCreation)
            let notificationInfo = CKNotificationInfo()
            notificationInfo.alertLocalizationKey = "New Chat"
            notificationInfo.shouldBadge = true

            subscription.notificationInfo = notificationInfo

            let publicData = CKContainer.defaultContainer().publicCloudDatabase
            publicData.saveSubscription(subscription) { (subscription: CKSubscription?, error: NSError?) in
                if error != nil {
                    print(error?.localizedDescription)
                } else {
                    userDefaults.setBool(true, forKey: "subscribed")
                    userDefaults.synchronize()
                }
            }
        }

    }
}

Ответы:


1

Я вижу, что вы используете push-уведомление как сигнал для перезагрузки всех данных. CloudKit использует механизм кэширования (подробности этого неизвестны) для определенного предиката. В вашем случае вы выполняете один и тот же предикат снова и снова. Из-за этого обналичивания вы могли пропустить рекорды. Попробуйте выполнить ручное обновление через минуту или около того, и вы увидите, что внезапно появятся ваши записи.

Вы должны обрабатывать push-уведомления по-другому. Когда вы получаете уведомление, вы также должны запрашивать сообщения уведомления (вы можете получить 1 push-уведомление, когда есть несколько уведомлений. Это может произойти, когда у вас много уведомлений)

Но сначала вы должны обработать текущее уведомление. Начните с проверки, предназначено ли уведомление для запроса, используя:

if cloudKitNotification.notificationType == CKNotificationType.Query {

Затем приведите его к уведомлению о запросе, используя:

if let queryNotification = cloudNotification as? CKQueryNotification

Получить идентификатор записи

if let recordID = queryNotification.recordID {

Затем, в зависимости от того, что произошло, измените свои локальные (в приложении) данные. Вы можете проверить это, используя:

if queryNotification.queryNotificationReason == .RecordCreated

Конечно тоже может быть. RecordDeleted или .RecordUpdated

Если это .RecordCreated или .RecordUpdated, вы должны получить эту запись, используя recordID

Затем, когда это будет обработано, вы должны получить другие необработанные уведомления. Для этого вам нужно создать CKFetchNotificationChangesOperation Вы должны знать, что вы должны передать ему токен изменения. Если вы отправите nil, вы получите все уведомления, которые когда-либо были созданы для ваших подписок. Когда операции завершатся, он отправит вам новый токен изменения. Вы должны сохранить это в своих userDefaults, чтобы вы могли использовать это в следующий раз, когда начнете обрабатывать уведомления.

Код для этого запроса будет выглядеть примерно так:

let operation = CKFetchNotificationChangesOperation(previousServerChangeToken: self.previousChangeToken)
operation.notificationChangedBlock = { notification in
...
operation.fetchNotificationChangesCompletionBlock = { changetoken, error in
...
operation.start()

Затем для этого уведомления вы должны выполнить ту же логику, что и выше для начального уведомления. И changetoken должен быть сохранен.

Еще одним преимуществом этого механизма является то, что ваши записи поступают одна за другой, и вы можете создать красивую анимацию, которая обновляет ваше табличное представление.

15.05.2016
Новые материалы

Коллекции публикаций по глубокому обучению
Последние пару месяцев я создавал коллекции последних академических публикаций по различным подполям глубокого обучения в моем блоге https://amundtveit.com - эта публикация дает обзор 25..

Представляем: Pepita
Фреймворк JavaScript с открытым исходным кодом Я знаю, что недостатка в фреймворках JavaScript нет. Но я просто не мог остановиться. Я хотел написать что-то сам, со своими собственными..

Советы по коду Laravel #2
1-) Найти // You can specify the columns you need // in when you use the find method on a model User::find(‘id’, [‘email’,’name’]); // You can increment or decrement // a field in..

Работа с временными рядами спутниковых изображений, часть 3 (аналитика данных)
Анализ временных рядов спутниковых изображений для данных наблюдений за большой Землей (arXiv) Автор: Рольф Симоэс , Жильберто Камара , Жильберто Кейрос , Фелипе Соуза , Педро Р. Андраде ,..

3 способа решить квадратное уравнение (3-й мой любимый) -
1. Методом факторизации — 2. Используя квадратичную формулу — 3. Заполнив квадрат — Давайте поймем это, решив это простое уравнение: Мы пытаемся сделать LHS,..

Создание VR-миров с A-Frame
Виртуальная реальность (и дополненная реальность) стали главными модными терминами в образовательных технологиях. С недорогими VR-гарнитурами, такими как Google Cardboard , и использованием..

Демистификация рекурсии
КОДЕКС Демистификация рекурсии Упрощенная концепция ошеломляющей О чем весь этот шум? Рекурсия, кажется, единственная тема, от которой у каждого начинающего студента-информатика..