iOS 开发技巧及常见误区

本文详述了iOS开发中的几个常见误区,包括NSNotification的使用、-[NSUserDefaults synchronize]、Block的循环引用处理、响应者链的工作原理以及GCD中的线程与队列理解。通过这些实践,开发者可以避免潜在问题并提升应用性能。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

本文列举了 iOS 开发中一些容易被误解或忽视的点,高德客户端研发的同学结合实际经验,做了一份小结,供大家查缺补漏。本文分为 Foundation,UIKit 和 GCD 三部分,一起来看下。

0x00 Foundation

0x01 Must call -[NSNotificationCenter removeObserver:] in dealloc?

-[NSObject dealloc] 方法中移除 self 对 NSNotificationCenter 的监听早已深入人心。它很容易被人遗忘,也让 NSNotificationCenter 的使用变得麻烦,这种机械式重复劳动实际上是设计的问题,完全可以通过框架来简化。

当然,Apple 也发现了这个问题。从 iOS 9 和 macOS 10.11 开始,开发者已经无需再在 -[NSObject dealloc] 中移除监听了。

参见 Apple 官方文档

If your app targets iOS 9.0 and later or macOS 10.11 and later, you don't need to unregister an observer in its dealloc method. 
Otherwise, you should call removeObserver:name:object: before observer or any object passed to this method is deallocated.

由于调用 -[NSNotificationCenter addObserverForName:object:queue:usingBlock:] 返回的对象会被系统 retain,所以还是需要被手动 remove.

0x02 -[NSUserDefaults synchronize]

-[NSUserDefaults synchronize] 是一个 Apple 一直恋恋不舍的 API。Apple 从 iOS 7 开始就在文档中说它已经废弃了:

-synchronize is deprecated and will be marked with the API_DEPRECATED macro in a future release.

但直到今天(iOS 13.2),也没给它打上 API_DEPRECATED。它是一个耗性能的 API,被调用时会阻塞当前线程直到所有数据被同步。

实际上,Apple 已在底层实现了一套内存缓存,使得 NSUserDefaults 不再需要频繁调用 synchronize,从而简化了 NSUserDefaults 的使用,同时也提高了性能。

对于 iOS App,移除 -[NSUserDefaults synchronize] 唯一需要适配的情况:

  • 写入数据后希望通知其它程序读取。在这种情况下,Apple 建议在其它程序中使用 KVO 去监听 NSUserDefaults

对于 non-app 进程,如 CLI,agent,daemon,需要适配的情况:

  • 在即将 exit 进程时,调用 CFPreferencesAppSynchronize(kCFPreferencesCurrentApplication)

0x03 Block

以下代码是不是很熟悉:

__weak typeof(self) weakSelf = self;
^{
    __strong typeof(self) strongSelf = weakSelf;
    // do something with `strongSelf`
};

这种做法虽然能够避免 block 循环引用,但它有诸多问题:

1)block 中后续的代码如果存在 self,则还会有循环引用

有小伙伴可能会说,我谨慎点写 self,不就行了?实际上,我们可能会遇到以下一些隐晦的 self:

  • 直接使用 _ivar 会被编译器编译为 self->_ivar

  • 使用一些系统的宏,展开后带有 self,如 NSAssert

所以,最佳实践是__strong typeof(self) self = weakSelf;

使用一个名为 self 的局部变量,抑制该作用域中本来的 self 关键字,来确保不会出现 self 的循环引用。ReactiveObjC 中的 @strongify() 便是这样实现的

2)使用 strongSelf 时,它可能已经为 nil

上述写法只能保证在 block 执行阶段,self 不被释放,但 self 可能在 block 执行之前就已经释放了。

所以,最佳实践是这样

__weak typeof(self) weakSelf = self;
^{
    __strong typeof(self) self = weakSelf;
    if (!self) {
        // error handling
        return;
    }
    // do something with `self`
};
延伸
  • 有小伙伴可能会有疑问,在 block 中使用 typeof(self) 不会导致循环引用吗?

答案当然是不会。typeof() 是一个编译期指令,在编译期,编译器就会将 typeof(self) 转换为 self 真正的类型,比如 UIViewController *

  • __strong 是必须的吗?

在 ARC 中,如果不指定变量的内存管理语义,则默认是 __strong,所以此处不是必须的。但这里牵扯 weak-strong 转换,为了遵守 Explicit is better than implicit. 的原则,还是建议写上。

  • Heap-Stack Dance

上面避免循环引用的方式也被人称作 "Weak-Strong Dance"。另一种解决循环引用的 pattern,称作 "Heap-Stack Dance",也常被使用,尤其在 API 设计时:

^(SomeType self) {
    // do something with `self`
};

这种方式在调用 block 时&

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值