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



每次不同的用户发布一些东西(比如说一种颜色(时,我都会得到他们发布的颜色、postID、他们的用户 ID、以秒为单位的日期以及该帖子被查看的次数。

不同的用户可以浏览表格视图,并查看每个用户发布的每种颜色的不同单元格。

每次正在查找的用户点击didSelectRow查看颜色的详细信息视图时,我都会运行一个Firebase TransactionBlock,该增加views计数属性以显示该特定颜色/单元格被点击的次数。

例如,如果用户滚动浏览 tableView 并看到一个 blueCell,则上面将有一个标签,上面写着视图:10(意味着它被查看了10次(。如果该用户再次按下该 blueCell,则观看次数将显示观看次数:11

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

如何跟踪用户点击的每个对象/单元格并在其上放置计时器,以便他们无法在可能再过一小时左右更新该特定对象的views count我有以秒为单位的日期和 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的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")
}
})
}
}

只是一个想法。

我想过创建另一个具有当前用户ID,postID和tappedTime属性的对象。然后我会创建一个单例。每次按下单元格时,我都会将数据传递到对象中,然后将对象发送到单例中的数组。在那里,我有一个当前时间属性。首先,我会检查 postID 是否在数组中,如果是,我会将 tappedTime 与当前时间 + 1 小时进行比较,以确定是否应该增加观看次数。我会有一个调度异步计时器,1 小时后它会自动从阵列中清除。不过,我不确定它有多实用。

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

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

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

var selectedColors: [ColorLastSelected] = []

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

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

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

我基本上做了一个专门为postId的房产ViewsTrackingObject

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

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

我发现分步解释事情更容易。有 0 - 21 个步骤。我将步骤列为每个相应代码段上方的注释掉的代码,从步骤 0 开始,从Tracker类的顶部开始,到步骤 21 结束didSelectRow的底部

视图跟踪对象:

class ViewsTrackingObject{
var postId: String?
}

单例类:

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)")
}
}
}
}
}

包含表视图的类。为 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")
}
})
}
}

最新更新