iOS开发:关于Model

Swift宏让Codable的使用更加简单

我很早之前的文章就写了有关Swift中JSON与Model的转换,回顾了自己使用的一些工具与经验,有兴趣的请点击会看:

Swift:JSON解析(上)

Swift:JSON解析(下)

现在Swift已经支持宏了,Codable可以更简单的嵌入到代码中,同时有AI的加持,让CV工作越来越简单。

不过关于Swift宏需要注意的是,目前Swift宏原生支持只支持SPM。

但是据我了解,很多项目开发还是通过Cocoapods集成的,所以在使用Codable宏的时候,考虑这个宏框架是否支持Cocoapods,当然在一个有Cocopods的项目中使用SPM也是可行的。

我找到两个相关的库同时支持SPM与Cocoapods,大家可以参考一下:

MetaCodable

CodableWrapper

如果不想使用Codable的宏,自己稍微手动写一写也没有问题。

这里我有"被坑"经验分享给大家。这个坑也是我在之前文章中对JSON中的值转为Swift的枚举的后续。

转enum类型的注意事项

举个简单的例子,后端会传一个性别的JSON数据,数据如下:

json 复制代码
{
  "sexType": 0,// 0表示男性,1表示女性
}

在App端我写了这样一段代码

swift 复制代码
enum SexType: Int {
    case man
    case woman
}

extension SexType: Codable {}

struct SexModel: Codable {
    var sexType: SexType?
    var name: String?
}

我们转换逻辑大致如下:

swift 复制代码
let SexTypeJSONString = """
{
    "sexType": 0,
    "name": "season",
}
"""

let sexData = SexTypeJSONString.data(using: .utf8)!

let sexModel = try? JSONDecoder().decode(SexModel.self, from: sexData)

这样看,转换应该没啥问题。结果某一天后台传过来的数据出错了,数据以"sexType": 2,传过来了,会转换成功吗?

这里大家可以试试动手试试?

有的同学会想,SexModel中定义的字段都是可选类型,那么应该name转换成功,sexType转换为nil。

真的是这样吗?实际上整个sexModel为nil,并且会抛出error!

但是放在这里,可能看不出来什么问题。

我们把这个逻辑放在Moya的网络请求中:

swift 复制代码
provider.rx.request(Service.fake)
    .map(SexModel.self)
    .compactMap { $0 }
    .asObservable()
    .asSingle()
    .subscribe { event in
        switch event {
        case .success(let model):
            /// 模型转成功,将数据绑定到正常的页面
            /// 构建正常页面
            break
        case .failure:
            /// 模型转失败,显示异常的页面
            /// 构建异常页面
            break
        }
    }
    .disposed(by: disposeBag)

如果按照之前认为model转换成功,只是sexType为nil,那么此刻会走success的逻辑,可能只是页面上的性别相关的显示异常。

但是实际上,会走到failure逻辑中,页面中不会显示任何用户想获取的信息。

想想看,如果一个JSON很多个字段,而且字段类型传的也是对的,只是恰好转Swift时,enum类型的匹配不上,页面异常了,尬不尬?

而且这种情况,很有可能是Android端仅有性别的UI异常了,而iOS端直接展示Error页面了。于是一定会有人丢出这句话:

为什么安卓是好的?iOS却不行?

想想就鸭梨山大。

JSON一口气转为enum固然美好,因为通过0或者1这样的字段表示某个状态,理解上总要"翻译"一下,但是这里带来的风险也需要开发者承担,一旦JSON返回的字段无法与枚举值匹配上,就会导致整个Model转换失败!

对于这个问题有以下几个解决方式:

  • 模型转的时候,还是使用Int类型去接受,另起一个只读计算属性去手动转Int为SexType,这个方法可以规避Codable转化的异常,但是增加了手动写代码的逻辑,而且不同模型中的不同的枚举都需要自己写一份,维护成本高;
  • 通过PropertyWrapper进行一次保底,这种属性包装器会允许转失败的时候向nil转换,或者附上默认值;
  • 通过引入Swift宏,通过宏运算符进行异常守护;

下面是通过propertyWrapper改造的一个例子:

swift 复制代码
@propertyWrapper
struct CanNilEnumType<Enum>: Codable where Enum: Codable, Enum: RawRepresentable, Enum.RawValue: Codable {
    var wrappedValue: Enum?
    
    init(from decoder: Decoder) throws {
        let container = try decoder.singleValueContainer()
        if let value = try? container.decode(Enum.RawValue.self) {
            wrappedValue = Enum(rawValue: value) ?? nil
        } else {
            wrappedValue = nil
        }
    }
    
    func encode(to encoder: Encoder) throws {
        var container = encoder.singleValueContainer()
        if let wrappedValue {
            try? container.encode(wrappedValue.rawValue)
        } else {
            /// do Nothing
        }
    }
}

代码这样改造:

swift 复制代码
struct SexModel: Codable {
    /// 注意使用属性包装器修饰的属性只能用var修饰
    @CanNilEnumType
    var sexType: SexType?
}

有兴趣的朋友可以上网找更多的例子,比如兼容后台传Int类型或者String类的数字,Swift都可以通过String类型接住等等,这里只是一个抛砖引玉。

同时考虑到性能,Swift宏应该属性包装器更好。

同时有关Swift宏做异常处理的例子,上面分享的MetaCodable链接中有详细的文档说明:

swift 复制代码
@Codable
struct CodableData {
    @Default("some")
    let field: String
}

总结

Codable是Swift中转Model的利器,同时也需要时时刻刻记住,Swift是一门强语言,任何数据类型的错误,任何数据匹配不上,都可能导致转模型失败,在书写Swift代码的同时,加强容错是我们需要考虑的,同时后端正确、正常的传值往往也是必要的。

参考文档

Swift 5 属性包装器Property Wrappers完整指南

Property Wrappers

相关推荐
AronTing1 小时前
09-RocketMQ 深度解析:从原理到实战,构建可靠消息驱动微服务
后端·面试·架构
AronTing2 小时前
11-Spring Cloud OpenFeign 深度解析:从基础概念到对比实战
后端·spring cloud·架构
D龙源2 小时前
VSCode-IoC和DI
后端·架构
AronTing2 小时前
10-Spring Cloud Alibaba 之 Dubbo 深度剖析与实战
后端·面试·架构
胎粉仔3 小时前
Swift —— delegate 设计模式
开发语言·设计模式·swift
SimonKing4 小时前
邮件通知,引发的线上S级故障
java·后端·架构
uhakadotcom5 小时前
Langflow 1.3.0 安全漏洞详解及利用示例,教你如何防范远程代码执行攻击
面试·架构·github
Wgllss5 小时前
Android下载进度百分比按钮,Compose轻松秒杀实现
android·架构·android jetpack