文章目录
  1. 1. Question1: Hiding Hashable in Swift
    1. 1.1. 问题描述
    2. 1.2. 问题解答
  2. 2. Question2: map function argument type
    1. 2.1. 问题描述
    2. 2.2. 问题解答
  3. 3. Question3:confusion over initialisation in swift
    1. 3.1. 问题解答
  4. 4. Question4: Prevent lazy initialization of static var
    1. 4.1. 问题描述
    2. 4.2. 问题解答
  5. 5. Question5: Can I create a class that subclasses from Array?
    1. 5.1. 问题描述
    2. 5.2. 问题解答

作者:shanks

本周共整理了 5 个问题。涉及问题有:协议作为字典key问题,map问题,构造器自动继承问题,静态变量延迟初始化问题和Array的继承问题。

本周整理问题如下:

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

Question1: Hiding Hashable in Swift

点击打开问题原地址

问题描述

]楼主的问题是,能不能定义一个字典,类型是:[Hashable: Any],其中Hashable是一个协议。显然是不行的,楼主尝试了一下采用折中的办法,定义了一个结构体,遵从协议Hashable, 内部包含了Any类型的值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
struct Hash: Hashable {
private let value: Any
private let equals: Hash -> Bool
init<H: Hashable>(_ h: H) {
self.value = h
self.hashValue = h.hashValue
self.equals = { ($0.value as! H) == h }
}
let hashValue: Int
}
func ==(lhs: Hash, rhs: Hash) -> Bool {
return lhs.equals(rhs)
}

问题解答

Swift 中不支持使用协议来作为字典的key,所以以下代码会报错:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
struct HashyStruct : Equatable, Hashable {
let smallNumber: UInt16
var hashValue: Int {
return Int(smallNumber)
}
}
func ==(lhs: HashyStruct, rhs: HashyStruct) -> Bool {
return lhs.smallNumber == rhs.smallNumber
}
class HashyClass : Equatable, Hashable {
let number: UInt64
init(number: UInt64) {
self.number = number
}
var hashValue: Int {
return Int(number)
}
}
func ==(lhs: HashyClass, rhs: HashyClass) -> Bool {
return lhs.number == rhs.number
}
let anyHashable: [Hashable:Any] = [HashyStruct(smallNumber: 5) : "struct", HashyClass(number: 0x12345678):"class"]

从另外一个角度想,即使 Swift 支持这样的语法,那么就会出现一个问题,如果判断2个key是否一致?因为一致的情况下,赋值新值就会产生覆盖行为。这样就要进行向下转型成具体的结构体或者类进行比较。显然是不合理的。

Question2: map function argument type

点击打开问题原地址

问题描述

