[решено] Функция возвращает пустой массив. Почему


#1

Доброго дня! Не пойму, почему массив пустой, функция же в него добавляет элементы.
print(arrayOfBookmarks, “initial”) - пустой
print(self.arrayOfBookmarks, “print 1”) - с элементами
print(self.arrayOfBookmarks, “inside 2” - с элементами
print(arrayOfBookmarks, “inside 3” - пустой
print(arrayOfBookmarks, “print 4”) - пустой

Также пробовал функцию, которая возвращает массив. Такая же печаль. Функция возвращает пустой массив. Не пойму почему внутри цикла for … in … в массив добавляются элементы, а за его пределами массив пустой.

Предыдущие темы посмотрел и попробовал решения из них, но результата ноль.
Прошу помощи у знатоков.

var arrayOfBookmarks = [BookmarksModel]()

override func viewWillAppear(_ animated: Bool) {
    super.viewWillAppear(true)

    fetchData()
    print(arrayOfBookmarks, "initial")
}

func fetchData() {
        loadingActivityIndicator.startAnimating()
        bookmarksTable.isHidden = true
    
    for i in 0..<arrayID.count {
        
        let jsonURL = "https://xxxxxxxx?linkCode=\(arrayLink[i])&elementId=\(arrayID[i])"
        //guard let url = URL(string: jsonURL) else { return }
        let url = URL(string: jsonURL) ?? URL(string: "https://mfc.group/mobile-api")
        URLSession.shared.dataTask(with: url!) { (data, response, error) in
            guard let data = data else { return }
            
            do {
                let bookmarksMod = try JSONDecoder().decode(BookmarksModel.self, from: data)
                self.arrayOfBookmarks.append(bookmarksMod)
                print(self.arrayOfBookmarks, "print 1")
            } catch let error {
                print("json serialization error", error)
            }
            print(self.arrayOfBookmarks, "inside 2", self.arrayOfBookmarks.count)
        }.resume()
        print(arrayOfBookmarks, "inside 3", arrayOfBookmarks.count)
    }
    print(arrayOfBookmarks, "print 4")
}

#2

Скорее всего нужно сделать через замыкание)
https://medium.com/@nimjea/completion-handler-in-swift-4-2-671f12d33178


#3

Замыкание выполняется последним, поэтому массив заполняется только после выполнения замыкания (принты внутри замыкания URLSession.shared.dataTask(with: url!) {...}.resume() ).


#4

Вы еще учтите, что вы делаете запросы в цикле, следовательно вам нужно отслеживать когда выполнятся все запросы и это не обязательно может быть последний.


#5

Ну да, те. цикл моментально накидал запросов, выполняя print3, и потом функция выполнила print4. Ду-кетч ждёт выполнения/невыполнения запросов. При успешном запросе выполняется print1 и потом выполняется print2. Поэтому только здесь (1 и 2) массив заполнен.

Вроде так )


#6

я говорил про то, что последний запрос может отработать быстрее чем первый. и проверять заполнение массива нужно только в том случае, когда все запросы отработают.


#7

@SHK83, при работе с сетью, насколько я пока знаю, лучшей практикой будет использовать колбеки (сбегающие замыкания), т.к. они будут срабатывать последними и вы к моменту передачи результат в колбек будете точно иметь какой-либо результат - или данные, или ошибку.

Ну я так делаю ))) Хотя может более опытные коллеги поправят.


#8

Имеет ли смысл сделать так:
Написать эту функцию без цикла, она будет принимать одну переменную интеджер. Во viewDidLoad в цикле вызывать функцию и передавать ей переменную i из цикла, которая будет определять сколько раз вызвать функцию и передавать новый i в функцию. Таким образом функция будет выполнять один запрос и передавать один результат в массив за один цикл.

Каково ваше мнение?


#9

никакой разницы, код работает синхронно. у вас все так же будет несколько запросов.


#10

Делайте через коллбек - тогда всё будет норм


#11

Попробуйте так:

func fetchData(completion: @escaping ([BookmarksModel]) -> Void) {
   
    var array = [BookmarksModel]()
    
    for i in 0..<arrayID.count {
        let jsonURL = "https://xxxxxxxx?linkCode=\(arrayLink[i])&elementId=\(arrayID[i])"
        let url = URL(string: jsonURL) ?? URL(string: "https://mfc.group/mobile-api")
        URLSession.shared.dataTask(with: url!) { (data, response, error) in
        guard let data = data else { return }
            do {
                let bookmarksMod = try JSONDecoder().decode(BookmarksModel.self, from: data)
                array.append(bookmarksMod)
             } catch let error {
                 print("json serialization error", error)
             }
        }.resume()  
    }
    completion(array)
}

Применяя функцию, посмотрите, что приходит - должно всё работать, хотя рядом мака нет - не проверял :wink:

fetchData(){ array in 
    print(array)
}

#12

Ok, попробую вечером, спасибо.


#13

Будет пустой массив.


#14

хмм, а как нужно тогда? )


#15

А в основной поток не надо посылать в do?


#16

Я тоже про это подумал, но теперь уже ни в чём не уверен )))


#17

А можете подсказать, пожалуйста) Как раз разбираюсь с escaping замыканием именно для такой ситуации. Вот как написать такую функцию, как у вас в примере, я разобралась. А как потом ее вызвать, в каком месте, чтобы это было по феншую? Если ее еще вынести в отдельный класс?))


#18

Хотя бы так:

func fetchData(completion: @escaping ([BookmarksModel]) -> Void) {
    var array = [BookmarksModel]()
    let group = DispatchGroup()
    for i in 0..<arrayID.count {
        group.enter()
        let jsonURL = "https://xxxxxxxx?linkCode=\(arrayLink[i])&elementId=\(arrayID[i])"
        let url = URL(string: jsonURL) ?? URL(string: "https://mfc.group/mobile-api")!
        URLSession.shared.dataTask(with: url) { (data, response, error) in
            enum Error: Error { case emptyData }
            do {
                guard let data = data else { throw error ?? Error.emptyData }
                let bookmarksMod = try JSONDecoder().decode(BookmarksModel.self, from: data)
                array.append(bookmarksMod)
            } catch let error {
                print("error", error)
            }
            group.leave()
        }.resume()
    }
    group.notify(queue: .main) {
        completion(array)
    }
}

#19

Ну вот оно - управление потоком ))) так и думал :slight_smile: Это пока у меня слабое место )) Спасибо.

З.Ы. Хотя делал для Firestore похожую загрузку профилей юзеров по циклу и с колбеком всё работало )) Видимо данные маленькие и успевали подгрузиться.


#20

Не удается пока проверить этот вариант. Компилятор дико орет на эту строку
enum Error: Error { case emptyData }

говорит: ‘Error’ declares raw type ‘Error’, but does not conform to RawRepresentable and conformance could not be synthesized

Не понимаю что это и как с этим быть.