swift if-let не разворачивает необязательный NSDictionary

В этом вопросе о переполнении стека рекомендуется ввести cast [AnyObject] в типизированный массив, но в моем случае возвращаемое значение представляет собой единственное число AnyObject, которое можно преобразовать в единственное число JSONObjectWithData:

// ObjC def: public class func JSONObjectWithData(data: NSData, options opt: NSJSONReadingOptions) throws -> AnyObject
if let jsonResult = try? NSJSONSerialization.JSONObjectWithData(data!, options: NSJSONReadingOptions.MutableContainers) as? NSDictionary {
     if let results = jsonResult!["results"] as? NSArray { // ! needed or compile error
     }
}

Как заставить Swift автоматически разворачивать jsonResult?

ОБНОВЛЕНИЕ: Вот лучший пример, чтобы показать проблему:

func intOrThrow(arg: Int) throws -> AnyObject? {
    if arg < 0 {
        throw NSError(domain: "test", code: 400, userInfo: nil)
    } else if arg == 0 {
        return ["ZERO"]
    } else if arg > 1000 {
        return nil
    }
    return arg * 2
}

func strOrNil(arg: Int) -> String? {
    if arg < 0 ||  arg > 1000 {
        return nil
    }
    return "NUMBER\(arg)"
}

print("before intOrThrow(100) and optional unwrap")
if let x = try? self.intOrThrow(100) as? [String], // incorrect type
results = x?.count {
    print("count is \(results). x is \(x)")
}
print("before intOrThrow(0) and optional unwrap")
if let x = try? self.intOrThrow(0) as? [String], // good type
results = x?.count {
    print("count is \(results). x is \(x)")
}
print("before intOrThrow(-100) and optional unwrap")
if let x = try? self.intOrThrow(-100) as? [String], // throw
results = x?.count {
    print("count is \(results). x is \(x)")
}
print("before intOrThrow(1111) and optional unwrap")
if let x = try? self.intOrThrow(1111) as? [String], // nil
results = x?.count {
    print("count is \(results). x is \(x)")
}

print("before intOrThrow(200) and block")
if let x = try? self.intOrThrow(200) as? [String] { // incorrect type
    print("count is \(x?.count). x is \(x)") // still require ! or ?, else compile error
}
print("before intOrThrow(0) and block")
if let x = try? self.intOrThrow(0) as? [String] { // good type
    print("count is \(x?.count). x is \(x)") // still require ! or ?, else compile error
}
print("before intOrThrow(-200) and block")
if let x = try? self.intOrThrow(-200) as? [String] { // throw
    print("count is \(x!.count). x is \(x)") // still require ! or ?, else compile error
}
print("before intOrThrow(2222) and block")
if let x = try? self.intOrThrow(2222) as? [String] { // nil
    print("count is \(x?.count). x is \(x)") // still require ! or ?, else compile error
}
print("done intOrThrow")

print("before strOrNil(3333) and block")
if let x = self.strOrNil(2222) { // nil, no type cast
    print("count is \(x.lowercaseString). x is \(x)") // don't require ! or ?
}
print("done strOrNil")
outputs:
    before intOrThrow(100) and optional unwrap
    before intOrThrow(0) and optional unwrap
    count is 1. x is Optional(["ZERO"])
    before intOrThrow(-100) and optional unwrap
    before intOrThrow(1111) and optional unwrap
    before intOrThrow(200) and block
    count is nil. x is nil
    before intOrThrow(0) and block
    count is Optional(1). x is Optional(["ZERO"])
    before intOrThrow(-200) and block
    before intOrThrow(2222) and block
    count is nil. x is nil
    done intOrThrow
    before strOrNil(3333) and block
    done strOrNil

person teddy    schedule 31.10.2015    source источник


Ответы (1)


Итак, здесь происходит несколько вещей, которые я бы изменил, и я пройдусь по ним и объясню, почему я лично сделал бы это по-другому.

Начнем с try?. Разница между try? и старым простым try заключается в том, что try? либо завершится успешно, либо вернет nil, в то время как try обещает успех или выдаст какую-то ошибку. Это меняет тип возвращаемого значения, в данном случае JSONObjectWithData переходит от возврата AnyObject к AnyObject?.

do {
    let jsonResult = try NSJSONSerialization.JSONObjectWithData(data!, .MutableContainers)
} catch {} //catch and report on the error

Таким образом, jsonResult гарантированно будет существовать как AnyObject, если не возникнет ошибка. Затем вы можете сделать if let jsonResult = jsonResult["results"] as? NSArray. Что, кстати, я бы не стал использовать NSArray, если вы можете помочь, постарайтесь как можно больше придерживаться нативных типов Swift (что-то вроде as? [AnyObject] в порядке).

Теперь ваш код выглядит так:

var data: NSData! = NSData(base64EncodedString: "SGVsbG8gd29ybGQh", options: NSDataBase64DecodingOptions.IgnoreUnknownCharacters)
do {
let jsonResult = try NSJSONSerialization.JSONObjectWithData(data!, options: NSJSONReadingOptions.MutableContainers)
    if let results = jsonResult["results"] as? [AnyObject] {
        print(results)
    } else {
        print("how did this happen?")
    }
} catch {

}

Примечание. Вам также не нужно приводить к [AnyObject] для его компиляции, но тогда results будет AnyObject

Теперь я не большой поклонник do catch в этой ситуации. Гораздо лучший способ сделать это imo:

guard let data = NSData(base64EncodedString: "SGVsbG8gd29ybGQh", options: NSDataBase64DecodingOptions.IgnoreUnknownCharacters),
    jsonResult = try? NSJSONSerialization.JSONObjectWithData(data!, options: NSJSONReadingOptions.MutableContainers),
    result = jsonResult["results"] as? [AnyObject] else {
    return 
}

Теперь это один оператор, который компилируется просто отлично, и все три опции разворачиваются в один оператор. Если оператор guard проходит успешно, все data, jsonResult и results будут заполнены. Если какой-либо из них терпит неудачу, вызывается else, и инструкция возвращается. В операторах guard, а также в операторах if let вы можете развернуть переменную и, если она проходит, использовать ее в цепочке развертываний, точно так же, как мой оператор guard сначала разворачивает data, затем jsonResult, который использует упакованные данные, и так далее.

person barndog    schedule 01.11.2015
comment
Спасибо за ответ. Хотя мне не интересно, почему if let не совсем работает. Вы правы, что это связано с try?. Однако для меня это выглядит как ошибка компилятора (или определения языка), поскольку, если try? производит nil, блок if let следует пропускать, как для использования без попытки. - person teddy; 01.11.2015
comment
опечатка: я в основном любопытствую... - person teddy; 01.11.2015
comment
Почему ваш исходный код не скомпилировался? Вы имеете в виду без актерского состава as? NSArray? - person barndog; 01.11.2015
comment
Нет, это только жалоба на то, что jsonResult должен быть развернут с помощью ~ или ?. - person teddy; 02.11.2015