【问题标题】:Typecasting and where in for loop类型转换和 for 循环中的位置
【发布时间】:2015-06-24 08:39:40
【问题描述】:

我有以下场景:

protocol A {}
protocol B: A {}
protocol C: A {}

let objects: [A] = ...

如何循环遍历数组并只为 B 类型的对象执行逻辑?

现在,我正在做这样的事情:

for object in objects {
    if let b = object as? B {
        ...
    }
}

但我想知道是否可以使用where 来使其更具表现力和优雅。

for b in objects where b is B // <- compiles, but b is typed as A, not B
for b: B in objects where b is B // <- doesn't compile
for b in objects as! [B] where b is B // <- I get a warning that "is" will always be true

【问题讨论】:

    标签: swift swift2


    【解决方案1】:

    还有for case(与switch 语句中的case 几乎相同)所以它看起来像这样:

    for case let b as B in objects {
      // use b which is now of type B
    }
    

    另一个很好的表达是:

    for case let b as protocol<B, C> in objects {
      // use b which is now of type protocol<B, C>
    }
    

    因此您可以同时使用两个协议中的方法、属性等

    【讨论】:

      【解决方案2】:

      as? subtype 及其变体是代码异味。这里的其他答案将帮助您完成您想要的,但我想建议您将此逻辑从 for 循环移动到协议(如果可能的话)。

      例如,考虑一个Shape 协议:

      protocol Shape {
          func draw()
          func executeSomeSpecialOperation()
      }
      
      extension Shape {
          func executeSomeSpecialOperation() {
              // do nothing by default
          }
      }
      

      创建三种符合它的形状类型:

      struct Circle : Shape {
          func draw() {
              // drawing code goes here
          }
      }
      
      struct Diamond : Shape {
          func draw() {
              // drawing code goes here
          }
      }
      
      struct Pentagon : Shape {
          func draw() {
              // drawing code goes here
          }
      
          func executeSomeSpecialOperation() {
              print("I'm a pentagon!")
          }
      }
      

      如您所知,您可以创建一个形状数组:

      let shapes : [Shape] = [Circle(), Diamond(), Pentagon()]
      

      这种方法可以让您在不知道其类型的情况下循环遍历该数组:

      for shape in shapes {
          shape.draw()
          shape.executeSomeSpecialOperation()
      }
      

      这有两个好处:

      • 减少耦合(运行for 循环的方法不需要知道Pentagon 是什么)
      • 增加凝聚力(与Pentagon 相关的逻辑包含在该类型的定义中)

      我不确定这是否适用于您的特定用例,但我认为一般来说这是一个更好的模式。

      【讨论】:

      • 这假定该方法对所有Shapes 都有意义。例如,如果您想计算 shapes 数组中所有圆的平均半径,您会怎么做?
      • 如果我需要执行依赖同质的操作,我不会将异构类型存储在数组中。例如,您可以拥有一个单独的 circles 数组,并将 shapes 设为计算属性。
      • 那么每次你需要所有形状的列表时,都会实例化一个新数组?
      • 是的,不过如果出现性能问题,缓存起来很容易。
      【解决方案3】:

      我不能 100% 确定这会回答您的情况 - 因为您已经有类似的东西了 - 我不完全理解您不喜欢您的版本的哪些地方。然而,这在 Swift 2 中有效:

      for object in objectArray where object is protocolB {
          //print(object) // only objects conforming to protocolB
      }
      

      这是我的声明:

      var objectArray: [AnyObject] = []
      // contains a mix of objects of the following 3 classes
      
      
      class class01: protocolA {
      }
      class class02: protocolA, protocolB {
          func test() -> String {
          // required to conform to protocolB
          return "hello"
          }
      }
      class class03: protocolA, protocolB, protocolC {
          func test() -> String {
          // required to conform to protocolB
          return "hello"
          }
      }
      
      protocol protocolA {
      }
      protocol protocolB: protocolA {
          func test() -> String
      }
      protocol protocolC: protocolA {
      }
      

      编译,但 b 被键入为 A,而不是 B

      这是我不明白的一点。很可能是因为我很笨。但是按照我的阅读方式,您的 protocolB 对象也符合定义的 protocolA 。我的定义也是一样的。

      【讨论】:

      • 如果protocolB 将定义一个函数test(),则如果不将object 类型转换为protocolB,您将无法在迭代中在object 上调用它,因为objectAnyObject 类型,而不是protocolB 类型。
      • 在更新的代码中,如果你尝试在你注释的循环中调用print(object.test()),你会得到一个错误('AnyObject'没有一个名为'test()'的成员)。
      • 它在 Xcode7 中工作得非常好。包括print(object)。那只是因为它是一个例如
      • 当然print(object) 有效,因为print() 确实采用AnyObject 参数。 print(object.test()) 没有,因为 test() 没有在 AnyObject 上定义。关键是你不能在object 上调用test(),而不是你不能打印object,在你的例子中你永远不会调用test()
      • 终于明白了。谢谢你一直陪着我。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2022-07-30
      • 2014-11-22
      • 1970-01-01
      • 2012-11-25
      • 1970-01-01
      • 2017-04-26
      • 1970-01-01
      相关资源
      最近更新 更多