09、Swift中的class

1、struct 和 class的差异

作为Swift中的另外一种自定义类型,从语法上来说,class和struct有很多相似的地方,它们都可以用来自定义类型、都可以有properties,也都可以有methods。
作为Swift中的引用类型,class表达的是一个具有明确生命周期的对象,我们需要关注这类内容的“生死存亡”,而值类型,我们更多关注的,就真的只是它的值而已。

1.1 引用类型必须明确指定init方法

首先,Swift并不会为class自动生成默认的init方法。如果我们不定义它,Swift编译器会报错。

1.2 引用类型关注的是对象本身

其次,class和struct对“常量”的理解是不同的。我们分别定义一个PointRef和PointValue的常量:

let p1 = PointRef(x: 0, y: 0)
let p2 = PointValue(x: 0, y: 0)

同样是常量,当我们修改p2的属性时,编译器会报错:p2 is a let constant:

p2.x = 10 // Compile time error

但是,我们却可以修改p1:

p1.x = 10 // OK

这是因为,p2作为一个值类型,常量的意义当然是:“它的值不能被改变”。但是p1作为一个引用类型,常量的意义则变成了,它可以修改自身的属性,但不能再引用其他的PointRef对象。如果我们尝试让p1引用另外一个PointRef对象,就会发生下面的错误:

p1 = PointRef(x: 1, y: 1) // Compile time error

以上就是引用类型代表的“对象”和值类型代表的“值本身”在语义上的差别。而这种差别,还体现在了对它们各自进行赋值之后的表现上:

var p3 = p1
var p4 = p2

这之后,当我们使用===比较p1和p3的时候,得到的结果是true:

p1 === p3 // true

并且,当我们修改了p3之后,p1的值,会一并修改:

p3.x = 10
p1.x // 10

但是,当我们修改了一个值类型时,却并不会这样:

p4.x = 10
p2.x // 0

1.3 引用类型默认是可以修改的

由于引用类型关注的是其引用的对象,而不是对象的值。因此,它的方法默认是可以修改对象属性的。例如:

class PointRef {
   
    // ...

    func move(to: PointRef) {
   
        self.x = to.x
        self.y = to.y
    }
}

但是,对于值类型PointValue来说move必须用mutating来修饰:

struct PointValue {
   
    // ...

    mutating func move(to: PointValue) {
   
        self.x = to.x
        self.y = to.y
    }
}

所以,修改一个struct的本意,实际上是你需要一个全新的值。
最后,还有一点要说明的是,在PointValue里,我们可以直接给self赋值:

mutating func move(to: PointValue) {
   
    self = to
}

编译器知道对一个值类型赋值就是简单的内存拷贝,因此,他会自动用to的每一个属性设置self的对应属性。但是,对于一个引用类型来说,你却不能这样:

class PointRef {
   
    // ...
    func move(to: PointRef) {
   
        self = to // !! Compile time error !!
    }
}

在class的方法里,self自身是一个常量,我们不能直接让它引用其它的对象。

2、理解class类型的各种init方法

2.1 默认init

2.1.1 方式一

给每一个属性都添加默认值

class Point2D {
   
    var x: Double = 0
    var y: Double = 0
}

调用
let origin = Point2D()

2.1.2 方式二

通常,我们还是至少会为class添加一个memberwise init方法。哪怕它就是一个逐个属性赋值的方法。

class Point2D {
   
    var x: Double
    var y: Double
    
    init(x: Double = 0, y: Double = 0) {
   
        self.x = x
        self.y = y
    }
}
为了让一个对象可以默认构造,class必须提供一个不需要参数的init方法,并且,这个方法必须初始化class的每一个属性。

在Swift里,这种真正初始化class属性的init方法,叫designated init,它们必须定义在class内部,而不能定义在extension里,否则会导致编译错误。

另外,除了designated init方法之外,还有一类不真正初始化class属性的方法。

2.2 Convenience init

extension Point2D {
   
    convenience init(at:(Double,Double)) {
   
        self.init(x:at.0,y:at.1)
    }
}

let point = Point2D(at: (2.0,2.0))
这时,我们就需要把作为参数的(2.0, 2.0)拆开成Point2D的每一个属性,然后调用designated init。对于完成这类任务的init方法,就叫做convenience init

可以看到,对于convenience init来说,它有两个要素:

  • 使用convienience关键字修饰;
  • 必须最终调用designated init完成对象的初始化;如果我们直接在convenience init中设置self.x或self.y,会导致编译错误;

2.3 Failable init

例如,我们希望用一个String tuple初始化Point2D:

let point44 = Point2D(at: ("4.0", "4.0"))

参考之前的convenience init,我们可以如法炮制一个:

class Point2D {
   
    // ...
    convenience init?(at: (String, String)) {
   
        guard let x = Double(at.0),
            let y 
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值