普通视图

发现新文章,点击刷新页面。
今天 — 2026年1月24日掘金 iOS

mach_msg_header_t详解

作者 iOS在入门
2026年1月23日 18:01

借助AI能力分析。

mach_msg_header_t - Mach 消息头

作用

这是 Mach 消息的头部结构,用于在 macOS/iOS 的进程间(或线程间)传递消息。

6个字段详解

typedef struct {
    mach_msg_bits_t      msgh_bits;         // 消息标志位
    mach_msg_size_t      msgh_size;         // 消息总大小(字节)
    mach_port_t          msgh_remote_port;  // 目标端口(收信人)
    mach_port_t          msgh_local_port;   // 本地端口(回信地址)
    mach_port_name_t     msgh_voucher_port; // 追踪端口(调试用)
    mach_msg_id_t        msgh_id;           // 消息ID(自定义)
} mach_msg_header_t;

形象比喻(信封):

字段 对应信封上的 说明
msgh_remote_port 收件人地址 消息发往哪个端口
msgh_local_port 回信地址 如果需要回复,发到这里
msgh_size 信件大小 包括信封和内容
msgh_bits 邮寄方式 挂号信、平信等
msgh_id 信件编号 用于区分不同类型的信
msgh_voucher_port 追踪单号 用于追踪和调试

在 RunLoop 中的使用

1. 发送唤醒消息(CFRunLoopWakeUp)

// 构造消息头
mach_msg_header_t header;
header.msgh_remote_port = rl->_wakeUpPort;  // 发往唤醒端口
header.msgh_local_port = MACH_PORT_NULL;    // 不需要回复
header.msgh_size = sizeof(mach_msg_header_t); // 只有头,无内容
header.msgh_bits = MACH_MSGH_BITS(MACH_MSG_TYPE_COPY_SEND, 0);
header.msgh_id = 0;

// 发送(唤醒 RunLoop)
mach_msg(&header, MACH_SEND_MSG, ...);

2. 接收消息(RunLoop 休眠)

// 准备缓冲区
uint8_t buffer[3 * 1024];
mach_msg_header_t *msg = (mach_msg_header_t *)buffer;

msg->msgh_local_port = waitSet;  // 在哪个端口等待
msg->msgh_size = sizeof(buffer);  // 缓冲区大小

// 阻塞等待(线程休眠)
mach_msg(msg, MACH_RCV_MSG, ...);

// 被唤醒后,检查消息来源
if (msg->msgh_local_port == _wakeUpPort) {
    // 手动唤醒
} else if (msg->msgh_local_port == _timerPort) {
    // 定时器到期
}

关键理解

mach_msg_header_t 是 Mach IPC 的核心

  1. 通信基础:所有 Mach 消息都以这个头开始
  2. 路由信息:指明消息的来源和去向
  3. RunLoop 休眠/唤醒:通过接收/发送消息实现

完整消息结构

┌──────────────────────┐
│ mach_msg_header_t    │ ← 消息头(必需)
├──────────────────────┤
│ 消息体(可选)        │ ← 实际数据
├──────────────────────┤
│ trailer(可选)       │ ← 附加信息
└──────────────────────┘

RunLoop 的简化消息:只有头部,无消息体(称为 "trivial message"),足以唤醒线程。

objc_msgSend(obj, @selector(foo)); 到底发生了什么?

作者 汉秋
2026年1月23日 17:53

objc_msgSend(obj, @selector(foo)); 到底发生了什么?

在 Objective-C 的世界里,有一句话几乎是底层原教旨主义

Objective-C 是一门基于消息发送(Message Sending)的语言,而不是函数调用。

而这一切,都浓缩在一行看似普通、却极其核心的代码中:

objc_msgSend(obj, @selector(foo));

本文将从语法糖 → 运行时 → 完整调用链,一步一步拆解:

  • 这行代码到底在“发什么”
  • 消息是如何被找到并执行的
  • 如果找不到方法,Runtime 又做了什么

一、从表面看:它等价于什么?

这行代码:

objc_msgSend(obj, @selector(foo));

在语义上 等价于

[obj foo];

也就是说:

给对象 obj 发送一条名为 foo 的消息

[obj foo] 只是编译器提供的语法糖,真正执行的永远是 objc_msgSend。


二、谁是发送者?谁是接收者?

很多初学者会卡在这个问题上:

到底是谁“调用”了谁?

正确理解方式

  • 接收者(receiver) :obj

  • 消息(selector) :foo

  • 发送动作的发起者:当前代码位置(不重要)

Objective-C 不关心调用栈的“谁” ,只关心:

👉 这条消息发给谁

所以永远用这句话来理解:

给 obj 发送 foo 消息


三、Runtime 真正发生的 6 个步骤(核心)

下面是你在 Xcode 里写下一行 [obj foo] 后,Runtime 在背后真实发生的完整流程


步骤 1️⃣:取得接收者obj

id obj = ...;
objc_msgSend(obj, @selector(foo));
  • obj 是一个对象指针
  • 本质上指向一块内存
  • 内存布局的第一个成员,就是 isa 指针
obj
 ├─ isa → Class
 ├─ ivar1
 ├─ ivar2

如果 obj == nil:

  • 整个流程直接结束
  • 返回 0 / nil
  • 不会崩溃(OC 的著名特性)

步骤 2️⃣:通过isa找到 Class

Class cls = obj->isa;
  • isa 指向对象所属的类

  • 这是 所有方法查找的起点

示例:

@interface Person : NSObject
- (void)foo;
@end
obj (Person 实例)
  └─ isa → Person

步骤 3️⃣:在方法缓存(cache)中查找

Runtime 首先查 cache,而不是方法列表

Class Person
 ├─ cache      ← ① 先查这里
 ├─ methodList ← ② 再查这里
 └─ superclass
  • cache 是一个哈希表:SEL → IMP

  • 命中 cache = 极快(接近 C 函数调用)

如果在 cache 中找到了 foo:

IMP imp = cache[foo];
imp(obj, @selector(foo));

流程结束****


步骤 4️⃣:在方法列表 & 父类中查找

