通过使用查询而不是重复观察单个事件,可以加快为我的社交网络应用程序获取文章的速度

我有一系列的关键字,可以为我的社交网络发布对象,比如 so/post/id/(post info)

当我加载文章时,我使用 observeSingleEventOfType(.Value)方法加载/post/0,然后加载/post/1等。

我使用 lazyTableView一次加载30个,它是相当慢的。有没有什么方法可以让我使用其中一种查询方法,或者其他方法使它更快,即使我必须重新构造我的 JSON 树中的数据。

我来自解析重新实现我的应用程序,到目前为止的经验是相当不错的。只是有一件事让我有点纠结。提前谢谢你的帮助!

编辑:

func loadNext(i: Int) {


// check if exhists
let ideaPostsRef = Firebase(url: "https://APPURL")


ideaPostsRef.childByAppendingPath(i.description).observeSingleEventOfType(.Value, withBlock: {
(snapshot) in


if i % 29 == 0 && i != 0 && !self.hitNull { return }
// false if nil
// true if not nil
if !(snapshot.value is NSNull) {
let postJSON  = snapshot.value as! [String: AnyObject]
print("GOT VALID \(postJSON)")
let post = IdeaPost(message: postJSON["message"] as! String, byUser: postJSON["user"] as! String, withId: i.description)
post.upvotes = postJSON["upvotes"] as! Int
self.ideaPostDataSource.append(post)
self.loadNext(i + 1)
} else {
// doesn't exhist
print("GOT NULL RETURNING AT \(i)")
self.doneLoading = true
self.hitNull = true
return
}
}
}

这个递归函数实际上运行从 firebase 获取关键字 i 的值。如果它是 NSNULL 它知道这是最后一个可能的文章加载,永远不会再做。如果 NSNULL 没有被命中,而是被 i % 29 == 0命中,那么它将作为基本情况返回,因此一次只加载30个帖子(0索引)。当我将 doneLoading设置为 true时,使用属性观察器调用 tableView.reloadData()

下面是我正在获取的数组的样例

"ideaPosts" : [ {
"id" : 0,
"message" : "Test",
"upvotes" : 1,
"user" : "Anonymous"
}, {
"id" : 1,
"message" : "Test2",
"upvotes" : 1,
"user" : "Anonymous"
} ]
29099 次浏览

Update: we now also cover this question in an AskFirebase episode.

Loading many items from Firebase doesn't have to be slow, since you can pipeline the requests. But your code is making this impossible, which indeed will lead to suboptimal performance.

In your code, you request an item from the server, wait for that item to return and then load the next one. In a simplified sequence diagram that looks like:

Your app                     Firebase
Database


-- request item 1 -->
S  L
e  o
r  a
v  d
e  i
<-  return item  1 --  r  n
g
-- request item 2 -->
S  L
e  o
r  a
v  d
e  i
r  n
<-  return item  2 --     g
-- request item 3 -->
.
.
.
-- request item 30-->
S  L
e  o
r  a
v  d
e  i
r  n
g
<-  return item 30 --

In this scenario you're waiting for 30 times your roundtrip time + 30 times the time it takes to load the data from disk. If (for the sake of simplicity) we say that roundtrips take 1 second and loading an item from disk also takes one second that least to 30 * (1 + 1) = 60 seconds.

In Firebase applications you'll get much better performance if you send all the requests (or at least a reasonable number of them) in one go:

Your app                     Firebase
Database


-- request item 1 -->
-- request item 2 -->  S  L
-- request item 3 -->  e  o
.             r  a
.             v  d
.             e  i
-- request item 30-->  r  n
g
<-  return item  1 --
<-  return item  2 --
<-  return item  3 --
.
.
.
<-  return item 30 --

If we again assume a 1 second roundtrip and 1 second of loading, you're waiting for 30*1 + 1 = 31 seconds.

So: all requests go through the same connection. Given that, the only difference between get(1), get(2), get(3) and getAll([1,2,3]) is some overhead for the frames.

I set up a jsbin to demonstrate the behavior. The data model is very simple, but it shows off the difference.

function loadVideosSequential(videoIds) {
if (videoIds.length > 0) {
db.child('videos').child(videoIds[0]).once('value', snapshot => {
if (videoIds.length > 1) {
loadVideosSequential(videoIds.splice(1), callback)
}
});
}
}


function loadVideosParallel(videoIds) {
Promise.all(
videoIds.map(id => db.child('videos').child(id).once('value'))
);
}

For comparison: sequentially loading 64 items takes 3.8 seconds on my system, while loading them pipelined (as the Firebase client does natively) it takes 600ms. The exact numbers will depend on your connection (latency and bandwidth), but the pipelined version should always be significantly faster.