当前位置: 首页 > 科技观察

Swift 基于闭包的类型擦除

时间:2023-03-20 01:53:22 科技观察

使Swift比许多其他语言更安全、更不容易出错的原因之一是其先进的(并且有点不宽容)类型系统。这是一种有时令人印象深刻的语言功能,使您的工作效率更高,但有时却令人沮丧。今天,我想强调一个在Swift中处理泛型时可能发生的情况,以及我通常如何使用基于闭包的类型擦除技术来解决这种情况。假设我们要编写一个允许我们通过网络加载模型的类。由于我们不想为应用程序中的每个模型复制此类,因此我们选择将其设为通用类,如下所示:classModelLoader{funcload(completionHandler:(Result)->Void){networkService.loadData(from:T.requestURL){dataindo{trycompletionHandler(.success(unbox(data:data)))}catch{leterror=ModelLoadingError.unboxingFailed(error)completionHandler(.error(error))}}}所以到目前为止,我们现在有一个ModelLoader可以加载任何模型(只要它符合Unboxable协议),并且可以为我们提供一个requestURL。但是,我们还希望使使用此模型加载器的代码易于测试,因此我们将其API提取到一个协议中:protocolModelLoading{associatedtypeModelfuncload(completionHandler:(Result)->Void)}这与依赖注入一起使我们能够在测试中轻松模拟我们的模型加载API。但这引入了一些复杂性——每当我们想使用这个API时,我们现在必须将其称为协议ModelLoading,它具有相关的类型要求。这意味着仅引用ModelLoading是不够的,因为编译器无法在没有更多信息的情况下推断其关联类型。所以尝试做这样的事情:classViewController:UIViewController{init(modelLoader:ModelLoading){...}}会给我们这个错误:Protocol'ModelLoading'canonlybeusedasagenericconstraintbecauseitasasSelforassociatedtyperequirementsbut不用担心,我们可以通过使用泛型轻松摆脱这个错误,coercion符合Modelloading的具体实现类型将由API用户指定,它会加载我们期望的模型。像这样:classViewController:UIViewController{init(modelLoader:T)whereT.Model==MyModel{...}}这可行,但由于我们还想引用我们的模型加载器,我们需要能够指定属性的类型。T仅在我们的初始化程序的上下文中已知,因此我们不能定义类型T的属性,除非我们使视图控制器类本身是通用的——这将很快把我们带到到处都是通用类的兔子洞(下到一个rabbithole出自Alice'sSleepwalking,意思是简单的事情变得越来越复杂和荒谬)。相反,让我们使用类型擦除,它允许我们在不实际使用其类型的情况下保留对某种T的引用。这可以通过创建擦除类型的类来完成,例如包装类:classAnyModelLoader:ModelLoading{typealiasCompletionHandler=(Result)->VoidprivateletloadingClosure:(CompletionHandler)->Voidinit(loader:L)whereL.Model==T{loadingClosure=loader.load}funcload(completionHandler:CompletionHandler){loadingClosure(completionHandler)}}上面的类型擦除技术其实在Swift标准库中是非常常用的,比如在AnySequence中类型。基本上,您将关联值所需的协议包装到泛型类型中,然后您可以直接使用它而无需使使用它的类也泛型。我们现在可以更新我们以前的ViewController以使用AnyModelloader:super.init(nibName:nil,bundle:nil)}}就是这样!我们现在有一个面向协议的API,它很容易模拟并且仍然可以在普通类中工作,这要归功于类型擦除。现在,奖励时间到了。上面的技术实际上很好,但它确实涉及到一个额外的步骤,给我们的代码增加了一些复杂性。然而,事实证明我们实际上可以直接在我们的视图控制器中执行基于闭包的类型擦除——而不必通过AnyModelloader类。我们的视图控制器将看起来像这样:loadsuper.init(nibName:nil,bundle:nil)}}就像我们的类型擦除类AnyModelloader一样,我们可以引用加载函数作为闭包的实现,并简单地将引用保留在我们的视图控制器中。现在,每当我们想要加载模型时,我们只需调用loadmodel,就像我们的任何其他函数或闭包一样:(model)case.error(leterror):render(error)}}}就是这样!希望您在处理Swift代码中的泛型和协议时会发现上述技术。本文转载自微信公众号“迅捷社区”,可通过以下二维码关注。转载请联系Swift社区公众号。