如果 cache 未命中:

  1. 查 Person 的方法列表
  2. 找不到 → superclass
  3. 一直向上查,直到 NSObject
Person
  ↓
NSObject

如果在某个类中找到:

  • 将 SEL → IMP 放入 cache(下次更快)
  • 立即执行 IMP

步骤 5️⃣:动态方法解析(resolve)

如果 整个继承链都没找到 foo

Runtime 会给你一次**“临时补救”的机会**。

+ (BOOL)resolveInstanceMethod:(SEL)sel;

示例:动态添加方法

@implementation Person

+ (BOOL)resolveInstanceMethod:(SEL)sel {
    if (sel == @selector(foo)) {
        class_addMethod(self, sel, (IMP)fooIMP, "v@:");
        return YES;
    }
    return [super resolveInstanceMethod:sel];
}

void fooIMP(id self, SEL _cmd) {
    NSLog(@"动态添加的 foo 被调用了");
}

@end

如果返回 YES:

  • Runtime 重新从步骤 3 开始查找

步骤 6️⃣:消息转发(Message Forwarding)

如果你没有动态添加方法,Runtime 进入 消息转发三连

6.1 快速转发

- (id)forwardingTargetForSelector:(SEL)aSelector;
- (id)forwardingTargetForSelector:(SEL)aSelector {
    if (aSelector == @selector(foo)) {
        return otherObj;
    }
    return [super forwardingTargetForSelector:aSelector];
}

等价于:

[otherObj foo];

6.2 完整转发(NSInvocation)

- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector;
- (void)forwardInvocation:(NSInvocation *)invocation;
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
    if (aSelector == @selector(foo)) {
        return [NSMethodSignature signatureWithObjCTypes:"v@:"];
    }
    return [super methodSignatureForSelector:aSelector];
}

- (void)forwardInvocation:(NSInvocation *)invocation {
    [invocation invokeWithTarget:otherObj];
}

6.3 最终失败 → 崩溃

如果以上都没处理:

-[Person foo]: unrecognized selector sent to instance

应用直接崩溃 💥


四、完整流程总览(记住这个顺序)

objc_msgSend
  ↓
isa
  ↓
cache
  ↓
method list
  ↓
superclass
  ↓
resolveInstanceMethod
  ↓
forwardingTargetForSelector
  ↓
forwardInvocation
  ↓
crash

五、为什么 objc_msgSend 这么重要?

  • KVC / KVO

  • 方法交换(Method Swizzling)

  • AOP / Hook

  • 崩溃防护

  • 热修复(早期方案)

全都建立在它之上。

理解 objc_msgSend,

才算真正“入门” Objective-C Runtime。


六、结语

当你再看到这行代码时:

objc_msgSend(obj, @selector(foo));

请在脑中自动展开:

cache → superclass → resolve → forwarding → crash

那一刻,你已经不是在“写 OC”,而是在和 Runtime 对话

Flutter 最新xyz

作者 忆江南
2026年1月23日 16:19

包含 55+ 道xyz,覆盖基础、原理、性能优化、复杂场景和高难度题目


一、Dart 语言基础xyz(15题)

1. Dart 是值传递还是引用传递?

答案

类型 传递方式 示例
基本类型(int、double、bool、String) 值传递 修改不影响原值
对象和集合(List、Map、Set、自定义类) 引用传递 修改会影响原对象
void modifyInt(int value) {
  value = 100; // 不影响原值
}

void modifyList(List<int> list) {
  list.add(4); // 会影响原列表
}

2. constfinal 的区别?

答案

特性 const final
赋值时机 编译时确定 运行时确定
是否可用于类成员 需要 static const 可以
对象创建 共享同一对象 每次创建新对象
嵌套要求 所有成员必须是 const 无要求
const int a = 10;                    // ✓ 编译期常量
final int b = DateTime.now().year;   // ✓ 运行时常量
const DateTime c = DateTime.now();   // ✗ 报错,编译时无法确定

3. vardynamicObject 的区别?

答案

关键字 类型检查时机 类型能否改变 使用场景
var 编译时 一旦确定不可改变 类型推断
dynamic 运行时 可随时改变 动态类型、JSON解析
Object 编译时 只能调用 Object 方法 需要类型安全的通用类型
var x = 'hello';    // x 被推断为 String
x = 123;            // ✗ 报错

dynamic y = 'hello';
y = 123;            // ✓ 可以

Object z = 'hello';
z.length;           // ✗ 报错,Object 没有 length

4. .. 级联操作符与 . 的区别?

答案

操作符 返回值 用途
. 方法的返回值 普通方法调用
.. this(当前对象) 链式调用配置
var paint = Paint()
  ..color = Colors.red
  ..strokeWidth = 5.0
  ..style = PaintingStyle.stroke;

5. Dart 的空安全(Null Safety)是什么?

答案

Dart 2.12+ 引入空安全,区分可空类型非空类型

String name = 'Flutter';      // 非空,不能赋值 null
String? nickname = null;       // 可空,可以赋值 null

// 空安全操作符
String? text = null;
int length = text?.length ?? 0;  // 安全访问 + 默认值
String value = text!;            // 断言非空(危险!)

6. late 关键字的作用?

答案

late 用于延迟初始化非空变量:

class MyWidget extends StatefulWidget {
  @override
  State<MyWidget> createState() => _MyWidgetState();
}

class _MyWidgetState extends State<MyWidget> {
  late AnimationController controller; // 延迟初始化

  @override
  void initState() {
    super.initState();
    controller = AnimationController(duration: Duration(seconds: 1), vsync: this);
  }
}

使用场景

  • 需要在构造函数之后初始化的非空变量
  • 惰性计算的变量

7. Mixin 是什么?与继承的区别?

答案

Mixin 用于代码复用,不同于继承:

特性 继承(extends) 混入(with)
数量 单继承 可多个
构造函数 可以有 不能有
代码复用
类型关系 is-a has-ability
mixin Flyable {
  void fly() => print('Flying!');
}

mixin Swimmable {
  void swim() => print('Swimming!');
}

class Duck extends Animal with Flyable, Swimmable {
  // Duck 同时拥有 fly() 和 swim()
}

8. extendswithimplements 的执行顺序?

