【问题标题】:How to loop over struct properties in Swift?如何在 Swift 中遍历结构属性?
【发布时间】:2015-02-02 05:28:28
【问题描述】:

是否可以在 Swift 中迭代结构的属性?

我需要在使用许多不同单元格类型的视图控制器中注册单元格重用标识符(单元格被组织在不同的 nib 文件中)。所以我的想法是将所有重用标识符和相应的 nib 文件作为静态元组属性(reuseID,nibName)放在一个结构中。但是如何遍历所有这些以将单元格注册到 tableView?

我已经尝试了一些东西(请参阅下面的答案)。但是有没有更简单的方法来做到这一点,例如没有把每个属性都放在一个数组中?

【问题讨论】:

    标签: ios uitableview swift ios8 tuples


    【解决方案1】:

    这是一个使用 Swifts 元组特性迭代结构属性的示例(重用 UITableViewCells 的标识符和相应的 NIB 名称)。如果您喜欢在 nib 文件中组织单元格并拥有一个使用许多不同单元格类型的 UIViewController,这将非常有用。

    struct ReuseID {
      static let prepaidRechargeCreditCell = "PrepaidRechargeCreditCell"
      static let threeTitledIconCell = "ThreeTitledIconCell"
      static let usageCell = "UsageCell"
      static let detailsCell = "DetailsCell"
      static let phoneNumberCell = "PhoneNumberCell"
    
      static let nibNamePrepaidRechargeCreditCell = "PrepaidRechargeCreditCell"
      static let nibNameThreeTitledIconCell = "IconCellWith3Titles"
      static let nibNameUsageCell = "ListElementRingViewCell"
      static let nibNameDetailsCell = "ListElementStandardViewCell"
      static let nibNamePhoneNumberCell = "PhoneNumberCell"
    
      static let allValuesAndNibNames = [
        (ReuseID.prepaidRechargeCreditCell, ReuseID.nibNamePrepaidRechargeCreditCell),          
        (ReuseID.threeTitledIconCell, ReuseID.nibNameThreeTitledIconCell), 
        (ReuseID.usageCell, ReuseID.nibNameUsageCell), 
        (ReuseID.detailsCell, ReuseID.nibNameDetailsCell), 
        (ReuseID.phoneNumberCell, ReuseID.nibNamePhoneNumberCell)]
    }
    

    使用该结构,可以很容易地使用 for 循环注册所有单元格类型:

    for (reuseID, nibName) in ReuseID.allValuesAndNibNames {
        if let xibPath = NSBundle.mainBundle().pathForResource(nibName, ofType: "nib") {
            let fileName = xibPath.lastPathComponent.stringByDeletingPathExtension
            self.tableView.registerNib(UINib(nibName: fileName, bundle: nil), forCellReuseIdentifier: reuseID)
    
        } else {
            assertionFailure("Didn't find prepaidRechargeCreditCell ?")
        }
    }
    

    【讨论】:

    • 这不是迭代结构属性。如果是,添加新属性应该会自动返回 allValuesAndNibNames。我认为现在的答案是否定的。
    • 对于示例,可以使用情节提要删除整个代码,这是一个更好的解决方案。
    • 坦率地说,这比依靠反射实现更好的做法
    【解决方案2】:

    虽然是老问题,但随着 Swift 的发展,这个问题有了新的答案。我认为您的方法对于所描述的情况要好得多,但是最初的问题是如何迭代结构属性,所以这是我的答案(适用于类和结构

    您可以使用 Mirror Structure Reference。关键是在调用 reflect 到某个对象之后,你会得到它的“镜像”,它非常节省但仍然有用的反射。

    因此我们可以轻松声明以下协议,其中key 是属性名称,value 是实际值:

    protocol PropertyLoopable
    {
        func allProperties() throws -> [String: Any]
    }
    

    当然我们应该利用新的协议扩展来为这个协议提供默认实现:

    extension PropertyLoopable
    {
        func allProperties() throws -> [String: Any] {
    
            var result: [String: Any] = [:]
    
            let mirror = Mirror(reflecting: self)
    
            guard let style = mirror.displayStyle where style == .Struct || style == .Class else {
                //throw some error
                throw NSError(domain: "hris.to", code: 777, userInfo: nil)
            }
    
            for (labelMaybe, valueMaybe) in mirror.children {
                guard let label = labelMaybe else {
                    continue
                }
    
                result[label] = valueMaybe
            }
    
            return result
        }
    }
    

    所以现在我们可以用这个方法循环 any classstruct 的属性。我们只需要将类标记为PropertyLoopable

    为了保持静态(如示例中),我还将添加一个单例:

    struct ReuseID: PropertyLoopable {
        static let instance: ReuseID = ReuseID()
    }
    

    无论是否使用单例,我们最终都可以像下面这样循环属性:

    do {
        print(try ReuseID.instance.allProperties())
    } catch _ {
    
    }
    

    循环结构属性就是这样。享受迅速;)

    【讨论】:

    • 你可以用let properties = mirror.children.filter { $0.label != nil }.map { $0.label! }替换那个循环
    • 在 Swift 2 中,您可以使用 flatMap 使其更短:let properties = mirror.children.flatMap { $0.label }
    • 但是你它不打印静态属性。有没有办法做到这一点?
    • 我有同样的问题@blackjacx,我想我会再发一个帖子。
    【解决方案3】:

    使用 hrs.to 的出色答案,我想提供一个 Swift 3 答案,它更切中要害且不使用单例。

    协议和扩展:

    protocol Loopable {
        func allProperties() throws -> [String: Any]
    }
    
    extension Loopable {
        func allProperties() throws -> [String: Any] {
    
            var result: [String: Any] = [:]
    
            let mirror = Mirror(reflecting: self)
    
            // Optional check to make sure we're iterating over a struct or class
            guard let style = mirror.displayStyle, style == .struct || style == .class else {
                throw NSError()
            }
    
            for (property, value) in mirror.children {
                guard let property = property else {
                    continue
                }
    
                result[property] = value
            }
    
            return result
        }
    }
    

    示例:

    struct Person: Loopable {
        var name: String
        var age: Int
    }
    
    var bob = Person(name: "bob", age: 20)
    
    print(try bob.allProperties())
    
    // prints: ["name": "bob", "age": 20]
    

    【讨论】:

    • 完美运行,但可以迭代 static 属性?
    【解决方案4】:

    知道在 Swift 1.2 中您可以使用 reflect(),并且由于 Swift 2 您可以使用 Mirror,这里是 hrs.to 对 Swift 3 和 4 的回答的补充。

    protocol Loopable {
        var allProperties: [String: Any] { get }
    }
    extension Loopable {
        var allProperties: [String: Any] {
            var result = [String: Any]()
            Mirror(reflecting: self).children.forEach { child in
                if let property = child.label {
                    result[property] = child.value
                }
            }
            return result
        }
    }
    

    在任何结构或类上的使用:

    extension NSString: Loopable {}
    
    print("hello".allProperties)
    // ["_core": Swift._StringCore(_baseAddress: Optional(0x00000001157ee000), _countAndFlags: 5, _owner: nil)]
    

    【讨论】:

      【解决方案5】:

      我根据@John R Perry 的解决方案制作了一个递归函数,该函数更深入地研究了对象的属性。它还需要一个参数来限制它的深度(默认为Int.max)以帮助防止stackoverflow:

      protocol Loopable {
          func allProperties(limit: Int) [String: Any]
      }
      
      extension Loopable {
          func allProperties(limit: Int = Int.max) [String: Any] {
              return props(obj: self, count: 0, limit: limit)
          }
      
          private func props(obj: Any, count: Int, limit: Int) -> [String: Any] {
              let mirror = Mirror(reflecting: obj)
              var result: [String: Any] = [:]
              for (prop, val) in mirror.children {
                  guard let prop = prop else { continue }
                  if limit == count {
                      result[prop] = val
                  } else {
                      let subResult = props(obj: val, count: count + 1, limit: limit)
                      result[prop] = subResult.count == 0 ? val : subResult
                  }
              }
              return result
          }
      }
      

      我不再检查对象是否为classstruct,因为参数不是classstruct 是递归函数的基本情况,而且更容易手动处理而不是出错。

      测试它:

      class C {
          var w = 14
      }
      class B: Loopable {
          var x = 12
          var y = "BHello"
          var z = C()
          static func test() -> String {
              return "Test"
          }
      }
      class A: Loopable {
          var a = 1
          var c = 10.0
          var d = "AHello"
          var e = true
          var f = B()
          var g = [1,2,3,4]
          var h: [String: Any] = ["A": 0, "B": "Dictionary"]
          var i: Int?
      }
      print(A().allProperties())
      

      打印:

      ["e": true, "g": [1, 2, 3, 4], "f": ["z": ["w": 14], "x": 12, "y": "BHello"], "h": ["A": 0, "B": "Dictionary"], "c": 10.0, "i": nil, "d": "AHello", "a": 1]
      

      (字典是无序的,所以如果你得到不同的顺序,那就是为什么)

      【讨论】:

        【解决方案6】:

        现在有一个更简单的方法来做到这一点:

        1:创建一个 Encodable 协议扩展:

        extension Encodable {
            var dictionary: [String: Any]? {
                guard let data = try? JSONEncoder().encode(self) else { return nil }
                return (try? JSONSerialization.jsonObject(with: data, options: .allowFragments)).flatMap { $0 as? [String: Any] }
            }
        }
        

        2:使你的结构/类符合 Encodable 协议

        struct MyStruct: Encodable {...}
        class MyClass: Encodable {...}
        

        然后你可以随时得到一个代表你的结构/类实例的字典:

        var a: MyStruct
        var b: MyClass
        
        print(a.dictionary)
        print(b.dictionary)
        

        然后你可以遍历键:

        for (key, value) in a.dictionary { ... }
        for (key, value) in b.dictionary { ... }
        

        【讨论】:

        • 尝试此操作时遇到两个错误: For-in 循环需要 '[String : Any]?'符合“顺序”;你的意思是解开可选的吗?元组模式不能匹配非元组类型'_'的值
        • 是的,这只是简化了,您应该检查: if let dic = a.dictionary {...} 或强制展开(不推荐)
        【解决方案7】:
        struct IdentifiersModel : Codable {
            let homeReuseID: String = "Home_Reuse_ID"
            let offersReuseID: String =  "Offers_Reuse_ID"
            let testReuseID: String =  "Test_Reuse_ID"
        
            func toDic() -> [String:Any] {
                var dict = [String:Any]()
                let otherSelf = Mirror(reflecting: self)
                for child in otherSelf.children {
                    if let key = child.label {
                        dict[key] = child.value
                    }
                }
                return dict
            }
        }
        

        从结构中创建一个新实例并调用 toDic() 函数

         let identifiersModel = IdentifiersModel()
          print(identifiersModel.toDic())
        

        输出:

        ["offersReuseID": "Offers_Reuse_ID", "testReuseID": "Test_Reuse_ID", "homeReuseID": "Home_Reuse_ID"]
        

        【讨论】:

          猜你喜欢
          • 2011-03-06
          • 1970-01-01
          • 2021-03-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2017-03-22
          • 2021-07-18
          • 2010-11-24
          相关资源
          最近更新 更多