普通视图

发现新文章,点击刷新页面。
昨天 — 2025年4月3日首页

SwiftUI 字体系统详解

作者 Lexiaoyao20
2025年4月3日 12:31

在 iOS 应用开发中,文本排版是用户界面设计的关键元素之一。SwiftUI 提供了一套丰富而灵活的字体系统,让开发者能够轻松创建既美观又符合系统设计规范的文本样式。本文将深入探讨 SwiftUI 的字体系统,以及如何有效地使用它们来提升应用的用户体验。

SwiftUI 字体系统概述

SwiftUI 的字体系统主要分为三类:系统文本样式、自定义系统字体和自定义字体。这种分层设计使开发者既可以快速应用预设样式,又能在需要时进行精细的自定义。

系统文本样式(Text Style)

系统文本样式是 SwiftUI 提供的预定义字体样式,它们会根据用户在系统设置中的偏好自动调整大小,支持动态文字大小功能(Dynamic Type)。

Text("大标题").font(.largeTitle)
Text("标题").font(.title)
Text("正文内容").font(.body)
Text("图片说明").font(.caption)

以下是主要的系统文本样式及其用途:

样式 用途
.largeTitle 最大的标题样式,通常用于页面的主标题
.title 标准标题,用于重要区域的标题
.title2 二级标题,比 title 稍小
.title3 三级标题,比 title2 稍小
.headline 段落标题,通常使用粗体
.subheadline 副标题,比 headline 小
.body 正文样式,是默认的字体大小
.callout 标注文本,稍小于正文
.caption 说明文字,小字体,常用于图片说明或次要信息
.caption2 更小的说明文字
.footnote 脚注样式,比正文小
.preformatted 等宽字体,适用于代码显示(iOS 17+)

自定义系统字体

当预设样式无法满足需求时,可以使用 .system() 方法创建自定义系统字体:

// 基本自定义
Text("自定义大小").font(.system(size: 16))

// 自定义大小、粗细和设计风格
Text("圆角粗体").font(.system(size: 16, weight: .bold, design: .rounded))

字体权重选项

  • .ultraLight - 超细
  • .thin - 细体
  • .light - 轻体
  • .regular - 常规
  • .medium - 中等
  • .semibold - 半粗体
  • .bold - 粗体
  • .heavy - 重体
  • .black - 黑体

设计风格选项

  • .default - 默认系统字体
  • .serif - 衬线字体
  • .rounded - 圆角字体
  • .monospaced - 等宽字体
  • .italic - 斜体(iOS 16+)

自定义字体

对于品牌专属字体或特殊设计需求,SwiftUI 支持使用自定义字体:

// 基本自定义字体
Text("品牌专属字体").font(.custom("Brand Font", size: 14))

// 支持动态文字大小的自定义字体
Text("可调整大小的自定义字体").font(.custom("Brand Font", size: 14, relativeTo: .body))

使用 relativeTo 参数可以确保自定义字体也能响应系统的动态文字大小设置。

字体修饰符

除了设置整体字体样式,SwiftUI 还提供了单独修改字体特定属性的修饰符:

Text("粗体文本").fontWeight(.bold)
Text("斜体文本").italic()
Text("小型大写字母").textCase(.uppercase)
Text("下划线文本").underline()
Text("删除线文本").strikethrough()

最佳实践

  1. 优先使用系统文本样式:自动适应用户偏好设置,提升无障碍性
  2. 保持层次结构:使用不同字体样式建立清晰视觉层次
  3. 慎用自定义字体:确保支持动态文字大小
  4. 考虑本地化:不同语言可能需要不同的文本空间
  5. 测试极端情况:在最大和最小文字大小设置下测试界面

Swift 6.1 新特性

作者 YungFan
2025年4月3日 11:50

Swift 6.1 内置于 Xcode 16.3,这是 Swift 6 之后的首个小版本更新,新特性很少。

尾随逗号

元组、函数的参数、闭包捕获列表以及字符串插值等都可以像数组一样,在最后一个元素的后面添加,,以便轻松地追加、删除、重新排列或者注释最后一个元素。

// 元组
(404, "Not Found",)
// 函数的参数
func sum(num1: Int, num2: Int,) -> Int {
    num1 + num2
}
var vehicle = "Car"
// 闭包捕获列表
let closure = { [vehicle,] in
    print("Vehicle:", vehicle)
}
// 字符串插值
"This is a \(vehicle,)"

混合开发

介绍

  • 增加新的关键字@implementation,配合@objc可以为 Objective-C 导入的.h声明提供实现。
  • 实现方式:在 Swift 中扩展 Objective-C 类,然后通过@objc @implementation实现属性与方法以替换 Objective-C 的@implementation

实现

  1. 新建一个基于 Swift 语言的 iOS 项目。
  2. 创建一个 Objective-C 类,此时会弹出一个提醒对话框(添加这个文件会创建一个 Objective-C 与 Swift 的混合项目,你是否希望 Xcode 自动配置一个桥接头文件来让 2 种语言的类文件相互可见?),点击Create Bridging Header
  3. 项目中多出 3 个文件,分别为创建的 Objective-C 类文件(.h.m)与 Bridging Header 文件,修改 Objective-C 类文件如下。
// .h文件
@interface Person: NSObject

@property(nonatomic, copy) NSString *name; 
-(void)eat;

@end


// .m文件
// @implementation Person
// @end
  1. 在 Bridging Header 文件中通过#import "类名.h"导入所有需要使用的 Objective-C 类的头文件。
  2. 在 Swift 中实现并且调用 Objective-C。
import UIKit

class ViewController: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()

        // 调用
        let person = Person()
        person.name = "zhangsan"
        person.eat() // zhangsan吃饭了
    }
}

// MARK: - @objc @implementation extension Objective-C类
@objc @implementation extension Person {
    // 实现属性
    var name: String?

    // 实现方法
    func eat() {
        print("\(name ?? "")吃饭了")
    }
}

注意:一旦在 Swift 进行了实现,Objective-C 中的@implementation不需要再实现,否则会报2 duplicate symbols的编译错误。

并发编程

  • actor 允许在属性与函数上使用nonisolated,表示该 API 可以在任何并发上下文中安全调用。Swift 6.1 将nonisolated支持到了类型与扩展,这样其内的所有属性与方法不需要再单独添加nonisolated
// 结构体
nonisolated struct Station {
}

class Train {
}
// 扩展
nonisolated extension Train {
}
  • withTaskGroup()withThrowingTaskGroup()的闭包可以推断出子任务结果类型。
// Swift6.1之前
await withTaskGroup(of: Int.self) { group in
    ...
}
await withThrowingTaskGroup(of: String.self) { group in
    ...
}
// Swift6.1之后
await withTaskGroup { group in
    ...
}
await withThrowingTaskGroup { group in
    ...
}
昨天以前首页

远离 dismiss,拥抱状态驱动

作者 Fatbobman
2025年4月2日 22:12

在 SwiftUI 开发中,环境值 dismiss 因其灵活、自适应的特性备受开发者青睐。它能够根据当前视图的上下文智能执行关闭操作:在模态视图中关闭窗口、在导航堆栈中弹出视图,甚至在多列导航容器中自动关闭边栏。正是这种看似“万能”的便捷性,让许多开发者将它作为首选工具。然而,便捷的背后往往隐藏着风险。频繁使用 dismiss 可能在应用程序中埋下隐患,引发测试难题乃至难以追踪的稳定性问题。本文将分析我们为何应谨慎对待 dismiss,并介绍更加健壮可靠的状态管理方案。通过重新审视视图呈现与消失的逻辑,我们能够打造出更稳定、易维护且可预测的 SwiftUI 应用。

iOS性能优化:OC和Swift实战指南

2025年4月1日 09:29

常见的iOS性能优化点,比如内存管理、UI优化、多线程处理、网络请求优化、启动优化、I/O操作、图像处理、算法优化、工具使用等。

