Попробуйте поймать весь блок в Swift 2

Я знаю, как сделать try-catch из одной функции. Но проблема в том, что я хочу попытаться поймать весь блок кода - просто, если что-то пойдет не так, проигнорируйте весь блок.

Позвольте мне показать вам простой пример. Это простой парсер из Facebook graph api для /me/taggable_friends:

let params:[NSObject : AnyObject] = ["fields": "first_name, last_name, id, picture"]
let request = FBSDKGraphRequest(graphPath: "/me/taggable_friends", parameters: params, HTTPMethod: "GET") 
request.startWithCompletionHandler { (connection, result, error) -> Void in

            if let error = error {
                print(error)
            }
            else{

                let resultDictionary:NSDictionary! = result as! [String: AnyObject]
                let array = resultDictionary["data"] as! NSArray
                for object in array {
                    let firstName = object["first_name"] as! String
                    let lastName = object["last_name"] as! String
                    let tagId = object["id"] as! String
                    let picture = object["picture"] as! NSDictionary
                    let pictureData = picture["data"] as! NSDictionary
                    let pictureUrl = pictureData["url"] as! String

                    let friend = FacebookFriend(firstName: firstName, lastName: lastName, tagId: tagId, picture: pictureUrl)

                    print(pictureUrl)

                    self.friends.append(friend)
                }


            }
        }

Этот код работает и тихо выйдет из строя, если ошибка не равна нулю. Но допустим, facebook что-то изменил или почему-то какие-то значения в json недоступны. Приложение вылетит. Поэтому мне приходится делать операторы if-let почти для каждой строки этого кода. Скоро читать будет долго и тяжело. В java я бы просто попытался поймать весь блок. Но в быстром я не знаю, как это сделать. Более того, компилятор говорит, что "как!" (поэтому разворачивание необязательного значения) не может вызвать исключение, которое является ложным :)


person Makalele    schedule 02.02.2016    source источник


Ответы (2)


Здесь пригодится оператор guard, основной синтаксис которого таков:

guard let foo = expression else {
    handle the error, and return (or throw)
}

В этом случае вы хотите защитить кучу необязательных парсеров от капризов Facebook, которые изменят свой API в какой-то момент в будущем. Использование guard и continue в этом случае позволяет безопасно пропустить оставшуюся часть блока:

    for object in array {
        guard
            let firstName = object["first_name"] as? String,
            let lastName = object["last_name"] as? String,
            let tagId = object["id"] as? String,
            let picture = object["picture"] as? NSDictionary,
            let pictureData = picture["data"] as? NSDictionary,
            let pictureUrl = pictureData["url"] as? String else {
                print("JSON format has changed")
                continue
        }

        let friend = FacebookFriend(firstName: firstName, lastName: lastName, tagId: tagId, picture: pictureUrl)

        print(pictureUrl)

        self.friends.append(friend)
    }

Вообще говоря, блоки try/catch в Swift на самом деле не аналогичны блокам try/catch в других языках, особенно в Java. В Java все исключения (в некоторой степени) обрабатываются, даже такие, как нулевые ссылки, проблемы с границами массива и т. д. В Swift ошибки try/catch определенно не предназначены для обработки исключений, а предназначены только для обработки ошибок. Таким образом, нет никакого способа использовать try/catch, чтобы защитить себя от as! String сбоя. Вы должны специально ожидать и защищаться от возможности, и именно здесь в игру вступает защита, потенциально в сочетании с броском.

person David Berry    schedule 02.02.2016
comment
Таким образом, в основном защита предотвратит сбой моего приложения, если некоторые ключи (например, first_name) не будут присутствовать в json, но все же мне придется добавить if-let для каждого из них? Но если я просто добавлю if-let, разве это не будет работать так же? - person Makalele; 03.02.2016
comment
guard let является заменой (своего рода) if let. Это очень похоже, за исключением того, что с if let foo = bar, foo доступно только в части успеха оператора if. С guard let foo foo доступен в остальной части блока, содержащего guard let, так что, как вы видите в блоке выше, вы не столкнетесь с кучей вложенных блоков if let. Это также имеет гораздо более естественный поток, поскольку исключительный случай обрабатывается немедленно, а основной поток рутинной работы не ослабевает. - person David Berry; 03.02.2016
comment
Кстати, guard можно использовать с любым условным выражением, а не только с guard let Guard устанавливает предварительные условия для блока кода, которые должны существовать для выполнения остальной части блока. Таким образом, вы также можете использовать что-то вроде guard index < array.count else { throw index out of bounds } - person David Berry; 03.02.2016

Чтобы упростить получение значений из json, вы можете использовать фреймворк SwiftyJSON. В ситуациях, подобных описанным в вашем вопросе, вы можете получить нужное вам значение и обернуть его в условие if let. Например (из SwiftyJSON Readme):

let json = JSON(data: dataFromNetworking)
if let userName = json[999999]["wrong_key"]["wrong_name"].string {
    //Calm down, take it easy, the ".string" property still produces the correct Optional String type with safety
} else {
    //Print the error
    print(json[999999]["wrong_key"]["wrong_name"])
}
person shpasta    schedule 02.02.2016
comment
Красиво, я бы проверил. - person Makalele; 03.02.2016