$ xcrun swift --version
Apple WWDC
发布Apple WWDC
支持 Linux系统,国外已经有人开始用Swift 写服务端克里斯·拉特纳
开始着手 Swift 编程语言的设计NS
前缀去掉了Cocoa
和 Cocoa Touch
框架取消了 Objective-C 的指针
及其他不安全访问的使用Smalltalk
的语法,全面改为句点表示法namespace
)、泛型(generic
)、运算对象重载(operator overloading
)smalltack-c
,迄今已经有 40 多年的历史,虽然 OC
的项目还会在未来持续一段时间,但是更换成 Swift
是未来必然的趋势let
定义常量,一经赋值不允许再修改var
定义变量,赋值之后仍然可以修改//: # 常量
//: 定义常量并且直接设置数值
let x = 20
//: 常量数值一经设置,不能修改,以下代码会报错
// x = 30
//: 使用 `: 类型`,仅仅只定义类型,而没有设置数值
let x1: Int
//: 常量有一次设置数值的机会,以下代码没有问题,因为 x1 还没有被设置数值
x1 = 30
//: 一旦设置了数值之后,则不能再次修改,以下代码会报错,因为 x1 已经被设置了数值
// x1 = 50
//: # 变量
//: 变量设置数值之后,可以继续修改数值
var y = 200
y = 300
重要技巧:Option + Click 可以查看变量的类型
[图片上传失败…(image-1c3941-1536302339170)]
如果要对不同类型的数据进行计算,必须要显式的转换
let x2 = 100
let y2 = 10.5
let num1 = Double(x2) + y2
let num2 = x2 + Int(y2)
var
let
负责
Optional
是 Swift 的一大特色,也是 Swift 初学者最容易困惑的问题可选的
,表示该变量可以有一个指定类型的值,也可以是 nil
?
,表示该变量是可选的nil
//: num 可以是一个整数,也可以是 nil,注意如果为 nil,不能参与计算
let num: Int? = 10
nil
,不允许参与计算解包(unwrap)
后才能参与计算!
,可以强行解包注意:必须要确保解包后的值不是 nil,否则会报错
//: num 可以是一个整数,也可以是 nil,注意如果为 nil,不能参与计算
let num: Int? = 10
//: 如果 num 为 nil,使用 `!` 强行解包会报错
let r1 = num! + 100
//: 使用以下判断,当 num 为 nil 时,if 分支中的代码不会执行
if let n = num {
let r = n + 10
}
unexpectedly found nil while unwrapping an Optional value
翻译
在[解包]一个可选值时发现 nil
??
运算符??
运算符可以用于判断 变量/常量
的数值是否是 nil
,如果是则使用后面的值替代??
能够简化代码的编写let num: Int? = nil
控制流
let r1 = (num ?? 0) + 10
print(r1)
非零即真
概念true
/ false
()
可以省略{}
不能省略let num = 200
if num <10 {
print("比 10 小")
} else if num > 100 {
print("比 100 大")
} else {
print("10 ~ 100 之间的数字")
}
三目
运算保持了和 OC 一致的风格var a = 10
var b = 20
let c = a > b ? a : b
print(c)
适当地运用三目,能够让代码写得更加简洁
nil
,而一旦为 nil
则不允许参与计算nil
let url = NSURL(string: "http://www.baidu.com")
//: 方法1: 强行解包 - 缺陷,如果 url 为空,运行时会崩溃
let request = NSURLRequest(URL: url!)
//: 方法2: 首先判断 - 代码中仍然需要使用 `!` 强行解包
if url != nil {
let request = NSURLRequest(URL: url!)
}
//: 1> 初学 swift 一不小心就会让 if 的嵌套层次很深,让代码变得很丑陋
if let u = url {
if u.host == "www.baidu.com" {
let request = NSURLRequest(URL: u)
}
}
//: 2> 使用 ',' 可以获取前面赋值对象
if let u = url , u.host == "www.baidu.com" {
let request = NSURLRequest(URL: u)
}
if let
不能与使用 &&
、||
等条件判断//: 3> 可以使用 `,` 同时判断多个可选项是否为空
let oName: String? = "张三"
let oNo: Int? = 100
if let name = oName {
if let no = oNo {
print("姓名:" + name + " 学号: " + String(no))
}
}
if let name = oName, let no = oNo {
print("姓名:" + name + " 学号: " + String(no))
}
let oName: String? = "张三"
let oNum: Int? = 18
if var name = oName, num = oNum {
name = "李四"
num = 1
print(name, num)
}
guard
是与 if let
相反的语法,Swift 2.0 推出的let oName: String? = "张三"
let oNum: Int? = 18
guard let name = oName else {
print("name 为空")
return
}
guard let num = oNum else {
print("num 为空")
return
}
// 代码执行至此,name & num 都是有值的
print(name)
print(num)
switch
不再局限于整数switch
可以针对任意数据类型
进行判断break
case
后面必须有可以执行的语句default
分支中case
中定义的变量仅在当前 case
中有效,而 OC 中需要使用 {}
let score = "优"
for 循环
switch score {
case "优":
let name = "学生"
print(name + "80~100分")
case "良": print("70~80分")
case "中": print("60~70分")
case "差": print("不及格")
default: break
}
var sum = 0
for var i = 0; i <10; i++ {
sum += I
}
print(sum)
for-in
,0..<10 表示从0到9sum = 0
for i in 0..<10 {
sum += I
}
print(sum)
sum = 0
for i in 0...10 {
sum += I
}
print(sum)
_
能够匹配任意类型_
表示忽略对应位置的值for _ in 0...10 {
字符串
print("hello")
}
在 Swift 中绝大多数的情况下,推荐使用
String
类型
String
和 NSString
之间的无缝转换for s in str.characters {
print(s)
}
// 返回以字节为单位的字符串长度,一个中文占 3 个字节
let len1 = str.lengthOfBytesUsingEncoding(NSUTF8StringEncoding)
// 返回实际字符的个数
let len2 = str.characters.count
\(变量名)
的方式可以快速拼接字符串let str1 = "Hello"
let str2 = "World"
let i = 32
str = "\(i) 个 " + str1 + " " + str2
我和我的小伙伴再也不要考虑
stringWithFormat
了 :D
Optional
??
操作符??
操作符用于检测可选项是否为 nil
nil
,使用当前值nil
,使用后面的值替代let str1 = "Hello"
let str2 = "World"
let i: Int? = 32
str = "\(i ?? 0) 个 " + str1 + " " + str2
String(format:...)
的方式let h = 8
let m = 23
let s = 9
let timeString = String(format: "%02d:%02d:%02d", arguments: [h, m, s])
let timeStr = String(format: "%02d:%02d:%02d", h, m, s)
String
和 Range
连用时,语法结构比较复杂NSString
再处理let helloString = "我们一起飞"
(helloString as NSString).substringWithRange(NSMakeRange(2, 3))
let str = "爱笑的人运气不会太差哟"
集合
let startIndex = str.index(str.startIndex, offsetBy: 2)
let endIndex = str.index(str.endIndex, offsetBy: -1)
let subStr1 = str.substring(to: startIndex)
print(subStr1)
let subStr2 = str.substring(from: endIndex)
print(subStr2)
let subStr3 = str.substring(with: startIndex..
//String 和 NSString 之间可以相互转换 可以转换为 NSString 来截取子串
let NStr = (str as NSString).substring(with: NSRange(location: 1, length: 2))
print(NStr)
[]
定义,这一点与 OC 相同//: [Int]
let numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
for num in numbers {
print(num)
}
let num1 = numbers[0]
let num2 = numbers[1]
let
定义不可变数组var
定义可变数组let array = ["zhangsan", "lisi"]
//: 不能向不可变数组中追加内容
//array.append("wangwu")
var array1 = ["zhangsan", "lisi"]
//: 向可变数组中追加内容
array1.append("wangwu")
//: array1 仅允许追加 String 类型的值
//array1.append(18)
var array2: [Any] = ["zhangsan", 18]
//: 在 Swift 中,数字可以直接添加到集合,不需要再转换成 `NSNumber`
array2.append(100)
:
可以只定义数组的类型[类型]()
可以实例化一个空的数组var array3: [String]
//: 实例化之前不允许添加值
//array3.append("laowang")
//: 实例化一个空的数组
array3 = [String]()
array3.append("laowang")
array3 += array1
//: 必须是相同类型的数组才能够合并,以下两句代码都是不允许的
//array3 += array2
//array2 += array3
//: 删除指定位置的元素
array3.removeAtIndex(3)
//: 清空数组
array3.removeAll()
[]
定义字典let
不可变字典var
可变字典[String : Any]
是最常用的字典类型//: [String : Any] 是最常用的字典类型
var dict = ["name": "zhangsan", "age": 18]
dict[key] = value
格式//: * 如果 key 不存在,会设置新值
dict["title"] = "boss"
//: * 如果 key 存在,会覆盖现有值
dict["name"] = "lisi"
dict
k
,v
可以随便写key
value
//: 遍历
for (k, v) in dict {
print("\(k) ~~~ \(v)")
}
//: 合并字典
错误处理
var dict1 = [String: NSObject]()
dict1["nickname"] = "大老虎"
dict1["age"] = 100
//: 如果 key 不存在,会建立新值,否则会覆盖现有值
for (k, v) in dict1 {
dict[k] = v
}
print(dict)
NS
前缀let url = URL(string: "http://www.weather.com.cn/data/sk/101010100.html")!
URLSession.shared.dataTask(with: url, completionHandler: { (data, _, error) in
if error != nil {
print(error)
return
}
}).resume()
// 方式1:强try.加载失败直接崩溃
函数
let dict = try! JSONSerialization.jsonObject(with: data!, options: [])
print(dict)
// 方式2:可选try.反序列化失败返回nil
let dict = try? JSONSerialization.jsonObject(with: data!, options: [])
print(dict)
// 方式3:默认try.返回序列化失败可以捕捉详细信息
do {
let dict = try JSONSerialization.jsonObject(with: data!, options: [])
print(dict)
}catch {
print(error)
}
func 函数名(行参列表) -> 返回值 {代码实现}
let result = 函数名(值1, 参数2: 值2...)
//Swfit 3.0
func sum(a: Int, b: Int) -> Int {
return a + b
}
let result = sum(a:10, b: 20)
//Swfit 2.0
func sum(a: Int, b: Int) -> Int {
return a + b
}
let result = sum(10, b: 20)
func demo(str: String) -> Void {
print(str)
}
func demo1(str: String) -> () {
print(str)
}
func demo2(str: String) {
print(str)
}
demo("hello")
demo1("hello world")
demo2("olleh")
func 函数名(外部参数名 形式参数名: 形式参数类型) -> 返回值类型 { // 代码实现 }
func sum1(num1 a: Int, num2 b: Int) -> Int {
return a + b
}
sum1(num1: 10, num2: 20)
func demo() {
//该内部函数的范围只能在 demo函数中可以被访问
func sum() {
print("哈哈哈")
}
//必须先声明内部函数才能调用调用
sum()
}
// 格式:func 函数名(形参1: 类型 = 默认值, _ 形参2: 类型 = 默认值...) -> 返回值 { // 代码实现 }
闭包
// 说明:包含默认值的函数可以不用传递,并且可以任意组合
//
// 格式:func 函数名(形参1: 类型, _ 形参2: 类型...) -> 返回值 { // 代码实现 }
// 说明:_ 可以忽略外部参数,与其他语言的函数风格更加类似
//
// 格式:func 函数名(外部参数1 形参1: 类型, 外部参数2 形参2: 类型...) -> 返回值 { // 代码实现 }
// 说明:外部参数名供外部调用使用,形参 在函数内部使用
//
// 格式:func 函数名(形参列表) -> 返回值 { // 代码实现 }
与 OC 中的 Block 类似,闭包
主要用于异步操作执行完成后的代码回调,网络访问结果以参数的形式传递给调用方
Block
self
时需要注意循环引用 func demo1A() {
func sum() {
print("哈哈哈")
}
sum()
}
in
用于区分函数定义和代码实现//使用闭包实现
func demo1B() {
//定义代码块 () -> ()
//没有参数没有返回值的闭包
let closure = { () -> Void in
print("OK")
}
//执行代码块
closure()
}
func demo3A() {
func sum(num1 a: Int, num2 b: Int) -> Int{
return a + b
}
let result = sum(num1: 10, num2: 20)
print(result)
}
func demo3B() {
let closure = { (num1 a: Int, num2 b: Int ) -> Int in
return a + b
}
let result = closure(num1: 100, num2: 200)
print(result)
}
in
用于区分函数定义和代码实现参数/返回值/in
统统都可以省略let closure = {
print("hello")
}
基本使用闭包的三种简写形式只需要了解即可,在实际开发中做到看到不陌生即可,直接使用系统提示的类型
func loadData() {
DispatchQueue.global().async {
//假装耗时
Thread.sleep(forTimeInterval: 2)
}
}
override func viewDidLoad() {
super.viewDidLoad()
loadData {
print("完成回调")
}
}
//异步请求网络数据没在主队列中回调 更新UI
//TODO: 待会解释该关键词
//@escaping
func loadData(a: Int,finished: @escaping (String) -> () ) {
DispatchQueue.global().async {
//假装耗时
Thread.sleep(forTimeInterval: 2)
//获取结果
let res = "老王"
//在主队列中回调数据
DispatchQueue.main.async {
//回调数据 只负责请求数据 不负责更新UI
//从外界传递闭包
finished(res)
}
}
}
override func viewDidLoad() {
循环引用
super.viewDidLoad()
//定义一个有参数没有返回值的闭包当做参数传递
let closure = { (res: String) -> () in
print("我是请求结果: \(res)")
}
loadData(a:20,finished: closure)
//使用尾随闭包的方式滴啊用
loadData(a: 20) { (res) in
print(res)
}
}
NetworkTools
对象class NetworkTools: NSObject {
/// 加载数据
///
/// - parameter finished: 完成回调
func loadData(finished: () -> ()) {
print("开始加载数据...")
// ...
finished()
}
deinit {
print("网络工具 88")
}
}
NetworkTools
并且加载数据class ViewController: UIViewController {
var tools: NetworkTools?
override func viewDidLoad() {
super.viewDidLoad()
tools = NetworkTools()
tools?.loadData() {
print("come here \(self.view)")
}
}
/// 与 OC 中的 dealloc 类似,注意此函数没有()
deinit {
print("控制器 88")
}
}
运行不会形成循环引用,因为 loadData 执行完毕后,就会释放对 self 的引用
NetworkTools
,定义回调闭包属性/// 完成回调属性
var finishedCallBack: (()->())?
/// 加载数据
///
/// - parameter finished: 完成回调
func loadData(finished: () -> ()) {
self.finishedCallBack = finished
print("开始加载数据...")
// ...
working()
}
func working() {
finishedCallBack?()
}
deinit {
print("网络工具 88")
}
运行测试,会出现循环引用
/// 类似于 OC 的解除引用
func demo() {
weak var weakSelf = self
tools?.loadData() {
print("\(weakSelf?.view)")
}
}
loadData { [weak self] in
print("\(self?.view)")
}
loadData { [unowned self] in
print("\(self.view)")
}
Swift
[weak self]
self
是可选项,如果self已经被释放,则为nil
[unowned self]
self
不是可选项,如果self已经被释放,则出现野指针访问
Objc
__weak typeof(self) weakSelf;
self
已经被释放,则为nil
__unsafe_unretained typeof(self) weakSelf;
self
已经被释放,则出现野指针访问KVC
在构造函数中的使用及原理重载
和 重写
didSet
)
构造函数
是一种特殊的函数,主要用来在创建对象时初始化对象,为对象成员变量
设置初始值,在 OC 中的构造函数是 initWithXXX,在 Swift 中由于支持函数重载,所有的构造函数都是init
构造函数的作用
alloc
init
Person
对象class Person: NSObject {
/// 姓名
var name: String
/// 年龄
var age: Int
}
提示错误 Class 'Person' has no initializers
-> 'Person' 类没有实例化器s
原因:如果一个类中定义了必选属性,必须通过构造函数为这些必选属性分配空间并且设置初始值
重写
父类的构造函数/// `重写`父类的构造函数
override init() {
}
提示错误 Property 'self.name' not initialized at implicitly generated super.init call
-> 属性 'self.name' 没有在隐式生成的 super.init 调用前被初始化
super.init()
调用/// `重写`父类的构造函数
override init() {
super.init()
}
提示错误 Property 'self.name' not initialized at super.init call
-> 属性 'self.name' 没有在 super.init 调用前被初始化
/// `重写`父类的构造函数
override init() {
name = "张三"
age = 18
super.init()
}
func
/// 学生类
class Student: Person {
/// 学号
var no: String
override init() {
no = "001"
super.init()
}
}
super.init()
,保持代码执行线索的可读性super.init()
必须放在本类属性初始化的后面,保证本类属性全部初始化完成Optional
属性Optional
class Person: NSObject {
/// 姓名
var name: String?
/// 年龄
var age: Int?
}
可选属性
不需要设置初始值,默认初始值都是 nil可选属性
是在设置数值的时候才分配空间的,是延迟分配空间的,更加符合移动开发中延迟创建的原则/// `重载`构造函数
///
/// - parameter name: 姓名
/// - parameter age: 年龄
///
/// - returns: Person 对象
init(name: String, age: Int) {
self.name = name
self.age = age
super.init()
}
init()
,则系统不再提供默认的构造函数init()
无法完成分配空间和设置初始值的工作重写
父类的构造函数/// `重写`父类构造函数
///
/// - parameter name: 姓名
/// - parameter age: 年龄
///
/// - returns: Student 对象
override init(name: String, age: Int) {
no = "002"
super.init(name: name, age: age)
}
重载
构造函数/// `重载`构造函数
///
/// - parameter name: 姓名
/// - parameter age: 年龄
/// - parameter no: 学号
///
/// - returns: Student 对象
init(name: String, age: Int, no: String) {
self.no = no
super.init(name: name, age: age)
}
注意:如果是重载的构造函数,必须
super
以完成父类属性的初始化工作
重载
和重写
重载
,函数名相同,参数名/参数类型/参数个数不同构造函数
withXXX...
重写
,子类需要在父类拥有方法的基础上进行扩展,需要 override
关键字/// `重写`构造函数
///
/// - parameter dict: 字典
///
/// - returns: Person 对象
init(dict: [String: Any]) {
setValuesForKeysWithDictionary(dict)
}
以上代码编译就会报错!
原因:
运行时
,动态向对象发送 setValue:ForKey:
方法,为对象的属性设置数值实例化
添加 super.init()
同样会报错
原因:
必选属性
必须在调用父类构造函数之前完成初始化分配工作讲必选参数修改为可选参数,调整后的代码如下:
/// 个人模型
class Person: NSObject {
/// 姓名
var name: String?
/// 年龄
var age: Int?
/// `重写`构造函数
///
/// - parameter dict: 字典
///
/// - returns: Person 对象
init(dict: [String: AnyObject]) {
super.init()
setValuesForKeysWithDictionary(dict)
}
}
运行测试,仍然会报错
错误信息:this class is not key value coding-compliant for the key age.
-> 这个类的键值 age 与 键值编码不兼容
可选
的概念/// 姓名
var name: String?
/// 年龄
var age: Int? = 0
/// `重写`构造函数
///
/// - parameter dict: 字典
///
/// - returns: Person 对象
init(dict: [String: Any]) {
super.init()
setValuesForKeysWithDictionary(dict)
}
提示:在定义类时,基本数据类型属性一定要设置初始值,否则无法正常使用 KVC 设置数值
init(dict: [String: Any]) {
super.init()
setValuesForKeysWithDictionary(dict)
}
override func setValue(value: Any?, forKey key: String) {
print("Key \(key) \(value)")
super.setValue(value, forKey: key)
}
// `NSObject` 默认在发现没有定义的键值时,会抛出 `NSUndefinedKeyException` 异常
override func setValue(value: Any?, forUndefinedKey key: String) {
print("UndefinedKey \(key) \(value)")
}
setValuesForKeysWithDictionary
会按照字典中的 key
重复调用 setValue:forKey
函数forUndefinedKey
函数,程序会直接崩溃NSUndefinedKeyException
异常forUndefinedKey
,会保证 setValuesForKeysWithDictionary
继续遍历后续的 key
forUndefinedKey
,子类可以不必再实现此函数/// 学生类
class Student: Person {
/// 学号
var no: String?
}
Designated
convenience
关键字修饰的构造方法就是便利构造函数nil
,self.init()
重写
或者 super
/// `便利构造函数`
///
/// - parameter name: 姓名
/// - parameter age: 年龄
///
/// - returns: Person 对象,如果年龄过小或者过大,返回 nil
convenience init?(name: String, age: Int) {
if age <20 || age > 100 {
return nil
}
self.init(dict: ["name": name, "age": age])
}
注意:在 Xcode 中,输入
self.init
时没有智能提示
/// 学生类
class Student: Person {
/// 学号
var no: String?
convenience init?(name: String, age: Int, no: String) {
self.init(name: name, age: age)
self.no = no
}
}
指定构造函数
或者用 self.
的方式调用父类的便利构造函数
nil
在 iOS 开发中,懒加载是无处不在的
lazy var person: Person = {
print("懒加载")
return Person()
}()
lazy var demoPerson: Person = Person()