一、内存优化

1. 循环引用处理

  • 原理:对象之间的强引用导致无法释放,内存泄漏。

  • Objective-C

    • 使用 __weak 或 __unsafe_unretained(需谨慎)打破循环:

      __weak typeof(self) weakSelf = self;
      self.block = ^{
          // 弱引用避免循环
          [weakSelf doSomething];
      };
      
    • 对 NSTimer 使用 NSProxy 或 weak 委托模式。

  • Swift

    • 使用 [weak self] 或 [unowned self] 捕获列表:

      self.block = { [weak self] in
          guard let self = self else { return }
          self.doSomething()
      }
      
    • unowned 适用于生命周期相同或更短的场景(如父子关系)。


2. 自动释放池(Autorelease Pool)

  • 原理:批量创建临时对象时,及时释放内存。

  • Objective-C

    @autoreleasepool {
        for (int i = 0; i < 100000; i++) {
            NSString *temp = [NSString stringWithFormat:@"%d", i];
            // 临时对象会被及时释放
        }
    }
    
  • Swift

    autoreleasepool {
        for i in 0..<100000 {
            let temp = "(i)"
            // 临时对象在块结束时释放
        }
    }
    
  • 适用场景:大量临时对象生成(如解析 JSON 数组、图像处理)。


二、UI 性能优化

1. 避免离屏渲染(Offscreen Rendering)

  • 原理:离屏渲染(如圆角、阴影)触发 GPU 额外绘制,导致卡顿。

  • 优化方法

    • 预渲染圆角

      • Objective-C

        // 使用 Core Graphics 提前绘制圆角
        UIGraphicsBeginImageContextWithOptions(view.bounds.size, NO, 0);
        [[UIBezierPath bezierPathWithRoundedRect:view.bounds cornerRadius:10] addClip];
        [image drawInRect:view.bounds];
        UIImage *roundedImage = UIGraphicsGetImageFromCurrentImageContext();
        UIGraphicsEndImageContext();
        view.layer.contents = (id)roundedImage.CGImage;
        
      • Swift

        let renderer = UIGraphicsImageRenderer(size: view.bounds.size)
        let roundedImage = renderer.image { context in
            UIBezierPath(roundedRect: view.bounds, cornerRadius: 10).addClip()
            image.draw(in: view.bounds)
        }
        view.layer.contents = roundedImage.cgImage
        
    • 避免 shouldRasterize:除非复用图层,否则会触发离屏渲染。


2. Cell 复用与轻量化

  • 原理:避免频繁创建/销毁 Cell,减少 CPU 和内存压力。

  • Objective-C

    - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
        // 复用 Cell
        UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"CellID"];
        if (!cell) {
            cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:@"CellID"];
        }
        // 轻量配置(避免复杂计算)
        cell.textLabel.text = [self.dataArray[indexPath.row] title];
        return cell;
    }
    
  • Swift

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "CellID", for: indexPath)
        cell.textLabel?.text = dataArray[indexPath.row].title
        return cell
    }
    
  • 优化点

    • 避免在 cellForRow 中执行耗时操作(如网络请求)。
    • 使用 prepareForReuse 清理旧数据。

三、多线程优化

1. 主线程任务最小化

  • 原理:主线程阻塞导致 UI 卡顿(16ms 内未完成一帧绘制)。

  • Objective-C

    // 将耗时操作放到后台线程
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        NSData *data = [NSData dataWithContentsOfURL:url];
        dispatch_async(dispatch_get_main_queue(), ^{
            self.imageView.image = [UIImage imageWithData:data];
        });
    });
    
  • Swift

    DispatchQueue.global(qos: .userInitiated).async {
        let data = try? Data(contentsOf: url)
        DispatchQueue.main.async {
            self.imageView.image = UIImage(data: data!)
        }
    }
    

