问题
1)柯里化,通过柯里化,改造target-action,因为selector只能使用字符串,在编译时无法发现错误,并且不利重构。
2)framework和命名空间之间的关系,写个demo验证一下。
3)对象初始化的过程,如何解决OC中潜在的初始化bug。
http://stackoverflow.com/questions/8056188/should-i-refer-to-self-property-in-the-init-method-with-arc
https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/MemoryMgmt/Articles/mmPractical.html
4)swift能像OC一样动态的分派方法吗? 参见:动态类型和多方法
- lazy方法的实现。
func lazy<S : SequenceType>(s: S) -> LazySequence<S>
func lazy<S : CollectionType where S.Index : RandomAccessIndexType>(s: S)
-> LazyRandomAccessCollection<S>
func lazy<S : CollectionType where S.Index : BidirectionalIndexType>(s: S)
-> LazyBidirectionalCollection<S>
func lazy<S : CollectionType where S.Index : ForwardIndexType>(s: S)
-> LazyForwardCollection<S>”
6) 多重Optional具体在库中使用的场景是什么?
7)swift中链表的时间,enum,indirect
8)实例方法的动态调用测试结果和书上的不一样
9)一个App的启动流程是怎样的?
10)GCD 和延时调用, 这一节的封装没看懂。
11)Comparison of Objective-C Enumeration Techniques
https://www.mikeash.com/pyblog/friday-qa-2010-04-09-comparison-of-objective-c-enumeration-techniques.html
12)R.swift 和 SwiftGen管理资源
13)swift的异常机制
1. Swift 新元素
柯里化(currying)
返回一个函数,
柯里化是量产类似方法的好办法,通过柯里化一个方法模板,可以避免写出很多重复方法。
Instance Methods are Curried Functions in Swift
https://oleb.net/blog/2014/07/swift-instance-methods-curried-functions/?utm_campaign=iOS_Dev_Weekly_Issue_157&utm_medium=email&utm_source=iOS%252BDev%252BWeekly
将 protocol 的方法声明为 mutating
写给别人使用的结构体中需要考虑添加mutating说明,
在使用 class 来实现带有 mutating 的方法的接口时,具体实现的前面是不需要加 mutating 修饰的,因为 class 可以随意更改自己的成员变量。所以说在接口里用 mutating 修饰方法,对于 class 的实现是完全透明,可以当作不存在的。
Sequence
具体查看一下Array的sequence实现。
GeneratorType 实现 next()-> Element? 的方法。每次调用都返回下一个元素,不能重置。
SequenceType包含一个Generator的对象。
map, filter, reduce方法是在sequence 基础上,用方法扩展实现的。它们只要能顺序编辑容器类型就可以了。
多元组 (Tuple)
交换值的新写法
返回多个值
@autoclosure和??
1)阅读更清晰
2)节省开销,如果不是闭包,在defaultValue传值的时候会被求值一次,这个是没有必要地。
Optional Chaining
optional chaining 返回的一定是一个optional值
操作符
fun的参数修饰
参数是let属性,不能当做var来使用
如果使用inout,传参时需要用&的方式。
字面量转换
特定的类型实现这些协议,将这些字面量转化为类型的值
下标
方法嵌套
方法现在是一等公民,可以当做参数,返回值,也可以在方法中定义方法。这些子方法只在当前方法内有效。
命名空间
了解framework
Any和Any Object
id和Any object的区别
typeelias和泛型接口
可变参数函数
初始化方法的顺序
保证安全的初始化。
Designated,Convenience 和 Required
在 Objective-C 中,init 方法是非常不安全的:没有人能保证 init 只被调用一次,也没有人保证在初始化方法调用以后实例的各个变量都完成初始化,甚至如果在初始化里使用属性进行设置的话,还可能会造成各种问题,虽然 Apple 也明确说明了不应该在 init 中使用属性来访问,但是这并不是编译器强制的,因此还是会有很多开发者犯这样的错误。
所以 Swift 有了超级严格的初始化方法。一方面,Swift 强化了 designated 初始化方法的地位。Swift 中不加修饰的 init 方法都需要在方法中保证所有非 Optional 的实例变量被赋值初始化,而在子类中也强制 (显式或者隐式地) 调用 super 版本的 designated 初始化,所以无论如何走何种路径,被初始化的对象总是可以完成
因此进行一下总结,可以看到初始化方法永远遵循以下两个原则:
- 初始化路径必须保证对象完全初始化,这可以通过调用本类型的 designated 初始化方法来得到保证;
- 子类的 designated 初始化方法必须调用父类的 designated 方法,以保证父类也完成初始化。
初始化返回 nil
所有的结果都将是 Int? 类型,通过 Optional Binding,我们就能知道初始化是否成功,并安全地使用它们了。我们在这类初始化方法中还可以对 self 进行赋值,也算是 init 方法里的特权之一。
protocol 组合
Any的定义 protocol<>
AnyObject的定义?
除了可以方便地表达空接口这一概念以外,protocol 的组合相比于新创建一个接口的最大区别就在于其匿名性。
static 和 class
在类型的定义属性时,只能使用static,
在定义类型的方法时,enum,struct 只能用static, 类里面可以使用static,也可以使用class
多类型和容器
在容器类使用protocol,这样就能够存储不同的
default 参数
默认参数没有只能是最后一个参数的限制,因为swift的方法调用可以指定调用时不传哪些参数
func NSLocalizedString(key: String, tableName: String? = default, bundle: NSBundle = default, value: String = default, comment: String) -> String
func assert(@autoclosure condition: () -> Bool, @autoclosure _ message: () -> String = default, file: StaticString = default, line: UWord = default)
正则表达式
模式匹配
... 和 ..<
“不难发现,其实这几个方法都是支持泛型的。除了我们常用的输入 Int 或者 Double,返回一个 Range 以外,这个操作符还有一个接受 Comparable 的输入,并返回 ClosedInterval 或 HalfOpenInterval 的重载。在 Swift 中,除了数字以外另一个实现了 Comparable 的基本类型就是 String。也就是说,我们可以通过 ... 或者 ..< 来连接两个字符串。一个常见的使用场景就是检查某个字符是否是合法的字符。比如想确认一个单词里的全部字符都是小写英文字母的话,可以这么做:”
AnyClass,元类型和 .self
typealias AnyClass = AnyObject.Type
其实在 Swift 中,.self 可以用在类型后面取得类型本身,也可以用在某个实例后面取得这个实例本身。前一种方法可以用来获得一个表示该类型的值,这在某些时候会很有用;而后者因为拿到的实例本身,所以暂时似乎没有太多需要这么使用的案例。
self.tableView.registerClass( UITableViewCell.self, forCellReuseIdentifier: "myCell”)
元编程,
通过配置文件初始化
接口和类方法中的 Self
“但是在这种情况下,Self 不仅指代的是实现该接口的类型本身,也包括了这个类型的子类。从概念上来说,Self 十分简单,但是实际实现一个这样的方法却稍微要转个弯”
动态类型和多方法
Swift 中我们虽然可以通过 dynamicType 来获取一个对象的动态类型 (也就是运行时的实际类型,而非代码指定或编译器看到的类型)。但是在使用中,Swift 现在却是不支持多方法的,也就是说,不能根据对象在动态时的类型进行合适的重载方法调用。”
(编译时确定调用哪个方法?)
属性观察
初始化方法对属性的设定,以及在 willSet 和 didSet 中对属性的再次设定都不会再次触发属性观察的调用,一般来说这会是你所需要的行为,可以放心使用能够。
属性观察 (Property Observers) 是 Swift 中一个很特殊的特性,利用属性观察我们可以在当前类型内监视对于属性的设定,并作出一些响应。Swift 中为我们提供了两个属性观察的方法,它们分别是 willSet 和 didSet。”
final
final 关键字可以用在 class,func 或者 var 前面进行修饰,表示不允许对该内容进行继承或者重写操作”
“一般来说,不希望被继承和重写会有这几种情况:
类或者方法的功能确实已经完备了”
“子类继承和修改是一件危险的事情”
“为了父类中某些代码一定会被执行”
lazy 修饰符和 lazy 方法
lazy属性的实现, 代码块(闭包)的调用 {}(), lazy方法的实现。
func lazy<S : SequenceType>(s: S) -> LazySequence<S> func lazy<S : CollectionType where S.Index : RandomAccessIndexType>(s: S) -> LazyRandomAccessCollection<S> func lazy<S : CollectionType where S.Index : BidirectionalIndexType>(s: S) -> LazyBidirectionalCollection<S> func lazy<S : CollectionType where S.Index : ForwardIndexType>(s: S) -> LazyForwardCollection<S>
Reflection 和 Mirror
swift这个功能还是很弱了,只能读取值,不能设置值,不能跟OC的运行时相比
隐式解包 Optional
除了在IB中,其它地方慎用。
多重 Optional
具体在库使用的场景呢?
Optional Map
Protocol Extension
协议扩展里实现了某个方法,类实现里也实现了某个方法。
整理一下相关的规则的话:
如果类型推断得到的是实际的类型
那么类型中的实现将被调用;如果类型中没有实现的话,那么接口扩展中的默认实现将被使用
如果类型推断得到的是接口,而不是实际类型
并且方法在接口中进行了定义,那么类型中的实现将被调用;如果类型中没有实现,那么接口扩展中的默认实现被使用
否则 (也就是方法没有在接口中定义),扩展中的默认实现将被调用”
where 和模式匹配
1)应用于switch case, if, for,可以使用if代替
2)应用于泛型限制
对self做进一步限制,如果不遵守Comparable协议,就不能调用sort方法,在编译时限制
extension SequenceType where Self.Generator.Element : Comparable {
public func sort() -> [Self.Generator.Element]
}
indirect 和嵌套 enum
2. 从 Objective-C/C 到 Swift
Selector
Swift 里对应原来 SEL 的类型是一个叫做 Selector 的结构体。
let someMethod = #selector(callMe) let anotherMethod = #selector(callMeWithParam(_:))
最后需要注意的是,selector 其实是 Objective-C runtime 的概念,如果你的 selector 对应的方法只在 Swift 中可见的话 (也就是说它是一个 Swift 中的 private 方法),在调用这个 selector 时你会遇到一个 unrecognized selector 错误,正确的做法是在 private 前面加上 @objc 关键字,这样运行时就能找到对应的方法了。”
实例方法的动态调用
Swift 中可以直接用 Type.instanceMethod 的语法来生成一个可以柯里化的方法。
单例
“在 Swift 1.2 以及之后,如果没有特别的需求,我们推荐使用下面这样的方式来写一个单例:
class MyManager { static let sharedInstance = MyManager() private init() {} }
这种写法不仅简洁,而且保证了单例的独一无二。在初始化类变量的时候,Apple 将会把这个初始化包装在一次 swift_once_block_invoke 中,以保证它的唯一性。另外,我们在这个类型中加入了一个私有的初始化方法,来覆盖默认的公开初始化方法,这让项目中的其他地方不能够通过 init 来生成自己的 MyManager 实例,也保证了类型单例的唯一性。如果你需要的是类似 defaultManager 的形式的单例 (也就是说这个类的使用者可以创建自己的实例) 的话,可以去掉这个私有的 init 方法。”
条件编译
swift不支持宏,编译标记需要在 swift compiler -> Custom Flags 加上 -D 标记
编译标记
//MARK: //TODO: //FIXME:
暂时还不支持
//WARNING:
@UIApplicationMain
“这个标签做的事情就是将被标注的类作为委托,去创建一个 UIApplication 并启动整个程序。在编译的时候,编译器将寻找这个标记的类,并自动插入像 main 函数这样的模板代码。我们可以试试看把 @UIApplicationMain 去掉会怎么样:
Undefined symbols _main
说明找不到 main 函数了。
在一般情况下,我们并不需要对这个标签做任何修改,但是当我们如果想要使用 UIApplication 的子类而不是它本身的话,我们就需要对这部分内容 “做点手脚” 了。
刚才说到,其实 Swift 的 app 也是需要 main 函数的,只不过默认情况下是 @UIApplicationMain 帮助我们自动生成了而已。和 C 系语言的 main.c 或者 main.m 文件一样,Swift 项目也可以有一个“名为 main.swift 特殊的文件。在这个文件中,我们不需要定义作用域,而可以直接书写代码。这个文件中的代码将作为 main 函数来执行。比如我们在删除 @UIApplicationMain 后,在项目中添加一个 main.swift 文件,然后加上这样的代码:
UIApplicationMain(Process.argc, Process.unsafeArgv, nil,
NSStringFromClass(AppDelegate))
现在编译运行,就不会再出现错误了。”
(监事应用的事件)
@objc 和 dynamic
swift和OC之间的互操作
swift调用OC
通过添加 {product-module-name}-Bridging-Header.h 文件,并在其中填写想要使用的头文件名称,我们就可以很容易地在 Swift 中使用 Objective-C 代码了
“Objective-C 和 Swift 在底层使用的是两套完全不同的机制,Cocoa 中的 Objective-C 对象是基于运行时的,它从骨子里遵循了 KVC (Key-Value Coding,通过类似字典的方式存储对象信息) 以及动态派发 (Dynamic Dispatch,在运行调用时再决定实际调用的具体实现)。而 Swift 为了追求性能,如果没有特殊需要的话,是不会在运行时再来决定这些的。也就是说,Swift 类型的成员或者方法在编译时就已经决定,而运行时便不再需要经过一次查找,而可以直接使用。”
“显而易见,这带来的问题是如果我们要使用 Objective-C 的代码或者特性来调用纯 Swift 的类型时候,我们会因为找不到所需要的这些运行时信息,而导致失败。解决起来也很简单,在 Swift 类型文件中,我们可以将需要暴露给 Objective-C 使用的任何地方 (包括类,属性和方法等) 的声明前面加上 @objc 修饰符。注意这个步骤只需要对那些不是继承自 NSObject 的类型进行,如果你用 Swift 写的 class 是继承自 NSObject 的话,Swift 会默认自动为所有的非 private 的类和成员加上 @objc。这就是说,对一个 NSObject 的子类,你只需要导入相应的头文件就可以在 Objective-C 里使用这个类了。”
“@objc 修饰符的另一个作用是为 Objective-C 侧重新声明方法或者变量的名字。虽然绝大部分时候自动转换的方法名已经足够好用 (比如会将 Swift 中类似 init(name: String) 的方法转换成 -initWithName:(NSString *)name 这样),但是有时候我们还是期望 Objective-C 里使用和 Swift 中不一样的方法名或者类的名字,”
可选接口和接口扩展
原生的 Swift protocol 里没有可选项,所有定义的方法都是必须实现的。如果我们想要像 Objective-C 里那样定义可选的接口方法,就需要将接口本身定义为 Objective-C 的,也即在 protocol 定义之前加上 @objc。另外和 Objective-C 中的 @optional 不同,我们使用没有 @ 符号的关键字 optional 来定义可选方法:
@objc protocol OptionalProtocol {
optional func optionalMethod()
}
一个不可避免的限制是,使用 @objc 修饰的 protocol 就只能被 class 实现了,也就是说,对于 struct 和 enum 类型,我们是无法令它们所实现的接口中含有可选方法或者属性的。另外,实现它的 class 中的方法还必须也被标注为 @objc,或者整个类就是继承自 NSObject。这对我们写代码来说是一种很让人郁闷的限制。
在swift中实现接口可选的方法是通过protocol extension的方式。
内存管理,weak 和 unowned
weak和unowned的区别
如果我们可以确定在整个过程中 self 不会被释放的话,我们可以将上面的 weak 改为 unowned,这样就不再需要 strongSelf 的判断。但是如果在过程中 self 被释放了而 printName 这个闭包没有被释放的话 (比如 生成 Person 后,某个外部变量持有了 printName,随后这个 Persone 对象被释放了,但是 printName 已然存在并可能被调用),使用 unowned 将造成崩溃。在这里我们需要根据实际的需求来决定是使用 weak 还是 unowned。
@autoreleasepool
值类型和引用类型
String 还是 NSString
UnsafePointer
C 指针内存管理
COpaquePointer 和 C convention
GCD 和延时调用
获取对象类型
通过dynamicType方法,进一步使用呢?
自省
如果是继承自NSObject类,可以用isKindOfClass, isMemberOfClass判断,
在swift里可以用is判断,is可以用于枚举,结构体和类
KVO
只能用于继承自NSObject的类,
考虑用reactiveCocoa swift版本?(好好研究一下)
局部 scope
C语言用两个{}来包裹代码段,swift因为和闭包的冲突,不能直接这样使用。
用 do
do {
let textLabel = UILabel(frame: CGRectMake(150, 80, 20, 40))
//...
}
匿名闭包
titleLabel = {
let label = UILabel(frame: CGRectMake(150, 30, 20, 40))
return label
}()
判等
swift中 == Equatable接口里的方法。注意的是, func 放在全局,因为要对全局生效。事实上,swfit的操作符都是全局的
protocol Equatable { func ==(lhs: Self, rhs: Self) -> Bool }
class TodoItem { let uuid: String var title: String
init(uuid: String, title: String) { self.uuid = uuid self.title = title } }
extension TodoItem: Equatable { }
func ==(lhs: TodoItem, rhs: TodoItem) -> Bool { return lhs.uuid == rhs.uuid }
对于原来 Objective-C 中使用 == 进行的对象指针的判定,在 Swift 中提供的是另一个操作符 ===。在 Swift 中 === 只有一种重载:
func ===(lhs: AnyObject?, rhs: AnyObject?) -> Bool
它用来判断两个 AnyObject 是否是同一个引用。
哈希
protocol Hashable : Equatable { var hashValue: Int { get } }
于哈希值,另一个特别需要提出的是,除非我们正在开发一个哈希散列的数据结构,否则我们不应该直接依赖系统所实现的哈希值来做其他操作。首先哈希的定义是单向的,对于相等的对象或值,我们可以期待它们拥有相同的哈希,但是反过来并不一定成立。其次,某些对象的哈希值有可能随着系统环境或者时间的变化而改变。因此你也不应该依赖于哈希值来构建一些需要确定对象唯一性的功能,在绝大部分情况下,你将会得到错误的结果。
类簇
OC中的实现,
在 Objective-C 中,init 开头的初始化方法虽然打着初始化的名号,但是实际做的事情和其他方法并没有太多不同之处。类簇在 Objective-C 中实现起来也很自然,在所谓的“初始化方法”中将 self 进行替换,根据调用的方式或者输入的类型,返回合适的私有子类对象就可以了。
但是 Swift 中的情况有所不同。因为 Swift 拥有真正的初始化方法,在初始化的时候我们只能得到当前类的实例,并且要完成所有的配置。也就是说对于一个公共类来说,是不可能在初始化方法中返回其子类的信息的。对于 Swift 中 的类簇构建,一种有效的方法是使用工厂方法来进行。例如下面的代码通过 Drinking 的工厂方法将可乐和啤酒两个私有类进行了类簇化
使用工程,积累提供一个类方法作为工厂方法。
Swizzle
swift借助OC运行时实现。
SWRoute (了解一下原理)
https://github.com/rodionovd/SWRoute/wiki/Function-hooking-in-Swift
调用 C 动态库
输出格式化
如果需要制定类似小数点后几位,借助NSString的format方法来实现。
Options
oc里的NSOptions被转化为OptionSetType的类型,
public struct UIViewAnimationOptions : OptionSetType {
public init(rawValue: UInt)
static var LayoutSubviews: UIViewAnimationOptions { get }
static var AllowUserInteraction: UIViewAnimationOptions { get }
//...
static var TransitionFlipFromBottom: UIViewAnimationOptions { get }
}
OptionSetType 是实现了 SetAlgebraType 的,因此我们可以对两个集合进行各种集合运算,包括并集 (union)、交集 (intersect) 等等。另外,对于不需要选项输入的情况,也就是对应原来的 kNilOptions,现在我们直接使用一个空的集合 [] 来表示:
数组 enumerate
在 Objective-C 中最方便的方式是使用 NSArray 的 enumerateObjectsUsingBlock: 方法
在 Swift 中,我们在遇到这样的需求的时候,有一个效率,安全性和可读性都很好的替代,那就是快速枚举某个数组的 EnumerateGenerator,它的元素是同时包含了元素下标索引以及元素本身的多元组:
var result = 0 for (idx, num) in [1,2,3,4,5].enumerate() { result += num if idx == 2 { break } } print(result)
类型编码 @encode
C 代码调用和 @asmname
在导入Darwin的时候,swfit会自动做类型转换。通过桥接头文件的方式在swift中引用C函数。
也可以通过asmname来指定名称。
“//File.swift
//将 C 的 test 方法映射为 Swift 的 c_test 方法
@asmname("test") func c_test(a: Int32) -> Int32
”
sizeof 和 sizeofValue
//sifeof 3.0 sizeof不能使用了 MemoryLayout<String>.size MemoryLayout.size(ofValue: "abcd")
delegate weak delegate
protocol MyClassDelegate { func method() }
class MyClass { weak var delegate: MyClassDelegate? }
// weak var delegate: MyClassDelegate? 编译错误 // 'weak' cannot be applied to non-class type 'MyClassDelegate”
因为ARC的weak属性只能用于Class类型,不能用于枚举和Struct,可以通过使用@objc,或者 protocol MyClassDelegate: class 来实现。推荐使用后者。
Associated Object
swift中也能使用OC运行时这个功能,接口有点不一样。
func objc_getAssociatedObject(object: AnyObject!, key: UnsafePointer<Void> ) -> AnyObject!
func objc_setAssociatedObject(object: AnyObject!, key: UnsafePointer<Void>, value: AnyObject!, policy: objc_AssociationPolicy)
Lock
@synchronized 虽然这个方法很简单好用,但是很不幸的是在 Swift 中它已经 (或者是暂时) 不存在了。其实 @synchronized 在幕后做的事情是调用了 objc_sync 中的 objc_sync_enter 和 objc_sync_exit 方法,并且加入了一些异常判断。因此,在 Swift 中,如果我们忽略掉那些异常的话,我们想要 lock 一个变量的话,可以这样写:
func myMethod(anObj: AnyObject!) { objc_sync_enter(anObj) // 在 enter 和 exit 之间 anObj 不会被其他线程改变 objc_sync_exit(anObj) }
更进一步,如果我们喜欢以前的那种形式,甚至可以写一个全局的方法,并接受一个闭包,来将 objc_sync_enter 和 objc_sync_exit 封装起来:
func synchronized(lock: AnyObject, closure: () -> ()) { objc_sync_enter(lock) closure() objc_sync_exit(lock) }
Toll-Free Bridging 和 Unmanaged
3. Swift 与开发环境及一些实践
Swift 命令行工具
可以像脚本语言一样执行swift
随机数生成
这是错误代码
let diceFaceCount = 6 let randomRoll = Int(arc4random()) % diceFaceCount + 1 print(randomRoll)
Swift 的 Int 是和 CPU 架构有关的:在 32 位的 CPU 上 (也就是 iPhone 5 和前任们),实际上它是 Int32,而在 64 位 CPU (iPhone 5s 及以后的机型) 上是 Int64。arc4random 所返回的值不论在什么平台上都是一个 UInt32,于是在 32 位的平台上就有一半几率在进行 Int 转换时越界,时不时的崩溃也就不足为奇了。
这种情况下,一种相对安全的做法是使用一个 arc4random 的改良版本:
func arc4random_uniform(_: UInt32) -> UInt32
这个改良版本接受一个 UInt32 的数字 n 作为输入,将结果归一化到 0 到 n - 1 之间。只要我们的输入不超过 Int 的范围,就可以避免危险的转换:
let diceFaceCount: UInt32 = 6 let randomRoll = Int(arc4random_uniform(diceFaceCount)) + 1 print(randomRoll)
最佳实践当然是为创建一个 Range 的随机数的方法,这样我们就能在之后很容易地复用,甚至设计类似与 Randomable 这样的接口了:
func randomInRange(range: Range<Int>) -> Int { let count = UInt32(range.endIndex - range.startIndex) return Int(arc4random_uniform(count)) + range.startIndex }
for _ in 0...100 { print(randomInRange(1...6)) }
print 和 debugPrint
在extension中实现CustomStringConvertible这个协议
错误和异常处理
异常和错误的区别
“Swift 现在的异常机制也并不是十全十美的。最大的问题是类型安全,不借助于文档的话,我们现在是无法从代码中直接得知所抛出的异常的类型的。”
因此,在 Swift 2 时代中的错误处理,现在一般的最佳实践是对于同步 API 使用异常机制,对于异步 API 使用泛型枚举。
关于 try 和 throws,想再多讲两个小点。首先,try 可以接 ! 表示强制执行,这代表你确定知道这次调用不会抛出异常。如果在调用中出现了异常的话,你的程序将会崩溃,这和我们在对 Optional 值用 ! 进行强制解包时的行为是一致的。另外,我们也可以在 try 后面加上 ? 来进行尝试性的运行。try? 会返回一个 Optional 值:如果运行成功,没有抛出错误的话,它会包含这条语句的返回值,否则将为 nil。和其他返回 Optional 的方法类似,一个典型的 try? 的应用场景是和 if let 这样的语句搭配使用,不过如果你用了 try? 的话,就意味着你无视了错误的具体类型:
“值得一提的是,在一个可以 throw 的方法里,我们永远不应该返回一个 Optional 的值。因为结合 try? 使用的话,这个 Optional 的返回值将被再次包装一层 Optional,使用这种双重 Optional 的值非常容易产生错误,也十分让人迷惑 (详细可参见多重 Optional 的内容)。
断言
func assert(@autoclosure condition: () -> Bool, @autoclosure _ message: () -> String = default, file: StaticString = default, line: UInt = default)
对应 target 的 Build Settings 中,我们在 Swift Compiler - Custom Flags 中的 Other Swift Flags 中添加 -assert-config Debug 来强制启用断言,或者 -assert-config Release 来强制禁用断言。当然,除非有充足的理由,否则并不建议做这样的改动。如果我们需要在 Release 发布时在无法继续时将程序强行终止的话,应该选择使用 fatalError。
fatalError
在调试时我们可以使用断言来排除类似这样的问题,但是断言只会在 Debug 环境中有效,而在 Release 编译中所有的断言都将被禁用。在遇到确实因为输入的错误无法使程序继续运行的时候,我们一般考虑以产生致命错误 (fatalError) 的方式来终止程序。
@noreturn func fatalError(@autoclosure message: () -> String = default, file: StaticString = default, line: UInt = default)
关于语法,唯一要需要解释的是 @noreturn,这表示调用这个方法的话可以不再需要返回值,因为程序整个都将终止。这可以帮助编译器进行一些检查,比如在某些需要返回值的 switch 语句中,我们只希望被 switch 的内容在某些范围内,那么我们在可以在不属于这些范围的 default 块里直接写 fatalError 而不再需要指定返回值:
代码组织和 Framework
但是要特别指出,虽然和 Apple 的框架的后缀名一样是 .framework,使用方式也类似,但是这些第三方框架都是实实在在的静态库,每个 app 需要在编译的时候进行独立地链接。
framework的制作和引用
安全的资源组织方式
使用字符串指定资源名称的问题。
OC中可以通过宏来制定文件名称。
“在 Swift 中是没有宏定义的,取而代之,我们可以灵活地使用 rawValue 为 String 的 enum 类型来字符串,然后通过为资源类型添加合适的 extension 来让编译器帮助我们在资源名称修改时能在代码中作为对应的改变。
enum ImageName: String { case MyImage = "my_image" }
enum SegueName: String { case MySegue = "my_segue" }
extension UIImage { convenience init!(imageName: ImageName) { self.init(named: imageName.rawValue) } }
extension UIViewController { func performSegueWithSegueName(segueName: SegueName, sender: AnyObject?) { performSegueWithIdentifier(segueName.rawValue, sender: sender) } }
不过在 Swift 中,根据项目内容来自动化生成像是 ImageName 和 SegueName 这样的类型并不是一件难事。Swift 社区中现在也有一些比较成熟的自动化工具了,R.swift 和 SwiftGen 就是其中的佼佼者。
Playground 延时运行
为了使 Playground 具有延时运行的本领,我们需要引入 Playground 的 “扩展包” XCPlayground 框架。现在这个框架中包含了几个与 Playground 的行为交互以及控制 Playground 特性的 API,其中就包括使 Playground 能延时执行的黑魔法,XCPlaygroundPage 和 needsIndefiniteExecution。
我们只需要在刚才的代码上面加上:
import XCPlayground XCPlaygroundPage.currentPage.needsIndefiniteExecution = true //swift 3.0中,这个属性已经不推荐使用了
默认时间是30秒, 但是如果你想改变这个时间的话,可以通过 Alt + Cmd + 回车 来打开辅助编辑器。在这里你会看到控制台输出和时间轴,将右下角的 30 改成你想要的数字,就可以对延时运行的最长时间进行设定了。
Playground 与项目协作
可以在项目中使用
数学和数字
NAN: Not a Number
JSON
最大的问题在于我们为了保证类型的正确性,做了太多的转换和判断。我们并没有利用一个有效的 JSON 容器总应该是字典或者数组这个有用的特性,而导致每次使用下标取得的值都是需要转换的 AnyObject。如果我们能够重载下标的话,就可以通过下标的取值配合 Array 和 Dictionay 的 Optional Binding 来简单地在 JSON 中取值。鉴于篇幅,我们在这里不给出具体的实现。感兴趣的读者可以移步看看 json-swift 或者 SwiftyJSON 这样的项目,它就使用了重载下标访问的方式简化了 JSON 操作。使用这个工具,上面的访问可以简化为下面的类型安全的样子:
// 使用 SwiftJSON
if let value = JSON(json)["menu"]["popup"]["menuitem"][0]["value"].string {
print(value)
}
这样就简单多了。
NSNull
文档注释
性能考虑
Log 输出
func printLog<T>(message: T,
file: String = #file,
method: String = #function,
line: Int = #line)
{
#if DEBUG
print("\((file as NSString).lastPathComponent)[\(line)], \(method): \(message)")
#endif
}
新版本的 LLVM 编译器在遇到这个空方法时,甚至会直接将这个方法整个去掉,完全不去调用它,从而实现零成本。
溢出
如果我们想要其他编程语言那样的对溢出处理温柔一些,不是让程序崩溃,而是简单地从高位截断的话,可以使用溢出处理的运算符,在 Swift 中,我们可以使用以下这五个带有 & 的操作符,这样 Swift 就会忽略掉溢出的错误:
溢出加法 (&+)
溢出减法 (&-)
溢出乘法 (&*)
溢出除法 (&/)
溢出求模 (&%)
宏定义 define
属性访问控制
public class MyClass {
public private(set) var name: String?
}
Swift 中的测试
@testable
Core Data
闭包歧义
“为了增强可读性和安全性,最直接是在调用时尽量指明闭包参数的类型。
泛型扩展
我们不能通过扩展来重新定义当前已有的泛型符号,但是可以对其进行使用;在扩展中也不能为这个类型添加泛型符号;但只要名字不冲突,我们是可以在新声明的方法中定义和使用新的泛型符号的。
兼容性
Apple 通过将一个最小化的运行库集成打包到 app 中这样的方式来解决兼容性的问题。使用了 Swift 语言的项目在编译时会在 app 包中带有这一套运行时环境,并在启动时加载这些 dylib 包作为 Swift 代码的运行环境。这些库文件位于打包好的 app 的 Frameworks 文件夹中:
这样带来的好处有两点。首先是虽然 Swift 语言在不断变化,但是你的 app 不论在什么样的系统版本上都可以保持与开发编译时的行为一致,因为你依赖的 Swift 运行时是和 app 绑定的。这对于确保 Swift 升级后新版本的 app 在原有的设备和系统上运行正常是必要的。
另一个好处是向下兼容。虽然 Swift 是和 iOS 8 及 OSX 10.10 一同推出的,但是通过加载 Swift 的动态库,Apple 允许 Swift 开发的 app 在 iOS 7 和 OSX 10.9 上也能运行,这对 Swift 的尽快推广和使用也是十分关键的。
列举 enum 类型
(不太懂)