设为首页收藏本站

LUPA开源社区

 找回密码
 注册
文章 帖子 博客
LUPA开源社区 首页 业界资讯 技术文摘 查看内容

Swift中的泛型使用

2014-10-16 11:43| 发布者: joejoe0332| 查看: 3864| 评论: 0|原作者: 洪丹阳1991, 凤阳马超, Boobs, jluflyingz, LeoXu, 痞子蔡|来自: oschina

摘要: 恭喜你,你可真棒!在不到半个时间里,你已经通过宣传IOS 8盛宴成功解锁了第一个Swift教程!它是Swift系列教程的一个缩水版,就先给你偷看一眼了好了,希望你能够喜欢! ...


初始数据结构

  点击“文件\新建\文件...”新建一个文件,并选择“IOS\Source\Swift File”。点击“下一步”并把这个文件命名为“OrderedDictionary”。最后,点击“创建”。


  你会得到一个空的Swift文件,加这样一段代码进去:

1
struct OrderedDictionary {  }


  到现在为止应该都没有什么问题。通过语义可以看出这个对象是一个结构体。


  注意:总之,值的语义可以想象为“复制、粘贴的行为”,而不是“分享、参考的行为”。值的语义带来一系列的好处,例如不用担心一段代码无意地修改你的数据。了解更多,点击"Swift by Tutorials"的第三章节:类和结构体。


  现在你需要将其一般化,以便它能够装载你需要的任何类型的数据。通过下列改变你对Swift中“结构”的定义:

1
struct OrderedDictionary<KeyType, ValueType>


  在尖括弧中的元素是通用类型的参数。KeyType和ValueType不是他们自身的类型,而是你可以使用在结构里定义取代的类型。现在就简洁清新许多了!


  最简单的实现一个有顺序的字典是保持一个数组和一个字典。字典中将会装载衍射,而数组将装载keys的顺序。


  在结构体内部的定义中,加入以下的代码:

1
typealias ArrayType = [KeyType]typealias DictionaryType = [KeyType: ValueType] var array = ArrayType()var dictionary = DictionaryType()


  这样声明有两个目的,就像上例描述的,有两种类型的用于给已经存在的类型的取新的名称的别名。在这,你将分别地为后面的数组和字典赋值了别名。声明别名是将复杂类型定义为更短名称的类型的一种非常有效的方式。


  你将注意怎么样从结构体中定义用“KeyType”和“ValueType”的参数类型中替换类型。上例的"KeyTypes"是数组类型的。当然这是没有这样的类型的“KeyType”;当在一般的实例化时,将替代Swift像对OrderedDictionary的类型的一切类型通过。


  就因为这样,你将会注意到编译错误:

1
Type 'Keytype' does not conform to protocol 'Hashable'


  或许你会诧异怎么会这样?请再观察下Dictionary的继承者:

1
struct Dictionary<KeyType: Hashable, ValueType>


  除了在KeyType之后的HashTable, 其他的都和OrderedDictionary的定义特别的相似。在分号后面为KeyType声明的Hashable,一定符合Hashable的协议。这是因为字典需要为hash key实现。


  用这种方式约束泛型参数是非常常见的。例如,你想要依据你的应用使用参数做什么,来约束值的类型以,确保相等性、可打印性协议。


  打开OrderedDictionary.Swift,用下例来取代你对结构体的定义:

1
struct OrderedDictionary<KeyType: Hashable, ValueType>


  这样为OrderedDictionary声明KeyType,必须符合Hashable。这就意味着,无论KeyType变成什么类型,都可以接受为没有声明的字典的KEY。

这样,文件再次编译,将不会报错!


