Swift 面向协议编程的 RMP 模式

Swift 面向协议编程的 RMP 模式

一、RMP 模式概述

RMP(Runtime Mixin Protocol)模式是 Swift 中一种强大的面向协议编程架构,通过三个核心要素解决传统面向对象编程的痛点,特别适合构建复杂的、需要高度复用的功能模块。通过合理的协议设计和运行时属性绑定,可以创建出既灵活又可维护的代码结构:

  • Protocol:解决多继承问题,通过协议声明能力

  • Runtime:解决协议无法储存对象变量问题,通过关联对象实现状态存储

  • Mixin:解决多协议冲突问题,通过协议组合实现能力复用

二、设计问题

面向对象继承的本质是单一树形结构 ,它在描述"是什么(is-a)"时很自然,

但在描述"能做什么(can-do)"时会遇到两个典型困境:

困境一:功能横切多个类

礼物动画、PK、连麦是独立功能,但视频房、语音房、多人房都需要它们。

用继承的话,要么写三遍,要么塞进公共基类------基类越来越臃肿。

困境二:功能组合爆炸

如果用继承来组合,"支持PK的视频房"和"支持PK的语音房"是两条继承链,

再加"支持连麦",变成四条------每增加一个维度,继承树翻倍。

解法:RMP 模式

把"能做什么"从类里剥离出来,定义为独立的能力单元(MixinProtocol)。类只需声明"我拥有这个能力",

能力的属性和逻辑全部自包含在 MixinProtocol 内,彼此不知道对方存在。


三、Swift 的实现基础

3.1 Protocol 层

Swift 的协议扩展(Protocol Extension) 天然支持为协议提供默认实现------这是实现

RMP 模式的基础。通过协议,我们可以声明能力接口,并通过扩展提供默认实现。

3.2 Runtime 层

协议扩展有一个限制:不能定义存储属性

这个限制通过 ObjC Runtime 关联对象(Associated Objects) 绕过:

Runtime 允许在运行时把任意值"绑定"到任意 NSObject 实例上,

等效于给协议扩展加了存储属性。

3.3 Mixin 层

通过协议组合,我们可以实现能力的自由组合,解决多协议冲突问题,

每个能力单元(MixinProtocol)专注于单一职责,通过 Mixin 协调多个能力的生命周期。

三者结合,就形成了完整的 RMP 模式:Protocol 声明能力,Runtime 存储状态,Mixin 组合功能


四、属性绑定方式

在 RMP 模式中,我们需要在协议扩展中实现属性存储。以下是几种常用的属性绑定方式:

方式一:懒加载对象属性

适合首次访问时才创建、之后复用的对象(视图、子控制器等)。

swift 复制代码
// 协议扩展中声明懒加载属性
var scoreView: PKScoreView {
    lazyVarObject { PKScoreView() }
}

// 实现原理示例
func lazyVarObject<T: Any>(_ block: () -> T, _ fun: String = #function, _ file: String = #file, _ line: Int = #line) -> T {
    let uniqueKey = "\(file)\(line)"
    let keyString = "\(uniqueKey).\(fun)"
    let key = UnsafeRawPointer(bitPattern: keyString.hashValue)!

    guard let obj = objc_getAssociatedObject(self, key) as? T else {
        let obj = block()
        objc_setAssociatedObject(self, key, obj, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
        return obj
    }
    return obj
}

方式二:绑定属性

setter\ggetter 自动处理包装和解包:

swift 复制代码
// 自动处理值类型的包装和解包
var count: Int {
    get { associatedValue(defaultValue: 0) }
    set { setAssociatedValue(newValue) }
}

var view: UIView? {
    get { associatedValue(defaultValue: nil) }
    set { setAssociatedValue(newValue) }
}

var isActive: Bool {
    get { associatedValue(defaultValue: false) }
    set { setAssociatedValue(newValue) }
}

// 实现原理示例
func associatedValue<T>(defaultValue: T, _ fun: String = #function, _ file: String = #file, _ line: Int = #line) -> T {
    let uniqueKey = "\(file)"
    let keyString = "\(uniqueKey).\(fun)"
    let key = UnsafeRawPointer(bitPattern: keyString.hashValue)!

    if let value = objc_getAssociatedObject(self, key) as? T {
        return value
    }
    return defaultValue
}

func setAssociatedValue<T>(_ value: T, _ fun: String = #function, _ file: String = #file, _ line: Int = #line) {
    let uniqueKey = "\(file)"
    let keyString = "\(uniqueKey).\(fun)"
    let key = UnsafeRawPointer(bitPattern: keyString.hashValue)!
    objc_setAssociatedObject(self, key, value, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
}

五、RMP 模式的标准分层结构

RMP 模式的协议通常分三层,层级越高越具体:

复制代码
┌─────────────────────────────────────────────────┐
│  Layer 0  工具层(Runtime 基础)                   │
│  BaseAssociatedProtocol                          │
│  提供 lazyVarObject 等工具方法,所有 RMP 能力的基础    │
└───────────────────┬─────────────────────────────┘
                    │ 继承
┌───────────────────▼─────────────────────────────┐
│  Layer 1  能力层(Protocol 声明)                 │
│  PKProtocol / LinkMicProtocol / GiftProtocol / ...│
│  每个能力单元 = 一个文件 = 一组内聚的属性+逻辑         │
└───────────────────┬─────────────────────────────┘
                    │ 组合
┌───────────────────▼─────────────────────────────┐
│  Layer 2  组合层(Mixin 协调)                    │
│  LiveRoomMixin : PKProtocol, LinkMicProtocol     │
│  简化 Host Class 的 conformance 声明             │
└───────────────────┬─────────────────────────────┘
                    │ 遵循
┌───────────────────▼─────────────────────────────┐
│  Layer 3  宿主层                                 │
│  class VideoRoomVC : BaseVC, LiveRoomMixin       │
│  class VoiceRoomVC : BaseVC, LiveRoomMixin       │
│  宿主类体内不写任何 RMP 逻辑,只声明 conformance   │
└─────────────────────────────────────────────────┘

六、RMP 模式的新实践:MVCM 聚合协议模式

在实践中,RMP 模式有了新的发展,根据功能复杂度采用不同的组织方式:

6.1 简单功能:独立能力单元

对于简单、单一职责的功能,直接创建一个独立的 RMP 能力单元:

适用场景:功能逻辑简单,不需要复杂的 UI、数据和操作分离。

示例BeautyProtocol.swift

  • 只负责美颜功能的核心逻辑

  • 包含美颜面板的管理、美颜参数的设置等

  • 宿主只需遵循此协议即可获得美颜能力

6.2 复杂功能:MVCM 聚合协议模式

对于复杂功能,采用 MVC 模式划分多个协议,再通过 Mixin 组合,形成 MVCM(Model-View-Controller-Mixin)结构:

核心思想:将复杂功能按照 MVC 职责划分为三个协议,每个协议负责一个维度,最后通过 Mixin 协议组合在一起,由 Mixin 层统合 MVC 三层的交互。

协议划分

协议类型 职责 命名规范 示例
Model 协议 数据管理、状态存储 {功能名}MProtocol.swift VideoReadyMProtocol.swift
View 协议 UI 组件、布局、渲染 {功能名}VProtocol.swift VideoReadyVProtocol.swift
Controller 协议 简单方法声明、回调管理 {功能名}CProtocol.swift VideoReadyCProtocol.swift
Mixin 协议 统合 MVC 三层、处理跨层交互 {功能名}Mixin.swift VideoReadyMixin.swift

职责详解

  1. Model 层

    • 负责数据的存储和管理

    • 定义数据结构和状态属性

    • 提供数据操作方法

    • 不直接与视图层交互

  2. View 层

    • 负责 UI 组件的创建和布局

    • 提供 UI 更新方法

    • 通过参数接收数据,不直接访问数据层

    • 不处理业务逻辑

  3. Controller 层

    • 定义简单的方法接口

    • 管理回调闭包

    • 不包含复杂的业务逻辑

    • 不直接操作视图

  4. Mixin 层

    • 统合 MVC 三层,处理跨层交互

    • 实现复杂的业务逻辑

    • 协调数据层和视图层的通信

    • 提供完整的功能实现

文件结构

复制代码
├── VideoReadyMProtocol.swift  ← 数据层
├── VideoReadyVProtocol.swift  ← 视图层
├── VideoReadyCProtocol.swift  ← 控制层
└── VideoReadyMixin.swift      ← 混合层(统合 MVC 三层)

优点

  1. 职责清晰:每个协议专注于自己的职责,符合单一职责原则
  2. 代码组织:复杂功能的代码被合理拆分,提高可读性和可维护性
  3. 易于测试:各层可以独立测试,提高测试覆盖率
  4. 灵活性:可以根据需要单独使用某一层的能力
  5. 逻辑集中:复杂的业务逻辑集中在 Mixin 层,便于维护

使用方式

宿主类只需遵循 Mixin 协议,即可获得完整的功能:

swift 复制代码
class VideoReadyController: BaseViewController, VideoReadyMixin {
    // 无需实现任何逻辑,只需声明遵循协议
}

核心实现特点

  • 控制层轻量化:只保留简单的方法声明和回调管理,复杂逻辑移至 Mixin 层

  • 混合层统合:Mixin 层负责协调 MVC 三层的交互,处理复杂业务逻辑

  • 跨层通信:通过 Mixin 层实现数据层和视图层的通信,避免直接耦合

  • 功能完整:宿主类只需遵循 Mixin 协议,即可获得完整的功能

这种 MVCM 结构特别适合复杂的业务模块,能够在保持 RMP 模式优势的同时,提供更清晰的代码结构和职责划分。


七、RMP 能力单元内访问宿主对象

RMP 能力单元的方法有时需要访问宿主 Controller 的属性(如 viewnavigationController 等)。

由于能力单元不知道自己会被谁遵循,用交叉类型条件转换来安全地取得完整上下文:

swift 复制代码
// Self 同时满足"宿主基类"和"当前能力单元"两个约束
func pk_start() {
    guard let self = self as? BaseController & PKProtocol else { return }
    // 现在可以同时访问 BaseController 的属性和 PKProtocol 的属性
    self.view.addSubview(scoreView)
    self.someMethod()
}

guard let self = self as? HostClass & ThisProtocol 是 RMP 能力单元内访问宿主的标准写法

不要用 as! 强转,不要假设 self 的具体类型。


八、新建一个 RMP 能力单元的完整模板

8.1 简单能力单元模板

swift 复制代码
import Foundation
import UIKit

// MARK: - 协议声明(定义对外暴露的接口)
/// 美颜功能 RMP 能力单元
public protocol BeautyProtocol: BaseAssociatedProtocol {
    func beauty_setup()
    func beauty_showPanel()
    func beauty_hidePanel()
    func beauty_updateOptions(_ options: [String: Any])
}

// MARK: - 默认实现(所有逻辑内聚于此)
public extension BeautyProtocol {

    // ---- 属性(Runtime 绑定)----

    // 懒加载属性
    private var beautyControlView: BeautyControlView {
        lazyVarObject { BeautyControlView() }
    }

    // 值类型属性
    private var beautyOptions: [String: Any] {
        get { associatedValue(defaultValue: [:]) }
        set { setAssociatedValue(newValue) }
    }

    private var isBeautyEnabled: Bool {
        get { associatedValue(defaultValue: true) }
        set { setAssociatedValue(newValue) }
    }

    // ---- 方法 ----

    func beauty_setup() {
        guard let self = self as? UIViewController & BeautyProtocol else { return }
        
        // 添加美颜控制面板
        self.view.addSubview(beautyControlView)
        beautyControlView.isHidden = true
        
        // 初始化美颜参数
        beautyOptions = [
            "smoothness": 0.5,
            "whiteness": 0.5,
            "redness": 0.5
        ]
    }

    func beauty_showPanel() {
        beautyControlView.isHidden = false
        // 显示美颜面板逻辑
    }

    func beauty_hidePanel() {
        beautyControlView.isHidden = true
        // 隐藏美颜面板逻辑
    }

    func beauty_updateOptions(_ options: [String: Any]) {
        beautyOptions = options
        // 应用美颜参数逻辑
    }
}

8.2 MVCM 结构模板

Model 协议
swift 复制代码
/// 视频准备模块数据协议
public protocol VideoReadyMProtocol: BaseAssociatedProtocol {
    // 数据属性
    var topic: String { get set }
    var coverImage: UIImage? { get set }
    var coverImageURL: String { get set }
    var isNotificationEnabled: Bool { get set }
    
    // 方法
    func resetState()
}

public extension VideoReadyMProtocol {
    var topic: String {
        get { associatedValue(defaultValue: "") }
        set { setAssociatedValue(newValue) }
    }
    
    var coverImage: UIImage? {
        get { associatedValue(defaultValue: nil) }
        set { setAssociatedValue(newValue) }
    }
    
    var coverImageURL: String {
        get { associatedValue(defaultValue: "") }
        set { setAssociatedValue(newValue) }
    }
    
    var isNotificationEnabled: Bool {
        get { associatedValue(defaultValue: true) }
        set { setAssociatedValue(newValue) }
    }
    
    func resetState() {
        topic = ""
        coverImage = nil
        coverImageURL = ""
        isNotificationEnabled = true
    }
}
View 协议
swift 复制代码
/// 视频准备模块视图协议
public protocol VideoReadyVProtocol: AnyObject {
    // UI 组件
    var previewView: UIView { get }
    var topicTextField: UITextField { get }
    var coverImageView: UIImageView { get }
    var startButton: UIButton { get }
    var notificationButton: UIButton { get }
    
    // 方法
    func setupUI()
    func updateTopic(_ topic: String)
    func updateCoverImage(_ image: UIImage)
    func updateNotificationButton(_ enabled: Bool)
}

public extension VideoReadyVProtocol where Self: UIView {
    var previewView: UIView {
        lazyVarObject { 
            let view = UIView()
            view.backgroundColor = .black
            return view
        }
    }
    
    var topicTextField: UITextField {
        lazyVarObject { 
            let textField = UITextField()
            textField.placeholder = "输入直播标题"
            textField.textColor = .white
            return textField
        }
    }
    
    var coverImageView: UIImageView {
        lazyVarObject { 
            let imageView = UIImageView()
            imageView.image = UIImage(named: "default_cover")
            imageView.contentMode = .scaleAspectFill
            return imageView
        }
    }
    
    var startButton: UIButton {
        lazyVarObject { 
            let button = UIButton(type: .custom)
            button.setTitle("开始直播", for: .normal)
            button.setTitleColor(.white, for: .normal)
            return button
        }
    }
    
    var notificationButton: UIButton {
        lazyVarObject { 
            let button = UIButton(type: .custom)
            button.setTitle("通知粉丝", for: .normal)
            button.setTitleColor(.white, for: .normal)
            return button
        }
    }
    
    func setupUI() {
        // 布局 UI 组件
        addSubview(previewView)
        addSubview(topicTextField)
        addSubview(coverImageView)
        addSubview(startButton)
        addSubview(notificationButton)
        
        // 设置布局约束
        // ...
    }
    
    func updateTopic(_ topic: String) {
        topicTextField.text = topic
    }
    
    func updateCoverImage(_ image: UIImage) {
        coverImageView.image = image
    }
    
    func updateNotificationButton(_ enabled: Bool) {
        let image = enabled ? UIImage(named: "notification_on") : UIImage(named: "notification_off")
        notificationButton.setImage(image, for: .normal)
    }
}
Controller 协议
swift 复制代码
/// 视频准备模块控制协议
public protocol VideoReadyCProtocol: AnyObject {
    // 回调闭包
    var onBack: (() -> Void)? { get set }
    var onStart: (([String: Any]) -> Void)? { get set }
    var onSelectCover: (() -> Void)? { get set }
    
    // 方法
    func backAction()
    func startAction()
    func coverTapped()
    func notificationTapped()
}

public extension VideoReadyCProtocol where Self: UIView {
    var onBack: (() -> Void)? {
        get { associatedValue(defaultValue: nil) }
        set { setAssociatedValue(newValue) }
    }
    
    var onStart: (([String: Any]) -> Void)? {
        get { associatedValue(defaultValue: nil) }
        set { setAssociatedValue(newValue) }
    }
    
    var onSelectCover: (() -> Void)? {
        get { associatedValue(defaultValue: nil) }
        set { setAssociatedValue(newValue) }
    }
    
    func backAction() {
        onBack?()
    }
    
    func startAction() {
        // 具体实现移至 Mixin 层
    }
    
    func coverTapped() {
        onSelectCover?()
    }
    
    func notificationTapped() {
        // 具体实现移至 Mixin 层
    }
}
Mixin 协议
swift 复制代码
/// 视频准备模块混合协议
public protocol VideoReadyMixin: VideoReadyMProtocol, VideoReadyVProtocol, VideoReadyCProtocol {}

public extension VideoReadyMixin {
    func setupVideoReady() {
        // 初始化数据
        resetState()
        
        // 设置 UI
        setupUI()
        
        // 绑定事件
        bindEvents()
        
        // 启动预览
        startPreview()
    }
    
    func bindEvents() {
        // 封面点击
        let coverTap = UITapGestureRecognizer { [weak self] _ in
            self?.coverTapped()
        }
        coverImageView.isUserInteractionEnabled = true
        coverImageView.addGestureRecognizer(coverTap)
        
        // 开始按钮点击
        startButton.addTarget(self, action: #selector(startAction), for: .touchUpInside)
        
        // 通知按钮点击
        notificationButton.addTarget(self, action: #selector(notificationTapped), for: .touchUpInside)
    }
    
    func startPreview() {
        // 启动相机预览逻辑
    }
    
    // 重写控制层方法
    func startAction() {
        // 验证输入
        guard !topic.isEmpty else {
            showAlert("请输入直播标题")
            return
        }
        
        // 构建参数
        var params: [String: Any] = [
            "topic": topic,
            "coverImageURL": coverImageURL,
            "notificationEnabled": isNotificationEnabled
        ]
        
        // 调用回调
        onStart?(params)
    }
    
    func notificationTapped() {
        // 切换通知状态
        isNotificationEnabled = !isNotificationEnabled
        
        // 更新 UI
        updateNotificationButton(isNotificationEnabled)
    }
    
    private func showAlert(_ message: String) {
        // 显示提示
    }
}

九、生命周期多路接入问题

问题根源

Swift 协议扩展对同一方法只有一个实现会生效。当多个 RMP 能力单元都想响应 viewDidLoad

就会产生静默冲突------编译通过,但只有一个能力单元的逻辑被执行:

swift 复制代码
extension PKProtocol      { func viewDidLoad() { /* 只有这个生效 */ } }
extension LinkMicProtocol { func viewDidLoad() { /* 被静默忽略 */ } }

根本原因viewDidLoad 是单入口,而我们有 N 个 RMP 能力单元需要接入它。

问题的本质是谁来做多路分发


解法一:各 RMP 能力单元用独立命名钩子,由 Mixin 链式编排

适用于不依赖 RxSwift 的场景,或需要精确控制调用顺序的模块。

每个 RMP 能力单元不实现 viewDidLoad,改为实现自己命名的钩子,

Mixin 协议提供统一实现,负责按序调用所有钩子。

swift 复制代码
// ---- 每个 RMP 能力单元只定义自己命名的钩子,无冲突 ----
extension PKProtocol {
    func pk_viewDidLoad() { /* PK 初始化 */ }
    func pk_viewWillAppear(_ animated: Bool) { /* PK 刷新 */ }
}

extension LinkMicProtocol {
    func lm_viewDidLoad() { /* 连麦初始化 */ }
}

// ---- Mixin:统一编排调用顺序 ----
protocol LiveRoomMixin: PKProtocol, LinkMicProtocol {
    func setup()
    func viewWillAppear(_ animated: Bool)
}

extension LiveRoomMixin {
    func setup() {
        pk_viewDidLoad()    // PK 初始化
        lm_viewDidLoad()    // 连麦初始化
    }
    
    func viewWillAppear(_ animated: Bool) {
        pk_viewWillAppear(animated) // PK 刷新
    }
}

// ---- 宿主:调用 Mixin 方法 ----
class VideoRoomController: BaseViewController, LiveRoomMixin {
    override func viewDidLoad() {
        super.viewDidLoad()
        setup() // 调用 Mixin 方法
    }
    
    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
        self.viewWillAppear(animated) // 调用 Mixin 方法
    }
}

解法二:使用事件发布-订阅模式

适用于使用 RxSwift 或其他响应式框架的项目。

swift 复制代码
// ---- 基础控制器 ----
class BaseViewController: UIViewController {
    let rx_viewDidLoad = PublishRelay<Void>()
    let rx_viewWillAppear = PublishRelay<Bool>()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        rx_viewDidLoad.accept(())
    }
    
    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
        rx_viewWillAppear.accept(animated)
    }
}

// ---- RMP 能力单元 ----
extension PKProtocol {
    func pk_bindEvents() {
        guard let self = self as? BaseViewController & PKProtocol else { return }
        
        // 监听生命周期事件
        self.rx_viewDidLoad
            .subscribe(onNext: { [weak self] in
                self?.pk_setup()
            })
            .disposed(by: self.disposeBag)
        
        self.rx_viewWillAppear
            .subscribe(onNext: { [weak self] _ in
                self?.pk_refresh()
            })
            .disposed(by: self.disposeBag)
    }
}

// ---- Mixin:统一绑定 ----
extension LiveRoomMixin {
    func bindMixins() {
        pk_bindEvents()
        lm_bindEvents()
    }
}

// ---- 宿主 ----
class VideoRoomController: BaseViewController, LiveRoomMixin {
    override func viewDidLoad() {
        super.viewDidLoad()
        bindMixins() // 绑定所有 RMP 能力单元
    }
}

十、何时用 RMP 能力单元,何时用继承

场景 推荐方式
功能需要跨多个无关类复用 RMP 能力单元
多个功能需要自由组合 RMP 能力单元
向现有类追加功能,不想修改它 RMP 能力单元
严格的 is-a 关系(Dog is-a Animal) 继承
需要访问 super、重写生命周期 继承
功能仅一个类使用,不存在复用 直接写在类里

不要滥用 RMP 能力单元 :如果一个功能只有一个宿主,直接写在类里更清晰。

RMP 能力单元的价值在于复用和隔离,强行拆分只会增加间接层。


十一、命名规范

协议文件命名

{功能名}Protocol.swift(建议使用功能名作为前缀,Protocol 作为后缀)

方法 / 属性前缀

每个 RMP 能力单元的公开成员加功能名前缀,防止不同能力单元之间命名冲突:

RMP 能力单元 前缀 示例
PK 功能 pk_ pk_start(), pk_scoreView
连麦 lm_ lm_onMic(), lm_isOnMic
礼物动效 gift_ gift_play(), gift_queue
美颜 beauty_ beauty_setup(), beauty_options
视频房 video_ video_initUI(), video_previewView

十二、常见错误

错误 后果 正确做法
把逻辑写进宿主 Controller RMP 能力单元失去意义,基类重新膨胀 逻辑全写在 extension XxxProtocol
在宿主里 override 方法后调用 RMP 能力单元 形成重复调用层 RMP 能力单元自己在 setup 里完成初始化
多个 RMP 能力单元共享关联对象 key 属性互相覆盖,产生诡异 bug 为每个属性生成唯一 key
as! 强转宿主类型 运行时崩溃 guard let self = self as? Host & XxxProtocol
RMP 能力单元之间直接互相调用 能力单元耦合,破坏独立性 通过宿主或组合协议传递依赖
对只有一个宿主的功能也拆 RMP 能力单元 过度设计,增加无谓间接层 直接写在类里

十三、RMP 模式的优势

  1. 代码复用:能力单元可以被多个类复用,避免重复代码
  2. 解耦:能力单元之间相互独立,降低耦合度
  3. 灵活性:可以自由组合不同的能力单元
  4. 可测试性:每个能力单元可以独立测试
  5. 清晰的职责划分:每个能力单元专注于单一职责
  6. 易于维护:功能模块化,便于理解和修改

RMP 模式是 Swift 面向协议编程的强大实践,特别适合构建复杂的、需要高度复用的功能模块。通过合理的协议设计和运行时属性绑定,可以创建出既灵活又可维护的代码结构。

相关推荐
愤豆2 小时前
08-Java语言核心-JVM原理-垃圾收集详解
java·开发语言·jvm
wregjru2 小时前
【读书笔记】Effective C++ 条款8:别让异常逃离析构函数
java·开发语言
烤麻辣烫2 小时前
I/O流 进阶流
java·开发语言·学习·intellij-idea
艾莉丝努力练剑2 小时前
【QT】QT快捷键整理
linux·运维·服务器·开发语言·图像处理·人工智能·qt
程序员_大白2 小时前
【2025版】最新Qt下载安装及配置教程(非常详细)零基础入门到精通,收藏这篇就够了
开发语言·qt
枫叶丹42 小时前
【HarmonyOS 6.0】ArkData 分布式数据对象新特性:资产传输进度监听与接续传输能力深度解析
开发语言·分布式·华为·wpf·harmonyos
高亚奇2 小时前
QT版本 MSVC/MinGW/GCC 含义及如何区分
开发语言·qt
山川行2 小时前
Python快速闯关专栏的总结
java·开发语言·笔记·python·算法·visual studio code·visual studio
liudanzhengxi2 小时前
Git+云原生:K8s配置版本管理实战指南
开发语言·编辑器