Swift iOS - Как установить таймер на Firebase TransactionBlock, чтобы предотвратить его увеличение в течение определенного периода времени - PullRequest
0 голосов
/ 05 мая 2018

Каждый раз, когда разные пользователи публикуют что-то (скажем, цвет), я получаю цвет, который они опубликовали, postID, их userId, дату в секундах и сколько раз это сообщение было просмотрено.

Другой пользователь может просматривать таблицу и просматривать разные ячейки с каждым цветом, опубликованным каждым пользователем.

Каждый раз, когда пользователь, который ищет, нажимает didSelectRow, чтобы просмотреть подробный вид цвета, я запускаю Firebase TransactionBlock, который увеличивает свойство подсчета views, чтобы показать, сколько раз этот конкретный цвет / ячейка был нажат.

Например, если пользователь просматривает tableView и видит blueCell, на нем будет метка, которая говорит просмотров: 10 (что означает, что его просматривали 10 раз). Если этот пользователь снова нажмет эту blueCell, то число просмотров будет показано просмотров: 11 .

Проблема в том, что если этот пользователь нажимает эту ячейку несколько раз, он может увеличить count на этой метке views в считанные секунды.

Как я могу отслеживать каждый объект / ячейку, к которому пользователь прикоснулся, и поставить на него таймер, чтобы он не мог обновить views count для этого конкретного объекта, возможно, еще час или около того? У меня есть дата в секундах и postId, которые являются уникальными для каждого объекта.

Обычно, если пользователь нажимает на blueCell в 12 часов вечера, количество просмотров объекта, связанного с этой конкретной ячейкой, возрастет до 11, но если они нажмут его снова в любое время в промежутке между 12 и 13 часами вечера, он не увеличится. После 13:00, если они нажимают это снова, количество просмотров для этого объекта увеличится до 12?

Объект модели и свойства, которые я могу использовать для идентификации каждого цветового объекта:

class ColorClass{
    var color: String?
    var postID: String?
    var userId: String?
    var date: NSNumber?
    var views: NSNumber? // keeps track of how many the post was viewed
}

TableView's didSelectRow:

// the current user who is pressing the cell
let currentUserID = Auth.auth().currentUser?.uid
var colors = [ColorClass]() // 500 model objects

func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    return colors.count
}

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    let cell = tableView.dequeueReusableCell(withIdentifier: "ColorsCell", for: indexPath) as! ColorsCell

    cell.viewsLabel.text = colors[indexPath.row].views // I separately convert this from a NSNumber to a String
    cell.colorLabel.text = colors[indexPath.row].color

    return cell
}

// pressing the cell will increase the count on the object's views property
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {

    guard let indexPath = tableView.indexPathForSelectedRow else { return }

    // the userId on the object of the cell that was pressed
    guard let userID = colors[indexPath.row].userId else { return }
    guard let postID = colors[indexPath.row].postId else { return }

    // make sure the current user can't update the views on their own post
    if currentUserID != userID{

        let viewsRef = databaseRef?.child(userID).child(postID).child("views")

        viewsRef?.runTransactionBlock({
            (currentData: MutableData) -> TransactionResult in

            let newValue: Int

            guard let existingValue = (currentData.value as? NSNumber)?.intValue else {
                return TransactionResult.abort()
            }

            newValue = existingValue + 1

            currentData.value = NSNumber(value: newValue)

            return TransactionResult.success(withValue: currentData)

        }, andCompletionBlock: {
            (error, completion, snap) in

            print(snap as Any)

            if !completion{
                print("The value wasn't able to update")
                print(error?.localizedDescription as Any)
            }else{
                print("The value updated")
            }
        })
    }
}

Просто идея.

Я думал о создании другого объекта, который имел бы свойства currentUserID, postID и tappedTime. Тогда я бы создал синглтон. Каждый раз, когда нажимается ячейка, я передаю данные в объект, а затем отправляю объект в массив в синглтоне. Там у меня будет свойство currentTime. Сначала я проверю, есть ли postID в массиве, и если да, то сравнил бы tappedTime с currentTime + 1 час, чтобы решить, следует ли увеличить количество просмотров. У меня был бы таймер асинхронной отправки, и через 1 час он автоматически удалялся из массива. Я не уверен, насколько это практично.

Ответы [ 2 ]

0 голосов
/ 05 мая 2018

Идея, которая возникла у меня в конце вопроса, сработала.

Я в основном сделал ViewsTrackingObject со свойством специально для postId

Затем я создал синглтон, который добавляет viewsTrackingObject в массив, проверяет, находится ли он в массиве, если не добавляет его в массив, а затем удаляет его из массива через xxx секунд.

Для этого примера я установил его на 15 секунд на шаге 9: .now() + 15, но если бы я хотел его в течение часа, я бы изменил его на .now() + 3600.

Мне легче объяснять вещи пошагово. Есть 0 - 21 шаг. Я перечислил шаги как закомментированный код над каждым соответствующим фрагментом кода, начиная с верхней части класса Tracker с шагом 0 и заканчивая нижней частью didSelectRow с шагом 21

ViewsTrackingObject:

class ViewsTrackingObject{
    var postId: String?
}

Класс Singleton:

class Tracker{

    static let sharedInstance = Tracker()

    var viewsObjects = [ViewsTrackingObject]()
    var updateCount = false // 0. need to access this inside didSelectRow (step 17 )to find out wether or not to update the number of views. This would set to true in step 3 below

