Hi,SwiftGG 翻译组启用了新的域名:swiftgg.team今后翻译组的各项活动将会在新域名下开展,不要错过哦!

作者:shanks

大家新年好,每周问答整理又来了。这个栏目会一直做下去,目前的表达形式也在想做一些改变,更加方便大家阅读,不过还没想好具体怎么做,大家有啥意见,都可以提出来哈。本周共整理了 5 个问题。涉及问题有:泛型输出类型问题,数组的contains方法问题,数组的元素合并问题,枚举原始值的默认值问题,字典的扩展问题

本周整理问题如下:

对应的代码都放到了 github 上,有兴趣的同学可以下载下来研究:点击下载

Question1: A variable in generic class gets wrong type

Q1链接地址

问题描述

这个问题有点绕,大家细细看代码,涉及到继承和泛型的知识点。楼主的问题是问,定义的serviceC中的属性cachedynamicType为神马是AbstractCache<B>,而不是AbstractCache<C>


// Models:
class A {}
class B: A { }
class C: B { }

// Cache:
class AbstractCache<T> {}

// Services:
class AbstractService<T> {
let cache = AbstractCache<T>()
}
class ServiceA<T: A>: AbstractService<T> {}
class ServiceB<T: B>: ServiceA<B> {}
class ServiceC<T: C>: ServiceB<C> {}

let serviceC = ServiceC()
print(serviceC.cache.dynamicType) // 这里输出AbstractCache<B>

问题解答

楼主首先定义了类A, B, C 的继承关系。然后定义了一个泛型类AbstractCache<T>,随后又定义了一个使用AbstractCache<T>的泛型类AbstractService<T>, 里面有一个属性cacheAbstractCache<T>的对象。这个时候 T 还没指定具体的类型。第一次指定 T 类型的时候是在定义ServiceB<T: B>时候,继承自ServiceA<B>,这里的 T 替换成了 B,所以即使ServiceC<T: C> 继承自 ServiceB<C>, 因为 B,C是继承关系,这样定义是合法的。但是改变不了 T 的指代。所以cachedynamicTypeAbstractCache<B>
要解决这个问题,见以下代码,定义继承的时候,不要指定泛型具体的类型,见以下代码:

class ServiceA<T: A>: AbstractService<T> {}
class ServiceB<T: B>: ServiceA<T> {}
class ServiceC<T: C>: ServiceB<T> {}

let serviceC = ServiceC()
print(serviceC.cache.dynamicType) // 这里输出AbstractCache<C>

Question2: Swift 2 Array Contains object?

Q2链接地址

问题描述

当数组的元素类型是自定义的类时,使用contains方法会报错:


var array = ["A", "B", "C"]

array.contains("A") // 正确

class Dog {
var age = 1
}

var dogs = [Dog(), Dog(), Dog()]
var sparky = Dog()
//dogs.contains(sparky) //报错

let result = dogs.contains({ $0 === sparky })

问题解答

数组的contains方法需要用到==操作符,所以需要自定义的类实现Equatable协议即可:

class Dog: Equatable {

var age = 1

}

func == (lhs: Dog, rhs: Dog) -> Bool {
return lhs.age == rhs.age
}

继续看看深度的解释:
Array 中 contains 有 2 个定义:

  • func contains(_ element: Self.Generator.Element) -> Bool

Generator.Element 存在 Generator.Element : Equatable的约束,要求元素实现Equatable协议。Dog没有实现此协议的话,那么就不能匹配这个方法。

  • func contains(@noescape _ predicate: (Self.Generator.Element) throws -> Bool) rethrows -> Bool

此方法定义了一个闭包来实现比较,在 Dog 没有实现Equatable协议的前提下,我们可以这样对Dog进行比较:

let result = dogs.contains({ $0 === sparky })

这样做只有比较2个对象的引用是否一样,而不是比较对象的值是否相等。所以不满足需求。
所以,最终的方案,还是实现Equatable协议吧。

Question3: Arrays of Int arrays. Storing duplicates in order only

Q3链接地址

问题描述

楼主问题是:给定一个数组,输出一个二维数组,连续相同的数组组成子数组。

比如:

输入:[7, 7, 3, 2, 2, 2, 1, 7, 5, 5]

输出:[[7, 7], [3], [2, 2, 2], [1], [7], [5, 5]]