答案

顺序为:extends → with → implements

class Child extends Parent with Mixin1, Mixin2 implements Interface {
  // 1. 首先继承 Parent
  // 2. 然后混入 Mixin1, Mixin2(后者覆盖前者的同名方法)
  // 3. 最后实现 Interface
}

方法查找顺序(从右到左): Child → Mixin2 → Mixin1 → Parent → Object


9. Dart 中的泛型是什么?

答案

泛型用于类型安全代码复用

// 泛型类
class Box<T> {
  T value;
  Box(this.value);
}

// 泛型方法
T first<T>(List<T> items) {
  return items[0];
}

// 泛型约束
class NumberBox<T extends num> {
  T value;
  NumberBox(this.value);

  double toDouble() => value.toDouble();
}

10. Dart 的 typedef 是什么?

答案

typedef 用于定义函数类型别名

// 定义函数类型
typedef Compare<T> = int Function(T a, T b);

// 使用
int sort(int a, int b) => a - b;
Compare<int> comparator = sort;

// 新语法(Dart 2.13+)
typedef IntList = List<int>;
typedef StringCallback = void Function(String);

11. Dart 的 extension 扩展方法是什么?

答案

extension 用于给现有类添加方法,无需继承:

extension StringExtension on String {
  String capitalize() {
    if (isEmpty) return this;
    return '${this[0].toUpperCase()}${substring(1)}';
  }

  bool get isEmail => contains('@');
}

// 使用
'hello'.capitalize();  // 'Hello'
'a@b.com'.isEmail;     // true

12. Dart 的 factory 构造函数是什么?

答案

factory 构造函数可以返回已有实例子类实例

class Logger {
  static final Logger _instance = Logger._internal();

  // 工厂构造函数
  factory Logger() {
    return _instance; // 返回单例
  }

  Logger._internal();
}

// 使用
var l1 = Logger();
var l2 = Logger();
print(l1 == l2); // true,同一个实例

13. Dart 3 的 Records(记录类型)是什么?

答案

Records 是 Dart 3 引入的匿名复合类型

// 位置记录
(int, String) getUserInfo() => (1, 'John');

var info = getUserInfo();
print(info.$1); // 1
print(info.$2); // 'John'

// 命名记录
({int id, String name}) getUser() => (id: 1, name: 'John');

var user = getUser();
print(user.id);   // 1
print(user.name); // 'John'

14. Dart 3 的 Pattern Matching(模式匹配)是什么?

答案

模式匹配用于解构和条件匹配

// switch 表达式
String describe(Object obj) => switch (obj) {
  int n when n > 0 => 'Positive number: $n',
  int n when n < 0 => 'Negative number: $n',
  String s => 'String: $s',
  _ => 'Unknown type',
};

// 解构
var (x, y) = (1, 2);
var {'name': name, 'age': age} = {'name': 'John', 'age': 30};

// if-case
if (json case {'name': String name, 'age': int age}) {
  print('Name: $name, Age: $age');
}

15. Dart 3 的 Sealed Class 是什么?

答案

sealed 类用于限制子类,实现穷尽式 switch:

sealed class Shape {}

class Circle extends Shape {
  final double radius;
  Circle(this.radius);
}

class Rectangle extends Shape {
  final double width, height;
  Rectangle(this.width, this.height);
}

// 编译器会检查是否穷尽所有子类
double area(Shape shape) => switch (shape) {
  Circle(radius: var r) => 3.14 * r * r,
  Rectangle(width: var w, height: var h) => w * h,
};

二、Flutter 核心原理xyz(15题)

16. Flutter 的三棵树是什么?各自职责是什么?

答案

类型 职责 特点
Widget Tree 配置层 描述 UI 结构 不可变、轻量、频繁重建
Element Tree 连接层 管理生命周期、持有 State 可变、持久化
RenderObject Tree 渲染层 布局、绘制、事件处理 重量级、存储几何信息

创建流程

Widget.createElement() → Element
Element.createRenderObject() → RenderObject

为什么需要三棵树?

  • Widget 频繁重建成本低
  • Element 复用避免重复创建
  • RenderObject 只在必要时更新

17. Flutter 完整渲染流程是什么?

答案

┌─────────────────────────────────────────────┐
│                   UI 线程                    │
├─────────────────────────────────────────────┤
│ 1. Build(构建)                             │
│    - 从脏 Element 开始重建                   │
│    - 调用 build() 方法                       │
│    - 生成新的 Widget 树                      │
├─────────────────────────────────────────────┤
│ 2. Layout(布局)                            │
│    - 约束从父向子传递                        │
│    - 几何信息从子向父返回                    │
│    - 计算大小和位置                          │
├─────────────────────────────────────────────┤
│ 3. Paint(绘制)                             │
│    - 生成绘制指令                            │
│    - 构建 Layer Tree                         │
└─────────────────────────────────────────────┘
                    ↓
┌─────────────────────────────────────────────┐
│               光栅线程(Raster)              │
├─────────────────────────────────────────────┤
│ 4. Composite(合成)                         │
│    - 图层合成                                │
│    - Skia/Impeller 栅格化                    │
│    - 提交给 GPU                              │
└─────────────────────────────────────────────┘
                    ↓
                显示到屏幕

性能标准

  • 60fps:每帧 ≤ 16ms
  • 120fps:每帧 ≤ 8.3ms

18. setState() 的底层原理是什么?

答案

void setState(VoidCallback fn) {
  // 1. 执行回调函数,修改状态
  fn();

  // 2. 标记当前 Element 为脏
  _element!.markNeedsBuild();
}

// markNeedsBuild() 的实现
void markNeedsBuild() {
  // 标记为脏
  _dirty = true;

  // 加入脏 Element 列表
  owner!.scheduleBuildFor(this);
}

流程

  1. 执行回调更新状态
  2. 标记 Element 为脏
  3. 注册到 BuildOwner 的脏列表
  4. 下一帧触发重建
  5. 只重建脏 Element 及其子树

19. Flutter 的约束(Constraints)系统是什么?

答案

约束是父节点向子节点传递的布局信息

