作者: shanks
本周共整理了 5 个问题。涉及问题有:浅拷贝问题,Any类型问题,数组初始化问题,泛型类型问题和有继承关系的协议实现问题。
本周整理问题如下:
对应的代码都放到了 github 上,有兴趣的同学可以下载下来研究: 点击下载
点击打开问题原地址
此问题是与 Swift 中的值类型和引用类型相关的。楼主提问:以下代码中,数组之间的赋值不是浅拷贝(arr2 只是指向 arr1,arr1内部值变动,arr2访问也会跟着变化)的?
class Person
{
var name:String = ""
}
var arr1:[Person] = [Person]()
let p1 = Person()
p1.name = "Umair"
let p2 = Person()
p2.name = "Ali"
arr1.append(p1)
arr1.append(p2)
var arr2 = arr1
print("/(arr1.count)") //"2/n"
print("/(arr2.count)") //"2/n"
arr1.removeFirst()
print("/(arr1.count)") //"1/n"
print("/(arr2.count)") //"2/n"
事实上,Array 在 Swift 中,是作为值类型存在的。而值类型的赋值,默认是深拷贝的。只有类在 Swift 中是浅拷贝的。这篇 文章 可以看到更多的细节。值类型和引用类型作为 Swift 中非常基础并且重要的概念,理解了值类型,写 Swift 代码就会更加清晰。
如果要在 Swift 中实现浅拷贝数组咋办?那就要借助一个类来实现了,用一个独立的类把数组包装起来,拷贝这个类的实例是浅拷贝的。见以下代码:
class Person
{
var name: String
init(_ name: String) {
self.name = name
}
}
let p1 = Person("Umair")
let p2 = Person("Ali")
class Container {
var people = [Person]()
init(people: [Person]) {
self.people = people
}
}
let arr1 = Container(people: [p1, p2])
let arr2 = arr1
print(arr1.people)
print(arr2.people)
arr1.people.removeFirst()
print(arr1.people)
print(arr2.people)
点击打开问题原地址
楼主定义了一个类的方法 on
,参数中包含一个含有 Any?
参数的闭包。见以下代码:
mutating func on(eventName:String, action:((Any?)->())) {
}
调用此方法时,以下代码会报错:
appSessionHadler.on("login") { (weak data: String?) in
//...
}
以下是正确的调用方式:
appSessionHadler.on("login") { (weak data: Any?) in
//...
}
那么问题来了,既然Any?是表示所有的类型,那么应该也包括了String?,在第一次调用时,传入String?为什么会报错?
此问题和问题5有点类似,Any是可以包含所有类型的值,但是这仅仅是表示,内部数据可以是任何值。在定义方法参数指定为Any?后,调用这个方法,也必须传入Any?的变量作为入参。所以以上把data变成String?当然会报错。此问题更好的方法是使用泛型来解决,可以在调用时候指定参数的类型:
mutating func on<T>(eventName:String, action:((T?)->()))
appSessionHadler.on("login") { (weak data: String?) in
//...
}
点击打开问题原地址
可以用以下代码定义一个数组,然后插入一个元素到头部:
var array: [Int] = Array(1...24)
array.insert(9999, atIndex: 0)
楼主的问题是,能否把这2句代码写成一句代码,类似以下这样的代码。。虽然是错的,想法倒是挺好的:
var array: [Int] = Array(9999...9999,1...24)
楼下提供了以下几种方式任君选择:
let arr = [1...5, 11...15].reduce([]) { $0.0 + Array($0.1) }
arr
var arr1 = Array([1...5, 11...15].flatten())
arr1
let arr2 = Array(10 ... 14) + Array(1 ... 24)
arr2
let arr3 = [10 ... 14, 1 ... 4].flatMap { $0 }
arr3
点击打开问题原地址
楼主定义了一个泛型函数,然后调用时候发现一个奇怪问题,第一次调用还好,数组元素和第二个参数的类型都是Int的。而第二次调用时,数组元素类型是字符串,而第二个参数类型是Double,为神马不会报错?
import Foundation
func findAll<T: Equatable>(arr: [T], _ elem: T) -> [Int] {
var indexesArr = [Int]()
var counter = 0
for i in arr {
if i == elem {
indexesArr.append(counter)
}
counter++
}
return indexesArr
}
findAll([5, 3, 7, 3, 9], 3)
findAll(["a", "b", "c", "b", "d"], 2.0) //这样调用也可以??
问题出在 import Foundation
上面,引入了 Foundation
以后,数组会映射为 [AnyObject]
,而值类型会映射了对应的 Foundation
类型(比如 NSString
, NSNumber
等),也就是AnyObject了。这样其实,第二个调用对应的函数原型是: func findAll(arr: [AnyObject], _ elem: AnyObject) -> [Int]
,传入 [String]
和 Double
是可以通过的。
可以把 import Foundation
去掉,就可以查看到错误。
那么正确的定义应该怎么样呢?二楼给出了答案:
public protocol NotAnyObject {}
extension Int: NotAnyObject {}
extension String: NotAnyObject {}
extension Double: NotAnyObject {}
func findAll<T: Equatable where T: NotAnyObject>(arr: [T], _ elem: T) -> [Int] {
var indexesArr = [Int]()
var counter = 0
for i in arr {
if i == elem {
indexesArr.append(counter)
}
counter++
}
return indexesArr
}
findAll([5, 3, 7, 3, 9], 3)
findAll(["a", "b", "c", "b", "d"], 2.0) //错误
另外增加了一个空的协议,让需要用到的类型去扩展此协议,这样做的目的就是强制把泛型限制在一致的类型上去。而不能使用不同的类型。
点击打开问题原地址
以下代码会报错,代码中, 类 Y 遵循协议 X,赋值时候,把A的子类B的实例赋值给了协议X中的存储属性。楼主认为既然A和B有继承关系,那么为啥会报错了?
protocol A {
}
class B : A {
var foo = "foo"
}
protocol X {
var someA : A {get set}
}
class Y : X { //错误: Type Y does not conform to protocol X
var someA = B()
}
编译器只能根据协议X的情况,去要求someA必须是类A的一个实例,B是继承自A的,它有自己的存储属性foo。当然不满足协议X了。如果真的需要声明一个B的实例,那么必须显式指定someA类型为A。见以下代码:
class Y : X {
var someA: A = B()
}
以上的代码虽然可以运行,但是someA已经不能调用B的存储属性了。如果需要调用,必须做一下显式转换: someA as! B
。一般使用as语句的地方,目前的主流做法都不太推荐。楼下举了一个例子,某个协议要求一个存储属性去存储一辆车的信息,这个时候某人有一辆保时捷,赋值给了这个存储属性,然后另外一个人有一辆马自达,同样也可以赋值给这个存储属性,但是我们看不出来这车是保时捷还是马自达,其实他们差别还是蛮大的。
关于这个话题,有以下2篇文章可以进一步解答: