Swift基础语法
一、特色:
- 取消了预编译指令包括宏
- 取消了OC的指针及其他不安全的访问的使用
- 全面改为句点表示法
- Swift3.0 去除了NS前缀
- 将绝大部分Class转化为struct
- @UIApplication 程序的入口
- 只有.swift 没有 .h/.m 在swift中默认全局共享
- 所有的代码都包装在{ }中,默认方法都有缩进
- 访问当前对象的属性,可以不用 ‘self’
- 没有 分号‘ ;’
- // MARK: - 作标记
- // TODO: 作标记
- // FIXME: 作临时标记
二、基础语法:
1. 变量和常量
- 定义变量 var 定义之后可修改,定义常量 let 定义之后不可修改
- 自动推导,变量或常量的类型会根据右侧的代码执行结果,推导对应的类型(option + click 查看类型)
- 在Swift中对类型要求异常严格,
任何不同类型的数据之间,不允许直接运算
,不会做默认的隐式转换,所有的类型确定,都要由程序猿负责 Swift中,不存在基本数据类型,都是结构体
func test() { let x = 10 let y = 10.5 //print(x + y) 这种写法是错误的 print(x + Int(y)) //将y转换成整数 print( Double(x) + y) //将x装换成 Double //整数:Int(OC 中 NSInteger 类似) //小数:Double 精度高(OC中的 CGFloat) } func test1() { //如果需要,指定类型 (实际开发中 极少指定类型) let x:Double = 10 let y = 10.5 print(x + y) }
2. 简单的分支和三目的写法
func test() {
// 1. 条件不需要 ()
// 2. 语句必须有 {}
let x = 10
if x > 5 {
print("大于5")
} else {
//永远不会执行 ,编译器编译的时候会做语法检查
print("小于5")
}
//三目运算
x > 10 ? print("大于10") : print("等于于10")
x > 10 ? print("大于10") : () //相当于没有else
}
3. Optional 可选项
强行解包与可选值
func test() { // Optional 是一个枚举 none:没有值 some:某一类值 let x : Optional = 10 // '?' 用来定义 y 是一个可选的Int类型 可能没有值,也可能有一个整数 let y: Int? = 20 // 不同类型之间的值不能直接运算,如果没有值 为nil,不是任何数据类型,不能参与计算 // '!' 强行解包 -> 从可选值中强行获取对应的非空值,如果是 nil,就会崩溃 print(x! + y!) }
变量和常量可选值的默认值
- var 的可选值默认为 nil
- let 的可选值没有默认值
对可选值的安全处理
test(x: 10, y: nil) //调用下面的方法 func test(x: Int?, y: Int?) { // 1. 强行解包有风险 // print(x! + y!) // 2. 使用if判断 if x != nil && y != nil { print(x! + y!) }else{ print("x 或 y 为nil") } // 3. if let 连用,判断值是否为nil ,进入分支后,a,b一定有值,不需要解包 // if var 连用 {}内可以对值进行修改 if let a = x, let b = y { print(a + b) // a,b作用域只在 {}中 }else{ print("x 或 y 为nil") } // 4. guard let 和 if let刚好相反 guard let m = x,let n = y else { print("x 或 y 为nil") return } print(m + n) // 5. ?? 是一个简单的三目 (如果前面有值,使用值,如果没有值,使用??后面的值) print((x ?? 0) + (y ?? 0)) }
4. switch的语法
- switch 可以针对任意类型的值进行分支,不在局限在整数
- switch 一般不需要 break
- switch 如果要多值,使用
,
所有的分支至少需要一条指令,如果什么都没有,才使用break
func test(num : String) { switch num { case "10", "9": print("优") case "8": break default: print("other") } }
5. For循环
以前的写法(在swift 3.0 被取消)
// i++ / ++i 在swift 3.0 被取消 改为 i += 1 for var i = 0; i < 10; i++ { }
swift3.0写法
//0..<5 => [0,5) 0...5 => [0,5] for i in 0..<5 { print(i) }
反序遍历
for i in (0..<5).reversed() { print(i) //输出 4 3 2 1 0 }
6. 字符串
字符串的遍历
let str = "Swift" for c in str.characters { print(c) }
字符串的长度
let str = "Swift基础" //2.1 返回指定编码的字节数量 UTF8的编码(0-4个),每个汉字3个字节 print(str.lengthOfBytes(using: .utf8)) //2.2 字符串长度 print(str.characters.count) //2.3 使用 NSString 中转 //as 类型转换 let ocStr = str as NSString print(ocStr.length)
字符串的拼接
let name = "Funky" let age = 18 let title: String? = "Swift基础" //注意 可选项Optional let str = "\(name) \(age) \(title ?? "")" print(str) //=> Funky 18 Swift基础
字符串的格式化
let h = 8 let m = 9 let s = 6 let date = "\(h):\(m):\(s)" print(date) //=> 8:9:6 let date1 = String(format: "%02d:%02d:%02d", h,m,s) print(date1) //=> 08:09:06
字符串的子串
//1. Swift取子串的方法一直在优化,一般使用 NSString 作为中转 let str = "Swift基础" let ocStr = str as NSString let s1 = ocStr.substring(with: NSMakeRange(2, 3)) print(s1) //=> ift //2.String 的 3.0 方法 print(str.startIndex) //=> _position: 0 print(str.endIndex) //=> _position: 7 let s2 = str.substring(from: "ab".endIndex) print(s2) //=> ift基础 // 取子字符串的范围 guard let range = str.range(of: "ift") else { print("不包含此字符串") return } print(range) print(str.substring(with: range)) //=>ift
7. 数组
数组的定义
//可变数组 (var),不可变数组 (let) // 自动推导的结果 【String】 -> 表示数组中存放的都是String let array = ["1","2","3"] print(array) //[Int] -> 表示存放的都是 Int,在Swift中基本数据类型不需要包装 let array1 = [1,2,3] print(array1) //[CGPoint] -> 存放的是CG结构体 let p = CGPoint(x:10, y:300) let array2 = [p] print(array2) //混合数组:[Any] ,开发中几乎不用,CG结构体需要包装 let array3 = ["Funky",18,NSValue(cgPoint:p)] as [Any] print(array3)
数组的遍历
//2.1 按下标遍历 let array = ["张三","李四","王五"] for i in 0..<array.count { print(i) } //2.2 for in遍历元素 for s in array { print(s) } //2.3 enum block遍历 //(offset: Int, element: String) 元组 for e in array.enumerated() { print(e) print("\(e.offset) \(e.element)") } //2.4 n 是索引下标; s 是下标对应的元素 (n/s 可以随便写) for (n, s) in array.enumerated() { print("\(n) \(s)") } //2.5 反序遍历 (先枚举在反序) for (n, s) in array.enumerated().reversed() { print("\(n) \(s)") }
数组的增、删、改
var array = ["张三","李四","王五"] //追加 array.append("Funky") //修改 array[0] = "老李" //删除 array.remove(at: 3) //删除所有,并且保留空间 array.removeAll(keepingCapacity: true)
数组的合并
var array = ["张三","李四","王五"] let array1 = ["Funky"] //将array1合并到array中(数组类型必须一致) array += array1 print(array)
8.字典
定义
//[String : Any] let dict = ["name":"Funky","age":18] as [String : Any] print(dict) //定义一个字典的数组 let array:[[String: Any]] = [ ["name":"Funky","age":18], ["name":"Funky","age":18] ] print(array)
增删改
var dict = ["name":"Funky","age":18] as [String : Any] //新增 dict["height"] = 180 print(dict) //修改 dict["age"] = 19 print(dict) //删除 dict.removeValue(forKey: "age") print(dict)
遍历
let dict1 = ["name":"Funky","age":18,"height":180] as [String : Any] for e in dict1 { print(e) print("\(e.key) \(e.value)") } for (key,value) in dict1 { print("\(key) \(value)") }
合并
var dict2 = ["name":"Funky","age":18,] as [String : Any] let dict3 = ["height":180] for e in dict3 { dict2[e.key] = dict3[e.key] } print(dict2)
三、Swift函数:
1. 函数
函数定义
print(sum(x: 10, y: 50)) //格式: 函数名(形参列表)-> 返回值类型 func sum(x:Int,y:Int) -> Int { return x + y }
外部参数 (在形参前加一个名字,不影响函数内部的细节)
print(sum1(num1: 10, num2: 10)) print(sum2(10, 20)) func sum1(num1 x:Int,num2 y:Int) -> Int { return x + y } //外部调用函数时,会忽略形参的名字 (Swift中 `_` 可以忽略任意不感兴趣的内容) func sum2(_ x:Int,_ y:Int) -> Int { return x + y }
默认值
print(sum3()) print(sum3(x: 10, y: 50)) print(sum3(x: 10)) print(sum3(y: 50)) //通过给参数设置默认值,在调用的时候,可以任意组合参数,如果不指定的,就使用默认值 func sum3(x:Int = 1,y:Int = 1) -> Int { return x + y }
无返回值 (
->
表示前面执行 后面输出目标结果)func test1() { print("XXX") } func test2() -> () { print("XXX") } func test3() -> Void { print("XXX") }
2. 闭包
最简单的闭包
/* //闭包的基本格式(in 是用于区分形参返回值和执行代码) { (形参列表) -> (返回值) in 需要执行的代码 } */ //bi: () -> () (没有参数,没有返回值) let b1 = { print("xxx") } b1() //执行闭包
带参数的闭包
//需要一个关键字 `in` 分隔定义和实现 //闭包中,参数,返回值,实现代码都是写在 {} 中 //b2: (Int) -> () let b2 = { (x: Int) -> () in print(x) } b2(100)
带参数带返回值的闭包
//需要一个关键字 `in` 分隔定义和实现 //闭包中,参数,返回值,实现代码都是写在 {} 中 //b2: (Int) -> () let b3 = { (x: Int) -> Int in return x + 250 } print(b3(100))
闭包的几种格式
//1.将闭包通过实参传递给函数 //2.如果闭包是函数的最后一个参数,那么闭包可以写在函数()的后面 //3.如果函数只接受一个参数,并且这个参数是闭包,那么()可以省略 loadData { () -> () in print("被回调了") } loadData (finished: { () -> () in print("被回调了") }) loadData(){() -> () in } loadData2(num: 10, finished: {() -> () in }) loadData2(num: 10){() -> () in } //如果闭包没有参数也没有返回值,那么in之前的都可以不写,包括in loadData { } //参数接受一个没有参数,没有返回值,名称是finished 一个闭包 func loadData(finished:() -> ()) { print("耗时操作") finished() } func loadData2(num:Int,finished:() -> ()) { print("耗时操作") finished() }
3.循环引用
造成循环引用
//定义属性 var completionCallBack: (() -> ())? //只是闭包对self 进行了copy,闭包执行完成后,会自动销毁,同时释放对self的引用 loadData { print(self.view) } //循环引用:单方向引用是不会产生循环引用的 //闭包对self 进行了copy,闭包执行完成后,会自动销毁,同时释放对self的引用 //同时需要 self 对闭包引用,就会造成循环引用 func loadData(completed:@escaping ()->()) -> (){ //使用属性记录闭包 -> self 对闭包引用了 completionCallBack = completed completed() } /* deinit 相当于OC的 dealloc ,对象被释放前自动调用 - 没有 func 不允许直接调用; - 没有( ) 不允许带参数,不允许被重载/重写 - 使用场景 :跟踪对象被销毁,NSTimer/CADisplayLink,通知,KVO... */ deinit { print("释放") }
解决循环引用
//方法一:OC的方式 //weak 只能修饰 var,不能修饰let //weak 可能会在运行时被修改 -> 指向的对象一旦被释放,会被自动设置为nil weak var weakSelf = self loadData { // ? 可选解包 - 如果 self 已经被释放,不会向对象发送 getter 的消息,更加安全 // ! 强行解包 - 如果 self 已经被释放,强行解包会导致崩溃 print(weakSelf?.view) //weakSelf?.view 只是单纯的发送消息,没有计算 } //方法二:Swift的方式 //[weak self] 表示 {}中的所有 self 都是循环引用,需要注意解包 loadData { [weak self] in print(self?.view) } //方法二:Swift的另外一种方式 //[unowned self] 表示闭包中的所有 self 都是assign弱引用的,不会强引用,但是如果对象释放,指针地址不会改变 //如果对象被释放,继续调用,就会出现野指针的问题 loadData { [unowned self] in print(self.view) }
—————————————-
Swift面向对象
一、构造函数
1. 构造函数基础
构造函数是一种特殊的函数
- 主要用来在创建对象时初始化对象
- 为对象的成员变量设置初始值
在OC中的构造函数是 initWithxxx,在 Swift 中由于支持函数重载,所有的构造函数都是init
- 重载 overload :(函数名相同,参数类型和个数不同)
- 重写 overrride : (父类存在相同的方法,子类重新编写父类方法的实现)
构造函数的作用
- 分配空间 alloc
- 设置初始值 init
2. 必选属性的构造过程
//错误1:Class 'Person' has no initializers
//Person 类没有初始化器,构造函数可以有多个,默认的是 init
class Person: NSObject {
var name:String
}
class Person: NSObject {
var name:String
//错误2:Overriding declaration requires an 'override' keyword
//重写(父类有这个方法,子类重新实现),需要 override 关键字
init() {
}
}
class Person: NSObject {
var name:String
//错误3:Property 'self.name' not initialized at implicitly generated super.init call
//implicitly(隐式生成的 super.init()) 调用父类的构造函数之前,属性 self.name 没有被初始化
override init() {
}
}
class Person: NSObject {
var name:String
override init() {
//错误4:Property 'self.name' not initialized at super.init call
//在调用父类 super.init() 的时候,没有给 self.name 初始化(分配空间,设置初始值)
super.init()
}
}
/*
必选属性的构造:
1.给自己的属性分配空间并且设置初始值
2.调用父类的构造函数,是给父类的属性分配空间设置初始值
NSObject 没有属性,只有一个成员变量 `isa`(记录是哪个类)
*/
class Person: NSObject {
var name:String
override init() {
name = "funky"
super.init()
}
}
3. 重写和重载
class Person: NSObject {
var name:String
//1.重写 : 父类有这个方法
override init() {
// name 确实有初始值,但所有的初始值都是 funky -> 引入重载设置初始值
name = "funky"
super.init()
}
//2.重载 : 函数名相同,但是参数和个数不同
// 重载可以给自己的属性从外部设置初始值
// OC 没有重载
//注意 : 如果重载了构造函数,并且没有实现父类 init 方法,系统不再提供 init() 构造函数!
init(name: String) {
self.name = name
super.init()
}
}
4. KVC 函数调用
定义模型属性的时候,如果是对象,通常都是可选的
- 在需要的时候创建
- 避免写构造函数,可以简化代码
如果是基本数据类型,不能设置成可选的,而且要设置初始值,否则 KVC 会崩溃
- 原因: Int是一个基本数据类型的结构体,OC中没有,OC 中只有基本数据类型
如果需要使用 KVC 设置数值,属性不能是 private 的
使用KVC 方法之前,应该调用 super.init() 保证对象已经完全初始化完成
class Person: NSObject {
var name:String?
//var age:Int? 不能这样写
var age:Int = 0
//如果是 private(私有的) 属性,使用 KVC 设置值的时候,同样无法设置!
//如果设置成 private 属性/方法,禁止外部访问的,使用运行时也获取不到属性(可以获取到ivar),同样会让KVC崩溃
//private var title: String?
var title: String?
init(dict:[String:AnyObject]) {
//保证对象已经完全初始化完成
super.init()
//Use of 'self' in method call 'setValuesForKeys' before super.init initializes self
//使用self的方法 setValuesForKeys 之前,应该调用 super.init
//KVC 是OC的方法(在运行时给对象发送消息),要求对象已经实例化完成
setValuesForKeys(dict)
}
//重写父类的方法
override func setValue(_ value: Any?, forUndefinedKey key: String) {
//没有调用 super,将父类的代码实现完全覆盖!不会崩溃
}
}
5. 运行时获取对象属性列表
class Person: NSObject {
var name:String?
var age:Int = 0
var title: String?
//使用运行时,获取当前类所有属性的数组
// class 相当于 OC 的加号方法
class func propertyList() -> [String] {
var count: UInt32 = 0
//1. 获取 `类` 的属性列表
let list = class_copyPropertyList(self, &count)
//2. 遍历数组
for i in 0..<Int(count) {
/*
//3. 根据下标获取属性
//objc_property_t?
let pty = list?[i]
//4. 获取属性的名称
//UnsafePointer<Int8>? Int8 C语言的字符串
let cName = property_getName(pty!)
//5. 转换成 String 的字符串
let name = String(utf8String: cName!)
*/
//安全守护
guard let pty = list?[i],
let cName = property_getName(pty),
let name = String(utf8String: cName)
else {
// 继续便利下一个
continue
}
print(name) //name是一定有值的
}
free(list)
return []
}
}
6. 便利构造函数
let button = UIButton(title: <#T##String#>, color: <#T##UIColor#>)
//extension 类似于 OC 中的 category
extension UIButton {
convenience init(title: String, color:UIColor = UIColor.darkGray) {
//便利构造函数 与 OC中的抽取分类类似,
//注意:便利构造函数 本身不负责对象的创建,使用self.init() 初始化对象
self.init()
self.setTitle(title, for:.normal)
self.setTitleColor(color, for: .normal)
self.sizeToFit()
}
}
7. 懒加载
//懒加载 - lazy
// 1. 能够延迟创建
// 2. 解除解包的烦恼
// 注意:懒加载的代码只会在第一次调用的时候,执行闭包,然后将闭包的结果保存在 label 的属性中
// Swift中一定注意不要主动清理视图或者控件,因为懒加载不会再次创建
lazy var label:UILabel? = UILabel()
override func viewDidLoad() {
super.viewDidLoad()
print(label)
label?.text = "hello"
label?.sizeToFit()
print(label)
label = nil //释放label
print(label)
}
8. getter&setter方法
class Person: NSObject {
private var _name: String?
//Swift 中一般不会重写 getter/setter方法
var name:String? {
get {
return _name
}
set {
_name = newValue
}
}
}
9. 只读属性 readonly
//readonly -> 重写 getter 方法
var title: String{
//只重写 getter 方法,就是只读属性
get {
return "Mr" + (name ?? "")
}
}
//只读属性的简写(直接 return)
//又称为 计算型属性(本身不保存内容,都是通过计算获得结果)
var title2: String {
return "Mr XXX" + (name ?? "")
}
10. 利用模型的didSet 设置 UI 界面
class DemoLabel: UILabel {
var person: Person? {
//就是替代 OC 中重写 setter 方法
//区别:不需要考虑 _成员变量 = 值
didSet {
//此时 name 属性已经有值,可以直接使用设置UI内容
text = person?.name
}
}
}