普通视图

发现新文章,点击刷新页面。
今天 — 2025年6月8日首页

解析鸿蒙 ArkTS 中的 Union 类型与 TypeAliases类型

作者 90后晨仔
2025年6月8日 18:41

在鸿蒙 ArkTS(基于 TypeScript 的扩展)中,Union 类型Type Aliases(类型别名) 是类型系统的两大核心特性。它们虽然常被同时使用,但解决的问题截然不同。本文将通过完整示例深入剖析二者的区别与应用场景。


一、Union 类型:灵活的多类型容器

核心概念:允许变量持有多种类型中的任意一种,使用 | 符号组合类型。

关键特性

  • 运行时需类型保护(typeof/instanceof
  • 增强 API 灵活性
  • 完美处理可选值(如 T | null

完整示例

// 1. 基础类型联合
type Primitive = string | number | boolean;
const logValue = (value: Primitive) => {
  if (typeof value === "string") {
    console.log(value.toUpperCase());
  } else {
    console.log(value.toFixed(2)); // 自动推断为 number | boolean
  }
};

// 2. 对象类型联合
class Cat {
  meow() { console.log("Meow!") }
}

class Dog {
  bark() { console.log("Woof!") }
}

type Pet = Cat | Dog;

const handlePet = (pet: Pet) => {
  if (pet instanceof Cat) {
    pet.meow();  // 类型保护
  } else {
    pet.bark();  // 自动推断为 Dog
  }
};

// 3. 带字面量的联合
type Status = "loading" | "success" | "error";
const setStatus = (status: Status) => {
  console.log(`Current status: ${status}`);
};

典型应用场景

  1. 函数参数支持多类型:parseInput(input: string | number)
  2. 状态机表示:type State = "idle" | "running" | "paused"
  3. 网络响应处理:type Response = SuccessData | ErrorInfo

二、Type Aliases:类型系统的快捷方式

核心概念:为现有类型创建可复用的别名,使用 type 关键字定义。

关键特性

  • 简化复杂类型声明
  • 提升代码可读性
  • 实现 DRY(Don't Repeat Yourself)原则

完整示例

// 1. 简化复杂结构
type UserProfile = {
  id: string;
  name: string;
  preferences: {
    theme: "light" | "dark";
    notifications: boolean;
  };
};

const updateProfile = (profile: UserProfile) => {
  /* 实现细节 */
};

// 2. 函数类型签名
type FetchHandler = (
  url: string, 
  options: { timeout: number; retries: number }
) => Promise<Response>;

const fetchData: FetchHandler = async (url, opts) => {
  /* 实现细节 */
};

// 3. 元组类型简化
type Point3D = [number, number, number];
const calculateDistance = (p1: Point3D, p2: Point3D) => {
  /* 三维空间计算 */
};

// 4. 泛型别名
type ApiResponse<T> = {
  data: T;
  timestamp: Date;
  statusCode: number;
};

const userResponse: ApiResponse<UserProfile> = await fetchUser();

典型应用场景

  1. 统一项目类型规范:type UserID = string
  2. 简化复杂对象结构
  3. 自文档化代码(见名知意)
  4. 泛型类型模板

三、黄金组合:Union 与 Alias 的协作

二者结合使用能最大化类型系统的优势:

// 1. 联合类型别名化
type Identifier = string | number | { id: string };

// 2. 复杂联合结构
type NetworkState =
  | { status: "idle" }
  | { status: "loading"; progress: number }
  | { status: "success"; data: ApiResponse<any> }
  | { status: "error"; code: number };

// 3. 类型安全的Redux模式
type Action = 
  | { type: "ADD_TODO"; text: string }
  | { type: "TOGGLE_TODO"; id: number }
  | { type: "DELETE_TODO"; id: number };

const reducer = (state: Todo[], action: Action) => {
  switch (action.type) {
    case "ADD_TODO":
      // action.text 可用
      return [...state, { text: action.text }];
    case "TOGGLE_TODO":
      // action.id 可用
      return state.map(todo => 
        todo.id === action.id ? { ...todo, done: !todo.done } : todo
      );
    // 无需 default case(类型穷尽检查)
  }
};

四、核心差异总结

特性 Union 类型 Type Aliases
本质 类型组合(逻辑或) 类型重命名
主要目的 扩展变量允许的类型范围 简化复杂类型声明
运行时影响 需要类型保护机制 纯编译时概念
典型符号 ` `(管道符) type 关键字
最佳实践 处理不确定类型的场景 创建领域特定语言(DSL)
嵌套能力 可包含其他联合类型 可封装任意类型(包括联合)

五、何时使用哪种类型?

选择 Union 类型当

  • 需要处理多种可能的输入类型
  • 构建状态机或复杂工作流
  • 变量可能为不同类型(如 string | null

选择 Type Alias 当

  • 重复使用复杂类型结构
  • 提升代码可读性(如 type Coordinate = [number, number]
  • 统一项目类型规范
  • 创建自解释的API签名

组合使用场景

  1. 为联合类型创建有意义的别名:
    type CardType = "visa" | "mastercard" | "amex"
  2. 简化包含联合的复杂结构:
    type PaymentMethod = {
      type: CardType;
      last4: string;
      expiry: Date;
    }
    

六、高级技巧:类型操作

// 1. 联合类型提取
type StringOrNumber = string | number;
type ExtractString<T> = T extends string ? T : never;
type Result = ExtractString<StringOrNumber>; // = string

// 2. 条件类型分发
type ToArray<T> = T extends any ? T[] : never;
type NumOrStrArray = ToArray<number | string>; // = number[] | string[]

// 3. 模板字面量类型(ArkTS 4.0+)
type HttpMethod = "GET" | "POST" | "PUT" | "DELETE";
type ApiEndpoint = `/api/${string}`;
type FullRoute = `${HttpMethod} ${ApiEndpoint}`;

总结

  • Union 类型 是类型系统的 "或运算符",扩展值的可能性
  • Type Aliases 是类型系统的 "命名工具",提升代码表达能力
  • 二者结合 能构建精确灵活的领域模型

在鸿蒙应用开发中:

// 典型ArkTS组件属性模式
type ButtonProps = {
  text: string;
  size: "small" | "medium" | "large";
  onPress: () => void;
  style?: StyleProp; // 联合类型 + 可选
};

@Entry
@Component
struct MyButton {
  @Prop config: ButtonProps; // 类型别名复用
  
  build() {
    Button(this.config.text)
      .onClick(() => this.config.onPress?.())
  }
}

掌握这两种类型特性,将使你的鸿蒙应用获得更强的类型安全保障和更优雅的代码架构。

RxSwift 框架解析

作者 90后晨仔
2025年6月8日 16:32

RxSwift 提供的是一种统一处理“异步+事件驱动”的编程范式,而不仅仅是一个库。它是一种思维方式。RxSwift的体验demo在文末。


一、RxSwift 核心架构图

graph TD
    A[RxSwift] --> B[Observable 可观察序列]
    A --> C[Observer 观察者]
    A --> D[Operator 操作符]
    A --> E[Subject 主体]
    A --> F[Scheduler 调度器]
    
    B --> B1[创建操作]
    B1 --> B11(create)
    B1 --> B12(deferred)
    B1 --> B13(just)
    B1 --> B14(from)
    B1 --> B15(interval)
    
    B --> B2[特征序列]
    B2 --> B21(Single)
    B2 --> B22(Completable)
    B2 --> B23(Maybe)
    
    E --> E1[PublishSubject]
    E --> E2[BehaviorSubject]
    E --> E3[ReplaySubject]
    E --> E4[AsyncSubject]
    
    F --> F1[MainScheduler]
    F --> F2[SerialDispatchQueueScheduler]
    F --> F3[ConcurrentDispatchQueueScheduler]
    F --> F4[OperationQueueScheduler]
    
    D --> D1[变换操作符]
    D1 --> D11(map)
    D1 --> D12(flatMap)
    D1 --> D13(scan)
    
    D --> D2[过滤操作符]
    D2 --> D21(filter)
    D2 --> D22(take)
    D2 --> D23(distinctUntilChanged)
    
    D --> D3[组合操作符]
    D3 --> D31(combineLatest)
    D3 --> D32(merge)
    D3 --> D33(zip)
    
    D --> D4[错误处理]
    D4 --> D41(catchError)
    D4 --> D42(retry)

二、Observable:异步流的抽象

作用:

RxSwift 中一切皆 Observable,它是异步事件流的载体,负责发出元素或错误/完成事件

特性:

  • 惰性执行,只有被订阅时才会触发
  • 可被多个 Observer 订阅
  • 可组合、链式操作

创建方式:

let observable = Observable<Int>.create { observer in
    observer.onNext(1)
    observer.onCompleted()
    return Disposables.create()
}

let justValue = Observable.just("hello")
let fromArray = Observable.from([1, 2, 3])
let timer = Observable<Int>.interval(.seconds(1), scheduler: MainScheduler.instance)

三、特征序列:语义更清晰的 Observable

1.Single

表示一次性返回一个元素或一个错误,常用于网络请求、文件读取等。

func fetchUser() -> Single<User> {
    return Single.create { single in
        API.getUser { result in
            switch result {
            case .success(let user): single(.success(user))
            case .failure(let error): single(.failure(error))
            }
        }
        return Disposables.create()
    }
}

2.Completable

只关心是否成功完成或失败,不发出任何元素。例如保存数据、写入文件等。

func save(user: User) -> Completable {
    return Completable.create { completable in
        let success = DB.save(user)
        success ? completable(.completed) : completable(.error(MyError()))
        return Disposables.create()
    }
}

3.Maybe

可能发出一个元素,也可能什么都不发,只完成或失败。用在“可选”任务中。

func loadCache() -> Maybe<Data> {
    return Maybe.create { maybe in
        if let cached = getCache() {
            maybe(.success(cached))
        } else {
            maybe(.completed)
        }
        return Disposables.create()
    }
}

四、Observer:消费 Observable 的响应者

响应事件:

  • onNext(_:):每个新值

  • onError(_:):错误终止

  • onCompleted():正常完成

示例:

observable.subscribe(
    onNext: { print("收到: ($0)") },
    onError: { print("错误: ($0)") },
    onCompleted: { print("完成") }
)

在实际工程中,用 bind() 替代 subscribe(),配合 disposeBag 自动内存管理。


五、Subject:Observable 与 Observer 的桥梁

类型 新订阅者收到 缓存行为 常用场景
PublishSubject 订阅后新事件 无缓存 事件广播
BehaviorSubject 最近一个事件 单个缓存 状态管理
ReplaySubject 所有缓存事件 自定义容量 数据重放
AsyncSubject 完成时最后一个事件 延迟发射 最终结果

示例:

let subject = BehaviorSubject(value: "初始值")

subject.onNext("新值")
subject.subscribe(onNext: { print("收到: ($0)") })

六、Operator:响应式的函数式构建块

RxSwift 提供数十种操作符,常见分类如下:

1. 变换类

observable.map { $0 * 2 }

textField.rx.text.orEmpty
    .flatMapLatest { query in searchAPI(query) }

2. 过滤类

observable.filter { $0 > 10 }

searchBar.rx.text.orEmpty
    .debounce(.milliseconds(300), scheduler: MainScheduler.instance)

3. 组合类

Observable.combineLatest(email.rx.text.orEmpty, password.rx.text.orEmpty)
    .map { !$0.isEmpty && !$1.isEmpty }

Observable.zip(userRequest, orderRequest) { user, orders in
    return Profile(user: user, orders: orders)
}

4. 错误处理

networkObservable
    .retry(3)
    .catchError { error in
        showAlert(error.localizedDescription)
        return .empty()
    }

七、Scheduler:线程控制器

用于指定事件在哪个线程产生和处理。

常用类型:

  • MainScheduler:主线程,UI 更新必用

  • SerialDispatchQueueScheduler:串行后台任务

  • ConcurrentDispatchQueueScheduler:并发执行

  • OperationQueueScheduler:基于 NSOperationQueue

使用示例:

apiObservable
    .subscribe(on: ConcurrentDispatchQueueScheduler(qos: .background))
    .observe(on: MainScheduler.instance)
    .subscribe(onNext: { updateUI($0) })

⚠️ observeOn 已废弃,使用 observe(on:)。


八、DisposeBag:内存管理利器

RxSwift 使用 DisposeBag 自动管理资源释放,避免内存泄漏。

class MyVC: UIViewController {
    let disposeBag = DisposeBag()

    override func viewDidLoad() {
        super.viewDidLoad()
        observable
            .subscribe(onNext: { ... })
            .disposed(by: disposeBag)
    }
}

也可以使用 takeUntil(self.rx.deallocated) 自动在控制器销毁时取消订阅。


九、最佳实践

1. 表单 UI 绑定

Observable.combineLatest(
    firstNameField.rx.text.orEmpty,
    lastNameField.rx.text.orEmpty
)
.map { $0 + " " + $1 }
.bind(to: fullNameLabel.rx.text)
.disposed(by: disposeBag)

2. 网络链式调用

loginService.login()
    .flatMapLatest { token in userService.fetchUser(token: token) }
    .observe(on: MainScheduler.instance)
    .subscribe(onNext: { user in
        self.updateUI(with: user)
    })
    .disposed(by: disposeBag)

3. 性能优化技巧

  • 使用 share(replay:scope:) 共享副作用序列
  • CPU密集操作切换线程:
computationObservable
    .subscribe(on: ConcurrentDispatchQueueScheduler(qos: .userInitiated))

十、结语:响应式的真正价值

RxSwift 将异步事件处理高度统一化,用 Observable 表达数据流,用操作符构建业务逻辑,用 Scheduler 控制线程,最终实现:

  • 声明式编程:清晰表达需求
  • 响应式系统:数据变化自动驱动 UI
  • 统一抽象:替代回调、代理、通知
  • 高可组合性:灵活构建复杂逻辑

📦 RxSwift体验demo

昨天以前首页

xcode 16.2报错 Sandbox: rsync.samba(xxxx)解决方案

作者 90后晨仔
2025年6月4日 23:26

解决方案来源

在开发 iOS 应用时,开发者常会遇到因 macOS 系统 沙盒机制(Sandbox) 导致的文件写入权限问题,典型错误如:

Sandbox: rsync.samba(...) deny(1) file-write-create ...

Snip20250604_4.png

这类错误通常发生在构建 Flutter、React Native 或集成第三方库(如 RxCocoa、Realm、MobileVLCKit)的项目中。本文将从原理、解决方案和实践技巧三个方面,帮助你快速定位并解决问题。


一、问题原理

1. 沙盒机制的作用

macOS 的沙盒机制是系统安全策略的一部分,限制应用程序对文件系统、网络等资源的访问权限。Xcode 在构建 iOS 应用时,默认启用沙盒保护,防止恶意行为。但某些场景下(如 Flutter 插件、第三方库的构建脚本),沙盒限制可能导致文件写入失败。

2. 常见触发场景

  • Flutter/React Native 项目:构建过程中需生成临时文件(如 .xcframeworkdSYM 文件),沙盒可能阻止写入。
  • CocoaPods/Carthage 集成:依赖库的构建脚本(如 rsync)可能因权限不足失败。
  • Xcode 版本升级:Xcode 15+ 引入更严格的沙盒策略,部分旧配置失效。
  • 多 Targets 项目:未统一配置所有 Target 的 ENABLE_USER_SCRIPT_SANDBOXING 设置。

二、通用解决方案

1. 禁用用户脚本沙盒

这是最直接的解决方案,适用于大多数场景:

操作步骤

  1. 打开 Xcode 项目。
  2. 进入 Project Navigator → 选择项目 → Build Settings
  3. 搜索 ENABLE_USER_SCRIPT_SANDBOXING
  4. 将值设为 No(适用于所有 Build Configurations:Debug/Release)。

示意图

Xcode 设置 ENABLE_USER_SCRIPT_SANDBOXING.png

注意事项

  • 需同时修改所有 Target:包括主工程(Runner)、Pods 工程(如 CleverPushNotificationServiceExtension)等。
  • 重启 Xcode:修改后需重启 Xcode 生效。

2. 清理缓存并重新安装依赖

沙盒错误可能由旧缓存或依赖残留导致:

操作步骤

  1. 删除 DerivedData
    rm -rf ~/Library/Developer/Xcode/DerivedData
    
  2. 清理 Flutter 缓存(针对 Flutter 项目):
    flutter clean
    flutter pub get
    
  3. 重新安装 CocoaPods 依赖
    cd ios
    pod deintegrate
    pod install --repo-update
    

参考资料

RxSwift 开源学习项目汇总

作者 90后晨仔
2025年6月4日 18:57

如果你有知道更好的开源学习项目请留言给我,我会更新到我得文章中,让更多爱学习的朋友看到!


🔝 1.RxSwift 官方示例项目

  • 地址ReactiveX/RxSwift

  • 简介:RxSwift 官方仓库提供了多个示例,展示了如何在实际项目中使用 RxSwift 进行异步操作和数据流处理。

  • 特点

    • 涵盖了 KVO 观察、异步操作、UI 事件等多个方面。
    • 提供了丰富的操作符使用示例。
  • 适合人群:希望深入理解 RxSwift 基础和高级用法的开发者。


📚 2.RxSwift 学习资料合集

  • 地址LeoMobileDeveloper/awesome-rxswift

  • 简介:这是一个精心整理的 RxSwift 学习资源合集,包含了开源应用、库、教程和社区资源。

  • 特点

    • 收录了多个开源项目,如 RxTodo、RxChat、RxGithub 等。
    • 提供了丰富的教程和学习资料链接。
  • 适合人群:希望通过多个项目和教程全面学习 RxSwift 的开发者。


🧱 3.iOS Clean Architecture MVVM + RxSwift 示例

  • 地址kwontaewan/iOS-Clean-Architecture-MVVM-RxSwift

  • 简介:该项目展示了如何使用 Clean Architecture、MVVM 架构和 RxSwift 构建 iOS 应用。

  • 特点

    • 采用分层架构,代码结构清晰。
    • 集成了 Moya 进行网络请求,使用 Kingfisher 进行图片加载。
  • 适合人群:希望学习如何在实际项目中应用 Clean Architecture 和 RxSwift 的开发者。


📖 4.RxSwift 教程配套项目

  • 地址kodecocodes/rxs-materials

  • 简介:这是 Raywenderlich.com 上 RxSwift 教程的配套项目,涵盖了从基础到高级的多个示例。

  • 特点

    • 每个章节都有对应的示例项目,便于学习和实践。
    • 涵盖了 RxSwift 的各个方面,包括操作符、调度器、错误处理等。
  • 适合人群:希望系统学习 RxSwift 的开发者。


🎨 5.MVVM + RxSwift 示例应用

  • 地址alexey-savchenko/MVVM-RxSwift-sample-app

  • 简介:该项目展示了如何结合 MVVM 架构和 RxSwift 构建 iOS 应用,使用了 SnapKit 进行布局。

  • 特点

    • 实现了相册、帖子和评论等功能。
    • 使用了 Coordinator 模式进行导航管理。
  • 适合人群:希望学习如何在实际项目中应用 MVVM 和 RxSwift 的开发者。


📱 6.SwiftHub - GitHub iOS 客户端

  • 地址khoren93/SwiftHub

  • 简介:SwiftHub 是一个使用 RxSwift 和 MVVM-C 架构构建的 GitHub iOS 客户端,展示了如何在实际应用中使用这些技术。

  • 特点

    • 实现了 GitHub 的多个功能模块,如仓库浏览、搜索等。
    • 采用了 Clean Architecture 和 Coordinator 模式。
  • 适合人群:希望学习如何构建复杂应用并应用 RxSwift 和 MVVM-C 架构的开发者。


📘 7.RxSwift 教程示例合集

  • 地址DroidsOnRoids/RxSwiftExamples

  • 简介:该项目包含了多个 RxSwift 教程的示例,涵盖了基础、网络请求、多线程等内容。

  • 特点

    • 每个示例都有详细的说明,便于理解和学习。
    • 涵盖了从基础到高级的多个主题。
  • 适合人群:希望通过多个小项目逐步学习 RxSwift 的开发者。


SwiftUI 值得学习的一些项目汇总

作者 90后晨仔
2025年6月4日 18:51

如果还有更好的学习项目欢迎留言,我会更新到我得文章中,让更多的人看到的!

🚀 推荐的 SwiftUI 开源项目

1.GeekMadeBySwiftUI

这是一个功能丰富的 SwiftUI 项目,展示了如何构建一个完整的应用程序。项目涵盖了用户界面设计、数据管理、网络请求等多个方面,非常适合初学者学习。

2.ZYSwiftUIFrame

该项目提供了一个完整的 SwiftUI 应用示例,包含网络请求、下拉刷新、上拉加载更多、数据增删改查、图片上传和预览等功能。项目还包含了服务端代码,使用 Go 语言编写,模拟真实的项目场景。

3.swiftui-example

这是一个 SwiftUI 示例、技巧和技术集合,旨在帮助开发者构建应用程序、解决问题,并了解 SwiftUI 的实际工作方式。项目主要内容来源于 hackingwithswift.com,适合想要深入了解 SwiftUI 的开发者。

4.Food Truck(苹果官方示例)

这是苹果在 WWDC22 发布的官方示例项目,展示了如何使用 SwiftUI 构建一个完整的应用程序。项目涵盖了以下内容:

  • 使用 NavigationSplitView 管理视图

  • 使用 Charts 展示趋势数据

  • 使用 WeatherService 获取天气数据

  • 实现了 Live Activities 和 Dynamic Island

2.Clean Architecture SwiftUI

该项目展示了如何在 SwiftUI 中应用 Clean Architecture 架构,涵盖了以下内容:

  • 使用 SwiftData 进行数据持久化

  • 实现网络请求

  • 依赖注入

  • 单元测试

适合希望构建可维护、可测试的 SwiftUI 应用的开发者。 


3.Fun SwiftUI Projects

该项目集合了 50 多个 SwiftUI 示例项目,包括:

  • 3D 柱状图

  • 饼图

  • 贪吃蛇游戏

  • 表情符号识别游戏

  • Reddit 客户端

每个项目都附有详细的教程,适合希望通过实践学习 SwiftUI 的开发者。


4.Simple SwiftUI

该项目由著名的 Swift 教程作者 Paul Hudson 创建,包含多个小型 SwiftUI 示例项目,如:

  • 新闻阅读器

  • 记事本

  • 记分板

  • 待办事项列表

适合初学者快速上手 SwiftUI。


5.SwiftUI Weather App

这是一个简单的天气应用示例,包含以下功能:

  • 主界面
  • 每日天气列表
  • 天气详情页面

适合初学者练习 SwiftUI 的基本布局和导航。 


6.SwiftUI App by Mindinventory

该项目展示了一个完整的应用流程,包括:

  • 引导页面

  • 登录/注册/忘记密码页面

  • 主界面

  • 收藏页面

  • 退出登录功能

适合希望了解完整应用流程的开发者。


7.SwiftUI 30 Projects

该项目包含了 30 个使用 SwiftUI 构建的应用示例,涵盖了各种常见的 UI 组件和功能,适合希望通过大量练习掌握 SwiftUI 的开发者。


8.SwiftUI Projects by MattWong

该项目包含多个 SwiftUI 示例项目,包括:

  • 宝可梦图鉴
  • 搜索功能
  • 列表和详情页面

适合希望通过实际项目学习 SwiftUI 的开发者。


📚 学习建议

  • 从基础开始:如果你是初学者,建议先从简单的项目入手,逐步了解 SwiftUI 的布局、视图和数据绑定等基础知识。
  • 逐步深入:在掌握基础知识后,可以尝试更复杂的项目,如 ZYSwiftUIFrame,学习如何处理网络请求、数据管理等实际开发中常见的问题。
  • 参考官方文档:苹果官方的 SwiftUI 文档 是学习 SwiftUI 的权威资料,建议结合项目实践进行学习。

从一个简单的登录示例开始解析Combine + MVVM

作者 90后晨仔
2025年6月4日 12:39

下边是一个登录的简单示例,直接复制到iOS项目工程中运行即可,URL需要替换成真实的才能起作用。这里的简单示例仅供学习!

// Combine + MVVM 示例:一个简单的登录界面,加入真实网络请求(使用 URLSession)

import SwiftUI
import Combine

// MARK: - 网络模型
struct LoginResponse: Decodable {
    let success: Bool
    let message: String
}

// MARK: - ViewModel
class LoginViewModel: ObservableObject {
    // 输入
    @Published var username: String = ""
    @Published var password: String = ""

    // 输出
    @Published var isLoginEnabled: Bool = false
    @Published var loginStatus: String = ""

    private var cancellables = Set<AnyCancellable>()

    init() {
        // 验证输入是否满足登录条件(用户名和密码长度 >= 3)
        Publishers.CombineLatest($username, $password)
            .map { username, password in
                return username.count >= 3 && password.count >= 3
            }
            .assign(to: &$isLoginEnabled)
    }

    func login() {
        guard let url = URL(string: "https://example.com/api/login") else {
            loginStatus = "无效的请求地址"
            return
        }

        let params = ["username": username, "password": password]
        let requestBody = try? JSONSerialization.data(withJSONObject: params)

        var request = URLRequest(url: url)
        request.httpMethod = "POST"
        request.setValue("application/json", forHTTPHeaderField: "Content-Type")
        request.httpBody = requestBody

        loginStatus = "登录中..."

        URLSession.shared.dataTaskPublisher(for: request)
            .map { $0.data }
            .decode(type: LoginResponse.self, decoder: JSONDecoder())
            .receive(on: DispatchQueue.main)
            .sink(receiveCompletion: { completion in
                if case let .failure(error) = completion {
                    self.loginStatus = "请求失败:\(error.localizedDescription)"
                }
            }, receiveValue: { response in
                self.loginStatus = response.success ? "登录成功 ✅" : "登录失败 ❌:\(response.message)"
            })
            .store(in: &cancellables)
    }
}

// MARK: - View
struct LoginView: View {
    @StateObject private var viewModel = LoginViewModel()

    var body: some View {
        VStack(spacing: 20) {
            TextField("用户名", text: $viewModel.username)
                .textFieldStyle(RoundedBorderTextFieldStyle())
                .autocapitalization(.none)

            SecureField("密码", text: $viewModel.password)
                .textFieldStyle(RoundedBorderTextFieldStyle())

            Button("登录") {
                viewModel.login()
            }
            .disabled(!viewModel.isLoginEnabled)
            .padding()
            .background(viewModel.isLoginEnabled ? Color.blue : Color.gray)
            .foregroundColor(.white)
            .cornerRadius(8)

            Text(viewModel.loginStatus)
                .foregroundColor(.green)
        }
        .padding()
    }
}

// MARK: - 预览
struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        LoginView()
    }
}

一、Combine 框架核心概念解析

1. 发布者 (Publisher)

@Published var username: String = ""
  • @Published 属性包装器:将普通属性转换为发布者
  • 当属性值改变时自动发送新值
  • 本质是创建了 Publisher 的派生类型:Published<Value>.Publisher

2. 操作符 (Operators)

Publishers.CombineLatest($username, $password)
    .map { ... } // 转换操作符
    .assign(to: &$isLoginEnabled) // 绑定操作符
  • CombineLatest:合并两个发布者的最新值(此处监听两个输入框)
  • map:值转换(将元组转换为布尔值)
  • assign:将值直接绑定到属性(自动管理内存)

3. 订阅者 (Subscriber)

.sink(receiveCompletion: { ... }, receiveValue: { ... })
  • sink:最常用的订阅者,提供两个闭包:
    • receiveValue:处理正常数据流
    • receiveCompletion:处理完成事件(成功/失败)
  • 返回 AnyCancellable 对象

4. 订阅管理

private var cancellables = Set<AnyCancellable>()
.store(in: &cancellables) 
  • AnyCancellable:类型擦除的取消令牌
  • 内存管理原理
    • cancellables 集合被释放时,自动取消所有订阅
    • 防止内存泄漏(尤其重要网络请求场景)

二、MVVM 架构实现细节

1. ViewModel 设计

class LoginViewModel: ObservableObject {
    // 输入属性
    @Published var username = ""
    @Published var password = ""
    
    // 输出属性
    @Published var isLoginEnabled = false
    @Published var loginStatus = ""
}
  • 数据流向
    graph LR
    View-->|绑定| ViewModel-->|发布| View
    
  • 双向绑定@Published 属性 + SwiftUI 的 $ 前缀语法
  • 状态驱动:所有 UI 变化都源自状态改变

2. 输入验证的实现

Publishers.CombineLatest($username, $password)
    .map { u, p in u.count >= 3 && p.count >= 3 }
    .assign(to: &$isLoginEnabled)
  • 响应式验证:当任一输入变化时自动重新验证
  • 无副作用:纯函数式转换(map 内部无状态修改)

三、网络请求关键实现

1. URLSession 的 Combine 扩展

URLSession.shared.dataTaskPublisher(for: request)
  • 返回 DataTaskPublisher 类型(遵守 Publisher 协议)
  • 发出值类型:(data: Data, response: URLResponse)
  • 错误类型:URLError

2. 响应处理链

.map { $0.data }  // 提取数据
.decode(type: LoginResponseModel.self, decoder: JSONDecoder()) // JSON解码
.receive(on: DispatchQueue.main) // 切换主线程
.sink(...) // 处理结果
  • 操作符链:典型的数据处理管道(Pipeline)
  • 线程切换
    • 网络请求默认在后台线程
    • receive(on:) 确保 UI 更新在主线程

3. 错误处理机制

sink(receiveCompletion: { 
    if case .failure(let error) = completion {
        self.loginStatus = "失败: \(error.localizedDescription)"
    }
}, ...)
  • 分离处理:成功走 receiveValue,失败走 receiveCompletion
  • 错误转换:保留原始错误信息(开发时可扩展为自定义错误类型)

四、SwiftUI 视图层实现要点

1. 视图与 ViewModel 的连接

@StateObject private var viewModel = LoginViewModel()
  • @StateObject:视图持有 ViewModel 的生命周期
  • @ObservedObject 的区别:创建者负责管理内存

2. 控件绑定

TextField("用户名", text: $viewModel.username)
SecureField("密码", text: $viewModel.password)
  • $ 语法糖:访问 @Published 属性的投影(Projection)
  • 等价于:Binding<String> 类型

3. 条件渲染

.background(viewModel.isLoginEnabled ? Color.blue : Color.gray)
.disabled(!viewModel.isLoginEnabled)
  • 响应式 UI:属性变化自动触发视图更新
  • 无命令式代码:无需手动 reloadupdate

五、关键优化点与注意事项

  1. 网络请求取消

    // 在 ViewModel 中增加
    func cancelRequests() {
        cancellables.forEach { $0.cancel() }
    }
    
    • 在视图消失时调用(防止后台更新销毁的视图)
  2. 输入防抖处理

    .debounce(for: .seconds(0.5), scheduler: RunLoop.main)
    
    • 避免频繁触发验证(尤其快速输入场景)
  3. 错误分类处理

    enum LoginError: Error {
        case invalidURL, networkError(URLError), decodingError
    }
    
    • 使用自定义错误类型提升可维护性
  4. 测试方案

    • ViewModel 单元测试:Mock URLSession
    • UI 测试:验证不同状态下的界面表现

六、MVVM + Combine 优势总结

特性 传统方式 Combine + MVVM
数据绑定 手动更新 自动响应
异步处理 回调地狱 声明式管道
状态管理 分散控制 集中处理
代码量 冗余 简洁(减少30%+)
可测试性 困难 依赖注入友好

完整技术栈:SwiftUI 声明式UI + Combine 响应式编程 + MVVM 架构模式


这个示例完整展示了现代 Swift 开发的核心理念:通过声明式语法描述 UI,通过响应式编程处理数据流,通过架构模式解耦逻辑

❌
❌