文章目录
  1. 1. Q1: Unwrapping NSNumber works fine in iOS Simulator but unexpectedly found nil on iPhone
    1. 1.1. 问题描述
    2. 1.2. 解答
  2. 2. Q2:Why my code is working in playground but not in my project?
    1. 2.1. 问题描述
    2. 2.2. 问题解答
  3. 3. Q3:Failable initialisers and unbound instance vars
    1. 3.1. 问题描述
    2. 3.2. 问题解答
  4. 4. Q4:Read-only property
    1. 4.1. 问题链接
    2. 4.2. 问题描述
    3. 4.3. 代码实现
  5. 5. Q5: Why? insert a new element into array and it always crash!
    1. 5.1. 问题链接
    2. 5.2. 问题描述
    3. 5.3. 问题解答
  6. 6. Q6:binary operator ‘??’ cannot be applied to functions?
    1. 6.1. 问题链接
    2. 6.2. 问题描述
    3. 6.3. 问题解答
  7. 7. Q7: Filter array on type
    1. 7.1. 问题链接
    2. 7.2. 问题描述
    3. 7.3. 问题解答
    4. 7.4. 思考
  8. 8. Q8、Numbers in swift
    1. 8.1. 问题链接
    2. 8.2. 问题描述
    3. 8.3. 问题解答

本周整理问题如下:

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

Q1: Unwrapping NSNumber works fine in iOS Simulator but unexpectedly found nil on iPhone

Q1链接地址

问题描述

1
2
3
4
5
6
7
8
var stack = Array<String>()
stack.append("2.3")
let lastElement = stack.popLast()!
print("Popped last element: \(lastElement)")
let number = NSNumberFormatter().numberFromString(lastElement)
print("NSNumber gives us: \(lastElement)")
let doubleValue = number!.doubleValue
print("Double value of this element is: \(doubleValue)")

上述代码在Playground 以及iOS 模拟器中执行结果如下:

1
2
3
Popped last element: 2.3
NSNumber gives us: 2.3
Double value of this element is: 2.3

但是在真机里是这样的:

1
2
3
4
Popped last element: 2.3
NSNumber gives us: 2.3
fatal error: unexpectedly found nil while unwrapping an Optional value
(lldb)

所以提问者修改了一行代码

1
let doubleValue = number?.doubleValue

再次执行:

1
2
3
Popped last element: 2.3
NSNumber gives us: 2.3
Double value of this element is: nil

发现解包失败,值为 nil,那么问题出在那里呢?

解答

junkpile解答:

你的手机所处国家可能对小数分隔符的定义是一个逗号‘,’,而不是句号‘.’ 看到这个回答我也是醉了,最后提问者现身说法,确实他设置的德国是使用逗号作为小数分隔符的,所以解包失败。

junkpile 还给出了一个小建议:

在需要对数字字符串进行格式化的地方,比如输入数字的用户控件,你就需要显式的指定数字格式的本地化属性。反之在接收用户输入的数字时,你应该判断本地化属性,让一切尽在掌握中。

Q2:Why my code is working in playground but not in my project?

Q2链接地址

问题描述

使用 NSDateFormater 解析一个字符串日期,代码如下:

1
2
3
4
5
6
7
import UIKit
let lTs = String("Mon, 07 Dec 2015 3:58 pm EST")
let dateFormatter = NSDateFormatter()
dateFormatter.dateFormat = "EEE, dd MMM yyyy h:mm a zzz"
let lDate = dateFormatter.dateFromString(lTs)
print("\(lDate!)")
// 输出The result is "2015-12-07 20:58:00 +0000\n"

不过当提问者复制这段代码到项目中时(原来在playground),居然crash掉了,问题出在对lDate!解包过程。

问题解答

由苹果员工eskimo解答:

如果是对固定格式的字符串日期解析,你需要确定用户所在地域的local。

增加一行代码:

1
dateFormatter.locale = NSLocale(localeIdentifier: "en_US_POSIX")

最近 swift.gg 也有一篇文章详解了 NSDate 的正确使用姿势,包含了日期格式的一些知识点,有兴趣的同学可以看看:Swift 的 NSDate 初学者指南

Q3:Failable initialisers and unbound instance vars

Q3链接地址

问题描述

1
2
3
4
5
6
7
8
9
10
11
12
13
class Foo {
let a :Int
let b : Int
init?(name: String, m: Int, n: Int){
if name != "fistro" {
a = m
b = n
}else{
return nil
}
}
}

编译器报错在 else{} 内并未对 a,b 进行变量初始化,但是其实提问者是想说,既然是一个可失败的构造器,为什么一定要对 a,b进行赋值才能返回 nil 呢?

问题解答

Jessy提供了这么一个方法:

1
2
3
4
5
6
7
8
9
10
11
class Foo {
let a :Int
let b : Int
init?(name: String, m: Int, n: Int){
a = m
b = n
guard name == "fistro" else { return nil }
}
}

然后ChrisLattner大神(swift之父)站出来了,明确说了:

这是 Swift 2.1 版本的限制,在即将发布的 Swift 2.2 中已经修复啦。

这里给个stackoverflow类似的问题链接:http://stackoverflow.com/questions/26495586/best-practice-to-implement-a-failable-initializer-in-swift

Q4:Read-only property

问题链接

Q4链接地址

问题描述

日常项目开发中,我们会遇到一些 Access Control 的问题。譬如,我想要在类中实现一个属性对外是readonly(可读),对内是write and read(可读写)。那么如何实现是最好的呢?下面提供一个简单的思路。

代码实现

1
2
3
4
5
6
7
8
class MyClass{
// 对内可修改属性
private var gip: Bool = false
// 这是一个对外的可读属性
var gameInProgress: Bool {
return gip
}
}

不过这并不是一个最佳的选择,希望有知道的小伙伴可以私信咱们。

Q5: Why? insert a new element into array and it always crash!

问题链接

Q5链接地址

问题描述

以下代码会在第三行崩溃:

1
2
3
4
let oldNums: [Int] = [1, 2, 3, 4, 5 ,6 , 7, 8, 9, 10]
var newArray = oldNums[1..<4]
newArray.insert(99, atIndex: 0) // <-- crash here
newArray.insert(99, atIndex: 1) // <-- work very well

问题解答

先看下 newArray 的类型以及其他一些属性。

1
2
print(newArray.dynamicType) //->ArraySlice<Int>
print(newArray.indices) //->1..<4

显然 newArray 并不是一个数组,而是一个SliceArray,它的 StartIndex 是从 1 开始的,也就是通过 [1..<4] 截取的下标开始的,所以插入下标为 0 的位置,就会报错。

修改如下:

1
newArray.insert(99, atIndex: newArray.startIndex)

当然如果你还是偏执地想要从0开始 那么不妨重新搞一个数组喽,要知道 Array 有个sliceArray 的构造方法。所以改动如下:

1
2
3
4
5
6
7
8
9
10
11
12
let oldNums: [Int] = [1, 2, 3, 4, 5 ,6 , 7, 8, 9, 10]
var sliceArray = oldNums[1..<4]
var newArray = Array(oldNums[1..<4])
print(newArray.dynamicType) //array
print(newArray.indices) //0..<3 数组下标为0 1 2
newArray.insert(99, atIndex: 0) //在0位置插入一个元素
print(newArray.dynamicType) //array
print(newArray.indices) //0..<4 数组下标为0 1 2 3
newArray.insert(99, atIndex: 1) //在1位置插入一个元素
print(newArray.dynamicType)
print(newArray.indices) //0..<5 数组下标为0 1 2 3 4

Q6:binary operator ‘??’ cannot be applied to functions?

问题链接

Q6链接地址

问题描述

在 Playground 中运行以下代码:

1
2
3
4
5
6
func f1() {
return
}
var f2:(()->())?
let f3 = f2 ?? f1

直接报错:binary operator ‘??’ cannot be applied to operands of type ‘(() -> ())?’ and ‘() -> ()’

问题解答

其实Chris Lattner 大神说了:这就是个已知的 BUG ! 处理 autoclosure 和 function 时已经有人 report 过了。

不过呢OOPer还是提供了他自己的解决方式。

1
2
var f = f1
let f3 = f2 ?? f //不再报错

注意倘若把 var 变成 let 常量定义的话就报错了!

1
2
let f = f1
let f3 = f2 ?? f //报错

亲测在 swift2 中依然存在这个BUG!希望大家引起注意。

Q7: Filter array on type

问题链接

Q7链接地址

问题描述

请看下面问题:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 声明了一个类
class X{
var v:Int
init(_ v:Int){self.v = v}
}
// 继承自X
class X1:X{}
// 继承自X
class X2:X{}
var a :[X]
var a1:[X1]
// 注意这里有些是用X1初始化 有些是用X2初始化
// 但是a数组的类型切记是[X],之所以能这么干的原因在于
// X1 X2都是X的子类,严格意义上来说,说X1 X2是X也是OK的
a=[X1(1),X1(2),X2(3),X2(4),X1(5)]
a[0].v //输出1
// 错误来了
a1 = a.filter { $0 is X1 } // ERROR
a1[2].v

is 关键字就是用来判断某个实例的所属类,注意说的是实例。

报错:

Playground execution failed: playground78.swift:15:8: error: cannot invoke 'filter' with an argument list of type '(@noescape (X) throws -> Bool)'

然后提问者就想可能是自己闭包格式没写全,于是又这么改:

1
a1 = a.filter { (p:X) -> Bool in p is X1 }

不出意外,还是挂了。

问题解答

首先我们要明白 filter 方法的用法,filter 函数接收一个闭包作为筛选数组元素的过滤器,闭包一次处理一个元素,符合返回true,反之false。只有那些true的元素才会被append到结果数组中返回。更多filter函数请点击这里