2. 线程安全与锁优化

  • Objective-C

    • @synchronized 实现简单但性能较低:

      objc

      @synchronized(self) {
          // 临界区
      }
      
    • 高性能场景使用 os_unfair_lock(替代已废弃的 OSSpinLock):

      #include <os/lock.h>
      os_unfair_lock lock = OS_UNFAIR_LOCK_INIT;
      os_unfair_lock_lock(&lock);
      // 临界区
      os_unfair_lock_unlock(&lock);
      
  • Swift

    • 使用 NSLock 或 DispatchQueue 屏障:

      let queue = DispatchQueue(label: "com.example.threadSafe", attributes: .concurrent)
      queue.async(flags: .barrier) {
          // 写操作(独占访问)
      }
      

四、网络优化

1. 请求合并与缓存

  • Objective-C

    // 使用 NSURLSession 的缓存策略
    NSURLRequest *request = [NSURLRequest requestWithURL:url cachePolicy:NSURLRequestReturnCacheDataElseLoad timeoutInterval:10];
    
  • Swift

    let request = URLRequest(url: url, cachePolicy: .returnCacheDataElseLoad, timeoutInterval: 10)
    
  • 优化点

    • 减少重复请求(如短时间内多次刷新)。
    • 使用 HTTP/2 多路复用降低连接开销。

五、启动时间优化

1. 冷启动阶段优化

  • 原理:减少 main() 函数之前的加载时间(T1)和首帧渲染时间(T2)。

  • Objective-C

    • 减少动态库数量,合并 Category。
    • 避免在 +load 方法中执行代码。
  • Swift

    • 使用 @UIApplicationMain 减少启动代码。

    • 延迟非必要初始化:

      DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
          // 延迟初始化第三方 SDK
      }
      

六、I/O 优化

1. 文件读写异步化

  • Objective-C

    dispatch_io_t channel = dispatch_io_create_with_path(DISPATCH_IO_STREAM, [path UTF8String], O_RDONLY, 0, queue, ^(int error) {});
    dispatch_io_read(channel, 0, SIZE_MAX, queue, ^(bool done, dispatch_data_t data, int error) {});
    
  • Swift

    let queue = DispatchQueue.global(qos: .background)
    DispatchIO.read(fromFileDescriptor: fd, queue: queue) { data, _ in
        // 处理数据
    }
    

七、图像处理优化

1. 异步解码与降采样

  • Objective-C

    // 使用 ImageIO 进行降采样
    CGImageSourceRef source = CGImageSourceCreateWithURL((__bridge CFURLRef)url, NULL);
    CGImageRef imageRef = CGImageSourceCreateImageAtIndex(source, 0, (__bridge CFDictionaryRef)@{(id)kCGImageSourceThumbnailMaxPixelSize: @(300)});
    UIImage *image = [UIImage imageWithCGImage:imageRef];
    
  • Swift

    let source = CGImageSourceCreateWithURL(url as CFURL, nil)!
    let options: [CFString: Any] = [kCGImageSourceThumbnailMaxPixelSize: 300]
    let imageRef = CGImageSourceCreateThumbnailAtIndex(source, 0, options as CFDictionary)!
    let image = UIImage(cgImage: imageRef)
    

八、算法与数据结构

1. 高效遍历与查询

  • Objective-C

    • 使用 NSDictionary 替代 NSArray 快速查找(O(1) vs O(n))。
  • Swift

    • 使用 lazy 延迟计算集合:

      let filteredData = data.lazy.filter { $0.isValid }.map { $0.value }
      
    • 使用 ContiguousArray 提升性能(连续内存布局)。


九、工具使用

1. Instruments 分析

  • Time Profiler:定位 CPU 热点函数。
  • Allocations:分析内存分配类型和泄漏。
  • Core Animation:检测离屏渲染和帧率。

十、Swift 特有优化

1. 减少动态派发

  • 使用 final 和 private 修饰类或方法:

    final class NetworkManager {
        private func fetchData() { ... }
    }
    

2. 值类型优先

  • 使用结构体(struct)替代类(class)减少引用计数开销:

    struct Point {
        var x: Double
        var y: Double
    }
    

