swift3学习笔记
一直没有时间好好看一下swift,最近复习了一遍语法,这里记录swift学习过程中遇到的一些问题和要点,和Object-C的一些相关特性这里也不做介绍,只记录swift特有的一些特性
一、基础语法
二、数据类型
TODO:Any,AnyObject,三、常量变量
// 常量:使用let声明,赋值后就不能再修改 let a = NSMutableArray() let b = 12 let c: Float = 12 // 类型标注(type annotation) let d = b + 12 a.addObject(11) // str == [11] let e = a // str == [11],d == [11] a.addObject(12) // str == [11,12],d == [11,12] // 变量:使用var声明 var f: Double? = 12 var g = "hello world" 类型标注在声明变量和常量的时候可以如果可以由编译器自动识别,可以不用制定类型,如下 let a = 12 //常量a会编译为Int类型 var b = 1.3 //变量b会编译为Double类型 我们也可以指定类型 let a: Double = 12 let b: Float = 1.3 可以在一行声明多个变量/常量,在最后一个声明类型 var red,green,blue: UInt 四、序列和集合1. 数组Arrayswift的数组可以是有类型的(泛型),存放同类型的数据,如果添加一个错误的类型会报编译错误,默认情况下编译器会自动识别 //1. 数组的写法为:Array<Int>,也可以简写成[Int] //2. 数组初始化与NSArray类似,直接用中括号括起来,里面值用逗号隔开 var array0 = [Int]() var array1: [Int] = [1,3,5,7,9] var array2: Array<Int> = array1 array1.append(11) // [1,9,11] array1.insert(0,atIndex: 0) // [0,1,11] array1.isEmpty // False array1.count // 7 // 3. 如果初始化时不指定类型,而编译器也不能识别出类型,这时候,会被当成NSArray处理 var array3 = [] // array3 为 NSArray类型的空数组 // 4. 如果声明的时候使用不同的类型,编译器会把数组识别为NSObject类型 var array4 = ["fdsa",121] // array4 为 Array<NSObject> 类型 // 5. 集合支持加法运算,相当于NSMutableArray的addObjectsFromArray array1 += [2,4,6,8,10] // [1,2,10,11] // 6. 使用let声明的数组不可变,不能修改数组array3 let array5: [Int] = [1,9] //array5.append(2) // 报编译错误 // 7. 集合使用下标索引,支持区间索引,区间不可越界 var array6: [Int] = [1,9] array6[1] = 4 // [1,9] array6[1...3] = [2,4] // [1,9] array6[0...2] = array6[1...3] // [2,9] // 8. 迭代数组的时候,如果需要索引,可以用enumerate方法 for (index,value) in array4.enumerated() { //do something } 2. 字典Dictionary与数组类型一样,字典也支持泛型,其键值类型都可以指定或有编译器识别,其中Key的类型,必须是可Hash的,swift中基础数据类型都是可hash的(String、Int、Double和Bool) // 1. 用法与oc类似,初始化不需要@ var dict1 = ["key1": 1,"key2": 2,"key3": 3] // 2. 声明方式 var dict2: Dictionary<String,Int> = dict1 //dict2与dict1不是一个对象 var dict3: [String: Int] = dict1 //通常采用这种方式声明类型 // 3. 不声明类型,编译器又无法识别,则为NSDictionary var dict4 = [:] var dict5: [Int: String] = [:] // 4. 修改或添加键值对 dict1["key3"] = 4 // 5. 删除键 dict1["key3"] = nil // 6. key不存在不报错,返回可空类型nil let value4 = dict1["key4"] // 7. 字典迭代返回key/value元组,类似python for (key,value) in dict1 { print("(key) = (value)") }
3. SetSet集合用于存放无序不重复的对象,用法与数组类似,重复的项会被忽略 var s: Set<Int> = [1,7] // [1,7] s.count s.isEmpty s.insert(3) s.remove(3) s.contains(3) 集合操作 let oddDigits: Set = [1,9] let evenDigits: Set = [0,8] let singleDigitPrimeNumbers: Set = [2,7] //合操作 oddDigits.union(evenDigits).sort() // [0,9] //交操作 oddDigits.intersection(evenDigits).sorted() // [] //减操作 oddDigits.subtracting(singleDigitPrimeNumbers).sorted() // [1,9] //不重叠集合 oddDigits.symmetricDifference(singleDigitPrimeNumbers).sorted() // [1,9]
4. 元组Tuple与python类似,swift也支持元组,可以很方便的使用元组包装多个值,也使得函数返回多个值变得更加方便,特别是临时组建值得时候
// 1. 声明一个元组,元组支持任意类型 let httpError1 = (404,"Not Found") let point = (100,50) // 2. 可以分别赋值 let (x,y) = point print(x) // 100 print(y) // 50 // 3. 使用下标取元组元素,下标从0开始 print(httpError1.0) // 404 print(httpError1.1) // Not Found // 4. 可以给数组元素取名 let httpError2 = (code: 404,errorMessage: "Not Found") print(httpError2.code) // 404 print(httpError2.errorMessage) // Not Found // 5. 可以用下划线表示忽略部分值 let (a,_) = point
5. 字符串Stringswift字符串是由Character字符组成的集合,支持 // 1. 与NSString不同,声明不需要@前缀,支持转移字符 let name1 = "bomon" // 2. 空串(下面两种方式等价) let name2 = "" let name3 = String() // 3. 字符串由字符Character组成,定义字符 let character1: Character = "!" // 4. 常见属性,方法 name1.isEmpty // 判空 name1.characters.count // 获取字符串的字符数 name1.uppercaseString name1.lowercaseString name1.hasPrefix("bo") name1.hasSuffix("mo") // 5. 加法运算 let hello = "hello " + name1 // hello bomon // 6. 比较(比较值,而不是地址) let name4 = "b" + "omon" name4 == name1 // True // 7. 字符串插值(使用反斜杠和括号站位) let city = "广州" let hello2 = "I'm (name1) from (city)" // 8. 格式化字符串 let f = 123.3233 var s = String(format: "%.2f",f) //123.32 6. 集合的赋值和拷贝行为swift的集合通常有Array和Dictionary,他们在赋值或传递的时候,行为上有所不同,字典类型Dictionary或数组类型Array在赋值给变量或常量的时候,只要有做修改,就会进行值拷贝,并且不会作用到原来变量上 var dict1 = ["a": 1,"b": 2] var dict2 = dict1 print(dict1 == dict2) // true dict2["a"] = 3 // 修改dict2 print(dict1 == dict2) // false var arr1 = ["a","b"] var arr2 = arr1 print(arr1 == arr2) // true arr1[0] = "c" // 修改arr1 // arr1.append("c") print(arr1 == arr2) // false 当数组或字典作为参数传递给函数的时候,由于在Swift3中不推荐使用变量参数,故所有函数参数不可变,故也不进行拷贝 五、可选类型(可空类型)swift加入了可空类型让我们使用数据的时候更为安全,我们需要在可空的地方使用可选类型声明该变量可为空,不能给非可选类型设值 1. 声明// 1. 声明可选类型,在类型后面加上? var obj1: NSObject? obj1 = NSObject() obj1 = nil // 2. 不能给一个可选类型赋nil,下面会报错, var obj = NSObject() obj = nil // 3. 如果声明可选变量时没有赋值,则默认为nil var i: Int? // 4. 一个函数返回一个可选类型 func getdog() -> String? { return "wangcai" } // 5. 不能把可选类型赋值给非可选类型,下面会报错 let cat: String = dog 2. 强制解析可选类型不能直接使用,需要通过取值操作符 var b: Int? var a: Int a = 12 b = 13 let c = a + b! // 先对b取值,再运算 var b: Bool? = nil if b! { // b为空,编译不报错,运行时报错 print("true") } else { print("false") } 3. 可选绑定使用可选绑定可以判断一个可选类型是否有值,如果有值,则绑定到变量上,如果没有值,返回false,使用 var i: Int? = nil if let number = i { print("(number)") } else { print("nil") } 可选绑定还支持绑定条件 var i: Int? = nil if let number = i where i > 10 { print("i不为空且大于10 (number)") } else { print("nil") } 可选绑定还支持多个绑定,不许所有的绑定都满足才返回true if let firstNumber = 1,let secondNumber = 2) } // 输出 "4 < 42 < 100" if let firstNumber = Int("4") { if let secondNumber = Int("42") { if firstNumber < secondNumber && secondNumber < 100 { print("(firstNumber) < (secondNumber) < 100") } } } 4. 隐式解析声明类型的时候可以使用隐式解析,即在使用可选变量的时候自动取值,不需要调用 // 一个函数返回一个可选类型 func getdog() -> String? { return "wangcai" } //假定我们通过getdog方法返回的值一定不为空 var dog: String? = getdog() let cat: String = dog! // 使用前需要通过!强制取值 使用dog的时候都需要取值我们觉得太麻烦了,可以声明成隐式可选类型,使用的时候自动取值 var dog: String! = getdog() // 实际上dog还是可选类型,只是使用的时候回自动取值 let cat: String = dog // 在使用dog的时候会自动进行取值,不需要取值操作符 5. 可选类型自判断链接在使用可选类型之前,需要进行判断其是否有值,才能使用,通过 class Person { var favDog: Dog? } class Dog { var name: String? } var p = Person() var d = Dog() // p.favDog = d p.favDog?.name = "tobi" // 如果p.favDog为空,不设置name if let name = p.favDog?.name { // p.favDog不为空且p.favDog.name不为空 } else { // p.favDog为空或p.favDog.name为空 } 自判断链接还支持多连接如 let identifier = john.residence?.address?.buildingIdentifier 6. 可选关联运算符可选关联运算符可对可选类型进行拆包,如果可选类型对象为nil,返回第二个操作数,第二个操作数类型必须和第一个操作数同类型(可选或不可选) let defaultColorName = "red" var userDefinedColorName: String? // defaults to nil var colorNameToUse = userDefinedColorName ?? defaultColorName
六、运算符swift运算符在原有的基础上做了一些改进,还添加了一下更高级的用法,还有新的运算符
七、流程控制swift使用三种语句控制流程: let names = ["Anna","Alex","Brian","Jack"] for name in names { print("Hello,(name)!") } //如果不需要使用到迭代的值,使用下划线`_`忽略该值 for _ in 1...10 print("hello") 流程控制语句的条件返回值必须是Bool,下面会报错 var dd: Bool? = true if dd { print("fd") } 条件判断可以与 var dd: Bool? = true if let ee = dd { print("fd") } 在Swift2.0以后,不支持 repeat { print("repeat while : (j)") j++ } while j < 3 guard-else翻译为保镖模式,在执行操作前,进行检查,如果不符合,则拦截,使用方式与if有些类似,如果与let结合使用,可以对可选类型解包,先看看普通的 func test(i: Int?) { if let i = i where i > 0 { // 符合条件的处理 return } // 不符合条件的处理 } 上面的处理把条件放在了条件判断内部,使用guard与之相反,把正确的情况放在最外部,而异常情况放在条件判断内部 func test(i: Int?) { guard let i = i where i > 0 else { // 在这里拦截,处理不符合条件的情况 return } // 符合条件的处理,这个时候已经对i进行了拆包,i是非可选类型,可以直接使用 print(i) } 保镖模式可以避免代码中过多的流程判断代码导致过多的代码块嵌套,增强可读性
switch-case
// 定义一个枚举 enum HttpStatus { case ServerError case NetworkError case Success case Redirect } var status = HttpStatus.Redirect switch status { // case可以接收多个值 case HttpStatus.ServerError,HttpStatus.NetworkError: print("error") // case语句结束显式写break,除非是空语句 case .Redirect: // 如果编译器可以识别出枚举类型,可以省略枚举名 print ("redirect") fallthrough // 像C语言一样,继续执行下一条case case HttpStatus.Success: print("success") } //元组,区间 let request = (0,"https://baidu.com") switch request { case (0,let a): // 支持绑定 print(a) case let (a,b) where a == 1: // 绑定可以卸载元组外面,支持where判断 print("cancel (b)") case (2...10,_): // 支持区间,支持忽略值 print("error") default: print("unknown") } // case可以与where进行进一步判断 let request2 = (0,10) switch request2 { case (0,let y) where y < 5: "success" //被输出 case (0,let y) where y >= 5: "error" //被输出 default: "unknown" } case除了和swift一起使用外,还支持与if语句结合使用,用法与switch一样 let bb = (12,"bomo") if case (1...20,let cc) = bb where cc == "bomo" { print(cc) } else { print("nil") } 带标签的语句如果有多层嵌套的情况下,有时候我们需要在某处直接退出多层循环,在objc下并没有比较好的方式实现,需要添加退出标识,然后一层一层退出,而在swift可以很方便的退出多层循环,首先需要使用标签标识不通的循环体,形式如下 labelName : while condition { statements } 看下面例子 outerLoop1 : for i in 1...10 { outerLoop2 : for j in 1...10 { outerLoop3 : for k in 1...10 { if j > 5 { // 1. 跳出一层循环(默认)继续outerLoop2的循环 break // 2. 跳出两层循环,继续outerLoop1的循环 // break outerLoop2 // 3. 跳出三层循环,退出整个循环,继续后面的语句 // break outerLoop1 } } } } 八、函数1. 基本形式//有返回值 func 函数名(参数名1:参数类型1,参数名2:参数类型2) -> 返回值类型 { // 函数体 } //多个返回值(元组) func getPoint() -> (x: Int,y: Int) { return (1,3) } var p = getPoint() p.x //无参数无返回值 func sayHello() { // 函数体 } //egg func add(a: Int,b: Int) -> Int { return a + b } // 调用 add(12,b: 232) 函数调用除了第一个参数,后面所有的参数必须带上参数名(符合Objc的函数命名规则)如果是调用构造器,第一个参数也需要显示声明 class A { var name: String init(name: String) { self.name = name } func sayHello(msg: String,count: Int) { for _ in 1...count { print (msg) } } } let a = A(name: "bomo") // 构造器所有参数都必须显示声明参数名 a.sayHello("hello",count: 2) // 函数参数除了第一个其他都需要显示声明参数名
@discardableResult func doWithResult(result: String) -> Bool { print(result) return true } 2. 可变参数可变参数只能作为最后一个参数,一个方法最多只有一个可变参数 func sum(numbers: Int...) -> Int { var sum = 0 for number in numbers { sum += number } return sum } 3. 外部参数名默认情况下,如果不指定外部参数名,swift编译器会自动为函数参数声明与内部参数名同名的外部参数名(格式为: //默认情况下,外部参数名与内部参数名一样 func add(first a: Int,second b: Int) -> Int { return a + b } // 调用 add(first: 10,second: 20) 如果函数在第一个参数定义外部参数名,必须显示指定,当然我们还可以通过下划线 func add(a: Int,_ b: Int) -> Int { return a + b } add(1,2) 4. 函数默认值函数还支持声明默认值,(格式为: func log(msg: String,isDebug: Bool = true) { if isDebug { print(msg) } } log("fail") log("success",isDebug: false) 如果使用默认值并且默认值不是出现在最后,那调用的时候必须写全所有参数
5. 闭包
func add(a: Int,b: Int) -> Int { return a + b } //函数作为变量,函数hello赋给somefunc,并调用 let somefunc: (Int,Int) -> Int = add somefunc(10,20) // 30 //函数作为参数 func logAdd(a:Int,b:Int,function: (Int,Int) -> Int) { // 函数内容 print("begin") function(a,b) print("end") } logAdd(12,b: 23,function: add) //函数作为返回值(包装一个函数,在执行前后输出信息),函数作为参数又作为返回值 func addWrapper(addFunc: (Int,Int) -> Int) -> ((Int,Int) -> Int) { // 函数内容 func wrapper(a: Int,b: Int) -> Int { print("begin") let res = addFunc(a,b) print("end") return res } return wrapper } var newAdd = addWrapper(add) newAdd(12,32) 闭包函数声明形式 { (parameters) -> returnType in statements // 可以有多行 } 闭包函数 //定义一个函数变量 var addfunc: (Int,Int) -> Int //闭包的写法 // 1. 完整写法 addfunc = {(a: Int,b: Int) -> (Int) in //var c = a + 1 //函数体可以有多条语句,如果在同一行,需要用分号隔开,函数体不需要大括号 return a + b } // 2. 前面的addfunc变量可以推断出后面函数的参数类型和返回值类型,故可以省略 addfunc = {(a,b) in return a + b} // 3. 参数列表括号可以省去,函数只有一条语句时,return可以省略 addfunc = {a,b in a + b} // 4. 参数和in可以省去,通过$和索引取得参数 addfunc = {$0 + $1} // 操作符需要的参数与函数参数一致,可以省去参数,并使用括号括起来,作为参数时,可不用括号 addfunc = (+) 6. Trailing(尾行)闭包如果函数作为另一个函数的参数,并且是最后一个参数时,可以通过Trainling闭包来增强函数的可读性 func someFunctionThatTakesAClosure(a: Int,closure: () -> ()) { // 函数体部分 } // 1. 一般形式 someFunctionThatTakesAClosure(10,closure: { // 闭包主体部分 }) // 2. Trainling闭包的方式 someFunctionThatTakesAClosure(10) { // 闭包主体部分 } // 3. 如果没有其他参数时,可以省略括号 someFunctionThatTakesAClosure { // 闭包主体部分 } 7. Escaping(逃逸)闭包如果一个闭包/函数作为参数传给另外一个函数,但这个闭包在传入函数返回之后才会执行,就称该闭包在函数中"逃逸",需要在函数参数添加 var completionHandlers: [() -> Void] = [] // 传入的闭包/函数并没有在函数内执行,需要在函数类型钱添加@escaping声明 func someFunctionWithEscapingClosure(completionHandler: @escaping () -> Void) { completionHandlers.append(completionHandler) }
8. 自动闭包对于没有参数的闭包,swift提供了一种简写的方式,直接写函数体,不需要函数形式(返回值和参数列表),如下 // 声明一个自动闭包(无参数,可以有返回值,返回值类型swift可以自动识别) let sayHello = { print("hello world") } //调用闭包函数 sayHello()
如果一个函数接受一个不带参数的闭包 func logIfTrue(predicate: () -> Bool) { if predicate() { print("True") } } 调用的时候可以使用自动闭包 logIfTrue(predicate: { return 1 < 2 }) // 可以简化return logIfTrue(predicate: { 1 < 2 }) 上面代码看起来可读性不是很好,swift引入了一个关键字 func logIfTrue(predicate: @autoclosure () -> Bool) { if predicate() { print("True") } } // 调用 logIfTrue(predicate:1 < 2)
9. 常量参数和变量参数默认情况下所有函数参数都是常量,意味着参数是不可变的,我们可以显式的声明参数为变量 func log(msg: String) { msg = "begin " + msg + " end" // 会报错,因为msg为常量 print(msg) } func log(var msg: String) { msg = "begin " + msg + " end" // 变量参数正常运行 print(msg) }
10. 输入输出参数在c语言里有指针,可以通过传址直接修改外部变量的值,在swift通过 func swap(inout a: Int,inout b: Int) { let temp = a a = b b = temp } var a = 19,b = 3 swap(&a,&b) 11. 嵌套函数swift的函数还支持嵌套,默认情况下,嵌套函数对外部不可见,只能在函数内部使用 func chooseStepFunction(backward: Bool) -> (Int) -> Int { //定义两个内部函数 func stepForward(input: Int) -> Int { return input + 1 } func stepBackward(input: Int) -> Int { return input - 1 } return backward ? stepBackward : stepForward } 嵌套函数相当于objc函数内的block 12. defer在swift2.0之后添加了 func test() { print("begin1") defer { // 入栈 print("end1") } print("begin2") defer { // 入栈 print("end2") } if true { print("begin4") defer { print("end4") } print("begin5") defer { print("end5") } } print("do balabala") return } 上面输出结果为 begin1 begin2 begin4 begin5 end5 end4 do balabala end2 end1 通常可以用在需要成对操作的逻辑中(如: 九、枚举swift的枚举比C语言的枚举更为强大,支持更多特性,swift的枚举更像类和结构体,支持类和结构体的一些特性,与
1. 声明和使用// 1. 定义枚举 enum CompassPoint { case North case South case East case West } // 2. 可以把枚举值定义在一行,用逗号隔开 enum CompassPoint2 { case North,South,East,West } // 3. 像对象一样使用枚举,代码结构更为清晰,枚举更为简短 let direction = CompassPoint.East // 4. 如果编译器可以识别出枚举的类型,可以省略枚举名 let direction2: CompassPoint direction2 = .East // 5. 如果编译器能确定case命中所有的情况,可以不需要default switch direction { case .East: print("east") case .West: print("west") case .South: print("south") case .North: print("north") //所有值都被枚举,则不需要default } 2. 嵌套枚举swift的枚举定义支持嵌套,在使用的时候一层一层引用 enum Character { enum Weapon { case Bow case Sword case Lance case Dagger } enum Helmet { case Wooden case Iron case Diamond } case Thief case Warrior case Knight } let character = Character.Thief let weapon = Character.Weapon.Bow let helmet = Character.Helmet.Iron 3. 递归枚举枚举的关联值的类型可以设为枚举自身,这样的枚举称为递归枚举 enum ArithmeticExpression { case number(Int) indirect case addition(ArithmeticExpression,ArithmeticExpression) indirect case multiplication(ArithmeticExpression,ArithmeticExpression) } 带递归类型的枚举需要在case前面添加关键字声明 indirect enum ArithmeticExpression { case number(Int) case addition(ArithmeticExpression,ArithmeticExpression) case multiplication(ArithmeticExpression,ArithmeticExpression) } 使用递归枚举取值的时候可以使用递归函数 func evaluate(_ expression: ArithmeticExpression) -> Int { switch expression { case let .number(value): return value case let .addition(left,right): return evaluate(left) + evaluate(right) case let .multiplication(left,right): return evaluate(left) * evaluate(right) } } let five = ArithmeticExpression.number(5) let four = ArithmeticExpression.number(4) let sum = ArithmeticExpression.addition(five,four) // (5 + 4) * 2 let product = ArithmeticExpression.multiplication(sum,ArithmeticExpression.number(2)) print(evaluate(product))
4. 原始值与C语言一样,可以为每个枚举指定值,并且可以支持更多类型( // 定义枚举,并初始化原始值 enum ASCIIControlCharacter: Character { case Tab = "t" case LineFeed = "n" case CarriageReturn = "r" } // 2. 通过两个属性获得原始值 var ch = ASCIIControlCharacter.Tab ch.hashValue // 获取是否有原始值 ch.rawValue // 获得原始值 // 3. 通过原始值构造枚举,如果不存在,则返回nil var tab = ASCIIControlCharacter.init(rawValue: "t") // 4. 如果是原始值是整形值,后面的值默认自增1,如果不指定,则默认为空,而不是从0开始 enum Planet: Int { case Mercury = 1,Venus // Venus = 2 case Neptune // Neptune = 3 } // 5. 如果没有指定枚举原始值的类型,则默认为空,而不是整型 enum CompassPoint { case North case South case East case West } //swift 不会为North,West设置为0,3,并且CompassPoint没有原始值(rawValue) // 6. 有原始值的枚举可以通过原始值构造(构造器返回可选类型) let lineFeed = ASCIIControlCharacter(rawValue: "n") 5. 关联值上面我们说到,枚举与类和结构体类似,swift的枚举可以给不同的枚举值绑定关联值,如下 enum Barcode { case UPCA(Int,Int,Int) //条形码,关联一个元组 case QRCode(String) //二维码,关联一个字符串 } var productBarcode = Barcode.UPCA(8,85909_51226,3) // var productBarcode = .QRCode("http://www.baidu.com") switch productBarcode { case .UPCA(let a,let b,let c): //在枚举的时候可以取得关联值 print("barcode: (a)(b)(c)") case let .QRCode(value): print("qrcode: (value)") } 如上面这种轻量的数据,在OC上一般我们可能需要定义两个类实现,而swift的枚举可以轻松的处理这种轻量数据,而减少项目中类的定义和维护 十、类与结构体先来看看结构体和类的一些差异
1. 结构体,类定义struct Point { let x: Int let y: Int func printPoint() { print("x=(x),y=(y)") } } class Person { var someObj = NSObject() // 定义属性,并初始化 var name: String // 定义属性,并指定类型 init(name: String) { // 构造函数 self.name = name } func hello() { print("hello (self.name)") } //析构函数 deinit { print("dealloc") } } swift中,许多基本类型如 2. 静态属性,静态方法swift中有两个 class Person { static var instanceCount: Int = 0 // 声明一个类属性 init () { Person.instanceCount += 1 // 通过类名引用类属性,子类可以访问基类的类属性 } // 使用class声明的静态方法可以被继承 class func overrideableComputedTypeProperty() { print("(Person.instanceCount)") } // 使用static声明的静态方法不能被继承 static func printInstanceCount() { // 声明一个静态方法 print("(Person.instanceCount)") } } 类和结构体的声明和用法与类类似,使用
3. 构造器和析构器swift的构造器规则和限制比较多,关于构造器可以参见:这里 析构器相当于objc里面的 class Person { deinit { print("释放额外的资源,如通知") } } 4. 类型判断在objc中,我们通常使用 class Parent { } class Son: Parent { } var s = Son() // isKindOfClass son is Son // true son is Parent // true // isMemberOfClass son.dynamicType == Son.self // true son.dynamicType == Parent.self // false // isSubclassOfClass 暂时没找到相关的API //TODO: swift动态性,反射 5. 弱引用与 class Person { weak var person: Person? = nil } 6. 访问级别在swift中,framework和bundle都被处理成模块
swift默认的访问级别为Internal,使用的时候只需要在类/变量/函数前面加上访问级别即可 public class Person { class public var peopleCount: Int = 0 // 类变量,通过class声明,类变量使用时使用类名引用 internal var age: Int // 实例变量 var name: String // 不声明,则为internal init() { self.age = 0 self.name = "" Person.peopleCount++ // 使用静态变量 } private func sayHello() { print("hello") } } 外层访问级别的必须是比成员更高,下面会报警告 class Person { // 默认为internal public var age: Int = 0 // 为public,比类访问级别高,会有警告 private var gender: Int = 10 private func sayHello() { print("hello") } } 函数的访问级别要比参数(或泛型类型)的访问级别低,否则会报警告 private class PrivatePerson { private var age: Int = 0 var gender: Int = 10 // 报警告 private func sayHello() { } } public class Test { public func test(person:PrivatePerson) { //报编译错误:这里参数访问级别为private,所以函数访问级别不能高于private,则只能为private } }
public enum CompassPoint { case North // 四个枚举成员访问级别都为public case South case East case West } 子类访问级别不能高于父类(包括泛型类型),协议继承也同理,子协议访问级别不能高于父协议 class Parent { } public class Son: Parent { // 报编译错误:Son访问级别必须低于Parent,应该为internal或private }
class Parent { } private class Son: Parent { } public class SomeClass { internal let sometuple = (Son(),Parent()) // 报编译错误:sometuple的访问级别不能高于成员类型的访问级别,由于Son为private,故sometuple必须为private } 变量的访问级别不能高于类型 private class PrivateClass { } public class SomeClass { public var value: PrivateClass // 报编译错误:变量value的访问级别不能高于其类型,故value必须声明为private } 属性的 Setter 访问级别不能高于 Getter访问级别 public class SomeClass { private(set) var num = 1_000_000 // 声明属性num,getter访问级别没有声明,默认为Internal,setter访问级别为private private internal(set) var name = "bomo" // 报编译错误:属性name的setter访问级别为internal,高于getter访问级别private } 协议与类的访问级别关系
类型别名访问级别与类型的关系
函数构造函数默认访问级别为internal,如果需要给其他模块使用,需显式声明为public
public class A { private func someMethod() {} } internal class B: A { override internal func someMethod() { // 在同一个文件,改变someMethod的访问级别 super.someMethod() } } 7. 属性
class DataImporter { } class DataManager { // 1. 只有第一次调用importer的get方法的时候才会初始化 lazy var importer = DataImporter() var data = [String]() } class Rectangle { var width: Double = 0.0 var height: Double = 0.0 // 2. 声明get方法和set方法的访问级别 private private(set) var weight: Double = 0 // 3. 自定义get/set方法 var square: Double { get { return (self.width + self.height)/2; } //set { //如果不指定名称,默认通过newValue使用新值 set(newValue) { self.width = newValue/2.0; self.height = newValue/2.0 } } // 4. 只读属性,可以省略get,直接使用一个花括号 var perimeter: Double { return (self.width + self.height) * 2 } // 5. 属性监视器,在初始化的时候不会触发 var someInt: Int = 0 { willSet { //用法与set一样如果不指定名称,默认通过newValue使用旧值 print("set方法之前触发") } didSet { //用法与set一样如果不指定名称,默认通过oldValue使用旧值 print("set方法完成后触发,可以在这里设置obj的值覆盖set方法设置的值") self.someInt = 0 // someInt的值永远为0,在监视器修改属性的值不会导致观察器被再次调用 } } }
对于结构体,与OC不同,swift的结构体允许直接对属性的子属性直接修改,而不需要取出重新赋值 someVideoMode.resolution.width = 1280 在oc上需要这样做 var resolution = someVideoMode.resolution resolution.width = 1024 someVideoMode.resolution = resolution 8. 继承我们都知道,在oc里所有的类都继承自NSObject/NSProxy,而在swift中的类并不是从一个通用的基类继承的,所有没有继承其他父类的类都称为 class Parent { final var gender = "unknown" init(gender: String) { self.gender = gender } private func hello() { print("parent hello") } } class Son: Parent { // 重写可以改变父类方法的访问级别 internal override func hello() { // 重写父类方法必须加上override,否则会报编译错误 //super.hello() // 可以通过super访问父类成员,包括附属脚本 print("son hello") } }
父类的属性,方法,类方法,附属脚本,包括类本身都可以被子类继承和重写,可以通过 class Parent { final var gender = "unknown" // 不允许被子类重写 var name: String // 可以被子类重写 init(gender: String) { self.gender = gender self.name = "" } final func hello() { // 不允许被重写 print("parent hello") } } swift编译器在识别数组类型的时候,如果数组元素有相同的基类,会被自动识别出来 class Person { } class Teacher: Person { } class Student: Person { } let t1 = Teacher() let t2 = Teacher() let s1 = Student() let s2 = Student() let people = [t1,t2,s1,s2] // people会被识别为[Person]类型 向下类型转换 for person in people { if let teacher = person as? Teacher { println("teacher") } else if let student = person as? Student { println("student") } } 9. 附属脚本subscript附属脚本可以让类、结构体、枚举对象快捷访问集合或序列,而不需要调用使用对象内的实例变量引用,看下面实例 class DailyMeal { enum MealTime { case Breakfast case Lunch case Dinner } var meals: [MealTime : String] = [:] } // 如果需要使用DailyMeal的meals对象的,需要这么用 var dailyMeal = DailyMeal() dailyMeal.meals[MealTime.Breakfast] = "Toast" 使用附属脚本可以直接通过类对象索引访问meals的值 class DailyMeal { enum MealTime { case Breakfast case Lunch case Dinner } var meals: [MealTime : String] = [:] // 定义附加脚本,类似属性 subscript(realMealTime: MealTime) -> String { get { if let value = meals[realMealTime] { return value } else { return "unknown" } } set(newValue) { meals[realMealTime] = newValue } } } var dailyMeal = DailyMeal() dailyMeal[.Breakfast] = "sala" print(dailyMeal[.Breakfast]) 附加脚本还支持多个参数 struct Matrix { let rows: Int,columns: Int var grid: [Double] init(rows: Int,columns: Int) { self.rows = rows self.columns = columns grid = Array(count: rows * columns,repeatedValue: 0.0) } func indexIsValidForRow(row: Int,column: Int) -> Bool { return row >= 0 && row < rows && column >= 0 && column < columns } subscript(row: Int,column: Int) -> Double { get { assert(indexIsValidForRow(row,column: column),"Index out of range") return grid[(row * columns) + column] } set { assert(indexIsValidForRow(row,"Index out of range") grid[(row * columns) + column] = newValue } } } var matrix = Matrix(rows: 2,columns: 2) matrix[0,1] = 1.5 matrix[1,0] = 3.2 附加脚本类似属性,拥有get/set方法,支持只读和读写两种方式,附加脚本也支持多个参数,附属脚本可以屏蔽外部对内部对象的直接访问,隐藏对象内部的细节,提高封装度,使得代码更加健壮和简洁 10. 类型嵌套与枚举一样,结构体和类都支持类型嵌套,可以在类里面再定义类/结构体/枚举 class SomeClass { // 类里面嵌套定义枚举 enum Suit: Character { case Spades = "?",Hearts = "?",Diamonds = "?",Clubs = "?" // 枚举里面嵌套定义结构体 struct Values { let first: Int,second: Int } } // 类里面嵌套定义结构体 struct Point { let x: Int let y: Int } // 类里面嵌套定义类 class InnerClass { var name: String = "" var id: Int = 0 } } // 使用的时候像属性一样引用 let values = SomeClass.Suit.Values(first: 1,second: 2) 11. 类型别名swift类型别名与c语言中取别名有点像,通过关键字 public typealias MyInt = Int func add(a: MyInt,b: MyInt) -> MyInt { return a + b }
十一、扩展Extension与oc一样,扩展就是对已有的类添加新的功能,与oc的category类似,swift的扩展可以:
swift扩展不可以:
class Person { func hello() { print("hello") } } // 定义扩展 extension Person { func fly() { print("fly") } } let p = Person() p.fly() 扩展也可以作用在结构体和枚举上 struct Rectangle { let width: Double let height: Double } extension Rectangle { var perimeter: Double { return 2 * (self.width + self.height) } } let rect = Rectangle(width: 100,height: 200) print(rect.perimeter) 扩展内的成员定义与类类似,这里不再说明 扩展属性由于swift不能扩展新的属性,有时候我们希望给类添加属性,在oc里可以用关联属性新增存储属性,在swift也可以,需要引入 import ObjectiveC class Point { var x: Int = 0 var y: Int = 1 } private var xoTag: UInt = 0 extension Point { var z: Int { get { return objc_getAssociatedObject(self,&xoTag) as! Int } set(newValue) { objc_setAssociatedObject(self,&xoTag,newValue,objc_AssociationPolicy.OBJC_ASSOCIATION_ASSIGN) } } } 十二、协议Protocalswift的协议在oc的基础上加了更多的支持,可以支持属性,方法,附加脚本,操作符等,协议的属性必须为变量 protocol SomeProtocol { // 属性要求 var mustBeSettable: Int { get set } // 只读属性 var doesNotNeedToBeSettable: Int { get } // 只读静态属性 static var staticProperty: Int { get } // 静态方法 static func hello() } 1. mutating在结构体/枚举中的值类型变量,默认情况下不能对其进行修改,编译不通过,如果需要修改值类型的属性,需要在方法声明前加上 struct Point { var x: Int var y: Int func moveToPoint(point: Point) { self.x = point.x // 报错:不能对值类型的属性进行修改 self.y = point.y // 报错:不能对值类型的属性进行修改 } mutating func moveToPoint2(point: Point) { self.x = point.x // 编译通过 self.y = point.y // 编译通过 } //可变方法还可以对self进行修改,这个方法和moveToPoint2效果相同 mutating func moveToPoint3(x deltaX: Int,y deltaY: Int) { self = Point(x:deltaX,y:deltaY) } } 可变方法还可以修改枚举值自身的值 enum TriStateSwitch { case Off,Low,High mutating func next() { switch self { case .Off: self = .Low case .Low: self = .High case .High: self = .Off } } } 特别是在定义Protocal的时候,需要考虑到协议可能作用于枚举或结构体,在定义协议的时候需要在方法前加上 protocol SomeProtocol { mutating func moveToPoint(point: Point) } 2. 协议类型协议虽然没有任何实现,但可以当做类型来用,与oc的protocal类似,用协议类型表示实现了该协议的对象,与oc的 3. 协议组合有时候我们需要表示一个对象实现多个协议,可以使用协议组合来表示,如下 protocol SwimProtocal { func fly() } protocol WalkProtocal { func walk() } func through(animal: protocol<WalkProtocal,SwimProtocal>) { animal.walk() animal.fly() } 4. 自身类型有时候我们需要表示实现协议的类型,可以使用 protocol CompareProtocal { // Self表示实现协议自己的类型本身 func compare(other: Self) -> Bool } class Product: CompareProtocal { var id: Int = 0 func compare(other: Product) -> Bool { return self.id == other.id } } 5. @objc协议swift声明的协议是不能直接被oc的代码桥接调用的,如果需要,需要在声明前加上 import Foundation @objc protocol HasArea { // 协议可以被桥接到oc中使用 var area: Double { get } } 6. Optional要求在oc中的protocal可以定义可选方法,在swift默认不支持可选方法,swift只有在添加了 import Foundation @objc protocol HasArea { optional var area: Double { get } // 定义可选属性 } 十三、错误与其他高级语言异常处理有点类似,swift引入了错误的机制,可以在出现异常的地方抛出错误,错误对象继承自Error,抛出的错误函数会立即返回,并将错误丢给调用函数的函数处理,如果一个函数可能抛出错误,那么必须在函数定义的时候进行声明,如下 //定义错误类型 enum OperationError: Error { case DivideByZero case Other } //定义可能抛出异常的函数,在函数声明的返回值前面加上throws func divide(a: Int,b: Int) throws -> Float { if b == 0 { throw OperationError.DivideByZero } return Float(a) / Float(b) } //调用可能出错的函数(调用出必须加上try) do { let result = try divide(a: 10,b: 0) print(result) } catch OperationError.DivideByZero { print(error) } catch { //其他错误 } 如果错误是一个对象,而不是枚举,可以用let绑定到变量上 do { try divide(a: 10,b: 0) } catch let err as SomeErrorType { print(err.message) } catch { print("other error") } 如果不处理错误的话可以使用 func serialize(obj: AnyObject) -> String { guard let jsonString = try? someSerializeFuncMayThrowError(obj) else { print(jsonString) } print("fail") }
十四、断言断言可以让我们在调试时候更好的发现问题,排查错误,几乎所有的高级语言都支持断言,swift也如此,断言的代码在release的时候回被忽略,不会影响发布程序的性能,只会在调试的时候生效 // 如果age小于0,程序会停止,并输出错误信息 assert(age >= 0,"A person's age cannot be less than zero") 十五、泛型关于泛型的介绍,这里不进行说明,swift的泛型是我认为最酷的特性之一,当然其他语言也有,可以让类或函数更大程度的重用,swift的泛型与其他语言的泛型有点类似 1. 定义在类或函数声明的时候,指定一个泛型类型参数(通常为T)然后使用的时候直接把T当成类型使用 //泛型函数定义 func swapTwoValues<T>(inout a: T,inout b: T) { let temporaryA = a a = b b = temporaryA } //泛型类定义 class Result<T> { var code: Int = 0 var errorMessage: String? var data: T? } //多个泛型类型参数 class Result<T,TK> { var code: Int = 0 var errorMessage: String? var data: T? var subData: TK? } 2. 泛型约束我们还可以对泛型进行约束,泛型类型参数只能是某些类型的子类,或实现了某些协议 func findIndex<T>(array: [T],valueToFind: T) -> Int? { for (index,value) in array.enumerate() { if value == valueToFind { return index } } return nil } 上面函数会报编译错误,因为在swift里,并不是所有的类都能用 func findIndex<T: Equatable>(array: [T],value) in array.enumerate() { if value == valueToFind { return index } } return nil } 3. 多泛型类型参数有时候我们需要用多个协议进行约束,可以使用下面方式(类与函数的使用方式类似) func someFunc<T : protocol<StudyProtocal,RunProtocal>>(arg: T) { // do stuff } 如果约束既有类又有协议的话可以使用 func someFunc<T,TK where T:Student,T: StudyProtocal>(t: T,tk: TK) { // do stuff } 4. 泛型是不可变的var dog1 = SomeClass<Parent>() var dog2 = SomeClass<Son>() dog1 = dog2 // 报错
5. 泛型协议swift的协议不支持泛型,不能像类一样定义泛型,而是通过类型参数定义泛型 protocol GenericProtocol { associatedtype T1 associatedtype T2 func someFunc(t2: T2) -> T1 } class SomeClass<T> : GenericProtocol { // 设置泛型类型 typealias T1 = String typealias T2 = T func someFunc(t2: T2) -> T1 { return "" } } 十六、运算符重载与其他高级语言的一样,swift也提供了运算符重载的功能,我们可以自定义运算符的实现,运算符通常分为三种类型
swift的运算符重载
1. 前缀,中缀,后缀运算符
1.1 声明运算符如果实现不存在的运算符需要添加运算符声明(系统的提供的,可以不需要声明),声明必须放在全局作用域 // 前缀运算符 prefix operator +++ {} // 中缀运算符(二元运算符) infix operator +++ {} // 后缀运算符 postfix operator +++ {} 1.2 实现上面三个运算符// 定义Point结构体 struct Point { var x: Int var y: Int } // 重载操作符要放在全局作用域 func +++ (left: Point,right: Point) -> Point { return Point(x: left.x + right.x,y: left.y + right.y) } // 如果需要修改操作数,需要添加inout关键字 prefix func +++ (inout left: Point) { left.x += 1 left.y += 1 } postfix func --- (right: Point) -> Point { return Point(x: right.x - 1,y: right.y - 1) } 1.3 使用var p1 = Point(x: 12,y: 21) var p2 = Point(x: 12,y: 2) let p3 = p1+++p2 // p3.x = 24,p3.y = 23 +++p1 // p1.x = 13,p1.y = 3 p1--- // p1.x = 12,p1.y = 2 2. 优先级这个很好理解,就是优先级高的运算符先执行,声明运算符的时候可以指明优先级 infix operator ^ { associativity left // 结合性,后面说 precedence 140 // 指定运算符优先级 } 这里可以查看默认运算符的优先级 3. 结合性运算符还可以定义结合性,对于双目运算符,当优先级一样的时候,可以定义运算符优先进行左结合还是右结合,运算符的结合性有下面三种
结合性设置为 // 定义一个双目操作符 infix operator ^ { associativity left // 结合性 precedence 140 // 指定运算符优先级 } func ^ (left: Int,right: Int) -> Int { return Int(pow(Double(left),Double(right))) } let a = 2 ^ 2 ^ 2 ^ 2 // 执行结果为256 // 相当于 let aa = ((2 ^ 2) ^ 2) ^ 2 如果我们设置结合性为 // 定义一个双目操作符 infix operator ^ { associativity right // 结合性 precedence 140 // 指定运算符优先级 } func ^ (left: Int,Double(right))) } let a = 2 ^ 2 ^ 2 ^ 2 // 执行结果为65536 // 相当于 let aa = 2 ^ (2 ^ (2 ^ 2)) 如果结合性设置为 十七、参考链接
二十、总结总的来说,swift还是比较装逼的,整出很多新名词,新概念,例如,指定构造器,便利构造器,构造器代理,但其实这些东西在别的语言基本上有,没那么复杂,另外swift的关键字太多了,有些可有可无,是不是苹果看到什么好的就想往swift里面塞还是怎么着,个人感觉编程语言应该是轻便,简单
(编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |