I'm fetching some cloud records using CKFetchRecordsOperation.
operation.perRecordCompletionBlock closure has a query to get some children records, but i noticed that operation.completionBlock closure is called before perRecordCompletionBlock completes his job. So, where should i call my completion closure which informs everything is done with that operation?
func getResidents(completion: #escaping (_ residents: [Resident]?, _ error: Error?) -> Void) {
var residents = [Resident]()
let recordNames = ["a56e39ad-9078-43b2-b699-5b9faa233f0d",
"e842daaa-3ff2-434b-9d50-3f14eb7e46d5",
"5cf075ad-d78a-4cea-872e-6b062781f14c",
"8e9fdbbb-cf06-486f-9fae-8be13abe2357",
"78b0cc19-9e33-4755-b199-7098fa319368",
"2d9bee21-d268-46cc-84e1-f7784495aac2",
"ae1da81b-bc1d-4e83-b145-c06882dc2203",
"3e13ed1e-e007-4ece-9a6c-8c09d638d6b3",
"78c224ab-e836-48c9-8c1c-f10bd4907457"
]
func getRecordIDs(names: [String]) -> [CKRecordID] {
var IDs = [CKRecordID]()
for name in names {
IDs.append(CKRecordID(recordName: name))
}
return IDs
}
let operation = CKFetchRecordsOperation(recordIDs: getRecordIDs(names: recordNames))
operation.perRecordCompletionBlock = { record, recordID, error in
if error != nil {
completion(nil, error)
} else {
if let record = record {
// Query debts (resident children)
let predicate = NSPredicate(format: "Resident = %#", record.recordID)
let query = CKQuery(recordType: Constants.RecordType.Debt, predicate: predicate)
self.publicCloudDB.perform(query, inZoneWith: nil) { records, error in
if error != nil {
// Retain the record IDs for failed fetches
completion(nil, error)
} else {
var debts = [Debt]()
if let debtRecords = records {
for item in debtRecords {
if let debt = Debt.parse(record: item) {
debts.append(debt)
}
}
}
if var resident = Resident.parse(record: record) {
resident.debts = debts
residents.append(resident)
print(resident)
}
}
}
}
}
}
operation.completionBlock = {
completion(residents, nil)
}
operation.allowsCellularAccess = true
operation.database = publicCloudDB
operation.start()
}
Related
I have sucessfully shared a record from private database customzone.
share url Optional(https://www.icloud.com/share/0N9smwzXZ0gfumZv8jyVo7uag)
When I print shared record I get this:
record completion <CKShare: 0x106702100; participants=(
"<CKShareParticipant: 0x2817115e0; hasProtectionInfo=1, isCurrentUser=YES, participantID=E742CBD3-41C9-4A9E-A392-08914E8C1D37, permission=readWrite, role=owner, acceptanceStatus=Accepted, identity=<CKUserIdentity: 0x281815ce0; lookupInfo=<CKUserIdentityLookupInfo: 0x2832ba910; email=vhrao#icloud.com>, cached=0, nameComponents=viswanatha, userID=<CKRecordID: 0x283c6f940; recordName=__defaultOwner__, zoneID=_defaultZone:__defaultOwner__>, contactIdentifiers={\n items = (\n );\n}>>"
), allowsReadOnlyParticipantsToSeeEachOther=YES, publicPermission=none, recordChangeTag=2z, rootRecordID=<CKRecordID: 0x283c39360; recordName=645B675E-121F-4DC8-B86D-8968C8DE7B1C-9651-00000ED7A0D0E95D, zoneID=userProfileZone:__defaultOwner__>, routingKey=0N9, recordID=Share-967C18F4-43C4-4958-9D40-6D890DD205AD:(userProfileZone:__defaultOwner__), recordType=cloudkit.share, etag=2z> and nil
record completion <CKRecord: 0x10bc05ac0; recordID=645B675E-121F-4DC8-B86D-8968C8DE7B1C-9651-00000ED7A0D0E95D:(userProfileZone:__defaultOwner__), recordChangeTag=30, values={
asOfDate = "2020-08-01 20:26:44 +0000";
latitude = "29.9288104125185";
locationId = "1D2A93A8-A22C-4B2A-9EC6-86F646667F3E-8282-00000CF78F38B1DB";
locationType = "Household User";
longitude = "-95.6080147578347";
profileImage = "<CKAsset: 0x10bc05f60; referenceSignature=<01ebb50c 4bd0e618 bb0f4486 b486145b 30f22689 83>, uploadRank=0, path=/private~/Library/Caches/CloudKit/0fce42f4be876f65546aa1b4549b3658e9291963/Assets/B21AB25E-C07A-4D65-9238-61A17B3E6372.01b1f7f738f239ee006815df2bb2896fade0f08229, size=34656, UUID=B21AB25E-C07A-4D65-9238-61A17B3E6372, signature=<01b1f7f7 38f239ee 006815df 2bb2896f ade0f082 29>, wrappedAssetKey=<24 bytes>>";
userType = "Consumer";
}, share=<CKReference: 0x283c38ae0; recordID=<CKRecordID: 0x283c390c0; recordName=Share-967C18F4-43C4-4958-9D40-6D890DD205AD, zoneID=userProfileZone:__defaultOwner__>>, recordType=infrastructure> and nil
Then I fetch RecordZones
func fetchRecordZones(completion: #escaping (CKRecordZoneID?, Error?) -> Void) {
var fetchedRecordZones: [CKRecordZoneID : CKRecordZone]? = nil
let fetchZonesOperation = CKFetchRecordZonesOperation.fetchAllRecordZonesOperation()
fetchZonesOperation.fetchRecordZonesCompletionBlock = { (recordZones: [CKRecordZoneID : CKRecordZone]?, error: Error?) -> Void in
guard error == nil else {
completion(nil, error)
return
}
if let recordZones = recordZones {
fetchedRecordZones = recordZones
for recordID in recordZones.keys {
print(recordID.zoneName)
if (recordID.zoneName == Cloud.SharedZone.UserProfile.ZoneName) {
completion(recordID, nil)
}
}
}
completion(nil, nil)
}
fetchZonesOperation.qualityOfService = .utility
self.sharedDB?.add(fetchZonesOperation)
}
I get no recordZones
I go to coud Kit and look at the shared database and fetch zones with fetch with change Token checkbox enabled I get name: userProfileZone owner record name:_9d4825db9b0aa911655e420ec0016129
Looks like it has a zone in shared database. At least seems like, so why am I not getting zones from shared database?
//Sharing code
var lookupInfos = [CKUserIdentity.LookupInfo]()
let creatorID = rootRecord.creatorUserRecordID
self.defaultContainer?.discoverUserIdentity(withUserRecordID: creatorID!) { [weak self] (userIdentity, error) in
guard error == nil else {
if let ckerror = error as? CKError {
self!.aErrorHandler.handleCkError(ckerror: ckerror)
}
completion(false, error)
return
}
if let userIdentity = userIdentity {
lookupInfos.append(userIdentity.lookupInfo!)
let share = CKShare(rootRecord: rootRecord)
share[CKShareTitleKey] = "Infrastructure" as CKRecordValue
//share[CKShareThumbnailImageDataKey] = shoppingListThumbnail as CKRecordValue
share[CKShareTypeKey] = "com.nr2r.infrastructure" as CKRecordValue
if let lookupInfo = userIdentity.lookupInfo {
let op: CKFetchShareParticipantsOperation = CKFetchShareParticipantsOperation(userIdentityLookupInfos: [lookupInfo])
op.fetchShareParticipantsCompletionBlock = { error in
if let error = error {
print("error: ", error)
}
}
op.shareParticipantFetchedBlock = { participant in
participant.permission = .readWrite
share.addParticipant(participant)
let modOp: CKModifyRecordsOperation = CKModifyRecordsOperation(recordsToSave: [rootRecord, share], recordIDsToDelete: nil)
modOp.savePolicy = .ifServerRecordUnchanged
modOp.perRecordCompletionBlock = {record, error in
print("record completion \(record) and \(String(describing: error))")
}
modOp.modifyRecordsCompletionBlock = {records, recordIDs, error in
guard let ckrecords: [CKRecord] = records, let record: CKRecord = ckrecords.first, error == nil else {
print("error in modifying the records " + error!.localizedDescription)
completion(false, error)
return
}
if let records = records {
for record in records {
print ("recordType: \(record.recordType)")
if (record.recordType == "cloudkit.share") {
self!.sharedCKRecordZoneID = record.recordID.zoneID
}
print ("recordName: \(record.recordID.recordName)")
print ("zoneID: \(record.recordID.zoneID.zoneName)")
print ("zoneID: \(record.recordID.zoneID.ownerName)")
}
}
if let anURL = share.url {
print("share url \(String(describing: share.url))")
completion(true, nil)
}
}
self?.privateDB?.add(modOp)
}
self?.defaultContainer?.add(op)
}
} else {
completion(false, nil)
}
}
More edits to show how to retrieve shared record:
let op = CKFetchShareMetadataOperation(shareURLs: [aURL])
op.perShareMetadataBlock = { shareURL, shareMetadata, error in
if let shareMetadata = shareMetadata {
if shareMetadata.participantStatus == .accepted {
let query = CKQuery(recordType: Cloud.Entity.Infrastructure, predicate: NSPredicate(format: "TRUEPREDICATE", argumentArray: nil))
let zone = CKRecordZoneID(zoneName: Cloud.PrivateZone.UserProfile.ZoneName, ownerName: (shareMetadata.ownerIdentity.userRecordID?.recordName)!)
self.sharedDB?.perform(query, inZoneWith: zone, completionHandler: { (records, error) in
if let ckerror = error as? CKError {
self.aErrorHandler.handleCkError(ckerror: ckerror)
completion(nil, error)
} else if let records = records, let firstRecord = records.first {
completion(firstRecord, nil)
}
})
}
}
Fetching shared database zones return zero
self.sharedDB?.fetchAllRecordZones(completionHandler: { (ckrecordZones, error) in
guard error == nil else {
if let ckError = error as? CKError {
self.aErrorHandler.handleCkError(ckerror: ckError)
}
completion (nil, error)
return
}
if let recordZones = ckrecordZones {
for i in 0 ..< recordZones.count{
// find the zone you want to query
if recordZones[i].zoneID.zoneName == Cloud.SharedZone.UserProfile.ZoneName {
completion (recordZones[i].zoneID, nil)
}
}
}
completion (nil, nil)
})
I am creating an onboarding portion of an app which gets the user's contacts to check which already have the app to add as friends. I'm using the CNContact framework. I have created several methods I'm using to get a full list of the users' contacts, check if they have the app, and enumerate them in a UITableView. However, when the view loads, the app crashes with the error "A property was not requested when contact was fetched." I already make a fetch request with the keys CNContactGivenNameKey, CNContactFamilyNameKey, CNContactPhoneNumbersKey, ad CNContactImageDataKey. I've included all my code here:
import Foundation
import Contacts
import PhoneNumberKit
struct ContactService {
static func createContactArray() -> [CNContact] {
var tempContacts = [CNContact]()
let store = CNContactStore()
store.requestAccess(for: .contacts) { (granted, error) in
if let _ = error {
print("failed to request access to contacts")
return
}
if granted {
let keys = [CNContactGivenNameKey, CNContactFamilyNameKey, CNContactPhoneNumbersKey, CNContactImageDataKey]
let request = CNContactFetchRequest(keysToFetch: keys as [CNKeyDescriptor])
request.sortOrder = CNContactSortOrder.familyName
do {
try store.enumerateContacts(with: request, usingBlock: { (contact, stop) in
tempContacts.append(contact)
})
} catch {
print("unable to fetch contacts")
}
print("created contact list")
}
}
return tempContacts
}
static func createFetchedContactArray(contactArray: [CNContact], completion: #escaping ([FetchedContact]?) -> Void) -> Void {
var temp = [FetchedContact]()
getNumsInFirestore { (nums) in
if let nums = nums {
for c in contactArray {
let f = FetchedContact(cnContact: c, numsInFirebase: nums)
temp.append(f)
}
return completion(temp)
} else {
print("Error retrieving numbers")
}
}
return completion(nil)
}
static func getNumsInFirestore(_ completion: #escaping (_ nums : [String]?) -> Void ) -> Void {
var numsInFirebase = [String]()
let db = FirestoreService.db
db.collection(Constants.Firestore.Collections.users).getDocuments() { (querySnapshot, err) in
if let err = err {
print("Error getting user documents: \(err)")
completion(nil)
} else {
for doc in querySnapshot!.documents {
let dict = doc.data()
numsInFirebase.append(dict[Constants.Firestore.Keys.phone] as! String)
}
completion(numsInFirebase)
}
}
}
static func getPhoneStrings(contact: CNContact) -> [String] {
var temp = [String]()
let cnPhoneNums = contact.phoneNumbers
for n in cnPhoneNums {
temp.append(n.value.stringValue)
}
return temp
}
static func hasBump(contact: CNContact, completion: #escaping (_ h: Bool) -> Void ) -> Void {
let contactNums = ContactService.getPhoneStrings(contact: contact)
ContactService.getNumsInFirestore { (nums) in
if let nums = nums {
return completion(contactNums.contains(where: nums.contains))
} else {
print("Error retrieving numbers from firestore")
return completion(false)
}
}
}
static func anyContactsWithBump(completion: #escaping (_ yes: Bool) -> Void) {
let contacts = createContactArray()
var tempBool = false
let workgroup = DispatchGroup()
for c in contacts {
workgroup.enter()
hasBump(contact: c) { (has) in
if has {
tempBool = true
}
workgroup.leave()
}
}
workgroup.notify(queue: .main) {
completion(tempBool)
}
}
}
Then I call the methods in the view controller class:
import UIKit
import Contacts
import FirebaseDatabase
import Firebase
import FirebaseFirestore
class AddContactsVC: UIViewController {
var fetchedContactsWithBump: [FetchedContact] = []
override func viewDidLoad() {
let cnContacts = ContactService.createContactArray()
var contactsWithBump = [CNContact]()
let workgroup = DispatchGroup()
for contact in cnContacts {
workgroup.enter()
ContactService.hasBump(contact: contact) { (has) in
if has {
contactsWithBump.append(contact)
}
workgroup.leave()
}
}
workgroup.notify(queue: .main) {
print("Number of contacts with Bump: \(contactsWithBump.count)")
ContactService.createFetchedContactArray(contactArray: contactsWithBump) { (fetchedContacts) in
if let fetchedContacts = fetchedContacts {
self.fetchedContactsWithBump = fetchedContacts
} else {
print("Error creating fetchedContacts array.")
}
}
}
I also get the message "Error creating fetchedContacts array" in the console, so I know something is going wrong with that method, I'm just not sure what. Any help is appreciated!
Edit: The exception is thrown at 3 points: 1 at the first line of my FetchedContact init method, which looks like this:
init(cnContact: CNContact, numsInFirebase: [String]) {
if cnContact.imageDataAvailable { self.image = UIImage(data: cnContact.imageData!) }
It also points to the line let f = FetchedContact(cnContact: c, numsInFirebase: nums) in createFetched contact array, and finally at my completion(numsInFirebase) call in getNumsInFirestore.
To start with
let contacts = createContactArray()
will always return an empty array.
This function has a return statement outside the closure so will immediately return an empty array.
Change createContactArray to use a completion handler like the other functions you have to populate contacts from inside the closure.
I am making and expanding an app for lists and adding extra data as I go. I have added a bool to mark the item as completed.
I want to change the value of a bool stored in core data. I can add and delete orders but now I'm looking to change properties. Please can I have help on the best way to change these?
I have made a func changeCompleated, but can't work out how to get it to work in my core data manager.
class CoreDataManager {
static let shared = CoreDataManager(moc: NSManagedObjectContext.current)
var moc: NSManagedObjectContext
private init(moc: NSManagedObjectContext) {
self.moc = moc
}
private func fetchOrder(name: String) -> Order? {
var orders = [Order]()
let request: NSFetchRequest<Order> = Order.fetchRequest()
request.predicate = NSPredicate(format: "name == %#", name)
do {
orders = try self.moc.fetch(request)
} catch let error as NSError {
print(error)
}
return orders.first
}
func changeCompleated(name:String, completed: Bool) {
do {
if let order = fetchOrder(name: name) {
self.moc.perform {
}
try self.moc.save()
}
} catch let error as NSError {
print(error)
}
}
func deleteOrder(name: String) {
do {
if let order = fetchOrder(name: name) {
self.moc.delete(order)
try self.moc.save()
}
} catch let error as NSError {
print(error)
}
}
func getAllOrders() -> [Order] {
var orders = [Order]()
let orderRequest: NSFetchRequest<Order> = Order.fetchRequest()
do {
orders = try self.moc.fetch(orderRequest)
} catch let error as NSError {
print(error)
}
return orders
}
func saveOrder(id: UUID, name: String, type: String, qty: Double, urgent: Bool, complete: Bool) {
let order = Order(context: self.moc)
order.id = id
order.name = name
order.type = type
order.qty = qty
order.urgent = urgent
order.complete = complete
do {
try self.moc.save()
} catch let error as NSError {
print(error)
}
}
}
Almost, just add a line to change the value, the perform block is not needed
func changeCompleated(name: String, completed: Bool) {
guard let order = fetchOrder(name: name) else { return }
do {
order.complete = completed
try self.moc.save()
} catch {
print(error)
}
}
And you can also shorten fetchOrder
private func fetchOrder(name: String) -> Order? {
let request: NSFetchRequest<Order> = Order.fetchRequest()
request.predicate = NSPredicate(format: "name == %#", name)
do {
return try self.moc.fetch(request).first
} catch {
print(error)
}
}
Trying unsuccessfully to get a contact match providing contact Identifier. I desire to return contact, then to use the image associated with it. I am getting a nil match. thank you. This code I got from a demo, I'm kinda new to programming
import Contacts
var contact = CNContact()
var contactStore = CNContactStore()
let foundContact = getContactFromID("94AAD3B1-E9E1-48C9-A796-F7EC1014230A")
func getContactFromID(contactID: String) -> CNContact {
AppDelegate.getAppDelegate().requestForAccess { (accessGranted) -> Void in
if accessGranted {
let predicate = CNContact.predicateForContactsWithIdentifiers([contactID])
let keys = [CNContactGivenNameKey, CNContactFamilyNameKey, CNContactEmailAddressesKey, CNContactPhoneNumbersKey, CNContactImageDataKey, CNContactImageDataAvailableKey]
var contacts = [CNContact]()
var message: String!
let contactsStore = AppDelegate.getAppDelegate().contactStore
do {
contacts = try contactsStore.unifiedContactsMatchingPredicate(predicate, keysToFetch: keys)
if contacts.count == 0 {
message = "No contacts were found matching the given name."
}
}
catch {
message = "Unable to fetch contacts."
}
if message != nil {
dispatch_async(dispatch_get_main_queue(), { () -> Void in
Utility.showAlert(nil, message: message)
})
} else {
dispatch_async(dispatch_get_main_queue(), { () -> Void in
self.contact = contacts[0]
print("self.contact: \(self.contact)")
})
}
}
}
return self.contact
}
I solved it :), I removed the dispatch_async stuff, works now: here is fixed code.
func getContactFromID(contactID: String) -> CNContact {
AppDelegate.getAppDelegate().requestForAccess { (accessGranted) -> Void in
if accessGranted {
let predicate = CNContact.predicateForContactsWithIdentifiers([contactID])
let keys = [CNContactGivenNameKey, CNContactFamilyNameKey, CNContactEmailAddressesKey, CNContactPhoneNumbersKey, CNContactImageDataKey, CNContactImageDataAvailableKey]
var contacts = [CNContact]()
var message: String!
let contactsStore = AppDelegate.getAppDelegate().contactStore
do {
contacts = try contactsStore.unifiedContactsMatchingPredicate(predicate, keysToFetch: keys)
if contacts.count == 0 {
message = "No contacts were found matching the given name."
}
}
catch {
message = "Unable to fetch contacts."
}
self.contact = contacts[0]
}
}
return self.contact
}
Im working with NSURLSession. I have an array with restaurants and i'm requesting the dishes for every restaurant in the array to the api. The dataTask works,i'm just having a real hard time trying to call a method only when the all dataTasks are finished.
self.findAllDishesOfRestaurants(self.restaurantsNearMe) { (result) -> Void in
if result.count != 0 {
self.updateDataSourceAndReloadTableView(result, term: "protein")
} else {
print("not ready yet")
}
}
the self.updateDataSourceAndREloadTableView never gets called, regardless of my completion block. Here is my findAllDishesOfRestaurants function
func findAllDishesOfRestaurants(restaurants:NSArray, completion:(result: NSArray) -> Void) {
let allDishesArray:NSMutableArray = NSMutableArray()
for restaurant in restaurants as! [Resturant] {
let currentRestaurant:Resturant? = restaurant
if currentRestaurant == nil {
print("restaurant is nil")
} else {
self.getDishesByRestaurantName(restaurant, completion: { (result) -> Void in
if let dishesArray:NSArray = result {
restaurant.dishes = dishesArray
print(restaurant.dishes?.count)
allDishesArray.addObjectsFromArray(dishesArray as [AnyObject])
self.allDishes.addObjectsFromArray(dishesArray as [AnyObject])
print(self.allDishes.count)
}
else {
print("not dishes found")
}
// completion(result:allDishesArray)
})
completion(result:allDishesArray)
}
}
}
And here is my the function where i perform the dataTasks.
func getDishesByRestaurantName(restaurant:Resturant, completion:(result:NSArray) ->Void) {
var restaurantNameFormatted = String()
if let name = restaurant.name {
for charachter in name.characters {
var newString = String()
var sameCharacter:Character!
if charachter == " " {
newString = "%20"
restaurantNameFormatted = restaurantNameFormatted + newString
} else {
sameCharacter = charachter
restaurantNameFormatted.append(sameCharacter)
}
// print(restaurantNameFormatted)
}
}
var urlString:String!
//not to myself, when using string with format, we need to igone all the % marks arent ours to replace with a string, otherwise they will be expecting to be replaced by a value
urlString = String(format:"https://api.nutritionix.com/v1_1/search/%#?results=0%%3A20&cal_min=0&cal_max=50000&fields=*&appId=XXXXXXXXXappKey=XXXXXXXXXXXXXXXXXXXXXXXXXXXX",restaurantNameFormatted)
let URL = NSURL(string:urlString)
let restaurantDishesArray = NSMutableArray()
let session = NSURLSession.sharedSession()
let dataTask = session.dataTaskWithURL(URL!) { (data:NSData?, response:NSURLResponse?, error:NSError?) -> Void in
do {
let anyObjectFromResponse:AnyObject = try NSJSONSerialization.JSONObjectWithData(data!, options: NSJSONReadingOptions.AllowFragments)
if let asNSDictionary = anyObjectFromResponse as? NSDictionary {
let hitsArray = asNSDictionary.valueForKey("hits") as? [AnyObject]
for newDictionary in hitsArray! as! [NSDictionary]{
let fieldsDictionary = newDictionary.valueForKey("fields") as? NSDictionary
let newDish = Dish.init(dictionary:fieldsDictionary!, restaurant: restaurant)
restaurantDishesArray.addObject(newDish)
}
}
completion(result:restaurantDishesArray)
} catch let error as NSError {
print("failed to connec to api")
print(error.localizedDescription)
}
}
dataTask.resume()
}
Like i said before, I need to wait until the fun findAllDishesOfRestaurants is done. I tried writing my completion blocks but I'm not sure I'm doing it right. Any help is greatly appreciated. Thank
The problem is that you are calling the completion method in findAllDishesOfRestaurants before al tasks are complete. In fact, you are calling it once for each restaurant in the list, which is probably not what you want.
My recommendation would be for you to look into NSOperationQueue for two reasons:
It will let you limit the number of concurrent requests to the server, so your server does not get flooded with requests.
It will let you easily control when all operations are complete.
However, if you are looking for a quick fix, what you need is to use GCD groups dispatch_group_create, dispatch_group_enter, dispatch_group_leave, and dispatch_group_notify as follows.
func findAllDishesOfRestaurants(restaurants:NSArray, completion:(result: NSArray) -> Void) {
let group = dispatch_group_create() // Create GCD group
let allDishesArray:NSMutableArray = NSMutableArray()
for restaurant in restaurants as! [Resturant] {
let currentRestaurant:Resturant? = restaurant
if currentRestaurant == nil {
print("restaurant is nil")
} else {
dispatch_group_enter(group) // Enter group for this restaurant
self.getDishesByRestaurantName(restaurant, completion: { (result) -> Void in
if let dishesArray:NSArray = result {
restaurant.dishes = dishesArray
print(restaurant.dishes?.count)
allDishesArray.addObjectsFromArray(dishesArray as [AnyObject])
// self.allDishes.addObjectsFromArray(dishesArray as [AnyObject]) <-- do not do this
// print(self.allDishes.count)
}
else {
print("not dishes found")
}
// completion(result:allDishesArray) <-- No need for this, remove
dispatch_group_leave(group) // Leave group, marking this restaurant as complete
})
// completion(result:allDishesArray) <-- Do not call here either
}
}
// Wait for all groups to complete
dispatch_group_notify(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0)) {
completion(result:allDishesArray)
}
}