iOS 底层面试swift基础知识

swift 闭包详解加应用

2021-01-04  本文已影响0人  大宝的爱情

1.表达式

闭包表达式包含参数,返回值,及函数体代码(参数不能有默认值)

{
   (参数列表) -> 返回值类型 in 
    函数体代码 
}
// 例
var fun = {
   (v1: Int, v2: Int) -> Int in
   return v1 + v2
}
// 闭包表达式调用的时候 不用写参数名
fun(10, 20)

//上述代码可直接写成(小括号意思是执行闭包)
{
   (v1: Int, v2: Int) -> Int in
   return v1 + v2
}(10, 20)

2.省略&简写

闭包可以省略一些参数(开发中应适当省略,不能全部最简,否则可读性太差,不好维护)

详细:
可以在闭包中定义参数,返回值。 闭包后用括号执行,并在括号中可以传参。 例如:

var str={ (arg1:String,arg2:String) -> String in
      return arg1+arg2
}("str1","str2")

基于上面最全的定义方式, 我么可以省略参数的类型:

var str={ arg1,arg2 -> String in
       return arg1+arg2
}("str1","str2")

为什么能省略参数类型?那是因为,swift的类型推导,根据后面括号的传参能自动判断参数的类型。
然后我们可以省略闭包中的返回值类型

var str:String={ arg1,arg2 in
         return arg1+arg2
}("str1","str2")

注意,闭包省略了返回值类型后,变量要显示声明它的类型, 之所以能省略返回值类型,那也是因为swift类型推导,先知道了变量的类型,所以可以省略返回值类型。
还不够爽,我们可以把参数也省略了

var str:String={
        return $0+$1
}("str1","str2")

如果闭包中只有一行代码, 其实return 也能省略。

var str:String={
         $0+$1
}("str1","str2")

如果闭包没有定义参数 ,像这样

var str:String={
       return "str1str2"
}()

括号中根本没有传参数, 括号能不能省略呢?
可以把括号省略了
省略括号的同时等号也不能写

var str:String{
       return "str1str2"
}

3.尾随闭包

stringAdd(str1: "str1", str2: "str2") {  str1, str2  in
    str1 + str2
}

尾随闭包形式:

stringAdd(str1: "str1", str2: "str2") {
    $0 + $1
}

4.值捕获

如下面的例子plus以及其捕获的变量a一起才是闭包。
下面我们先看一下代码,可以看到这里内部的函数捕获变量a:

typealias Fn = (Int) -> (Int)  // 函数类型

var a = 10
// 这个内嵌的函数其实就是一个闭包,只不过这个闭包已func的形式表现,而不是闭包表达式的形式
func exec() -> Fn {
    func plus (_ i: Int) -> Int {
        a += i
        return a
    }
    return plus  // 其实返回的就是plus地址
}

let fn = exec()
print(fn(1))  //打印结果11
print(fn(2))  //打印结果13

因为这里的a是一个全局变量(a是个全局变量,a的存放地址就在全局数据区,就不用把a放到堆空间,不需要捕获。所以这里没有发生值捕获),因此我们调用了两次访问的都是同一个变量地址,因此这里的结果肯定是连续相加的;

那如果a是一个局部变量呢?

func exec() -> Fn {
    var a = 10
    func plus (_ i: Int) -> Int {
        a += i
        return a
    }
    return plus
}

let fn = exec()
print(fn(1))  //打印结果11
print(fn(2))  //打印结果13

let fn1 = exec()   // 此句之后,会给a再次创建一个堆空间
print(fn1(1))  //打印结果11

/*
  1>.执行完上面一行代码之后,即调用exec方法之后。
  2>.按理说,exec方法执行完,方法里面的局部变量a应该从栈空间被释放.
  3>.但是此时fn里面存储了就是plus这个函数地址,
     而此时plus方法中用到的这个a会被放到堆内存(值捕获),从而延长了a的生命周期
     这样a这个内存就不会在执行完exec之后被销毁。
*/