class BoxConstraints {
  final double minWidth;   // 最小宽度
  final double maxWidth;   // 最大宽度
  final double minHeight;  // 最小高度
  final double maxHeight;  // 最大高度
}

布局算法

1. 父节点传递约束给子节点
2. 子节点选择约束范围内的大小
3. 子节点返回实际大小给父节点
4. 父节点确定子节点位置

严格约束(Tight Constraints):

  • minWidth == maxWidthminHeight == maxHeight
  • 子节点无法改变大小
  • 父节点可直接定位而无需重新布局子节点

20. Key 的作用是什么?有哪些类型?

答案

作用:帮助 Flutter 在 Widget 树重建时正确匹配和复用 Element

Key 类型 作用域 使用场景
GlobalKey 整个应用唯一 跨组件访问 State、保持状态
LocalKey 局部唯一 列表项复用
ValueKey 基于值 数据驱动列表
ObjectKey 基于对象引用 对象唯一性
UniqueKey 随机唯一 强制重建
// GlobalKey 示例
final GlobalKey<FormState> formKey = GlobalKey<FormState>();
formKey.currentState?.validate();

// ValueKey 示例
ListView(
  children: items.map((item) =>
    ListTile(key: ValueKey(item.id), title: Text(item.name))
  ).toList(),
)

21. BuildContext 是什么?

答案

BuildContext 是 Widget 在 Widget 树中的位置引用,本质是 Element 对象

// 向上查找
Theme.of(context);           // 获取主题
Navigator.of(context);       // 获取导航器
MediaQuery.of(context);      // 获取媒体查询
Scaffold.of(context);        // 获取 Scaffold

// InheritedWidget 查找
MyInheritedWidget.of(context);

注意事项

  • initState() 中不能使用 context(Element 未完全挂载)
  • 异步操作后需检查 mounted 状态

22. Widget 有哪些分类?

答案

类型 代表类 作用
组合类 StatelessWidget、StatefulWidget 组合其他 Widget
代理类 InheritedWidget、ParentDataWidget 状态共享、数据传递
绘制类 RenderObjectWidget 真正的布局和绘制

RenderObject 三个子类

  • LeafRenderObjectWidget:叶子节点(无子节点)
  • SingleChildRenderObjectWidget:单子节点
  • MultiChildRenderObjectWidget:多子节点

23. StatefulWidget 的完整生命周期?

答案

class MyWidget extends StatefulWidget {
  @override
  State<MyWidget> createState() => _MyWidgetState(); // 1. 创建 State
}

class _MyWidgetState extends State<MyWidget> {
  @override
  void initState() {                    // 2. 初始化(只调用一次)
    super.initState();
  }

  @override
  void didChangeDependencies() {         // 3. 依赖变化
    super.didChangeDependencies();
  }

  @override
  Widget build(BuildContext context) {   // 4. 构建 UI
    return Container();
  }

  @override
  void didUpdateWidget(MyWidget old) {   // 5. Widget 更新
    super.didUpdateWidget(old);
  }

  @override
  void reassemble() {                    // 6. 热重载时调用
    super.reassemble();
  }

  @override
  void deactivate() {                    // 7. 暂时移除
    super.deactivate();
  }

  @override
  void dispose() {                       // 8. 永久销毁
    super.dispose();
  }
}

生命周期图

createState → initState → didChangeDependencies → build
                                    ↓
                          [setState/父Widget更新]
                                    ↓
                          didUpdateWidget → build
                                    ↓
                          deactivate → dispose

24. InheritedWidget 的原理是什么?

答案

InheritedWidget 用于数据向下传递,避免多层传参:

class ThemeProvider extends InheritedWidget {
  final Color color;

  ThemeProvider({required this.color, required Widget child})
    : super(child: child);

  static ThemeProvider? of(BuildContext context) {
    return context.dependOnInheritedWidgetOfExactType<ThemeProvider>();
  }

  @override
  bool updateShouldNotify(ThemeProvider oldWidget) {
    return color != oldWidget.color;
  }
}

性能优化原理

  • Element 维护 InheritedWidget 哈希表
  • 查找时间复杂度 O(1)
  • 避免遍历父链(O(N))

25. 热重载(Hot Reload)的原理是什么?

答案

流程

  1. 代码修改保存
  2. IDE 发送变更到 Dart VM
  3. VM 增量编译新代码
  4. 新代码注入到 VM(保留旧实例)
  5. 调用 reassemble()
  6. 触发完整的 build 流程

不支持热重载的场景

  • ❌ 修改 main() 函数
  • ❌ 修改 initState() 方法
  • ❌ 修改全局变量初始化
  • ❌ 修改枚举类型
  • ❌ 修改泛型类型

26. Flutter 与原生如何通信?

答案

三种 Channel

Channel 用途 数据流向
MethodChannel 方法调用 双向请求/响应
EventChannel 事件流 原生 → Flutter
BasicMessageChannel 消息传递 双向自定义编解码
// MethodChannel 示例
const platform = MethodChannel('com.example/battery');

Future<int> getBatteryLevel() async {
  try {
    return await platform.invokeMethod('getBatteryLevel');
  } on PlatformException catch (e) {
    return -1;
  }
}

// EventChannel 示例
const eventChannel = EventChannel('com.example/sensor');
Stream<dynamic> get sensorStream => eventChannel.receiveBroadcastStream();

27. Impeller 与 Skia 的区别?

答案

特性 Skia Impeller
平台 全平台 iOS(默认)、Android(预览)
着色器编译 运行时 预编译
首帧卡顿
Emoji 渲染 可能卡顿 流畅
GPU 内存管理 一般 优化

28. Flutter 的 Layer Tree 是什么?

答案

Layer Tree 是绘制阶段生成的图层树

Layer Tree 结构:
├── TransformLayer(变换层)
├── ClipRectLayer(裁剪层)
├── OpacityLayer(透明度层)
├── PictureLayer(绘制层)
└── ...

用途

  • 优化重绘(只重绘变化的图层)
  • 支持合成效果(透明度、变换等)
  • 提交给 GPU 合成

29. RepaintBoundary 的作用是什么?

答案

RepaintBoundary 用于隔离重绘区域

