为泛型 Swift 扩展一个类模型

Posted

技术标签:

【中文标题】为泛型 Swift 扩展一个类模型【英文标题】:Extending a class model for generic type Swift 【发布时间】:2019-09-20 09:08:33 【问题描述】:

我正在使用 Moya 传递 API 响应并获取此值。我能够获取对象,但我扩展了基本响应以处理额外参数,但扩展值似乎不起作用。预期的数据可能是一个对象数组,也可能只是一个常规对象。传递此值后,它停止工作并且未获取数据,但除data 之外的所有其他参数(如statusmessage)都被传递。这是我的基本回复以及我如何使用它

class MaxResponseBase: Codable 
    var status: String?
    var message: String?
    var pagination: Pagination?

    var isSucessful: Bool 
        return status == "success"
    

    struct ErrorMessage 
        static let passwordInvalid = " Current password is invalid."
        static let loginErrorIncorrectInfo = " Incorrect username/password."
        static let loginErrorAccountNotExist = " Invalid request"
    


class MaxResponse<T: Codable>: MaxResponseBase 
    var data: T?


class MaxArrayResponse<T: Codable>: MaxResponseBase 
    var data = [T]()

这是我的登录 API 调用示例

func signin(email: String, password: String) -> Observable<MaxResponse<AuthResponse>> 
        return provider.rx.request(.signin(username: email, password: password))
            .filterSuccess()
            .mapObject(MaxResponse<AuthResponse>.self)
            .asObservable()
    

如何调整它以获取数据对象

JSON


  "status" : "success",
  "data" : 
    "is_locked" : false,
    "__v" : 0,
    "created_at" : "2019-04-15T11:57:12.551Z"
  

它也可以是一个数据数组

【问题讨论】:

来自服务器的 JSON 是什么样的? @DanielT。我已更新以包含 JSON 但是对于这个端点,你的 JSON 有时看起来不一样? 这就是 MaxResponse 和 MaxArrayResponse 的区别 让它始终是对象中的一个数组。如果响应不是数组,则为解码部分编写一个自定义方法,将对象放入数组中。虽然这是一个奇怪的 api ... 【参考方案1】:

(注意:下面的所有代码都可以放在 Playground 中以显示它的工作原理。)

为了解决这个问题,您必须手动编写所有初始化程序。我在下面发布了大部分代码,但我强烈建议您使用结构而不是类。如果您使用结构和包含而不是类和继承,那么在各个方面都会更好。

struct Pagination: Codable  

struct AuthResponse: Codable 
    let isLocked: Bool
    let __v: Int
    let createdAt: Date


class MaxResponseBase: Codable 
    let status: String?
    let message: String?
    let pagination: Pagination?

    var isSucessful: Bool 
        return status == "success"
    

    struct ErrorMessage 
        static let passwordInvalid = " Current password is invalid."
        static let loginErrorIncorrectInfo = " Incorrect username/password."
        static let loginErrorAccountNotExist = " Invalid request"
    

    enum CodingKeys: String, CodingKey 
        case status, message, pagination
    

    required init(from decoder: Decoder) throws 
        let container = try decoder.container(keyedBy: CodingKeys.self)
        status = try container.decode(String?.self, forKey: .status)
        message = try? container.decode(String?.self, forKey: .message) ?? nil
        pagination = try? container.decode(Pagination?.self, forKey: .pagination) ?? nil
    


class MaxResponse<T: Codable>: MaxResponseBase 
    let data: T?

    enum DataCodingKeys: String, CodingKey 
        case data
    

    required init(from decoder: Decoder) throws 
        let container = try decoder.container(keyedBy: DataCodingKeys.self)
        data = try container.decode(T?.self, forKey: .data)
        try super.init(from: decoder)
    


let json = """

"status" : "success",
"data" : 
"is_locked" : false,
"__v" : 0,
"created_at" : "2019-04-15T11:57:12.551Z"


"""

let data = json.data(using: .utf8)!

let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "YYYY-MM-dd'T'HH:mm:ss.SSZ"
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
decoder.dateDecodingStrategy = .formatted(dateFormatter)
let response = try decoder.decode(MaxResponse<AuthResponse>.self, from: data)

print(response)

只使用结构体更简单,代码更少:

struct AuthResponse: Codable 
    struct ResponseData: Codable 
        let isLocked: Bool
        let __v: Int
        let createdAt: Date
    
    let status: String?
    let data: ResponseData


let json = """

"status" : "success",
"data" : 
"is_locked" : false,
"__v" : 0,
"created_at" : "2019-04-15T11:57:12.551Z"


"""

let data = json.data(using: .utf8)!

let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "YYYY-MM-dd'T'HH:mm:ss.SSZ"
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
decoder.dateDecodingStrategy = .formatted(dateFormatter)
let response = try decoder.decode(AuthResponse.self, from: data)

print(response)

如果你真的需要MaxResponse 类型,那么让它成为一个协议并让你的其他类型符合它。不过我几乎愿意打赌你不需要它。


针对您的 cmets,这是一个使用结构的通用解决方案:

struct LoginResponseData: Codable 
    let isLocked: Bool
    let __v: Int
    let createdAt: Date


struct BlogResponseData: Codable 
    let xxx: Bool
    let yyy: Int
    let createdAt: Date


struct BaseRresponse<ResponseData: Codable>: Codable 
    let status: String?
    let data: ResponseData

【讨论】:

第二个选项我明白你的意思,但这是一个大项目,像这样传递 JSON 结构是行不通的,因为我会重复代码。 我认为您的“重复代码”概念已经过时了。在我的第二个示例中,我只声明了一个类型,而不是编写任何逻辑。没有重复的“代码”。无论如何,少写代码总比不重复代码好。 T,我的意思是,我登录时返回的对象与我分享博文时返回的对象不同。这意味着我必须创建另一个 struct BlogResponse: Codable struct ResponseData: Codable let xxx: Bool let yyy: Int let createdAt: Date let status: String? let data: ResponseData 我的意思是,你只是在声明一个类型,而不是写逻辑。用两种不同的类型来表示两种不同的事物并没有错。 哦,现在知道了。谢谢你的启发。

以上是关于为泛型 Swift 扩展一个类模型的主要内容,如果未能解决你的问题,请参考以下文章

数据模型和数据访问类以及属性扩展和泛型集合

Swift-扩展(Extensions)(十八)

Swift-扩展(Extensions)(十八)

使用反射为泛型类创建构造函数

Swift 泛型和协议扩展

泛型编程的术语