Swift 使用完成处理程序后数组显示为空
Swift array showing up empty after using completion handler
我正在尝试从 Firebase 数据库中获取服务列表。这是我的参考:
let REF_SERVICES = DB_REF.child("services")
这是我的获取函数:
func fetchServices(completion: @escaping([Service]) -> Void) {
var services = [Service]()
REF_SERVICES.observe(.value) { (snapshot) in
for child in snapshot.children.allObjects as! [DataSnapshot] {
guard let dictionary = child.value as? [String : Any] else { return }
let title = child.key
let service = Service(title: title, dictionary: dictionary)
services.append(service)
}
}
completion(services)
}
这是 Service 结构:
enum ServiceStatus: Int {
case available
case busy
}
struct Service {
let title: String
var details: String?
var location: CLLocation?
var status: ServiceStatus!
init(title: String, dictionary: [String: Any]) {
self.title = title
if let details = dictionary["details"] as? String {
self.details = details
}
if let index = dictionary["status"] as? Int {
self.status = ServiceStatus(rawValue: index)
}
}
}
我不太了解完成处理程序和异步调用的工作原理,所以我不知道如何让它工作。
这是我在 ViewController 中调用此函数的地方:
private var services: [Service]? {
didSet {
let addServiceController = AddServiceController()
addServiceController.services = self.services!
}
}
func fetchServices() {
Services.shared.fetchServices { (services) in
self.services = services
}
}
在两个 ViewController 中,数组显示为空。我通过使用 print 语句验证它是否正在获取数据来使用调试的圣杯。
谢谢。
您需要将 completion(services)
调用移动到 observe 方法的完成回调中。
func fetchServices(completion: @escaping([Service]) -> Void) {
var services = [Service]()
REF_SERVICES.observe(.value) { (snapshot) in
for child in snapshot.children.allObjects as! [DataSnapshot] {
guard let dictionary = child.value as? [String : Any] else { return }
let title = child.key
let service = Service(title: title, dictionary: dictionary)
services.append(service)
}
completion(services) //<-- at this point services will contain the info you appended in for loop
}
}
和上面arturdev说的一样,需要把completion放在observe方法里面。 Firebase 调用是异步代码,这基本上意味着代码尽可能快地执行,而不是从上到下。因此,在您的原始函数中,完成是在执行 REF_SERVICES 之后立即调用的,而不是在 REF_SERVICES 函数中的代码之后。
我相信你还需要包括一个循环计数器,否则完成只会在 1 个循环后执行(同样,async 会尽快执行)。
最后一个提示是尽量避免在代码中使用 "as!",而是使用 if let 语句。如果出于某种原因,您的数据库出现问题并且无法将数据转换为 [DataSnapshot],您的应用程序将会崩溃。
下面的代码应该可以工作,但我还没有测试过。祝你好运!
func fetchServices(completion: @escaping (_ [Service]?) -> ()) {
REF_SERVICES.observe(.value) { (snapshot) in
if let data = snapshot.children.allObjects as? [DataSnapshot] {
var services = [Service]()
var counter = 0
for child in data {
let title = child.key
if let dictionary = child.value as? [String : Any] {
let service = Service(title: title, dictionary: dictionary)
services.append(service)
counter = counter + 1
if counter == data.count {
completion(services)
}
} else {
print("There was an error casting dictionary for \(title)")
counter = counter + 1
if counter == data.count {
completion(services)
}
}
}
} else {
print("Could not cast data as [DataSnapshot]")
return nil
}
}
}
我正在尝试从 Firebase 数据库中获取服务列表。这是我的参考:
let REF_SERVICES = DB_REF.child("services")
这是我的获取函数:
func fetchServices(completion: @escaping([Service]) -> Void) {
var services = [Service]()
REF_SERVICES.observe(.value) { (snapshot) in
for child in snapshot.children.allObjects as! [DataSnapshot] {
guard let dictionary = child.value as? [String : Any] else { return }
let title = child.key
let service = Service(title: title, dictionary: dictionary)
services.append(service)
}
}
completion(services)
}
这是 Service 结构:
enum ServiceStatus: Int {
case available
case busy
}
struct Service {
let title: String
var details: String?
var location: CLLocation?
var status: ServiceStatus!
init(title: String, dictionary: [String: Any]) {
self.title = title
if let details = dictionary["details"] as? String {
self.details = details
}
if let index = dictionary["status"] as? Int {
self.status = ServiceStatus(rawValue: index)
}
}
}
我不太了解完成处理程序和异步调用的工作原理,所以我不知道如何让它工作。
这是我在 ViewController 中调用此函数的地方:
private var services: [Service]? {
didSet {
let addServiceController = AddServiceController()
addServiceController.services = self.services!
}
}
func fetchServices() {
Services.shared.fetchServices { (services) in
self.services = services
}
}
在两个 ViewController 中,数组显示为空。我通过使用 print 语句验证它是否正在获取数据来使用调试的圣杯。
谢谢。
您需要将 completion(services)
调用移动到 observe 方法的完成回调中。
func fetchServices(completion: @escaping([Service]) -> Void) {
var services = [Service]()
REF_SERVICES.observe(.value) { (snapshot) in
for child in snapshot.children.allObjects as! [DataSnapshot] {
guard let dictionary = child.value as? [String : Any] else { return }
let title = child.key
let service = Service(title: title, dictionary: dictionary)
services.append(service)
}
completion(services) //<-- at this point services will contain the info you appended in for loop
}
}
和上面arturdev说的一样,需要把completion放在observe方法里面。 Firebase 调用是异步代码,这基本上意味着代码尽可能快地执行,而不是从上到下。因此,在您的原始函数中,完成是在执行 REF_SERVICES 之后立即调用的,而不是在 REF_SERVICES 函数中的代码之后。
我相信你还需要包括一个循环计数器,否则完成只会在 1 个循环后执行(同样,async 会尽快执行)。
最后一个提示是尽量避免在代码中使用 "as!",而是使用 if let 语句。如果出于某种原因,您的数据库出现问题并且无法将数据转换为 [DataSnapshot],您的应用程序将会崩溃。
下面的代码应该可以工作,但我还没有测试过。祝你好运!
func fetchServices(completion: @escaping (_ [Service]?) -> ()) {
REF_SERVICES.observe(.value) { (snapshot) in
if let data = snapshot.children.allObjects as? [DataSnapshot] {
var services = [Service]()
var counter = 0
for child in data {
let title = child.key
if let dictionary = child.value as? [String : Any] {
let service = Service(title: title, dictionary: dictionary)
services.append(service)
counter = counter + 1
if counter == data.count {
completion(services)
}
} else {
print("There was an error casting dictionary for \(title)")
counter = counter + 1
if counter == data.count {
completion(services)
}
}
}
} else {
print("Could not cast data as [DataSnapshot]")
return nil
}
}
}