// 场景:动画只影响一小块区域
Stack(
  children: [
    StaticBackground(),  // 不需要重绘
    RepaintBoundary(
      child: AnimatedWidget(), // 动画只在此区域重绘
    ),
  ],
)

原理

  • 创建独立的绘制边界
  • 子树重绘不影响外部
  • 外部重绘不影响子树

30. Flutter 架构分层是什么?

答案

┌─────────────────────────────────────────┐
│           应用层(Your App)              │
├─────────────────────────────────────────┤
│        Framework 层(Dart)               │
│  ┌─────────────────────────────────────┐ │
│  │ Material / Cupertino Widgets        │ │
│  ├─────────────────────────────────────┤ │
│  │ Widgets Layer                       │ │
│  ├─────────────────────────────────────┤ │
│  │ Rendering Layer                     │ │
│  ├─────────────────────────────────────┤ │
│  │ Foundation / Animation / Gesture    │ │
│  └─────────────────────────────────────┘ │
├─────────────────────────────────────────┤
│          Engine 层(C++)                 │
│  Skia / Impeller / Dart VM / Text       │
├─────────────────────────────────────────┤
│        Embedder 层(平台适配)             │
│  Android / iOS / Web / Desktop          │
└─────────────────────────────────────────┘

三、异步编程xyz(10题)

31. Dart 事件循环是怎样的?

答案

main() {
  print('1. main start');           // 同步

  Future(() => print('4. event'));  // 事件队列

  scheduleMicrotask(              // 微任务队列
    () => print('3. microtask')
  );

  print('2. main end');             // 同步
}

// 输出顺序:1 → 2 → 3 → 4

优先级:同步代码 > 微任务队列 > 事件队列


32. Future 和 Stream 的区别?

答案

特性 Future Stream
返回值次数 一次 多次
使用场景 网络请求、文件读取 按钮点击、WebSocket
订阅方式 .then() / await .listen()
取消 不可取消 可取消
// Future
Future<String> fetchData() async {
  return await http.get(url);
}

// Stream
Stream<int> countStream() async* {
  for (int i = 0; i < 10; i++) {
    yield i;
    await Future.delayed(Duration(seconds: 1));
  }
}

33. Stream 的两种订阅模式?

答案

模式 特点 使用场景
单订阅 只能有一个监听者 文件读取、HTTP 响应
广播 多个监听者 按钮点击、状态变化
// 单订阅(默认)
stream.listen((data) => print(data));

// 转为广播
Stream broadcastStream = stream.asBroadcastStream();
broadcastStream.listen((data) => print('1: $data'));
broadcastStream.listen((data) => print('2: $data'));

34. Isolate 是什么?如何使用?

答案

Isolate 是 Dart 的并发模型,拥有独立的内存和事件循环:

// 方法1:Isolate.run()(推荐)
Future<List<Photo>> loadPhotos() async {
  final jsonString = await rootBundle.loadString('assets/photos.json');

  return await Isolate.run(() {
    final data = jsonDecode(jsonString) as List;
    return data.map((e) => Photo.fromJson(e)).toList();
  });
}

// 方法2:compute()
final result = await compute(parseJson, jsonString);

使用场景

  • JSON 解析(大文件)
  • 图片处理
  • 复杂计算
  • 加密解密

35. async/await 的执行顺序?

答案

Future<void> test() async {
  print('1');
  await Future.delayed(Duration.zero);  // 让出执行权
  print('2');
}

main() {
  print('a');
  test();
  print('b');
}

// 输出:a → 1 → b → 2

原理await 之前同步执行,之后加入微任务队列


36. Future.wait 和 Future.any 的区别?

答案

// Future.wait:等待所有完成
final results = await Future.wait([
  fetchUser(),
  fetchPosts(),
  fetchComments(),
]);
// results = [user, posts, comments]

// Future.any:返回最先完成的
final fastest = await Future.any([
  fetchFromServer1(),
  fetchFromServer2(),
]);
// fastest = 最快返回的结果

37. StreamController 的使用?

答案

class EventBus {
  final _controller = StreamController<Event>.broadcast();

  Stream<Event> get stream => _controller.stream;

  void emit(Event event) {
    _controller.add(event);
  }

  void dispose() {
    _controller.close();
  }
}

// 使用
final bus = EventBus();
bus.stream.listen((event) => print(event));
bus.emit(LoginEvent());

38. FutureBuilder 和 StreamBuilder 的区别?

答案

Widget 数据源 使用场景
FutureBuilder Future(一次性) 网络请求
StreamBuilder Stream(持续) 实时数据
// FutureBuilder
FutureBuilder<User>(
  future: fetchUser(),
  builder: (context, snapshot) {
    if (snapshot.hasData) return UserWidget(snapshot.data!);
    if (snapshot.hasError) return ErrorWidget(snapshot.error!);
    return CircularProgressIndicator();
  },
)

// StreamBuilder
StreamBuilder<int>(
  stream: countStream(),
  builder: (context, snapshot) {
    return Text('Count: ${snapshot.data ?? 0}');
  },
)

39. async* 和 sync* 生成器的区别?

答案

// sync*:同步生成器,返回 Iterable
Iterable<int> syncGenerator() sync* {
  yield 1;
  yield 2;
  yield 3;
}

// async*:异步生成器,返回 Stream
Stream<int> asyncGenerator() async* {
  for (int i = 0; i < 3; i++) {
    await Future.delayed(Duration(seconds: 1));
    yield i;
  }
}

40. Completer 的作用?

答案

Completer 用于手动完成 Future

Future<String> fetchWithTimeout() {
  final completer = Completer<String>();

  // 设置超时
  Future.delayed(Duration(seconds: 5), () {
    if (!completer.isCompleted) {
      completer.completeError(TimeoutException('Timeout'));
    }
  });

  // 模拟网络请求
  http.get(url).then((response) {
    if (!completer.isCompleted) {
      completer.complete(response.body);
    }
  });

  return completer.future;
}

四、性能优化xyz(10题)

41. 如何减少 Widget 重建?

答案

// 1. 使用 const Widget
const Text('Hello');
const MyWidget();

