一、Swift预览
1.1 简单值
let作为常量 var作为变量,常量只有在定义时赋值一次,可以多次使用。如
let myConstant = 79 //常量
var myVariable = 80 //变量
常量和变量在设定值时必须采用相同格式。但并不需要定义精准的类型。创建一个常量或变量时提供一个值,让编译器判断其类型。在上面的例子中,编译器指定 myVariable 是一个整数,因为它的初始值是整数
若初始化时未提供足够信息(没有初始值),可以在变量后面指定类型,用冒号隔开。
let myConstant1 = 79
let myConstant2 = 80.0
let myConstant3 : Double = 81.0
值在转化为另一种类型时从不具有隐含性。如果需要转化值到另一种类型,请明确性地为值进行格式转换。
let label = "The width is "
let width = 94
let widthLabel = label + String(width)
更简单的方法将值转换为String:将值写在括号中,并在括号前添加一个反斜杠。例
let apples = 3
let oranges = 5
let appleSummary = "I have \(apples) apples."
let fruitSummary = "I have \(apples + oranges) pieces of fruit."
通过 [] 创建一个数组和字典,通过 index 和 key 获取对应的值
var shoppingList = ["catfish", "water", "tulips", "blue paint"]
shoppingList[1] = "bottle of water"
var occupations = [
"Malcolm": "Captain",
"Kaylee": "Mechanic",
]
occupations["Jayne"] = "Public Relations"
创建空数组和字典,以及初始化语法。
let emptyArray = String[]()
let emptyDictionary = Dictionary()
为了防止类型信息被更改,空数组列用[],空字典用[:]进行初始化 - 例如,为变量赋新值和给函数传递参数的时候。
shoppingList = []
1.2 流程控制
使用 if 和 switch 判断条件,使用 for-in 、 for 、 while 和 do-while 处理循环。条件和循环变量的括号可以省略,语句体的大括号是必须的。
let personalScore = [90,129,49,28,109]
var teamScore = 0
for score in personalScore{
if score > 60 {
teamScore += 3
}else{
teamScore += 1
}
}
在 if 语句中,条件必须是一个布尔表达式 —— 这意味着像 if score { ... } 这样的代码将报错,而不会隐形地与 0 做对比。有些变量的值是可选的。一个可选的值如果是一个具体的值或者是 nil ,那表明这个值缺失。在类型后面加一个 ? 来标记这个变量的值是可选的。
var testName: NSString? = ""
testName = nil
if let name = testName {
print("success \(name)")
}else{
print("what the fuck, guy")
}
如果变量的可选值是 nil ,条件会判断为 false ,并且大括号中的代码会被跳过。如果不是nil,会将值赋给let后面的常量,这样代码块中就可以使用这个值了。
使用switch 支持任意类型的数据以及各种比较操作——不仅仅是整数以及测试相等。
let vegetable = "西红柿"
switch vegetable {
case "土豆":
print( "我就是土豆.")
case "油麦", "苦菊":
print("我就是绿叶菜.")
case let x where x.hasSuffix("土"):
print( "\(x) 我就是土里的")
default:
print("我不在你的菜篮子里")
}
可以在循环中使用 ..< 和 ... 来表示范围
var total = 0
// ..< 即 0到100 不包括100 ... 即0到100 包括100
for i in 0..<100 {
if i % 2 == 0 {
total += i
}
}
print("100 以内的偶数的和为\(total)")
1.3 函数与闭包
使用 func 来声明一个函数,通过函数的名字和参数来调用函数。使用 -> 指定函数返回值(分离了返回值和参数)
func getMaxValue(num1 : Int , num2 : Int) -> Int{
if num1 > num2 {
return num1
}else{
return num2
}
}
var max = getMaxValue(num1: 100, num2: 120)
print("最大值为\(max)")
返回值为元祖
func getVegetablesPrice()->( Double , Double){
return (3.35,4.35)
}
var (price1,price2) = getVegetablesPrice()
print("price1 = \(price1) price2 = \(price2)")
//或者
var prices = getVegetablesPrice()
print("price1 = \(prices.0) price2 = \(prices.1)")
1.4 对象与类
通过类名和 () 创建一个类的实例,实例通过点语法访问属性和方法。
var shape = Shape()
shape.numberOfSides = 7
var shapeDescription = shape.simpleDescription()
如果子类需要重写父类的方法,使用 override 关键字;如果没有使用 override 就重写父类的方法,编译器将会报错。同样的编译器会检查 override 是否在父类中。
为了更简单的存储属性,属性可以有 setter 和 getter 方法
var shapeLength : Int{
get{
return shapeLength
}
set{
if newValue < 0 {
shapeLength = 0
}else{
shapeLength = newValue
}
}
}
在shapeLength的 setter 中,新值有一个隐式的名称是newValue。可以在set之后的括号中提供一个明确的名称.如果不需要计算属性值,但是需要在设置新值之前或者之后执行一些代码,可以通过 willSet 和 didSet 完成
使用set 必须要用get 但是get 可以单独使用(只有get的叫只读计算属性)
属性改变之前触发willSet方法,属性改变之后触发didSet方法
在给属性添加观察者之前必须要明确申明属性的类型,否则编译器会报错
属性初始化时,willSet和didSet都不会调用,只有在设置属性值时才会调用
当设置的值和原来的值一样时,willSet和didSet也会被调用
willSet有一个newValue参数,,,didSet有一个newValue参数
常量和变量的命名
可以用任何喜欢的字符作为常量和变量名,包括 Unicode 字符:
let π = 3.14159
let 你好 = "你好世界"
let 🐶🐮 = "dogcow"
常量与变量名不能包含数学符号,箭头,保留的(或者非法的)Unicode 码位,连线与制表符。也不能以数字开头,但是可以在常量与变量名的其他地方包含数字。
一旦将常量或者变量声明为确定的类型,就不能使用相同的名字再次进行声明,或者改变其存储的值的类型。同时,也不能将常量与变量进行互转。
注意:
如果需要使用与Swift保留关键字相同的名称作为常量或者变量名,可以使用反引号(`)将关键字包围的方式将其作为名字使用。无论如何,应当避免使用关键字作为常量或变量名,除非别无选择。
可以更改现有的变量值为其他同类型的值,在下面的例子中,friendlyWelcome的值从"Hello!"改为了"Bonjour!":
var friendlyWelcome = "Hello!"
friendlyWelcome = "Bonjour!"
// friendlyWelcome 现在是 "Bonjour!"
与变量不同,常量的值一旦被确定就不能更改了。尝试这样做会导致编译时报错:
let languageName = "Swift"
languageName = "Swift++"
// 这会报编译时错误 - languageName 不可改变
输出常量和变量
可以用print(_:separator:terminator:)函数来输出当前常量或变量的值:
print(friendlyWelcome)
// 输出 "Bonjour!"
print(_:separator:terminator:) 是一个用来输出一个或多个值到适当输出区的全局函数。如果用 Xcode,print(_:separator:terminator:) 将会输出内容到“console”面板上。separator 和 terminator 参数具有默认值,因此调用这个函数的时候可以忽略它们。默认情况下,该函数通过添加换行符来结束当前行。如果不想换行,可以传递一个空字符串给 terminator 参数--例如,print(someValue, terminator:"") 。关于参数默认值的更多信息,请参考默认参数值。
Swift 用字符串插值(string interpolation)的方式把常量名或者变量名当做占位符加入到长字符串中,Swift 会用当前常量或变量的值替换这些占位符。将常量或变量名放入圆括号中,并在开括号前使用反斜杠将其转义:
print("The current value of friendlyWelcome is \(friendlyWelcome)")
// 输出 "The current value of friendlyWelcome is Bonjour!
注意:
字符串插值所有可用的选项,请参考字符串插值。
分号
与其他大部分编程语言不同,Swift 并不强制要求在每条语句的结尾处使用分号(;),当然,也可以按照你自己的习惯添加分号。有一种情况下必须要用分号,即打算在同一行内写多条独立的语句:
let cat = "🐱"; print(cat)
// 输出 "🐱"
类型别名
类型别名(type aliases)就是给现有类型定义另一个名字。可以使用typealias关键字来定义类型别名。
当你想要给现有类型起一个更有意义的名字时,类型别名非常有用。假设正在处理特定长度的外部资源的数据:
typealias AudioSample = UInt16
定义了一个类型别名之后,可以在任何使用原始名的地方使用别名:
var maxAmplitudeFound = AudioSample.min
// maxAmplitudeFound 现在是 0
本例中,AudioSample被定义为UInt16的一个别名。因为它是别名,AudioSample.min实际上是UInt16.min,所以会给maxAmplitudeFound赋一个初值0。
可选类型
使用可选类型(optionals)来处理值可能缺失的情况。可选类型表示:
有值,等于 x
或者
没有值
注意:
C 和 Objective-C 中并没有可选类型这个概念。最接近的是 Objective-C 中的一个特性,一个方法要不返回一个对象要不返回nil,nil表示“缺少一个合法的对象”。然而,这只对对象起作用——对于结构体,基本的 C 类型或者枚举类型不起作用。对于这些类型,Objective-C 方法一般会返回一个特殊值(比如NSNotFound)来暗示值缺失。这种方法假设方法的调用者知道并记得对特殊值进行判断。然而,Swift 的可选类型可以让你暗示任意类型的值缺失,并不需要一个特殊值。
来看一个例子。Swift 的 Int 类型有一种构造器,作用是将一个 String 值转换成一个 Int 值。然而,并不是所有的字符串都可以转换成一个整数。字符串 "123" 可以被转换成数字 123 ,但是字符串 "hello, world" 不行。
下面的例子使用这种构造器来尝试将一个 String 转换成 Int:
let possibleNumber = "123"
let convertedNumber = Int(possibleNumber)
// convertedNumber 被推测为类型 "Int?", 或者类型 "optional Int"
因为该构造器可能会失败,所以它返回一个可选类型(optional)Int,而不是一个 Int。一个可选的 Int 被写作 Int? 而不是 Int。问号暗示包含的值是可选类型,也就是说可能包含 Int 值也可能不包含值。(不能包含其他任何值比如 Bool 值或者 String 值。只能是 Int 或者什么都没有。)
nil
可以给可选变量赋值为nil来表示它没有值:
var serverResponseCode: Int? = 404
// serverResponseCode 包含一个可选的 Int 值 404
serverResponseCode = nil
// serverResponseCode 现在不包含值
注意:
nil不能用于非可选的常量和变量。如果你的代码中有常量或者变量需要处理值缺失的情况,请把它们声明成对应的可选类型。
如果你声明一个可选常量或者变量但是没有赋值,它们会自动被设置为 nil:
var surveyAnswer: String?
// surveyAnswer 被自动设置为 nil
注意:
Swift 的 nil 和 Objective-C 中的 nil 并不一样。在 Objective-C 中,nil 是一个指向不存在对象的指针。在 Swift 中,nil 不是指针——它是一个确定的值,用来表示值缺失。任何类型的可选状态都可以被设置为 nil,不只是对象类型。
空合运算符
空合运算符(a ?? b)将对可选类型 a 进行空判断,如果 a 包含一个值就进行解封,否则就返回一个默认值 b。表达式 a 必须是 Optional 类型。默认值 b 的类型必须要和 a 存储值的类型保持一致。
空合运算符是对以下代码的简短表达方法:
a != nil ? a! : b
上述代码使用了三目运算符。当可选类型 a 的值不为空时,进行强制解封(a!),访问 a 中的值;反之返回默认值 b。无疑空合运算符(??)提供了一种更为优雅的方式去封装条件判断和解封两种行为,显得简洁以及更具可读性。
注意: 如果 a 为非空值(non-nil),那么值 b 将不会被计算。这也就是所谓的短路求值。
下文例子采用空合运算符,实现了在默认颜色名和可选自定义颜色名之间抉择:
let defaultColorName = "red"
var userDefinedColorName: String? //默认值为 nil
var colorNameToUse = userDefinedColorName ?? defaultColorName
// userDefinedColorName 的值为空,所以 colorNameToUse 的值为 "red"
userDefinedColorName 变量被定义为一个可选的 String 类型,默认值为 nil。由于 userDefinedColorName 是一个可选类型,我们可以使用空合运算符去判断其值。在上一个例子中,通过空合运算符为一个名为 colorNameToUse 的变量赋予一个字符串类型初始值。 由于 userDefinedColorName 值为空,因此表达式 userDefinedColorName ?? defaultColorName 返回 defaultColorName 的值,即 red。
另一种情况,分配一个非空值(non-nil)给 userDefinedColorName,再次执行空合运算,运算结果为封包在 userDefaultColorName 中的值,而非默认值。
userDefinedColorName = "green"
colorNameToUse = userDefinedColorName ?? defaultColorName
// userDefinedColorName 非空,因此 colorNameToUse 的值为 "green"
区间运算符
闭区间运算符
闭区间运算符(a...b)定义一个包含从 a 到 b(包括 a 和 b)的所有值的区间。a 的值不能超过 b。 闭区间运算符在迭代一个区间的所有值时是非常有用的,如在 for-in 循环中:
for index in 1...5 {
print("\(index) * 5 = \(index * 5)")
}
// 1 * 5 = 5
// 2 * 5 = 10
// 3 * 5 = 15
// 4 * 5 = 20
// 5 * 5 = 25
半开区间运算符
半开区间(a..<b)定义一个从 a 到 b 但不包括 b 的区间。 之所以称为半开区间,是因为该区间包含第一个值而不包括最后的值。
半开区间的实用性在于当你使用一个从 0 开始的列表(如数组)时,非常方便地从0数到列表的长度。
let names = ["Anna", "Alex", "Brian", "Jack"]
let count = names.count
for i in 0..<count {
print("第 \(i + 1) 个人叫 \(names[i])")
}
// 第 1 个人叫 Anna
// 第 2 个人叫 Alex
// 第 3 个人叫 Brian
// 第 4 个人叫 Jack
字符串索引
每一个String值都有一个关联的索引(index)类型,String.Index,它对应着字符串中的每一个Character的位置。
前面提到,不同的字符可能会占用不同数量的内存空间,所以要知道Character的确定位置,就必须从String开头遍历每一个 Unicode 标量直到结尾。因此,Swift 的字符串不能用整数(integer)做索引。
使用startIndex属性可以获取一个String的第一个Character的索引。使用endIndex属性可以获取最后一个Character的后一个位置的索引。因此,endIndex属性不能作为一个字符串的有效下标。如果String是空串,startIndex和endIndex是相等的。
通过调用 String 的 index(before:) 或 index(after:) 方法,可以立即得到前面或后面的一个索引。您还可以通过调用 index(_:offsetBy:) 方法来获取对应偏移量的索引,这种方式可以避免多次调用 index(before:) 或 index(after:) 方法。
你可以使用下标语法来访问 String 特定索引的 Character。
let greeting = "Guten Tag!"
greeting[greeting.startIndex]
// G
greeting[greeting.index(before: greeting.endIndex)]
// !
greeting[greeting.index(after: greeting.startIndex)]
// u
let index = greeting.index(greeting.startIndex, offsetBy: 7)
greeting[index]
// a
试图获取越界索引对应的 Character,将引发一个运行时错误。
greeting[greeting.endIndex] // error
greeting.index(after: endIndex) // error
使用 characters 属性的 indices 属性会创建一个包含全部索引的范围(Range),用来在一个字符串中访问单个字符。
for index in greeting.characters.indices {
print("\(greeting[index]) ", terminator: "")
}
// 打印输出 "G u t e n T a g ! "
注意: 可以使用 startIndex 和 endIndex 属性或者 index(before:) 、index(after:) 和 index(_:offsetBy:) 方法在任意一个确认的并遵循 Collection 协议的类型里面,如上文所示是使用在 String 中,也可以使用在 Array、Dictionary 和 Set中。
插入和删除
调用 insert(_:atIndex:) 方法可以在一个字符串的指定索引插入一个字符,调用 insert(contentsOf:at:) 方法可以在一个字符串的指定索引插入一个段字符串。
var welcome = "hello"
welcome.insert("!", at: welcome.endIndex)
// welcome 变量现在等于 "hello!"
welcome.insert(contentsOf:" there".characters, at: welcome.index(before: welcome.endIndex))
// welcome 变量现在等于 "hello there!"
调用 remove(at:) 方法可以在一个字符串的指定索引删除一个字符,调用 removeSubrange(_:) 方法可以在一个字符串的指定索引删除一个子字符串。
welcome.remove(at: welcome.index(before: welcome.endIndex))
// welcome 现在等于 "hello there"
let range = welcome.index(welcome.endIndex, offsetBy: -6)..<welcome.endIndex
welcome.removeSubrange(range)
// welcome 现在等于 "hello"
注意: 可以使用 insert(_:at:)、insert(contentsOf:at:)、remove(at:) 和 removeSubrange(_:) 方法在任意一个确认的并遵循 RangeReplaceableCollection 协议的类型里面,如上文所示是使用在 String 中,也可以使用在 Array、Dictionary 和 Set 中。