Как вернуть значение из блока?


#1

Пишу приложение, которое работает с ВК при помощи фреймворка SwiftyVK. Имеется следующий код:

var photoID = ""
VK.API.Upload.Photo.toWall.toUser(media, userId: userIDForVK).send(
    onSuccess: { response in
        print("!!!!!!!!!!! SwiftyVK: uploadPhoto success \n \(response)")
        photoID = response[0,"id"].stringValue },
    onError: { error in
        print("!!!!!!!!!!! SwiftyVK: uploadPhoto fail \n \(error)") },
    onProgress: { done, total in
        print("!!!!!!!!!!! SwiftyVK: uploadPhoto progress: \(done) of \(total))") }
)

где блоки onSuccess, onError и onProgress имеют тип (JSON) -> (). Мне нужно вытащить конкретное значение из переменной response блока onSuccess, как это сделать? Мой код не работает, переменная photoID как была, так и остается пустой.

UPD: Внесу больше ясности в свой вопрос:
У меня есть некоторая функция sendToVK(message: String, photos: [UIImage]). Опишу в общих чертах ее тело:


func sendToVK(message: String, photos: [UIImage]) {
    // описание некоторых переменных
    var attachments = "" // в этой переменной в итоге должна лежать информация обо всех фотографиях, 
//которые я хочу прикрепить к посту, в виде "photo123456_987654,photo123456_612366,photo123456_123156",
// где 123456 - это id юзера, к которому на стену загрузятся фотки (он у меня уже есть),
// а цифры после "_" - это как раз id непосредственно фотографии, 
//который приходит в ответе сервера после успешной загрузки фото

    for photo in photos {
       // в этом цикле загружаю на сервер фотографии, и по идее где то в нем же я должен получать ID фотографии из ответа (response) сервера для дальнейшей работы
      //в этом же цикле выполняется кусок кода, опубликованный в моем самом первом сообщении
      //пусть photoString - это сформированная строка вида "photo12345_612515"

      attachments += photoString
    }

    //тут в wallPostParameters есть все параметры, необходимые для публикации, включая и attachments
    VK.API.Wall.post(wallPostParameters).send(
            onSuccess: {response in print(response)},
            onError: {error in print(error)},
            onProgress: {done, total in print(" !!!!!!!!!!!!!!! send \(done) of \(total)")}
        )
}

И сама проблема заключается в том, что в цикле, где я итерирую по фотографиям, задача загрузки на сервер отправляется на другой поток, соответственно, ответ в photoString записывается тоже в другое время “где-то там”. И по факту получается, что к моменту, когда мне уже нужны заполненные attachments (прямо перед вызовом VK.API.Wall.post), они заполнены без ID фотографий, то есть строкой вида “photo123456_,photo123456_,photo123456_”, потому что где-то в другом потоке эти фотографии еще не успели загрузиться, и, как следствие, в строку attachments необходимые ID не были добавлены.

Суть вопроса заключается в следующем: как мне реализовать на свифте то, что в цикле при каждой итерации мы не продолжаем выполнение, а ждем загрузки фото, добавляем ее id в photoString и накапливаем эти photoString в attachments, чтобы перед вызовом VK.API.Wall.post все было готово к отправке?


#2

Не могу сказать точно насчет SwiftyVK, т.к. никогда с ним не работал. Но попробуй несколько способов:

  1. Возможно у переменной response есть параметр value или что-то вроде того.
    response.value
  2. Попробуй конвертировать response в словарь
    response as! [String: AnyObject]

Если помог - отпиши


#3

А ещё чтоб всем было понятно что приходит в ответ, пришли пожалуйста ответ VK на твой запрос


#4

Пример ответа:

[
  {
    "id" : 456239434,
    "photo_2560" : "https:\/\/pp.userapi.com\/c836237\/v836237744\/2b792\/Olgj2NMza2g.jpg",
    "photo_807" : "https:\/\/pp.userapi.com\/c836237\/v836237744\/2b790\/5t_lXlJb3co.jpg",
    "photo_1280" : "https:\/\/pp.userapi.com\/c836237\/v836237744\/2b791\/eOoFNnKSDSU.jpg",
    "owner_id" : 12345678,
    "text" : "",
    "album_id" : -14,
    "photo_604" : "https:\/\/pp.userapi.com\/c836237\/v836237744\/2b78f\/cT-Or_ZUhWI.jpg",
    "photo_130" : "https:\/\/pp.userapi.com\/c836237\/v836237744\/2b78e\/JEPSoAlNidU.jpg",
    "height" : 1920,
    "width" : 2560,
    "photo_75" : "https:\/\/pp.userapi.com\/c836237\/v836237744\/2b78d\/kqYycVRV2Jc.jpg",
    "date" : 1490162836
  }
]

