Swift 里面的Any类型的nil处理方法

1,064 阅读3分钟

有一个这样的日志需求,记录异常的网络请求信息,以便排查问题。通过model用来传递,和其它信息汇总到一个字典中写入本地等待上传。

@objc public class BTLogNetworkParams: NSObject {
   
  /// api排查的path,包含http及socket
  @objc public var networkApiPath: String?
   
  /// 参数内容
  public var networkParams: [String: Any]?
   
  /// 接口状态
  @objc public var networkStatus: Int = 0
   
  /// 响应时间
  @objc public var networkRequestTime: Int32 = 0
   
  /// status != 1的返回值。
  public var networkResponse: [String: Any]?
}

模型中属性转字典,自然的就使用了反射的方式Mirror。

public protocol BDLogParamTransfersable {
  /// 转换为字典
  func transferToDictionary() -> [String: Any]
}
​
extension BDLogParamTransfersable {
  public func transferToDictionary() -> [String: Any] {
      let hMirror = Mirror(reflecting: self)
      var transferDic = [String:Any]()
      for case let children in hMirror.children {
          let label = children.label
          let value = children.value
          if let la = label {
              transferDic.updateValue(value, forKey: la)
          }
      }
      return transferDic
  }
}
​
let tempparams = BTLogNetworkParams()
tempparams.networkParams = ["a": "1", "b": "2"]
       
let tempDcit = tempparams.transferToDictionary()
print(tempDcit)
​
// ["networkResponse": nil, "networkRequestTime": 0, "networkStatus": 0, "networkParams": Optional(["a": "1", "b": "2"]), "networkApiPath": nil]

诉求:想把为nil的过滤掉。

准备

struct MyStruct {
  let myString: String?
  let myInt: Int?
  let myDouble: Double?
  
  init(_ myString: String?, _ myInt: Int?, _ myDouble: Double?) {
      self.myString = myString
      self.myInt = myInt
      self.myDouble = myDouble
  }
}

创建一个这样的结构体备用。

PartOne 判断是否为nil

Mirror 是一个结构体, 有一个children的属性,children是一个集合,元素是Child类型。

public struct Mirror {
  public typealias Child = (label: String?, value: Any)
  public typealias Children = AnyCollection<Mirror.Child>
}
let myStruct = MyStruct("合合信息", nil, nil)
let children = Mirror(reflecting: myStruct).children
let properties = children.filter {
  $0.label != nil
}
​
print(properties)
// [(label: Optional("myString"), value: Optional("合合信息")), (label: Optional("myInt"), value: nil), (label: Optional("myDouble"), value: nil)]
​
for property in properties {
  /// Comparing non-optional value of type 'Any' to 'nil' always returns false
  if property.value == nil {
      print("property (property.label ?? "") is nil")
  }
}

提示警告: Comparing non-optional value of type 'Any' to 'nil' always returns false

发现 children.value 是一个Any类型, 怎么可能会为nil呢?

children.value 是明确的Any类型, 打印 又是一个nil,可以类型转换为String,来判断。

for property in properties {
  if checkAnyContainsNil(object: property.value) {
      print("property (property.label ?? "") is nil")
  }
}
​
func checkAnyContainsNil(object : Any) -> Bool {
  let value = "(object)"
  if value == "nil" {
      return true
  }
  return false
}

PartTwo 是否可以将Any类型的值用Any?来承接,再判断是否为nil?

let myStruct = MyStruct("合合信息", nil, nil)
let children = Mirror(reflecting: myStruct).children
let properties = children.filter {
  $0.label != nil
}
​
for property in properties {
  let value = property.value
  let tempValue: Any? = value
  print(tempValue)
  /** 输出
    Optional(Optional("合合信息"))
    Optional(nil)
    Optional(nil)
    */
    
  // 不能将Any强制转换为Any?类型
  // Any不能与nil进行比较,因为它实际上持有包含nil的枚举Optional< T >。
  if tempValue == nil {
      print("property (property.label ?? "") is nil")
  }
   
   
  //⚠️ value 有可能是非String类型,不可以这么处理。
  if !(value is String) {
      print("property (property.label ?? "") is nil")
       
      // property myInt is nil
      // property myDouble is nil
  }
}
  • 不能将Any强制转换为Any?类型
  • Any不能与nil进行比较,因为它实际上持有包含nil的枚举Optional< T >
  • 特定情况下,!(value is String) 可以这么判断达到目的。【 不合理的处理方案:有可能是非String类型】

PartThree 利用可选类型来解决问题

Optional 是一个枚举,none 和 some(Any) 两个case。

@frozen public enum Optional<Wrapped> : ExpressibleByNilLiteral {
​
  /// The absence of a value.
  ///
  /// In code, the absence of a value is typically written using the `nil`
  /// literal rather than the explicit `.none` enumeration case.
  case none
​
  /// The presence of a value, stored as `Wrapped`.
  case some(Wrapped)
}

刚才说到: Any不能与nil进行比较,因为它实际上持有包含nil的枚举Optional。

let myStruct = MyStruct("合合信息", nil, nil)
let children = Mirror(reflecting: myStruct).children
​
// 为nil的情况
for case (let label, Optional<Any>.none) in children {
  // property myInt is nil
  // property myDouble is nil
  print("property (label ?? "") is nil")
}
​
/// 为nil 和 不为nil的条件
for property in children {
  if let label = property.label {
      if case Optional<Any>.some(let x) = property.value {
          print("property (label) is not nil (value: (x))")
      }
      else {
          print("property (label) is nil")
      }
  }
}
​
/// 为nil 和 不为nil的条件
for property in children {
  switch(property) {
  case (let label, Optional<Any>.some(let x)):
      print("property (label ?? "") is not nil (value: (x))")
  case (let label, _):
      print("property (label ?? "") is nil")
  }
}

PartFour Optional

/// 实现一个Optional
let company: Optional<String> = "合合信息"
let product: String? = "启信宝"
print(company)
print(product)
// Optional("合合信息")
// Optional("启信宝")
​
​
let name: String? = "小明"
let wrapped = name.unsafelyUnwrapped
print(wrapped)
print(wrapped.count)
// 小明
// 2
​
​
let year: String?
// Constant 'year' used before being initialized
print(year.unsafelyUnwrapped)
​
var area: Stringprint(area.unsafelyUnwrapped) // ⚠️ Crash: unsafelyUnwrapped of nil optional

声明一个可选值

  • Optional
  • String?

两种效果一样。

unsafelyUnwrapped

获取非nil case的some值,如果没有值就会crash。

谨慎使用 unsafelyUnwrapped

  • let声明的常量,如果没有被initialized,编译期间就会报错。
  • var声明的变量,如果没有被initialized,运行期间会crash。unsafelyUnwrapped of nil optional


\