之前已经看过好几次Swift的语法规定,但是至此也没用过几次,所以难免就出现了遗漏或者忘记和混淆的情况,所以,这次基于Swift4.2再做一次简单的总结。
1、多行字符串(""")
使用以"""
开头和结尾来表示一段多行内容的字符串,但是必须有如下规定:内容和"""
不能在同一行,即如下表达是错误的:
let constantStr = """I am a Constant string""" // 这是错误的
而如下是正确的:
let quotation = """
I said "I have \(apples) apples."
And then I said "I have \(apples + oranges) pieces of fruit."
"""
注:其他类型转换为字符串有如下两种方法:
- 使用String(var)来转换
- 使用
"\(var)"
的方式来转换
2、字典和数组
字典不再是使用大括号{}
来表示,数组和字典都是使用中括号[]
来表示,如下所示:
var shoppingList = ["catfish", "water", "tulips", "blue paint"]
shoppingList[1] = "bottle of water"
var occupations = [
"Malcolm": "Captain",
"Kaylee": "Mechanic",
]
occupations["Jayne"] = "Public Relations"
注1:如果是空的数组和字典,则必须指定其内容的类型,否则会报错:
注2:无论是空数组还是空字典,都可以指定其类型(空的必须指定),而指定内容类型有两种方式,我称之为前置和后置:
- 前置
let arr2:[String] = []
let arr22: Array = []
let dic2: [String: Int] = [:]
let dic4: Dictionary = [:]
- 后置
let arr1 = [String]()
let dic1 = [String: Int]()
注3:从字典中通过键获取到的值是可选类型的值;从数组中获取时要防止出现数组越界的情况
3、空合运算符(??
)
主要是对可选值的操作,格式如下:
let nonNil = nilVar ?? value 或者 let nonNil = nilVar ?? nonNilVar
需要说明的是,value一定是个非空值、非可选值,nonNilVar一定是个非可选类型的变量;而空合运算的结果一定是一个非可选值。
let var1 = ""
var var2: String? = nil
var var3 = var2 ?? var1
4、switch
变化很大,具体如下:
- 可比值的类型不再是整数,而是更多的基本上所有的类型,如tuple,range,string等,包括可选值类型,而我不关心的可以使用“_”来表示;也可以做值的绑定;也可以添加where来做进一步的限制
let var1 = ""
var var2: String? = nil
var var3 = var2 ?? var1
//if var2 { // 这样是有问题的
//
//}
switch var2 {
case "haha" :
print("I am \(var1)")
default:
print("I am default")
}
let tuple1 = ("zhou", 18)
switch tuple1 {
case (let name, 20):
print("name is \(name)")
case ("zhou", let age):
print("age is \(age)")
case (let name, var age): // 相当于替换了default
age += 0
print("name is \(name), age is \(age)") // 会走这里
//default:
// print("name is \(tuple1.0), age is \(tuple1.1)")
}
switch tuple1 {
case (let name, 20):
print("name is \(name)")
case ("zh", let age):
print("age is \(age)")
case let (name, age) where name.count > 0 && age > 20:
print("name is \(name), age is \(age)")
default:
print("name is \(tuple1.0), age is \(tuple1.1)")
}
- 每个case和最后的default不再需要使用break去终止当前case
- 对于多个case想执行同一个代码块,可以直接在case后跟随多个值,值与值之间使用逗号分隔开
var2 = "peng"
switch var2 {
case "haha" :
print("I am \(var1)")
case "zhou":
print("I am zhou")
case "peng", "zu":
print("I am double case")
default:
print("I am default")
}
- 在case语句里可以使用let等定义一个变量
let var1 = "zh"
switch var1 {
case "haha" :
print("I am \(var1)")
case "zhou":
print("I am zhou")
case "peng", "zu":
print("I am double case")
case let nonNilVar where nonNilVar.hasPrefix("z"):
print(nonNilVar)
default:
print("I am default")
}
- 一般来说default必须要有,但是如果穷举完了所有可能出现的情况,则不需要default:
enum Direction {
case East
case West
}
let direction = Direction.East
switch direction {
case .East:
print("East")
case .West:
print("West")
}
注1:利用省略default可以保证在匹配枚举的时候,保证所有的枚举都有对应的归属,这样,每次添加一个类型的枚举,相应的就会报错误
思考:如果不是像枚举这样可以轻松枚举所有情况的时候,可以使用其他办法代替default吗?(思路:类似于上面的let或者var的使用,或者使用“_”也可以)
let `switch` = 99
switch `switch` {
case 90...100:
print("优秀!")
case 80..<90:
print("良好!")
case 70..<80:
print("中等!")
case _:
print("")
//default:
// print("糟糕!")
}
5、for in
在swift下不再有类似于for(int i = 0; i < 20;i++)
这样的循环,取而代之的是for i in 0..<10
或for i in 0...10
,前者是小于,后者是小于等于。
6、类
class People {
// 存储属性
var name = ""
var age: Int = 0 {
// 观察者
willSet {
print(newValue)
}
didSet {
print(oldValue)
}
}
// 计算属性,有set则必有get,反之则不是必然
var getAge: Int {
get {
return age
}
set {
print(newValue)
}
}
// 初始化方法,如果不自己定义初始化方法,则会有一个默认的无参构造函数
// 如果自己实现了某个构造函数,则初始化对象时只能使用自己创建的构造函数
init(age: Int) {
}
}
class Student : People {
var school = ""
// 如果子类实现了其构造函数,则初始化时不能使用其父类的构造函数初始化
init(school: String, className: String) {
}
}
7、协议protocol
/**
* 协议
* 1、协议里只能定义计算属性,不能定义存储属性
* 2、类计算属性只能使用static修饰
* 3、属性只能使用var修饰,不能用let
* 4、可以定义构造函数
* 5、类、结构体、枚举都可遵守协议
* 6、协议也可以有继承关系
* 7、遵循协议的类、结构体、枚举等,对协议总的属性都可以做行为修改
* 8、协议中的计算属性在遵守该协议的类、结构体和枚举中可以重新实现为存储属性或者计算属性
* 9、协议中只能定义指定构造函数,不能指定便利构造函数
*/
protocol ExampleProtocol {
var name: String {get set}
var age: Int {get}
static var position: String{get set}
// class var positionCls: String{get set}
init(description: String)
func userInfo() -> String
}
protocol SubExampleProtocol: ExampleProtocol {
}
class ProtocolClass : SubExampleProtocol {
var name: String = ""
var age: Int {
get {
return 20
}
set {
}
}
static var position: String = ""
required init(description: String) {
}
func userInfo() -> String {
return ""
}
}
protocol PZProtocol {
var name:String {get}
}
class People : PZProtocol {
var name: String = ""
// var name: String {
// get {
// return ""
// }
// set {
//
// }
// }
}
let peo = People()
peo.name = "zhou"
print(peo.name)
8、扩展extension
- 需要明确的是扩展的到的是更多的计算属性和方法的实现,而不只是定义;
- 协议、类、枚举、结构体都可以扩展
- 扩展中可以包括:计算属性、方法和便利构造器(指定构造器不允许)、索引以及遵守某些协议
extension ExampleProtocol {
func extensionFunc() {
print("我是协议的扩展")
print(self.name)
}
}
enum ServerResponse {
case result(code: Int, data: [String : Any])
case fail(reason: String)
}
extension ServerResponse {
func enumExtension() {
}
}
class People {
init() {
}
init(name: String) {
}
init(name: String, age: Int) {
}
convenience init(age: Int) {
print("super")
self.init()
}
}
extension People {
convenience init(others: String) {
self.init()
}
}
9、大数字格式化(下划线"_")
如果碰到了类似于100000000这种大数字,swift提供了一种自定义的格式话的展示,使用下划线来按需分割,如下所示:
let paddedMillion = 1_000000 // 1_000_000或其他
print(paddedMillion)
10、关于startIndex和endIndex
在使用时,要优先判空,否则容易引起崩溃。
var str1 = ""
print("StartIndex:\(str1.startIndex), EndIndex:\(str1.endIndex)")
if str1.startIndex == str1.endIndex {
print("StartIndex == EndIndex")
}
print(str1[str1.startIndex]) // 这里会崩溃
print(str1[str1.startIndex..
11、方法的Label和Parameter
定义多个参数的时候,可以有相同的Label,但是为了增加方法的可读性,应尽量保证Label不一样,主要在调用的时候会有体现,label会在调用的地方展示出来,如果label不同,则表示的意思不一样,所以如果相同的话,就会傻傻分不清了:
func labelAndParameters(label parameter1: String, label parameter2: Int) {
}
labelAndParameters(label: <#T##String#>, label: <#T##Int#>) // 这是屌用,可以发现,label会出现在调用的地方
12、方法的默认值
相比较于OC以及一些其他的编程语言,Swift定义的方法的参数可以设置默认值,对于设置了默认值的参数,在调用的时候可以选择不传递该参数。
13、初始化--属性
需要说明的是,在枚举、结构体和类中都可以定义属性,只是其中的枚举中只能定义计算属性:
struct PZStruct {
var name:String
var age:Int
}
enum PZEnum {
// 不能是存储属性
// var name: String
// var age: Int
// 可以是计算属性
var school : String {
get {
return ""
}
set {
}
}
}
class PZClass {
var name: String
var age: Int
// 上面的属性没有初始化,则必须有相应的构造函数,并在构造函数中对属性做赋值
init() {
self.name = ""
self.age = 0
}
}
注意:
- 枚举中只能有计算属性
- 结构体中的属性在没有自定义初始化函数时,可以只定义存储属性而不必赋值;但是在类中,对于不可空类型的存储属性要么在定义的时候就完成赋值,要么在构造函数(必须是在指定构造函数)中完成赋值,而对于可空类型的则不需要。
14、属性观察器
需要明确的有以下几点:
- 父类的存储属性不能在子类中重写,如下面的age属性
- 只读的计算属性是不能添加属性观察器的,因为我们压根儿就没办法改变其只读性
- 类属性也可以添加属性观察器
- lazy修饰的属性是不能添加属性观察器的
- 计算属性没必要添加属性观察器,因为计算属性有明显的get和set方法,可以在set方法中直接知道值是否改变
- 全局存储属性也可以添加属性观察器
var globalVar : String = "" {
willSet {
}
}
class PZClass {
static var clsVar: String = "" {
willSet {
}
}
var name: String = ""
var age: Int = 0 {
willSet {
}
didSet {
}
}
var school : String {
get {
return ""
}
// 如果去了下面的set,则子类中就不用再添加观察者了
set {
}
}
// 懒属性是不能添加观察者的
// lazy var family : String = "" {
// willSet {
//
// }
// }
}
class PZPersonalInfo : PZClass {
// override var age: Int = 20 // 这样是不允许的
override var name: String {
willSet {
}
}
override var school: String {
willSet {
}
}
override var age: Int {
willSet {
}
}
}
15、lazy修饰的属性
- 不能是常量存储属性
- 不能是计算属性
class PZClass {
var name: String = ""
var age: Int = 0 {
willSet {
}
didSet {
}
}
var school : String {
get {
return ""
}
// 如果去了下面的set,则子类中就不用再添加观察者了
set {
}
}
// 懒属性是不能添加观察者的
// lazy var family : String = "" {
// willSet {
//
// }
// }
lazy var family1 = ""
// lazy let family2 = "" // 懒属性不能是常量
// 懒属性不能是计算属性
// lazy var family3: String {
// get {
// return ""
// }
// set {
//
// }
// }
}
16、类属性(class和static两种修饰)
- class、enum、struct都可以定义类属性,但是枚举里不能定义对象属性
enum PZEnum {
// var name = ""
static var name = ""
}
- static修饰的子类没办法重写,class修饰的子类可以重写
- 子类没办法重写父类的存储属性(只能重写为计算属性),但是可以重写父类的计算属性,所以要想子类重写父类计算属性和方法只能是父类的相应地方法和计算属性使用class修饰
class People {
// class var name = ""
static var name = ""
static let age = 0
class var computedClsName: String {
return ""
}
static var computedStaticName: String {
return ""
}
}
class Student : People {
// 不能重写父类的static修饰的方法
// static var computedStaticName: String {
// return ""
// }
override static var computedClsName: String {
get {
return ""
}
set {
}
}
}
- 类存储属性只能使用static修饰,但是类计算属性即可以使用class也可以使用static(其实类计算属性和类方法是一样的,都是一种方法)
- 类存储属性即可以是常量也可以是变量(var和let)
- 类属性也是可以添加观察器的(见前面)
- 类存储属性必须在定义的时候就赋值(可以想想为什么,使用结构体更容易得出结论)
- 类存储属性的初始化赋值是在第一次访问的时候完成的(这一点和lazy相似哦,但是本质是不同的)
class People {
// class var name = ""
static var name = ""
static let age = 0
class var computedClsName: String {
return ""
}
static var computedStaticName: String {
return ""
}
}
//People.age = 20
People.name = "zhou"
print(People.name)
17、索引(subscript
)
- 必须用关键字subscript
- 定义和方法的定义一样
- 实现和计算属性相同
- 调用和使用数组、字典等一样
class People {
var name = ""
var age = 20
subscript(name: String , age: Int) -> String {
get {
return self.name
}
set {
self.name = name
self.age = age
}
}
}
let people = People()
people["zhou", 20] = ""
print(people.name) // zhou
print(people["peng", 20]) // zhou
18、重写
- 对于类属性和方法,只有class修饰的才能被子类重写,因此类属性中只有计算属性才能被子类重写
- 子类不能直接将父类的存储属性重写为自己的存储属性,但是可以重写为计算属性
- 重写只能放大权限,至少不能使权限低于父类的权限,如果父类的是只读的,则子类只能是只读或者是读写都可以的
- 常量不能重写(⚠️)
class People {
var name = ""
var age = 20
subscript(name: String , age: Int) -> String {
get {
return self.name
}
set {
self.name = name
self.age = age
}
}
let address = ""
}
class Student : People {
override var name: String {
get {
return super.name
}
set {
super.name = newValue
}
}
}
19、继承
- 值类型是不存在继承的(如枚举和结构体),只有引用类型存在(类)
- 子类不会自动继承父类的构造器
20、构造器(构造函数)
Swift提供了两种构造器:指定构造器和便利构造器(用convience修饰的指定构造器)
- 指定构造器不能自己调用自己的构造器(指定和便利)
- 便利构造器必须且只能调用自己的构造器(指定和便利)
- 指定构造器只能调用直接父类的指定构造器
21、构造器自动继承
一般来说,子类是不会自动默认继承父类的构造器的,但是下面的方法除外:
- 如果子类没有实现任何的自己的指定构造器(和便利构造器无关),则会默认继承所有的父类的构造器(指定构造器和便利构造器)
- 如果子类重写了父类的所有的指定构造器,则子类会默认继承父类的便利构造器,如下代码,如果将Student中的某个重写的指定构造器去掉,则会报错:
class People {
init() {
}
init(name: String) {
}
init(name: String, age: Int) {
}
convenience init(age: Int) {
print("super")
self.init()
}
}
class Student : People {
override init() {
super.init()
}
override init(name: String) {
super.init()
}
override init(name: String, age: Int) {
super.init()
}
init(school:String) {
super.init()
}
}
let stu = Student.init(age: 20)
注意⚠️:父类的指定构造器是可以被子类重写并且定义为便利构造器的
23、可失败构造器
有时候我们初始化一个变量的时候,会给构造函数传递参数,但是这些参数不一定都是满足条件的,所以我们可以在条件不满足的时候,返回nil,表示初始化失败。
注意⚠️:可失败构造器和其他构造器是一样的,且子类可以将父类的可失败构造器重写成非可失败构造器