在swift中,实现回调传值常用的有三种方式:
- 闭包 closure
- 代理 delegate
- 通知 NSNotification
今天我们主要来讲下闭包,swift里面的闭包就相当于OC中的block,只不过swift里面的闭包要比OC中的block更强大,使用更方便
1. 话不多说,咱们先来上个最普通的闭包代码
//无参无返回值 闭包类型为:() -> Void
let handler:() -> Void = {
() in
print("调用了无参无返回闭包")
}
handler() //调用闭包
//无参无返回简写 闭包类型为:() -> Void (注:可直接省略 () in )
let simpleHandler:() -> Void = {
print("调用了--简写--无参无返回闭包")
}
simpleHandler() //调用闭包
//有参有返回 闭包类型为:(Int,Int) -> Int
let addNumberHandler:(_ a:Int, _ b:Int) -> Int = {
(a:Int, b:Int) in
return a + b
}
let result = addNumberHandler(10,20) //调用闭包
print("有参有返回-->返回值为:\(result)")
//有参有返回简写 闭包类型为:(Int,Int) -> Int
let addNumberSimpleHandler:(Int,Int) -> Int = {
(a,b) in
return a + b
}
let resultSimple = addNumberSimpleHandler(10,20) //调用闭包
print("有参有返回-->简写-->返回值:\(resultSimple)")
/** 执行结果值输出:
调用了无参无返回闭包
调用了--简写--无参无返回闭包
有参有返回-->返回值为:30
有参有返回-->简写-->返回值:30
*/
2. swift中,可以声明一个闭包类型,方便下文引用,用关键字 typealias
类似OC中的 typedef
下面来组示例
//swift中
typealias Handler = (_ name: String, _ age: Int) -> Void
//3.0一般简写为,实现也是,参数会根据上下文推断
typealias Handler = (String,Int) -> Void
//OC中
typedef void(^Handler)(NSString *name, int age);
例:现在在两个不同的类中传值,在OtherViewController定义个闭包属性,ViewController类里实现这个闭包,pop回ViewController之前调用这个闭包从而实现传值,话不多说,上代码
class ViewController: UIViewController
//点击按钮push到OtherViewController页面
@IBAction func PushToOtherViewController(_ sender: UIButton) {
let otherVC = OtherViewController()
//实现CallBack闭包属性-->等待调用
otherVC.CallBack = {
(name,age) in
print("我的名字叫\(name),今年\(age)岁了")
}
self.navigationController?.pushViewController(otherVC, animated: true)
}
class OtherViewController: UIViewController
//声明闭包类型
typealias Handler = (String,Int) -> Void
//声明闭包属性
var CallBack:Handler?
//或者 var CallBack:((String,Int) -> Void)?
//点按方法-->pop会ViewController页面,调用闭包传值
@IBAction func popToViewController(_ sender: UIButton) {
self.CallBack!("超人",24) //调用CallBack闭包
self.navigationController?.popViewController(animated: true)
}
当我们Push到OtherViewController页面,点击按钮调用popToViewController方法时,会在ViewController页面打印:我的名字叫超人,今年24岁了
3. 我们来写个简单的网络请求使用闭包,同时讲下尾随闭包和逃逸闭包是怎么回事,咱先看代码
一个简单的get请求,static来修饰,相当于OC中的类方法,请求地址和请求参数分别以字符串的和字典的形式传进方法内进行拼接,拼完发请求,返回结果以调用闭包的形式传出
调用闭包resultBlock: ((_ result:Any?,_ error:Any?) -> Void)?两个参数分别都是可选的并且为任意类型,包括整个闭包也是可选实现的 可简写为:resultBlock: ((Any?,Any?) -> Void)? 直接省略参数名
class HttpManager: NSObject
//Get请求
static func getRequestData(urlString: String,paras: Dictionary<String,Any>?,resultBlock: ((_ result:Any?,_ error:Any?) -> Void)?) {
var i = 0
var address = urlString
if let para = paras {
for (key,value) in para {
if i == 0 {
address += "?\(key)=\(value)"
}else {
address += "&\(key)=\(value)"
}
i += 1
}
}
//转换请求URL字符串中的特殊字符
let request = URLRequest.init(url: URL.init(string: address.addingPercentEncoding(withAllowedCharacters: CharacterSet.urlQueryAllowed)!)!)
let session = URLSession.shared
let dataTask = session.dataTask(with: request) { (data, respond, error) in
if let data = data {
//系统建议使用的try crash类型,转换JSON失败直接返回nil
guard let result = try? JSONSerialization.jsonObject(with: data, options: .allowFragments) else {
return
}
resultBlock!(result,nil) //请求成功,传入result,error传nil
}else {
resultBlock!(nil,error) //请求失败,result传nil,传入error
}
}
dataTask.resume()
}
在ViewController类中调用方法发请求
class ViewController: UIViewController
/** Get请求测试 */
func getRequest() {
//随便用个连接测试下
let urlStr = "http://yiapi.xinli001.com/v2/yi/ad-list.json?key=18ee0593a01c07f71d28fa936d671457&_platform=ios&_version=3.3.3&name=yiapp-home-ad"
HttpManager.getRequestData(urlString: urlStr, paras: nil) { (result, error) in
if let result = result {
print(result)
}else {
print("Get请求失败:\(error)")
}
}
}
-
尾随闭包: 闭包作为参数追加在方法末尾,这个没什么,无非就是一个方法实现闭包的简写形式
其实上面的Get请求方法getRequestData(urlString: String,paras: Dictionary<String,Any>?,resultBlock: ((Any?,Any?) -> Void)?)
中的resultBlock
就是尾随闭包,上面在ViewController
类中实现请求就是尾随闭包的简写形式,联想出方法敲回车,系统自动用尾随闭包的形式写出来了
下面我们来上代码做下对比
//尾随闭包简写形式
HttpManager.getRequestData(urlString: urlStr, paras: nil) { (result, error) in
if let result = result {
print(result)
}else {
print("Get请求失败:\(error)")
}
}
//正常书写方式
HttpManager.getRequestData(urlString: urlStr, paras: nil , resultBlock:{ (result, error) in
if let result = result {
print(result)
}else {
print("Get请求失败:\(error)")
}
})
4. 逃逸闭包与非逃逸闭包
Swift 的闭包分为 逃逸 与 非逃逸 两种,逃逸闭包(可能)会在函数返回之后才被调用,也就是说,闭包逃离了函数的作用域,而非逃逸闭包则相反,调用闭包要在函数返回之前 (举个简单的例子,网络请求数据,在发请求的时是开启了分线程去请求,主线程还是接着执行下面的代码,很快跑完这个函数方法的作用域,请求到数据的时候,调用闭包返回数据,那么这个闭包必须要逃逸)
逃逸的缺陷: 为了管理内存,一个闭包会强引用它捕获的所有对象,如果你在闭包中访问了当前对象中的任意属性或实例方法,闭包会持有当前对象,因为这些方法和属性都隐性地携带了一个 self参数。这种方式很容易导致,循环引用,这解释了为什么编译器会要求你在闭包中显式地写出对 self 的引用,这迫使你考虑潜在的循环引用。然而,使用非逃逸的闭包不会产生循环引用,编译器可以保证在函数返回时闭包会释放它捕获的所有对象。因此,编译器只要求在逃逸闭包中明确对 self 的强引用
** 逃逸闭包与非逃逸闭包的默认: **
- 从 Swift 3.0 开始,非逃逸闭包变成了闭包参数的默认形式,如果你想允许一个闭包参数逃逸,需要给闭包形参前面增加一个 @escaping 的标注
- 在 Swift 3 之前,完全是另外一回事:逃逸是默认状态,你可以添加 @noescape 来覆盖此状态。新的行为更好,因为在默认状态下是安全的:遇到有潜在循环引用的情况时,一个方法调用必须显式地予以标注。因此@escaping 标识符还有警示开发者的作用
** 可选型的闭包总是逃逸的: **
讲到这里我们会奇怪,既然swift3.0以后默认非逃逸闭包,但是上面的网络请求闭包形参前面没加@escaping修饰呢,哈哈哈...标题已经写出来了,可选闭包默认是逃逸闭包,闭包被用作参数,但是当闭包被包裹在其他类型(例如元组、枚举的 case 以及可选型)中的时候,闭包仍旧是逃逸的。由于在这种情况下闭包不再是即时的参数,它会自动变成逃逸闭包。因此,在 Swift 3.0 中,当你编写一个接受函数类型参数的函数时,该参数不能同时是可选型和非逃逸的
那这样吧,我再写个Post请求类方法(简写形式,省略参数名),这样用非可选类型,逃逸闭包标识,与上面的Get请求方法做对比,这样更好理解(两者写的方式不一样,但实质上都是逃逸的)
class HttpManager: NSObject
//POST请求
static func postRequestData(urlString:String,paras:Dictionary<String,Any>?,resultBlock: @escaping (Any?,Any?) -> Void) {
var i = 0
var body:String = ""
if let paras = paras {
for (key,value) in paras {
if i == 0 {
body += "\(key)=\(value)"
}else {
body += "&\(key)=\(value)"
}
i += 1
}
}
var request = URLRequest.init(url: URL.init(string: urlString)!)
request.httpMethod = "POST"
request.httpBody = body.data(using: .utf8)
request.allHTTPHeaderFields = ["Content-Type":"application/json"] //有的服务器端可能要加上这个请求头
let session = URLSession.shared
let dataTask = session.dataTask(with: request) { (data, respond, error) in
if let data = data {
guard let result = try? JSONSerialization.jsonObject(with: data, options: .allowFragments) else {
return
}
resultBlock(result,nil) //请求成功
}else {
resultBlock(nil,error) //请求失败
}
}
dataTask.resume()
}