#5

Дело тут не в переменной response, а в том, что блок onSuccess выполняется асинхронно и на другом потоке. И практически весь код, который идет после VK.API.Upload.Photo.toWall.toUser(media, userId: userIDForVK).send, успевает выполнится до того, как отработает метод send(onSuccess:onError:onProgress:), включая и тот код, где используется этот самый ID, который я хочу получать из ответа сервера (т.е. response).

Тут уже моя вина в том, что я не до конца разобрался в самой сути проблемы и в первом посте описал только “верхушку айсберга”.


#6

Знаете, Вам стоит немножко почитать теории про многопоточность! Ответ у вас в таком формате [[String: Any]], судя по скобкам!
тут нужно как то так делать:

 onSuccess: { response in
     var photoIdArray = [Int]()
     guard let dictionary = response as? [[String: Any]] else {
              print("success answer != [[String:Any]]")
              photoAnalizator(photoIdArray)
     }
     for obj: [String: Any] in dictionary {
             if let photoId = obj["Id"] as? Int {
                    photoIdArray.appended(photoId)
             }
     }
    photoAnalizator(photoIdArray)

}

 func photoAnalizator(_ items: [Int]){
        print("делайте тут то что вам нужно")
 }

Писал на скорую руку , и старался расписать чтоб вы поняли принцип!Но в теории вам нужно разобраться!


#7

Ответ в формате (JSON), потому что в SwiftyVK встроена библиотека SwiftyJSON, которая обеспечивает разбор и удобное преобразование подобных массивов (поэтому для доступа к нужному полю мне достаточно написать response[0, “id”].stringValue).

Вот про многопоточность мне реально очень нужна теория. Вы не могли бы дать несколько ссылок на ресурсы с матчастью по данному вопросу? Как в общих чертах, так и про многопоточность конкретно в Swift.

Про ключевое слово guard сейчас почитаю в документации, не знал про такое.

P.S. Добавил большое обновление в свой первый пост.


#8

Вот хороший курс по основам!хоть еще не перевели на swift 3, но его рекомендую!
Первая ссылка в гугле, и сразу хорошая статья
И вот еще хорошая статья


#9

Прочитал статью с хабра из комментов, в итоге пришел к решению:

private static func sendToVK(_ message: String, photos: [UIImage], userIDForVK: String) {
        var wallPostParameters = Dictionary<VK.Arg, String>()
        wallPostParameters[VK.Arg.message] = message
        var attachments = ""
        var successfulUploadsCount = 0

        let sendPostWorkItem = DispatchWorkItem {
            VK.API.Wall.post(wallPostParameters).send(
            onSuccess: {response in print(response)},
            onError: {error in print(error)},
            onProgress: {done, total in print("\n send \(done) of \(total)\n")} )
        }

        for photo in photos {
            let media = Media(imageData: Data(UIImageJPEGRepresentation(photo, 1.0)!), type: Media.ImageType.JPG)

            VK.API.Upload.Photo.toWall.toUser(media, userId: userIDForVK).send(

                onSuccess: { response in
                    let photoID = response[0,"id"].stringValue
                    let photoString = "photo" + userIDForVK + "_" + photoID

                    if attachments.isEmpty {
                        attachments = photoString
                    } else {
                        attachments += "," + photoString
                    }
                    successfulUploadsCount += 1

                    if successfulUploadsCount == photos.count {
                        wallPostParameters[VK.Arg.attachments] = attachments
                        sendPostWorkItem.perform()
                    }
                    print("\n SwiftyVK: uploadPhoto success \n \(response)\n") },

                onError: { error in
                    print("\n SwiftyVK: uploadPhoto fail \n \(error)\n") },

                onProgress: { done, total in
                    print("\n SwiftyVK: uploadPhoto progress: \(done) of \(total))\n") } )
        }

        if photos.isEmpty {
            sendPostWorkItem.perform()
        }
}