阅读视图

发现新文章,点击刷新页面。

JS 高手必会:手写 new 与 instanceof

手写 instanceof

首先我们需要了解 instanceof是啥?

在其他面向对象编程语言中instanceof大多为实例判断运算符,即检查对象是否是某个构造函数的实例。

但是在 JS 中,instanceof原型关系判断运算符,用于检测构造函数的prototype属性是否出现在某个对象的原型链上。

// object instanceof Constructor
A instanceof B // A 的原型链上是否有 B 的原型 

而在大型项目、多人协作的情况下,在搞不懂对象上有哪些属性和方法,通过instanceof来查找继承关系

原型链关系:

  • __proto__: 指向原型对象(上一位的 .prototype),用于属性查找
  • constructor: 指向构造函数本身,用于标识对象的创建者
  • prototype: 函数的属性,指向原型对象(内置构造函数的 prototype 上通常有默认方法)
子.__proto__ === 父.prototype
父.prototype.constructor === 父
子.__proto__.constructor === 父(通过原型链访问)

举个最简单的例子:

const arr = [];
console.log(
    arr.__proto__, // Array.prototype
    arr.__proto__.__proto__, // Object.prototype
    arr.__proto__.__proto__.__proto__ // null
)

那么arr的原型链关系就是:arr -> Array.prototype -> Object.prototype -> null

而我们要手写instanceof,也就是只需要沿着原型链去查找,那么用简单的循环即可。

手写代码如下

// right 是否出现在 left 的原型链上
function isInstanceOf(left, right) {
    let proto = left.__proto__;
    // 循环查找原型链
    while (proto) {
        if (proto === right.prototype) {
            return true;
        }
        proto = proto.__proto__; // null 结束循环
    }
    return false;
}

手写 new

new对我们来说并不陌生,也就是实例运算符

在之前的文章我也提到过new的伪代码,让我们再来复习一下:

// 从空对象开始
let obj = {}
// this -> 创建空对象,运行构造函数
Object.call(obj)
// 将空对象的__proto__ 指向 构造函数的原型对象
obj.__proto__ = Object.prototype
// 返回新对象
return obj

写法一:(es6 新写法)

假如我们要new一个实例对象,但是不知道构造函数上的参数数量,而在es6中有一个新的运算符,也就是...运算符,它的诸多功能就可以满足我们的需求。

...扩展运算符

...有双重身份,在不同情况下的作用也不同

  • 函数调用 / 数组 / 对象字面量中...称为扩展运算符,将可迭代对象“展开”为独立元素

  • 函数参数 / 解构赋值中: ...称为剩余参数/剩余元素,将多个值“收集”为一个数组

// 展开数组
const arr1 = [1, 2, 3]; 
const arr2 = [...arr1, 4, 5]; // [1, 2, 3, 4, 5] 

// 收集数组
const [first, ...rest] = [1, 2, 3, 4];
console.log(first); // 1
console.log(rest);  // [2, 3, 4]

而依据我们的伪代码即可模拟 new的功能

手写代码如下

function objectFactory(Construstor, ...args) {
    // 创建空对象
    var obj = new Object(); 
    // 绑定 this 并执行构造函数
    Construstor.apply(obj, args); // 不能使用call,因为apply调用数组
    // 设置原型链
    obj.__proto__ = Construstor.prototype; 
    return obj;
}

// 使用:完全不需要知道 Person 需要几个参数
function Person(name, age, city) {
  this.name = name;
  this.age = age;
  this.city = city;
}

const p = objectFactory(Person, 'Alice', 25, 'Beijing'); // 自动适配

写法二:(根据arguments es5)

当然,在es6之前我们并没有...运算符,那么如何手写new呢?这就不得不提到arguments了。

arguments 是什么?

arguments 是 JS 中的一个类数组对象,它在所有非箭头函数内部自动可用,用于访问传递给该函数的所有实参

类数组对象:

  • 拥有 length 属性和若干索引属性,但不具备数组原型方法(如 .push(), .map(), .forEach() 等)的对象,所以其不是真正的数组

  • 普通函数中自动绑定。

  • 箭头函数内部没有自己的 arguments,但是会沿作用域链查找外层函数的 arguments,如果外层有就用外层的。

不妨来看个例子理解一下:

function greet() {
  console.log(arguments); // 类数组对象
  console.log(arguments.length); // 实际传入参数个数
  console.log(arguments[0]);     // 第一个参数
}
greet('Alice', 'Bob'); // 输出: { '0': 'Alice', '1': 'Bob' }, length: 2, 'Alice'

如何将 arguments 转为真数组?

因为 arguments 不是数组,不能直接用数组方法。但是可以将其转换为数组:

方法 1:扩展运算符(ES6+,最简洁)

function fn() {
  const args = [...arguments];
  args.map(x => x * 2); // 可用数组方法
}

方法 2:Array.from()

const args = Array.from(arguments);

方法 3:[].slice.call()

const args = Array.prototype.slice.call(arguments);
// 或简写(更推荐)
const args = [].slice.call(arguments);

slice是数组原型上的一个方法,用于返回一个从原数组或类数组中浅拷贝出来的新数组(实现将类数组转换成数组)

[].slice是因为arguments上没有这个方法,所以需要去空数组中“借用”,并且通过 .call()slicethis指向arguments,变相的让arguments可以使用这个方法。

在了解了arguments后,聪明的你已经想到了如何通过它来实现手写new

手写代码如下

function objectFactory() {
    // 创建空对象
    var obj = new Object(); 
    // 将 arugments 的第一项提出来(也就是 构造函数)
    var Construstor = [].shift.call(arguments);
    // 绑定 this 并执行构造函数
    Construstor.apply(obj, arguments); // 不能使用call,因为 apply调用数组
    // 设置原型链
    obj.__proto__ = Construstor.prototype; 
    return obj;
}

function Person(name, age, city) {
  this.name = name;
  this.age = age;
  this.city = city;
}

const p = objectFactory(Person, 'Alice', 25, 'Beijing'); // 自动适配

每日一题-统计被覆盖的建筑🟡

给你一个正整数 n,表示一个 n x n 的城市,同时给定一个二维数组 buildings,其中 buildings[i] = [x, y] 表示位于坐标 [x, y] 的一个 唯一 建筑。

如果一个建筑在四个方向(左、右、上、下)中每个方向上都至少存在一个建筑,则称该建筑 被覆盖 

返回 被覆盖 的建筑数量。

 

示例 1:

输入: n = 3, buildings = [[1,2],[2,2],[3,2],[2,1],[2,3]]

输出: 1

解释:

  • 只有建筑 [2,2] 被覆盖,因为它在每个方向上都至少存在一个建筑:
    • 上方 ([1,2])
    • 下方 ([3,2])
    • 左方 ([2,1])
    • 右方 ([2,3])
  • 因此,被覆盖的建筑数量是 1。

示例 2:

输入: n = 3, buildings = [[1,1],[1,2],[2,1],[2,2]]

输出: 0

解释:

  • 没有任何一个建筑在每个方向上都有至少一个建筑。

示例 3:

输入: n = 5, buildings = [[1,3],[3,2],[3,3],[3,5],[5,3]]

输出: 1

解释:

  • 只有建筑 [3,3] 被覆盖,因为它在每个方向上至少存在一个建筑:
    • 上方 ([1,3])
    • 下方 ([5,3])
    • 左方 ([3,2])
    • 右方 ([3,5])
  • 因此,被覆盖的建筑数量是 1。

 

提示:

  • 2 <= n <= 105
  • 1 <= buildings.length <= 105
  • buildings[i] = [x, y]
  • 1 <= x, y <= n
  • buildings 中所有坐标均 唯一 

SDWebImage深度解析:高效图片加载背后的架构设计与卓越实践

在移动应用开发中,图片加载和缓存是影响用户体验的关键环节。SDWebImage作为iOS平台上最受欢迎的图片加载库,以其高性能、丰富的功能和稳定的表现赢得了全球开发者的信赖。本文将深入探讨SDWebImage的核心原理、架构设计,并解答实际使用中的常见问题。

一、整体架构设计:模块化与职责分离

为了让你能迅速抓住核心,我们先用一张图,从宏观视角看懂它的工作原理与核心流程。

deepseek_mermaid_20251210_9277be.png

SDWebImage采用了清晰的分层架构设计,将复杂的图片加载过程分解为独立的模块,各司其职:

1. 核心协调者:SDWebImageManager

这是SDWebImage的"大脑",负责协调缓存查找、下载和图片处理流程。它使用组合模式将SDImageCacheSDWebImageDownloader组合在一起,实现了对图片加载全生命周期的管理。

2. 缓存模块:SDImageCache 负责内存和磁盘缓存的双层存储。内存缓存基于NSCache实现,提供快速访问;磁盘缓存将图片持久化存储到文件系统中,支持自定义缓存策略。

3. 下载模块:SDWebImageDownloader 基于NSURLSession构建的异步下载器,支持并发控制、请求优先级设置和身份验证等高级功能。它通过操作队列管理下载任务,确保高效利用网络资源。

4. 解码与处理模块 负责图片解码、缩放、裁剪等后处理操作。SDWebImage在后台线程执行这些操作,避免阻塞主线程。

5. 视图扩展:UIImageView+WebCache 为UIKit组件提供的便捷接口,开发者可以通过一行代码实现图片的异步加载和缓存管理。

这种模块化设计不仅使代码结构清晰,还提高了库的可扩展性和可维护性。

二、图片解码机制:后台线程的高效处理

iOS系统在渲染图片时需要将其解码为位图格式,这个过程默认在主线程进行,可能导致界面卡顿。SDWebImage对此进行了重要优化:

解码时机与线程策略 SDWebImage在图片从磁盘加载或网络下载完成后,立即在后台线程进行解码。解码器使用专门的NSOperationQueue,避免了解码任务阻塞主线程。

空间换时间的缓存策略 解码后的位图数据会被缓存到内存中。当同一图片再次请求时,可以直接使用缓存的解码结果,无需重复解码,显著提升了性能。

渐进式解码支持 对于网络下载的大图片,SDWebImage支持渐进式解码。图片在下载过程中逐步显示,用户可以更快地看到图片内容,提升等待体验。

三、缓存机制:智能的双层存储系统

SDWebImage的缓存系统是其高性能的核心保障:

1. 内存缓存(Memory Cache) 基于NSCache实现,具有自动清理机制。当系统内存紧张时,NSCache会自动释放部分缓存。默认情况下,SDWebImage不限制内存缓存大小,但支持通过totalCostLimitcountLimit进行自定义限制。

2. 磁盘缓存(Disk Cache) 图片以文件形式存储在Cache目录中,文件名经过MD5哈希处理,确保唯一性和安全性。

3. 默认最大缓存大小 SDWebImage默认的磁盘缓存大小为100MB。当缓存超过此限制时,SDWebImage会基于文件的最后访问时间进行清理,优先移除最久未访问的图片。这一设置可以在SDImageCacheConfig中自定义。

四、缓存清理机制:灵活的资源管理

SDWebImage提供了多种缓存清理方式,满足不同场景的需求:

1. 自动清理机制

  • 基于时间的清理:默认配置下,SDWebImage会自动清理超过一周的缓存文件
  • 基于大小的清理:当缓存超过设定的大小时,自动清理最旧的图片文件

2. 手动清理接口

// 清理所有内存缓存
[[SDImageCache sharedImageCache] clearMemory];

// 清理所有磁盘缓存(异步)
[[SDImageCache sharedImageCache] clearDiskOnCompletion:nil];

// 清理过期的磁盘缓存(异步)
[[SDImageCache sharedImageCache] deleteOldFilesWithCompletionBlock:nil];

3. 细粒度控制 开发者可以针对特定URL或key清理缓存,实现更精准的缓存管理。

五、动态图支持:从GIF到现代动画格式

SDWebImage对动态图的支持经历了显著的演进:

早期方案 早期版本通过将GIF分解为帧序列,使用UIImage的动画API播放,这种方式内存占用高且功能有限。

现代方案:SDAnimatedImage协议 从SDWebImage 5.0开始,引入了SDAnimatedImage协议,提供了统一的动画图片接口,支持多种格式:

  • GIF:完整的解码和播放支持
  • APNG:Apple原生支持的动画格式
  • WebP:Google的高效图片格式(需要额外编码器)
  • HEIC:高效的现代图片格式

内存优化 SDAnimatedImageView采用惰性解码策略,仅解码当前显示和预加载的帧,大幅降低了内存占用。开发者还可以通过maxBufferSize属性控制缓冲帧数,在流畅度和内存消耗之间找到平衡。

六、视图可见性触发加载:按需加载的智能策略

在列表等场景中,实现"视图出现在屏幕才开始加载图片"是提升性能的关键。SDWebImage与UIKit的协同工作实现了这一目标:

UITableView/UICollectionView的优化加载

// 在cellForRowAtIndexPath中设置图片
- (UITableViewCell *)tableView:(UITableView *)tableView 
         cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@&#34;Cell&#34;];
    
    // 获取图片URL
    NSURL *imageURL = [self imageURLForIndexPath:indexPath];
    
    // 使用SDWebImage加载图片
    [cell.imageView sd_setImageWithURL:imageURL
                      placeholderImage:[UIImage imageNamed:@&#34;placeholder&#34;]
                             completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, NSURL *imageURL) {
        // 加载完成处理
    }];
    
    return cell;
}

// 在prepareForReuse中取消未完成的加载
- (void)prepareForReuse {
    [super prepareForReuse];
    [self.imageView sd_cancelCurrentImageLoad];
}

工作原理

  1. 当cell准备显示时,tableView:cellForRowAtIndexPath:被调用,开始图片加载
  2. 如果cell滚出屏幕,prepareForReuse会被调用,取消未完成的图片加载
  3. SDWebImage内部会管理加载队列,优先处理可见cell的请求

高级优化技巧

// 1. 预加载:提前加载即将显示的图片
- (void)tableView:(UITableView *)tableView 
  willDisplayCell:(UITableViewCell *)cell 
forRowAtIndexPath:(NSIndexPath *)indexPath {
    // 预加载下一批图片
    if (indexPath.row + 5 < [self.dataSource count]) {
        NSURL *preloadURL = [self imageURLForIndexPath:[NSIndexPath indexPathForRow:indexPath.row + 5 inSection:indexPath.section]];
        [[SDWebImagePrefetcher sharedImagePrefetcher] prefetchURLs:@[preloadURL]];
    }
}

// 2. 设置不同的加载优先级
SDWebImageOptions options = SDWebImageLowPriority | SDWebImageProgressiveLoad;
[cell.imageView sd_setImageWithURL:imageURL placeholderImage:nil options:options];

七、静态图与动态图的选择策略

在实际开发中,我们经常面临选择:使用UIImageView还是SDAnimatedImageView?以下是明确的指导原则:

1. 静态图片场景

  • 使用标准的UIImageView
  • 通过sd_setImageWithURL:方法加载
  • 性能最佳,内存占用最低

2. 动态图片场景

  • 使用SDAnimatedImageView
  • 通过sd_setImageWithURL:方法加载(SDWebImage会自动检测图片类型)
  • 支持GIF、APNG、WebP等多种动态格式

3. 未知图片类型的处理策略 当不确定图片是静态还是动态时,推荐采用以下方案:

// 方案1:统一使用SDAnimatedImageView(推荐)
// 优点:自动适应所有图片类型,代码简洁
// 缺点:对静态图有轻微性能开销
SDAnimatedImageView *imageView = [SDAnimatedImageView new];
[imageView sd_setImageWithURL:imageURL];

// 方案2:根据URL或响应头动态选择
// 在知道图片类型的情况下优化性能
if ([url.pathExtension isEqualToString:@&#34;gif&#34;] || 
    [url.absoluteString containsString:@&#34;animated&#34;]) {
    // 使用SDAnimatedImageView
    SDAnimatedImageView *animatedImageView = [SDAnimatedImageView new];
    [animatedImageView sd_setImageWithURL:url];
} else {
    // 使用普通UIImageView
    UIImageView *staticImageView = [UIImageView new];
    [staticImageView sd_setImageWithURL:url];
}

// 方案3:使用SDWebImage的自动检测功能
// SDWebImage 5.0+ 可以自动检测图片类型并选择合适的视图
UIImageView *imageView = [UIImageView new];
SDWebImageOptions options = SDWebImageAutoHandleAnimatedImage;
[imageView sd_setImageWithURL:url options:options];

性能对比与建议

场景 推荐视图 内存占用 CPU使用 兼容性
已知静态图 UIImageView 最佳
已知动态图 SDAnimatedImageView 中等 中等 最佳
未知类型 SDAnimatedImageView 中等 中等 最佳
大量静态图列表 UIImageView 最佳

最佳实践建议

  1. 对于图片社交应用(如Instagram),用户上传内容类型未知,建议统一使用SDAnimatedImageView
  2. 对于电商应用,商品主图大多是静态图,使用UIImageView即可
  3. 在性能敏感的场景(如大规模图片列表),可以考虑先获取图片元信息再决定视图类型

八、高级功能与定制扩展

自定义缓存策略

// 创建自定义缓存配置
SDImageCacheConfig *config = [SDImageCacheConfig defaultCacheConfig];
config.maxDiskAge = 7 * 24 * 60 * 60; // 一周
config.maxDiskSize = 200 * 1024 * 1024; // 200MB
config.maxMemoryCost = 100 * 1024 * 1024; // 100MB内存缓存
config.diskCacheExpireType = SDImageCacheConfigExpireTypeAccessDate; // 按访问时间过期

// 创建自定义缓存实例
SDImageCache *customCache = [[SDImageCache alloc] initWithNamespace:@&#34;Custom&#34; diskCacheDirectory:customPath config:config];

图片转换器

// 创建圆角图片转换器
SDImageRoundCornerTransformer *transformer = [SDImageRoundCornerTransformer transformerWithRadius:10 corners:UIRectCornerAllCorners borderWidth:1 borderColor:[UIColor whiteColor]];

// 加载时应用转换器
[imageView sd_setImageWithURL:url placeholderImage:nil options:0 context:@{SDWebImageContextImageTransformer: transformer}];

九、性能监控与调试

内存使用监控

// 获取缓存统计信息
NSUInteger memCost = [[SDImageCache sharedImageCache] totalMemoryCost];
NSUInteger diskCount = [[SDImageCache sharedImageCache] totalDiskCount];
NSUInteger diskSize = [[SDImageCache sharedImageCache] totalDiskSize];

// 监控图片加载性能
[SDWebImageManager.sharedManager setCacheKeyFilter:^NSString * _Nullable(NSURL * _Nullable url) {
    // 记录加载时间
    CFTimeInterval startTime = CACurrentMediaTime();
    return [url absoluteString];
}];

十、总结与展望

SDWebImage通过其精良的架构设计,在图片加载的各个关键环节都做了深度优化。从后台解码到智能缓存,从动态图支持到可见性触发加载,每一个设计决策都体现了对性能与用户体验的极致追求。

随着iOS开发技术的演进,SDWebImage也在不断发展。未来,我们期待看到:

  1. 对Swift Concurrency的更好支持
  2. 与SwiftUI的更深度集成
  3. 对新型图片格式的更快适配
  4. 更智能的缓存预取和淘汰算法

无论你是刚刚接触SDWebImage的新手,还是希望深入优化图片加载性能的资深开发者,理解SDWebImage的核心原理都将帮助你构建更流畅、更高效的iOS应用。

Expo 进阶指南:赋予 TanStack Query “原生感知力” —— 深度解析 AppState 与 NetInfo

在 Web 开发中,我们习惯了浏览器的“智能”:切换标签页时页面会自动刷新,断网重连后请求会自动重试。这是因为浏览器底层自动处理了窗口聚焦(Window Focus)和网络状态(Online Status)的事件。

然而,在 React Native (Expo) 环境中,App 默认是“盲”和“聋”的。TanStack Query 不知道用户什么时候把 App 切到了后台,也不知道手机什么时候断了网。

为了让 App 拥有极致的用户体验(如:切回来自动刷新、断网自动暂停重试),我们需要手动配置 “感官系统” 。本文将深入解读 AppStateStatussetOnline,并提供生产环境的完整配置方案。


一、 深度解读:App 的“感官系统”

1. 视觉神经:AppStateStatus (应用状态)

AppStateStatus 是 React Native 原生提供的一个状态类型,用于描述 App 当前处于什么处境。

三个核心状态:

  • active (前台/活跃) :用户正在盯着屏幕,App 在最上层运行。这是我们唯一希望触发数据刷新的状态。
  • background (后台) :用户按了 Home 键,或者切换到了微信。App 还在内存中运行,但界面不可见。
  • inactive (非活跃) :App 处于“半死不活”的过渡态(如被来电画面覆盖、拉下了通知栏、iOS 多任务切换界面)。

为什么要配置它?

TanStack Query 有一个核心功能叫 Window Focus Refetching。

  • 问题:RN 默认没有 window.onfocus 事件。
  • 解决:我们需要监听 AppState 的变化。当状态变为 active 时,手动调用 focusManager.setFocused(true)
  • 效果:TanStack Query 收到信号后,会立即检查页面上的数据是否过期(Stale)。如果过期,它会在后台悄悄发起请求,用户切回来的一瞬间看到的就是最新数据。

2. 听觉神经:setOnline (联网回调)

setOnline 不是一个你可以直接导入的函数,它是 TanStack Query 的 onlineManager.setEventListener 方法传递给你的一个回调函数(参数)。你可以把它理解为 TanStack Query 递给你的一把“开关”。

逻辑流程图:从断网到重连

+---------------------------+
| 📡 物理层                  |
| 手机 4G/WiFi 信号变化       |
+-------------+-------------+
              |
              v (触发系统事件)
+-------------+-------------+
| 🎧 B: NetInfo 监听器       |
+-------------+-------------+
              |
              v (获取状态: isConnected)
+-------------+-------------+
| 💻 C: 开发者代码逻辑        |
| (app/_layout.tsx)         |
+-------------+-------------+
              |
              v (调用 setOnline)
+-------------+-------------+
| ⚙️ D: TanStack Query      |
|     (onlineManager)       |
+-------------+-------------+
       /             \
      / (false)       \ (true)
     v                 v
+---------+       +---------+
| E: 🛑   |       | F: 🚀   |
| 暂停     |       | 恢复    |
| (Pause) |       | (Resume)|
| 停止重试 |       | 立即重试 |
+---------+       +---------+

为什么要配置它?

  • 省电保护:如果没配置,断网时 TanStack Query 会傻傻地一直重试请求,消耗用户电量。配置后,断网即休眠。
  • 体验优化:如果没配置,网络恢复后,App 不会有反应,用户必须手动下拉刷新。配置后,信号恢复的瞬间,数据自动加载成功(“电梯效应”)。

二、 生产环境完整配置 (app/_layout.tsx)

这是融合了上述原理的完整配置代码。请确保你已安装了 @react-native-community/netinfo

import { useEffect } from 'react';
import { AppState, AppStateStatus, Platform } from 'react-native';
import { Slot } from 'expo-router';
import NetInfo from '@react-native-community/netinfo';
import { 
  QueryClient, 
  QueryClientProvider, 
  focusManager, 
  onlineManager 
} from '@tanstack/react-query';

// =================================================================
// 1. 配置网络监听 (给 App 装上“耳朵”)
// =================================================================
onlineManager.setEventListener((setOnline) => {
  // setOnline 是 Query 传进来的一个函数,用来接收网络状态
  // 我们使用 NetInfo 来监听真实的网络变化
  return NetInfo.addEventListener((state) => {
    // state.isConnected 可能为 null,用 !! 强转为 boolean
    // 当这里调用 setOnline(true) 时,Query 会立即重试刚才失败的请求
    setOnline(!!state.isConnected);
  });
});

// =================================================================
// 2. 配置 App 状态监听 (给 App 装上“眼睛”)
// =================================================================
function onAppStateChange(status: AppStateStatus) {
  // Web 浏览器会自动处理窗口聚焦,只有原生 App 需要手动处理
  if (Platform.OS !== 'web') {
    // 核心逻辑:
    // 当 AppState 变为 'active' (前台) 时
    // 我们手动告诉 Query 的 focusManager:“用户现在聚焦在 App 上了”
    // Query 收到信号后,会检查页面数据是否过期 (stale),如果过期则自动 Refetch
    focusManager.setFocused(status === 'active');
  }
}

// =================================================================
// 3. 初始化 QueryClient (配置全局策略)
// =================================================================
const queryClient = new QueryClient({
  defaultOptions: {
    queries: {
      retry: 2,             // 失败后自动重试 2 次
      staleTime: 1000 * 60, // 数据 1 分钟内算“新鲜”,切回来也不刷新
                            // 超过 1 分钟后切回来,会触发自动刷新
    },
  },
});

export default function RootLayout() {
  // 注册 AppState 监听器
  useEffect(() => {
    // 开始监听系统状态变化
    const subscription = AppState.addEventListener('change', onAppStateChange);
    
    // 组件卸载时取消监听,这是防止内存泄漏的标准做法
    return () => subscription.remove();
  }, []);

  return (
    
      {/* Slot 是 Expo Router 的页面入口 */}
      
    
  );
}

三、 总结

在 React Native 中使用 TanStack Query,90% 的人只做了“安装”,而忽略了“配置”

如果不配置这两个管理器,你的 App 就失去了灵魂:

  1. AppStateStatus + focusManager:让 App 知道“我醒了”,从而实现微信切回来的自动刷新。
  2. setOnline + onlineManager:让 App 知道“我有网了”,从而实现走出电梯后的自动重连。

只要在 _layout.tsx 中写好这 50 行代码,你应用里所有的 useQuery 就都自动拥有了这些原生级别的感知能力。这就是从“能用”到“好用”的关键跨越。

从美团全栈化看 AI 冲击:前端转全栈,是自救还是必然 🤔🤔🤔

我正在开发 DocFlow,它是一个完整的 AI 全栈协同文档平台。该项目融合了多个技术栈,包括基于 Tiptap 的富文本编辑器、NestJs 后端服务、AI 集成功能和实时协作。在开发过程中,我积累了丰富的实战经验,涵盖了 Tiptap 的深度定制、性能优化和协作功能的实现等核心难点。

如果你对 AI 全栈开发、Tiptap 富文本编辑器定制或 DocFlow 项目的完整技术方案感兴趣,欢迎加我微信 yunmz777 进行私聊咨询,获取详细的技术分享和最佳实践。

大厂日报 称,美团履约团队近期正在推行"全栈化"转型。据悉,终端组的部分前端同学在 11 月末左右转到了后端组做全栈(前后端代码一起写),主要是 agent 相关项目。内部打听了一下,团子目前全栈开发还相对靠谱,上线把控比较严格。

这一消息在技术圈引起了广泛关注,也反映了 AI 时代下前端工程师向全栈转型的必然趋势。但更重要的是,我们需要深入思考:AI 到底给前端带来了什么冲击?为什么前端转全栈成为了必然选择?

最近,前端圈里不断有"前端已死"的话语流出。有人说 AI 工具会替代前端开发,有人说低代码平台会让前端失业,还有人说前端工程师的价值正在快速下降。这些声音虽然有些极端,但确实反映了 AI 时代前端面临的真实挑战。

一、AI 对前端的冲击:挑战与机遇并存

1. 代码生成能力的冲击

冲击点:

  • 低复杂度页面生成:AI 工具(如 Claude Code、Cursor)已经能够快速生成常见的 UI 组件、页面布局
  • 重复性工作被替代:表单、列表、详情页等标准化页面,AI 生成效率远超人工
  • 学习门槛降低:新手借助 AI 也能快速产出基础代码,前端"入门红利"消失

影响: 传统前端开发中,大量时间花在"写页面"上。AI 的出现,让这部分工作变得极其高效,甚至可以说,只会写页面的前端工程师,价值正在快速下降。这也正是"前端已死"论调的主要依据之一。

2. 业务逻辑前移的冲击

冲击点:

  • AI Agent 项目激增:如美团案例中的 agent 相关项目,需要前后端一体化开发
  • 实时交互需求:AI 应用的流式响应、实时对话,要求前后端紧密配合
  • 数据流转复杂化:AI 模型调用、数据处理、状态管理,都需要全栈视角

影响: 纯前端工程师在 AI 项目中往往只能负责 UI 层,无法深入业务逻辑。而 AI 项目的核心价值在于业务逻辑和数据处理,这恰恰是后端能力。

3. 技术栈边界的模糊

冲击点:

  • 前后端一体化趋势:Next.js、Remix 等全栈框架兴起,前后端代码同仓库
  • Serverless 架构:边缘函数、API 路由,前端开发者需要理解后端逻辑
  • AI 服务集成:调用 AI API、处理流式数据、管理状态,都需要后端知识

影响: 前端和后端的边界正在消失。只会前端的前端工程师,在 AI 时代会发现自己"够不着"核心业务。

4. 职业发展的天花板

冲击点:

  • 技术深度要求:AI 项目需要理解数据流、算法逻辑、系统架构
  • 业务理解能力:全栈开发者能更好地理解业务全貌,做出技术决策
  • 团队协作效率:全栈开发者减少前后端沟通成本,提升交付效率

影响: 在 AI 时代,只会前端的前端工程师,职业天花板明显。而全栈开发者能够:

  • 独立负责完整功能模块
  • 深入理解业务逻辑
  • 在技术决策中发挥更大作用

二、为什么前端转全栈是必然选择?

1. AI 项目的本质需求

正如美团案例所示,AI 项目(特别是 Agent 项目)的特点:

  • 前后端代码一起写:业务逻辑复杂,需要前后端协同
  • 数据流处理:AI 模型的输入输出、流式响应处理
  • 状态管理复杂:对话状态、上下文管理、错误处理

这些需求,纯前端工程师无法独立完成,必须掌握后端能力。

2. 技术发展的趋势

  • 全栈框架普及:Next.js、Remix、SvelteKit 等,都在推动全栈开发
  • 边缘计算兴起:Cloudflare Workers、Vercel Edge Functions,前端需要写后端逻辑
  • 微前端 + 微服务:前后端一体化部署,降低系统复杂度

3. 市场需求的转变

  • 招聘要求变化:越来越多的岗位要求"全栈能力"
  • 项目交付效率:全栈开发者能独立交付功能,减少沟通成本
  • 技术决策能力:全栈开发者能更好地评估技术方案

三、后端技术栈的选择:Node.js、Python、Go

对于前端转全栈,后端技术栈的选择至关重要。不同技术栈有不同优势,需要根据项目需求选择。

1. Node.js + Nest.js:前端转全栈的最佳起点

优势:

  • 零语言切换:JavaScript/TypeScript 前后端通用
  • 生态统一:npm 包前后端共享,工具链一致
  • 学习成本低:利用现有技能,快速上手
  • AI 集成友好:LangChain.js、OpenAI SDK 等完善支持

适用场景:

  • Web 应用后端
  • 实时应用(WebSocket、SSE)
  • 微服务架构
  • AI Agent 项目(如美团案例)

学习路径:

  1. Node.js 基础(事件循环、模块系统)
  2. Nest.js 框架(模块化、依赖注入)
  3. 数据库集成(TypeORM、Prisma)
  4. AI 服务集成(OpenAI、流式处理)

2. Python + FastAPI:AI 项目的首选

优势:

  • AI 生态最完善:OpenAI、LangChain、LlamaIndex 等原生支持
  • 数据科学能力:NumPy、Pandas 等数据处理库
  • 快速开发:语法简洁,开发效率高
  • 模型部署:TensorFlow、PyTorch 等模型框架

适用场景:

  • AI/ML 项目
  • 数据分析后端
  • 科学计算服务
  • Agent 项目(需要复杂 AI 逻辑)

学习路径:

  1. Python 基础(语法、数据结构)
  2. FastAPI 框架(异步、类型提示)
  3. AI 库集成(OpenAI、LangChain)
  4. 数据处理(Pandas、NumPy)

3. Go:高性能场景的选择

优势:

  • 性能优秀:编译型语言,执行效率高
  • 并发能力强:Goroutine 并发模型
  • 部署简单:单文件部署,资源占用少
  • 云原生友好:Docker、Kubernetes 生态完善

适用场景:

  • 高并发服务
  • 微服务架构
  • 云原生应用
  • 性能敏感场景

学习路径:

  1. Go 基础(语法、并发模型)
  2. Web 框架(Gin、Echo)
  3. 数据库操作(GORM)
  4. 微服务开发

4. 技术栈选择建议

对于前端转全栈的开发者:

  1. 首选 Node.js:如果目标是快速转全栈,Node.js 是最佳选择

    • 学习成本最低
    • 前后端代码复用
    • 适合大多数 Web 应用
  2. 考虑 Python:如果专注 AI 项目

    • AI 生态最完善
    • 适合复杂 AI 逻辑
    • 数据科学能力
  3. 学习 Go:如果追求性能

    • 高并发场景
    • 微服务架构
    • 云原生应用

建议:

  • 第一阶段:选择 Node.js,快速转全栈
  • 第二阶段:根据项目需求,学习 Python 或 Go
  • 长期目标:掌握多种技术栈,根据场景选择

四、总结

AI 时代的到来,给前端带来了深刻冲击:

  1. 代码生成能力:低复杂度页面生成被 AI 替代
  2. 业务逻辑前移:AI 项目需要前后端一体化
  3. 技术边界模糊:前后端边界正在消失
  4. 职业天花板:只会前端的前端工程师,发展受限

前端转全栈,是 AI 时代的必然选择。

对于技术栈选择:

  • Node.js:前端转全栈的最佳起点,学习成本低
  • Python:AI 项目的首选,生态完善
  • Go:高性能场景的选择,云原生友好

正如美团的全栈化实践所示,全栈开发还相对靠谱,关键在于:

  • 选择合适的技术栈
  • 建立严格的开发流程
  • 持续学习和实践

对于前端开发者来说,AI 时代既是挑战,也是机遇。转全栈,不仅能应对 AI 冲击,更能打开职业发展的新空间。那些"前端已死"的声音,其实是在提醒我们:只有不断进化,才能在这个时代立足。

求求你试试 DiffableDataSource!别再手算 indexPath 了(否则迟早崩)

求求你试试 DiffableDataSource!别再手算 indexPath 了(否则迟早崩)

你是不是也遇到过这些问题:

  • insertRows 一插就崩:Invalid update: invalid number of rows
  • 多 section 一更新就乱:indexPath 不匹配
  • 想做动画很麻烦:beginUpdates + performBatchUpdates
  • 更新某一条会闪烁:reloadData()
  • 复杂场景(聊天流、瀑布流、Feed 流)代码写到怀疑人生

这些问题的本质是:

你在手动维护 UI 和数据的同步,而 TableView/CollectionView 的 index 一旦不一致,就会瞬间把你崩回桌面。

但自从 iOS 13 开始,Apple 已经给了我们一个“几乎不会崩”的方案:

DiffableDataSource


为什么要用 DiffableDataSource?

一句话:

你只管“数据最终长什么样”,UI 自动算出该怎么更新。

它的三大优势:

  1. 不再维护 indexPath
    Diffable 不依赖 index,所有操作基于 item 唯一标识(Hashable),避免大部分 crash。
  2. 动画自动处理
    插入、删除、移动、局部更新都自动生成动画,不再写 batchUpdates
  3. 复杂列表场景刚需
    多 section、聊天流、Feed、搜索、瀑布流……传统方式写起来代码膨胀,Diffable 轻松搞定。

传统 DataSource 容易崩的案例

假设你有一个 users: [User] 的数据源,传统做法:

users.insert(user, at: index)
tableView.insertRows(at: [IndexPath(row: index, section: 0)], with: .automatic)

问题

  • 异步修改数据时,index 一不对齐就崩
  • 多次 insert/delete 后,indexPath 不匹配
  • batchUpdates 太复杂,容易出错

经典报错:

Invalid update: invalid number of rows in section 0


DiffableDataSource 的安全写法

核心思想:操作 item 标识符,系统自动计算差异并更新 UI

数据模型

struct User: Hashable {
    let id = UUID()
    var name: String
}

Section

enum Section {
    case main
}

DataSource 初始化

class ViewController: UIViewController {

    var tableView: UITableView!
    var dataSource: UITableViewDiffableDataSource<section>!
    var users: [User] = []

    override func viewDidLoad() {
        super.viewDidLoad()

        tableView = UITableView(frame: view.bounds, style: .plain)
        tableView.register(UITableViewCell.self, forCellReuseIdentifier: &#34;cell&#34;)
        view.addSubview(tableView)

        dataSource = UITableViewDiffableDataSource<section>(tableView: tableView) { tableView, indexPath, user in
            let cell = tableView.dequeueReusableCell(withIdentifier: &#34;cell&#34;, for: indexPath)
            cell.textLabel?.text = user.name
            return cell
        }

        applySnapshot(animated: false)
    }
}

Snapshot 封装

func applySnapshot(animated: Bool = true) {
    var snapshot = NSDiffableDataSourceSnapshot<section>()
    snapshot.appendSections([.main])
    snapshot.appendItems(users)
    dataSource.apply(snapshot, animatingDifferences: animated)
}

Diffable 常用操作示例

1. 插入某下标

func insertUser(_ user: User, atIndex index: Int) {
    var snapshot = dataSource.snapshot()
    var items = snapshot.itemIdentifiers(inSection: .main)
    let safeIndex = max(0, min(index, items.count))

    if safeIndex == items.count {
        snapshot.appendItems([user], toSection: .main)
    } else {
        let before = items[safeIndex]
        snapshot.insertItems([user], beforeItem: before)
    }

    users.insert(user, at: safeIndex)
    dataSource.apply(snapshot, animatingDifferences: true)
}

2. 删除某下标

func deleteUser(atIndex index: Int) {
    var snapshot = dataSource.snapshot()
    let items = snapshot.itemIdentifiers(inSection: .main)
    guard items.indices.contains(index) else { return }

    let itemToDelete = items[index]
    snapshot.deleteItems([itemToDelete])
    users.remove(at: index)

    dataSource.apply(snapshot, animatingDifferences: true)
}

3. 移动 item

func moveItem(from fromIndex: Int, to toIndex: Int) {
    var snapshot = dataSource.snapshot()
    var items = snapshot.itemIdentifiers(inSection: .main)
    guard items.indices.contains(fromIndex),
          items.indices.contains(toIndex) else { return }

    let item = items.remove(at: fromIndex)
    items.insert(item, at: toIndex)

    snapshot.deleteSections([.main])
    snapshot.appendSections([.main])
    snapshot.appendItems(items, toSection: .main)

    let moved = users.remove(at: fromIndex)
    users.insert(moved, at: toIndex)

    dataSource.apply(snapshot, animatingDifferences: true)
}

4. 更新 item 的字段(安全写法)

func updateUserName(atIndex index: Int, newName: String) {
    var snapshot = dataSource.snapshot()
    let items = snapshot.itemIdentifiers(inSection: .main)
    guard items.indices.contains(index) else { return }

    let oldItem = items[index]
    let updatedItem = User(id: oldItem.id, name: newName)

    // 更新本地 users 数组
    users[index] = updatedItem

    // 判断是否有下一个 item 可作为插入参照
    if index + 1 < items.count {
        let nextItem = items[index + 1]
        snapshot.deleteItems([oldItem])
        snapshot.insertItems([updatedItem], beforeItem: nextItem)
    } else {
        // 如果是最后一个,直接删除再 append
        snapshot.deleteItems([oldItem])
        snapshot.appendItems([updatedItem], toSection: .main)
    }

    dataSource.apply(snapshot, animatingDifferences: true)
}

✅ 不依赖自定义 safe 下标
✅ 自动处理最后一个 item
✅ 保持 id 不变,动画安全


总结

DiffableDataSource 的核心优势:

  • 不再维护 indexPath → 避免崩溃
  • 动画自动生成 → 插入/删除/移动/更新一气呵成
  • 复杂场景稳如老狗 → 多 section、Feed、聊天、搜索、瀑布流都轻松

一句话:Diffable 是 2025 年 iOS 列表开发的标配。
不用它,你会花大量时间调 index;
用了它,你会怀疑自己以前为什么受苦。


IOS开发SwiftUI相关学习记录

UI布局

  • Swift开发不使用StoryBoard和xib来进行UI布置,属性和事件也不需要连线。没有单独的UIViewController的概念。是使用SwiftUI,声明式布局。

一个最简单的布局

struct ContentView:View {
    var body: some View {
        Text(&#34;hello word&#34;)
    }
}
  • ContentView 是一个结构体,它遵循了 View 协议。
  • 协议唯一的要求是提供一个 body 计算属性,其类型是 some View(某种视图)。
  • body 描述了 ContentView 这个视图具体由什么构成(这里是一个hello word文本)。

iOS中的路由 NavigationView

// 导航容器
NavigationView {
    // 根视图
    VStack {
        NavigationLink(&#34;跳转到详情页&#34;, destination: DetailView())
    }
    .navigationTitle(&#34;首页&#34;) // 导航栏标题
    .navigationBarTitleDisplayMode(.inline) // 标题显示模式(large/inline/automatic)
    .navigationBarItems(trailing: Button(&#34;设置&#34;) {
        print(&#34;点击设置&#34;)
    }) // 右侧按钮
}
  • 这里的路由很简单,利用NavigationView包含根视图。利用NavigationLink,进行点击跳转到目标视图。

视图的三大支柱

属性

  • 属性:存储视图的状态与数据
struct GreetingView: View {
   let name: String // 传入的常量属性
   @State private var isOn = false // 私有的可变状态
   
   var body: some View { ... }
}
  1. 用常规属性(如 let name)存储传入的、不变的数据
  2. 用 @State@Binding@ObservedObject 等属性包装器来管理可变状态,这是 SwiftUI 数据驱动的核心。

修饰符

  • 修饰符:修改视图的外观与行为
Text(&#34;示例&#34;)
    .font(.headline) // 修改字体
    .padding()       // 添加内边距
    .background(.yellow) // 设置背景
    .onTapGesture {  // 添加交互手势
        print(&#34;被点击&#34;)
    }
  1. 链式调用,顺序有时会影响效果。
  2. 每个修饰符(如 .font.padding)通常会返回一个新的视图,而非修改原视图。

视图构建

  • 视图构建器:组合多个视图

body 中使用特定的语法(由 @ViewBuilder 驱动)来组合视图:

var body: some View {
    VStack { // 垂直堆叠多个子视图
        Image(systemName: &#34;star&#34;)
        Text(&#34;标题&#34;)
        HStack { // 内嵌一个水平堆叠
            Text(&#34;左&#34;)
            Text(&#34;右&#34;)
        }
    }
}
  1. 常用的容器视图:VStack(垂直)、HStack(水平)、ZStack(重叠)、List(列表)、Group(逻辑分组)。

SwiftUI交互事件

SwiftUI中处理点击事件主要有Button控件手势修饰符两种核心方式。为帮助你快速选择,下表汇总了它们的特点和典型用途:

方法 核心组件/修饰符 主要特点 适用场景
控件触发 Button 语义化控件,内置交互样式(如按压效果) 按钮、明确的用户操作
手势识别 onTapGesture 通用点击检测,可加在任何视图上 自定义视图、图片、文本等非按钮元素的点击
手势识别 TapGesture 更灵活的手势配置,可组合使用 需要与其它手势(如长按)配合的场景

方法一:使用Button控件

Button 是用于触发操作的标准控件,使用 action 闭包来处理点击事件。你可以方便地自定义其外观。

Button(action: {
    // 点击后执行的操作
    print(&#34;按钮被点击&#34;)
}) {
    // 定义按钮外观
    Text(&#34;点击我&#34;)
        .padding()
        .background(Color.blue)
        .foregroundColor(.white)
        .cornerRadius(10)
}

如果你想以编程方式触发按钮的点击事件(例如在一定时间后自动点击),可以直接调用该按钮action闭包中的逻辑。

方法二:使用手势修饰符

1. 使用 onTapGesture 修饰符 这是为任何视图(如TextImage)添加点击监听最快捷的方式。

Text(&#34;点击这段文字&#34;)
    .onTapGesture {
        print(&#34;文字被点击&#34;)
    }

2. 使用 TapGesture 手势类型 它比onTapGesture更灵活,允许你使用 count 参数来监听双击或多击事件。

Text(&#34;双击我&#34;)
    .gesture(
        TapGesture(count: 2)
            .onEnded { _ in
                print(&#34;检测到双击&#34;)
            }
    )

方法三:

  • 用NavigationLink包装的组件,可以直接跳转至目标页。
  • 用Link包装的组件,可直接跳转至目标网址。

进阶技巧与常见问题

掌握了基本用法后,了解以下技巧能帮你解决更复杂的需求:

  • 控制按钮点击频率:通过disabled(_:)修饰符和状态变量,可以防止按钮在短时间内被重复提交。
  • 在动态列表(ForEach)中处理点击:关键在于确保数据模型(如@State数组)是可变的,这样点击后更新数据,视图才会随之刷新。
  • 处理手势冲突:当多个手势重叠时,可以使用 highPriorityGesture()simultaneousGesture() 来管理优先级或允许同时识别。
  • 高级手势:除了点击,SwiftUI还内置了LongPressGesture(长按)、DragGesture(拖拽)等,可通过.gesture()修饰符使用。

实际开发注意事项

在应用中处理点击事件时,还需要留意两点:

  • 视图层次影响:如果父视图有手势,可能会被子视图拦截。确保手势添加在了正确的视图层级上。
  • 状态管理:点击操作常伴随界面变化(如颜色、显示内容)。务必使用@State@ObservedObject等将相关数据声明为响应式,这样视图才会自动更新。

Link 控件解析

Link 控件解析

Link(&#34;lil.software&#34;, destination: URL(string: &#34;https://lil.software&#34;)!)
  • 第一个参数 &#34;lil.software&#34;:是用户在界面上看到的可点击文本。
  • 第二个参数 destination:指定点击后要跳转的目标网址(URL)。这里是 https://lil.software

用户点击蓝色、带下划线的 “lil.software” 文字后,系统会自动打开 Safari 浏览器并跳转到这个网站。

Link 与 Button 的核心区别

虽然看起来像按钮,但 LinkButton 有明确分工:

控件 核心用途 系统行为 默认样式
Link 专用于打开本地/网络URL 跳转 Safari 或相应 App 蓝色文字,带下划线
Button 触发应用内任意操作 执行你定义的代码(如弹窗、导航) 无默认样式,需完全自定义

简单来说Link = 专用于“跳转出去”的快捷工具;Button = 处理“内部事务”的通用触发器。

如何自定义 Link 样式

Button 一样,你也可以用修饰符来改变 Link 的外观,让它更符合你的应用设计:

Link(&#34;访问官网&#34;, destination: URL(string: &#34;https://www.example.com&#34;)!)
    .font(.headline)
    .foregroundColor(.white)
    .padding()
    .background(Color.orange)
    .cornerRadius(8)
// 这样它就看起来像一个圆角橙色按钮,但点击功能仍是打开网页

使用提示

  1. 确保 URL 有效:如果提供的 URL(string:) 初始化失败(比如链接格式错误),Link 在点击时可能不会有任何反应。
  2. 平台差异:在 iOS/iPadOS 上点击会跳转至 Safari;在 macOS 上会使用默认浏览器打开。

状态管理

一个最小的状态管理实例:

import SwiftUI

struct CounterView: View {
    // 1. 使用 @State 创建可观察的状态
    @State private var count: Int = 0
    
    var body: some View {
        VStack(spacing: 20) {
            // 2. 显示状态值
            Text(&#34;点击次数: \(count)&#34;)
                .font(.largeTitle)
            
            // 3. 按钮修改状态值
            Button(&#34;点我增加&#34;) {
                // 修改状态,视图会自动更新
                count += 1
            }
            .padding()
            .background(Color.blue)
            .foregroundColor(.white)
            .cornerRadius(10)
            
            // 4. 另一个按钮重置状态
            Button(&#34;重置&#34;) {
                count = 0
            }
            .foregroundColor(.red)
        }
        .padding()
    }
}

// 预览
#Preview {
    CounterView()
}

Swift动画相关

SwiftUI 的动画是声明式状态驱动的。与直接描述动画过程不同,你只需声明视图的最终状态,SwiftUI 会自动计算并渲染出平滑的过渡效果。

核心概念:隐式动画 vs. 显式动画

类型 使用方法 特点 适用场景
隐式动画 .animation(_:) 修饰符 自动为指定视图的所有合格变化添加动画 视图的简单属性变化(如缩放、颜色)。
显式动画 withAnimation { } 闭包 明确地包裹触发状态变化的代码,作用范围更精确。 响应事件(如按钮点击),需要同步动画多个视图。

隐式动画示例

在视图上添加 .animation 修饰符,该视图所有可动画的变化都会生效。

struct ImplicitAnimationView: View {
    @State private var isScaled = false
    @State private var angle: Double = 0

    var body: some View {
        VStack(spacing: 30) {
            // 1. 缩放动画
            Circle()
                .fill(isScaled ? .orange : .blue)
                .frame(width: isScaled ? 150 : 100, 
                       height: isScaled ? 150 : 100)
                .scaleEffect(isScaled ? 1.5 : 1.0)
                .animation(.spring(response: 0.3, dampingFraction: 0.6), 
                           value: isScaled) // 指定监听 isScaled 变化
            
            // 2. 旋转动画
            Rectangle()
                .fill(.green)
                .frame(width: 100, height: 100)
                .rotationEffect(.degrees(angle))
                .animation(.linear(duration: 2), value: angle) // 线性旋转
            
            // 3. 控制按钮
            Button(&#34;触发动画&#34;) {
                // 改变状态,视图会自动产生动画
                isScaled.toggle()
                angle += 180
            }
            .padding()
            .background(Color.blue)
            .foregroundColor(.white)
            .cornerRadius(10)
        }
        .padding()
    }
}

显式动画示例

使用 withAnimation 函数包裹状态变化的代码,可以更精确地控制。

struct ExplicitAnimationView: View {
    @State private var isExpanded = false
    @State private var offsetX: CGFloat = 0
    
    var body: some View {
        VStack(spacing: 40) {
            // 1. 多个视图同步动画
            RoundedRectangle(cornerRadius: isExpanded ? 50 : 10)
                .fill(isExpanded ? .purple : .pink)
                .frame(width: isExpanded ? 300 : 100, 
                       height: isExpanded ? 300 : 100)
                .offset(x: offsetX)
                .animation(.easeInOut(duration: 0.6), value: isExpanded)
            
            HStack(spacing: 20) {
                Button(&#34;展开并右移&#34;) {
                    // 用一个动画闭包控制两个状态变化
                    withAnimation(.spring(dampingFraction: 0.6)) {
                        isExpanded = true
                        offsetX = 100
                    }
                }
                
                Button(&#34;重置&#34;) {
                    // 这个重置操作也有动画
                    withAnimation(.easeOut(duration: 0.8)) {
                        isExpanded = false
                        offsetX = 0
                    }
                }
                .tint(.red)
            }
            .buttonStyle(.borderedProminent)
        }
        .padding()
    }
}

转场动画

当视图插入或移除视图层次时,使用 .transition 指定动画效果。

struct TransitionView: View {
    @State private var showMessage = false
    
    var body: some View {
        VStack {
            Button(showMessage ? &#34;隐藏消息&#34; : &#34;显示消息&#34;) {
                withAnimation(.easeInOut(duration: 0.8)) {
                    showMessage.toggle()
                }
            }
            .padding()
            
            if showMessage {
                Text(&#34;你好,SwiftUI!&#34;)
                    .font(.title)
                    .padding()
                    .background(Color.yellow)
                    .cornerRadius(10)
                    .transition(
                        .asymmetric( // 进入和退出使用不同动画
                            insertion: .scale.combined(with: .opacity), // 进入:缩放+淡入
                            removal: .move(edge: .leading).combined(with: .opacity) // 退出:向左滑出+淡出
                        )
                    )
            }
            
            Spacer()
        }
        .padding()
    }
}

动画曲线与时长预设

SwiftUI 提供多种内置动画曲线:

VStack(spacing: 20) {
    // 1. 基础缓动曲线
    Circle()
        .animation(.easeIn(duration: 1), value: isAnimated) // 先慢后快
    Circle()
        .animation(.easeOut(duration: 1), value: isAnimated) // 先快后慢
    Circle()
        .animation(.easeInOut(duration: 1), value: isAnimated) // 慢-快-慢
    
    // 2. 弹性动画
    Circle()
        .animation(.spring(
            response: 0.5,    // 动画持续时间 (seconds)
            dampingFraction: 0.6, // 阻尼:越小弹力越强 (0-1)
            blendDuration: 0.25 // 混合时间
        ), value: isAnimated)
    
    // 3. 重复动画
    Circle()
        .animation(
            .linear(duration: 1)
            .repeatForever(autoreverses: true), // 永久重复且自动反向
            value: isAnimated
        )
    
    // 4. 延迟动画
    Circle()
        .animation(
            .easeInOut(duration: 1)
            .delay(0.5), // 延迟 0.5 秒执行
            value: isAnimated
        )
}

实际应用:加载动画

一个实用的加载指示器动画:

struct LoadingAnimationView: View {
    @State private var isLoading = false
    @State private var progress: CGFloat = 0.0
    
    var body: some View {
        VStack(spacing: 40) {
            // 1. 旋转加载指示器
            Circle()
                .trim(from: 0, to: 0.7) // 剪裁出缺口
                .stroke(Color.blue, lineWidth: 5)
                .frame(width: 50, height: 50)
                .rotationEffect(Angle(degrees: isLoading ? 360 : 0))
                .animation(
                    .linear(duration: 1)
                    .repeatForever(autoreverses: false),
                    value: isLoading
                )
            
            // 2. 进度条动画
            VStack {
                GeometryReader { geometry in
                    ZStack(alignment: .leading) {
                        Rectangle()
                            .frame(width: geometry.size.width, height: 8)
                            .opacity(0.3)
                            .foregroundColor(.gray)
                        
                        Rectangle()
                            .frame(
                                width: min(progress * geometry.size.width, 
                                         geometry.size.width),
                                height: 8
                            )
                            .foregroundColor(.blue)
                            .animation(.linear(duration: 0.5), value: progress)
                    }
                    .cornerRadius(4)
                }
                .frame(height: 20)
                
                Text(&#34;\(Int(progress * 100))%&#34;)
                    .font(.caption)
            }
            .frame(width: 200)
            
            // 3. 控制按钮
            Button(isLoading ? &#34;停止加载&#34; : &#34;开始加载&#34;) {
                if isLoading {
                    stopLoading()
                } else {
                    startLoading()
                }
            }
            .buttonStyle(.borderedProminent)
        }
        .onAppear {
            startLoading()
        }
    }
    
    func startLoading() {
        isLoading = true
        progress = 0
        // 模拟进度更新
        Timer.scheduledTimer(withTimeInterval: 0.1, repeats: true) { timer in
            if progress >= 1.0 {
                timer.invalidate()
                isLoading = false
            } else {
                withAnimation(.linear(duration: 0.1)) {
                    progress += 0.05
                }
            }
        }
    }
    
    func stopLoading() {
        isLoading = false
    }
}

动画最佳实践

  1. 明确动画依赖值:使用 .animation(.easeInOut, value: someState) 指定动画监听的状态,避免不必要的动画。
  2. 性能优先:优先动画简单属性(位置、大小、透明度、旋转),复杂的形状路径动画可能影响性能。
  3. 组合使用:将 .transition.animation 结合,创建更丰富的视图层级变化效果。
  4. 测试中断:确保用户能随时中断动画(如快速点击),避免界面“卡死”。

Swift网络请求相关

Swift 最常用的网络请求框架是 Alamofire(第三方)和 URLSession(苹果官方)。以下是它们的特点对比和简单用法:

框架对比

框架 类型 特点 适合场景
Alamofire 第三方框架 语法优雅、功能丰富、社区活跃 快速开发、复杂网络需求
URLSession 苹果官方 无需依赖、轻量可控、安全可靠 简单需求、不想引入第三方库

Alamofire(最流行)

安装依赖(Swift Package Manager)

在 Xcode 项目中:

  1. File → Add Packages...
  2. 输入 URL:https://github.com/Alamofire/Alamofire.git
  3. 选择版本规则(推荐 "Up to Next Major")
  4. 点击 Add Package

简单使用示例

import Alamofire

// 1. 基础 GET 请求
func fetchDataWithAlamofire() {
    AF.request(&#34;https://jsonplaceholder.typicode.com/posts/1&#34;)
        .responseJSON { response in
            switch response.result {
            case .success(let value):
                print(&#34;请求成功: \(value)&#34;)
            case .failure(let error):
                print(&#34;请求失败: \(error)&#34;)
            }
        }
}

// 2. 带参数的 GET 请求
func fetchDataWithParameters() {
    let parameters = [&#34;userId&#34;: &#34;1&#34;]
    
    AF.request(&#34;https://jsonplaceholder.typicode.com/posts&#34;,
               parameters: parameters)
        .responseDecodable(of: [Post].self) { response in
            switch response.result {
            case .success(let posts):
                print(&#34;获取到 \(posts.count) 条帖子&#34;)
            case .failure(let error):
                print(&#34;错误: \(error)&#34;)
            }
        }
}

// 3. POST 请求
func postData() {
    let parameters = [
        &#34;title&#34;: &#34;测试标题&#34;,
        &#34;body&#34;: &#34;测试内容&#34;,
        &#34;userId&#34;: 1
    ] as [String : Any]
    
    AF.request(&#34;https://jsonplaceholder.typicode.com/posts&#34;,
               method: .post,
               parameters: parameters,
               encoding: JSONEncoding.default)
        .responseJSON { response in
            print(&#34;POST 响应: \(response)&#34;)
        }
}

// 4. 配合 Codable 模型
struct Post: Codable {
    let id: Int?
    let title: String
    let body: String
    let userId: Int
}

func fetchDecodableData() {
    AF.request(&#34;https://jsonplaceholder.typicode.com/posts/1&#34;)
        .responseDecodable(of: Post.self) { response in
            if let post = response.value {
                print(&#34;帖子标题: \(post.title)&#34;)
            }
        }
}

URLSession(苹果官方,无需依赖)

简单使用示例

import Foundation

// 1. 基础 GET 请求
func fetchDataWithURLSession() {
    guard let url = URL(string: &#34;https://jsonplaceholder.typicode.com/posts/1&#34;) else {
        return
    }
    
    let task = URLSession.shared.dataTask(with: url) { data, response, error in
        // 确保在主线程更新 UI
        DispatchQueue.main.async {
            if let error = error {
                print(&#34;请求失败: \(error)&#34;)
                return
            }
            
            guard let data = data else {
                print(&#34;没有数据&#34;)
                return
            }
            
            do {
                // 解析 JSON
                let json = try JSONSerialization.jsonObject(with: data, options: [])
                print(&#34;请求成功: \(json)&#34;)
            } catch {
                print(&#34;JSON 解析失败: \(error)&#34;)
            }
        }
    }
    
    task.resume() // 开始请求
}

// 2. 配合 Codable 的改进版本
func fetchDataWithCodable() {
    guard let url = URL(string: &#34;https://jsonplaceholder.typicode.com/posts/1&#34;) else {
        return
    }
    
    let task = URLSession.shared.dataTask(with: url) { data, response, error in
        DispatchQueue.main.async {
            if let error = error {
                print(&#34;错误: \(error)&#34;)
                return
            }
            
            guard let data = data else {
                print(&#34;没有数据&#34;)
                return
            }
            
            do {
                // 使用 JSONDecoder 解码到模型
                let decoder = JSONDecoder()
                let post = try decoder.decode(Post.self, from: data)
                print(&#34;获取到帖子: \(post.title)&#34;)
            } catch {
                print(&#34;解码失败: \(error)&#34;)
            }
        }
    }
    
    task.resume()
}

// 3. POST 请求
func postWithURLSession() {
    guard let url = URL(string: &#34;https://jsonplaceholder.typicode.com/posts&#34;) else {
        return
    }
    
    var request = URLRequest(url: url)
    request.httpMethod = &#34;POST&#34;
    request.setValue(&#34;application/json&#34;, forHTTPHeaderField: &#34;Content-Type&#34;)
    
    let body = [
        &#34;title&#34;: &#34;测试标题&#34;,
        &#34;body&#34;: &#34;测试内容&#34;,
        &#34;userId&#34;: 1
    ] as [String: Any]
    
    do {
        request.httpBody = try JSONSerialization.data(withJSONObject: body, options: [])
    } catch {
        print(&#34;创建请求体失败: \(error)&#34;)
        return
    }
    
    let task = URLSession.shared.dataTask(with: request) { data, response, error in
        DispatchQueue.main.async {
            // 处理响应...
        }
    }
    
    task.resume()
}

网络层封装示例(实用版)

对于真实项目,建议进行简单封装:

import Alamofire

class NetworkManager {
    static let shared = NetworkManager()
    private init() {}
    
    // 通用请求方法
    func request(
        _ url: String,
        method: HTTPMethod = .get,
        parameters: Parameters? = nil,
        completion: @escaping (Result) -> Void
    ) {
        AF.request(url,
                   method: method,
                   parameters: parameters,
                   encoding: JSONEncoding.default)
            .validate() // 验证响应状态码
            .responseDecodable(of: T.self) { response in
                switch response.result {
                case .success(let value):
                    completion(.success(value))
                case .failure(let error):
                    completion(.failure(error))
                }
            }
    }
}

// 使用封装后的方法
func fetchUserData() {
    NetworkManager.shared.request(
        &#34;https://jsonplaceholder.typicode.com/users/1&#34;
    ) { (result: Result) in
        switch result {
        case .success(let user):
            print(&#34;用户: \(user.name)&#34;)
        case .failure(let error):
            print(&#34;错误: \(error)&#34;)
        }
    }
}

struct User: Codable {
    let id: Int
    let name: String
    let email: String
}

选择建议

  1. 新手或简单项目:从 URLSession 开始,理解基础原理
  2. 生产环境或复杂需求:使用 Alamofire,提升开发效率
  3. 需要高级功能:Alamofire 支持:
    • 请求/响应拦截器
    • 网络状态监听
    • 自动重试
    • 文件上传/下载进度
    • 证书验证

实际项目使用技巧

Alamofire 进阶用法:

// 添加请求头
let headers: HTTPHeaders = [
    &#34;Authorization&#34;: &#34;Bearer token123&#34;,
    &#34;Accept&#34;: &#34;application/json&#34;
]

AF.request(url, headers: headers).responseJSON { response in
    // ...
}

// 上传图片
AF.upload(multipartFormData: { multipartFormData in
    if let imageData = image.jpegData(compressionQuality: 0.8) {
        multipartFormData.append(imageData, 
                                 withName: &#34;image&#34;, 
                                 fileName: &#34;photo.jpg&#34;, 
                                 mimeType: &#34;image/jpeg&#34;)
    }
}, to: &#34;https://api.example.com/upload&#34;).responseJSON { response in
    // 处理上传结果
}

错误处理增强:

enum NetworkError: Error {
    case invalidURL
    case noData
    case decodingError
    case serverError(String)
}

func handleNetworkError(_ error: AFError) -> NetworkError {
    if error.isResponseSerializationError {
        return .decodingError
    } else if let statusCode = error.responseCode {
        return .serverError(&#34;服务器错误: \(statusCode)&#34;)
    } else {
        return .serverError(error.localizedDescription)
    }
}

建议

  • 学习阶段:先掌握 URLSession,理解网络请求基本原理
  • 开发阶段:根据项目需求选择框架,大部分情况下 Alamofire 更高效
  • 保持更新:关注 Swift 官方网络库的更新,未来可能会有更好用的原生方案

Swift 条件编译指令

一、#if os(iOS) 核心本质:Swift 条件编译指令

#if os(iOS) 是 Swift 提供的编译时条件判断指令,核心作用是:根据编译目标的操作系统(平台),决定是否编译某一段代码——只有当工程的编译目标是 iOS 时,#if os(iOS)#else 之间的代码才会被编译进最终产物;非 iOS 平台(如 macOS、watchOS、tvOS、visionOS 等)则编译 #else 分支的代码。

二、这段代码中该指令的具体作用

#if os(iOS)
// 仅 iOS 平台执行:Label 加字体+垂直内边距
Label(title, systemImage: icon).font(.headline).padding(.vertical, 8)
#else
// 非 iOS 平台(macOS/watchOS/tvOS 等)执行:仅基础 Label
Label(title, systemImage: icon)
#endif
  • iOS 平台:给 Label 增加 font(.headline)(标题字体)和 padding(.vertical, 8)(垂直方向8pt内边距),适配 iOS 系统的 UI 设计规范(比如 iOS 列表项通常需要内边距和醒目字体,贴合 Settings/备忘录等原生 App 风格);
  • 非 iOS 平台:仅保留基础 Label,不添加额外样式——因为不同平台的 UI 逻辑不同(比如 macOS 的 Label 嵌入 NavigationLink 时,默认样式已适配侧边栏/列表布局,额外 padding 会导致布局拥挤;watchOS 屏幕尺寸极小,多余内边距会浪费空间)。

三、关键语法细节

1. 完整语法结构
#if 条件
    // 条件满足时编译的代码
#else
    // 条件不满足时编译的代码
#endif // 必须配对结束,否则编译报错
  • #if/#else/#endif 是 Swift 保留的编译指令,不是普通的运行时 if-else
  • 编译阶段就决定代码是否被包含,而非运行时判断(这是和 if #available(...) 的核心区别)。
2. 支持的平台参数

os(平台) 中可填写的常用平台值:

参数 对应平台 补充说明
iOS iOS/iPadOS iPadOS 编译时仍识别为 iOS
macOS macOS 包括 Intel/Apple Silicon 机型
watchOS watchOS 苹果手表系统
tvOS tvOS 苹果电视系统
visionOS visionOS Vision Pro 系统
Linux/Windows Linux/Windows Swift 跨平台支持
3. 多条件组合

可通过 ||(或)、&&(与)组合多个条件,比如:

#if os(iOS) || os(visionOS) // iOS 或 Vision Pro 平台
    Label(title, systemImage: icon).font(.headline).padding(.vertical, 8)
#elseif os(macOS) // macOS 平台单独处理
    Label(title, systemImage: icon).font(.subheadline).padding(.horizontal, 4)
#else // 其他平台(watchOS/tvOS)
    Label(title, systemImage: icon)
#endif

四、和 #available 的核心区别(易错点)

很多开发者会混淆 #if os(...)#available,两者完全不同:

特性 #if os(iOS)(条件编译) #available(iOS 17.0, *)(运行时判断)
执行阶段 编译时(决定代码是否被打包) 运行时(代码已打包,仅判断是否执行)
作用 区分不同平台编译不同代码 区分同一平台的不同系统版本执行代码
产物体积 非目标平台代码不会被编译,体积小 所有分支代码都编译,体积稍大
示例场景 iOS 加 padding,macOS 不加 iOS 17+ 用新 API,iOS 16- 用兼容代码

示例对比:

// 1. 条件编译(不同平台编译不同代码)
#if os(iOS)
    // 仅 iOS 编译这段代码,macOS 产物中无此代码
    Label(/* iOS 样式 */)
#endif

// 2. 运行时判断(同一平台不同版本执行不同代码)
if #available(iOS 17.0, *) {
    // iOS 17+ 设备运行时执行
    Label(/* iOS 17 新样式 */)
} else {
    // iOS 16- 设备运行时执行
    Label(/* 兼容样式 */)
}

五、该写法的设计初衷(为什么要这么做)

这段代码是 SwiftUI 跨平台开发的典型实践:

  1. SwiftUI 天然跨平台:同一份代码可运行在 iOS/macOS/watchOS 等平台,但不同平台的 UI 规范、屏幕尺寸、交互逻辑差异大;
  2. 按需定制样式:iOS 平台需要额外的 padding/font 优化视觉,其他平台保持原生样式即可,避免“一刀切”导致的跨平台布局问题;
  3. 减少冗余代码:通过条件编译,非目标平台的样式代码不会被编译,降低最终产物体积,且代码结构更清晰。

六、拓展:其他常用条件编译指令

除了 os(...),Swift 还支持其他实用的条件编译:

  1. 判断是否为模拟器:
    #if targetEnvironment(simulator)
        // 仅模拟器编译的代码(比如测试日志)
        print(&#34;运行在模拟器中&#34;)
    #else
        // 真机编译的代码
        print(&#34;运行在真机中&#34;)
    #endif
    
  2. 判断编译器版本:
    #if compiler(>=5.9)
        // Swift 5.9+ 编译器支持的语法(比如新的宏)
    #endif
    
  3. 判断调试/发布模式:
    #if DEBUG
        // 调试模式编译(比如打印调试日志)
        Label(title, systemImage: icon).border(.red) // 调试时显示边框
    #else
        // 发布模式编译
        Label(title, systemImage: icon)
    #endif
    

总结

这段代码中的 #if os(iOS) 是为了在编译阶段区分 iOS 和其他平台,给 iOS 端的 Label 增加专属的字体和内边距样式,其他平台保持基础样式,既兼顾 SwiftUI 跨平台的代码复用,又适配不同平台的 UI 规范。核心要记住:它是编译时指令,而非运行时判断,这是和 #available 最关键的区别。

iOS 电量监控与优化完整方案

目录


电量消耗概述

电量消耗来源

graph TB
    A[电量消耗] --> B[CPU]
    A --> C[网络]
    A --> D[定位]
    A --> E[屏幕]
    A --> F[后台任务]
    A --> G[传感器]
    
    B --> B1[主线程占用]
    B --> B2[后台计算]
    B --> B3[频繁唤醒]
    
    C --> C1[频繁请求]
    C --> C2[大数据传输]
    C --> C3[长连接]
    
    D --> D1[GPS 定位]
    D --> D2[持续定位]
    D --> D3[高精度定位]
    
    E --> E1[高亮度]
    E --> E2[高刷新率]
    E --> E3[复杂动画]
    
    F --> F1[后台刷新]
    F --> F2[推送唤醒]
    F --> F3[音频播放]
    
    G --> G1[加速度计]
    G --> G2[陀螺仪]
    G --> G3[磁力计]
    
    style A fill:#FF6B6B
    style B fill:#FFA07A
    style C fill:#FFD93D
    style D fill:#6BCF7F
    style E fill:#4D96FF
    style F fill:#9D84B7
    style G fill:#F38181

电量消耗占比

pie title iOS 应用电量消耗分布
    &#34;CPU&#34; : 30
    &#34;网络&#34; : 25
    &#34;定位&#34; : 20
    &#34;屏幕&#34; : 15
    &#34;后台任务&#34; : 5
    &#34;传感器&#34; : 5

电量等级划分

等级 电量消耗 用户感知 优化优先级
优秀 < 5% / 小时 无感知 P3
良好 5-10% / 小时 轻微感知 P2
一般 10-15% / 小时 明显感知 P1
较差 15-20% / 小时 强烈感知 P0
很差 > 20% / 小时 严重发热 P0

电量监控方案

监控架构

graph TB
    A[电量监控系统] --> B[实时监控]
    A --> C[数据采集]
    A --> D[数据分析]
    A --> E[告警系统]
    
    B --> B1[电量变化]
    B --> B2[充电状态]
    B --> B3[电池健康]
    
    C --> C1[CPU 使用率]
    C --> C2[网络流量]
    C --> C3[定位使用]
    C --> C4[后台活动]
    
    D --> D1[耗电排行]
    D --> D2[异常检测]
    D --> D3[趋势分析]
    
    E --> E1[实时告警]
    E --> E2[日报周报]
    E --> E3[优化建议]
    
    style A fill:#4ECDC4
    style B fill:#FF6B6B
    style C fill:#FFA07A
    style D fill:#95E1D3
    style E fill:#F38181

1. 电量监控管理器

import UIKit
import Foundation

class BatteryMonitor {
    
    static let shared = BatteryMonitor()
    
    // 监控数据
    private var batteryLevel: Float = 1.0
    private var batteryState: UIDevice.BatteryState = .unknown
    private var isMonitoring = false
    
    // 历史记录
    private var batteryHistory: [BatteryRecord] = []
    private let maxHistoryCount = 1000
    
    // 监控间隔
    private var monitoringTimer: Timer?
    private let monitoringInterval: TimeInterval = 60 // 60秒
    
    struct BatteryRecord: Codable {
        let timestamp: Date
        let level: Float
        let state: String
        let temperature: Float?
        let voltage: Float?
        let current: Float?
    }
    
    private init() {
        setupBatteryMonitoring()
    }
    
    // MARK: - Setup
    
    private func setupBatteryMonitoring() {
        // 启用电池监控
        UIDevice.current.isBatteryMonitoringEnabled = true
        
        // 监听电量变化
        NotificationCenter.default.addObserver(
            self,
            selector: #selector(batteryLevelDidChange),
            name: UIDevice.batteryLevelDidChangeNotification,
            object: nil
        )
        
        // 监听充电状态变化
        NotificationCenter.default.addObserver(
            self,
            selector: #selector(batteryStateDidChange),
            name: UIDevice.batteryStateDidChangeNotification,
            object: nil
        )
    }
    
    // MARK: - Monitoring Control
    
    func startMonitoring() {
        guard !isMonitoring else { return }
        
        isMonitoring = true
        
        // 立即记录一次
        recordBatteryStatus()
        
        // 定时记录
        monitoringTimer = Timer.scheduledTimer(
            withTimeInterval: monitoringInterval,
            repeats: true
        ) { [weak self] _ in
            self?.recordBatteryStatus()
        }
        
        print(&#34;🔋 电量监控已启动&#34;)
    }
    
    func stopMonitoring() {
        isMonitoring = false
        monitoringTimer?.invalidate()
        monitoringTimer = nil
        
        print(&#34;🔋 电量监控已停止&#34;)
    }
    
    // MARK: - Battery Status
    
    @objc private func batteryLevelDidChange() {
        batteryLevel = UIDevice.current.batteryLevel
        print(&#34;🔋 电量变化: \(Int(batteryLevel * 100))%&#34;)
        
        // 检查低电量
        if batteryLevel < 0.2 && batteryLevel > 0 {
            notifyLowBattery()
        }
        
        recordBatteryStatus()
    }
    
    @objc private func batteryStateDidChange() {
        batteryState = UIDevice.current.batteryState
        
        let stateString = batteryStateString(batteryState)
        print(&#34;🔋 充电状态变化: \(stateString)&#34;)
        
        recordBatteryStatus()
    }
    
    private func recordBatteryStatus() {
        let record = BatteryRecord(
            timestamp: Date(),
            level: UIDevice.current.batteryLevel,
            state: batteryStateString(UIDevice.current.batteryState),
            temperature: getBatteryTemperature(),
            voltage: getBatteryVoltage(),
            current: getBatteryCurrent()
        )
        
        batteryHistory.append(record)
        
        // 限制历史记录数量
        if batteryHistory.count > maxHistoryCount {
            batteryHistory.removeFirst()
        }
        
        // 保存到本地
        saveBatteryHistory()
    }
    
    // MARK: - Battery Info
    
    func getCurrentBatteryLevel() -> Float {
        return UIDevice.current.batteryLevel
    }
    
    func getCurrentBatteryState() -> UIDevice.BatteryState {
        return UIDevice.current.batteryState
    }
    
    func isCharging() -> Bool {
        let state = UIDevice.current.batteryState
        return state == .charging || state == .full
    }
    
    private func batteryStateString(_ state: UIDevice.BatteryState) -> String {
        switch state {
        case .unknown:
            return &#34;未知&#34;
        case .unplugged:
            return &#34;未充电&#34;
        case .charging:
            return &#34;充电中&#34;
        case .full:
            return &#34;已充满&#34;
        @unknown default:
            return &#34;未知&#34;
        }
    }
    
    // MARK: - Battery Metrics (需要私有 API 或估算)
    
    private func getBatteryTemperature() -> Float? {
        // iOS 不提供公开 API 获取电池温度
        // 可以通过 IOKit 私有 API 获取(不推荐上架 App Store)
        return nil
    }
    
    private func getBatteryVoltage() -> Float? {
        // iOS 不提供公开 API 获取电池电压
        return nil
    }
    
    private func getBatteryCurrent() -> Float? {
        // iOS 不提供公开 API 获取电池电流
        return nil
    }
    
    // MARK: - Battery Analysis
    
    // 计算电量消耗速率(%/小时)
    func calculateBatteryDrainRate() -> Float? {
        guard batteryHistory.count >= 2 else { return nil }
        
        let recentRecords = batteryHistory.suffix(10)
        guard let firstRecord = recentRecords.first,
              let lastRecord = recentRecords.last else {
            return nil
        }
        
        let timeDiff = lastRecord.timestamp.timeIntervalSince(firstRecord.timestamp)
        guard timeDiff > 0 else { return nil }
        
        let levelDiff = firstRecord.level - lastRecord.level
        let hoursDiff = Float(timeDiff / 3600)
        
        let drainRate = (levelDiff / hoursDiff) * 100
        
        return drainRate
    }
    
    // 预估剩余使用时间(小时)
    func estimateRemainingTime() -> Float? {
        guard let drainRate = calculateBatteryDrainRate(),
              drainRate > 0 else {
            return nil
        }
        
        let currentLevel = UIDevice.current.batteryLevel * 100
        let remainingTime = currentLevel / drainRate
        
        return remainingTime
    }
    
    // 获取电量消耗报告
    func getBatteryReport() -> BatteryReport {
        let currentLevel = UIDevice.current.batteryLevel
        let drainRate = calculateBatteryDrainRate()
        let remainingTime = estimateRemainingTime()
        
        return BatteryReport(
            currentLevel: currentLevel,
            drainRate: drainRate,
            remainingTime: remainingTime,
            isCharging: isCharging(),
            recordCount: batteryHistory.count
        )
    }
    
    struct BatteryReport {
        let currentLevel: Float
        let drainRate: Float?
        let remainingTime: Float?
        let isCharging: Bool
        let recordCount: Int
        
        func description() -> String {
            var desc = &#34;&#34;&#34;
            
            ========== 电量报告 ==========
            当前电量: \(Int(currentLevel * 100))%
            充电状态: \(isCharging ? &#34;充电中&#34; : &#34;未充电&#34;)
            &#34;&#34;&#34;
            
            if let drainRate = drainRate {
                desc += &#34;\n消耗速率: \(String(format: &#34;%.2f&#34;, drainRate))%/小时&#34;
            }
            
            if let remainingTime = remainingTime {
                desc += &#34;\n预计剩余: \(String(format: &#34;%.1f&#34;, remainingTime)) 小时&#34;
            }
            
            desc += &#34;\n记录数量: \(recordCount)&#34;
            desc += &#34;\n===========================\n&#34;
            
            return desc
        }
    }
    
    // MARK: - Notifications
    
    private func notifyLowBattery() {
        print(&#34;⚠️ 低电量警告&#34;)
        
        // 发送通知
        NotificationCenter.default.post(
            name: NSNotification.Name(&#34;LowBatteryWarning&#34;),
            object: nil
        )
    }
    
    // MARK: - Persistence
    
    private func saveBatteryHistory() {
        let encoder = JSONEncoder()
        encoder.dateEncodingStrategy = .iso8601
        
        if let data = try? encoder.encode(batteryHistory) {
            UserDefaults.standard.set(data, forKey: &#34;BatteryHistory&#34;)
        }
    }
    
    private func loadBatteryHistory() {
        guard let data = UserDefaults.standard.data(forKey: &#34;BatteryHistory&#34;) else {
            return
        }
        
        let decoder = JSONDecoder()
        decoder.dateDecodingStrategy = .iso8601
        
        if let history = try? decoder.decode([BatteryRecord].self, from: data) {
            batteryHistory = history
        }
    }
    
    // MARK: - Export
    
    func exportBatteryHistory() -> String {
        var csv = &#34;时间,电量(%),状态\n&#34;
        
        for record in batteryHistory {
            let dateFormatter = DateFormatter()
            dateFormatter.dateFormat = &#34;yyyy-MM-dd HH:mm:ss&#34;
            let timeString = dateFormatter.string(from: record.timestamp)
            
            csv += &#34;\(timeString),\(Int(record.level * 100)),\(record.state)\n&#34;
        }
        
        return csv
    }
}

2. CPU 监控

import Foundation

class CPUMonitor {
    
    static let shared = CPUMonitor()
    
    private var monitoringTimer: Timer?
    private var cpuHistory: [CPURecord] = []
    
    struct CPURecord {
        let timestamp: Date
        let usage: Double
        let threads: Int
    }
    
    private init() {}
    
    // 开始监控
    func startMonitoring(interval: TimeInterval = 1.0) {
        monitoringTimer = Timer.scheduledTimer(
            withTimeInterval: interval,
            repeats: true
        ) { [weak self] _ in
            self?.recordCPUUsage()
        }
    }
    
    // 停止监控
    func stopMonitoring() {
        monitoringTimer?.invalidate()
        monitoringTimer = nil
    }
    
    // 获取当前 CPU 使用率
    func getCurrentCPUUsage() -> Double {
        var totalUsage: Double = 0
        var threadsList: thread_act_array_t?
        var threadsCount = mach_msg_type_number_t(0)
        
        let threadsResult = task_threads(mach_task_self_, &threadsList, &threadsCount)
        
        guard threadsResult == KERN_SUCCESS,
              let threads = threadsList else {
            return 0
        }
        
        for index in 0...stride)
        )
        
        return totalUsage
    }
    
    // 记录 CPU 使用情况
    private func recordCPUUsage() {
        let usage = getCurrentCPUUsage()
        let threads = getThreadCount()
        
        let record = CPURecord(
            timestamp: Date(),
            usage: usage,
            threads: threads
        )
        
        cpuHistory.append(record)
        
        // 限制历史记录
        if cpuHistory.count > 1000 {
            cpuHistory.removeFirst()
        }
        
        // 检查异常
        if usage > 80 {
            print(&#34;⚠️ CPU 使用率过高: \(String(format: &#34;%.1f&#34;, usage))%&#34;)
        }
    }
    
    // 获取线程数量
    private func getThreadCount() -> Int {
        var threadsList: thread_act_array_t?
        var threadsCount = mach_msg_type_number_t(0)
        
        let result = task_threads(mach_task_self_, &threadsList, &threadsCount)
        
        if result == KERN_SUCCESS {
            vm_deallocate(
                mach_task_self_,
                vm_address_t(UInt(bitPattern: threadsList)),
                vm_size_t(Int(threadsCount) * MemoryLayout.stride)
            )
            return Int(threadsCount)
        }
        
        return 0
    }
    
    // 获取平均 CPU 使用率
    func getAverageCPUUsage(duration: TimeInterval = 60) -> Double {
        let cutoffTime = Date().addingTimeInterval(-duration)
        let recentRecords = cpuHistory.filter { $0.timestamp > cutoffTime }
        
        guard !recentRecords.isEmpty else { return 0 }
        
        let totalUsage = recentRecords.reduce(0) { $0 + $1.usage }
        return totalUsage / Double(recentRecords.count)
    }
}

3. 网络监控

import Foundation

class NetworkMonitor {
    
    static let shared = NetworkMonitor()
    
    private var totalBytesSent: Int64 = 0
    private var totalBytesReceived: Int64 = 0
    private var lastCheckTime: Date = Date()
    
    private var monitoringTimer: Timer?
    
    struct NetworkStats {
        let bytesSent: Int64
        let bytesReceived: Int64
        let uploadSpeed: Double  // KB/s
        let downloadSpeed: Double  // KB/s
    }
    
    private init() {}
    
    // 开始监控
    func startMonitoring(interval: TimeInterval = 1.0) {
        // 初始化基准值
        updateNetworkStats()
        
        monitoringTimer = Timer.scheduledTimer(
            withTimeInterval: interval,
            repeats: true
        ) { [weak self] _ in
            self?.updateNetworkStats()
        }
    }
    
    // 停止监控
    func stopMonitoring() {
        monitoringTimer?.invalidate()
        monitoringTimer = nil
    }
    
    // 获取网络统计信息
    func getNetworkStats() -> NetworkStats? {
        var ifaddr: UnsafeMutablePointer?
        
        guard getifaddrs(&ifaddr) == 0 else {
            return nil
        }
        
        defer { freeifaddrs(ifaddr) }
        
        var bytesSent: Int64 = 0
        var bytesReceived: Int64 = 0
        
        var ptr = ifaddr
        while ptr != nil {
            defer { ptr = ptr?.pointee.ifa_next }
            
            guard let interface = ptr?.pointee else { continue }
            
            let name = String(cString: interface.ifa_name)
            
            // 只统计 WiFi 和蜂窝网络
            if name.hasPrefix(&#34;en&#34;) || name.hasPrefix(&#34;pdp_ip&#34;) {
                if let data = interface.ifa_data?.assumingMemoryBound(to: if_data.self).pointee {
                    bytesSent += Int64(data.ifi_obytes)
                    bytesReceived += Int64(data.ifi_ibytes)
                }
            }
        }
        
        // 计算速度
        let now = Date()
        let timeDiff = now.timeIntervalSince(lastCheckTime)
        
        let uploadSpeed = Double(bytesSent - totalBytesSent) / timeDiff / 1024
        let downloadSpeed = Double(bytesReceived - totalBytesReceived) / timeDiff / 1024
        
        totalBytesSent = bytesSent
        totalBytesReceived = bytesReceived
        lastCheckTime = now
        
        return NetworkStats(
            bytesSent: bytesSent,
            bytesReceived: bytesReceived,
            uploadSpeed: uploadSpeed,
            downloadSpeed: downloadSpeed
        )
    }
    
    private func updateNetworkStats() {
        guard let stats = getNetworkStats() else { return }
        
        // 检查异常流量
        if stats.uploadSpeed > 100 || stats.downloadSpeed > 100 {
            print(&#34;⚠️ 网络流量异常 - 上传: \(String(format: &#34;%.1f&#34;, stats.uploadSpeed)) KB/s, 下载: \(String(format: &#34;%.1f&#34;, stats.downloadSpeed)) KB/s&#34;)
        }
    }
}

CPU 优化

CPU 优化策略

graph TB
    A[CPU 优化] --> B[减少计算]
    A --> C[异步处理]
    A --> D[缓存结果]
    A --> E[降低频率]
    
    B --> B1[算法优化]
    B --> B2[减少循环]
    B --> B3[避免重复计算]
    
    C --> C1[后台线程]
    C --> C2[GCD 优化]
    C --> C3[Operation Queue]
    
    D --> D1[内存缓存]
    D --> D2[磁盘缓存]
    D --> D3[计算结果缓存]
    
    E --> E1[降低刷新率]
    E --> E2[延迟执行]
    E --> E3[批量处理]
    
    style A fill:#4ECDC4
    style B fill:#FF6B6B
    style C fill:#FFA07A
    style D fill:#95E1D3
    style E fill:#F38181

1. 定时器优化

class OptimizedTimer {
    
    private var timer: DispatchSourceTimer?
    private let queue: DispatchQueue
    
    init(queue: DispatchQueue = .global(qos: .utility)) {
        self.queue = queue
    }
    
    // 使用 DispatchSourceTimer 替代 Timer
    func startTimer(interval: TimeInterval, 
                   leeway: DispatchTimeInterval = .milliseconds(100),
                   handler: @escaping () -> Void) {
        
        let timer = DispatchSource.makeTimerSource(queue: queue)
        timer.schedule(
            deadline: .now() + interval,
            repeating: interval,
            leeway: leeway  // 允许系统延迟,节省电量
        )
        
        timer.setEventHandler(handler: handler)
        timer.resume()
        
        self.timer = timer
    }
    
    func stop() {
        timer?.cancel()
        timer = nil
    }
    
    deinit {
        stop()
    }
}

// 使用示例
class SomeViewController: UIViewController {
    
    private let optimizedTimer = OptimizedTimer()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        // 使用优化的定时器
        optimizedTimer.startTimer(interval: 1.0) {
            // 定时任务
            print(&#34;Timer fired&#34;)
        }
    }
    
    deinit {
        optimizedTimer.stop()
    }
}

2. 图片处理优化

class ImageProcessor {
    
    static let shared = ImageProcessor()
    
    private let processingQueue = DispatchQueue(
        label: &#34;com.app.imageProcessing&#34;,
        qos: .utility
    )
    
    private let cache = NSCache()
    
    private init() {
        cache.countLimit = 100
        cache.totalCostLimit = 50 * 1024 * 1024 // 50MB
    }
    
    // 异步处理图片
    func processImage(_ image: UIImage,
                     size: CGSize,
                     completion: @escaping (UIImage?) -> Void) {
        
        let cacheKey = &#34;\(size.width)x\(size.height)&#34; as NSString
        
        // 检查缓存
        if let cachedImage = cache.object(forKey: cacheKey) {
            completion(cachedImage)
            return
        }
        
        // 后台处理
        processingQueue.async {
            let processedImage = self.resize(image, to: size)
            
            // 缓存结果
            if let processedImage = processedImage {
                self.cache.setObject(processedImage, forKey: cacheKey)
            }
            
            DispatchQueue.main.async {
                completion(processedImage)
            }
        }
    }
    
    private func resize(_ image: UIImage, to size: CGSize) -> UIImage? {
        UIGraphicsBeginImageContextWithOptions(size, false, 0.0)
        defer { UIGraphicsEndImageContext() }
        
        image.draw(in: CGRect(origin: .zero, size: size))
        return UIGraphicsGetImageFromCurrentImageContext()
    }
    
    // 降采样大图片
    func downsampleImage(at url: URL, to size: CGSize) -> UIImage? {
        let options: [CFString: Any] = [
            kCGImageSourceCreateThumbnailFromImageIfAbsent: true,
            kCGImageSourceCreateThumbnailWithTransform: true,
            kCGImageSourceShouldCacheImmediately: true,
            kCGImageSourceThumbnailMaxPixelSize: max(size.width, size.height)
        ]
        
        guard let imageSource = CGImageSourceCreateWithURL(url as CFURL, nil),
              let image = CGImageSourceCreateThumbnailAtIndex(imageSource, 0, options as CFDictionary) else {
            return nil
        }
        
        return UIImage(cgImage: image)
    }
}

3. 列表滚动优化

class OptimizedTableViewController: UITableViewController {
    
    private var data: [String] = []
    private let cellReuseIdentifier = &#34;Cell&#34;
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        // 1. 预估行高,避免实时计算
        tableView.estimatedRowHeight = 44
        tableView.rowHeight = UITableView.automaticDimension
        
        // 2. 注册 cell
        tableView.register(UITableViewCell.self, forCellReuseIdentifier: cellReuseIdentifier)
    }
    
    override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return data.count
    }
    
    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        // 3. 复用 cell
        let cell = tableView.dequeueReusableCell(withIdentifier: cellReuseIdentifier, for: indexPath)
        
        // 4. 避免在 cellForRow 中进行复杂计算
        cell.textLabel?.text = data[indexPath.row]
        
        // 5. 异步加载图片
        if let imageURL = getImageURL(for: indexPath.row) {
            cell.imageView?.image = UIImage(named: &#34;placeholder&#34;)
            
            ImageLoader.shared.loadImage(from: imageURL) { image in
                // 检查 cell 是否被复用
                if let currentCell = tableView.cellForRow(at: indexPath) {
                    currentCell.imageView?.image = image
                }
            }
        }
        
        return cell
    }
    
    // 6. 延迟加载
    override func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) {
        if !decelerate {
            loadVisibleCells()
        }
    }
    
    override func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
        loadVisibleCells()
    }
    
    private func loadVisibleCells() {
        // 只加载可见 cell 的数据
    }
    
    private func getImageURL(for index: Int) -> URL? {
        // 返回图片 URL
        return nil
    }
}

// 图片加载器
class ImageLoader {
    
    static let shared = ImageLoader()
    
    private let cache = NSCache()
    private let loadingQueue = DispatchQueue(label: &#34;com.app.imageLoading&#34;, qos: .utility)
    
    func loadImage(from url: URL, completion: @escaping (UIImage?) -> Void) {
        
        // 检查缓存
        if let cachedImage = cache.object(forKey: url as NSURL) {
            completion(cachedImage)
            return
        }
        
        // 后台加载
        loadingQueue.async {
            guard let data = try? Data(contentsOf: url),
                  let image = UIImage(data: data) else {
                DispatchQueue.main.async {
                    completion(nil)
                }
                return
            }
            
            // 缓存图片
            self.cache.setObject(image, forKey: url as NSURL)
            
            DispatchQueue.main.async {
                completion(image)
            }
        }
    }
}

网络优化

网络优化策略

graph TB
    A[网络优化] --> B[减少请求]
    A --> C[优化传输]
    A --> D[智能调度]
    A --> E[缓存策略]
    
    B --> B1[请求合并]
    B --> B2[批量处理]
    B --> B3[取消无效请求]
    
    C --> C1[数据压缩]
    C --> C2[增量更新]
    C --> C3[协议优化]
    
    D --> D1[WiFi 优先]
    D --> D2[延迟执行]
    D --> D3[后台上传]
    
    E --> E1[本地缓存]
    E --> E2[过期策略]
    E --> E3[预加载]
    
    style A fill:#4ECDC4
    style B fill:#FF6B6B
    style C fill:#FFA07A
    style D fill:#95E1D3
    style E fill:#F38181

1. 网络请求优化

class NetworkOptimizer {
    
    static let shared = NetworkOptimizer()
    
    private var pendingRequests: [String: [Request]] = [:]
    private let batchInterval: TimeInterval = 0.5
    
    struct Request {
        let url: URL
        let completion: (Result) -> Void
    }
    
    private init() {}
    
    // 批量请求
    func batchRequest(url: URL, completion: @escaping (Result) -> Void) {
        
        let key = url.host ?? &#34;&#34;
        
        let request = Request(url: url, completion: completion)
        
        if pendingRequests[key] == nil {
            pendingRequests[key] = []
            
            // 延迟执行,收集更多请求
            DispatchQueue.main.asyncAfter(deadline: .now() + batchInterval) {
                self.executeBatchRequests(for: key)
            }
        }
        
        pendingRequests[key]?.append(request)
    }
    
    private func executeBatchRequests(for key: String) {
        guard let requests = pendingRequests[key], !requests.isEmpty else {
            return
        }
        
        pendingRequests[key] = nil
        
        print(&#34;📦 批量执行 \(requests.count) 个请求&#34;)
        
        // 执行批量请求
        for request in requests {
            URLSession.shared.dataTask(with: request.url) { data, response, error in
                if let error = error {
                    request.completion(.failure(error))
                } else if let data = data {
                    request.completion(.success(data))
                }
            }.resume()
        }
    }
    
    // 根据网络状态调整策略
    func shouldExecuteRequest() -> Bool {
        // 检查网络类型
        let networkType = getNetworkType()
        
        switch networkType {
        case .wifi:
            return true
        case .cellular:
            // 蜂窝网络下,检查是否允许
            return UserDefaults.standard.bool(forKey: &#34;AllowCellularData&#34;)
        case .none:
            return false
        }
    }
    
    enum NetworkType {
        case wifi
        case cellular
        case none
    }
    
    private func getNetworkType() -> NetworkType {
        // 实现网络类型检测
        return .wifi
    }
}

2. 后台上传优化

class BackgroundUploader {
    
    static let shared = BackgroundUploader()
    
    private lazy var session: URLSession = {
        let config = URLSessionConfiguration.background(withIdentifier: &#34;com.app.backgroundUpload&#34;)
        config.isDiscretionary = true  // 允许系统选择最佳时机
        config.sessionSendsLaunchEvents = true
        return URLSession(configuration: config, delegate: self, delegateQueue: nil)
    }()
    
    private init() {}
    
    // 后台上传
    func upload(fileURL: URL, to uploadURL: URL) {
        var request = URLRequest(url: uploadURL)
        request.httpMethod = &#34;POST&#34;
        
        let task = session.uploadTask(with: request, fromFile: fileURL)
        task.earliestBeginDate = Date().addingTimeInterval(60)  // 延迟1分钟
        task.countOfBytesClientExpectsToSend = 1024 * 1024  // 预估大小
        task.countOfBytesClientExpectsToReceive = 1024
        
        task.resume()
        
        print(&#34;📤 后台上传任务已创建&#34;)
    }
}

extension BackgroundUploader: URLSessionDelegate, URLSessionTaskDelegate {
    
    func urlSession(_ session: URLSession, 
                   task: URLSessionTask, 
                   didCompleteWithError error: Error?) {
        if let error = error {
            print(&#34; 上传失败: \(error)&#34;)
        } else {
            print(&#34; 上传成功&#34;)
        }
    }
    
    func urlSessionDidFinishEvents(forBackgroundURLSession session: URLSession) {
        print(&#34; 后台上传任务完成&#34;)
    }
}

定位优化

定位优化策略

graph TB
    A[定位优化] --> B[降低精度]
    A --> C[减少频率]
    A --> D[智能切换]
    A --> E[延迟启动]
    
    B --> B1[使用低精度]
    B --> B2[避免GPS]
    B --> B3[WiFi定位]
    
    C --> C1[增加间隔]
    C --> C2[距离过滤]
    C --> C3[按需定位]
    
    D --> D1[前后台切换]
    D --> D2[场景适配]
    D --> D3[电量感知]
    
    E --> E1[延迟初始化]
    E --> E2[用户触发]
    E --> E3[批量定位]
    
    style A fill:#4ECDC4
    style B fill:#FF6B6B
    style C fill:#FFA07A
    style D fill:#95E1D3
    style E fill:#F38181

1. 定位管理器优化

import CoreLocation

class OptimizedLocationManager: NSObject {
    
    static let shared = OptimizedLocationManager()
    
    private let locationManager = CLLocationManager()
    private var isMonitoring = false
    
    // 定位回调
    var locationUpdateHandler: ((CLLocation) -> Void)?
    
    private override init() {
        super.init()
        setupLocationManager()
    }
    
    private func setupLocationManager() {
        locationManager.delegate = self
        
        // 1. 使用低精度定位
        locationManager.desiredAccuracy = kCLLocationAccuracyHundredMeters
        
        // 2. 设置距离过滤器
        locationManager.distanceFilter = 100  // 移动100米才更新
        
        // 3. 允许后台定位(如需要)
        locationManager.allowsBackgroundLocationUpdates = false
        
        // 4. 暂停自动更新
        locationManager.pausesLocationUpdatesAutomatically = true
        
        // 5. 活动类型
        locationManager.activityType = .other
    }
    
    // 请求权限
    func requestAuthorization() {
        let status = CLLocationManager.authorizationStatus()
        
        switch status {
        case .notDetermined:
            locationManager.requestWhenInUseAuthorization()
        case .authorizedWhenInUse, .authorizedAlways:
            print(&#34; 已授权定位&#34;)
        case .denied, .restricted:
            print(&#34; 定位权限被拒绝&#34;)
        @unknown default:
            break
        }
    }
    
    // 开始定位
    func startUpdatingLocation() {
        guard !isMonitoring else { return }
        
        // 检查电量
        let batteryLevel = UIDevice.current.batteryLevel
        if batteryLevel < 0.2 && batteryLevel > 0 {
            // 低电量模式:降低精度
            locationManager.desiredAccuracy = kCLLocationAccuracyKilometer
            locationManager.distanceFilter = 500
            print(&#34;⚡️ 低电量模式:降低定位精度&#34;)
        }
        
        locationManager.startUpdatingLocation()
        isMonitoring = true
        
        print(&#34;📍 开始定位&#34;)
    }
    
    // 停止定位
    func stopUpdatingLocation() {
        locationManager.stopUpdatingLocation()
        isMonitoring = false
        
        print(&#34;📍 停止定位&#34;)
    }
    
    // 单次定位(更省电)
    func requestSingleLocation() {
        locationManager.requestLocation()
        print(&#34;📍 请求单次定位&#34;)
    }
    
    // 区域监控(地理围栏)
    func startMonitoringRegion(center: CLLocationCoordinate2D, radius: CLLocationDistance) {
        let region = CLCircularRegion(
            center: center,
            radius: radius,
            identifier: &#34;CustomRegion&#34;
        )
        
        region.notifyOnEntry = true
        region.notifyOnExit = true
        
        locationManager.startMonitoring(for: region)
        
        print(&#34;📍 开始区域监控&#34;)
    }
    
    // 重要位置变化(最省电)
    func startMonitoringSignificantLocationChanges() {
        locationManager.startMonitoringSignificantLocationChanges()
        print(&#34;📍 开始监控重要位置变化&#34;)
    }
}

extension OptimizedLocationManager: CLLocationManagerDelegate {
    
    func locationManager(_ manager: CLLocationManager, 
                        didUpdateLocations locations: [CLLocation]) {
        
        guard let location = locations.last else { return }
        
        print(&#34;📍 位置更新: \(location.coordinate.latitude), \(location.coordinate.longitude)&#34;)
        
        locationUpdateHandler?(location)
    }
    
    func locationManager(_ manager: CLLocationManager, 
                        didFailWithError error: Error) {
        print(&#34; 定位失败: \(error.localizedDescription)&#34;)
    }
    
    func locationManager(_ manager: CLLocationManager, 
                        didChangeAuthorization status: CLAuthorizationStatus) {
        switch status {
        case .authorizedWhenInUse, .authorizedAlways:
            print(&#34; 定位权限已授予&#34;)
        case .denied, .restricted:
            print(&#34; 定位权限被拒绝&#34;)
        default:
            break
        }
    }
    
    func locationManagerDidPauseLocationUpdates(_ manager: CLLocationManager) {
        print(&#34;⏸️ 定位已暂停(自动)&#34;)
    }
    
    func locationManagerDidResumeLocationUpdates(_ manager: CLLocationManager) {
        print(&#34;▶️ 定位已恢复&#34;)
    }
}

2. 定位策略选择

class LocationStrategy {
    
    enum Strategy {
        case highAccuracy      // 高精度(GPS)
        case balanced          // 平衡模式
        case lowPower          // 省电模式
        case significantChange // 重要变化
    }
    
    static func selectStrategy(batteryLevel: Float, 
                              isCharging: Bool,
                              userPreference: Strategy?) -> Strategy {
        
        // 用户偏好优先
        if let preference = userPreference {
            return preference
        }
        
        // 充电时使用高精度
        if isCharging {
            return .highAccuracy
        }
        
        // 根据电量选择
        if batteryLevel < 0.2 {
            return .significantChange
        } else if batteryLevel < 0.5 {
            return .lowPower
        } else {
            return .balanced
        }
    }
    
    static func applyStrategy(_ strategy: Strategy, 
                             to locationManager: CLLocationManager) {
        
        switch strategy {
        case .highAccuracy:
            locationManager.desiredAccuracy = kCLLocationAccuracyBest
            locationManager.distanceFilter = kCLDistanceFilterNone
            
        case .balanced:
            locationManager.desiredAccuracy = kCLLocationAccuracyHundredMeters
            locationManager.distanceFilter = 100
            
        case .lowPower:
            locationManager.desiredAccuracy = kCLLocationAccuracyKilometer
            locationManager.distanceFilter = 500
            
        case .significantChange:
            // 使用重要位置变化监控
            locationManager.stopUpdatingLocation()
            locationManager.startMonitoringSignificantLocationChanges()
        }
    }
}

后台任务优化

后台任务策略

graph TB
    A[后台任务优化] --> B[后台刷新]
    A --> C[后台下载]
    A --> D[后台上传]
    A --> E[后台处理]
    
    B --> B1[降低频率]
    B --> B2[批量处理]
    B --> B3[智能调度]
    
    C --> C1[延迟下载]
    C --> C2[WiFi优先]
    C --> C3[断点续传]
    
    D --> D1[延迟上传]
    D --> D2[压缩数据]
    D --> D3[批量上传]
    
    E --> E1[后台任务]
    E --> E2[推送唤醒]
    E --> E3[音频播放]
    
    style A fill:#4ECDC4
    style B fill:#FF6B6B
    style C fill:#FFA07A
    style D fill:#95E1D3
    style E fill:#F38181

1. 后台刷新优化

import UIKit

class BackgroundTaskManager {
    
    static let shared = BackgroundTaskManager()
    
    private var backgroundTask: UIBackgroundTaskIdentifier = .invalid
    
    private init() {}
    
    // 注册后台刷新
    func registerBackgroundRefresh() {
        UIApplication.shared.setMinimumBackgroundFetchInterval(
            UIApplication.backgroundFetchIntervalMinimum
        )
    }
    
    // 执行后台刷新
    func performBackgroundFetch(completion: @escaping (UIBackgroundFetchResult) -> Void) {
        
        print(&#34;🔄 开始后台刷新&#34;)
        
        // 检查电量
        let batteryLevel = UIDevice.current.batteryLevel
        if batteryLevel < 0.2 && batteryLevel > 0 {
            print(&#34;⚡️ 低电量,跳过后台刷新&#34;)
            completion(.noData)
            return
        }
        
        // 执行刷新任务
        fetchNewData { hasNewData in
            if hasNewData {
                completion(.newData)
            } else {
                completion(.noData)
            }
        }
    }
    
    private func fetchNewData(completion: @escaping (Bool) -> Void) {
        // 实现数据获取逻辑
        DispatchQueue.global().asyncAfter(deadline: .now() + 2) {
            completion(true)
        }
    }
    
    // 开始后台任务
    func beginBackgroundTask() {
        backgroundTask = UIApplication.shared.beginBackgroundTask { [weak self] in
            self?.endBackgroundTask()
        }
    }
    
    // 结束后台任务
    func endBackgroundTask() {
        if backgroundTask != .invalid {
            UIApplication.shared.endBackgroundTask(backgroundTask)
            backgroundTask = .invalid
        }
    }
}

// AppDelegate 中使用
extension AppDelegate {
    
    func application(_ application: UIApplication, 
                    performFetchWithCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) {
        
        BackgroundTaskManager.shared.performBackgroundFetch(completion: completionHandler)
    }
    
    func applicationDidEnterBackground(_ application: UIApplication) {
        // 开始后台任务
        BackgroundTaskManager.shared.beginBackgroundTask()
        
        // 执行必要的清理工作
        performCleanup {
            BackgroundTaskManager.shared.endBackgroundTask()
        }
    }
    
    private func performCleanup(completion: @escaping () -> Void) {
        // 保存数据、清理缓存等
        DispatchQueue.global().async {
            // 清理工作
            Thread.sleep(forTimeInterval: 2)
            
            DispatchQueue.main.async {
                completion()
            }
        }
    }
}

2. 后台 URLSession

class BackgroundSessionManager: NSObject {
    
    static let shared = BackgroundSessionManager()
    
    private lazy var session: URLSession = {
        let config = URLSessionConfiguration.background(withIdentifier: &#34;com.app.background&#34;)
        
        // 配置后台会话
        config.isDiscretionary = true  // 允许系统优化
        config.sessionSendsLaunchEvents = true
        config.shouldUseExtendedBackgroundIdleMode = true
        
        return URLSession(configuration: config, delegate: self, delegateQueue: nil)
    }()
    
    private var completionHandlers: [String: () -> Void] = [:]
    
    private override init() {
        super.init()
    }
    
    // 后台下载
    func downloadFile(from url: URL, completion: @escaping () -> Void) {
        let task = session.downloadTask(with: url)
        
        // 设置最早开始时间(延迟执行)
        task.earliestBeginDate = Date().addingTimeInterval(60)
        
        completionHandlers[task.taskIdentifier.description] = completion
        
        task.resume()
        
        print(&#34;📥 后台下载任务已创建&#34;)
    }
    
    // 处理后台事件
    func handleEventsForBackgroundURLSession(identifier: String, 
                                            completionHandler: @escaping () -> Void) {
        completionHandlers[identifier] = completionHandler
    }
}

extension BackgroundSessionManager: URLSessionDownloadDelegate {
    
    func urlSession(_ session: URLSession, 
                   downloadTask: URLSessionDownloadTask, 
                   didFinishDownloadingTo location: URL) {
        
        print(&#34; 下载完成: \(location)&#34;)
        
        // 处理下载的文件
        // ...
    }
    
    func urlSession(_ session: URLSession, 
                   task: URLSessionTask, 
                   didCompleteWithError error: Error?) {
        
        if let error = error {
            print(&#34; 任务失败: \(error)&#34;)
        }
        
        // 调用完成回调
        if let handler = completionHandlers[task.taskIdentifier.description] {
            handler()
            completionHandlers.removeValue(forKey: task.taskIdentifier.description)
        }
    }
    
    func urlSessionDidFinishEvents(forBackgroundURLSession session: URLSession) {
        print(&#34; 后台会话完成&#34;)
        
        DispatchQueue.main.async {
            if let handler = self.completionHandlers[session.configuration.identifier!] {
                handler()
                self.completionHandlers.removeValue(forKey: session.configuration.identifier!)
            }
        }
    }
}

渲染优化

1. 动画优化

class AnimationOptimizer {
    
    // 使用 CADisplayLink 优化动画
    static func optimizeAnimation(duration: TimeInterval, 
                                  animations: @escaping (CGFloat) -> Void,
                                  completion: (() -> Void)? = nil) {
        
        let displayLink = CADisplayLink(target: self, selector: #selector(updateAnimation))
        
        var startTime: CFTimeInterval?
        var progress: CGFloat = 0
        
        displayLink.add(to: .main, forMode: .common)
        
        // 降低帧率以节省电量
        if #available(iOS 15.0, *) {
            displayLink.preferredFrameRateRange = CAFrameRateRange(
                minimum: 30,
                maximum: 60,
                preferred: 60
            )
        } else {
            displayLink.preferredFramesPerSecond = 30
        }
    }
    
    @objc private static func updateAnimation(displayLink: CADisplayLink) {
        // 更新动画
    }
    
    // 暂停不可见视图的动画
    static func pauseAnimationsForInvisibleViews(in view: UIView) {
        if view.window == nil {
            view.layer.speed = 0
        }
    }
    
    // 恢复动画
    static func resumeAnimations(in view: UIView) {
        view.layer.speed = 1
    }
}

2. 屏幕刷新率优化

class DisplayOptimizer {
    
    static let shared = DisplayOptimizer()
    
    private init() {}
    
    // 根据场景调整刷新率
    func adjustFrameRate(for scenario: Scenario) {
        if #available(iOS 15.0, *) {
            let range: CAFrameRateRange
            
            switch scenario {
            case .video:
                // 视频播放:固定60fps
                range = CAFrameRateRange(minimum: 60, maximum: 60, preferred: 60)
                
            case .scrolling:
                // 滚动:30-120fps
                range = CAFrameRateRange(minimum: 30, maximum: 120, preferred: 120)
                
            case .static:
                // 静态内容:降低刷新率
                range = CAFrameRateRange(minimum: 10, maximum: 30, preferred: 30)
                
            case .lowPower:
                // 低电量模式:最低刷新率
                range = CAFrameRateRange(minimum: 10, maximum: 30, preferred: 10)
            }
            
            // 应用到 CADisplayLink 或动画
            print(&#34;🖥️ 调整刷新率: \(range)&#34;)
        }
    }
    
    enum Scenario {
        case video
        case scrolling
        case static
        case lowPower
    }
}

传感器优化

1. 传感器管理

import CoreMotion

class SensorManager {
    
    static let shared = SensorManager()
    
    private let motionManager = CMMotionManager()
    private var isMonitoring = false
    
    private init() {}
    
    // 开始监控传感器
    func startMonitoring() {
        guard !isMonitoring else { return }
        
        // 检查电量
        let batteryLevel = UIDevice.current.batteryLevel
        
        // 根据电量调整采样频率
        if batteryLevel < 0.2 && batteryLevel > 0 {
            motionManager.accelerometerUpdateInterval = 1.0  // 低频
        } else {
            motionManager.accelerometerUpdateInterval = 0.1  // 正常频率
        }
        
        // 开始更新
        motionManager.startAccelerometerUpdates(to: .main) { data, error in
            guard let data = data else { return }
            self.handleAccelerometerData(data)
        }
        
        isMonitoring = true
        print(&#34;📱 传感器监控已启动&#34;)
    }
    
    // 停止监控
    func stopMonitoring() {
        motionManager.stopAccelerometerUpdates()
        motionManager.stopGyroUpdates()
        motionManager.stopMagnetometerUpdates()
        
        isMonitoring = false
        print(&#34;📱 传感器监控已停止&#34;)
    }
    
    private func handleAccelerometerData(_ data: CMAccelerometerData) {
        // 处理加速度计数据
    }
    
    // 使用 CMMotionActivityManager(更省电)
    func startActivityMonitoring() {
        let activityManager = CMMotionActivityManager()
        
        activityManager.startActivityUpdates(to: .main) { activity in
            guard let activity = activity else { return }
            
            if activity.walking {
                print(&#34;🚶 用户正在走路&#34;)
            } else if activity.running {
                print(&#34;🏃 用户正在跑步&#34;)
            } else if activity.stationary {
                print(&#34;🧍 用户静止&#34;)
            }
        }
    }
}

完整监控方案

综合监控面板

class PowerMonitoringDashboard: UIViewController {
    
    private let batteryMonitor = BatteryMonitor.shared
    private let cpuMonitor = CPUMonitor.shared
    private let networkMonitor = NetworkMonitor.shared
    
    private var dashboardView: UIView!
    private var metricsLabels: [UILabel] = []
    
    private var updateTimer: Timer?
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        setupUI()
        startMonitoring()
    }
    
    private func setupUI() {
        view.backgroundColor = .systemBackground
        
        // 创建仪表板视图
        dashboardView = UIView()
        dashboardView.backgroundColor = UIColor.systemGray6
        dashboardView.layer.cornerRadius = 12
        dashboardView.translatesAutoresizingMaskIntoConstraints = false
        view.addSubview(dashboardView)
        
        NSLayoutConstraint.activate([
            dashboardView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 20),
            dashboardView.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 20),
            dashboardView.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -20),
            dashboardView.heightAnchor.constraint(equalToConstant: 300)
        ])
        
        // 创建指标标签
        let metrics = [&#34;电量&#34;, &#34;CPU&#34;, &#34;网络&#34;, &#34;定位&#34;, &#34;后台任务&#34;]
        
        var previousLabel: UILabel?
        
        for metric in metrics {
            let label = UILabel()
            label.text = &#34;\(metric): --&#34;
            label.font = .monospacedSystemFont(ofSize: 14, weight: .regular)
            label.translatesAutoresizingMaskIntoConstraints = false
            dashboardView.addSubview(label)
            
            NSLayoutConstraint.activate([
                label.leadingAnchor.constraint(equalTo: dashboardView.leadingAnchor, constant: 20),
                label.trailingAnchor.constraint(equalTo: dashboardView.trailingAnchor, constant: -20)
            ])
            
            if let previous = previousLabel {
                label.topAnchor.constraint(equalTo: previous.bottomAnchor, constant: 15).isActive = true
            } else {
                label.topAnchor.constraint(equalTo: dashboardView.topAnchor, constant: 20).isActive = true
            }
            
            metricsLabels.append(label)
            previousLabel = label
        }
    }
    
    private func startMonitoring() {
        // 启动各项监控
        batteryMonitor.startMonitoring()
        cpuMonitor.startMonitoring(interval: 1.0)
        networkMonitor.startMonitoring(interval: 1.0)
        
        // 定时更新UI
        updateTimer = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { [weak self] _ in
            self?.updateMetrics()
        }
    }
    
    private func updateMetrics() {
        // 更新电量
        let batteryLevel = batteryMonitor.getCurrentBatteryLevel()
        let batteryReport = batteryMonitor.getBatteryReport()
        
        var batteryText = &#34;电量: \(Int(batteryLevel * 100))%&#34;
        if let drainRate = batteryReport.drainRate {
            batteryText += &#34; (\(String(format: &#34;%.1f&#34;, drainRate))%/h)&#34;
        }
        metricsLabels[0].text = batteryText
        
        // 更新CPU
        let cpuUsage = cpuMonitor.getCurrentCPUUsage()
        metricsLabels[1].text = &#34;CPU: \(String(format: &#34;%.1f&#34;, cpuUsage))%&#34;
        
        // 更新网络
        if let networkStats = networkMonitor.getNetworkStats() {
            let networkText = &#34;网络: \(String(format: &#34;%.1f&#34;, networkStats.uploadSpeed)) \(String(format: &#34;%.1f&#34;, networkStats.downloadSpeed)) KB/s&#34;
            metricsLabels[2].text = networkText
        }
        
        // 更新定位
       ```swift
        // 更新定位
        metricsLabels[3].text = &#34;定位: \(OptimizedLocationManager.shared.isMonitoring ? &#34;运行中&#34; : &#34;已停止&#34;)&#34;
        
        // 更新后台任务
        let backgroundTaskStatus = BackgroundTaskManager.shared.backgroundTask != .invalid
        metricsLabels[4].text = &#34;后台任务: \(backgroundTaskStatus ? &#34;运行中&#34; : &#34;空闲&#34;)&#34;
        
        // 根据电量状态更新颜色
        updateMetricsColor(batteryLevel: batteryLevel)
    }
    
    private func updateMetricsColor(batteryLevel: Float) {
        let color: UIColor
        
        if batteryLevel < 0.2 {
            color = .systemRed
        } else if batteryLevel < 0.5 {
            color = .systemOrange
        } else {
            color = .systemGreen
        }
        
        metricsLabels[0].textColor = color
    }
    
    override func viewWillDisappear(_ animated: Bool) {
        super.viewWillDisappear(animated)
        
        // 停止监控
        updateTimer?.invalidate()
        updateTimer = nil
    }
    
    deinit {
        batteryMonitor.stopMonitoring()
        cpuMonitor.stopMonitoring()
        networkMonitor.stopMonitoring()
    }
}

电量优化建议系统

class PowerOptimizationAdvisor {
    
    static let shared = PowerOptimizationAdvisor()
    
    struct OptimizationSuggestion {
        let title: String
        let description: String
        let priority: Priority
        let action: (() -> Void)?
        
        enum Priority {
            case high
            case medium
            case low
        }
    }
    
    private init() {}
    
    // 分析并生成优化建议
    func analyzePowerUsage() -> [OptimizationSuggestion] {
        var suggestions: [OptimizationSuggestion] = []
        
        // 1. 检查 CPU 使用率
        let cpuUsage = CPUMonitor.shared.getCurrentCPUUsage()
        if cpuUsage > 50 {
            suggestions.append(OptimizationSuggestion(
                title: &#34;CPU 使用率过高&#34;,
                description: &#34;当前 CPU 使用率 \(String(format: &#34;%.1f&#34;, cpuUsage))%,建议优化计算密集型任务&#34;,
                priority: .high,
                action: {
                    // 提供优化建议或自动优化
                    print(&#34;建议:将计算任务移到后台线程&#34;)
                }
            ))
        }
        
        // 2. 检查网络使用
        if let networkStats = NetworkMonitor.shared.getNetworkStats() {
            if networkStats.uploadSpeed > 100 || networkStats.downloadSpeed > 100 {
                suggestions.append(OptimizationSuggestion(
                    title: &#34;网络流量较大&#34;,
                    description: &#34;当前网络流量较大,建议在 WiFi 环境下使用&#34;,
                    priority: .medium,
                    action: nil
                ))
            }
        }
        
        // 3. 检查定位服务
        if OptimizedLocationManager.shared.isMonitoring {
            let batteryLevel = UIDevice.current.batteryLevel
            if batteryLevel < 0.3 {
                suggestions.append(OptimizationSuggestion(
                    title: &#34;定位服务消耗电量&#34;,
                    description: &#34;当前电量较低,建议降低定位精度或暂停定位&#34;,
                    priority: .high,
                    action: {
                        // 自动降低定位精度
                        OptimizedLocationManager.shared.stopUpdatingLocation()
                        print(&#34;已自动停止定位服务&#34;)
                    }
                ))
            }
        }
        
        // 4. 检查后台刷新
        let backgroundRefreshStatus = UIApplication.shared.backgroundRefreshStatus
        if backgroundRefreshStatus == .available {
            suggestions.append(OptimizationSuggestion(
                title: &#34;后台刷新已启用&#34;,
                description: &#34;后台刷新会消耗额外电量,可在设置中关闭&#34;,
                priority: .low,
                action: nil
            ))
        }
        
        // 5. 检查屏幕亮度
        let brightness = UIScreen.main.brightness
        if brightness > 0.8 {
            suggestions.append(OptimizationSuggestion(
                title: &#34;屏幕亮度较高&#34;,
                description: &#34;当前屏幕亮度 \(Int(brightness * 100))%,建议降低亮度以节省电量&#34;,
                priority: .medium,
                action: {
                    UIScreen.main.brightness = 0.5
                    print(&#34;已自动调整屏幕亮度&#34;)
                }
            ))
        }
        
        return suggestions
    }
    
    // 自动优化
    func autoOptimize() {
        let suggestions = analyzePowerUsage()
        
        // 执行高优先级的优化建议
        let highPrioritySuggestions = suggestions.filter { $0.priority == .high }
        
        for suggestion in highPrioritySuggestions {
            print(&#34;🔧 执行优化: \(suggestion.title)&#34;)
            suggestion.action?()
        }
    }
    
    // 生成优化报告
    func generateOptimizationReport() -> String {
        let suggestions = analyzePowerUsage()
        
        var report = &#34;&#34;&#34;
        
        ========== 电量优化报告 ==========
        生成时间: \(Date())
        
        &#34;&#34;&#34;
        
        if suggestions.isEmpty {
            report += &#34; 当前电量使用良好,无需优化\n&#34;
        } else {
            report += &#34;发现 \(suggestions.count) 项优化建议:\n\n&#34;
            
            for (index, suggestion) in suggestions.enumerated() {
                let priorityEmoji: String
                switch suggestion.priority {
                case .high: priorityEmoji = &#34;🔴&#34;
                case .medium: priorityEmoji = &#34;🟡&#34;
                case .low: priorityEmoji = &#34;🟢&#34;
                }
                
                report += &#34;\(index + 1). \(priorityEmoji) \(suggestion.title)\n&#34;
                report += &#34;   \(suggestion.description)\n\n&#34;
            }
        }
        
        report += &#34;================================\n&#34;
        
        return report
    }
}

电量优化设置页面

class PowerSettingsViewController: UITableViewController {
    
    private let settings = [
        SettingSection(
            title: &#34;定位服务&#34;,
            items: [
                SettingItem(title: &#34;定位精度&#34;, value: &#34;平衡&#34;, action: #selector(adjustLocationAccuracy)),
                SettingItem(title: &#34;后台定位&#34;, value: &#34;关闭&#34;, action: #selector(toggleBackgroundLocation))
            ]
        ),
        SettingSection(
            title: &#34;网络&#34;,
            items: [
                SettingItem(title: &#34;仅 WiFi 下载&#34;, value: &#34;开启&#34;, action: #selector(toggleWiFiOnly)),
                SettingItem(title: &#34;后台刷新&#34;, value: &#34;关闭&#34;, action: #selector(toggleBackgroundRefresh))
            ]
        ),
        SettingSection(
            title: &#34;性能&#34;,
            items: [
                SettingItem(title: &#34;降低动画效果&#34;, value: &#34;关闭&#34;, action: #selector(toggleReduceMotion)),
                SettingItem(title: &#34;自动优化&#34;, value: &#34;开启&#34;, action: #selector(toggleAutoOptimization))
            ]
        )
    ]
    
    struct SettingSection {
        let title: String
        let items: [SettingItem]
    }
    
    struct SettingItem {
        let title: String
        var value: String
        let action: Selector
    }
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        title = &#34;电量优化设置&#34;
        tableView.register(UITableViewCell.self, forCellReuseIdentifier: &#34;Cell&#34;)
    }
    
    // MARK: - Table View Data Source
    
    override func numberOfSections(in tableView: UITableView) -> Int {
        return settings.count
    }
    
    override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return settings[section].items.count
    }
    
    override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
        return settings[section].title
    }
    
    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: &#34;Cell&#34;, for: indexPath)
        
        let item = settings[indexPath.section].items[indexPath.row]
        
        cell.textLabel?.text = item.title
        cell.detailTextLabel?.text = item.value
        cell.accessoryType = .disclosureIndicator
        
        return cell
    }
    
    override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        tableView.deselectRow(at: indexPath, animated: true)
        
        let item = settings[indexPath.section].items[indexPath.row]
        perform(item.action)
    }
    
    // MARK: - Actions
    
    @objc private func adjustLocationAccuracy() {
        let alert = UIAlertController(
            title: &#34;定位精度&#34;,
            message: &#34;选择定位精度级别&#34;,
            preferredStyle: .actionSheet
        )
        
        alert.addAction(UIAlertAction(title: &#34;高精度(耗电)&#34;, style: .default) { _ in
            self.setLocationAccuracy(.best)
        })
        
        alert.addAction(UIAlertAction(title: &#34;平衡&#34;, style: .default) { _ in
            self.setLocationAccuracy(.hundredMeters)
        })
        
        alert.addAction(UIAlertAction(title: &#34;省电&#34;, style: .default) { _ in
            self.setLocationAccuracy(.kilometer)
        })
        
        alert.addAction(UIAlertAction(title: &#34;取消&#34;, style: .cancel))
        
        present(alert, animated: true)
    }
    
    private func setLocationAccuracy(_ accuracy: CLLocationAccuracy) {
        let locationManager = OptimizedLocationManager.shared
        // 设置定位精度
        print(&#34;设置定位精度: \(accuracy)&#34;)
    }
    
    @objc private func toggleBackgroundLocation() {
        // 切换后台定位
        print(&#34;切换后台定位&#34;)
    }
    
    @objc private func toggleWiFiOnly() {
        // 切换仅 WiFi 下载
        let current = UserDefaults.standard.bool(forKey: &#34;WiFiOnly&#34;)
        UserDefaults.standard.set(!current, forKey: &#34;WiFiOnly&#34;)
        print(&#34;仅 WiFi 下载: \(!current)&#34;)
    }
    
    @objc private func toggleBackgroundRefresh() {
        // 切换后台刷新
        print(&#34;切换后台刷新&#34;)
    }
    
    @objc private func toggleReduceMotion() {
        // 切换降低动画效果
        print(&#34;切换降低动画效果&#34;)
    }
    
    @objc private func toggleAutoOptimization() {
        // 切换自动优化
        let current = UserDefaults.standard.bool(forKey: &#34;AutoOptimization&#34;)
        UserDefaults.standard.set(!current, forKey: &#34;AutoOptimization&#34;)
        
        if !current {
            PowerOptimizationAdvisor.shared.autoOptimize()
        }
        
        print(&#34;自动优化: \(!current)&#34;)
    }
}

最佳实践

优化检查清单

## iOS 电量优化检查清单

### ✅ CPU 优化
- [ ] 避免主线程阻塞
- [ ] 使用后台线程处理耗时任务
- [ ] 优化算法和数据结构
- [ ] 减少定时器使用
- [ ] 使用 Instruments 分析 CPU 热点
- [ ] 实现计算结果缓存
- [ ] 降低动画帧率

### ✅ 网络优化
- [ ] 减少网络请求频率
- [ ] 实现请求合并和批处理
- [ ] 使用数据压缩
- [ ] 实现智能缓存策略
- [ ] WiFi 优先策略
- [ ] 后台任务延迟执行
- [ ] 使用 HTTP/2 或 HTTP/3

### ✅ 定位优化
- [ ] 使用合适的定位精度
- [ ] 设置距离过滤器
- [ ] 使用重要位置变化监控
- [ ] 实现定位超时机制
- [ ] 根据电量调整定位策略
- [ ] 不需要时及时停止定位
- [ ] 使用地理围栏替代持续定位

### ✅ 后台任务优化
- [ ] 降低后台刷新频率
- [ ] 使用后台 URLSession
- [ ] 实现任务延迟执行
- [ ] 及时结束后台任务
- [ ] 批量处理后台任务
- [ ] 监控后台任务时长

### ✅ 渲染优化
- [ ] 减少视图层级
- [ ] 使用异步绘制
- [ ] 优化图片加载
- [ ] 降低动画复杂度
- [ ] 使用 CADisplayLink 优化动画
- [ ] 暂停不可见视图的动画
- [ ] 根据场景调整刷新率

### ✅ 传感器优化
- [ ] 降低传感器采样频率
- [ ] 不使用时及时停止
- [ ] 使用 CMMotionActivityManager
- [ ] 批量处理传感器数据
- [ ] 根据电量调整采样策略

### ✅ 监控与分析
- [ ] 实现电量监控系统
- [ ] 收集电量消耗数据
- [ ] 定期分析电量报告
- [ ] 设置电量告警
- [ ] 提供优化建议
- [ ] 实现自动优化

电量优化效果对比

优化项 优化前 优化后 节省电量
CPU 使用 持续 50% 平均 20% 40%
网络请求 每秒 5 次 每 10 秒 1 次 60%
定位服务 GPS 持续定位 重要位置变化 80%
后台刷新 每 15 分钟 每小时 75%
动画渲染 60fps 持续 按需 30fps 50%
总体电量 15%/小时 6%/小时 60%

常见问题解决

Q1: 如何检测应用的电量消耗?

A: 使用多种方法:

class BatteryDiagnostics {
    
    // 1. 使用 Xcode Energy Log
    static func enableEnergyLogging() {
        // 在 Xcode 中:Product → Profile → Energy Log
        print(&#34;使用 Xcode Energy Log 分析电量消耗&#34;)
    }
    
    // 2. 使用 Instruments
    static func useInstruments() {
        // 使用 Energy Log 和 Time Profiler
        print(&#34;使用 Instruments 分析&#34;)
    }
    
    // 3. 监控电量变化
    static func monitorBatteryDrain() {
        let monitor = BatteryMonitor.shared
        monitor.startMonitoring()
        
        // 记录一段时间的电量变化
        DispatchQueue.main.asyncAfter(deadline: .now() + 3600) {
            if let drainRate = monitor.calculateBatteryDrainRate() {
                print(&#34;电量消耗速率: \(drainRate)%/小时&#34;)
            }
        }
    }
    
    // 4. 使用 MetricKit(iOS 13+)
    static func useMetricKit() {
        // 收集电量诊断数据
        print(&#34;使用 MetricKit 收集电量数据&#34;)
    }
}

Q2: 低电量模式如何适配?

A: 实现低电量模式检测和适配:

class LowPowerModeManager {
    
    static let shared = LowPowerModeManager()
    
    private init() {
        // 监听低电量模式变化
        NotificationCenter.default.addObserver(
            self,
            selector: #selector(powerStateDidChange),
            name: Notification.Name.NSProcessInfoPowerStateDidChange,
            object: nil
        )
    }
    
    // 检查是否处于低电量模式
    func isLowPowerModeEnabled() -> Bool {
        return ProcessInfo.processInfo.isLowPowerModeEnabled
    }
    
    @objc private func powerStateDidChange() {
        let isLowPower = isLowPowerModeEnabled()
        print(&#34;⚡️ 低电量模式: \(isLowPower ? &#34;开启&#34; : &#34;关闭&#34;)&#34;)
        
        if isLowPower {
            enablePowerSavingMode()
        } else {
            disablePowerSavingMode()
        }
    }
    
    // 启用省电模式
    private func enablePowerSavingMode() {
        print(&#34;🔋 启用省电模式&#34;)
        
        // 1. 降低定位精度
        OptimizedLocationManager.shared.stopUpdatingLocation()
        
        // 2. 减少网络请求
        NetworkOptimizer.shared.batchInterval = 2.0
        
        // 3. 降低动画帧率
        DisplayOptimizer.shared.adjustFrameRate(for: .lowPower)
        
        // 4. 停止传感器监控
        SensorManager.shared.stopMonitoring()
        
        // 5. 暂停后台任务
        BackgroundTaskManager.shared.endBackgroundTask()
        
        // 6. 降低屏幕亮度
        UIScreen.main.brightness = max(UIScreen.main.brightness - 0.2, 0.3)
    }
    
    // 禁用省电模式
    private func disablePowerSavingMode() {
        print(&#34;🔋 禁用省电模式&#34;)
        
        // 恢复正常设置
        NetworkOptimizer.shared.batchInterval = 0.5
        DisplayOptimizer.shared.adjustFrameRate(for: .scrolling)
    }
}

Q3: 如何优化推送通知的电量消耗?

A: 优化推送策略:

class PushNotificationOptimizer {
    
    static let shared = PushNotificationOptimizer()
    
    private init() {}
    
    // 智能推送策略
    func shouldSendPushNotification(priority: Priority) -> Bool {
        
        // 1. 检查电量
        let batteryLevel = UIDevice.current.batteryLevel
        if batteryLevel < 0.1 && priority != .critical {
            print(&#34;⚡️ 电量过低,跳过非关键推送&#34;)
            return false
        }
        
        // 2. 检查低电量模式
        if ProcessInfo.processInfo.isLowPowerModeEnabled && priority == .low {
            print(&#34;⚡️ 低电量模式,跳过低优先级推送&#34;)
            return false
        }
        
        // 3. 检查时间段(夜间减少推送)
        let hour = Calendar.current.component(.hour, from: Date())
        if (22...6).contains(hour) && priority != .critical {
            print(&#34;🌙 夜间时段,跳过非关键推送&#34;)
            return false
        }
        
        return true
    }
    
    enum Priority {
        case critical  // 关键通知
        case high      // 高优先级
        case normal    // 普通
        case low       // 低优先级
    }
    
    // 批量推送
    func batchPushNotifications(notifications: [UNNotificationRequest]) {
        // 合并相似的通知
        let grouped = Dictionary(grouping: notifications) { $0.content.categoryIdentifier }
        
        for (category, requests) in grouped {
            if requests.count > 1 {
                // 创建合并通知
                let content = UNMutableNotificationContent()
                content.title = category
                content.body = &#34;您有 \(requests.count) 条新消息&#34;
                
                let request = UNNotificationRequest(
                    identifier: UUID().uuidString,
                    content: content,
                    trigger: nil
                )
                
                UNUserNotificationCenter.current().add(request)
                
                print(&#34;📦 合并了 \(requests.count) 条通知&#34;)
            } else {
                // 单独发送
                requests.forEach { UNUserNotificationCenter.current().add($0) }
            }
        }
    }
}

Q4: 如何监控第三方 SDK 的电量消耗?

A: 实现 SDK 监控:

class SDKPowerMonitor {
    
    static let shared = SDKPowerMonitor()
    
    private var sdkMetrics: [String: SDKMetric] = [:]
    
    struct SDKMetric {
        var cpuUsage: Double = 0
        var networkBytes: Int64 = 0
        var locationUpdates: Int = 0
        var startTime: Date
    }
    
    private init() {}
    
    // 开始监控 SDK
    func startMonitoring(sdkName: String) {
        sdkMetrics[sdkName] = SDKMetric(startTime: Date())
        print(&#34;📊 开始监控 SDK: \(sdkName)&#34;)
    }
    
    // 停止监控并生成报告
    func stopMonitoring(sdkName: String) -> String? {
        guard let metric = sdkMetrics[sdkName] else {
            return nil
        }
        
        let duration = Date().timeIntervalSince(metric.startTime)
        
        let report = &#34;&#34;&#34;
        
        ========== SDK 电量报告 ==========
        SDK 名称: \(sdkName)
        运行时长: \(String(format: &#34;%.1f&#34;, duration)) 秒
        CPU 使用: \(String(format: &#34;%.1f&#34;, metric.cpuUsage))%
        网络流量: \(metric.networkBytes / 1024) KB
        定位次数: \(metric.locationUpdates)
        ================================
        
        &#34;&#34;&#34;
        
        sdkMetrics.removeValue(forKey: sdkName)
        
        return report
    }
    
    // 记录 SDK 活动
    func recordActivity(sdkName: String, type: ActivityType) {
        guard var metric = sdkMetrics[sdkName] else { return }
        
        switch type {
        case .cpuUsage(let usage):
            metric.cpuUsage = usage
        case .networkBytes(let bytes):
            metric.networkBytes += bytes
        case .locationUpdate:
            metric.locationUpdates += 1
        }
        
        sdkMetrics[sdkName] = metric
    }
    
    enum ActivityType {
        case cpuUsage(Double)
        case networkBytes(Int64)
        case locationUpdate
    }
}

性能优化工具

1. Xcode Instruments

class InstrumentsHelper {
    
    // 使用 Energy Log
    static func analyzeWithEnergyLog() {
        print(&#34;使用 Xcode Energy Log 分析:&#34;)
    }
    
    // 使用 Time Profiler
    static func analyzeWithTimeProfiler() {
        print(&#34;使用 Time Profiler 分析 CPU:&#34;)
    }
    
    // 使用 Network
    static func analyzeWithNetwork() {
        print(&#34;使用 Network Instrument 分析:&#34;)
    }
}

2. 性能测试

class PowerPerformanceTest {
    
    // 电量消耗测试
    static func testBatteryDrain(duration: TimeInterval = 3600) {
        print(&#34;🧪 开始电量消耗测试(\(duration/60) 分钟)&#34;)
        
        let monitor = BatteryMonitor.shared
        monitor.startMonitoring()
        
        let startLevel = monitor.getCurrentBatteryLevel()
        let startTime = Date()
        
        DispatchQueue.main.asyncAfter(deadline: .now() + duration) {
            let endLevel = monitor.getCurrentBatteryLevel()
            let endTime = Date()
            
            let drain = (startLevel - endLevel) * 100
            let actualDuration = endTime.timeIntervalSince(startTime) / 3600
            let drainRate = drain / Float(actualDuration)
            
            print(&#34;&#34;&#34;
            
            ========== 电量测试结果 ==========
            测试时长: \(String(format: &#34;%.1f&#34;, actualDuration)) 小时
            电量消耗: \(String(format: &#34;%.1f&#34;, drain))%
            消耗速率: \(String(format: &#34;%.1f&#34;, drainRate))%/小时
            ================================
            
            &#34;&#34;&#34;)
        }
    }
    
    // CPU 压力测试
    static func testCPULoad() {
        print(&#34;🧪 开始 CPU 压力测试&#34;)
        
        let monitor = CPUMonitor.shared
        monitor.startMonitoring(interval: 0.1)
        
        // 模拟 CPU 密集任务
        DispatchQueue.global(qos: .userInitiated).async {
            var result = 0.0
            for i in 0..

3531. 统计被覆盖的建筑

解法一

思路和算法

根据建筑被覆盖的定义,当一个建筑在四个方向都至少存在一个其他建筑时,该建筑被覆盖。为了计算被覆盖的建筑数量,需要分别判断每个建筑是否被覆盖,因此需要分别统计每个 $x$ 坐标和每个 $y$ 坐标的所有建筑的列表。

使用两个哈希表分别记录每个 $x$ 坐标的所有建筑列表和每个 $y$ 坐标的所有建筑列表,两个哈希表分别为 $x$ 分组哈希表和 $y$ 分组哈希表。遍历数组 $\textit{buildings}$ 将所有建筑加入两个哈希表,然后将两个哈希表中的每个 $x$ 坐标和 $y$ 坐标对应的建筑列表排序,排序方法如下:在 $x$ 分组哈希表中,将每个 $x$ 坐标的所有建筑列表按 $y$ 坐标升序排序;在 $y$ 分组哈希表中,将每个 $y$ 坐标的所有建筑列表按 $x$ 坐标升序排序。

排序结束之后,即可判断每个建筑在 $x$ 坐标方向和 $y$ 坐标方向是否被覆盖。遍历所有建筑,对于位于坐标 $(x, y)$ 的建筑,判断方法如下。

  • 从 $y$ 分组哈希表中得到该建筑的相同 $y$ 坐标的所有建筑的列表,列表已经按 $x$ 坐标升序排序,判断当前 $x$ 坐标是否为列表中的最小值或最大值,如果既不是最小值也不是最大值,则该建筑在 $x$ 坐标方向被覆盖。

  • 从 $x$ 分组哈希表中得到该建筑的相同 $x$ 坐标的所有建筑的列表,列表已经按 $y$ 坐标升序排序,判断当前 $y$ 坐标是否为列表中的最小值或最大值,如果既不是最小值也不是最大值,则该建筑在 $y$ 坐标方向被覆盖。

当一个建筑同时在 $x$ 坐标方向和 $y$ 坐标方向被覆盖时,该建筑被覆盖。

遍历结束之后,即可得到被覆盖的建筑数量。

代码

###Java

class Solution {
    public int countCoveredBuildings(int n, int[][] buildings) {
        Map<Integer, List<Integer>> xToBuildings = new HashMap<Integer, List<Integer>>();
        Map<Integer, List<Integer>> yToBuildings = new HashMap<Integer, List<Integer>>();
        for (int[] building : buildings) {
            int x = building[0], y = building[1];
            xToBuildings.putIfAbsent(x, new ArrayList<Integer>());
            xToBuildings.get(x).add(y);
            yToBuildings.putIfAbsent(y, new ArrayList<Integer>());
            yToBuildings.get(y).add(x);
        }
        Set<Map.Entry<Integer, List<Integer>>> xEntries = xToBuildings.entrySet();
        Set<Map.Entry<Integer, List<Integer>>> yEntries = yToBuildings.entrySet();
        for (Map.Entry<Integer, List<Integer>> entry : xEntries) {
            Collections.sort(entry.getValue());
        }
        for (Map.Entry<Integer, List<Integer>> entry : yEntries) {
            Collections.sort(entry.getValue());
        }
        int count = 0;
        for (int[] building : buildings) {
            int x = building[0], y = building[1];
            List<Integer> xList = yToBuildings.get(y);
            List<Integer> yList = xToBuildings.get(x);
            int xMin = xList.get(0), xMax = xList.get(xList.size() - 1);
            int yMin = yList.get(0), yMax = yList.get(yList.size() - 1);
            if (x > xMin && x < xMax && y > yMin && y < yMax) {
                count++;
            }
        }
        return count;
    }
}

###C#

public class Solution {
    public int CountCoveredBuildings(int n, int[][] buildings) {
        IDictionary<int, IList<int>> xToBuildings = new Dictionary<int, IList<int>>();
        IDictionary<int, IList<int>> yToBuildings = new Dictionary<int, IList<int>>();
        foreach (int[] building in buildings) {
            int x = building[0], y = building[1];
            xToBuildings.TryAdd(x, new List<int>());
            xToBuildings[x].Add(y);
            yToBuildings.TryAdd(y, new List<int>());
            yToBuildings[y].Add(x);
        }
        foreach (KeyValuePair<int, IList<int>> pair in xToBuildings) {
            ((List<int>) pair.Value).Sort();
        }
        foreach (KeyValuePair<int, IList<int>> pair in yToBuildings) {
            ((List<int>) pair.Value).Sort();
        }
        int count = 0;
        foreach (int[] building in buildings) {
            int x = building[0], y = building[1];
            IList<int> xIList = yToBuildings[y];
            IList<int> yIList = xToBuildings[x];
            int xMin = xIList[0], xMax = xIList[xIList.Count - 1];
            int yMin = yIList[0], yMax = yIList[yIList.Count - 1];
            if (x > xMin && x < xMax && y > yMin && y < yMax) {
                count++;
            }
        }
        return count;
    }
}

复杂度分析

  • 时间复杂度:$O(m \log m)$,其中 $m$ 是数组 $\textit{buildings}$ 的长度。将所有建筑存入两个哈希表的时间是 $O(m)$,排序的时间是 $O(m \log m)$,排序之后计算被覆盖的建筑数量的时间是 $O(m)$,因此时间复杂度是 $O(m \log m)$。

  • 空间复杂度:$O(m)$,其中 $m$ 是数组 $\textit{buildings}$ 的长度。哈希表的空间是 $O(m)$。

解法二

思路和算法

判断一个建筑是否被覆盖时,需要知道如下信息。

  • 在相同 $y$ 坐标的所有建筑的列表中,该建筑的 $x$ 坐标是否为列表中的最小值或最大值。

  • 在相同 $x$ 坐标的所有建筑的列表中,该建筑的 $y$ 坐标是否为列表中的最小值或最大值。

因此,不需要维护每个 $x$ 坐标和每个 $y$ 坐标的所有建筑,只需要维护每个 $x$ 坐标的最小 $y$ 坐标和最大 $y$ 坐标,以及每个 $y$ 坐标的最小 $x$ 坐标和最大 $x$ 坐标。遍历数组 $\textit{buildings}$ 之后,将最小坐标与最大坐标的信息存入哈希表。再次遍历数组,即可根据哈希表中的最小坐标与最大坐标的信息判断每个建筑是否被覆盖,计算被覆盖的建筑数量。

代码

###Java

class Solution {
    public int countCoveredBuildings(int n, int[][] buildings) {
        Map<Integer, int[]> xToMinMax = new HashMap<Integer, int[]>();
        Map<Integer, int[]> yToMinMax = new HashMap<Integer, int[]>();
        for (int[] building : buildings) {
            int x = building[0], y = building[1];
            xToMinMax.putIfAbsent(x, new int[]{Integer.MAX_VALUE, Integer.MIN_VALUE});
            int[] yMinMax = xToMinMax.get(x);
            yMinMax[0] = Math.min(yMinMax[0], y);
            yMinMax[1] = Math.max(yMinMax[1], y);
            yToMinMax.putIfAbsent(y, new int[]{Integer.MAX_VALUE, Integer.MIN_VALUE});
            int[] xMinMax = yToMinMax.get(y);
            xMinMax[0] = Math.min(xMinMax[0], x);
            xMinMax[1] = Math.max(xMinMax[1], x);
        }
        int count = 0;
        for (int[] building : buildings) {
            int x = building[0], y = building[1];
            int[] xMinMax = yToMinMax.get(y);
            int[] yMinMax = xToMinMax.get(x);
            int xMin = xMinMax[0], xMax = xMinMax[1];
            int yMin = yMinMax[0], yMax = yMinMax[1];
            if (x > xMin && x < xMax && y > yMin && y < yMax) {
                count++;
            }
        }
        return count;
    }
}

###C#

public class Solution {
    public int CountCoveredBuildings(int n, int[][] buildings) {
        IDictionary<int, int[]> xToMinMax = new Dictionary<int, int[]>();
        IDictionary<int, int[]> yToMinMax = new Dictionary<int, int[]>();
        foreach (int[] building in buildings) {
            int x = building[0], y = building[1];
            xToMinMax.TryAdd(x, new int[]{int.MaxValue, int.MinValue});
            int[] yMinMax = xToMinMax[x];
            yMinMax[0] = Math.Min(yMinMax[0], y);
            yMinMax[1] = Math.Max(yMinMax[1], y);
            yToMinMax.TryAdd(y, new int[]{int.MaxValue, int.MinValue});
            int[] xMinMax = yToMinMax[y];
            xMinMax[0] = Math.Min(xMinMax[0], x);
            xMinMax[1] = Math.Max(xMinMax[1], x);
        }
        int count = 0;
        foreach (int[] building in buildings) {
            int x = building[0], y = building[1];
            int[] xMinMax = yToMinMax[y];
            int[] yMinMax = xToMinMax[x];
            int xMin = xMinMax[0], xMax = xMinMax[1];
            int yMin = yMinMax[0], yMax = yMinMax[1];
            if (x > xMin && x < xMax && y > yMin && y < yMax) {
                count++;
            }
        }
        return count;
    }
}

复杂度分析

  • 时间复杂度:$O(m)$,其中 $m$ 是数组 $\textit{buildings}$ 的长度。遍历所有建筑维护两个哈希表的时间是 $O(m)$,计算被覆盖的建筑数量的时间是 $O(m)$,因此时间复杂度是 $O(m)$。

  • 空间复杂度:$O(m)$,其中 $m$ 是数组 $\textit{buildings}$ 的长度。哈希表的空间是 $O(m)$。

统计行列的最小值和最大值(Python/Java/C++/Go)

分析

如果一个点在同一行的最左边,那么它左边没有点;如果一个点在同一行的最右边,那么它右边没有点。

如果一个点在同一列的最上边,那么它上边没有点;如果一个点在同一列的最下边,那么它下边没有点。

反之,如果一个点不在同一行的最左边也不在最右边,那么这个点左右都有点;如果一个点不在同一列的最上边也不在最下边,那么这个点上下都有点。

算法

记录同一行的最小横坐标和最大横坐标,同一列的最小纵坐标和最大纵坐标。

对于每个建筑 $(x,y)$,如果 $x$ 在这一行的最小值和最大值之间(不能相等),$y$ 在这一列的最小值和最大值之间(不能相等),那么答案加一。

本题视频讲解,欢迎点赞关注~

###py

class Solution:
    def countCoveredBuildings(self, n: int, buildings: List[List[int]]) -> int:
        row_min = [n + 1] * (n + 1)
        row_max = [0] * (n + 1)
        col_min = [n + 1] * (n + 1)
        col_max = [0] * (n + 1)

        for x, y in buildings:
            # 手写 min max 更快
            if x < row_min[y]: row_min[y] = x
            if x > row_max[y]: row_max[y] = x
            if y < col_min[x]: col_min[x] = y
            if y > col_max[x]: col_max[x] = y

        ans = 0
        for x, y in buildings:
            if row_min[y] < x < row_max[y] and col_min[x] < y < col_max[x]:
                ans += 1
        return ans

###java

class Solution {
    public int countCoveredBuildings(int n, int[][] buildings) {
        int[] rowMin = new int[n + 1];
        int[] rowMax = new int[n + 1];
        int[] colMin = new int[n + 1];
        int[] colMax = new int[n + 1];
        Arrays.fill(rowMin, n + 1);
        Arrays.fill(colMin, n + 1);

        for (int[] p : buildings) {
            int x = p[0], y = p[1];
            rowMin[y] = Math.min(rowMin[y], x);
            rowMax[y] = Math.max(rowMax[y], x);
            colMin[x] = Math.min(colMin[x], y);
            colMax[x] = Math.max(colMax[x], y);
        }

        int ans = 0;
        for (int[] p : buildings) {
            int x = p[0], y = p[1];
            if (rowMin[y] < x && x < rowMax[y] && colMin[x] < y && y < colMax[x]) {
                ans++;
            }
        }
        return ans;
    }
}

###cpp

class Solution {
public:
    int countCoveredBuildings(int n, vector<vector<int>>& buildings) {
        vector<int> row_min(n + 1, INT_MAX), row_max(n + 1);
        vector<int> col_min(n + 1, INT_MAX), col_max(n + 1);
        for (auto& p : buildings) {
            int x = p[0], y = p[1];
            row_min[y] = min(row_min[y], x);
            row_max[y] = max(row_max[y], x);
            col_min[x] = min(col_min[x], y);
            col_max[x] = max(col_max[x], y);
        }

        int ans = 0;
        for (auto& p : buildings) {
            int x = p[0], y = p[1];
            if (row_min[y] < x && x < row_max[y] && col_min[x] < y && y < col_max[x]) {
                ans++;
            }
        }
        return ans;
    }
};

###go

func countCoveredBuildings(n int, buildings [][]int) (ans int) {
type pair struct{ min, max int }
row := make([]pair, n+1)
col := make([]pair, n+1)
for i := 1; i <= n; i++ {
row[i].min = math.MaxInt
col[i].min = math.MaxInt
}

add := func(m []pair, x, y int) {
m[y].min = min(m[y].min, x)
m[y].max = max(m[y].max, x)
}
isInner := func(m []pair, x, y int) bool {
return m[y].min < x && x < m[y].max
}

for _, p := range buildings {
x, y := p[0], p[1]
add(row, x, y) // x 加到 row[y] 中
add(col, y, x) // y 加到 col[x] 中
}

for _, p := range buildings {
x, y := p[0], p[1]
if isInner(row, x, y) && isInner(col, y, x) {
ans++
}
}
return
}

复杂度分析

  • 时间复杂度:$\mathcal{O}(n+m)$,其中 $m$ 是 $\textit{buildings}$ 的长度。
  • 空间复杂度:$\mathcal{O}(n)$。

分类题单

如何科学刷题?

  1. 滑动窗口与双指针(定长/不定长/单序列/双序列/三指针/分组循环)
  2. 二分算法(二分答案/最小化最大值/最大化最小值/第K小)
  3. 单调栈(基础/矩形面积/贡献法/最小字典序)
  4. 网格图(DFS/BFS/综合应用)
  5. 位运算(基础/性质/拆位/试填/恒等式/思维)
  6. 图论算法(DFS/BFS/拓扑排序/基环树/最短路/最小生成树/网络流)
  7. 动态规划(入门/背包/划分/状态机/区间/状压/数位/数据结构优化/树形/博弈/概率期望)
  8. 常用数据结构(前缀和/差分/栈/队列/堆/字典树/并查集/树状数组/线段树)
  9. 数学算法(数论/组合/概率期望/博弈/计算几何/随机算法)
  10. 贪心与思维(基本贪心策略/反悔/区间/字典序/数学/思维/脑筋急转弯/构造)
  11. 链表、树与回溯(前后指针/快慢指针/DFS/BFS/直径/LCA)
  12. 字符串(KMP/Z函数/Manacher/字符串哈希/AC自动机/后缀数组/子序列自动机)

我的题解精选(已分类)

欢迎关注 B站@灵茶山艾府

模拟

解法:模拟

把 $x$ 值相同的建筑放在一个 vector 里,对它们的 $y$ 值排序,就能知道每个建筑上下有没有其它建筑。左右的处理类似。

复杂度 $\mathcal{O}(n\log n)$。

参考代码(c++)

class Solution {
public:
    int countCoveredBuildings(int n, vector<vector<int>>& buildings) {
        int m = buildings.size();
        int cnt[m];
        memset(cnt, 0, sizeof(cnt));

        typedef pair<int, int> pii;
        // 把 x 值相同的建筑放在同一个 vector 里,对它们的 y 值排序
        unordered_map<int, vector<pii>> X;
        // 把 y 值相同的建筑放在同一个 vector 里,对它们的 x 值排序
        unordered_map<int, vector<pii>> Y;
        for (int i = 0; i < m; i++) {
            X[buildings[i][0]].push_back({buildings[i][1], i});
            Y[buildings[i][1]].push_back({buildings[i][0], i});
        }

        for (auto &p : X) {
            auto &vec = p.second;
            sort(vec.begin(), vec.end());
            // 标记下面有其它建筑的房子
            for (int i = 1; i < vec.size(); i++) cnt[vec[i].second]++;
            // 标记上面有其它建筑的房子
            for (int i = 0; i + 1 < vec.size(); i++) cnt[vec[i].second]++;
        }
        for (auto &p : Y) {
            auto &vec = p.second;
            sort(vec.begin(), vec.end());
            // 标记左面有其它建筑的房子
            for (int i = 1; i < vec.size(); i++) cnt[vec[i].second]++;
            // 标记右面有其它建筑的房子
            for (int i = 0; i + 1 < vec.size(); i++) cnt[vec[i].second]++;
        }

        int ans = 0;
        // 需要有 4 个标记
        for (int i = 0; i < m; i++) if (cnt[i] == 4) ans++;
        return ans;
    }
};

ChatGPT 三周年之际,我怀念我写得很烂的时候

米色墙纸

我翻了翻我向 ChatGPT 提的第一个任务是什么:不出所料,果然是文字工作,写一个英语文书。后面还有随大流,让它帮我做一个减肥食谱。当年标志性的黑绿配色,真是唤醒人的记忆。

那时的 GPT 还不像现在这般「巧言令色」,也不如现在智能,长长的文本我要截断成几节,每一次发过去都要在开头附上 prompt,保证它理解任务。

三年前,ChatGPT 像彗星一样出现,不只是它在事务型工作上的便捷和智能,恰恰是它在这种对话、探讨当中,闪现出了「像人一样」的苗头。不管是记忆能力,还是绝不重复的语句,它第一次让人意识到,纯粹的二进制语言,居然可以有这样的表现。

它逻辑通顺、情感充沛、几近完美。从此「表达的门槛」不存在了,语病、错字、词不达意,都可以交给吸收了亿万数据的大语言模型,由它生产不会出错的成品——甚至只需一次输入。

但代价是什么?文学评论中有一种说法叫「米色散文 beige prose」,指的是语言平实、构简洁的行文风格,类似于中文里的「描白」。这种文风简练、舒适,但也因此缺乏识别度,像米白这种颜色一样,不会出错也不会出彩。

像极了 ChatGPT 会给出的东西,尽管现在三年过去,模型的更新一次比一次强,但始终不会脱离 LLM、transformer 最最底层的核心:概率。

概率的暴政

其实平心而论,GPT 的口吻和腔调如今已经形成一种「AI 风味」,还真有了一定的识别度。在 GPT 5.1 上线之前,在 OpenRouter 平台以隐名模型上线,也被网友通过和往届模型的回答相似性做比较,找出来是 OpenAI 的出品。

ChatGPT 几代以来,各自都有不同的文风:经典的「不是……而是……」,更早一点的「接住」「我在」,历久弥新的破折号、加粗、还有不分青红皂白就出现的 emoji 表情符号。

虽然说,这些小花招并不是总讨人喜欢,但不会出大错:本质上,大语言模型是在「预测下一个词」,它依据概率行事。只要踩着最大公约数走,又能坏到哪儿去呢?

不过,一个冷知识是,模型算法并不总是选择预测中概率最高的词——这解释了为什么同一个 prompt 会得到同一个大意下不同的结果。算法工程中会引入 Temprature、Top-P、Top-K 等方法,为结果注入随机性。

为了符合人类反馈强化学习(RLHF)中对「有用性」和「安全性」的定义,这些参数带来变化,但它们仍然必须在「概率较高的一组词」里抽样。所以算法并不完全输出平均值,而是会画一个圈,在不出大错的情况下,进行一些小小的发挥。

于是,三年当中,从小红书里的探店文案,到年终总结里的自我剖析,再到营销号的起号文案——你会发现一种惊人的相似性,所有的文字都变得通顺了,所有的观点都变得「不是……而是」了,偶尔有些不错的发挥,可总体而言,所有的情绪也都变得粗钝了。ChatGPT 带来一种无风险的创造力,也是概率的暴政。某种程度上,算法厌恶惊喜,它的本质是平滑。

不过无论如何,AI 味道的内容已经渗入我们的生活,我们也逐渐不再为此暴跳如雷。我们和 AI 形成了一种诡谲的默契:为了效率与得体,可以心甘情愿地让渡了部分性格。

思维的逆行

如果说前两年我们在训练 AI,那么第三年,AI 开始训练我们。尤其到了第三年时,各种应用工具都越来越丝滑,也越来越全能的情况下,用户和 AI 的关系,走向了一种奇异的「共生」。

这体现在,我们已经分不清谁在训练谁。

起初,我们以为自己在训练 AI。我们给它数据,给它反馈,教它像人一样说话。

除了工程师,没有人是为了训练它而用,都是要解决具体问题的,需要它交付答案乃至更复杂的成果的。于是,为了得到更精准的答案,我们开始钻研「提示词工程」(Prompt Engineering)。我们学会了把复杂的、充满歧义的人类想法,拆解成条理清晰、逻辑递进的指令。

在提问之前,我们的脑子里会先进行一轮「预处理」,剔除掉那些过于感性、过于跳跃的念头,因为我们潜意识里知道:「AI 不明白这些个东西,要用它能听懂的方式下指令才行。」

使用工具的过程,就是在被工具形塑 ——这句话已经说倦了。所以,在一个强调平滑的大语言模型面前,我们也变了,变得更合乎逻辑,更有效率了,也更像机器了。

看着屏幕上飞速生成的文字,我们既感到「一切尽在掌握」的快感,又感到一种主体性流失的虚无在暗中扼住喉咙。

唯一留下的

「这也算更新?」到第三年时,ChatGPT 的更新已经完全不像曾经那样 引起惊呼,更多的是吐槽和埋怨。苹果用了十多年才做到的事,OpenAI 三年就做到了。

然而吐槽归吐槽,用还是在用。ChatGPT 如今是坐拥 7 亿用户的超级巨头,在它生日这一天,有很多的「生贺」——连罗伯特都酸了。

再联系到 GPT 5 上线时,全球各地用户对 4o 被强制下架的不满和抗议,你不得不正视一件事:我们和 ChatGPT 之间,还有一个关系维度叫「情感维度」。

越来越多的人在向 ChatGPT 倾诉那些无法对活人说出口的秘密。听起来很悲哀,但如果你真的体验过,你会发现其中的张力极其迷人:你知道屏幕对面是一堆冰冷的矩阵乘法,你知道它的「共情」只是基于统计学的模仿。但在某些时刻,这种「模拟的理解」比「真实的不耐烦」要温柔得多。

人类的倾听往往带着评判,带着「我早告诉过你」的傲慢,或者带着急于给出建议的焦虑。而 AI 只是倾听(或者说,处理),只是安抚,它提供了一种「无风险的亲密」,还是无条件的。

「论迹不论心」,何况 GPT 都没有心,在一个没有实体的对象面前,好像人才能真正意义上的卸下防备。

情感维度的张力,恰恰最能代表我们和 ChatGPT 之间的关系:常常帮助,总是交心,偶尔纠结。

这种关系,也代表了我们和人工智能的第一个阶段。三年挺长的,但又还很短,只是人类和技术漫长共舞当中,一小段浅尝辄止的舞步。

在下一个三年,又一个三年当中,我们会继续停留在这种充满张力的关系中,而我们所能做的,最「人类」的事情,就是保持那一点点偶尔的纠结——这样才能证明,那个坐在屏幕前的,依然是一个复杂、矛盾、无法被完全计算的人。

#欢迎关注爱范儿官方微信公众号:爱范儿(微信号:ifanr),更多精彩内容第一时间为您奉上。

爱范儿 | 原文链接 · 查看评论 · 新浪微博


众筹历史第一后,这家 3D 打印「黑马」获高瓴、美团、顺为同时押注数亿元

作者|苏子华
编辑|郑玄
 

刚刚,极客公园获悉,3D 打印「黑马」快造科技(Snapmaker)正式宣布完成数亿元 B 轮融资。由高瓴创投、美团联合领投,顺为资本、美团龙珠、南山战新投跟投,老股东同创伟业、东证资本继续加注。

Snapmaker 今年在行业里备受关注,核心原因是其 8 月份推出的 3D 打印机 Snapmaker U1,在 Kickstarter 上获得超 2000 万美元(约 1.5 亿人民币)的众筹成绩,刷新 3D 打印机历史众筹金额最高记录,一鸣惊人。其在国内售价显示为 5999 元一台。

严格意义上来讲,这是 Snapmaker 第一次推出纯粹的 3D 打印机产品。在此之前,他们主推的产品是集成 3D 打印、激光雕刻、CNC 雕刻切割技术于一体的「三合一」桌面制造工具。

据团队介绍,Snapmaker 成立于 2016 年,创始人陈学栋获得过 WRO 世界机器人奥林匹克竞赛最佳技术奖,保送厦门大学。

他们的首款「三合一」多功能打印机 Snapmaker Original 于 2017 年推出,在 Kickstarter 众筹 1500 万人民币,创中国项目第一;2019 年 5 月,Snapmaker 2.0 再登 Kickstarter,筹得 5400 万人民币(约 785 万美元),刷新科技类目历史第一。

然而,「三合一」系列产品的市场,后续逐渐被 3D 打印、激光雕刻、CNC 三个赛道里优秀的单一功能消费级产品所侵蚀。

 

此前,消费者需花费一两千美元购买一台多功能产品,如今只需五六百美元就能分别买到 3D 打印机、激光设备和 CNC 设备——多功能产品的价格优势不再明显,且单一功能产品的消费化程度、应用场景更加精准。

Snapmaker 团队告诉极客公园,他们因为原有「三合一」产品,对 3D 打印、激光雕刻、CNC 三个赛道的行业发展、市场规模及竞争态势均有深入了解。他们当时判断,3D 打印是三大赛道中规模最大、潜力最强的领域——激光和 CNC 均属于减材制造,而 3D 打印属于增材制造,应用场景更具想象空间。

于是,他们早在 2020 年就启动了单一功能产品的 ID 设计,2021 年完成原型研发。最终,直到 2023 年,创始人陈学栋最终确定集中资源攻坚,聚焦消费级 3D 打印产品。

Snapmaker 团队通过分析千余条用户反馈及深度访谈发现:多色打印虽是用户购买新设备的主要动力,但普遍存在效率低、材料浪费等痛点。

于是,基于过往在多工具头协作与高精度部件自制方面的技术积累,Snapmaker U1 成为国内第一个推出独立四头并联系统的产品,能够实现多材料快速切换,提升了打印速度、更加省料,尤其拓展了多色柔性软料的应用空间。

对于新一轮融资,Snapmaker 创始人陈学栋表示,将重点推进三方面工作:「一是加速核心技术研发,攻克多色打印、高速成型等用户痛点;二是拓展全球人才布局,特别是招募硬件研发、AI 软件与内容生态等领域的顶尖人才;三是构建开放生态,联合创作者、开发者与供应链伙伴,降低创造门槛,让 3D 打印成为人人可用的 『通用性』 创造工具。」

有行业人士评价,3D 打印仍处于早期阶段,受众群体仍以创客为主,还未进入主流大众消费市场。产品形态、易用性、材料便捷性、成本控制及生态完善度均有较大提升空间。

不过,3D 打印市场在国内的发展仍然是超出预期。已有不少企业开始在抖音、小红书等平台进行投放,不少视频获得几百万点赞,打破了原有产品用户画像,吸引了大量女性潜在受众。3D 打印机一方面成为了流量密码,一方面也预示着其成为下一台「家电」的潜力。

有行业人士判断,3D 打印设备未来将达到家电级的出货量。「消费级 3D 打印机年出货量约在 400 多万台左右,而微波炉、冰箱、洗衣机、电视机等家电的年出货量高达数千万台,即便是扫地机器人,年出货量也处于数百万台至数千万台的区间。」

类比之下,消费级 3D 打印机的潜力巨大,「未来,3D 打印设备与配套内容的关系,可能会类似电视机与影视内容、游戏机与游戏作品的关联——用户购买设备的核心需求,更多是为了获取后续的优质内容。」

据极客公园了解,目前拓竹旗下的模型内容社区 MakeWorld 已经拥有 5000 万左右注册用户,月活超过 1000 万。「行业下一阶段的竞争焦点将逐步转向内容生态。」上述人士表示。

拓竹自从 2022 年发布首款产品之后,已经在行业里声名鹊起,快速改写着行业格局。而今年,Snapmaker U1 的推出,也成为了搅动消费级 3D 打印市场的一股核心力量,再加上前不久大疆投资智能派(英文名:ELEGOO)数亿元、以及行业内创想三维和纵维立方等厂商陆续推出的新产品,都在宣告——消费级 3D 打印市场的终局还远未到来。

随着资本密集入局,各家 3D 打印厂商开始囤积粮草、招兵买马,我们可以预测:行业集中度将迅速提高,正如手机行业早期也曾有众多品牌,而近年来全球范围内已逐渐集中到少数几家头部企业。

2026 年,行业必将格外热闹。这将是一场涵盖营销、公关、人才、供应链和研发等各个方面的「全面竞争」。

css的臂膀,前端动效的利器,还是布局的“隐形陷阱”?


前言

在现代 Web 开发中,transform 几乎成了动效与居中的代名词。它高效、灵活、支持硬件加速,一句 transform: translate(-50%, -50%) 就能优雅实现未知尺寸元素的绝对居中——这曾是多少前端开发者梦寐以求的解决方案。然而,在赞美其强大之余,我们是否忽略了它那“温柔面具”下的另一面?transform 不仅改变视觉,更悄然重塑了 CSS 的底层规则——尤其是与 position 的交互,常常成为布局崩坏的隐形元凶。


一、transform 的“理想面”:高效、无侵入、表现力强

核心优势:不扰动文档流

这是 transform 最被称道的特性:它只作用于合成层(compositing layer),不影响文档流中的占位
对比传统方案:

/* 低效:触发重排(reflow) */
.box { top: 10px; left: 20px; }

/* 高效:仅触发合成(compositing) */
.box { transform: translate(20px, 10px); }

前者会迫使浏览器重新计算整个布局树,而后者直接由 GPU 处理,帧率更高、能耗更低。

经典用例:绝对居中

.centered {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
}
  • 无需知道宽高-50% 基于元素自身尺寸,完美适配动态内容。
  • 语义清晰:意图明确,代码简洁。
  • 性能优异:远胜 margin: autocalc() 方案。

交互增强:微动效提升体验

.card:hover {
  transform: translateY(-4px) scale(1.02);
  transition: transform 0.2s ease;
}

这种“浮起”效果已成为现代 UI 的标配,而 transform 是实现它的最佳载体。


二、transform 的“暗面”:当它遇上 position

问题来了:如果 transform 如此美好,为何无数开发者在使用 Modal、Tooltip 时遭遇“fixed 元素跟着滚动”的诡异现象?

答案就藏在 CSS 规范的一个角落里:

“任何非 nonetransform 值,都会使该元素成为其 position: fixed 后代的包含块(containing block)。”

这意味着:fixed 元素的定位参考系,不再是视口,而是最近的带 transform 的祖先!

实例:一个“失效”的固定导航栏

<div class="app">
  <div class="animated-section"> 
    <nav class="navbar">我是导航栏</nav>
  </div>
</div>
.animated-section {
  transform: translateX(0); /* 哪怕是“无操作”变换! */
  height: 200vh; /* 足够滚动 */
}

.navbar {
  position: fixed;
  top: 0;
  width: 100%;
  background: black;
  color: white;
  z-index: 1000;
}

预期行为:导航栏固定在顶部,页面滚动时不动。
实际行为:导航栏随 .animated-section 一起滚动!

原因.animated-sectiontransform 成为新的包含块,fixedtop: 0 变成了“相对于该容器顶部”,而非视口。

这并非浏览器 bug,而是 CSS 规范的明确设计。但对开发者而言,这却是一个典型的“符合规范但违背直觉”的陷阱。


补充


关键补充属性
1. transform-origin:修改变换原点

默认变换原点是元素中心(50% 50% 或 center center),可通过该属性自定义:

  • 语法:transform-origin: x y;(x/y 支持 px、%、关键字(left/right/top/bottom/center));

  • 示例:

    css

    .box {
      transform: rotate(45deg);
      transform-origin: left top; /* 绕左上角旋转 */
    }
    
2. transform-style:3D 变换层级

当使用 3D 变换(如 rotateX/rotateY)时,需设置该属性让子元素继承 3D 空间:

  • transform-style: preserve-3d;:保留 3D 变换效果(常用);
  • transform-style: flat;:默认值,子元素扁平化到 2D 平面。

三、不止 transformfilterperspective 的共谋

更令人头疼的是,transform 并非孤例。以下属性同样会创建新的包含块:

  • filter: blur(0)(哪怕无视觉变化)
  • perspective: 1000px
  • will-change: transform

它们常被用于:

  • 动画库(Framer Motion、GSAP)自动注入 transform
  • 组件库内部使用 filter 实现毛玻璃效果
  • 3D 卡片组件启用 perspective

结果就是:你的 fixed Modal 突然无法覆盖全屏,Tooltip 错位,悬浮按钮失效……


四、反思:我们是否过度依赖 transform

transform 的流行,某种程度上掩盖了我们对 CSS 布局模型理解的不足。我们习惯性地用它解决一切位置问题,却忘了问:

“这个元素真的需要脱离文档流吗?它的祖先是否干净?”

在 React/Vue 等组件化框架中,问题被进一步放大:

  • 组件嵌套深,难以追踪哪个祖先加了 transform
  • 第三方库内部实现不可控
  • 动画与布局逻辑耦合,调试困难

于是,transform 从“解决方案”变成了“问题源头”。


五、破局之道:理性使用 + 架构规避

1. 浮层组件必须脱离当前上下文

在 React 中,永远使用 ReactDOM.createPortal 渲染 Modal/Toast/Dropdown

const Modal = ({ children }) => 
  createPortal(
    <div>
      {children}
    </div>,
    document.body
  );

这样可确保其祖先无 transformfixed 行为符合预期。

2. 避免在可能包含浮层的区域使用 transform

  • 导航区域、主内容区慎用动画
  • 若必须用,确保浮层不在其子树中

3. 调试时牢记“包含块”概念

fixed 行为异常,立即检查:

  • 所有祖先是否有 transform / filter / perspective
  • 是否意外创建了层叠上下文

结语:工具无善恶,认知定成败

transform 本身并无过错。它是 CSS 进化的重要里程碑,极大提升了 Web 的表现力与性能。
真正的风险,来自于我们对其副作用的无知,以及对“简单一行代码就能解决问题”的盲目信任。

前端开发不仅是写代码,更是理解系统。当我们熟练运用 transform: translate(-50%, -50%) 的同时,也应深知:

每一个看似优雅的解决方案背后,都有一套严密的规则在运行。忽视规则,终将被规则反噬。

因此,请继续热爱 transform,但请带着敬畏之心使用它——尤其是在与 position 共舞之时。

中科曙光回应英伟达合作等问题:产品采用开放架构,支持多类AI加速卡

12月10日,中科曙光披露投资者关系活动记录表,就市场关注的超节点640产品订单、适配H200及与英伟达合作等问题作出回应。中科曙光方面表示,曙光scaleX640依托先进的硬件架构、冷却、供电等技术,集约化程度全球领先,超节点综合性能国内领先。随着大模型参数规模进一步增大和高通量推理集群化部署,该产品优势将更加突出,具备广阔市场前景。此外,曙光scaleX640超节点采用AI计算开放架构,支持市场上主流的国产和国际AI加速卡,用户可按需选择加速芯片,实现“软硬协同、生态兼容”的AI计算解决方案。(北京商报)

热门中概股美股盘前多数下跌,百度涨超1%

36氪获悉,热门中概股美股盘前多数下跌,截至发稿,拼多多跌超3%,哔哩哔哩、理想汽车、名创优品跌超1%,小鹏汽车跌0.61%,蔚来跌0.2%;百度涨超1%,小马智行涨0.92%,阿里巴巴涨0.72%。
❌