引言
Foundation 提供字符串、集合、日期、文件、网络和并发等基础类型,是 iOS 与 macOS 应用常用的系统框架。本文聚焦公开 API 的使用边界和可复现代码,不对 Apple 未公开的内部布局、指针编码或线程调度实现做确定断言。具体行为应以当前 SDK 文档和目标系统测试为准。
1. NSString:关注不可变语义
NSString 是不可变类型,创建方式不同并不意味着业务代码可以依赖某种内存布局。不要根据字符串长度猜测是否使用 tagged pointer,也不要把内部类名当成稳定 API。需要可变文本时使用 NSMutableString,并在跨线程共享时明确所有权和同步边界。
NSString *title = @"SEO 基础架构";
NSString *url = [NSString stringWithFormat:@"https://blog.zenleak.cn/post/%@/", @"201"];
if ([title length] > 0) {
NSLog(@"%@", url);
}
字符串编码、长度和字符数量也要区分:length 返回 UTF-16 code unit 数量,不等同于用户看到的字素数量。处理用户输入时,先明确校验规则和显示需求。
2. 集合与线程安全
不可变集合在创建后不能被修改,但“不可变”不等于整个业务流程自动线程安全。可变集合在并发读写时需要串行队列、锁或其他明确的同步策略;不要用未经测试的固定崩溃比例描述风险。
NSMutableArray<NSString *> *names = [NSMutableArray array];
dispatch_queue_t namesQueue = dispatch_queue_create("cn.zenleak.names", DISPATCH_QUEUE_SERIAL);
dispatch_async(namesQueue, ^{
[names addObject:@"Django"];
});
dispatch_sync(namesQueue, ^{
NSLog(@"count = %lu", (unsigned long)names.count);
});
如果数据可以在构建后共享,优先生成不可变副本并通过属性传递。使用 @synchronized 前要考虑锁对象生命周期、异常路径和性能;对于高频数据结构,串行队列或专用并发读写方案通常更容易审查。
3. RunLoop 与线程生命周期
主线程的 RunLoop 由应用框架管理。后台线程只有在需要处理输入源、定时器或持续事件时才需要运行 RunLoop;启动 RunLoop 不会自动让任意后台任务变成长期服务。
NSThread *worker = [[NSThread alloc] initWithBlock:^{
@autoreleasepool {
NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
[runLoop addPort:[NSPort port] forMode:NSDefaultRunLoopMode];
[runLoop runMode:NSDefaultRunLoopMode
beforeDate:[NSDate dateWithTimeIntervalSinceNow:1.0]];
}
}];
[worker start];
生产代码要保存退出条件,避免线程因为无休止的 RunLoop 而无法回收。需要执行一次后台任务时,优先使用 GCD 或 NSOperationQueue,并让任务拥有明确的取消和错误处理路径。
4. NSURLSession 网络请求
网络请求应处理状态码、响应类型、错误、超时和取消。不要把任何响应都当成 JSON,也不要在后台线程直接更新界面。
NSURL *url = [NSURL URLWithString:@"https://blog.zenleak.cn/"];
NSURLSessionDataTask *task = [[NSURLSession sharedSession]
dataTaskWithURL:url
completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
NSHTTPURLResponse *http = (NSHTTPURLResponse *)response;
if (error || http.statusCode < 200 || http.statusCode >= 300) {
NSLog(@"request failed: %@ status=%ld", error, (long)http.statusCode);
return;
}
dispatch_async(dispatch_get_main_queue(), ^{
// 仅在这里更新 UI,并先检查 data 的解析结果。
NSLog(@"received %lu bytes", (unsigned long)data.length);
});
}];
[task resume];
正式应用还应配置请求超时、缓存策略、证书和隐私说明;上传文件时校验类型、大小和服务端返回值。
5. GCD 与取消边界
GCD 适合把独立工作放到后台队列,但 dispatch_block_t 的取消只对尚未执行的可取消任务有意义,已经开始运行的代码需要自己检查状态。共享状态应放在串行队列、锁或 actor 等明确的同步边界内。
dispatch_queue_t workQueue = dispatch_get_global_queue(QOS_CLASS_UTILITY, 0);
dispatch_async(workQueue, ^{
@autoreleasepool {
// 分块处理数据,必要时在每个阶段检查取消状态。
NSLog(@"background work");
}
});
6. 发布前验证
- 在目标 Xcode、SDK 和最低系统版本上编译代码。
- 用 Thread Sanitizer 检查可疑的并发读写,用 Instruments 观察内存和线程生命周期。
- 为网络错误、取消、空响应和非 2xx 状态编写测试。
- 不引用未公开类名、内存布局或未经来源支持的“崩溃率”。
Foundation API 的稳定价值来自公开语义和可重复测试,而不是对内部实现的猜测。需要更完整的工程验证时,可结合本站的 Django 访问分析后台设计 和 站内搜索实战 了解如何把状态、错误和回退路径记录下来。
评论 (0)
暂无评论,快来抢沙发吧!