// 2. 拆分 Widget
class ParentWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        const ExpensiveWidget(),  // 不会重建
        DynamicWidget(),           // 可能重建
      ],
    );
  }
}

// 3. 使用 Consumer 精确订阅
Consumer<CounterProvider>(
  builder: (context, counter, child) {
    return Text('${counter.value}');
  },
  child: const ExpensiveChild(), // 不会重建
)

// 4. 使用 Selector 订阅单个字段
Selector<AppState, String>(
  selector: (context, state) => state.userName,
  builder: (context, userName, child) {
    return Text(userName);
  },
)

42. 如何优化 ListView 性能?

答案

ListView.builder(
  // 1. 指定固定高度(避免高度计算)
  itemExtent: 80,

  // 2. 设置缓存范围
  cacheExtent: 500,

  // 3. 使用懒加载
  itemCount: items.length,
  itemBuilder: (context, index) {
    // 4. 使用 RepaintBoundary 隔离重绘
    return RepaintBoundary(
      // 5. 使用 const
      child: ListItemWidget(item: items[index]),
    );
  },
)

// 6. 使用 AutomaticKeepAliveClientMixin 保持状态
class _ItemState extends State<Item> with AutomaticKeepAliveClientMixin {
  @override
  bool get wantKeepAlive => true;

  @override
  Widget build(BuildContext context) {
    super.build(context);
    return ...;
  }
}

43. 如何避免 saveLayer 导致的性能问题?

答案

saveLayer 是昂贵操作,以下 Widget 会触发:

Widget 替代方案
Opacity 直接设置颜色透明度
ShaderMask 简化效果
ColorFilter 直接应用到 Image
Clip.antiAliasWithSaveLayer 使用 Clip.hardEdge
// ❌ 触发 saveLayer
Opacity(
  opacity: 0.5,
  child: Container(color: Colors.blue),
)

// ✓ 直接设置透明度
Container(
  color: Colors.blue.withOpacity(0.5),
)

44. 如何优化图片加载?

答案

// 1. 设置缓存尺寸
Image.network(
  url,
  cacheWidth: 200,
  cacheHeight: 200,
)

// 2. 预加载图片
precacheImage(NetworkImage(url), context);

// 3. 使用渐进式加载
FadeInImage.memoryNetwork(
  placeholder: kTransparentImage,
  image: url,
)

// 4. 使用缓存库
CachedNetworkImage(
  imageUrl: url,
  placeholder: (context, url) => CircularProgressIndicator(),
  errorWidget: (context, url, error) => Icon(Icons.error),
)

// 5. 及时释放
@override
void dispose() {
  imageProvider.evict();
  super.dispose();
}

45. 如何优化动画性能?

答案

// 1. 使用 AnimatedBuilder 而非 setState
AnimatedBuilder(
  animation: controller,
  builder: (context, child) {
    return Transform.rotate(
      angle: controller.value * 2 * pi,
      child: child, // child 不重建
    );
  },
  child: const ExpensiveWidget(),
)

// 2. 使用 RepaintBoundary 隔离重绘
RepaintBoundary(
  child: AnimatedWidget(),
)

// 3. 使用 Transform 而非改变布局
// ❌ 触发布局
Container(
  margin: EdgeInsets.only(left: animation.value),
  child: widget,
)

// ✓ 只触发绘制
Transform.translate(
  offset: Offset(animation.value, 0),
  child: widget,
)

// 4. 使用 vsync
AnimationController(
  vsync: this, // 与屏幕刷新率同步
  duration: Duration(seconds: 1),
)

46. 如何检测和解决内存泄漏?

答案

常见泄漏场景

class _MyWidgetState extends State<MyWidget> {
  StreamSubscription? subscription;
  Timer? timer;
  AnimationController? controller;
  TextEditingController? textController;

  @override
  void initState() {
    super.initState();
    subscription = stream.listen((_) {});
    timer = Timer.periodic(duration, (_) {});
    controller = AnimationController(vsync: this);
    textController = TextEditingController();
  }

  @override
  void dispose() {
    // ✓ 必须释放所有资源
    subscription?.cancel();
    timer?.cancel();
    controller?.dispose();
    textController?.dispose();
    super.dispose();
  }
}

异步回调中的安全检查

Future<void> loadData() async {
  final data = await fetchData();

  // ✓ 检查 mounted 状态
  if (!mounted) return;

  setState(() => this.data = data);
}

47. 如何优化启动性能?

答案

// 1. 延迟初始化非关键服务
void main() {
  WidgetsFlutterBinding.ensureInitialized();

  // 只初始化必需的
  initCriticalServices();

  runApp(MyApp());

  // 延迟初始化其他服务
  Future.delayed(Duration(seconds: 1), () {
    initNonCriticalServices();
  });
}

// 2. 使用懒加载
class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: FutureBuilder(
        future: loadInitialData(),
        builder: (context, snapshot) {
          if (!snapshot.hasData) {
            return SplashScreen();
          }
          return HomeScreen(data: snapshot.data);
        },
      ),
    );
  }
}

// 3. 使用 deferred loading(代码分割)
import 'package:heavy_module/heavy_module.dart' deferred as heavy;

Future<void> loadHeavyModule() async {
  await heavy.loadLibrary();
  heavy.doSomething();
}

48. 如何使用 DevTools 进行性能分析?

答案

1. Performance 视图

  • Flutter Frames Chart:查看每帧的 UI/Raster 时间
  • Frame Analysis:自动检测性能问题
  • Timeline Events:详细追踪事件

2. 关键指标

✓ 绿色帧:< 16ms(正常)
✗ 红色帧:> 16ms(卡顿)

UI Thread:构建和布局时间
Raster Thread:绘制和合成时间

3. 常见优化建议

  • 避免在 build 中创建对象
  • 使用 const Widget
  • 减少 Widget 深度
  • 使用 RepaintBoundary

49. Flutter 3.24+ 性能优化新特性?

答案

1. Impeller 渲染引擎优化

  • 预编译着色器,消除首帧卡顿
  • Emoji 滚动更流畅
  • GPU 内存管理改进

2. 新的 Sliver 组件

