【问题标题】:Swift: To struct, or not to struct斯威夫特:结构,或不结构
【发布时间】:2018-01-04 17:24:24
【问题描述】:

我终于从 Objective-C 切换到 Swift。我正在为我的客户创建一个视图布局系统,以使他们的应用程序在布局上更加灵活,而不使用自动布局,因为他们想要远程设计他们的屏幕,而自动布局对他们来说太复杂了。我尝试使用structsprotocols 来做到这一点,但我发现它非常笨拙,所以我怀疑我没有以正确的方式思考它。

使用类,结构如下:

class ViewModel {
    var frame: CGRect = .zero
}

class ViewGroupModel: ViewModel {
    var weight: Int = 1
    var children:[ViewModel] = [ViewModel]()
}

class HorizontalViewGroupModel: ViewGroupModel {
}

class VerticalViewGroupModel: ViewGroupModel {
}

我尝试通过定义ViewModel 协议和ViewGroupModel 协议来使用协议来处理它,但我发现它创建了很多重复(属性)。有更好的方法吗?在这种情况下使用类是否被认为是一种好习惯?

编辑:如果不使用类会更好,我正在寻找一个答案,它可以根据structsprotocols 给我一个具体的解决方案。

【问题讨论】:

  • 请注意,CGRect 已经完成了您的模型所做的工作。
  • @mag_zbc 仅仅因为你想使用库而改变设计是最糟糕的理由。
  • ... 尤其是因为 Swift 4 在很大程度上淘汰了该库。
  • ...问题中根本没有提到JSON数据处理。
  • ...因为它不像 ObjectMapper 是处理 JSON 的唯一方法。

标签: ios swift class struct


【解决方案1】:

一般来说,只有在需要类的特殊功能时才使用类,它们是:

  • 一个类可以有一个超类和/或一个子类;结构不能。

  • 类是reference type,而结构是值类型。

  • Objective-C 可以自省一个类(尤其是如果它派生自 NSObject),而它甚至看不到在 Swift 中声明的结构。

【讨论】:

    【解决方案2】:

    编写接口/协议总是比编写类/结构好。这是你的模型明智的。 为此,您也可以漂亮地使用泛型。我希望它会为你节省很多变量和重复。 在我看来,出于布局目的,加上协议和泛型的结构将是一个很好的设计。我认为您不需要在您的情况下使用类。 了解某个功能的内外以更好地使用它总是很好的。 Swift 中结构体和类的主要区别是

    1. 类对象作为引用存储/传递,而结构实例作为值存储/传递
    2. 引用计数允许对一个类实例进行多个引用。
    3. 对于 Class,我们有恒等运算符 === 和 !== 用于检查两个变量或常量是否引用同一个 Class 实例。对于 Struct 不会出现恒等运算符的问题,因为两个不同的 Struct 变量或常量不能指向同一个实例。您可以尝试将标识运算符应用于 Struct 类型。你会得到编译时错误。
    4. 继承使一个类能够继承另一个类的特征。
    5. 类型转换使您能够在运行时检查和解释类实例的类型。
    6. Deinitializers 使类的实例能够释放它已分配的任何资源。

    详细讨论可以通过我的帖子Struct Vs Classes in Swift

    【讨论】:

    • 我知道这些事实。我更多的是寻找一种方法来实现我上面写的结构和协议。
    【解决方案3】:

    如果关心的只是如何实现协议的属性,我不一定会让我在structclass 之间做出选择。如果您的struct 类型必须实现多种属性,那么您有两个基本选择:

    1. 如果您正在谈论一些属性,只需在符合该协议的struct 类型中实现这几个属性。我们一直这样做。例如。在定义符合MKAnnotation 的自定义类型时,我们只需实现三个必需的属性。

      当然,如果我们谈论的是一组更大的属性,这会变得乏味,但编译器会在这个过程中牵着我们的手,确保我们不会遗漏任何东西。所以挑战是相当温和的。

    2. 虽然我不喜欢这种方法,但https://stackoverflow.com/a/38885813/1271826 表明您可以将共享属性实现为一个组件,您可以在其中使用struct 来包装所有这些属性,然后为您的扩展中的协议:

      enum SubviewArrangement {
          case none
          case horizontal
          case vertical
          case flow
      }
      
      struct ViewComponent {
          var frame = CGRect.zero
          var weight = 1
          var subviews = [ViewModel]()
          var subviewArrangement = SubviewArrangement.none
      }
      
      protocol HasViewComponent {
          var viewComponent: ViewComponent { get set }
      }
      
      protocol ViewModel: HasViewComponent { }
      
      extension ViewModel {
          var frame: CGRect {
              get { return viewComponent.frame }
              set { viewComponent.frame = newValue }
          }
          var weight: Int {
              get { return viewComponent.weight }
              set { viewComponent.weight = newValue }
          }
          var subviews: [ViewModel] {
              get { return viewComponent.subviews }
              set { viewComponent.subviews = newValue }
          }
          var subviewArrangement: SubviewArrangement {
              get { return viewComponent.subviewArrangement }
              set { viewComponent.subviewArrangement = newValue }
          }
      }
      

      然后,您可以创建一个符合ViewModel 的实例,如下所示:

      struct LabelModel: ViewModel {
          var viewComponent = ViewComponent()
      }
      
      var label = LabelModel()
      label.weight = 2
      print(label.weight)
      

      我不得不承认,这不是最优雅的方法。 (我什至不愿展示它。)但它避免了必须在符合ViewModel 的类型中单独实现所有这些属性。

    所以,让我们把财产问题放在一边。真正的问题是您应该使用值类型 (struct) 还是引用类型 (class)。我认为在Protocol-Oriented Programming in Swift 视频的末尾(@42:15)考虑 Apple 关于价值与参考语义的讨论是很有启发性的。它们涉及您实际上可能仍想使用类的那些情况。例如,他们建议您在“复制或比较实例没有意义”时使用引用类型。他们建议在处理“窗口”实例时可能适用此规则。这里也一样。

    最重要的是,在我看来,使用值类型来表示视图层次结构(它是引用类型对象的集合)并没有太多好处。这只会让事情变得更加混乱。我会坚持使用class 类型,因为它会准确地反映它所代表的视图层次结构。

    不要误会我的意思:我们已经习惯了使用引用类型,所以我认为挑战我们的先入为主的观念并仔细研究值类型是否能更好地解决这种情况总是好的。不过,在这种情况下,我根本不会担心它,而只是坚持使用 class 层次结构,该层次结构反映了您正在建模的那些对象的层次结构。


    话虽如此,您的问题中提出的类层次结构也不太正确。感觉很奇怪,您实际上可以实例化一个 ViewModel,您以后不能向其添加子视图(而所有 UIView 对象都有 subview 属性)。此外,您的水平和垂直组类型也感觉不正确。例如,它是否应该是具有某些“轴”属性的单一类型,例如 UIStackView 或其他一些“排列”属性,以扩大概念以捕获 UICollectionView 布局?正如您将在上面的 ViewComponent 示例中看到的那样,我已经将这点展平了一点,考虑到这两个警告,但做任何您认为合适的事情。

    【讨论】:

    • 感谢您的出色回答(我收到的最好的回答之一)。是的,我在问题中列出的结构感觉不对。我猜 ViewModel 没有子视图来自 Android 模型,其中 View 没有子视图。我还在玩这个模型,试图找到一个适合所有三个大平台的模型。但是,是的,我现在使用的“集合”类的行为就像你建议的那样。
    猜你喜欢
    • 2017-02-02
    • 2018-10-10
    • 1970-01-01
    • 2017-07-22
    • 2017-03-24
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多