以下代码在定义view1的时候会报错。报错信息:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import UIKit
func foo() {
let views1 = (1...2).map { _ in
let v = UIView()
return v
}
let views2 = (1...2).map { _ in
return UIView()
}
}
error: cannot invoke 'map' with an argument list of type '(@noescape (Int) throws -> _)'
let views1 = (1...2).map { _ in
^
note: expected an argument list of type '(@noescape (Self.Generator.Element) throws -> T)'
let views1 = (1...2).map { _ in

问题解答

定义一个变量来返回UIView实例,map方法就不能推断出返回的类型了。需要显式定义view1的类型,就能编译通过了:

1
2
3
4
5
6
7
8
9
10
11
12
import UIKit
func foo() {
let views1: [UIView] = (1...2).map { _ in
let v = UIView()
return v
}
let views2 = (1...2).map { _ in
return UIView()
}
}

类型推断在 Swift 中,是一个很重要的特性。但是目前推断能力还不是很强。推荐大家都显式地定义类型。避免一些莫名其妙的错误出现。

Question3:confusion over initialisation in swift

点击打开问题原地址

这个问题是对官方文档关于子类会自动继承父类的构造器知识点的疑惑。下面是官方文档的例子,ShoppingListItem由于满足所有属性都已经赋值,且没有构造器,所以自动继承父类RecipeIngredient,其中还包含一个自动继承的无参构造器init()。见下图:

楼主的疑问是,当创建一个ShoppingListItem实例时候,自动继承的构造器里面,会运行super.init(name: name), 这条语句的super, 是访问的RecipeIngredientinit(name: name), 还是Foodinit(name: name)呢?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
class Food {
var name: String
init(name: String){
self.name = name
}
convenience init() {
self.init(name: "[unnamed]")
}
}
class RecipeIngredient: Food{
var quantity: Int
init(name: String, quantity: Int){
self.quantity = quantity
super.init(name: name)
}
override convenience init(name: String){
self.init(name: name, quantity: 1)
}
}
class ShoppingListItem: RecipeIngredient {
var purchased = false
var description: String {
var output = "\(quantity) X \(name)"
output += purchased ? " ✔" : " ✘"
return output
}
}
let ingredientThree = ShoppingListItem(name: "apple", quantity: 10)
ingredientThree.quantity

问题解答

这里的规则其实可以这样解释:ShoppingListItem 本身没有构造器,只是借用了父类RecipeIngredient的构造器。也就是说,创建一个ShoppingListItem的实例时候,调用的是父类RecipeIngredient的构造器。既然是调用了RecipeIngredient的构造器,那么这里的super就是指的是Food

可以假设superRecipeIngredient, 那么调用的super.init(name: name)会把quantity重置为 1。 不满足 Swift 中构造器的两段式构造过程的规则。

楼下的回答没有直接回答楼主的疑问,但是通过打印出构造过程的输出,可以清晰的看出来,楼主问题的答案了。

Question4: Prevent lazy initialization of static var

点击打开问题原地址

问题描述

类中的静态变量,为什么天生就有lazy属性,也就是说,在第一次用的时候,才去做初始化。例如以下的代码,在第一次使用Features.feature1的时候,才去生成一个Feature类的实例。楼主问,能不能在这个类第一次使用到的时候,去初始化静态变量呢?

1
2
3
4
5
6
7
class Features
{
static var feature1 = Feature()
static var feature2 = Feature()
}
Features.feature1 //用到的时候才去生成实例

问题解答

楼下大神的回答很精彩,可以反过来想一下,如果静态变量不是第一次用到时候去初始化,那什么时候去初始化呢?App 启动时候?类库被加载时候?还是第一个实例被初始化的时候呢?即使可以在以上情况下可行,那么是先初始化feature1,还是feature2呢?如果静态变量是存在不同的扩展内的呢?那么应该怎么做?这些问题显然不能用语法很好的表达出来。
可以参看 Swift 刚出生那段时候的官方blog 的一篇文章:Files and Initialization ,很显然,在第一次用到的时候,才去初始化静态变量。是最好的方式。

Question5: Can I create a class that subclasses from Array?

点击打开问题原地址

问题描述

楼主想继承 Swift 中的 Array,来实现一些新的数组方法。发现会报错。但是继承 oc 中 NSArray 是可以的。这是为什么呢?

1
2
3
4
5
import Foundation
class Test : NSArray {}
class Test : Array {}
class Test : [AnyObject] {}

问题解答

实际情况是,Swift 不太推崇面向对象编程了。。虽然保留了类的特性,但是很多数据类型,都是定义为了结构体,也就是值类型,包括Array。而结构体,是不能继承的。但是OC 中的NSArray显然是一个类,那就可以继承了。
那么应该如何做到楼主想要实现的功能呢?答案是用扩展,为 Array 做一个扩展,就可以实现新的数组方法。

1
2
3
4
extension Array
{
func randomObject() -> Element { return self[ Int( arc4random_uniform( UInt32( self.count ) ) ) ] }
}

扩展和协议,是面向协议编程的2个重要的基础,大家如有兴趣可以去看看去年的 WWDC 关于面向协议编程 Session,会了解到更多的关于这方面的细节。

文章目录
  1. 1. Question1: Hiding Hashable in Swift
    1. 1.1. 问题描述
    2. 1.2. 问题解答
  2. 2. Question2: map function argument type
    1. 2.1. 问题描述
    2. 2.2. 问题解答
  3. 3. Question3:confusion over initialisation in swift
    1. 3.1. 问题解答
  4. 4. Question4: Prevent lazy initialization of static var
    1. 4.1. 问题描述
    2. 4.2. 问题解答
  5. 5. Question5: Can I create a class that subclasses from Array?
    1. 5.1. 问题描述
    2. 5.2. 问题解答