CustomScrollView(
  slivers: [
    SliverFloatingHeader(...),     // 浮动头部
    PinnedHeaderSliver(...),       // 固定头部
    SliverResizingHeader(...),     // 可调整大小头部
  ],
)

3. 增强的 Performance 视图

  • 着色器编译追踪
  • 更详细的帧分析
  • 自动性能建议

50. 如何实现高性能的无限滚动列表?

答案

class InfiniteScrollList extends StatefulWidget {
  @override
  State<InfiniteScrollList> createState() => _InfiniteScrollListState();
}

class _InfiniteScrollListState extends State<InfiniteScrollList> {
  final List<Item> items = [];
  final ScrollController controller = ScrollController();
  bool isLoading = false;
  bool hasMore = true;
  int page = 1;

  @override
  void initState() {
    super.initState();
    controller.addListener(_onScroll);
    _loadMore();
  }

  void _onScroll() {
    if (controller.position.pixels >=
        controller.position.maxScrollExtent - 200) {
      _loadMore();
    }
  }

  Future<void> _loadMore() async {
    if (isLoading || !hasMore) return;

    setState(() => isLoading = true);

    try {
      final newItems = await fetchItems(page: page);
      setState(() {
        items.addAll(newItems);
        page++;
        hasMore = newItems.length >= 20;
        isLoading = false;
      });
    } catch (e) {
      setState(() => isLoading = false);
    }
  }

  @override
  Widget build(BuildContext context) {
    return ListView.builder(
      controller: controller,
      itemExtent: 80,                    // 固定高度
      cacheExtent: 500,                  // 缓存范围
      itemCount: items.length + (hasMore ? 1 : 0),
      itemBuilder: (context, index) {
        if (index == items.length) {
          return Center(child: CircularProgressIndicator());
        }
        return RepaintBoundary(          // 隔离重绘
          child: ItemWidget(item: items[index]),
        );
      },
    );
  }

  @override
  void dispose() {
    controller.dispose();
    super.dispose();
  }
}

五、复杂场景xyz(5题)

51. 如何实现自定义 RenderObject?

答案

class CustomProgressBar extends LeafRenderObjectWidget {
  final double progress;
  final Color color;

  const CustomProgressBar({
    required this.progress,
    required this.color,
  });

  @override
  RenderObject createRenderObject(BuildContext context) {
    return RenderCustomProgressBar(
      progress: progress,
      color: color,
    );
  }

  @override
  void updateRenderObject(
    BuildContext context,
    RenderCustomProgressBar renderObject,
  ) {
    renderObject
      ..progress = progress
      ..color = color;
  }
}

class RenderCustomProgressBar extends RenderBox {
  double _progress;
  Color _color;

  RenderCustomProgressBar({
    required double progress,
    required Color color,
  })  : _progress = progress,
        _color = color;

  set progress(double value) {
    if (_progress != value) {
      _progress = value;
      markNeedsPaint();  // 触发重绘
    }
  }

  set color(Color value) {
    if (_color != value) {
      _color = value;
      markNeedsPaint();
    }
  }

  @override
  void performLayout() {
    size = constraints.constrain(Size(300, 20));
  }

  @override
  void paint(PaintingContext context, Offset offset) {
    final canvas = context.canvas;

    // 背景
    canvas.drawRect(
      Rect.fromLTWH(offset.dx, offset.dy, size.width, size.height),
      Paint()..color = Colors.grey[300]!,
    );

    // 进度
    canvas.drawRect(
      Rect.fromLTWH(offset.dx, offset.dy, size.width * _progress, size.height),
      Paint()..color = _color,
    );
  }
}

52. 状态管理方案如何选择?

答案

方案 复杂度 适用场景 特点
setState 简单组件 最基础
InheritedWidget 数据传递 Flutter 原生
Provider 中小型应用 官方推荐
Riverpod 现代应用 类型安全、可测试
Bloc 大型应用 事件驱动、清晰分层
GetX 快速开发 轻量、功能全

53. 如何实现国际化(i18n)?

答案

// 1. 定义翻译
class AppLocalizations {
  static Map<String, Map<String, String>> _localizedValues = {
    'en': {'hello': 'Hello', 'world': 'World'},
    'zh': {'hello': '你好', 'world': '世界'},
  };

  static String translate(BuildContext context, String key) {
    Locale locale = Localizations.localeOf(context);
    return _localizedValues[locale.languageCode]?[key] ?? key;
  }
}

// 2. 配置 MaterialApp
MaterialApp(
  localizationsDelegates: [
    GlobalMaterialLocalizations.delegate,
    GlobalWidgetsLocalizations.delegate,
  ],
  supportedLocales: [
    Locale('en', 'US'),
    Locale('zh', 'CN'),
  ],
)

// 3. 使用
Text(AppLocalizations.translate(context, 'hello'))

54. 如何实现复杂的表单验证?

答案

class FormValidator {
  static String? validateEmail(String? value) {
    if (value?.isEmpty ?? true) return '邮箱不能为空';
    if (!RegExp(r'^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$').hasMatch(value!)) {
      return '邮箱格式错误';
    }
    return null;
  }

  static String? validatePassword(String? value) {
    if (value?.isEmpty ?? true) return '密码不能为空';
    if (value!.length < 6) return '密码至少6位';
    if (!value.contains(RegExp(r'[A-Z]'))) return '需要包含大写字母';
    return null;
  }
}

class LoginForm extends StatefulWidget {
  @override
  State<LoginForm> createState() => _LoginFormState();
}

class _LoginFormState extends State<LoginForm> {
  final _formKey = GlobalKey<FormState>();
  final _emailController = TextEditingController();
  final _passwordController = TextEditingController();
  bool _isLoading = false;

  Future<void> _submit() async {
    if (!_formKey.currentState!.validate()) return;

    setState(() => _isLoading = true);

    try {
      await login(_emailController.text, _passwordController.text);
      if (!mounted) return;
      Navigator.pushReplacementNamed(context, '/home');
    } catch (e) {
      if (!mounted) return;
      ScaffoldMessenger.of(context).showSnackBar(
        SnackBar(content: Text('登录失败: $e')),
      );
    } finally {
      if (mounted) setState(() => _isLoading = false);
    }
  }