总结

  • Objective-C 优化重点:手动内存管理、@autoreleasepool、避免 performSelector 潜在泄漏。
  • Swift 优化重点:值类型、协议扩展、DispatchQueue 和现代语法(如 Result 类型)。
  • 通用原则:减少主线程阻塞、复用资源、异步化耗时操作、利用工具分析瓶颈。

为什么 Swift 的反射像个“玩具”?聊聊 Mirror 背后的设计哲学

作者 JQShan
2025年3月31日 21:03

引子:当程序员想“窥探”一个对象

如果你用过 Java 或 C#,大概对「反射」这个词不陌生——它能让你在运行时像变魔术一样动态调用方法、修改属性,甚至篡改私有字段。但在 Swift 中,如果你想“窥探”一个对象的内部,苹果只给了你一个看似简陋的工具:Mirror。它不能修改属性,无法获取方法,连私有变量都藏着掖着。这不禁让人想问:反射这么重要的能力,苹果为何只做了个“玩具”出来?

今天,我们就来聊聊 Mirror 的设计逻辑,以及它背后隐藏的 Swift 语言哲学。

一、Mirror 的“克制”:安全与性能的双重防线

想象一下,你正在造一辆车。Java 的反射像是给你一套万能扳手,能拆发动机、改刹车片,甚至把车门卸下来。而 Swift 的 Mirror 更像是一把车钥匙——它能让你打开车门,看看内饰,但别想乱改零件。这种“克制”背后,是苹果对 Swift 的两大核心坚持:

  1. 安全第一 Swift 从诞生起就带着「杜绝未定义行为」的执念。反射能绕过编译检查,就像给代码开了个后门。比如,Java 中你可以用反射强行修改 final 字段,但这可能导致不可预知的崩溃。而 Mirror 的只读设计,本质上是在说: “你可以看看,但别乱动。”
  2. 速度不能妥协 Swift 被用于 iOS 系统内核、高性能游戏引擎等场景。反射的动态类型检查(as?Any)会带来运行时开销,而 Mirror 的轻量化设计,让它在需要时足够快,甚至能被编译器优化掉部分成本。

二、为什么不需要“万能扳手”?Swift 的编译时魔法

苹果似乎对动态反射兴趣缺缺,但其实他们找到了一种更“Swift 风格”的解决方案:把问题消灭在编译时。举个例子:

  • 场景:JSON 解析 在 Java 中,你可能用反射遍历字段,匹配 JSON 的 key。而在 Swift 中,Codable 协议通过编译器自动生成代码,直接映射属性——无需运行时反射,且类型安全零开销。
// 编译器自动生成编解码逻辑!
struct User: Codable {
    var name: String
    var age: Int
}
  • 场景:依赖注入 Java 的 Spring 框架依赖反射创建对象,而 Swift 可以通过泛型 + 协议,在编译时完成类型绑定:
// 编译时就知道 Container 里存了什么类型
container.register(UserService()) 
let service: UserService = container.resolve()

苹果的逻辑很明确:能通过类型系统解决的问题,绝不留到运行时。

三、Mirror 的生存空间:优雅的妥协

当然,总有些场景需要运行时信息。比如调试工具、动态生成日志,或是教学demo中展示对象结构。这时 Mirror 就派上用场了:

// 打印对象的所有属性
func debugPrint(_ value: Any) {
    let mirror = Mirror(reflecting: value)
    for child in mirror.children {
        print("(child.label ?? "?"): (child.value)")
    }
}

但你会发现,Mirror 的设计处处透着“小心翼翼”:

  • 不支持方法反射(避免动态派发)
  • 不暴露内存布局(防止不安全访问)
  • 对枚举和结构体的支持有限(鼓励模式匹配)

它更像是一个“安全气囊”,只在必要时弹出,而非让开发者随时飙车。

