一些初学者关于Objective-C/iPhone的问题

7

我刚开始学习(过去几天一直在阅读),这里有一些问题,希望有人能够回答。

1. 构造函数代码中的(self!=nil)检查。为什么要这样做?是为了防止意外访问包裹在其中的“仅运行一次”的代码吗?这种意外访问可能来自哪里?这样做表明我对正在发生的事情没有控制。

- (id)init {
    self = [super init]
    if (self != nil) {
    // Code..
    }
    return self;
}

2. 静态方法返回时不需要释放任何东西是怎么回事?(或者这是我得到的想法)

3. 如何理解str = @“Hi there!”和str = [[NSString alloc] initWithString:“Hi there!”]之间的区别?据我所知,您必须释放使用第二种方法获得的str,但不用于第一种方法?如果是这样,第一种方法何时被释放?哪个更可取(不考虑输入长度)?

str = [[NSString alloc] initWithString:@"Hi there!"];

4. 如果iphone没有垃圾回收,autorelease是什么?我注意到在main.m中创建了一个“autorelease池”。[myObject autorelease];是将myObject添加到最近的包装“autorelease池”的一种方式,以释放它吗?基本上,这是一些魔法,可以避免自己释放它?为什么使用它?

好的,目前为止就是这样。感谢任何答案!

5个回答

11
在Objective-C中,可以从-init返回一个除self以外的实例。例如,类可以这样做来强制使用单例实例,或在类群的情况下。例如,NSNumber根据传递给其初始化程序的值的类型返回子类。因此,当您调用[[NSNumber alloc] initWithLong:long_value]时,会在NSNumber+alloc之后调用NSNumber-initWithLong:初始化程序,但是NSNumber的子类可能会返回到原始的调用者。 因此,出现了以下模式:

self = [super init];

它将self重新分配为[super init]的值,使得self指向[super init]返回的实际实例。如果+alloc或超级的init方法失败,则[super init]的结果可能为nil。 为避免在初始化失败的情况下产生副作用,该模式则变为:

- (id) init {
  if(self = [super init]) {
    // do initialization of instance variables etc.
  }

  return self;
}
请注意,你必须从init方法中返回self(或nil或其他实例)。你应该将self分配给[super init],然后在继续工作之前可以检查是否为nil

你可能需要释放静态方法的返回值。你应该阅读Cocoa内存管理指南。规则通常非常简单:如果你调用的方法在其签名中包含“new”、“alloc”或“copy”,则结果属于调用方,调用者必须调用-release释放该实例,否则将导致内存泄漏。当然,如果你想保留对其他内容(即不来自“alloc”、“new”或“copy”方法)的引用,则应该调用-retain,然后在完成了该实例之后调用-release-autorelease

str=@"Hi there!",假设str被声明为NSString *str;,将字符串常量@"Hi there!"的地址赋给了str变量的值。你不需要保留或释放字符串常量。str=[[NSString alloc]initWithString:@"Hi there!"];分配了一个新的字符串实例。 str的值将是该实例的地址。每次调用str=[[NSString alloc] initWithString:@"Hi there!"];都会分配一个新实例。因此,在str2 = [[NSString alloc] initWithString:@"Hi there!"];之后,str != str2,但在str2=@"Hi There!";之后,str==str2。另请参见这里的答案。

-autorelease将接收器添加到当前的NSAutoreleasPool中。 当池被释放(通常在当前运行循环迭代结束时,或者当手动释放池时),池将对池中所有实例调用-release。如果此-release将保留计数降至0,则对象将被释放(并调用-dealloc),就像任何其他-release一样。使用自动释放池在iPhone上通常不建议,因为它可能导致您在运行循环迭代结束之前积累许多未使用的实例。如果可以使用-release而不是-autorelease,通常应该这样做。同样,请参见Cocoa内存管理指南了解更多信息。


这样一个深思熟虑、充满例子的回复。谢谢,现在大部分都清楚了。接受。 - Karolis

2
有一种思想认为,在大多数情况下,分配self指针应该由系统而不是程序员来完成。
此外,许多人更喜欢保持程序的主要流程尽可能不缩进。在这种情况下,初始化代码可以重写为:
- (id)init {
    if (![super init]) {
        return nil; // There is a problem so bail early.
    }
    // Initialisation code here.
    return self
}

"Will Shipley比我讲得更清楚。"

