V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
jiangzm
V2EX  ›  Swift

Swift 中系统 API delegate 参数一定要在当前类实现传 self 吗

  •  
  •   jiangzm · 38 天前 · 899 次点击
    这是一个创建于 38 天前的主题,其中的信息可能已经有所发展或是发生改变。

    因为要写原生插件,swift 刚接触,语法比 objc 好看多了。

    在开发相机组件时遇到一个问题,AVCapturePhotoCaptureDelegate 契约用自定义 UIView/UIViewController 实现没问题, 换成独立的类实现就回调不了,不是什么原因。

    上代码:

    TakePhotoDelegate.swift

    import Foundation
    import AVFoundation
    import UIKit
    
    public typealias TakePhotoCallback = ((String, String) -> Void)?;
    
    class TakePhotoWithCompletion : NSObject, AVCapturePhotoCaptureDelegate {
    
        // ...
        var completion: TakePhotoCallback;
        
        init(callback: TakePhotoCallback) {
    
            self.completion = callback;
            super.init();
        }
    
        func photoOutput(_ output: AVCapturePhotoOutput, didFinishProcessingPhoto photo: AVCapturePhoto, error: Error?) {
        
            // todo sth
        
            self.completion!("{file path}", "{error}");
            
        }
    }
    
    
    

    CameraxView.swift

    import AVFoundation
    import UIKit
    
    class CameraxView: UIView {
    
        // ...
    
        @objc
        func takePhoto(options: Dictionary<String, String>, completion: TakePhotoCallback) {
            
            let photoSettings = AVCapturePhotoSettings.init(format:[AVVideoCodecKey:AVVideoCodecType.jpeg])
            let delegate = TakePhotoWithCompletion.init(callback: completion)
            
            imageOutPut?.capturePhoto(with:photoSettings, delegate: delegate)
    
        }
    
      // ...
    }
    

    点击拍照按钮能听到快门声,但是 photoOutput 没有回调

    如果换成如下自定义 UIView 实现 AVCapturePhotoCaptureDelegate 没有问题,能正常回调

    import AVFoundation
    import UIKit
    
    class CameraxView: UIView, AVCapturePhotoCaptureDelegate {
    
        // ...
        var completion: TakePhotoCallback = nil;
    
        @objc
        func takePhoto(options: Dictionary<String, String>) {
            
            let photoSettings = AVCapturePhotoSettings.init(format:[AVVideoCodecKey:AVVideoCodecType.jpeg])
    
            self.completion = completion
            
            imageOutPut?.capturePhoto(with:photoSettings, delegate: self)
    
        }
    
        @objc
        func photoOutput(_ output: AVCapturePhotoOutput, didFinishProcessingPhoto photo: AVCapturePhoto, error: Error?) {
        
            // todo sth
        
            self.completion!("{file path}", "{error}");
            
        }
    
        // ...
    
    }
    
    

    有这个差异的原因是因为系统 API 的限制吗还是 swift 语言层面问题, 如果 takePhoto 方法希望传闭包参数回调拍照结果,正确应该怎么写。

    (回调参数赋值给实例对象, 个人感觉是个很奇怪的做法)

    6 条回复    2024-10-08 19:24:02 +08:00
    hguandl
        1
    hguandl  
       38 天前
    我推测这与弱引用有关。capturePhoto 不会保持对 delegate 的引用,导致其在 takePhoto 结尾被直接释放掉了。可以试一试给 TakePhotoWithCompletion 加个自定义 deinit ,打一下日志或断点看一下释放时机。
    jiangzm
        2
    jiangzm  
    OP
       38 天前
    @hguandl #1 是弱引用问题, 加了 deinit 有打印出来。 那除了强引用设置成实例成员还有其他方式解决么。
    一般用闭包封装委托是怎么做的。感觉我的写法和这个例子差不多 https://stackoverflow.com/questions/24173210/implement-delegate-with-closure-in-swift
    hguandl
        3
    hguandl  
       38 天前
    @jiangzm delegate 这种设计模式 + Swift 的 ARC 特性决定了几乎只能这样写,不然就会造成内存泄漏。如果是纯 Swift 应用的话可以封装成 async 函数来解决,但是看你是要和桥接 objc ,那就没有太好的方法。
    louiswang002
        4
    louiswang002  
       38 天前 via iPhone
    let delegate = TakePhotoWithCompletion.init(callback: completion)
    创建的对象没有引用,imageOutPut 对 delegate 是弱引用,当前栈弹出时,delegate 对象就被释放了,肯定不会有输出;你在第二种实现中,对 delegate 对象创建了一个强引用,imageOutPut 对 delegate 的弱引用对象也就存在了,也能触发 delegate 了
    jiangzm
        5
    jiangzm  
    OP
       38 天前
    不知道为啥 swift 要搞一个 delegate 概念出来,也不是语法关键字 就是普通的 class 实现接口(契约) 。既然方法参数和属性能传函数(闭包),没有委托好像也行。发现有些系统 api 原来是传函数的,新版搞个新 class 新 api 就要传委托。^_^

    这个和 C#的 delegate 完全不同,委托是函数引用/指针,匿名函数(swift 闭包)、lambda 表达式、事件都是基于委托的。

    Swift 应该是差一个事件的概念, 但是呢没有一步到位整了个中间的 delegate 概念,再加上内存管理不是那么智能,使用起来有点束手束脚的感觉。

    C#中的委托和事件都是可以多播的,大部分组件对外暴露的都是事件,拿上面委托例子来说就是这样:

    ``` imageOutPut.onPhotoOutput = () => { xxx }; ```

    或者
    ```imageOutPut.onPhotoOutput += () => { xxx }; ```
    Sricecake
        6
    Sricecake  
       30 天前
    因为这些 API 是 OC 时代定义的,OC 当时没有闭包,所以只能以这种类似 JAVA 的 Interface 方式实现回调,你虽然使用的是 Swift ,但是其实还是在调用原 OC 的库。
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   3111 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 20ms · UTC 13:04 · PVG 21:04 · LAX 05:04 · JFK 08:04
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.