四、从 Mirror 看 Swift 的“价值观”

  1. 开发者不是敌人 Java 的反射默认允许访问私有字段,而 Swift 的 Mirror 对私有属性的可见性取决于模块边界——它假设开发者是理性的,但依然用访问控制保护代码的封装性。
  2. 工具链即力量 Swift 更倾向于通过编译器(如自动生成 Codable 代码)、Xcode 工具链(如 LLDB 调试器)来辅助开发,而非依赖运行时动态能力。
  3. 生态的统一性 在 SwiftUI 中,属性包装器(@State)、函数式编程等特性,让开发者无需反射也能实现动态 UI 和数据绑定。反射不再是必需品,而是备胎。

五、如果你真的需要“万能扳手”……

虽然苹果不鼓励,但总有极客想突破限制。比如:

  • 用 @dynamic 修饰符兼容 Objective-C 的运行时
  • 通过指针黑魔法直接操作内存布局(危险!但刺激)
  • 第三方库如 Runtime 提供元编程能力

但当你走这条路时,苹果的设计师可能会在背后叹气: “何必呢?明明有更安全的方式啊。”

结语:Mirror 是一面镜子,照出 Swift 的灵魂

Mirror 的简陋,恰恰反映了 Swift 的野心——它不想成为另一个“什么都能做,但处处是坑”的动态语言,而是试图用类型安全、编译时优化和清晰的API,重新定义现代编程的边界。就像 Swift 之父 Chris Lattner 所说: “我们希望开发者写出明显正确的代码,而非依赖运行时的小聪明。”

所以,下次当你嫌弃 Mirror 功能弱时,不妨换个角度想:或许不是苹果吝啬,而是他们相信,最好的魔法,应该发生在编译时 ✨。

MCP 崛起与苹果的 AI 框架设想 - 肘子的 Swift 周报 #77

作者 Fatbobman
2025年3月31日 22:00

在最近一段时间,在社交网络上,越来越多的 Model Context Protocol(MCP)使用者展示了各种丰富多彩的应用场景,从操控 Blender 创建精美场景,到利用最新的 GPT-4o 图片构建完整的漫画故事。MCP 巧妙地打开了以文本为主要互动手段的大模型,与现实世界之间的大门。

SwiftUI-国际化

作者 YungFan
2025年3月30日 20:13

介绍

  • 如果 App 需要提供给不同国家的用户使用,则需要进行国际化处理。
  • SwiftUI 项目的国际化主要包括:Info.plist 文件国际化、文本国际化等。

配置国际化语言

在进行国际化之前,必须要添加需要的国际化语言,选中国际化的项目 —> PROJECT —> Info —> Localizations,点击+添加需要的国际化语言(默认已经存在英文)。

Info.plist文件国际化

  1. 新建一个Strings File,必须命名为InfoPlist.strings
  2. 选中InfoPlist.strings,在 Xcode 的右侧文件检查器中找到Localization,点击Localize...,然后勾选配置的国际化语言。
  3. InfoPlist.strings左侧多了一个箭头,点击箭头展开后可以看见不同语言的Strings File,里面存放的是形如Key = Value的键值对。
  4. 在不同语言的Strings File中设置需要国际化的内容,如 App 名称等。
// 英文App名
"CFBundleName" = "I18N";
// 中文App名
"CFBundleName" = "国际化";

文本国际化

  1. 新建一个Strings File,必须命名为Localizable.strings
  2. 选中InfoPlist.strings,在 Xcode 的右侧文件检查器中找到Localization,点击Localize...,然后勾选配置的国际化语言。
  3. Localizable.strings左侧多了一个箭头,点击箭头展开后可以看见不同语言的Strings File
  4. 在不同语言的Strings File中设置需要国际化的文本键值对。
// 英文
"title" = "Reminder";
"message" = "Weather Information";
// 插值
"Weather is %@" = "Today is %@";
"Temperature is %lld" = "The temperature is %lld";
// 中文
"title" = "提示";
"message" = "今日天气";
// 插值
"Weather is %@" = "今天 %@";
"Temperature is %lld" = "气温 %lld 度";
  1. SwiftUI 文本国际化非常简单,开箱即用,因为大多数 View 与 Modifier 的构造方法中都将LocalizedStringKey作为参数类型,该参数的值为文本键值对中的键。