谢谢,那篇博客非常有帮助。但也很令人困惑。我明白它讨论的是Cocoa而不是Cocoa Touch,所以也许出于iPhone环境中其他人提到的原因(内存不足等情况),进行这个检查仍然是有效的? - Karolis
如果你刚开始学习,可能会感到困惑。我举了一个例子,说明即使是初始化器这样简单的东西也有其他的编写方式。收藏Wil的“Pimp My Code”系列,在几个月后回来看看,你会发现它对于思考如何编写Cocoa/Obj-C代码非常有用。 - Abizern
Objective-C是一种编程语言。Cocoa和Cocoa Touch是框架。虽然两者之间有一些模式上的差异,但Objective-C的原则(包括init模式)适用于两者。 - Barry Wark

1

调用

self = [super init];

如果父类由于某些原因无法初始化自己,包括内存不可用或某些先决条件未满足,则可能返回nil。如果是这种情况,您不希望尝试设置self的变量,或将self设置为代理,或将self添加到数组中,如果self为nil。

自动释放池是每次iPhone向您的应用程序发送事件时创建的东西。它在任何代码运行之前创建,并在每个事件完成后释放。您在调用autorelease 的任何对象都将放入当前的自动释放池中。自动释放池中的任何对象都将在您的代码完成后被释放多次。通过这种方式,您不必担心谁负责释放由一种方法创建并返回到另一种方法的对象。

您可以根据需要创建自己的自动释放池。

str = [[NSString alloc] initWithString:@"Hi there!"];

这行代码创建了一个不在自动释放池中的字符串,因此您需要手动释放它。只需编写

@"Hi there!";

返回一个字符串,您无需担心释放。扩展您之前的示例:

str = [[[NSString alloc] initWithString:@"Hi there!"] autorelease];

这将是另一种创建字符串的方法,您不需要担心释放。

垃圾回收和自动释放池之间的一个区别是,垃圾回收处理循环引用。使用自动释放池时,您不希望有两个对象相互保留,并希望一旦没有其他对象引用它们,它们就会消失;它们不会。


把对象放入自动释放池的好处是不需要手动释放它们,但缺点是失去了对何时释放它们的控制?基本上,如果我把我的对象交给自动释放池,那么我就要看它的心情来决定何时释放它了? - Karolis
你可能会失去释放对象的控制权,但不会失去它被解除分配的控制权。如果你想保留一个已经放入自动释放池中的对象,你可以简单地调用[obj retain],这样在自动释放池释放它后,它的引用计数仍然大于1。 - Ed Marty

1

1:这个检查是为了确保超级构造函数返回了一个新对象。

2:静态方法不引用实例。

3:

str = @"Hi there!"

这将常量字符串“Hi there!”的地址分配给指针str。

str = [[NSString alloc] initWithString:@"Hi there!"];

这将分配一个字符串并将“Hi There!”复制到其中。这意味着a)str是可修改的,b)在完成后需要将其释放。


但从一般意义上讲,两种str用法都会消耗相同的内存量,只是第一种是不可变的。使用第二种方法是否保证空间在需要时被释放?仅仅分配@"Hi there!"也会占用空间,对吧?但是没有对内存中的空间进行控制,对吧? - Karolis
NSString是不可变的,因此str在任何情况下都无法修改。 - Jason Coco
不,str是一个不可变字符串的可变容器。 - Brian Mitchell
Brian,基本上,使用第二种方法可以更改str的值,通过释放当前值并分配一个新值?那该怎么做呢?[str release]; str = [[NSString... ? - Karolis

1
  1. 如果在超级初始化后selfnil,那么你可能已经没有内存了。你唯一合理的做法是返回nil并希望在堆栈上方处理得当。

  2. 静态方法不允许在堆上分配内存,因此没有需要释放的内容。

  3. 在第一种情况下,字符串被编译到应用程序的数据段中,无法释放。在第二种情况下,您正在从堆中分配内存,并将静态字符串(来自数据段)复制到其中。

  4. 这是简单的垃圾回收。为什么要使用它,简单的答案是不要使用。由于资源有限,不建议在iPhone上使用autorelease。


因此,基本上,无论是否到达执行分配的代码部分,str = @"Hi there!" 都会在整个应用程序运行时段位于数据段中?换句话说,我拥有的这类字符串越多,我的应用程序最初需要的内存就越多?谢谢。 - Karolis
“Hi there!” 位于 bss 段,str(即用于表示地址所需的 4-8 字节存储空间)位于 data 段。str 的初始值为 bss 段中字符串的地址。 - Brian Mitchell

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接