swift: ярлык для охранника "let self = ..."? - PullRequest
0 голосов
/ 19 ноября 2018

Я работаю с данными из JSON, я должен разобрать их в быстрый объект. Я использую этот код:

struct MyGPSCoords {

    var latitude:Double
    var longitude:Double
    var accuracy:Int
    var datetime:NSDate

    init?(infobrutFromJson_:[String:String]?)
    {
        guard let infobrut = infobrutFromJson_ else {
            // first time the user sign up to the app, php server returns "null" in Json 
            return nil
        }

        guard
        let lat:Double = Double(infobrut["latitude"] ?? "nil"),
        let lng = Double(infobrut["longitude"] ?? "nil"),
        let acc = Int(infobrut["accuracy"] ?? "nil"),
        let dtm = NSDate(timeIntervalSince1970: Double(infobrut["time"] ?? "nil"))
        else {
            print("warning : unable to parse data from server. Returning nil");
            return nil ; // position not NIL but format not expected => = nil
        }
        self.latitude = lat
        self.longitude = lng
        self.accuracy = acc
        self.datetime = dtm
    }


}

Я хочу сделать «охранное» заявление максимально коротким. Например, я добавил ?? "nil", поэтому, если один из ключей не существует, Double ("nil") = nil и оператор guard может обработать. Для NSDate я сделал расширение с удобством init? которые возвращают ноль, если его параметр равен нулю, так что я могу так же.

Теперь мой вопрос: могу ли я сделать это еще короче, присваивая непосредственно self.latitude значения прямо в выражении guard? Когда я пытаюсь это:

guard self.latitude = Double(infobrut["latitude"] ?? "nil"), ... 

Там написано, что он не может быть сотворён из Дабла? удвоить ... Итак, есть ли способ сделать этот предохранитель еще короче и избежать меня, чтобы назначить переменные буферизации lat, lng, acc и dtm?

Ответы [ 3 ]

0 голосов
/ 19 ноября 2018

То, что вы хотите сделать, невозможно. Компилятор уже говорит вам об этом, хотя сообщение об ошибке немного вводит в заблуждение. Вы можете использовать guard let для создания новой переменной или guard с логическим выражением. В вашем случае нет let, поэтому компилятор пытается проанализировать логическое выражение. Вместо этого он видит назначение и выдает сообщение об ошибке, которое не соответствует типу. Если типы будут соответствовать (как в guard self.latitude = 12.0), сообщение об ошибке будет более ясным: error: use of '=' in a boolean context, did you mean '=='?

0 голосов
/ 20 ноября 2018

Другие решения кажутся слишком сложными. Просто сделай это

struct MyGPSCoords: Codable {

    var latitude: Double?
    var longitude: Double?
    var accuracy: Int?
    var datetime: Date?

    var isValid {
        return [latitude, longitude, accuracy, datetime].filter { $0 == nil }.isEmpty
    }
}

// jsonData is whatever payload you get back from the URL request.
let coords = JSONDecoder().decode(jsonData, type: MyGPSCoords.self)

if !coords.isValid {
    print("warning : unable to parse data from server.")
}

Поскольку все ваши свойства Optional, синтаксический анализ не может завершиться ошибкой, если отсутствует одно или несколько из них. Проверка isValid намного проще, чем предложение guard let... в исходном коде.

РЕДАКТИРОВАТЬ: Если, как предполагает Роб Нейпир, все значения JSON кодируются как String s, то есть еще один способ структурировать MyGPSCoords:

struct MyGPSCoords: Codable {

    // These are the Codable properties
    fileprivate var latitudeString: String?
    fileprivate var longitudeString: String?
    fileprivate var accuracyString: String?
    fileprivate var datetimeString: String?

    // Default constant to use as a default check for validity
    let invalid = Double.leastNonzeroMagnitude

    // And these are the derived properties that you want users to use
    var latitude: Double {
        return Double(latitudeString ?? "\(invalid)") ?? invalid
    }

    var longitude: Double {
        return Double(longitudeString ?? "\(invalid)") ?? invalid
    }

    var accuracy: Int {
        return Int(accuracyString ?? "\(invalid)") ?? Int(invalid)
    }

    var date: Date {
        return <whatever-formatter-output-you-need>
    }

    var isValid {
        return [latitudeString, longitudeString, accuracyString, datetimeString].filter { $0 == nil }.isEmpty
               && latitude != invalid && longitude != invalid
               && accuracy != Int(invalid) /* && however you compare dates */
    }
}
0 голосов
/ 19 ноября 2018

Во-первых, вы, конечно, должны попытаться исправить JSON, поскольку этот JSON искажен.Строки не являются числами в JSON.Предполагая, что вы не можете исправить этот сломанный JSON, вам нужен инструмент flatMap, который преобразует T ??к Т?(это то, что ожидает охранник).

guard
    let lat = infobrut["latitude"].flatMap(Double.init),
    let lng = infobrut["longitude"].flatMap(Double.init),
    let acc = infobrut["accuracy"].flatMap(Int.init),
    let dtm = infobrut["time"].flatMap(TimeInterval.init).flatMap(Date.init(timeIntervalSince1970:))
    else {
        print("warning : unable to parse data from server. Returning nil")
        return nil // position not NIL but format not expected => = nil
}

Я видел много комментариев о том, что Codable не будет работать здесь, но это точно будет, и это действительно то, что вы должны использовать.Вот один из способов (это немного небрежно в отношении сообщений об ошибках, но он прост):

struct MyGPSCoords: Decodable {

    var latitude:Double
    var longitude:Double
    var accuracy:Int
    var datetime:Date

    enum CodingKeys: String, CodingKey {
        case latitude, longitude, accuracy, datetime
    }

    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)

        guard
            let lat = Double(try container.decode(String.self, forKey: .latitude)),
            let lng = Double(try container.decode(String.self, forKey: .longitude)),
            let acc = Int(try container.decode(String.self, forKey: .accuracy)),
            let dtm = TimeInterval(try container.decode(String.self,
                                                        forKey: .datetime)).flatMap(Date.init(timeIntervalSince1970:))
        else {
            throw DecodingError.dataCorrupted(.init(codingPath: [], debugDescription: "Could not decode"))
        }

        self.latitude = lat
        self.longitude = lng
        self.accuracy = acc
        self.datetime = dtm
    }

}

Или вы можете по-настоящему полюбить внутреннюю полезную функцию и избавиться от всех временных переменныхи опционально через throws.

struct MyGPSCoords: Decodable {

    var latitude:Double
    var longitude:Double
    var accuracy:Int
    var datetime:Date

    enum CodingKeys: String, CodingKey {
        case latitude, longitude, accuracy, datetime
    }

    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)

        func decodeBrokenJSON<T>(_ type: T.Type,
                                 forKey key: CodingKeys) throws -> T
            where T: Decodable & LosslessStringConvertible {
                return try T.init(container.decode(String.self, forKey: key)) ?? {
                    throw DecodingError.dataCorruptedError(forKey: key,
                                                           in: container,
                                                           debugDescription: "Could not decode \(key)")
                    }()
        }

        self.latitude = try decodeBrokenJSON(Double.self, forKey: .latitude)
        self.longitude = try decodeBrokenJSON(Double.self, forKey: .longitude)
        self.accuracy = try decodeBrokenJSON(Int.self, forKey: .accuracy)
        self.datetime = Date(timeIntervalSince1970: try decodeBrokenJSON(TimeInterval.self, forKey: .datetime))
    }

}

(ИМО, это отличный пример того, как throws действительно сияет и должен использоваться гораздо чаще, чем обычно.)

...