import SwiftUI

struct ContentView: View {
    let weather = "Sunny"
    let temperature = 10

    var body: some View {
        VStack {
            // 纯文本,有3种方式
            Text(title)
            
            Text(LocalizedStringKey("title"))

            Text("title", comment: "The title of the dialog.")
            
            // 自定义View
            MessageView("message")
            
            // 插值
            Text("Weather is \(weather)")
            
            Text("Temperature is \(temperature)")   
        }
    }
}

struct MessageView: View {
    var messaege: LocalizedStringKey

    init(_ messaege: LocalizedStringKey) {
        self.messaege = messaege
    }

    var body: some View {
        Text(messaege)
    }
}

注意:插值格式参考 String Format Specifiers

测试

默认情况下,App 的语言随着系统语言的变化而变化。但在开发阶段,如果才能快速测试 App 的国际化效果?主要有以下几种方式。

  1. 运行 App 之后在设备/模拟器通过设置(Settings)—> 通用(General)—> 语言与地区(Languages & Region) 切换系统语言以查看 App 的国际化效果。
  2. 通过 Xcode 菜单 —> Product —> Scheme —> Edit Scheme... —> Run —> Options —> App Language,选择需要测试的国际化语言之后再运行 App。
  3. 通过 Xcode 菜单 —> Product —> Scheme —> Manage Scheme... —> 选择需要复制的 Scheme —> 点击下方的圆形...图标 —> Duplicate —> 重命名 Scheme,然后将复制的 Scheme 按照方式 2 将 App Language 设置为需要测试国际化语言,最后运行时选择对应国际化语言的 Scheme。

效果

  • 英文。

英文.png

  • 中文。

中文.png

用「属性包装器」给你的Swift代码请个“管家” —— 告别重复代码的魔法指南

作者 JQShan
2025年3月27日 20:03

大家好!今天咱们来聊一个 Swift 中的宝藏特性: @propertyWrapper(属性包装器)。它就像给你的代码请了个聪明的管家,能帮你自动处理那些繁琐的属性管理逻辑。举个栗子🌰:数据范围限制、自动保存到本地、线程安全检查……这些重复劳动都能被它轻松承包。

一、为什么需要这个“管家”?

想象你在开发一个调色板应用,每个颜色通道的值都要限制在0-255之间。传统写法可能是这样的:

struct Color {
    private var _red: Int = 0
    var red: Int {
        get { _red }
        set { _red = min(max(newValue, 0), 255) }
    }
    // 绿色和蓝色还要再写两遍同样的逻辑 😫
}

每次写这种代码都像是复制粘贴的流水线工人,不仅容易出错,修改需求时更是灾难。这时候就该祭出 @propertyWrapper 了!

二、打造你的第一个“管家”

咱们来造一个自动限制数值范围的管家,就叫它 Clamped

@propertyWrapper
struct Clamped {
    private var value: Int
    let min: Int
    let max: Int
    
    // 重点:初始化时传入 wrappedValue(被包装的默认值)
    init(wrappedValue: Int, min: Int, max: Int) {
        self.min = min
        self.max = max
        self.value = Swift.min(Swift.max(wrappedValue, min), max)
    }
    
    var wrappedValue: Int {
        get { value }
        set { value = Swift.min(Swift.max(newValue, min), max) }
    }
}

用法简单到像写诗 ✨:

struct Color {
    @Clamped(min: 0, max: 255) var red = 0
    @Clamped(min: 0, max: 255) var green = 0
    @Clamped(min: 0, max: 255) var blue = 0
}

var myColor = Color()
myColor.red = 300
print(myColor.red) // 自动变成255,深藏功与名

三、管家的隐藏技能:投影值(Projected Value)

有时候除了管理属性值,咱们还想让管家“多嘴”说点别的。比如记录每次数值变化的日志:

