****函数****
目标
- 掌握函数的定义格式
- 掌握外部参数的用处
- 掌握无返回类型的三种函数定义方式
****代码实现****
函数定义
函数的定义
格式fund 函数名(行参列表) -> 返回值 {代码实现}
-
调用 let result = 函数名(值1,参数2:值2...)
fun sum(a:Int,b: Int) -> Int { return a + b } let result = sum(10, b: 20)
-
函数格式小结
// 格式:func 函数名(形参1: 类型 = 默认值, _ 形参2: 类型 = 默认值...) -> 返回值 { // 代码实现 } // 说明:包含默认值的函数可以不用传递,并且可以任意组合 // // 格式:func 函数名(形参1: 类型, _ 形参2: 类型...) -> 返回值 { // 代码实现 } // 说明:_ 可以忽略外部参数,与其他语言的函数风格更加类似 // // 格式:func 函数名(外部参数1 形参1: 类型, 外部参数2 形参2: 类型...) -> 返回值 { // 代码实现 } // 说明:外部参数名供外部调用使用,形参 在函数内部使用 // // 格式:func 函数名(形参列表) -> 返回值 { // 代码实现 }
****没有返回值****
没有返回值的函数,一共有三种写法
省略
()
-
Void
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("oleo")
-
外部参数
- 在形参名前在增加一个外部参数名,能够方便调用人员更好第理解函数的语义
- 格式: func 函数名(外部参数名 形式参数名: 形式参数类型) -> 返回值类型 { // 代码实现 }
- Swift 2.0中,默认第一个参数名省略
func sum1(num1 a: Int, num2 b: Int) -> Int {
return a + b
}
sum1(num1: 10, num2: 20)
****闭包****
与 OC 中的 Block 类似,闭包主要用于异步操作执行完成后的代码回调,网络访问结果以参数的形式传递给调用方
目标
掌握闭包的定义
掌握闭包的概念和用法
了解尾随闭包的写法
掌握解除循环引用的方法
-
OC 中 Block 概念回顾
- 闭包类似于 OC 中的 Block
- 预先定义好的代码
- 在需要时执行
- 可以当作参数传递
- 可以有返回值
- 包含 self 时需要注意循环引用
****闭包的定义****
- 定义一个函数
//: 定义一个 sum 函数
func sum(num1 num1: Int, num2: Int) -> Int {
return num1 + num2
}
sum(num1: 10, num2: 30)
//: 在 Swift 中函数本身就可以当作参数被定义和传递
let mySum = sum
let result = mySum(num1: 20, num2: 30)
- 定义一个闭包
- 闭包= { (行参) -> 返回值 in // 代码实现 }
- in 用于区分函数定义和代码实现
//: 闭包 = { (行参) -> 返回值 in // 代码实现 }
let sumFunc = { (num1 x: Int, num2 y: Int) -> Int in
return x + y
}
sumFunc(num1: 10, num2: 20)
- 闭包格式小结
// 格式: let 闭包名: (形参) -> 返回类型 = { (形参1, 形参2, ...) in 代码实现 }
// 说明: 使用形参列表,可以直接提供外部参数
//
// 格式: let 闭包名: (形参类型) -> 返回类型 = { (形参1, 形参2, ...) in 代码实现 }
//
// 格式: { (外部参数1 形参1, 外部参数2 形参2, ...) -> 返回类型 in 代码实现 }
// 说明: 使用外部参数会方便调用
//
// 格式: { (形参列表) -> 返回类型 in 代码实现 }
最简单的闭包,如果没有参数/返回值,则 参数/返回值/in 统统都可以省略
- {代码实现}
let demoFunc = {
print("hello")
}
****基本使用****
GCD异步
- 模拟在后台线程加载数据
func loadData() {
dispatch_async(dispatch_get_global_queue(0, 0), { () -> Void in
print("耗时操作 \(NSThread .currentThread())")
})
}
- 尾随闭包,如果闭包是最后一个参数,可以用以下写法
- 注意上下两段代码,} 的位置
func loadData() {
dispatch_async(dispatch_get_global_queue(0, 0)) { () -> Void in
print("耗时操作 \(NSThread .currentThread())")
}
}
- 闭包的简写,如果闭包中没有参数和返回值,可以省略
func loadData() {
dispatch_async(dispatch_get_global_queue(0, 0)) {
print("耗时操作 \(NSThread .currentThread())")
}
}
****自定义闭包参数,实现主线程回调****
- 添加没有参数,没有返回值的闭包
override func viewDidLoad() {
super.viewDidLoad()
loadData {
print("完成回调")
}
}
// MARK: - 自定义闭包参数
func loadData(finished: ()->()) {
dispatch_async(dispatch_get_global_queue(0, 0)) {
print("耗时操作 \(NSThread.currentThread())")
dispatch_sync(dispatch_get_main_queue()) {
print("主线程回调 \(NSThread.currentThread())")
// 执行回调
finished()
}
}
}
- 添加回调参数
override func viewDidLoad() {
super.viewDidLoad()
loadData4 { (html) -> () in
print(html)
}
}
/// 加载数据
/// 完成回调 - 传入回调闭包,接收异步执行的结果
func loadData4(finished: (html: String) -> ()) {
dispatch_async(dispatch_get_global_queue(0, 0)) {
print("加载数据 \(NSThread.currentThread())")
dispatch_sync(dispatch_get_main_queue()) {
print("完成回调 \(NSThread.currentThread())")
finished(html: "hello world
")
}
}
}
****循环引用****
- 创建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类似的方法
/// 类似于 OC 的解除引用
func demo() {
weak var weakSelf = self
tools?.loadData() {
print("\(weakSelf?.view)")
}
}
- Swift 推荐的方法
loadData { [weak self] in
print("\(self?.view)")
}
- 还可以
loadData { [unowned self] in
print("\(self.view)")
}
闭包(Block) 的循环引用小结
- Swift
- [weak self]
- self是可选项,如果self已经被释放,则为nil
- [unowned self]
- self不是可选项,如果self已经被释放,则出现野指针访问
- Objc
- __weak typeof(self) weakSelf;
- 如果self已经被释放,则为nil
- __unsafe_unretained typeof(self) weakSelf;
- 如果self已经被释放,则出现野指针访问
****面向对象****
构造函数基础
构造函数是一种特殊的函数,主要用来在创建对象时初始化对象,为对象成员变量设置初始值,在 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()
}
小结
- 非 Optional 属性,都必须在构造函数中设置初始值,从而保证对象在被实例化的时候,属性都被正确初始化
- 在调用父类构造函数之前,必须保证本类的属性都已经完成初始化
- Swift 中的构造函数不用写 func
子类的构造函数
- 自定义子类时,需要在构造函数中,首先为本类定义的属性设置初始值
- 然后再调用父类的构造函数,初始化父类中定义的属性
/// 学生类
class Student: Person {
/// 学号
var no: String
override init() {
no = "001"
super.init()
}
}
小结
- 先调用本类的构造函数初始化本类的属性
- 然后调用父类的构造函数初始化父类的属性
- Xcode 7 beta 5之后,父类的构造函数会被自动调用,强烈建议写 super.init(),保持代码执行线索的可读性
- super.init() 必须放在本类属性初始化的后面,保证本类属性全部初始化完成
****Optional 属性****
- 将对象属性类型设置为 Optional
class Person: NSObject {
/// 姓名
var name: String?
/// 年龄
var age: Int?
}
- 可选属性不需要设置初始值,默认初始值都是 nil
- 可选属性是在设置数值的时候才分配空间的,是延迟分配空间的,更加符合移动开发中延迟创建的原则
重载构造函数
- Swift 中支持函数重载,同样的函数名,不一样的参数类型
/// `重载`构造函数
///
/// - 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 以完成父类属性的初始化工作
****重载和重写****
- 重载,函数名相同,参数名/参数类型/参数个数不同
- 重载函数并不仅仅局限于构造函数
- 函数重载是面相对象程序设计语言的重要标志
- 函数重载能够简化程序员的记忆
- OC 不支持函数重载,OC 的替代方式是 withXXX...
- 重写,子类需要在父类拥有方法的基础上进行扩展,需要 override 关键字
****KVC 字典转模型构造函数****
/// `重写`构造函数
///
/// - parameter dict: 字典
///
/// - returns: Person 对象
init(dict: [String: AnyObject]) {
setValuesForKeysWithDictionary(dict)
}
以上代码编译就会报错!
-
原因:
- KVC 是 OC 特有的,KVC 本质上是在运行时,动态向对象发送 setValue:ForKey: 方法,为对象的属性设置数值
- 因此,在使用 KVC 方法之前,需要确保对象已经被正确实例化
- 添加 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 与 键值编码不兼容
- 原因:
- 在 Swift 中,如果属性是可选的,在初始化时,不会为该属性分配空间
- 而 OC 中基本数据类型就是保存一个数值,不存在可选的概念
- 解决办法:给基本数据类型设置初始值
- 修改后的代码如下:
/// 姓名
var name: String?
/// 年龄
var age: Int? = 0
/// `重写`构造函数
///
/// - parameter dict: 字典
///
/// - returns: Person 对象
init(dict: [String: AnyObject]) {
super.init()
setValuesForKeysWithDictionary(dict)
}
提示:在定义类时,基本数据类型属性一定要设置初始值,否则无法正常使用 KVC 设置数值
****KVC 函数调用顺序****
init(dict: [String: AnyObject]) {
super.init()
setValuesForKeysWithDictionary(dict)
}
override func setValue(value: AnyObject?, forKey key: String) {
print("Key \(key) \(value)")
super.setValue(value, forKey: key)
}
// `NSObject` 默认在发现没有定义的键值时,会抛出 `NSUndefinedKeyException` 异常
override func setValue(value: AnyObject?, forUndefinedKey key: String) {
print("UndefinedKey \(key) \(value)")
}
- setValuesForKeysWithDictionary 会按照字典中的 key 重复调用 setValue:forKey 函数
- 如果没有实现 forUndefinedKey 函数,程序会直接崩溃
- NSObject 默认在发现没有定义的键值时,会抛出 NSUndefinedKeyException 异常
- 如果实现了 forUndefinedKey,会保证 setValuesForKeysWithDictionary 继续遍历后续的 key
- 如果父类实现了 forUndefinedKey,子类可以不必再实现此函数
子类的 KVC 函数
/// 学生类
class Student: Person {
/// 学号
var no: String?
}
- 如果父类中已经实现了父类的相关方法,子类中不用再实现相关方法
** KVC 字典转模型示意图**
便利构造函数
目的
- 条件判断,只有满足条件,才实例化对象,可以防治造成不必
要的内存开销 - 简化对象的创建
- 本身不负责属性的创建和初始化工作
特点
- 默认情况下,所有的构造方法都是指定构造函数 Designated
- convenience 关键字修饰的构造方法就是便利构造函数
- 便利构造函数具有以下特点:
- 可以返回 nil
- 只有便利构造函数中可以调用 self.init()
- 便利构造函数不能被重载或者 super
- 便利构造函数主要用于条件监测或者简化对象创建
/**
便利构造函数的目的:
1. 条件判断
2. 简化对象的创建
3. 本身不负责属性的创建和初始化工作
*/
convenience init?(dict: [String: AnyObject]) {
// 判断是否包含姓名
guard let _ = dict["name"] as? String else {
printLog("没有指定姓名")
return nil
}
guard let age = dict["age"] as? Int else {
printLog("没有指定年龄")
return nil
}
if age > 100 || age < 0 {
printLog("年龄不正确")
return nil
}
// Convenience initializer for 'Person' must delegate (with 'self.init') rather than chaining to a superclass initializer (with 'super.init')
// 遍历构造函数必须调用本类的 self.init,而不能调用父类的 super.init
self.init()
printLog(self.classForCoder)
// Use of 'self' in method call 'setValuesForKeysWithDictionary' before super.init initializes self
// 使用 self 调用 setValuesForKeysWithDictionary 之前需要调用 super.init 方法
// 只有确保对象已经被正确的实例化之后,才能向对象发送消息
setValuesForKeysWithDictionary(dict)
}
便利构造函数应用场景
- 根据给定参数判断是否创建对象,而不像指定构造函数那样必须要实例化一个对象出来
- 在实际开发中,可以对已有类的构造函数进行扩展,利用便利构造函数,简化对象的创建
构造函数小结 - 指定构造函数必须调用其直接父类的的指定构造函数(除非没有父类)
- 便利构造函数必须调用同一类中定义的其他指定构造函数或者用 self. 的方式调用父类的便利构造函数
- 便利构造函数可以返回 nil
- 便利构造函数不能被重载
懒加载
在 iOS 开发中,懒加载是无处不在的
- 懒加载的格式如下
lazy var person: Person = {
print("懒加载")
return Person()
}()
- 懒加载本质上是一个闭包
- 以上代码可以改写为以下格式
let personFunc = { () -> Person in
print("懒加载")
return Person()
}
lazy var demoPerson: Person = self.personFunc()
- 懒加载的简单写法
lazy var demoPerson: Person = Person()
只读属性
- 在 Swift 中 getter & setter 很少用,以下代码仅供了解
private var _name: String?
var name: String? {
get {
return _name
}
set {
_name = newValue
}
}
存储型属性 & 计算型属性
- 存储型属性 - 需要开辟空间,以存储数据
- 计算型属性 - 执行函数返回其他内存地址
var title: String {
get {
return "Mr " + (name ?? "")
}
}
- 只实现 getter 方法的属性被称为计算型属性,等同于 OC 中的 ReadOnly 属性
- 计算型属性本身不占用内存空间
- 不可以给计算型属性设置数值
- 计算型属性可以使用以下代码简写
var title: String {
return "Mr " + (name ?? "")
}
计算型属性与懒加载的对比
- 计算型属性
- 不分配独立的存储空间保存计算结果
- 每次调用时都会被执行
- 更像一个函数,不过不能接收参数,同时必须有返回值
var title2: String {
return "Mr" + (name ?? "")
}
- 懒加载属性
- 在第一次调用时,执行闭包并且分配空间存储闭包返回的数值
- 会分配独立的存储空间
- 与 OC 不同的是,lazy 属性即使被设置为 nil 也不会被再次调用
lazy var title: String = {
return "Mr " + (self.name ?? "")
}()
网络访问
ATS 应用传输安全
App Transport Security (ATS) lets an app add a declaration to its Info.plist file that specifies the domains with which it needs secure communication. ATS prevents accidental disclosure, provides secure default behavior, and is easy to adopt. You should adopt ATS as soon as possible, regardless of whether you’re creating a new app or updating an existing one.
If you’re developing a new app, you should use HTTPS exclusively. If you have an existing app, you should use HTTPS as much as you can right now, and create a plan for migrating the rest of your app as soon as possible.
强制访问
NSAppTransportSecurity
NSAllowsArbitraryLoads
设置白名单
NSAppTransportSecurity
NSExceptionDomains
localhost
NSTemporaryExceptionAllowsInsecureHTTPLoads
- 网络访问代码
let url = NSURL(string: "http://www.weather.com.cn/data/sk/101010100.html")!
NSURLSession.sharedSession().dataTaskWithURL(url) { (data, _, error) in
if error != nil {
print(error)
return
}
}.resume()
运行提示:App Transport Security has blocked a cleartext HTTP (http://) resource load since it is insecure. Temporary exceptions can be configured via your app's Info.plist file.
JSON 序列化
let dict = try! NSJSONSerialization.JSONObjectWithData(data!, options: NSJSONReadingOptions(rawValue: 0))
print(dict)
- try catch
do {
let tmpData = "{\"name\": \"zhangsan\"}".dataUsingEncoding(NSUTF8StringEncoding)
let dict = try NSJSONSerialization.JSONObjectWithData(tmpData!, options: NSJSONReadingOptions[])
print(dict)
} catch {
print(error)
}