Swift iOS - 如何在Firebase TransactionBlock上放置计时器以防止它在一定时间内增加

时间:2018-05-05 01:27:50

标签: ios swift firebase firebase-realtime-database transactions

每当不同的用户发布某些内容(让我们说出一种颜色)时,我会获得他们发布的颜色,postID,他们的userId,以秒为单位的日期以及该帖子被查看的次数。

另一个用户可以查看tableView并查看每个用户发布的每种颜色的不同单元格。

每次想要点击didSelectRow的用户查看颜色的详细视图时,我都会运行Firebase TransactionBlock,增加views点数属性,以显示该特定颜色的次数/细胞被轻拍。

例如,如果用户滚动查看tableView并看到一个blueCell,则会在其上显示 views:10 的标签(意味着它被查看了10次)。如果该用户再次按下该blueCell,则视图计数将显示视图:11

问题是如果该用户反复按下该单元格,那么他们可以在几秒钟内增加该count标签上的views

如何跟踪用户点击并在其上放置计时器的每个对象/单元格,以便他们无法更新该特定对象的views count或者可能再过一小时或所以?我的日期是secs和postId,它们对每个对象都是唯一的。

基本上,如果用户在下午12点按下blueCell,则与该特定单元相关联的对象的视图计数将达到11,但如果他们在下午12点至下午1点之间的任何时间再次按下它,则它不会上升。如果他们再次按下它,那么下午1点之后,该对象的视图计数将达到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&#39> 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 :(得分:0)

您可以创建一个typealias,其中包含您填充单元格的对象以及视图控制器顶部的Date,如下所示:

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

然后,创建一个数组来存储ColorLastSelected个对象。

  var selectedColors: [ColorLastSelected] = []

didSelectRow开始,您可以执行一个保护语句来检查对象是否包含在selectedColors数组中。如果没有,那么做你应该做的任何事情,最后,初始化一个ColorLastSelected对象并将其附加到selectedColors数组。

就保持selectedColors最新而言,您可以在重复计时器上运行更新方法,以删除超过1小时的ColorLastSelected。或者,您可以在guard语句之前过滤selectedColors数组,以删除超过一小时的内容。如果你要在视图控制器之间跳转,你可能需要创建一个“保持活着”的单例,或者你可以将selectedColors数组保存在某个地方

答案 1 :(得分:0)

我在问题底部的想法奏效了。

我基本上使用ViewsTrackingObject

的属性制作了postId

然后我创建了一个将viewsTrackingObject添加到数组中的单例,检查它是否在数组中,如果没有将它添加到数组中,则在xxx秒后将其从数组中删除。

对于此示例,我在步骤9中将其设置为15秒:.now() + 15但如果我想要它一小时,我会将其更改为.now() + 3600

我发现按步骤解释事情更容易。有0-21步。我在步骤0的Tracker类顶部开始,在每个对应的代码段上面列出了注释掉的代码,并在步骤21结束了didSelectRow的底部

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的类。 为Tracker的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")
            }
        })
    }
}