Swift中Double Protocol Conformance的陷阱

前言

一般情况下,我们修复问题都会将我们能想到的错误case处理,但有时还是会有意想不到的副作用产生。这篇文章介绍的就是Double Protocol Conformance带来的问题

什么是 Double Protocol Conformance

简单说就是一个类型被声明多次遵循相同协议

swift 复制代码
// Item 已经遵循了Codable协议
public struct Item: Codable { } 
// Error: Redundant conformance of 'Item' to protocol 'Decodable'
// Error: Redundant conformance of 'Item' to protocol 'Encodable'
// Item 的扩展遵循了Codable协议
extension Item: Codable { }

如果我们在同一个Target里面书写上面的代码会报错

有趣的部分

  1. 我们在同一个Target里面,对一个类型和这个类型的扩展同时声明遵循相同协议,Xcode编译器会检测出来并报错
  2. 但当我们在不同的Target里分别对一个类型和这个类型的扩展声明遵循相同协议时,Xcode就检测不出来了

在TargetA中声明Item遵循Codable,并使用编译器的默认实现

swift 复制代码
// TargetA
public struct Item: Codable { 
    public let id: UUID
    public let name: String 
    public init(id: UUID, name: String) { 
        self.id = id 
        self.name = name 
    }
}

在TargetB中引入TargetA,并在Item的扩展中声明遵循Codable协议,自定义实现编解码

swift 复制代码
// TargetB
import Foundation
import TargetA 
extension Item: Codable { 
    // MARK: - Coding Keys 
    enum CodingKeys: String, CodingKey {
        case id = "product_id"
        case name = "product_name"
    } 
    // MARK: - Encodable 
    public func encode(to encoder: Encoder) throws { 
        var container = encoder.container(keyedBy: CodingKeys.self) 
        try container.encode(id, forKey: .id) 
        try container.encode(name, forKey: .name) 
    } 
    // MARK: - Decodable 
    public init(from decoder: Decoder) throws { 
        let values = try decoder.container(keyedBy: CodingKeys.self) 
        let id = try values.decode(UUID.self, forKey: .id) 
        let name = try values.decode(String.self, forKey: .name) 
        self = .init(id: id, name: name) 
    }
}

此时Xcode不会报错,而只会弹出警告。但就隐藏着问题了。只要代码能Run,我们就会认为没啥问题

暴露问题

假设我们有3个Target

  1. TargetA里面声明了Item,但是没有遵循Codable协议
  2. TargetB依赖TargetA,并在Item的扩展中遵循了Codable协议
  3. TargetC依赖TargetA,并未依赖TargetB,也在Item的扩展中遵循了Codable协议

看起来我们的代码好像没啥问题,我们也很难觉察到有Double Protocol Conformance的问题

如果我们在另外的Target里引入了TargetA、TargetB、TargetC,引入的顺序会影响最终的实现

swift 复制代码
// 先引入B再引入C
import TargetA
import TargetB
import TargetC 
let item = Item(id: .init(), name: "My Item") 
let encoder = JSONEncoder()
let encoded = try encoder.encode(item)
let jsonString = String(data: encoded, encoding: .utf8)

/** 输出的结果如下
{ 
    "product_name_B": "My Item", 
    "product_id_B": "42A18A37-51FA-422C-9BB9-46D382CDF1CA"
}
*/
swift 复制代码
// 先引入C再引入B
import TargetA
import TargetC
import TargetB
let item = Item(id: .init(), name: "My Item") 
let encoder = JSONEncoder()
let encoded = try encoder.encode(item)
let jsonString = String(data: encoded, encoding: .utf8)

/** 输出的结果如下
{ 
    "product_name_C": "My Item", 
    "product_id_C": "42A18A37-51FA-422C-9BB9-46D382CDF1CA"
}
*/
  1. 从上面的例子中,我们可以知道当一个类在不同的Target中同时遵循协议的时候,采用的是实现是第一个import中的实现
  2. 我们一般在工作中都是模块化的,会比例子里面的情况更复杂。所以对于协议遵循指定一套规则是很重要的

资料

alexanderweiss.dev/blog/2023-0...

相关推荐
用户38034165882972 天前
八个私有 SPM 包撑起六款 App:分支当版本、钻石依赖,和一次谁都没报错的失效
ios·swift
健了个平_242 天前
【iOS】关于传统竖屏 UIKit 项目如何简单适配 iPhone Duo 的第一版
ios·app·swift
2501_915106323 天前
iOS开发之Xcode常用调试技巧总结
ide·vscode·ios·objective-c·个人开发·swift·敏捷流程
sakiko_3 天前
使用SPM(Xcode27), 并解决Package.swift不存在问题
swift
Coder_张三5 天前
iOS 26 适配
前端·ios·swift
用户38034165882975 天前
崩溃率从 0.3% 到 0.04%:我们做对的不是修 bug,是让每个崩溃只发生一次
ios·swift
东坡肘子5 天前
当 Build in Public 重新转入地下 -- 肘子的 Swift 周报 #157
人工智能·swiftui·swift
用户38034165882976 天前
Flutter 迁 SwiftUI:AI 把页面"搬"过来只用了两周,我们补结构花了两个月
ios·swift
茶底世界之下6 天前
四角变换后的最小包围视口,为什么不能只把宽高取整?
ios·swift
茶底世界之下7 天前
预乘 Alpha 图层合成为什么会越叠越暗?
架构·swift