Swift 闭包(二)

·  阅读 2037
Swift 闭包(二)

「这是我参与2022首次更文挑战的第3天,活动详情查看:2022首次更文挑战」。

OC Block 和 Swift 闭包相互调用

我们在 OC 中定义的 Block,在 Swift 中是如何调用的呢?我们来看一下

typedef void(^ResultBlock)(NSError *error); 

@interface LGTest : NSObject

+ (void)testBlockCall:(ResultBlock)block; 

@end

Swift 中我们可以这么使用

LGTest.testBlockCall{ error in
    let errorcast = error as NSError
    print(errorcast) 
}

func test(_ block: ResultBlock){
    let error = NSError.init(domain: NSURLErrorDomain, code: 400, userIn: nil) 
    block(error)
}

比如我们在 Swift 里这么定义,在 OC 中也是可以使用的

class LGTeacher: NSObject{
    @objc static var closure: (() -> ())?
}
+ (void)test{
    // LGTeacher.test{}
    LGTeacher.closure = ^{ 
        NSLog(@"end");
    };
}
LGTest.test() 
LGTeacher.closure!()

@convention 关键字介绍

@convention:用于修饰函数类型

  • 修饰 Swift 中的函数类型(调用 C 函数的时候)
  • 调用 OC 方法时,修饰 Swift 函数类型

defer 的用法

定义:defer {} 里的代码会在当前代码块返回的时候执行,无论当前代码块是从哪个分支 return 的,即使程序抛出错误,也会执行。

  • 案例 1

如果多个 defer 语句出现在同一作用域中,则它们出现的顺序与它们执行的顺序相反,也就是先出现的后执行。

image.png

  • 案例 2
func append(string: String, terminator: String = "\n", toFileAt url: URL) throws {
    // The data to be added to the file
    let data = (string + terminator).data(using: .utf8)!
    let fileHandle = try FileHandle(forUpdating: url)

    defer {
        fileHandle.closeFile()
    }
    
    // If file doesn't exist, create it
    guard FileManager.default.fileExists(atPath: url.path) else {
        try data.write(to: url)
        return
    }
    
    // If file already exists, append to it
    fileHandle.seekToEndOfFile()
    fileHandle.write(data)
}

let url = URL(fileURLWithPath: NSHomeDirectory() + "/Desktop/swift.txt")
try append(string: "iOS开发语言", toFileAt: url)
try append(string: "Swift", toFileAt: url)

这里有时候如果当前方法中多次出现 closeFile,那么我们就可以使用 defer

  • 案例 3
let count = 2
let pointer = UnsafeMutablePointer<Int>.allocate(capacity: count)
pointer.initialize(repeating: 0, count: count)
defer {
    pointer.deinitialize(count: count)
    pointer.deallocate()
}

在我们使用指针的时候,allocatedeallocateinitializedeinitialize 都是成对出现的,这时候我们就可以将 deallocatedeinitialize 放到 defer {} 中。

  • 案例 4
func netRquest(completion: () -> Void) {
    defer {
        self.isLoading = false
        completion()
    }
    guard error == nil else { return }
}

比如我们在进行网络请求的时候,可能有不同的分支进行回调函数的执行,这个时候可以使用 defer {} 来管理 completion 回调,前提是这里是非异步的,defer {} 就是方便我们来管理代码块。

defer 使用注意

image.png

使用 defer 的时候要避免像这样书写,编译器也会提示 defer 可能不会立即执行。

image.png

这里需要注意的是 temp 的值还是 1,因为 defer {} 是在返回之后执行。其实这里 defer 这样使用并没有意义,我们要避免这样的写法,defer 我们要用来管理一些统一资源, 优化冗余代码, 使代码更加简洁直观。

逃逸闭包

逃逸闭包:当闭包作为一个实际参数传递给一个函数的时候,并且是在函数返回之后调用,我们就说这个闭包逃逸了。当我们声明一个接受闭包作为形式参数的函数时,你可以在形式参数前加上 @escaping 来明确闭包是允许逃逸的。

  • 作为函数的参数传递
  • 当前闭包在函数内部异步执行或者被存储
  • 函数结束,闭包被调用,生命周期结束

注意:可选类型默认是逃逸闭包!

  • 使用场景 1
class CXTeacher {
    var completionHandle: ((Int) -> Void)?
    
    func makeIncrementer(_ amout: Int, handler: @escaping (Int) -> Void) {
        var count = 10
        count += amout
        
        completionHandle = handler
    }
    
    func dosomething() {
        makeIncrementer(20) {
            print($0)
        }
    }
}

let t = CXTeacher()
t.dosomething()
t.completionHandle?(10) 

这里是定义了一个 completionHandle 属性,在 makeIncrementer 函数中将闭包参数 handler 赋值给 completionHandle,这时候闭包的生命周期比 makeIncrementer 函数的生命周期要长,我们可以在需要的时机调用闭包,这个时候这个闭包就是逃逸闭包。

  • 使用场景 2
class CXTeacher {
    var completionHandle: ((Int) -> Void)?
    
    func makeIncrementer(_ amout: Int, handler: @escaping (Int) -> Void) {
        var count = 10
        count += amout
        
        DispatchQueue.global().asyncAfter(deadline:  .now() + 0.1) {
            handler(count)
        }
    }
    
    func dosomething() {
        makeIncrementer(20) {
            print($0)
        }
    }
}

let t = CXTeacher()
t.dosomething()
t.completionHandle?(10)

这里是在原函数中异步执行闭包,这个时候闭包的生命周期也比原函数要长,这时候 handler 也是逃逸闭包。

非逃逸闭包

func testNoEscaping(_ f: () -> Void) {
    f()
}

func test() -> Int {
    var age = 18
    testNoEscaping {
        age += 20
    }

    return 30
}

test()

这个就是一个非逃逸闭包,当函数调用完之后这个闭包也就消失了。并且非逃逸闭包还有以下优点:

  • 不会产生循环引用,函数作用域内释放

  • 编译器会做更多性能优化 (retainrelsase)

  • 上下文的内存保存在栈上,不是堆上

分类:
iOS
标签:
分类:
iOS
标签:
收藏成功!
已添加到「」, 点击更改