Swift 协议属性和方法的默认实现

协议是 Swift 中头等重要的内容,在实际开发中,我们会大量地使用面向协议编程。

方法的默认实现

一个协议中可能会定义许多的属性和方法,有些属性和方法却不是遵循该协议的对象必须实现的,又或者,我们不希望在每个遵循该协议的对象中写入过多重复的代码。

我们可以给协议的属性和方法添加默认的实现来解决上面的问题,我们先来看看方法的默认实现。

第一种方式是使用 @objc 来实现:

1
2
3
4
5
6
7
8
9
10
@objc protocol Human {

@objc optional func read()
func eat()
}

class Man: Human {

func eat() {}
}

Human 协议定义了两个方法,其中 eat 为必须实现的,而 read 并不是必须的,使用 @objc optional 表明该方法为可选。然后我们让 Man 这个类遵循 Human 协议,只需要实现 eat 方法即可。

第二种实现方式是使用 extension 给协议添加一个默认实现方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
protocol Human {

func read()
func eat()
}

extension Human {

func read() {}
}

struct Man: Human {

func eat() {}
}

我们在协议扩展中默认实现了 read 方法,因此在结构体 Man 中就无需实现该方法了。

注意这里我将 Man 的类型从 class 改为了 struct ,这样是没有问题的。但是如果我们在第一种实现方式中,将 Man 的类型从 class 改为 struct,编译器就会报错。

因为使用 @objc 标记的协议,只有遵循 NSObject 的 class 类才能遵循该协议,这也是它的局限性所在。比如 structenum 都不能遵循该协议,关联类型更是无法在该协议中使用。

使用 extension 为协议添加默认实现虽然需要多写点代码,但却摆脱了上面的限制,笔者个人习惯使用第二种实现方式,这也是笔者推荐的方式。

属性的默认实现

我们给 Human 添加一个属性 ageheight,然后添加一个新的结构体 Woman ,代码变成如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
protocol Human {

var age: Int { get set }
var heigth: CGFloat { get set }
func read()
func eat()
}

extension Human {

func read() {}
}

struct Man: Human {

func eat() {}
}

struct Woman: Human {

func eat() {}
}

毫不意外,编译器报错了,因为 ManWoman 没有实现协议中的 age 和 height 属性,我们当然可以在 Man 和 Woman 中实现这两个属性,但是如果还有其它遵循 Human 协议的结构体,我们不想在每个结构体中都把这两个属性实现一遍,而是希望能有个默认值。那么该如何实现呢?很遗憾 extension 无法实现我们的需求,我们需要另辟蹊径。

在软件设计中,许多问题都可以通过添加中间层来实现。这里我们可以借鉴这个思路,先定义一个如下结构体将各个属性包装起来:

1
2
3
4
5
struct HumanProperties {

var age: Int = 0
var height: CGFloat = 30.0
}

然后我们在 Human 协议中添加一个属性:

1
2
3
4
5
6
protocol Human {

var hp: HumanProperties { get set }
func read()
func eat()
}

然后我们在扩展中添加属性:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
extension Human {

var age: Int {
get { hp.age }
set { hp.age = newValue }
}

var height: CGFloat {
get { hp.height }
set { hp.height = newValue }
}

func read() {}
}

然后在遵循 Human 协议的结构体中实现 hp 属性:

1
2
3
4
5
6
7
8
9
10
11
struct Man: Human {

var hp = HumanProperties()
func eat() {}
}

struct Woman: Human {

var hp = HumanProperties()
func eat() {}
}

不管 Human 有多少属性要实现,我们都可以使用 HumanProperties 包装起来,并且为之提供了默认值。

方法参数的默认值

我们为 eat 方法添加如下参数:

1
func eat(breakfast: String, lunch: String, dinner: String)

假设我们现在正在健康饮食阶段,想给 breakfast 和 dinner 添加两个默认值来约束 Man 的行为。

遗憾的是,协议方法并不支持带默认值的参数。

我们可以故技重施,将上面的参数包装起来:

1
2
3
4
5
struct Recipe {
var breakfast: String = "Bread & Milk"
var lunch: String
var dinner: String = "Fruits"
}

然后将方法修改为:

1
func eat(_ food: Recipe)

接着实现该方法:

1
2
3
4
5
6
7
8
9
struct Man: Human {


var hp = HumanProperties()

func eat(_ food: Recipe) {
print(food)
}
}

调用:

1
2
var p = Man()
p.eat(.init(lunch: "Rice & Beef & Vegetables"))
您的支持将鼓励我继续创作!
0%