示例代码来源于 《iOS 11 Programming Fundamentals with Swift》
概览
语句分隔符
Swift的语句是可以通过分析断句的,如果一个语句结束后换行就开始下一个语句,如果写了分号也表示结束这个语句,但是有了换行就不需要分号了,写上也没有问题。如果一个语句没有结束,换行没有实际效果。下边的代码都是合法的:
print("hello")
print("world")
print("hello"); print("world")
print("hello");
print("world");
print(
"world")
注释
依旧是://和/…/,其中/* … */可以嵌套
对象的类型
Swift中一切都是对象。按照类型划分,有6种:
- struct,Bool,Int,Double,String,Array,Dictionary等
- enum,Optional类型
protocol,ExpressibleByIntegerLiteral
- tuple,函数返回多值时使用。
- function,print,自定义函数。
举个例子:
字面变量是对象:
let s = 1.description
#### 值类型和引用类型
按照内存管理来划分,Swift对象有值类型和引用类型,值类型在赋值的时候是copy的(不考虑Swift优化),引用类型是共享内存的。
官方文档对值类型和引用类型的解释:
Types in Swift fall into one of two categories: first,“value types”,where each instance keeps a unique copy of its data,usually defined as a struct,enum,or tuple. The second,“reference types”,where instances share a single copy of the data,and the type is usually defined as a class.
在Swift中,class和function是引用类型,struct,enum,tuple都是值类型。protocol本身不允许有实例,但是采用protocol的可以是struct, enum或者class。
数据类型
变量与常量
@H_404_95@let one = 1
@H_404_95@var two = 2
使用let声明的是常量,使用var声明的是变量
类型推断
从上面例子可以看出,如果在声明变量的时候就赋值,有时候是可以不写类型的,让编译器推断。上文中one和two都是Int类型。
那么什么时候需要些类型呢?
- 只声明,不初始化。
@H_404_95@var x : Int
- 想要的类型和推断的类型不符合
let separator : CGFloat = 2.0
- 不能推断出类型
let opts : UIViewAnimationOptions = [.autoreverse,.repeat]
- 还有一种情况是提醒自己这个变量是啥类型
@H_404_95@let duration : CMTime = track.timeRange.duration
基本类型用法
Bool
- Bool是一个struct类型
- 只有true和false两个值,不能做它解释。
Int
- Int是struct类型
- Int的取值在Int.max和Int.min之间,平台相关
Double
- Double是struct类型
- 64位架构处理器上,Double的精度是15位
- Double的边界是Double.infinity,还有Double.pi等
- 使用isZero来判断Double是否为0
数字类型转换
只有字面变量可以被隐式转换!
@H_404_95@let d : Double = 10
将字面变量10转换成了Double类型,但是变量就不可以,下列的代码不能通过编译:
@H_404_95@let i = 10
@H_404_95@let d : Double = i // compile error
正确的写法是:
@H_404_95@let i = 10
@H_404_95@let d : Double = Double(i)
String
@H_404_95@let @H_404_95@str = "Hello World" //欧耶,终于不用写@了
多行字面变量的写法:
func f() {
let s = """ Line 1 Line 2 Line 3 """
// @H_404_95@...
}
func f() {
let s = """ Line "1" Line 2 \ and this is still Line 2 """
// @H_404_95@...
}
在String字面变量中使用(…)来计算表达式
let n = 5
let s = "You have \(n) widgets."
String支持+号和+=号
let s = "hello"
let s2 = " world"
let greeting = s + s2
String的utf8编码:
@H_404_95@let s = "\u{BF}Qui\u{E9}n?"、
@H_404_95@for i @H_404_95@in s.utf8 {
print(i) // 194,191,81,117,105,195,169,110,63
}
String和数值的转化:
@H_404_95@let i = 7
@H_404_95@let s = String(i) // "7"
@H_404_95@let i = 31
@H_404_95@let s = String(i,radix:16) // "1f"
Range
Range是一个struct。 字面变量: a…b表示区间[a,b] a..< b表示区间[a,b)
最常见的就是在for循环中使用:
@H_404_95@for ix @H_404_95@in 1...3 {
print(ix) // 1,@H_404_95@then 2,@H_404_95@then 3
}
Range 有实例方法:
let ix = // @H_404_95@... an Int @H_404_95@...
@H_404_95@if (1...3).contains(ix) { // @H_404_95@...
let s = "hello"
let ix2 = s.index(before: s.endIndex)
let s2 = s[..<ix2] // "hell"
Tuple
tuple是一个有序的轻量级的collection。
tuple的声明:
var pair : (Int,String)
初始化:
var pair : (Int,String) = (1,"Two")
var pair = (1,"Two")
tuple可以同时给多个变量赋值:
@H_404_95@let ix: Int
@H_404_95@let s: String
(ix,s) = (1,"Two")
tuple在for-in中的应用:
@H_404_95@let s = "hello"
for (ix,c) in s.enumerated() { print("character \(ix) is \(c)") }
对Tuple中值的引用:
@H_404_95@let pair = (1,"Two")
@H_404_95@let ix = pair.0 // now ix is 1
如果在声明的时候给值一个label,可以通过label引用:
@H_404_95@let pair : (first:Int,second:String) = (1,"Two")
//or: let pair = (first:1,second:"Two")
var pair = (first:1,second:"Two")
@H_404_95@let x = pair.first // 1
pair.first = 2
@H_404_95@let y = pair.0 // 2
还可以给Tuple起一个别名
typealias Point = (x:Int,y:Int) func piece(at p:Point) -> Piece? { let (i,j) = p // @H_404_95@... error-checking goes here @H_404_95@... @H_404_95@return self.grid[i][j] }
可选类型
Swift中变量如果不初始化是不能使用的。这点和OC不同,OC中值类型会有一个默认值,引用类型默认为nil。Swift中如何表示nil呢?答案就是Optional(可选类型)
Optional类型的底层是enum类型,可以包装一个其他类型,具体内部实现这里不讨论。
比如:
@H_404_95@var stringMaybe = Optional("howdy")
就定义了一个包装了String的Optional类型。包装不同类型的Opational也是不同的类型,不能互相赋值。Optional(String)类型可以简写为String?
如果没有给Optional的变量装箱一个值,那么它就是空的,空的Optional变量可以和nil比较:
@H_404_95@var stringMaybe : String? = "Howdy"
print(stringMaybe) // Optional("Howdy")
@H_404_95@if stringMaybe == nil {
print("it is empty") // does not print
}
stringMaybe = nilprint(stringMaybe) // nil
@H_404_95@if stringMaybe == nil {
print("it is empty") // prints
}
在Swift中nil是一个关键字,不是一个值,可以将nil赋值给Optional的类型。
自动装箱,将一个值直接值给包装它的Optional类型。
@H_404_95@var stringMaybe: String? = "farewell
根据自动装箱机制,可以在任何需要Optional类型的地方传入原始类型,但是反过来不行。
@H_404_95@let stringMaybe : String? = "howdy"
@H_404_95@let upper = stringMaybe.uppercased() // compile error
不能给Optional类型直接发送消息,需要拆箱得到原始数据。
拆箱
@H_404_95@let stringMaybe : String? = "howdy"
@H_404_95@let upper = stringMaybe!.uppercased()
在变量后边加上叹号,就拆箱得到原始类型。
自动拆箱,在定义变量的时候使用!而不是?就定义了一个自动拆箱的Opational变量,在需要使用原始类型的地方,直接传入自动解包的Opational变量即可。
func realStringExpecter(_ s:String) {}
@H_404_95@var stringMaybe : String! = "howdy"
realStringExpecter(stringMaybe) // no problem
注意,如果自动解包的Optional是nil,会引起Crash。不能给一个是nil的Optional类型解压,这是Swift最重要的规则之一。 所以,如果不是必须,最好不要使用这个特性,因为这样就失去了Swift中可选类型的安全特性。
!定义的Optional和?定义的Optional是同一个类型,比如self.view是一个UIView!,但是如下代码却产生编译错误。
@H_404_95@var stringMaybe : String! = "howdy"
@H_404_95@var anotherStr = stringMaybe //ok
@H_404_95@var pureStr: String = stringMaybe //ok
@H_404_95@var errStr: String = anotherStr // compile error
stringMaybe是自动拆箱的String?,所以赋值给String类型是可以的;但是anotherStr却没有自动拆箱的标志,仅仅是一个String?,所以不能赋值给String类型。
Optianal Chain是Swift中很重要的一个概念。
拆箱nil会引起Crash,那么如果每次拆箱都得判断是否为nil,代码就会很难看。于是Swift提供了语法糖:
var stringMaybe : String? // @H_404_95@... stringMaybe might be assigned a real value here @H_404_95@... let upper = stringMaybe?.uppercased()
在拆箱的时候,不用!而是用?,这叫做选择性拆箱。英文很有意思:unwarp the Optional optionally。
选择性拆箱实际上替你做了判断工作,就是如果stringMaybe是nil,那么什么也不做,如果不是nil,拆箱得到String,然后发送uppercased消息。
这很好,但是如果“什么也不做”返回值upper是啥?答案是nil。那么nil是不能赋值给String类型的,于是又引入一个规则:
如果一个Optional Chain上有一个可能的Optional的类型(选择性拆包才有),那么返回值就是Optional的。
也就是说虽然uppercased方法返回的是String类型,但是因为它在一个Optional Chain中,所以返回值自动被装箱,成为String?类型。一个Optional Chain返回一个Optional的类型也合情合理。
因为自动装箱,给一个Opational Chain赋值会比较简单。
// self is a UIViewController
@H_404_95@self.navigationController?.hidesBarsOnTap = @H_404_95@true
同样,如果navigationController是nil,什么也不会发。那么如何知道赋值成功了呢?
@H_404_95@let ok : Void? = self.navigationController?.hidesBarsOnTap = true
如果ok不是nil,就是赋值成功。
Optional类型是可以和原始类型直接比较的。下边的代码没有问题。
let s : String? = "Howdy"
@H_404_95@if s == "Howdy" { // @H_404_95@... they _are_ equal!
如果s是nil,返回false,如果s不是nil,拆箱之后再和”Howdy”比较。
但是不能比较不等关系,下边的代码是不能通过编译的:
@H_404_95@let i : Int? = 2
@H_404_95@if i < 3 { // compile error
因为Swift不能确定如果是nil,结果是什么。
函数
函数的定义
func sum (_ x:Int,_ y:Int) -> Int {
let result = x + y
@H_404_95@return result
}
- func 是keyword,sum是函数名。
- 括号内部是参数,参数标签,变量名,冒号后是类型。和OC结构一样。
- -> Int表示返回值是Int类型。如果函数返回Void,可以写成->()
- 函数体在大括号内部。
- 参数前边的”_”符号表示忽略参数的标签。
参数标签
func echoString(_ s:String,@H_404_95@times:Int) -> String {
var result = ""
@H_404_95@for _ @H_404_95@in 1...@H_404_95@times { result += s }
return result
}
times就是参数的外部名字(external name),也可以叫参数标签。这是和OC语言的参数名字和变量名字分开是一致的。
let s = echoString("hi",times:3)
- 默认的,参数的变量名(internal name)就是参数标签(external name)。
- 如果使用了_,表示没有标签,调用方也不能使用标签调用了。
- 具有相同的函数签名,但是参数标签不同的函数,是两个不同的函数。
函数参数默认是不可变的。意思是,不能在函数中给函数参数再次赋值。对于引用类型,是可以改变内部属性的。
func say(_ s:String,times:Int,loudly:Bool) {
loudly = true // compile error
}
如果想要重新给参数赋值需要满足以下几个条件:
- 给函数参数添加intout关键字
- 传入的变量应该是var而不是let的
- 传入变量的地址。
func removeCharacter(_ c:Character,from s: inout String) -> Int {
var howMany = 0
@H_404_95@while let ix = s.index(of:c) {
s.remove(at:ix)
howMany += 1
}
@H_404_95@return howMany
}
@H_404_95@var s = "hello"
@H_404_95@let result = removeCharacter("l",@H_404_95@from:&s)
Swift中函数是first-class object,意思是函数可以赋值给变量,可以作为函数的参数和返回值。
func doThis(_ f:() -> ()) { f() } func whatToDo() { print("I did it") } doThis(whatToDo)
函数是first-class object这一特点可以衍生出很多编程模式,装饰器,偏函数,函数工厂等等。
class,struct & enum
概览
enum,struct在Swift中和class很像,都可以定义方法,初始化函数等,但是有两个重大的区别:
- enum,struct是值类型,class是引用类型
- enum,struct不能继承
在这3种类型中,可以有的结构是:
- 初始化函数。
- 属性,分为成员属性和类属性。对于struct和enum用static关键字,对于class用class关键字。
- 方法,成员方法和类方法。
- 下标(subscripts)
- 嵌套定义(值类型的不能嵌套自己的类型)。
在Swift中没有一个像NSObject那样的公共基类。
class
初始化方法
由于Swift中不允许使用未经初始化的变量,并且想在编译阶段强制的保证这一点。于是对于class类型的初始化,引入了很多规则。虽然规则条数很多,但都是围绕这一个原则:从初始化函数中返回的对象的所有属性也是初始化的,并且在初始化完成之前不能使用这个对象。
@H_404_95@class Dog {
@H_404_95@let name : String
@H_404_95@let license : Int
init(name:String = "",license:Int = 0) {
self.name = name
self.license = license
}
}
如果删除self.license = license,将会产生编译错误,因为license没有初始化。
- 在初始化所有属性之前,不能使用self
@H_404_95@class Cat {
@H_404_95@var name : String
@H_404_95@var license : Int
init(name:String,license:Int) {
@H_404_95@self.name = name
meow() // too soon - compile error
@H_404_95@self.license = license
}
func meow() {
@H_404_95@print("meow")
}
}
meow()实际上隐式的使用了self,即self.meow()。应该将meow()的调用放到最后。
如果初始化函数之间发生调用关系,初始化函数就分成了两类:designated initializer 和convenience initializer。
designated initializer就是能独立完成对象的初始化的初始化函数,而convenience initializer必须直接或者间接的调用designated initializer来完成初始化工作。
class Dog{
var name: String
var age: Int
init(){
self.name = "test"
self.age = 10
}
convenience init(name:String){
self.init(name: name,age: 10)
}
init(name: String,age: Int){
self.name = name
self.age = age
}
}
在class中designated initializer不需要特别指明,但是convenience initializer必须使用convenience关键字。(这一条只是对class来讲,如果把class换成struct就不需要使用convenience,这和class是能继承有关系,稍后会介绍到继承)
这又有一条规则: convenience initializer在使用self之前,必须调用designated initializer。
举个例子:
class Dog{
var name: String
var age: Int
init(){
self.name = "test"
self.age = 10
}
convenience init(name:String){
self.age = 11
self.name = "haha"
self.init(name: name,age: Int){
self.name = name
self.age = age
}
}
上边的代码会发生编译错误,因为convenience初始化函数中在self被designated initializer初始化之前就使用了self。从这一点上看,convenience initializer并不是一个真正的初始化函数,只是能提供初始化功能的一般函数。
在高级篇介绍的继承体系中,会有更复杂的初始化规则。不过如果你违反了这些规则,编译器都会提示的很清楚。只要理解这些规则的目的都是确保对象被完全初始化即可。
属性(对struct和class都适用)
在类的属性全部被初始化完毕之前,不能使用self。
class Moi {
@H_404_95@let first = "Matt"
@H_404_95@let last = "Neuburg"
@H_404_95@let whole = @H_404_95@self.first + " " + @H_404_95@self.last // compile error
}
对于静态属性的使用,在非静态函数中应该使用类名.属性,在静态函数中可以使用self.属性或者类名.属性
@H_404_95@class Greeting {
@H_404_95@static let friendly = "hello there"
@H_404_95@static let hostile = "go away"
@H_404_95@static @H_404_95@var ambivalent : String {
@H_404_95@return @H_404_95@self.friendly + " but " + @H_404_95@self.hostile
}
}
下标(对struct和class都适用)
下标是一种调用实例方法的方式。一般在通过整数参数或者String类型的key获取元素的时候使用下标。
struct Digit {
var number : Int
init(_ n:Int) {
self.number = n
}
subscript(ix:Int) -> Int {
@H_404_95@get {
@H_404_95@let s = String(self.number)
return Int(String(s[s.index(s.startIndex,offsetBy:ix)]))!
}
}
}
@H_404_95@var d = Digit(1234)
@H_404_95@let aDigit = d[1] // 2
嵌套定义
@H_404_95@class Dog {
struct Noise {
@H_404_95@static @H_404_95@var noise = "woof"
}
func bark() {
@H_404_95@print(Dog.Noise.noise)
}
}
注意:struct不能直接或者间接嵌套自己的类型。
Struct
struct大部分特性都和class一致,可以看做是没有继承特性的值类型的class。
一些不同:
- 改变struct属性的方法需要标记为mutating,在enum章节中会有例子。
- 默认的初始化函数可以提供逐一赋值功能(memberwise),只要能保证所有属性都初始化。
@H_404_95@struct Digit {
@H_404_95@var number = 42
@H_404_95@var number2
}
@H_404_95@var d = Digit(number: 3,number2: 34)
@H_404_95@var f = Digit() //compile error
enum
enum Filter {
@H_404_95@case albums
@H_404_95@case playlists
@H_404_95@case podcasts
@H_404_95@case books
}
@H_404_95@let type = Filter.albums
在能根据上下文推断出enum的类型的时候,可以简写成:
@H_404_95@let @H_404_95@type : Filter = .albums
RawValue
可以给enum指定一个存储类型,存储类型只能是数字或者String
enum PepBoy : Int {
@H_404_95@case manny
@H_404_95@case moe
@H_404_95@case jack
}
enum Filter : String {
@H_404_95@case albums
@H_404_95@case playlists
@H_404_95@case podcasts
@H_404_95@case books
}
PepBoy中默认从0开始,Filter中默认值就是case的名字。
@H_404_95@let @H_404_95@type = Filter.albums
print(@H_404_95@type.rawValue) // albums
可以通过rawValue初始化enum
@H_404_95@let @H_404_95@type = Filter(rawValue:"Albums")
Swift中的enum可以有初始化方法
enum Filter : String {
@H_404_95@case albums = "Albums"
@H_404_95@case playlists = "Playlists"
@H_404_95@case podcasts = "Podcasts"
@H_404_95@case books = "Audiobooks"
static var cases : [Filter] = [.albums,.playlists,.podcasts,.books]
init(_ ix:Int) {
self = Filter.cases[ix]
}
}
上边的代码就可以通过一个Int来初始化一个存储类型是String的enum。
enum Shape {
case rectangle
case ellipse
case diamond
func addShape (to p: CGMutablePath,@H_404_95@in r: CGRect) -> () {
switch self {
case .rectangle:
p.addRect(r)
case .ellipse:
p.addEllipse(@H_404_95@in:r)
case .diamond:
p.move(to: CGPoint(x:r.minX,y:r.midY))
p.addLine(to: CGPoint(x: r.midX,y: r.minY))
p.addLine(to: CGPoint(x: r.maxX,y: r.midY))
p.addLine(to: CGPoint(x: r.midX,y: r.maxY))
p.closeSubpath()
}
}
}
上边的代码能根据这个enum实际的值,来创建一个图形。
如果一个enum的实例方法能够修改这个enum的值,那需要将方法声明为mutating
enum Filter : String {
@H_404_95@case albums = "Albums"
@H_404_95@case playlists = "Playlists"
@H_404_95@case podcasts = "Podcasts"
@H_404_95@case books = "Audiobooks"
@H_404_95@static @H_404_95@var cases : [Filter] = [.albums,.books]
mutating func advance() {
@H_404_95@var ix = Filter.cases.index(of:@H_404_95@self)!
ix = (ix + 1) % 4
@H_404_95@self = Filter.cases[ix]
}
}
原理是这样的,enum是一个值类型,值类型是不可变的,要改变enum的值,只有再创建一个enum。这个动作在Swift中是需要开发人员显示指定的。这一条也适用于struct。
Associated Value
在Swift中enum还可以作为C语言中的Union使用。
enum MyError {
@H_404_95@case number(Int)
@H_404_95@case message(String)
@H_404_95@case fatal
}
- 在MyError中不声明任何存储类型
- 在每个case后边用tuple定义类型
MyErrorj就是一个可能保存Int或者String的数据类型。
let num = 4
let err : MyError = .number(num)
因为Associated Value是动态赋值的,所以Associated Value类型的enum不能使用enum比较。
@H_404_95@if err == MyError.fatal { // compile error
因为Swift不知道如何比较,两个实例的fatal可能关联了不同的值,那么到底是相同还是不相同?
集合数据类型
Array
- Array只能保存一种数据类型,是指声明为同一种的数据类型,不是实际类型。
- 如果想保存混合类型的数据,使用[Any],Any是为了和OC交互定义的数据类型。
- 保存不同类型的Array属于不同的数据类型。
- Array是值类型,是struct。
保存Int类型的Array有两种写法:
@H_404_95@let arr1 = Array<Int>()
@H_404_95@let arr2 = [Int]()
可以使用Range:
@H_404_95@let arr3 = Array(1...3)
Array有很多初始化函数,比如还可以接受一个集合类型,创建出一个Array
@H_404_95@let arr4 = Array("hey".characters)
有一个初始化函数需要注意:init(repeating:count),如果参数是引用类型,那么Array中的所有元素将指向同一个元素。
@H_404_95@class Person {
var name = "123"
}
var p = Person()
let arr5 = Array(repeatElement(p,count: 3))
//[{name "123"},{name "123"},{name "123"}]
arr5[1].name = "555"
//[{name "555"},{name "555"},{name "555"}]
Array作为一个整体可以类型转换:
@H_404_95@let dog1 : Dog = NoisyDog() @H_404_95@let dog2 : Dog = NoisyDog() @H_404_95@let arr = [dog1,dog2] @H_404_95@let arr2 = arr @H_404_95@as! [NoisyDog]
NoisyDog 是 Dog的子类, arr是[Dog]类型,可以时间用as!或者as?转换为[NoisyDog]类型。
两个Array相等的条件是Array中的每一个元素相等(注意并没有要求两个Array的类型是一样的)。和其他语言类似,可以自己提供比较函数。
@H_404_95@let nd1 = NoisyDog()
@H_404_95@let d1 = nd1 @H_404_95@as Dog
@H_404_95@let nd2 = NoisyDog()
@H_404_95@let d2 = nd2 @H_404_95@as Dog
@H_404_95@if [d1,d2] == [nd1,nd2] { // they are equal!
Array的下标是支持切片的(slicing),切片仅仅是原来Array的一个映像,底层还是引用的是原来的Array
let arr = ["manny","moe","jack"]
let slice = arr[1...2] // ["moe","jack"]
print(slice[1]) // moe
slice是arr的从1到2闭区间的切片,下标也是从1开始,到2结束。==如果引用了下标0,则会产生运行时错误==。如果改变了切片中的元素(前提是可以改变),则原来的数组也会受到影响。
但是,Array不支持负数下标。
Array有一些常用的属性:
let arr = ["manny","jack"]
arr.count
arr.isEmpty
arr.first
arr.last
arr.startIndex
arr.endIndex
//@H_404_95@...
判断元素是否存在:
let arr = [1,2,3]
let ok = arr.contains(2) // true
let ok2 = arr.contains {$0 > 3} // false
let arr = [1,3]
let ok = arr.starts(with:[1,2]) // true
let ok2 = arr.starts(with:[1,-2]) {abs($0) == abs($1)} // true
改变Array元素:
@H_404_95@var arr = [1,2,3]
arr.append(4)
arr.append(contentsOf:[5,6])
arr.append(contentsOf:7...8) // arr is now [1,2,3,4,5,6,7,8]
var arr = ["manny","jack"]
arr.insert("333",at: 1) //["manny","333","jack"]
arr.remove(at: 1) //arr is ["manny","jack"]
let arr = [[1,2],[3,4],[5,6]]
let joined = Array(arr.joined(separator:[10,11]))
// [1,10,11,3,4,5,6]
let arr = [1,6]
let arr2 = arr.split {$0 % 2 == 0} // split at evens: [[1],[3],[5]]
遍历Array元素
let pepboys = ["Manny","Moe","Jack"]
@H_404_95@for pepboy @H_404_95@in pepboys {
print(pepboy) // prints Manny,@H_404_95@then Moe,@H_404_95@then Jack
}
let pepboys = ["Manny","Jack"]
pepboys.forEach {print($0)} // prints Manny,@H_404_95@then Jack
let pepboys = ["Manny","Jack"]
@H_404_95@for (ix,pepboy) in pepboys.enumerated() {
@H_404_95@print("Pep boy \(ix) is \(pepboy)") // Pep boy 0 is Manny,etc.
}
// or:
pepboys.enumerated().@H_404_95@forEach {@H_404_95@print("Pep boy \($0.0) is \($0.1)")}
let pepboys = ["Manny","Jack","Moe"]
let arr1 = pepboys.filter{$0.hasPrefix("M")} // ["Manny","Moe"]
let arr2 = pepboys.prefix{$0.hasPrefix("M")} // ["Manny"]
let arr3 = pepboys.drop{$0.hasPrefix("M")} // ["Jack","Moe"]
Array和OC的关系
如果一个NSArray没有任何额外信息则转化为[Any],NSArray中的对象都是class类型。把一个Array转化为NSArray没有额外的工作要做。
let arr = [UIBarButtonItem(),UIBarButtonItem()]
@H_404_95@self.navigationItem.leftBarButtonItems = arr
@H_404_95@let arr = ["Manny","Jack"]
@H_404_95@let s = (arr @H_404_95@as NSArray).componentsJoined(by:",")
// s is "Manny,Moe,Jack"
不能把一个Array转化成一个NSMutableArray。如果需要调用NSMutabelArray的方法,使用NSMutableArray的构造函数创建一个。
@H_404_95@var arr = ["Manny","Jack"]
@H_404_95@let arr2 = NSMutableArray(array:arr)
arr2.remove("Moe")
arr = arr2 @H_404_95@as! [String]
在Xcode7以后,有些OC的API提供了额外的类型信息,比如:
+ (NSArray<NSString *> *)fontNamesForFamilyName:(NSString *)familyName;
这时候返回的值就能直接转换为String。
Dictionary
Dictionary的语法:
@H_404_95@var d : [String:String] = [:]
@H_404_95@var d = [String:String]()
@H_404_95@var d = ["CA": "California","NY": "New York"]
两个Array,一个保存Key,一个保存Value,初始化一个Dictionary
let abbrevs = ["CA","NY"]
let names = ["California","New York"]
let tuples = zip(abbrevs,names)
let d = Dictionary(uniqueKeysWithValues: tuples)
如果两个Array长度不同,zip自动忽略额外的部分,保证成对。
从Dictionary中取出来的值是Opational的,因为如果不存在的话会返回nil。可以使用有默认值的方式获取
@H_404_95@let d = ["CA": "California","NY": "New York"]
@H_404_95@let state = d["MD",@H_404_95@default:"N/A"] // state is a String (not an Optional)
使用了default关键字返回的就是String而不是String?
Dictionary的遍历:
遍历key:
var d = ["CA": "California","NY": "New York"]
@H_404_95@for s @H_404_95@in d.keys {
print(s) // NY,@H_404_95@then CA
}
遍历key和value:
var d = ["CA": "California","NY": "New York"]
@H_404_95@for (abbrev,state) @H_404_95@in d {
print("\(abbrev) stands for \(state)")
}
可以将Dictionary变成一个Tuple的Array:
var d = ["CA": "California","NY": "New York"]
let arr = Array(d)
// [(key: "NY",value: "New York"),(key: "CA",value: "California")]
和NSDictionary的关系:
NSDictionary对应[AnyHashable: Any],NSDictionary向Swift转换:
@H_404_95@let prog = n.userInfo?["progress"] @H_404_95@as? Double
@H_404_95@if prog != nil {
self.progress = prog!
}
Swift中使用Cocoa接口:
UINavigationBar.appearance().titleTextAttributes = [
.font: UIFont(name: "ChalkboardSE-Bold",size: 20)!,.foregroundColor: UIColor.darkText,.shadow.: {
let shad = NSShadow()
shad.shadowOffset = CGSize(width:1.5,height:1.5)
return shad
}()
]
Set
@H_404_95@let @H_404_95@set : @H_404_95@Set<Int> = [1,5]
在Swift中Set没有字面变量,但是可以用Array构建。
在Array中去重:
@H_404_95@let arr = [1,1,5]
@H_404_95@let @H_404_95@set = Set(arr)
@H_404_95@let arr2 = Array(@H_404_95@set) // [5,1,perhaps
insert 和 update,假设Dog的比较函数是name相等。
var @H_404_95@set : @H_404_95@Set = [Dog(name:"Fido",license:1)] let d = Dog(name:"Fido",license:2) @H_404_95@set.@H_404_95@insert(d) // [Dog(name: "Fido",license: 1)] @H_404_95@set.@H_404_95@update(@H_404_95@with:d) // [Dog(name: "Fido",license: 2)]
当已经存在的时候,insert不会改变set,update更新set。
两个set可以使用==比较,相等的条件是每一个元素相等。
求两个Set的交集:
intersection(_:) 和 formIntersection(_:)
求两个Set的并集:
union(_:) 和 formUnion(_:)
求两个Set的异或:
symmetricDifference(_:),formSymmetricDifference(_:)
求两个Set的差集:
subtracting(_:),subtract(_:)
还有几个集合的函数,判断是不是子集,判断有没有相交等。
Optional Set
Optional Set是和NS_OPTIONS对应的。
typedef NS_OPTIONS(NSUInteger,UIViewAnimationOptions) {
UIViewAnimationOptionLayoutSubviews = 1 << 0,UIViewAnimationOptionAllowUserInteraction = 1 << 1,UIViewAnimationOptionBeginFromCurrentState = 1 << 2,UIViewAnimationOptionRepeat = 1 << 3,UIViewAnimationOptionAutoreverse = 1 << 4,// @H_404_95@...
};
对应Swift中:
UIViewAnimationOptions.layoutSubviews
UIViewAnimationOptions.allowUserInteraction
UIViewAnimationOptions.beginFromCurrentState
UIViewAnimationOptions.repeat
UIViewAnimationOptions.autoreverse
UIViewAnimationOptions被定义为一个Set,这样就可以模拟bitmask了。
var opts = UIViewAnimationOptions.autoreverse
opts.insert(.repeat)
也可以使用运算符操作:
@H_404_95@let @H_404_95@val = UIViewAnimationOptions.autoreverse.rawValue | UIViewAnimationOptions.repeat.rawValue @H_404_95@let opts = UIViewAnimationOptions(rawValue: @H_404_95@val)
控制结构
if 语句
@H_404_95@if condition { statements } @H_404_95@else @H_404_95@if condition { statements } @H_404_95@else { statements }
条件语句不需要用括号括起来。
条件绑定(conditional binding),这个在Optional变量的使用中非常常见
@H_404_95@if @H_404_95@let prog = n.userInfo?["progress"] @H_404_95@as? Double {
self.progress = prog
}
等号后边是一个Optional Chain,可能返回nil,或者一个Double?,如果Optional Chain返回的是nil,则条件不成立,不会执行大括号内内容。如果Optional Chain不是nil,则==自动拆箱==,然后把拆箱后的值赋给prog,注意,==prog是Double而不是Double?==,prog的作用域在条件语句内部
Switch 语句
不用写break,自动break
@H_404_95@switch i {
@H_404_95@case 1:
@H_404_95@print("You have 1 thingy!")
@H_404_95@case 2:
@H_404_95@print("You have 2 thingies!")
@H_404_95@default:
@H_404_95@print("You have \(i) thingies!")
}
但是Switch的case语句必须覆盖所有情况,否则会发生编译错误。case语句也不能为空,至少要写一句break
可以在case中定义变量
@H_404_95@switch i {
case 1:
print("You have 1 thingy!")
case let n:
print("You have \(n) thingies!")
}
如果i不是1,就将i赋值给n(好像并没有什么卵用)
可以使用Range 匹配:
@H_404_95@switch i {
@H_404_95@case 1:
@H_404_95@print("You have 1 thingy!")
@H_404_95@case 2...10:
@H_404_95@print("You have \(i) thingies!")
@H_404_95@default:
@H_404_95@print("You have more thingies than I can count!")
}
Switch的另外一种用法:
func position(@H_404_95@for bar: UIBarPositioning) -> UIBarPosition { @H_404_95@switch @H_404_95@true { @H_404_95@case bar === @H_404_95@self.navbar: @H_404_95@return .topAttached @H_404_95@case bar === @H_404_95@self.toolbar: @H_404_95@return .bottom @H_404_95@default: @H_404_95@return .any } }
在switch中先指定结果(只能是true或者false),然后在case中判断表达式的结果是否和switch中相同。
case语句中还可以加filter:
@H_404_95@switch i {
@H_404_95@case @H_404_95@let j @H_404_95@where j < 0:
print("i is negative")
@H_404_95@case @H_404_95@let j @H_404_95@where j > 0:
print("i is positive")
@H_404_95@case 0:
print("i is 0")
@H_404_95@default:@H_404_95@break
}
上述代码等价于:
@H_404_95@switch i {
@H_404_95@case ..<0:
@H_404_95@print("i is negative")
@H_404_95@case 1...:
@H_404_95@print("i is positive")
@H_404_95@case 0:
@H_404_95@print("i is 0")
@H_404_95@default:@H_404_95@break
}
还可以判断对象类型:
@H_404_95@switch d {
case @H_404_95@is NoisyDog:
print("You have a noisy dog!")
case _:
print("You have a dog.")
}
@H_404_95@switch d { @H_404_95@case @H_404_95@let nd @H_404_95@as NoisyDog: nd.beQuiet() @H_404_95@case @H_404_95@let d: d.bark() }
注意:第二段代码中是as而不是as?,如果d是NoisyDog,nd才会被赋值,如果不是就不走这一个分支了。
Switch还可以比较tuple:
@H_404_95@switch (d["size"],d["desc"]) {
@H_404_95@case @H_404_95@let (size @H_404_95@as Int,desc @H_404_95@as String):
print("You have size \(size) and it is \(desc)")
@H_404_95@default:@H_404_95@break
}
如果switch的type是enum,那么还可以有很多花样:
enum MyError {
@H_404_95@case number(Int)
@H_404_95@case message(String)
@H_404_95@case fatal
}
@H_404_95@switch err {
case .number(let theNumber):
print("It is a number: \(theNumber)")
case let .message(theMessage):
print("It is a message: \(theMessage)")
case .fatal:
print("It is fatal")
}
@H_404_95@switch err {
@H_404_95@case .number(1...):
@H_404_95@print("It's a positive error number")
@H_404_95@case .number(..<0):
@H_404_95@print("It's a negative error number")
@H_404_95@case .number(0):
@H_404_95@print("It's a zero error number")
@H_404_95@default:@H_404_95@break
}
因为Optional本身是一个enum,所以可以这样写:
@H_404_95@switch i {
case .none: @H_404_95@break
case .some(1):
print("You have 1 thingy!")
case .some(let n):
print("You have \(n) thingies!")
}
fallthrough关键字:
@H_404_95@switch pep {
@H_404_95@case "Manny": @H_404_95@fallthrough
@H_404_95@case "Moe": @H_404_95@fallthrough
@H_404_95@case "Jack":
print("\(pep) is a Pep boy")
@H_404_95@default:
print("I don't know who \(pep) is")
}
if case
@H_404_95@if case let .number(n) = err {
print("The error number is \(n)")
}
这是一个switch语句的简写,直接将err enum的Associated value取出来。
条件赋值:
@H_404_95@let title : String = {
@H_404_95@switch type {
@H_404_95@case .albums:
@H_404_95@return "Albums"
@H_404_95@case .playlists:
@H_404_95@return "Playlists"
@H_404_95@case .podcasts:
@H_404_95@return "Podcasts"
@H_404_95@case .books:
@H_404_95@return "Books"
}
}()
??
func tableView(_ tv: UITableView,numberOfRowsInSection sec: Int) -> Int {
@H_404_95@return self.titles?.count ?? 0
}
self.titles是[String]?类型,如果不是nil,拆箱,获取count属性;否则,返回0
这段代码是什么意思?
@H_404_95@let someNumber = i1 @H_404_95@as? Int ?? i2 @H_404_95@as? Int ?? 0
while
两种方式:
@H_404_95@while @H_404_95@condition { statements } repeat { statements } @H_404_95@while @H_404_95@condition
for循环
for…in
@H_404_95@for i @H_404_95@in 1...5 {
@H_404_95@print(i) // 1,5
}
可以加filter:
@H_404_95@for i @H_404_95@in 0...10 @H_404_95@where i % 2 == 0 {
print(i) // 0,8,10
}
可以使用case简写:
let arr : [MyError] = [
.message("ouch"),.message("yipes"),.number(10),.number(-1),.fatal
]
@H_404_95@for @H_404_95@case let .number(i) in arr {
print(i) // 10,-1
}
stride
@H_404_95@for i in stride(from: 10,through: 0,@H_404_95@by: -2) {
print(i) // 10,0
}
从10开始到0,步幅为-2,迭代。
sequence
sequence是一个函数,有两个参数,一个是初始值,一个是生成函数。sequence返回的是一个生成器,只有用的时候才会计算并返回下一个值。
sequence用法:
let seq = sequence(first:1) {$0 >= 10 ? nil : $0 + 1}
@H_404_95@for i in seq { print(i) // 1,9,10 }
或者:
let seq = sequence(first:1) {$0 + 1}
@H_404_95@for i @H_404_95@in seq.prefix(5) {
@H_404_95@print(i) // 1,5
}
jumping
几个跳转的关键字:
fallthrough,是switch case中执行下一个case的意思。
continue,循环中,结束当前循环,从判断条件开始进行下一个循环。
break,在循环中,跳出当前循环,在switch…case中,跳出switch语句。
Swift中的循环可以带label,这样嵌套循环中的break和continue可以指定跳出哪一个循环。
outer: @H_404_95@for i @H_404_95@in 1...5 {
@H_404_95@for j @H_404_95@in 1...5 {
@H_404_95@print("\(i),\(j);")
@H_404_95@break outer
}
}
// 1,1;
Error
Swift采用throw…catch的方式来管理错误。
error定义:
enum MyFirstError : @H_404_95@Error {
@H_404_95@case firstMinorMistake
@H_404_95@case firstMajorMistake
@H_404_95@case firstFatalMistake
}
enum MySecondError : @H_404_95@Error {
@H_404_95@case secondMinorMistake(i:Int)
@H_404_95@case secondMajorMistake(s:String)
@H_404_95@case secondFatalMistake
}
使用:
@H_404_95@do {
// throw can happen here
} @H_404_95@catch MyFirstError.firstMinorMistake {
// catches MyFirstError.firstMinorMistake
} @H_404_95@catch @H_404_95@let err @H_404_95@as MyFirstError {
// catches all other cases of MyFirstError
} @H_404_95@catch MySecondError.secondMinorMistake(@H_404_95@let i) @H_404_95@where i < 0 {
// catches e.g. MySecondError.secondMinorMistake(i:-3)
} @H_404_95@catch {
// catches everything else
}
enum NotLongEnough : Error {
@H_404_95@case iSaidLongIMeantLong
}
func giveMeALongString(_ s:String) throws {
@H_404_95@if s.characters.count < 5 {
@H_404_95@throw NotLongEnough.iSaidLongIMeantLong
}
print("thanks for the string")
}
throws也是函数签名的一部分,giveMeALongString的函数签名就是(String) throws -> ()
含有throws的函数必须使用try调用,try语句必须在do…catch中,或者是另外一个throws的函数中。
try!,这个try!的意思是这个函数虽然被标记为throws,但是我知道它肯定不会throw错误,try!是不需要do…catch或者throws函数中使用的。但是如果真的throw的错误,程序就会Crash。
同样有一个try?的作用在try和try!之间。try?可以在任何地方调用,但是会吞掉error。如果函数返回一个Optional,则返回nil。
rethrows关键字
一个参数中有接受throws函数的函数,如果自己本身不会throw error,则可以标记为rethrows。标记了rethrows关键字的函数,可以接受throw函数或者非throw的函数,如果调用者传入的参数是非throw的函数,那么可以不使用try来调用这个函数。擦!
func receiveThrower(_ f:(String) throws -> ()) rethrows {
@H_404_95@try f("ok?")
}
func callReceiveThrower() { // no throws needed
receiveThrower { s @H_404_95@in // no @H_404_95@try needed
print("thanks for the string!")
}
}
Swift和OC的错误处理转换:
在OC中NSString有一个初始化函数:
- (instancetype)initWithContentsOfFile:(NSString *)path encoding:(NSStringEncoding)enc error:(NSError **)error;
需要传入一个NSError的地址,如果初始化失败,则返回nil,错误信息在NSError中。
在Swift中这个初始化函数变成了throw的:
init(contentsOfFile path: String,encoding enc: String.Encoding) throws
所以OC中的传入NSError地址的函数,全都被Swift中的throw函数替代。
do {
let f = // path to some file,maybe
let s = @H_404_95@try String(contentsOfFile: f)
// @H_404_95@... @H_404_95@if successful,do something with s @H_404_95@...
} catch CocoaError.fileReadNoSuchFile {
print("no such file")
} catch {
print(error)
}
由Swift的Error转换陈NSError的时候,domain属性不变,code是enum的case的index
do … break
给do语句块加一个label,在语句块内部使用break label的时候就能跳出语句块。
out: do {
// @H_404_95@...
@H_404_95@if somethingBadHappened {
@H_404_95@break out
}
// we won't get here if somethingBadHappened }
defer
defer的语句在离开当前的大括号之前一定会执行。比如一个释放资源的代码,在函数的任何一个退出分支都需要写,很容易遗忘。使用defer语句可以避免这个麻烦。
func doSomethingTimeConsuming() { defer { UIApplication.shared.endIgnoringInteractionEvents() } UIApplication.shared.beginIgnoringInteractionEvents() // @H_404_95@... do stuff @H_404_95@... @H_404_95@if somethingHappened { @H_404_95@return } // @H_404_95@... do more stuff @H_404_95@... }
defer语句要写在尽可能靠前的位置,如果在return之后,那么defer语句是不会执行的。
abort
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
就是直接让程序挂掉,应该是一种调试手段。
assert还是可以使用的。
guard
guard是为了解决if false return 这样的嵌套问题的。是一个简写。
guard let s = optionalString @H_404_95@else {@H_404_95@return}
// s @H_404_95@is now a String (@H_404_95@not an Optional)
相当于
let s = optionalString
@H_404_95@if s == nil {
@H_404_95@return
}
guard可以和try?合起来使用:
@H_404_95@let f = // path to some file,maybe
guard @H_404_95@let s = @H_404_95@try? String(contentsOfFile: f) @H_404_95@else {@H_404_95@return}
// s is now a String (not an Optional
和case合起来使用:
guard @H_404_95@case @H_404_95@let .number(n) = err @H_404_95@else {@H_404_95@return}
// n is now the extracted number
和表达式一起使用:
guard howMany() > 10 @H_404_95@else {@H_404_95@return}