内存管理

大多数 Java 开发者遇到的第一个问题是,J2ObjC 是如何实现内存管理的,因为 Java 具有垃圾回收功能,而 Objective-C 默认没有垃圾回收。iOS 具有两种内存管理方法:引用计数和自动引用计数 (ARC)。

J2ObjC 会根据所选方法生成不同的内存管理代码。使用 -use-arc 选项进行转换,以生成使用 ARC 的代码。默认情况下,它使用手动引用计数。

引用计数

引用计数方法明确了对象所有权。方法在创建对象时拥有对象,直到释放该对象。从其他方法接收对象时,如果方法返回后需要引用,则接收方方法将保留该对象。当方法不再需要引用对象时,必须将其释放。当对象的保留计数为零时,其内存会被释放,对象不再有效。释放对象后,系统会调用其 dealloc() 方法,以释放其实例变量的所有权。

这种方法存在的一个问题是,如何转移对象的所有权。例如,系统可能会调用工厂方法来创建对象。如果工厂方法在返回对象之前释放对象(因为它不再希望拥有该对象),则在调用方法可以保留它之前,请先释放该对象。

为了转移对象的所有权,方法会向该对象发送自动发布消息(而不是发布消息),这会延迟发布消息。这样,工厂方法就可以创建要返回的对象,并放弃其所有权,而无需使该对象失效。系统会定期(例如在 iOS 应用中的每个事件循环迭代之后)自动发布池“排空”,这意味着系统会向该池中的所有对象发送延迟发布消息。保留计数降至零的所有对象都会照常释放。

由于内存管理的负担由开发者承担,因此使用引用计数方法很容易造成内存泄漏。不过,Apple 建议遵循一些最佳实践来尽量避免这个问题,由 J2ObjC 实现。

此外,还提供用于检测内存泄漏的运行时和工具支持。Objective-C 运行时会在应用退出时报告检测到的任何泄漏,这就是 J2ObjC 将 JUnit 测试转换为可执行二进制文件的原因之一。Xcode 使用 Clang,并且该编译器针对内存问题提供出色的静态分析功能,而 Xcode 通过其 Analyze 命令提供这种分析。

自动引用计数 (ARC)

ARC 是 Apple 推荐使用的内存管理方法。它将引用计数的责任转移到了编译器,编译器将在编译期间添加适当的保留、释放和自动发布方法。对于搭载 iOS 5 及更高版本的设备,ARC 支持弱引用。

我们建议项目使用 ARC 翻译代码。转译后的 Objective-C 代码就像手写的 Objective-C 代码。使用 ARC 可以帮助开发者避免与内存相关的常见错误(例如过度释放或引用不足),从而使内存管理变得更简单且不易出错。

请注意,默认情况下,ARC 并非异常安全。具体而言,它会在抛出异常时泄漏内存。由于异常在 Java 中更常见,并且通常可恢复,因此这可能会带来问题。使用 arc 进行编译时使用 -fobjc-arc-exceptions 可以修复漏洞,同时降低性能。

我们还建议新项目针对手写 Objective-C 代码使用 ARC,并且只有在分析数据显示存在实际性能问题时,才回退到手动引用计数。ARC 代码和非 ARC 代码都可以顺利编译并关联到同一应用。

弱引用

字段可以使用 com.google.devtools.j2objc.Weak 进行注解,转译器会使用该注解生成遵循 Objective-C 弱引用语义的字段。使用引用计数时,这意味着该字段在初始化时不会保留,而是在包含的实例释放时自动释放。使用 ARC 时,弱字段会使用 __unsafe_unretained 注解进行标记,相关属性会声明为弱字段。

在某些情况下,内部类实例与其外部实例陷入引用循环。此处,com.google.devtools.j2objc.WeakOuter 注解用于标记内部类,因此系统会按上述方式处理对外部类的引用。内部类中的其他字段不受此注解的影响。

J2ObjC 还支持 WeakReference 类,因此使用该类的 Java 代码在转换后会以相同方式工作。请注意,WeakReference 在 JVM 上本质上是不确定的;希望在保持可预测性的同时避免内存泄漏的应用应首选 @Weak@WeakOuter

内存管理工具