  @override
  Widget build(BuildContext context) {
    return Form(
      key: _formKey,
      child: Column(
        children: [
          TextFormField(
            controller: _emailController,
            validator: FormValidator.validateEmail,
            decoration: InputDecoration(labelText: '邮箱'),
          ),
          TextFormField(
            controller: _passwordController,
            validator: FormValidator.validatePassword,
            obscureText: true,
            decoration: InputDecoration(labelText: '密码'),
          ),
          ElevatedButton(
            onPressed: _isLoading ? null : _submit,
            child: _isLoading
              ? CircularProgressIndicator()
              : Text('登录'),
          ),
        ],
      ),
    );
  }

  @override
  void dispose() {
    _emailController.dispose();
    _passwordController.dispose();
    super.dispose();
  }
}

55. Flutter 3.24+ 最新特性有哪些?

答案

1. 新的 Sliver 组件

  • SliverFloatingHeader:浮动头部
  • PinnedHeaderSliver:固定头部
  • SliverResizingHeader:可调整大小头部

2. CarouselView(轮播)

CarouselView(
  itemCount: 10,
  itemBuilder: (context, index, realIndex) {
    return Container(color: Colors.primaries[index % 10]);
  },
)

3. TreeView(树形视图)

TreeView(
  nodes: [
    TreeViewNode(title: Text('Parent'), children: [...]),
  ],
)

4. AnimationStatus 增强

if (status.isRunning) { ... }
if (status.isForwardOrCompleted) { ... }

5. Flutter GPU(预览)

  • 直接渲染 3D 图形

6. Web 热重载支持


总结表

分类 核心知识点 题目数
Dart 基础 语法特性、空安全、泛型、扩展方法 15
Flutter 原理 三棵树、渲染流程、生命周期、Key 15
异步编程 Event Loop、Future/Stream、Isolate 10
性能优化 Widget 重建、列表优化、内存管理 10
复杂场景 自定义渲染、状态管理、表单验证 5

掌握这 55 道xyz,可以应对 99% 的 Flutter 面试!🚀

App Groups in iOS

作者 songgeb
2026年1月23日 14:41

参考:developer.apple.com/documentati…

一、什么是 App Group

  • App Group 允许同一开发者团队(Team)下的多个 App访问一个或多个共享空间(Shared Container)
  • 默认情况下(未使用 App Group):
    • 每个 App 都运行在独立进程
    • 拥有独立 沙盒
    • 无法进行数据共享
    • 无法进行 进程间通信
  • 对于 iOS 应用
    • 即使开启了 App Group
    • 只能实现多 App / App Extension 之间的数据或空间共享
    • 无法实现真正的跨进程通信( IPC
  • 对于 macOS 应用
    • App Group 可以放宽沙盒边界
    • 允许通过 Mach IPC、UNIX domain socket 等机制实现 IPC

⚠️ App Group 在 iOS 与 macOS 上的能力存在显著差异

二、App Group 的历史背景

  • App Group 是在 WWDC 2014 中提出的能力
  • iOS 8(以及 OS X 10.10 Yosemite)一起发布
  • 设计初衷是配合 App Extension 的出现:
    • 主 App
    • Widget
    • Share / Action Extension
    • 等多个进程之间的安全数据共享

三、App Group 的基本规则与限制

1. 数量限制

  • 一个开发者账号 最多可以注册 1000 个 App Group
  • 一个 App:
    • 可以不使用 App Group
    • 也可以属于一个或多个 App Group

2. 使用范围

  • 以下组合都可以使用 App Group:
    • App ↔ App Extension
    • App ↔ App
    • App ↔ App Clip

3. Container ID 规则

  • 创建 App Group 时需要设置一个 Container ID
  • Container ID 用于标识共享空间
  • 当 App Group 包含 iOS App(而非 macOS App)时
    • Container ID 必须以 group. 作为前缀

示例:

group.com.company.shared

四、iOS 中 App Group 能做什么,不能做什么

4.1 能做的事情

  • 多进程( App / Extension/App Clip)共享数据

4.2 不能做的事情

  • ❌ 不支持进程间通信(IPC)
  • ❌ 不支持 Mach IPC、socket、shared memory 等机制
  • ❌ 不能假设共享目录的真实路径
  • ❌ 不能假设共享目录一定长期存在

在 iOS 中,App Group 的本质是: 共享存储权限,而不是通信权限

五、iOS App 使用 App Group 共享空间的方式

系统提供了三种主要方式:

5.1 通过 UserDefaults 共享数据

  • 必须使用 init(suiteName:) 初始化

let defaults = UserDefaults(suiteName: "group.com.company.shared") ``defaults?.set("value", forKey: "key")

适用于:

  • 配置项
  • 功能开关
  • 小体量状态数据

5.2 通过共享容器路径读写文件

  • 使用 containerURL(forSecurityApplicationGroupIdentifier:) 获取共享空间 URL

let containerURL = FileManager.default.containerURL( ``forSecurityApplicationGroupIdentifier: "group.com.company.shared" )

说明:

  • 系统只会自动创建 Library/Caches 目录
  • 其他目录需要自行创建
  • 适合存储:
    • JSON
    • SQLite
    • 缓存文件

5.3 App Extension 使用 Background URL Session

  • 对于 App Extension:
    • 使用 URLSessionConfiguration.background
    • 设置 sharedContainerIdentifier
  • 下载的数据会直接存储在 App Group 的共享空间中

适用于:

  • 后台下载
  • Extension 与主 App 共享下载结果

六、工程实践注意事项

  • 不要写死 App Group 的磁盘路径
  • 不要假设共享容器一定存在
    • 当设备上属于同一个app group中的所有应用都卸载后,共享容器也会被删除
  • 多个 App / Extension 需要:
    • 统一目录结构
    • 统一数据格式

七、总结

  • App Group 是 iOS 8 引入的一项共享容器能力
  • 在 iOS 平台上:
    • 它解决的是数据共享问题
    • 而不是进程间通信 问题
  • 合理使用 App Group,可以安全地协调多个 App / Extension 之间的状态与资源
❌
❌