@propertyWrapper
struct Logged<T> {
    private var value: T
    
    var wrappedValue: T {
        get { value }
        set {
            print("[日志] 值从 (value) → (newValue)")
            value = newValue
        }
    }
    
    // 用 $ 访问投影值
    var projectedValue: String { "当前值: (value)" }
    
    init(wrappedValue: T) { self.value = wrappedValue }
}

struct Test {
    @Logged var score = 60
}

let test = Test()
test.score = 90 // 控制台输出:[日志] 值从 60 → 90
print(test.$score) // 输出:当前值: 90

这个 $ 符号就像管家的对讲机,随时获取额外信息!

四、实战:自动保存用户设置

每次用 UserDefaults 保存设置都要写一堆代码?管家来搞定:

@propertyWrapper
struct UserDefault<T> {
    let key: String
    let defaultValue: T
    
    init(_ key: String, defaultValue: T) {
        self.key = key
        self.defaultValue = defaultValue
        // 首次注册默认值
        UserDefaults.standard.register(defaults: [key: defaultValue])
    }
    
    var wrappedValue: T {
        get { UserDefaults.standard.object(forKey: key) as? T ?? defaultValue }
        set { UserDefaults.standard.set(newValue, forKey: key) }
    }
}

// 用法优雅如呼吸
enum Settings {
    @UserDefault("HAS_SEEN_TUTORIAL", defaultValue: false) 
    static var hasSeenTutorial: Bool
}

Settings.hasSeenTutorial = true // 自动保存到本地

五、高级管家:线程安全守护者

在多线程环境下保护数据安全?加个锁就完事:

@propertyWrapper
struct ThreadSafe<T> {
    private var value: T
    private let queue = DispatchQueue(label: "com.threadsafe.queue")
    
    init(wrappedValue: T) { self.value = wrappedValue }
    
    var wrappedValue: T {
        get { queue.sync { value } }
        set { queue.sync { value = newValue } }
    }
}

class Counter {
    @ThreadSafe var count = 0
    
    func increment() {
        count += 1 // 多线程操作也稳如老狗
    }
}

六、注意事项:别让管家“翻车”

  1. 初始化顺序:第一个参数必须是 wrappedValue
  2. 别用在计算属性上:管家只管存储属性
  3. 性能敏感处慎用:比如高频调用的属性别加复杂逻辑
  4. 避免套娃:别在包装器里调用自身导致死循环

七、总结:让代码自己“长脑子”

通过 @propertyWrapper,咱们把重复的逻辑封装成一个个“智能管家”,让属性自己学会管理自己。这种写法不仅让代码更简洁,还能让团队协作时减少低级错误——毕竟,谁不喜欢一个会主动干活的管家呢?

下次写代码时,不妨想想:“这段逻辑能不能交给属性包装器?” 也许你会发现,代码世界突然清爽了许多 🌟。

动手时间到! 试着给你的项目写一个属性包装器,评论区等你分享奇思妙想~ 🚀

HelloGitHub 第 108 期

2025年3月28日 07:52
本期共有 39 个项目,包含 C 项目 (3),C# 项目 (1),C++ 项目 (3),CSS 项目 (1),Go 项目 (3),Java 项目 (1),JavaScript 项目 (5),Kotlin 项目 (1),Python 项目 (5),Rust 项目 (2),Swift 项目 (1),人工智能 (5),其它 (5),开源书籍 (3)

SwiftUI Environment:理念与实践

作者 Fatbobman
2025年3月26日 22:12

SwiftUI 的 Environment 是一个优雅且功能强大的依赖注入机制,几乎每个 SwiftUI 开发者都会在日常开发中接触和应用。这一机制不仅简化了视图间的数据传递,也为应用架构设计提供了更多的可能性。本文将暂且搁置具体的实现细节,转而聚焦于 Environment 在架构中的角色与边界,探讨那些常被忽视却至关重要的设计理念与实践经验。

❌
❌