现在来看看问题代码:

1
a1 = a.filter { $0 is X1 }

先看式子右边a.filter { $0 is X1 }传入了一个简化版闭包$0 is X1,其实就是作为筛选条件,一旦a中元素的类型为X1,即我们想要的元素,不过这里的元素类型依旧是X,而非X1,不难得出最后返回的是[X]结果数组; 在看式子左边a1,这货的类型是[X1]。原因找到了!就是因为[X1]≠[X]造成的,修改方式嘛,自然就是as喽。所以最后修改代码如下

1
2
a1 = a.filter{ $0 is X1} as! [X1]
a1.map{print("\($0.v)")}

其实吧,我更推荐第二种方式,使用 flatMap 实现:

1
a1 = a.flatMap{$0 as? X1}

我们对a数组中的元素进行遍历,每个都执行$0 as? X1类型转换,倘若成功就将元素转换为X1类型,失败则返回nil,最后flapMap会为我们剔除nil值。

思考

现在有个问题:倘若我们使用面向对象编程呢?上述两种方法还适用吗?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
protocol X{
var v:Int{get}
}
// 继承自X
class X1:X{
var v:Int
init(_ v:Int){self.v = v}
}
// 继承自X
class X2:X{
var v:Int
init(_ v:Int){self.v = v}
}
var a :[X]
var a1:[X1]
// 注意这里有些是用X1初始化 有些是用X2初始化
a=[X1(1),X1(2),X2(3),X2(4),X1(5)]
a[0].v
a1 = a.filter{ $0 is X1} as! [X1] //报错:fatal error: array element cannot be bridged to Objective-C
var a2 = a.flatMap{ $0 as? X1}

看来只有flatMap依旧坚挺!如果想要使用filter的话,可以这么实现:

1
(a.filter { $0 is XValue }).map { $0 as! XValue }

画蛇添足的赶脚。有木有更好的方法呢?报错说我们没有桥接到OC,让我想到了@objc,于是我尝试了下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@objc protocol X{
var v:Int{get}
}
// 继承自X
class X1:X{
@objc var v:Int
init(_ v:Int){self.v = v}
}
// 继承自X
class X2:X{
@objc var v:Int
init(_ v:Int){self.v = v}
}
var a :[X]
var a1:[X1]
// 注意这里有些是用X1初始化 有些是用X2初始化
a=[X1(1),X1(2),X2(3),X2(4),X1(5)]
a[0].v
var a2 = a.filter{ $0 is X1} as! [X1]

这样是ok的。

Q8、Numbers in swift

问题链接

Q8链接地址

问题描述

提问者吐槽,以下代码会出现编译错误:

1
2
3
var x: Int = 2
var y: Double = 2.0
var z: Double = y / x

然后又表示,下面代码也是错的,让人难以接受这是 Swift 干的事情:

1
2
3
4
5
var k: Double
k = Double(x)
k = Double(x.value)
k = (Double) x
k = (Double) x.value

问题解答

OOPer 大神在回帖中写到,其实k = Double(x) 是可以执行的, Swift 是强调强类型的语言,不过也提供了不同类型的转换方式。对不同类型进行运算, Swift 是不允许的。以下代码是可以运行的:

1
2
3
var x: Int = 2
var y: Double = 2.0
var z: Double = y / Double(x)

另外,回帖中还提供了一种使用协议和代码的方式来解决这个问题:

1
2
3
4
5
6
7
8
protocol DoubleBOWEMOJI {
var Double: Swift.Double {get}
}
extension Int: DoubleBOWEMOJI {
var Double: Swift.Double {return Swift.Double(self)}
}
var z = y / x.Double
文章目录
  1. 1. Q1: Unwrapping NSNumber works fine in iOS Simulator but unexpectedly found nil on iPhone
    1. 1.1. 问题描述
    2. 1.2. 解答
  2. 2. Q2:Why my code is working in playground but not in my project?
    1. 2.1. 问题描述
    2. 2.2. 问题解答
  3. 3. Q3:Failable initialisers and unbound instance vars
    1. 3.1. 问题描述
    2. 3.2. 问题解答
  4. 4. Q4:Read-only property
    1. 4.1. 问题链接
    2. 4.2. 问题描述
    3. 4.3. 代码实现
  5. 5. Q5: Why? insert a new element into array and it always crash!
    1. 5.1. 问题链接
    2. 5.2. 问题描述
    3. 5.3. 问题解答
  6. 6. Q6:binary operator ‘??’ cannot be applied to functions?
    1. 6.1. 问题链接
    2. 6.2. 问题描述
    3. 6.3. 问题解答
  7. 7. Q7: Filter array on type
    1. 7.1. 问题链接
    2. 7.2. 问题描述
    3. 7.3. 问题解答
    4. 7.4. 思考
  8. 8. Q8、Numbers in swift
    1. 8.1. 问题链接
    2. 8.2. 问题描述
    3. 8.3. 问题解答