作者: shanks
本周共整理了 5 个问题。涉及问题有:协议作为字典key问题,map问题,构造器自动继承问题,静态变量延迟初始化问题和 Array
的继承问题。
本周整理问题如下:
对应的代码都放到了 github 上,有兴趣的同学可以下载下来研究: 点击下载
点击打开问题原地址
]楼主的问题是,能不能定义一个字典,类型是: [Hashable: Any]
,其中 Hashable
是一个协议。显然是不行的,楼主尝试了一下采用折中的办法,定义了一个结构体,遵从协议 Hashable
, 内部包含了 Any
类型的值。
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,所以以下代码会报错:
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是否一致?因为一致的情况下,赋值新值就会产生覆盖行为。这样就要进行向下转型成具体的结构体或者类进行比较。显然是不合理的。
点击打开问题原地址
以下代码在定义 view1
的时候会报错。报错信息:
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的类型,就能编译通过了:
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 中,是一个很重要的特性。但是目前推断能力还不是很强。推荐大家都显式地定义类型。避免一些莫名其妙的错误出现。
点击打开问题原地址
这个问题是对官方文档关于子类会自动继承父类的构造器知识点的疑惑。下面是官方文档的例子, ShoppingListItem
由于满足所有属性都已经赋值,且没有构造器,所以自动继承父类 RecipeIngredient
,其中还包含一个自动继承的无参构造器 init()
。见下图:
楼主的疑问是,当创建一个 ShoppingListItem
实例时候,自动继承的构造器里面,会运行 super.init(name: name)
, 这条语句的 super
, 是访问的 RecipeIngredient
的 init(name: name)
, 还是 Food
的 init(name: name)
呢?
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
。
可以假设 super
是 RecipeIngredient
, 那么调用的 super.init(name: name)
会把 quantity
重置为 1。 不满足 Swift 中构造器的 两段式构造过程 的规则。
楼下的回答没有直接回答楼主的疑问,但是通过打印出构造过程的输出,可以清晰的看出来,楼主问题的答案了。
点击打开问题原地址
类中的静态变量,为什么天生就有 lazy
属性,也就是说,在第一次用的时候,才去做初始化。例如以下的代码,在第一次使用 Features.feature1
的时候,才去生成一个 Feature
类的实例。楼主问,能不能在这个类第一次使用到的时候,去初始化静态变量呢?
class Features
{
static var feature1 = Feature()
static var feature2 = Feature()
}
Features.feature1 //用到的时候才去生成实例
楼下大神的回答很精彩,可以反过来想一下,如果静态变量不是第一次用到时候去初始化,那什么时候去初始化呢?App 启动时候?类库被加载时候?还是第一个实例被初始化的时候呢?即使可以在以上情况下可行,那么是先初始化 feature1
,还是 feature2
呢?如果静态变量是存在不同的扩展内的呢?那么应该怎么做?这些问题显然不能用语法很好的表达出来。
可以参看 Swift 刚出生那段时候的官方blog 的一篇文章: Files and Initialization ,很显然,在第一次用到的时候,才去初始化静态变量。是最好的方式。
点击打开问题原地址
楼主想继承 Swift 中的 Array,来实现一些新的数组方法。发现会报错。但是继承 oc 中 NSArray 是可以的。这是为什么呢?
import Foundation
class Test : NSArray {}
class Test : Array {}
class Test : [AnyObject] {}
实际情况是,Swift 不太推崇面向对象编程了。。虽然保留了类的特性,但是很多数据类型,都是定义为了结构体,也就是值类型,包括 Array
。而结构体,是不能继承的。但是OC 中的NSArray显然是一个类,那就可以继承了。
那么应该如何做到楼主想要实现的功能呢?答案是用扩展,为 Array 做一个扩展,就可以实现新的数组方法。
extension Array
{
func randomObject() -> Element { return self[ Int( arc4random_uniform( UInt32( self.count ) ) ) ] }
}
扩展和协议,是面向协议编程的2个重要的基础,大家如有兴趣可以去看看去年的 WWDC 关于 面向协议编程 Session ,会了解到更多的关于这方面的细节。