安路的技术博客

Nothing in life is to be feared. It is only to be understood

Swift3-associatedtype用法

| Comments

swift中的协议采用的是"Associated Types"的方式来实现泛型功能的,通过associatedtype关键字来声明一个类型的占位符作为协议定义的一部分,swift的协议不支持下面的定义方式:

1
2
3
protocol GeneratorType {
    public mutating func next() -> Element?
}

而是应该使用这样的定义方式:

1
2
3
4
protocol GeneratorType {
    associatedtype Element
    public mutating func next() -> Self.Element?
}

在Swift中,class,struct,enums都可以使用参数化类型来表达泛型的,只有在协议中需要使用associatedtype关键字来表达参数化类型。为什么协议不采用这样的语法形式呢?我查看了很多讨论,原因大概总结为两点:

  1. 采用语法的参数化方式的泛型其实定义了整个类型的家族,在概念上这对于一个可以具体实现的类型(clas,stuct,enums)是由意义的,比方说Array.但对于协议来说,协议表达的含义是single的。你智慧实现一次 GeneratorType,而不会实现一个GeneratorType协议。接着又实现另外一个GeneratorType协议。
  2. 协议在swift中有两个目的,第一个目的是用来实现多继承(swift语言被设计成单继承的),第二个目的是强制实现者必须准守自己所指定的泛型约束。关键字associatedtype是用来实现第二个目的的,在GeneratorType中由associatedtype指定的Element,是用来控制next()方法的返回类型的。而不是用来指定GeneratorType的类型的。

我们可以用一个例子进一步解释一下第二个观点

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public protocol Automobile {
    associatedtype FuelType
    associatedtype ExhaustType
    
    func drive(fuel: FuelType) -> ExhaustType}

public protocol Fuel {
    associatedtype ExhaustType
    func consume() -> ExhaustType
}

public protocol Exhaust {
    init()
    
    func emit()
}

我们定义了三个协议,机动车(Automobile),燃料(Fuel),尾气(Exhaust),因为Automobile涉及到然后和尾气所以它内定以了两个关联类型FuelTypeExhaustType

Fuel燃烧后会排放Exhaust,所以在Fuel内定义了关联类型ExhaustType,而Exhaust不需要关联类型。

接下来我们做三个具体的实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
public struct UnleadedGasoline: Fuel {
  public func consume() -> E {    print("...consuming unleaded gas...")    return E()
  }
}
public struct CleanExhaust: Exhaust {
  public init() {}
  public func emit() {  print("...this is some clean exhaust...")
  }
}
public class Car: Automobile {
  public func drive(fuel: F) -> E {      return fuel.consume()
  }
}

我们重点关注Car的定义,我们之所以在Car的定义中同事使用了两种占位符F和E,就是为了给这两个占位符所代表的类型增加约束,因为我们使用一种燃料,必然要排放这种燃料多对应的尾气。于是我么你这样使用Car

1
2
var car = Car()
car.drive(UnleadedGasoline()).emit()

Car, CleanExhaust 在这里成为了一种具体的类型,从Car的意义上来看,燃料成为Car类型的一部分是无可厚非的,因为汽车本身就是可以用燃料进行乐行区分的。

但是尾气成为Car类型的一部分真的有意义吗?从现实生活来看,这是没有意义的,因为尾气一定属于某种燃料类型,用燃料作为类型的一部分已经足够了。尾气成为类型的一部分问题出在了,我们需要在技术上进行泛型约束。

我们现在来调整下Car的实现部分:

1
2
3
4
5
6
7
8
9
10
11
12

模板:
public class Car: Automobile {
  public func drive(fuel: F) -> F.ExhaustType {   
     return fuel.consume()
  }
}

具体例子:
 public func drive(fuel: UnleadedGasoline) -> UnleadedGasoline.ExhaustType {
        return fuel.consume()
    }

在新的定义中,我们把E从参数中去掉,而是换做drive方法的返回值。这样的效果是非常明显的,因为E的存在就是为了泛型约束,让其作为返回值是完全可以实现这种约束。而且没有使其成为类型一部分的副作用。我们现在就可以这样获得一个Car的实例的。

1
var fusion = Car()

倒车入库技巧

| Comments

1

倒车入库难在哪儿呢?倒车不只需要对油门、刹车和方向的操作,同时还考验着对整部车安全范围以及行驶轨迹的判断。倒不好,不但失面子、影响心情,还浪费时间甚至让爱车受伤……那么,怎样才能练就一身过硬功夫,做到准确而漂亮的入库呢?

用CoreLocation实现-地理围栏

| Comments

地理围栏就是当你的手机设备进入或者离开某个区域的时候进行消息提醒,它会让你的程序变得更cool,设想一下,当你离开家,或者靠近一家你喜欢的商场附近时,能够及时给你发送最新的或者最优惠的信息。

MapKit教程:起步

| Comments

MapKit在ios开发中真的是很棒的API,它能很容易的展示地图和位置坐标,定位当前位置,可以路线规划甚至可以自定义覆盖物在地图上面

手动挡驾驶技巧1

| Comments

手动挡车不同于自动挡,离合器、刹车、油门要配合好才可以稳稳当当的开好车,手动挡车驾驶虽然比自动挡繁琐些,但也有自身优点,开常了便会驾轻就熟的。下面就我自己的一点经验给大家说一下

手动挡驾驶技巧-侧方停车

| Comments

基本知识:A柱在发动机舱和驾驶舱之间,左右后视镜的上方,会遮挡你一部分的转弯视界,尤其是左转弯;B柱在驾驶舱的前座和后座之间,就是两侧两扇门之间的那根纵向杠子,从车顶延伸到车底部,从内侧看,安全带就在B柱上;C柱在后座头枕的两侧。A柱、B柱与C柱都是支撑车辆结构强度的主要部分