    func checkForObjectInArray(object: ViewsTrackingObject){

        // 1. check to see if the object is in the array. If it is return true if not return false. Use dot notation to compare the postId on the viewsTrackingObject vs what's inside the array to find out if it exists
        let boolVal = viewsObjects.contains(where: {$0.postId == object.postId})

        // 2. if the object is NOT inside the array then append to the array and then add it to the function that will remove it from the array in whatever secs you specify from the moment it's added. I specified 15 secs
        if !boolVal{

            updateCount = true // 3. change this to true which means in didSelectRow in step 18 return TransactionResult.success(withValue: currentData) will run
            viewsObjects.append(object) // 4. add it to the above array property
            removeObjectFromArray(object) // 5. will remove the viewsTrackingObject passed into the object parameter above in 15 secs from now. Look at step 9
        }
    }

    // 6. this is called above when an object is appended to the array
    func removeObjectFromArray(_ object: ViewsTrackingObject){

        // 7. even though the object should definitely be inside the array double check. If it's in there return true if not return false 
        let boolVal = viewsObjects.contains(where: {$0.postId == object.postId})

        // 8. if the object is in the array which mean the boolVal is true then proceed to step 9
        if boolVal{

            // 9. Fire off in 15 secs from now
            DispatchQueue.main.asyncAfter(deadline: .now() + 15) {

                // 10. find the index of the viewsTrackingObject inside the array
                if let index = self.views.index(where: {$0.postId == viewsModel.postId}){

                    // 11. remove the viewsTrackingObject at the corresponding index from the array
                    self.viewsObjects.remove(at: index)
                    print("++++SUCCESS OBJECT REMOVED++++") // in 15 secs these print statements will print to the console
                    print("----viewsObjects count: \(views.count)")
                    print("....viewsObjects items: \(views.description)")
                }
            }
        }
    }
}

Класс, который содержит tableView. Объявите свойство для sharedInstance трекера , чтобы все проходило через Singleton класс

// 12. This is declared as a class property and it's used in didSelectRow. Its the Singleton Class
let tracker = Tracker.sharedInstance

let currentUserID = Auth.auth().currentUser?.uid // the current user who is pressing the cell
var colors = [ColorClass]() // 500 model objects


func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {

    guard let indexPath = tableView.indexPathForSelectedRow else { return }

    // 14. Get the postId of the colorObject corresponding to the tapped cell
    guard let postID = colors[indexPath.row].postId else { return }
    guard let userID = colors[indexPath.row].userId else { return } // the userId on the object of the cell that was pressed. This is used as a child in the databaseRef below to update the user's view's property

    // make sure the current user can't update the views on their own post
    if currentUserID != userID{

        // 15. Create a ViewsTrackingObject and set it's postID property to the same postId property from step 14 
        let viewsTrackingObject = ViewsTrackingObject()
        viewsTrackingObject.postId = postID

        // 16. using the tracker's shared instance, call the method to find out if the object is currently inside the Singleton's array
        tracker.checkForObjectInArray(object: viewsTrackingObject)

        let viewsRef = databaseRef?.child(userID).child(postID).child("views")

        viewsRef?.runTransactionBlock({
            (currentData: MutableData) -> TransactionResult in

            let newValue: Int

            guard let existingValue = (currentData.value as? NSNumber)?.intValue else {
                return TransactionResult.abort()
            }

            newValue = existingValue + 1

            currentData.value = NSNumber(value: newValue)

            // 17. check to see if the singleton's updateCount property was set to true in step 3. If is true then proceed to step 18
            if self.tracker.updateCount{

                // 18. reset singleton's updateCount property back false since it was set to true in step 3
                self.tracker.updateCount = false
                print("*****Views Updated")
                return TransactionResult.success(withValue: currentData)
            }

            // 19. if the singleton's updateCount property was false to begin with then the views won't get updated in firebase because the transaction will get aborted
            print("=====Views NOT Updated")
            return TransactionResult.abort()

        }, andCompletionBlock: {
            (error, completion, snap) in

            print(snap as Any)

            if !completion{

                // 20. If something went wrong reset singleton's updateCount property back false
                self.tracker.updateCount = false

                print("The value wasn't able to update")
                print(error?.localizedDescription as Any)
            }else{

                // 21. it's unnecessary but to be on the safe side
                self.tracker.updateCount = false

                print("The value updated")
            }
        })
    }
}
0 голосов
/ 05 мая 2018

Вы можете создать typealias, состоящий из того, чем является объект, которым вы заполняете свои ячейки, и Date в верхней части контроллера представления, например:

  typealias ColorLastSelected = (colorClass: ColorClass, timeSelected: Date)

Затем создайте массив для хранения ColorLastSelected объектов.

  var selectedColors: [ColorLastSelected] = []

Оттуда, в didSelectRow, вы можете сделать оператор защиты, чтобы проверить, содержится ли объект в массиве selectedColors. Если нет, то делайте все, что вам нужно, и в конце инициализируйте объект ColorLastSelected и добавьте его в массив selectedColors.

С точки зрения поддержания selectedColors в актуальном состоянии, вы можете запустить метод обновления на повторяющемся таймере, чтобы удалить ColorLastSelected s старше 1 часа. В качестве альтернативы, вы можете просто отфильтровать массив selectedColors перед оператором guard, чтобы удалить вещи, которые старше часа. Если вы собираетесь прыгать между контроллерами представления, вам может потребоваться создать синглтон, который «остается в живых», или вы можете сохранить массив selectedColors где-нибудь

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...