stringWithFormat 和 initWithFormat 在 ARC 中产生不同的值

stringWithFormat and initWithFormat result different values in ARC

我是 objective C 和 iOS 的新手。当像下面这样使用 NSString stringWithFormat 和 initWithFormat 时,我对 2 个结果值感到困惑:

NSString* str0 = @"Hello";
__weak NSString* str1 = [[NSString alloc] initWithFormat:@"%@", str0 ]; // warning: Assigning retained object to weak variable; object will be released after assignment
__weak NSString* str2 = [NSString stringWithFormat:@"%@",str0];

NSLog(@"Result: str0 %@, str1 %@, str2 %@", str0, str1, str2);

输出:结果:str0 Hello, str1 (null), str2 Hello

环顾网络,这些呼叫者的答案在 ARC 示例下是相同的:stringWithFormat vs initWithFormat under ARC

但是对于上面的代码,str2 在这里似乎并不意味着弱或强,即我可以删除 str2 声明的 __weak 并得到相同的结果。

我担心的是,如果通过 stringWithFormat 创建的字符串归框架所有(或以其他方式超出用户应用程序的范围)?

str1 为空,因为你对字符串只有弱引用,所以 ARC 可能会立即释放它,因为你没有对它的强引用。事实上,编译器甚至可能会警告您:

warning: assigning retained object to weak variable; object will be released after assignment.

str2Hello 因为 stringWithFormat 创建和 "autorelease" 对象(一个没有强引用的对象,但在池被耗尽之前不会被释放,即在你屈服于 OS 之后)。它不是 "owned by the framework",只是还没有被释放。

请注意,使用 NSString 查看内存管理有时会出现问题(尤其是在处理字符串文字时),因为内部优化有时会影响其内存管理,但您向我们展示的这些结果正是你会期待的。


考虑到非典型的 NSString 内存管理,您可能希望使用您自己的自定义对象来观察此模式:

@interface CustomObject: NSObject
+ (instancetype)customObject;
@end

@implementation CustomObject
+ (instancetype)customObject {
    return [[self alloc] init];
}
@end

__weak CustomObject *obj1 = [[CustomObject alloc] init];
__weak CustomObject *obj2 = [CustomObject customObject];

NSLog(@"obj1=%@; obj2=%@", obj1, obj2);

在这种情况下,您会看到 obj1 始终是 nil,但 obj2 不是。 basic rule of memory management 是您拥有名称以“alloc”、“new”、“copy”或“mutableCopy”开头的任何对象。因此,这些对象的所有权已转移给您,ARC 将为您释放它们(即您只有一个 weak 引用,因此它会立即释放)。名称以除这些前缀之外的任何内容开头的对象尚未将所有权传递给您(因此,在 Objective-C 中,已作为自动释放对象传递给您的应用程序,如果没有强引用,当池被释放时,这些对象将被释放筋疲力尽。


对于你问题的根源,对象是否"owned by the framework",答案一般是否定的,但也有例外。例如 NSString 有一些优化可以保留字符串引用。同样有 UIImage 方法,特别是 imageNamed 缓存图像。等等

但是,一般来说,您不必担心 OS 正在做什么。只要确保你解决了你自己的强引用,让 OS 做它会做的。通常,如果它进行缓存,无论如何都会在内存压力时清除这些缓存。

我对你问题的回答

If you want to have a weak attribute to a property someone should have already retained it

.h

@property (nonatomic, weak) NSString *str1;
@property (nonatomic, weak) NSString *str2;

.m

@synthesize str1,str2;


-(void)viewDidLoad
{
   NSString* str0 = @"Hello";
   NSString* str1 = [[NSString alloc] initWithFormat:@"%@", str0 ];
   NSString* str2 = [NSString stringWithFormat:@"%@",str0];
   NSLog(@"Result: str0 %@, str1 %@, str2 %@", str0, str1, str2);
}

输出

Result: str0 Hello, str1 Hello, str2 Hello

Graver answer here

来自Apple Forum Document

的详细解释

The difference is in how the return values are memory-managed. alloc/initWithFormat: returns a retained string that you will have to release or autorelease yourself. stringWithFormat: returns a string that has already been autoreleased. (An autoreleased object will be released when the current autorelease pool disappears; that usually happens right before the next event is processed.)

Which you should use depends on what you're doing with the string. If it's a temporary string, if it's going to be stored inside another object, or if it's going to be stored in your own object using the "self.property" syntax, use stringWithFormat:; if it's going to be assigned to a global, static or instance variable (without using "self.property"), use alloc/initWithFormat:.

In your case, you could go either way. stringWithFormat: would allow you to remove the explicit release call, but autoreleasing is also very slightly slower than releasing. It's up to you to decide whether you want your code to be smaller and simpler or longer and faster.

弱变量不包含对对象的引用。当对象被释放时,weak 变量将被设置为 nil。由于您在其他地方没有参考资料,因此随时可能发生。

另一方面,iOS 可以随意保留其他引用。所以不能保证 weak 变量会被设置为 nil。因此,根据您的代码,每个变量可能为 nil,也可能不为 nil 且值为 "Hello"。

这就是你得到的。每个变量都可以设置为 nil 或包含 "Hello",这就是发生的事情。

@gnasher729:我希望知道在哪种情况下指向 NSString stringWithFormat 的 weak/strong 指针使用正确与否,而它与 NSString alloc、init 有很大不同。 我认为清楚地了解它有助于确定我何时以及如何正确使用它。 顺便说一下,我同意@Rob 和@user3182143 的观点,NSString stringWithFormat 会产生一个自动释放的对象。 为了证明这一点,我确实更改为强制释放该对象,现在很清楚了:

NSString* str0 = @"Hello";
__weak NSString* str1 = [[NSString alloc] initWithFormat:@"%@", str0 ];

__weak NSString* str2_weak = nil;
NSString* str2 = nil;


@autoreleasepool {
    str2_weak = [NSString stringWithFormat:@"%@",str0];
    str2 = [NSString stringWithFormat:@"%@",str0];
}

NSLog(@"Result: str0 %@, str1 %@, str2_weak %@ str2 %@", str0, str1, str2_weak, str2);

结果:str0 你好,str1(空),str2_weak(空)str2 你好

使用上面修改后的代码,一个自动释放池块来覆盖 stringWithFormat 的结果,str2_weak 保持对自动释放对象的弱引用,因此它在块之后应该为空; str2 实际上保留自己的字符串,因此不会被释放。 现在输出对我来说是'make sense':