Swift:选用结构体还是类?

5

我终于决定从Objective-C转向Swift。我正在为我的客户创建一个视图布局系统,以使他们的应用程序在布局方面更加灵活,而不使用自动布局,因为他们希望远程设计屏幕,并且自动布局对他们来说过于复杂。我尝试使用结构体协议来实现这一点,但发现它相当笨拙,所以我怀疑自己没有正确思考。

使用类,结构将如下所示:

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 协议来解决它,但我发现这样会创建很多重复的属性。有更好的方法吗?在这种情况下使用类是否被认为是一种良好的实践?
编辑:如果不使用类可能更好,那么我正在寻找一种在结构体和协议方面给出具体解决方案的答案。

1
请注意,已经有CGRect可以完成您的模型所做的事情。 - Sulthan
6
仅仅因为想使用一个库而改变设计是最糟糕的理由。 - Sulthan
3
特别是自从Swift 4发布以来,那个库基本上已经过时了。 - Rob
5
问题中完全没有提到JSON数据处理。 - vadian
1
因为ObjectMapper并不是处理JSON的唯一方式。 - Tom Harrington
显示剩余3条评论
3个回答

6

如果只是关心如何实现协议的属性,那么我不会因为这个而在structclass之间做出选择。如果你有多个属性需要你的struct类型来实现,你有两个基本选项:

  1. If you're talking about a few properties, just implement those few properties in your struct types that conform to that protocol. We do this all the time. E.g. when defining custom types that conform to MKAnnotation, we simply implement the three required properties.

    Sure, if we're talking about a much larger set of properties, this gets tedious, but the compiler holds our hand through this process, ensuring that we don't miss anything. So the challenge is fairly modest.

  2. While I'm not a fan of this approach, https://dev59.com/sFkT5IYBdhLWcg3wPdGU#38885813 shows that you could implement the shared properties as a component, where you have struct to wrap all of these properties, and then implement default computed properties for your protocol in an extension:

    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 }
        }
    }
    

    Where, you can then create an instance that conforms to ViewModel, like so:

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

    I have to confess, this isn't the most elegant approach. (I hesitate to even present it.) But it avoids having to implement all of those properties individually in your types that conform to ViewModel.

因此,让我们把属性问题放在一旁。真正的问题是,您应该使用值类型(struct)还是引用类型(class)。我认为考虑到苹果公司在 Swift中面向协议编程 视频的末尾(@42:15)对值和引用语义进行的讨论是很有启发性的。他们提到了那些您实际上仍然可能希望使用类的情况。例如,当“复制或比较实例没有意义”时,他们建议您可能需要使用引用类型。他们建议这个规则可能适用于处理 “Window” 实例的情况。这同样适用于这里。
此外,我觉得使用值类型来表示视图层次结构(一个引用类型对象的集合)并没有太多好处。这只会让它更加混乱。我会坚持使用 class 类型,因为它将准确地反映它所代表的视图层次结构。
不要误会:我们习惯使用引用类型,我认为挑战我们先入为主的概念并仔细思考是否值类型可以更好地解决情况总是好的。但在这种情况下,我只会选择不担心它,并坚持使用反映您正在建模的对象层次结构的 class 层次结构。
话虽如此,您提出的类层次结构似乎也不太对。感觉奇怪的是,您实际上可以实例化一个 ViewModel,在其中不能添加子视图(而所有的 UIView 对象都有 subview 属性)。另外,您的水平和垂直组类型也不正确。例如,它应该是一个单一的类型,具有某些“轴”属性,例如 UIStackView 或其他“排列”属性,以扩大概念以捕获 UICollectionView 布局。正如您在我的 ViewComponent 示例中看到的那样,我考虑到了这两个警告,并将其放平,但是请按照您认为合适的方式进行操作。

感谢您的出色回答(我收到的最好的之一)。是的,我在问题中提出的结构感觉不对。我猜ViewModel没有子视图来自Android模型,其中View没有子视图。我仍然在尝试使用该模型,试图找到一个适合所有三个主要平台的模型。但是,是的,我现在正在使用像您建议的那样行为的“Collection”类。 - Joris Weimar

4

通常情况下,只有在需要类的特殊功能时才使用类,这些功能包括:

  • 类可以具有超类和/或子类;结构体则不行。

  • 类是引用类型,而结构体是值类型。(了解更多)

  • Objective-C 可以内省类(特别是如果它派生自 NSObject),而无法查看在 Swift 中声明的结构体。


0

编程时,按照接口/协议编码总是比按照类/结构体编码更好。明智地选择你的模型。你也可以很好地利用泛型来实现这个目的。我希望这能为你节省很多变量和重复代码。

在我的意见中,为了布局的目的,结构体与协议和泛型相结合会形成一个很好的设计。我认为在你的情况下没有必要使用类。

了解一个特性的内部和外部细节总是很有益的。Swift中结构体和类的主要区别如下:

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

如果您想进行详细讨论,可以查看我的帖子Swift中的结构体与类


我知道这些事实。我更想找到一种通过结构体和协议来实现我上面所写的方法。 - Joris Weimar

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接