Keys, Values 和所有的这些趣事

  如果不能为字典添加值,那么字典有什么作用了?打开OrderedDictionary.swift,在你的结构体定义中添加以下函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 1mutating func insert(value: ValueType, forKey key: KeyType, atIndex index: Int) -> ValueType?{
  var adjustedIndex = index
   // 2
  let existingValue = self.dictionary[key]
  if existingValue != nil {
    // 3
    let existingIndex = find(self.array, key)!     // 4
    if existingIndex < index {
      adjustedIndex--
    }
    self.array.removeAtIndex(existingIndex)
  }   // 5
  self.array.insert(key, atIndex:adjustedIndex)
  self.dictionary[key] = value
   // 6
  return existingValue}


  下面介绍一些新的特性。让我们一步一步来介绍:

  1. 插入一个新对象的方法,insert(_:forKey:atIndex),需要三个参数:一个特别的key的值,插入一对key-value的索引。这是你之前没有注意到的一个关键字:改变。

    结构体的设计是默认不变的,这意味着通常你在实例化的方法中,不能改变结构的成员变量。这十分有限,你能添加改变的关键字,并告诉编译器这个方式在结构体中是允许改变的。这将帮助编译器做出决定什么时候复制结构体(他们是写时复制的),也有助于API的编档。

  2. 你为字典的索引器输入一个如果已经存在,那么返回已存在的值的key,这个插入方法模拟字典更新值相同的行为,因此为这个值保持已经存在的值。

  3. 如果这有一个已经存在的值,只有这样函数才能为这个值在数组里找出索引。

  4. 如果这个已经存在的key在插入索引的之前,这时你需要调整插入的索引,因为你需要移除已经存在的key。

  5. 你将适当地更新数组和字典。

  6. 最后,你返回已存在的值,当这或许没有已存在的值,这个函数返回一个可选的值!


现在你可以为字典添加移除值?

像下列对OrderedDictionary结构体的定义的函数:

1
2
3
4
5
6
// 1mutating func removeAtIndex(index: Int) -> (KeyType, ValueType){
  // 2
  precondition(index < self.array.count, "Index out-of-bounds")   // 3
  let key = self.array.removeAtIndex(index)   // 4
  let value = self.dictionary.removeValueForKey(key)!   // 5
  return (key, value)}

现在再让我们一步一步分析:

1.这是改变结构体状态的函数,removeAtIndex的名称需要和数组的方法匹配。恰当的时候,考虑使用镜像系统库中API是不错的选择。这样帮助开发者在他们的工作平台里,非常容易地使用你的API。

2.首先,你需要检查索引,观察他们是否是在大量的数组里。尝试着从未声明的数组中移除越位的元素,将会导致超时错误,所有在这时检查将会更早符合这样的情况。你或许在Objective-C中使用断言函数;在Swift中断言也是可使用的。但是前提是在释放的工程中是活动的,否则你运行的应用的将会终止。

3.接着,当同时从数组中移除值时,你在给定的索引中数组中获得值。

4.然后,你从字典中为这个key移除的值,同时也会返回这个值。或许在给出的key中,字典也没有相应的值,所以removeValueForKey返回一个可选的。这种情况下,你知道字典将会为给出的key,包含一个值,因为这是唯一的自己给字典添加值的方法--insert(_:forKey:atIndex:),这时你可以选择使用“!”,表明这将会有正义感值。

5.最后,你在一个元组返回的key和value。数组的removeAtIndex和字典的removeValueForKey是一样的返回已存在的值功能。


值的读取跟写入

  把值写入字典(dictionary)是没问题了, 可是这样还不够! 你还需要实现一个方法(method) 从字典中读出相应的值.


  打开 OrderedDictionary.swift 文件, 然后把下列代码添加到结构定义(struct definition)当中, , 就放在 thearrayanddictionaryvariable 声明的下面:

1
2
var count: Int {
  return self.array.count}


  这个常用的属性, 用来算出字典里面有几条记录. 只要返回数组的 count 属性的中值就可以了!


  接下来, 就是如何访问(Access)字典中的记录了(Element). 我们可以通过下标(Subscript)来访问, 代码如下:

1
let dictionary = [1"one"2"two"]let one = dictionary[1// Subscript


  下标的语法我们会用了, 但是如果是我们自己定义的类那该怎么用呢? 好在 Swift 支持在自定义类里头添加这项功能. 而且实现起来也不复杂.


  把下列代码添加到结构定义的底部:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 1
subscript(key: KeyType) -> ValueType? {
  // 2(a)
  get {
    // 3
    return self.dictionary[key]
  }
  // 2(b)
  set {
    // 4
    if let index = find(self.array, key) {
    else {
      self.array.append(key)
    }     // 5
    self.dictionary[key] = newValue  
  }
}


  我们详细解释下这段代码:

  • 上面代码注释中标有 1 的那一段:跟 func 和 var 类似, subscript 也是个关键字, 通过它定义下标.  参数 key 是出现在中括号中的那个对象.

  • 注释中标有 2 的那一段: 下标由 setter 跟 getter 两部分组成. 本例同时定义了 setter (代码中的 set  ) 跟 getter (代码中的 get ). 当然, 不是每个下标都要同时定义 setter 跟 getter. 

  • 注释中标有 3 的那一段: getter 比较简单, 只要通过参数 key, 在字典中找到相应的值即可. 字典返回的是可选值(optinal), 如果 key 不存在, 该值为 nil.

  • 注释中标有 4 的那一段: setter 就复杂些. 首先要检测这个 key 在有序字典里面是不是已经存在. 如果不存在, 则把 key 添加到数组中. 由于我们需要把 key 添加到数组的尾部, 所以这里调用的是 append 方法.

  • 注释中标有 5 的那一段: 把值添加到字典中. 这里用隐性命名的变量 newValue 获取传递过来的值.


  就像用 Swift 自带的字典类的下标那样去, 你可以通过 key 来查找某个值. 可是如果我们需要像访问数组那样, 用下标索引(index)访问某个值, 该怎么办呢? 既然是有序字典, 没有道理不能通过下标索引一个一个的按顺序访问.


  结构体跟类可以定义多个参数类型不同的下标(subscript). 把下列代码添加到结构定义的底部:

1
2
3
4
5
6
7
8
9
10
subscript(index: Int) -> (KeyType, ValueType) {
  // 1
  get {
    // 2
    precondition(index < self.array.count, 
                 "Index out-of-bounds")     // 3
    let key = self.array[index]     // 4
    let value = self.dictionary[key]!     // 5
    return (key, value)
  }}


  这段代码跟前面那段类似, 不同的是参数类型变成了 Int. 因为我们现在要实的功能是现像数组那样, 使用下标索引访问有序字典. 不过这次返回的是由 key 跟 value 组成的一个元组(tuple). 因为有序字典就是由这样一个一个的元组构成的.


  下面具体解释下这段代码:

  1. 这个下标只定义了 getter. 当然你也可以把 setter 加上. 不过要注意先检查 index 会不会越界.

  2. index 的值不能超出数组的界限, 也就是字典元组的个数. 我们可以利用 precondition 提示开发人员, 程序出现越界访问.

  3. 用 index 从数组中读出 key.

  4. 再用 key 从字典中读取 value. 需要注意的是, 由于数组中的每一个 key 跟字典的 value 是一一对应的, 所以这里使用符号 ! (unwrapped) 对读出来的 value 拆包.

  5. 最后, 返回一个包含 key 和 value 的元组.


挑战: 为上面那个下标实现 setter . 可以参考前面的例子.

提示 1


注意, newValue 是个包含 key 跟 value 的元组.


提示 2


下列代码可以将值从元组中提取出来:
 let(key, value) = newValue


  也许你会好奇, 如果 KeyType 是 Int 型的, 会出现什么问题? 使用泛型的好处是, 不管是什么类型, 只要能算出哈希值(hashable)的就行,  所以 Int 当然也能用.  问题是, 当 key 也是 Int 型的时候, 这俩个下标该怎么区分呢?


  这就需要我们给编译器提供更多的类型信息. 让它知道在什么时调用哪个下标. 比如我们定义的这两个下标, 返回的类型不一样. 如果你用 key-value 类型的元组给它赋值, 编译器就会自动调用那个数组式(array-style )的下标.



酷毙

雷人

鲜花

鸡蛋

漂亮
  • 快毕业了,没工作经验,
    找份工作好难啊?
    赶紧去人才芯片公司磨练吧!!

最新评论

关于LUPA|人才芯片工程|人才招聘|LUPA认证|LUPA教育|LUPA开源社区 ( 浙B2-20090187 浙公网安备 33010602006705号   

返回顶部