上述的exec方法里面的闭包可以以闭包表达式的形式简写为:

func exec() -> Fn {
  var a = 0
  return {
    a += $0
    return a 
  }
}

类比总结:可以把闭包想象成一个类的实例对象

  1. (闭包的)内存在堆空间上。
  2. 捕获的局部变量、常量就相当于对象的成员(存储属性),当然是在堆内存上。
  3. 组成闭包的函数就是相当于类内部定义的方法(就是上面的plus方法)。

5.非逃逸闭包、逃逸闭包 escaping

注意:让闭包逃逸意味着必须在闭包中显式引用self.

// 非逃逸闭包
func test(_ fn: () -> ()) {
  // 在函数作用域里面调用了这个闭包
  fn()
}

test {
  print(1)
}

// 逃逸闭包
import Dispatch
typealias Fn = () -> ()

var gFn: Fn?
func test2(_ fn: @escaping Fn) {
  // 在函数作用域里面没有调用fn这个闭包,而是赋值给了一个变量
  gFn = fn
}

func test(_ fn: @escaping Fn) {
  DispatchQueue.global().async {
    fn()
  }
}

6.自动闭包

func assert(_ condition: @autoclosure ()->Bool, 
            _ message: @autoclosure ()-> String) {
     return condition() ? message() : "no String"
}

// 调用
assert(number > 3,"number 不大于3")
// autoclosure 会自动将 number > 3 封装成{ number > 3 }

// 如果没有autoclosure修饰,则需要用闭包表达式的方式调用。
assert( { number > 3 }, { "number 不大于3" } )

逃逸闭包和自动闭包的使用

var names = ["Lewis","Jonathan","Zack","Lux"]
var providers: [() -> String] = [] //存放闭包的数组

func collectCustomerProviders(provider: @autoclosure @escaping () -> String) {
    providers.append(provider) // 此时闭包没有调用,需要等函数执行完调用所以要用@escaping 修饰
}

// 传入的值为一个String形式,由于用autoclosure修饰,会自动转为闭包类型
collectCustomerProviders(provider: names.remove(at: 0)) //此时names.count = 4
collectCustomerProviders(provider: names.remove(at: 0)) //此时names.count = 4

for provider in providers {
    print(provider()) //此时的闭包才被执行
}
// 此时names.count = 2

7.实际应用:

1,cell中赋值model(注:didSet情况,必须有默认值,可以用可选类型或添加默认值,否则报错,set无需设置默认值)

private var model: YourModel? {
    didSet {
       //具体的赋值操作函数
    }
}

2,懒加载创建控件

lazy var scrollView: UIScrollView = {
        let scrollView = UIScrollView()
        scrollView.backgroundColor = .white
        //可以对其他属性进行设置
        return scrollView
    }()

    lazy var tableview: UITableView = {
        let tableview = UITableView()
        //设置代理或其他操作:注册cell等
        tableview.delegate = self
        tableview.dataSource = self
        return tableview 
    }()

3,页面传值
下边的用法试用自定制view或cell涉及到的点击事件,以及页面间传值
注:参数类型为任意:Any,传值用swift特有的元组,当然,接收的地方也要用元组类型来接收

var clickBlock = ((Any) -> ())?

func clickAction() {
    clickBlocK?((yourValur1, yourValue2))
}

4,网络请求回调

let getDataUrl = "拼接的url"
func getData(parameters: [String: Any], success: @escaping(_ response: [String: Any])->(), failure: @escaping(_ error: Error)->()) {
        let urlString = BaseUrl + getDataUrl
        AlamofireRequest.getListRequest(urlString: urlString, parameters: parameters, success: { (response) in
            success(response)
        }) { (error) in
            failure(error)
        }
    }

参考1:深入理解swift闭包
参考2:汇编分析swift闭包
参考3:闭包及其本质探究

上一篇 下一篇

猜你喜欢

热点阅读