Sci*_*eSE 1 cocoa objective-c ffi rust
我正在努力解决与可可基金会内存管理相关的一个问题。基本上我有一个用 Rust 编写的项目,我正在使用Objective-Ccocoa-rs并objc-rs与之交互。我熟悉CoreFoundation和CocoaFoundation中的内存管理(我已经阅读了文档中的相应文章)。当我使用 CoreFoundation 函数时,我没有遇到任何内存问题,但是当我使用 CocoaFoundation 相关的东西时,我遇到了很多问题,似乎从 CocoaFoundation 获取任何对象都会泄漏内存。
这是导致内存的函数之一的简化版本:
pub fn enumerate_apps()-> Vec<Rc<AppInfo>> {
let mut apps_list = Vec::new();
unsafe {
let shared_workspace: *mut Object = msg_send![class("NSWorkspace"), sharedWorkspace];
let running_apps: *mut Object = msg_send![shared_workspace, runningApplications];
let apps_count = msg_send![running_apps, count];
for i in 0..apps_count {
let app: *mut Object = msg_send![running_apps, objectAtIndex:i];
// Those ones are not used at the moment, but I actually need them,
// I just removed all business logic to keep the example simple and compilable
// to demonstrate the problem.
let bundle_url: *mut Object = msg_send![app, bundleURL];
let app_bundle: *mut Object = msg_send![class("NSBundle"), bundleWithURL:bundle_url];
let info_dict: *mut Object = msg_send![app_bundle, infoDictionary];
apps_list.push(Rc::new(AppInfo {
pid: msg_send![app, processIdentifier],
}));
}
}
apps_list
}
Run Code Online (Sandbox Code Playgroud)
我尝试在循环内调用此函数以使内存泄漏可见:
fn main() {
loop {
for i in 0..200 {
enumerate_apps();
}
std::thread::sleep(std::time::Duration::from_millis(5000));
}
}
Run Code Online (Sandbox Code Playgroud)
当我运行该应用程序时,我可以看到它随着时间的推移消耗越来越多的内存。
我的问题是:为什么?在此类 FFI 代码中管理内存的正确方法是什么?如果我使用普通的 Objective-C 在 XCode 中运行相同的代码,它可以正常工作,并且似乎不会泄漏内存。那么,XCode 中之所以没有内存泄漏,是因为 ARC 默认是开启的。据我所知,当我们以这种方式使用 Rust 中的 Objective-C 时,ARC 并未启用,所以基本上这意味着我们必须自己管理内存。注释包含bundle_url, app_bundle,的 3 行info_dict会产生内存泄漏消失的错觉(如果不注释它们,进程每 2 秒就会泄漏几兆字节的内存),但实际上内存仍然泄漏,但泄漏的速度没有那么快。
我尝试过的:
NSAutoreleasePool在函数的开头创建一个 并在创建时autorelease()调用bundle_url和app_bundle。没有帮助,内存仍然泄漏。release()手动调用bundle_url和app_bundle,没有任何效果。dealloc()给他们(我认为这是一个错误的方式),这也无助于解决我的问题。难道我做错了什么?或者它是一个错误objc-rs(我猜这不太可能,但谁知道)?
由于 Objective-C ARC 未在objc-rs/ 中实现cocoa-rs,因此您需要遵循内存管理规则,特别是对于这个问题:您不得放弃不属于您的对象的所有权。也就是说,您不应调用autorelease(),release()或dealloc()任何返回的对象。
你应该做的是在函数内部创建一个 NSAutoreleasePool,不要碰其他任何东西。池将在释放时释放所有这些对象。
pub fn enumerate_apps()-> Vec<Rc<AppInfo>> {
let mut apps_list = Vec::new();
unsafe {
let autoreleasePool: *mut Object = msg_send![class("NSAutoreleasePool"), new];
// ...
// all code unchanged
// ...
msg_send![autoreleasePool, release];
}
apps_list
}
Run Code Online (Sandbox Code Playgroud)
为什么调用autorelease()/ release()/dealloc()上bundle_url/ app_bundle/info_dict不能减少内存?因为不仅仅是这些对象泄漏内存。最大的消耗是running_apps对象。
为什么显式调用autorelease()/ release()/dealloc()是错误的?让我们回顾一下 ObjC 内存管理规则,并将其与普通 Rust 代码进行比较(我假设您知道该Rc<T>类型是如何工作的):
您拥有您创建的任何对象——您使用名称以“alloc”、“new”、“copy”或“mutableCopy”开头的方法创建对象
你可以这样想:
// Objective-C code:
NSMutableString* s = [NSMutableString new];
NSMutableString* t = [s mutableCopy];
// Similar to this in Rust:
let s: Rc<NSMutableString> = Rc::new(NSMutableString::new());
let t: Rc<NSMutableString> = Rc::new(s.mutableCopy());
Run Code Online (Sandbox Code Playgroud)
您的代码从未调用过任何以“alloc”、“new”、“copy”或“mutableCopy”开头的方法,因此您不拥有它们中的任何一个。所有 ObjC API 都遵循此命名约定。
您可以使用 retain 取得对象的所有权。
这类似于拥有一个对象a: Rc<T>,然后通过调用获得一个新的引用b = Rc::clone(&a)。现在b还通过引用计数“拥有”原始对象:
// Objective-C code:
NSMutableString* u = [t retain];
// Similar to this in Rust:
let u: Rc<NSMutableString> = Rc::clone(&u);
Run Code Online (Sandbox Code Playgroud)
但是您从未调用过retain,因此您仍然不拥有任何对象。
当您不再需要它时,您必须放弃您拥有的对象的所有权——您可以通过向对象发送release消息或autorelease消息来放弃对象的所有权。
在 Rust 中,发送-release消息相当于丢弃 Rc 对象。
// Objective-C code:
[u release];
// Similar to this in Rust:
drop(u);
Run Code Online (Sandbox Code Playgroud)-autorelease将所有权转移到自动释放池。会找到最近分配的 NSAutoreleasePool ,对象的所有权被移动到那个池中,我们只保留一个借用的引用(*)。
// Objective-C code:
NSMutableString* v = [t autorelease];
// Similar to this in Rust:
let pool: &NSAutoreleasePool = find_top_autorelease_pool()?;
let v: &NSMutableString = pool.add_object(t);
// `t` is passed-by-value, so `pool` now owns `t`.
// `pool` returns a borrowed reference,
// so that we can still access the memory pointed to by `t`,
// but we no longer own it.
Run Code Online (Sandbox Code Playgroud)您不得放弃不属于您的对象的所有权。
此外,调用-dealloc就像drop(*s)在 Rust 中显式调用析构函数一样。这绕过了引用计数机制,并且明确不鼓励这样做。
让我们回顾一下:
sharedWorkspace/ runningApplications/ objectAtIndex:/ bundleURL/ bundleWithURL:/ infoDictionary)开始alloc/ new/ copy/ mutableCopy。-retain。release()autorelease()根据规则 4,您永远不应该调用or。调用-release或调用-autorelease您不拥有的对象会导致双重释放。这可能会导致 SEGFAULT、无操作或任何未定义的行为。
如果我们不提供 NSAutoreleasePool,为什么程序会像筛子一样泄漏?该runningApplications/bundleWithURL:方法做分配对象,但秉承可可内存管理规则,他们称之为-autorelease内部,以确保你没有得到所拥有的对象。但是如果我们不分配任何池,-autorelease就可以将所有权转移到任何地方,即那些自动释放的对象成为任何人都不拥有的,没有人拥有释放它们的所有权,从而泄漏。
(*):这个类比并不完美,因为您可以通过[[x autorelease] retain]. 但是这个细节在这里无关紧要。
| 归档时间: |
|
| 查看次数: |
465 次 |
| 最近记录: |