楼主给出了常规的实现(不过跑不起来,要改一下实现),见以下代码:

let mainArray = [7, 7, 3, 2, 2, 2, 1, 7, 5, 5]
var oldNum = mainArray[0]
var array = [[Int]]()
for var i = 0; i < mainArray.count; i++ {
var columnArray = Array<Int>()
for var j in 0...9 {
if oldNum == mainArray[j]{
columnArray.append(mainArray[j])
}
else {
array.append(columnArray)
//j += 1
break;
}
oldNum = mainArray[j];
j += 1
}

}
array //输出错误的结果

正确的常规做法应该是:

array = [[Int]]()
var columnArray = Array<Int>()
for var i = 0; i < mainArray.count; i++ {
if oldNum == mainArray[i]{
columnArray.append(oldNum)
} else {
array.append(columnArray)
columnArray = Array<Int>()
oldNum = mainArray[i]
i--
}
}
array.append(columnArray) // 补齐最后的部分

问题解答

来看看如何用函数式编程来解决这个问题吧:

let result = mainArray.reduce([[Int]]()) { (var result, num) -> [[Int]] in
if var lastSequence = result.last where lastSequence.first == num {
result[result.count-1].append(num)
} else {
result.append([num])
}
return result
}

result

Question4: swift enum case declaration synatx

问题链接

Q4链接地址

问题描述

吐槽一下,帖子标题的syntax单词写错了。
这个问题是关于枚举类型的默认值的,楼主是想知道, Swift 中的枚举类型的原始值默认值有那些规则?

问题解答

Swift 的枚举类型,如果指定了原始值的类型(Int, String等),就会有默认值的存在。如果不指定原始值类型,编译器就不会分配默认值。
当枚举类型的原始值类型指定为 Int 时,这个时候,就和其他语言的枚举的默认值分配一样。比如下面的例子,Bar,Baz, Boz 的原始值就是 0, 1, 2

enum Foo: Int {
case Bar, Baz, Boz
}

Foo.Boz.rawValue // 原始值为2

如果指定了一个枚举值的原始值,后面接下来的枚举值的原始值会自动 +1, 比如:

enum Foo1: Int {
case Bar = 2, Baz, Boz // Baz 原始值为 3, Boz 原始值为 4
}
Foo1.Boz.rawValue

原始值类型为字符串的时候,默认的值就是和枚举值的字符串一样,比如:

enum Foo2: String {
case Bar, Baz, Boz
}
Foo2.Bar.rawValue // 原始值为 Bar

可以显示设置原始值,那么没有设置原始值的,就使用默认值

enum Foo3: String {
case Bar = "Something"
case Baz = "Something else"
case Boz // 隐式设置为 "Boz"
}

Foo2.Boz.rawValue

Question5: Swift 2.1 Dictionary extension

问题链接

Q5链接地址

问题描述

楼主对 Dictionary 做了一个扩展,报错了。


extension Dictionary where Key: StringLiteralConvertible, Value: AnyObject {
mutating func auth() -> Dictionary {
self.updateValue("2.0", forKey: "api") //Cannot invoke 'updateValue' with an argument list of type '(String, forKey:String)'

return self
}
}

问题解答

  • 需要显式转换成 Value 和 Key
  • 因为 mutating 修饰了函数,所以不用显式返回self,updateValue直接就修改了字典了
extension Dictionary {
mutating func auth() {
updateValue("2.0" as! Value, forKey: "api" as! Key)
}
}

var dic: [String:AnyObject] = [:]

print(dic) // "[:]\n"
dic.auth()
print(dic) // "["api": 2.0]\n"
文章目录
  1. 1. Question1: A variable in generic class gets wrong type
    1. 1.1. 问题描述
    2. 1.2. 问题解答
  2. 2. Question2: Swift 2 Array Contains object?
    1. 2.1. 问题描述
    2. 2.2. 问题解答
  3. 3. Question3: Arrays of Int arrays. Storing duplicates in order only
    1. 3.1. 问题描述
    2. 3.2. 问题解答
  4. 4. Question4: swift enum case declaration synatx
    1. 4.1. 问题链接
    2. 4.2. 问题描述
    3. 4.3. 问题解答
  5. 5. Question5: Swift 2.1 Dictionary extension
    1. 5.1. 问题链接
    2. 5.2. 问题描述
    3. 5.3. 问题解答