Objective-C对象之初始化和两段构造法(二)

时间:2023-01-09 19:47:43

Objective-C对象之初始化和两段构造法(二)

作者:wangzz
转载请注明出处
如果觉得文章对你有所帮助,请通过留言或关注微信公众帐号wangzzstrive来支持我,谢谢!

Objective-C为我们提供了两种初始化对象的方法:Objective-C2.0以后可用的new方法和两段构造法。既然要比较这两种初始化方法,就从它们本身的异同出发吧。

一、两段构造法

这是Objective-C特有的对象创建方法,书写形式如下:

NSString*s=[[NSString alloc] init];

所谓的两段构造,就是指将alloc和init分开来写,这和大多数其它语言(如C、C++、Java、JavaScript)都不一样。先来看看alloc和init都干了什么吧:

1、alloc方法

当对象创建时,cocoa会从应用程序的虚拟地址空间上为该对象分配足够的内存。cocoa会遍历该对象所有的成员变量,通过成员变量的类型来计算所需占用的内存。
当我们通过alloc或allocWithZone方法创建对象时,cocoa会返回一个未”初使化“过的对象。在这个过程中,cocoa除了上面提到的申请了一块足够大的内存外,还做了以下3件事:
①将该新对象的引用计数(Retain Count)设置成1。
②将该新对象的isa成员变量指向它的类对象。isa成员变量指向分配内存的类对象(class object),这是在NSObject类中定义的,所以保证Cocoa的所有对象都带有此成员变量。它与Objective-C的运行时是一体的,借助该变量可以实现Cocoa对象在运行时的自省(Introspection)功能。
③将该新对象的所有其它成员变量的值设置成零。(根据成员变量类型的不同,零有可能是指nil或0)
④返回指向该对象的一个指针。

2、init方法

大部分情况下,我们都不希望所有成员变量都是零,所以

①init方法会做真正的初使化工作,让对象的成员变量的值符合我们程序逻辑中的初始化状态。例如,NSMutableString可能就会额外再申请一块字符数组,用于动态修改字符串。

②返回真正可以使用的指向该对象的指针

init还有一个需要注意的问题,某些情况下,init会造成alloc的原本空间不够用,而进行第二次分配内存空间。所以下面的写法是错的:
NSString  *s=[NSString alloc];

[s init];// 这儿init返回的地址可能会变。s原本的指针地址可能是无效的地址。

为此,苹果引入了一个编程规范,让大家写的时候将alloc 和init写在一行。所以上面的代码正确的写法是
NSString  *s=[[NSString alloc] init];

二、new方法

可能是为了和其他语言保持一致,苹果后来也推出了new方法来初始化对象。作为类方法的new,只是简单地等价于 alloc + init,却不能指定init的参数,所以实际使用中很少见到。

三、使用两段构造法的原因

有人可能要问,Objective-C的对象创建方法和大多数其它语言(如C、C++、Java、JavaScript)都不一样,是什么原因促使Objective-C做了这种设计?

1、历史原因

这里面多多少少就有历史的因素了。Objective-C是一门非常老的语言。如果你查阅文档,你会发现它和C++出生在同一时代(两种语言的发行年份都是1983年),都是作为C语言的面向对象的接班人被推出。当然,最终C++胜出。由于历史久远,Objective-C也无法有太多优秀的语言做参考,所以,有很多历史遗留的设计。

2、设计原则

简单看来,根据设计模式的Single Responsibility的设计原则,苹果觉得alloc和init是做的2件不同的事情,把这两件事情分开放在2个函数中,对于程序员更加清楚明了。更详细查阅文档后,我觉得这是由于历史原因,让苹果觉得alloc方法过于复杂,在历史上,alloc不仅仅是分配内存,还可以详细的指定该内存所在的内存分区(用NSZone表示)。

同时由于分配和初始化阶段是分开的,初始化方法的实现只需处理新实例的变量,并完全忽略有关分配的问题,简化了初始化方法的过程。

四、NSZone简介

早期苹果是建议程序员使用 allocWithZone来管理内存分配的,每个NSZone表示一块内存分区,+allocWithZone:(NSZone *)zone方法可以允许对象从指定分区分配内存。内存区是Cocoa的一个功能部件,它能使同时使用的对象或计算机的地址空间中相邻的对象保持在内存中,以此提高程序的性能。要解释对象在内存中的位置会如何影响性能,需要解释应用程序需要比物理内存更大的内存时会发生什么情况。

每个Cocoa应用程序都有很大的可寻址内存,当应用程序动态的分配内存时,即使计算机的所有物理内存都已经被占用,操作系统仍然会提供内存。要满足该分配要求,操作系统会使用页面调度(paging)或者交换(swapping)操作将一些物理内存中的内容复制到硬盘,之前正在使用的物理内存就可以被提供出来使用了,而之前的那些数据应经被写入硬盘。如果有需要先前复制到硬盘的那部分内存数据,操作系统会将另外一块物理内存复制到硬盘,并将先前的旧内存再度调回内存。即时内存在硬盘间调度,操作系统仍然能为每个应用程序映射地址空间到物理内存。操作系统的这一功能即是虚拟内存(virtual memory)。

由于从物理内存额硬盘中相互调度是很消耗时间的,因此,使用虚拟内存会影响性能。过多的页面调度会降低系统性能,这称为抖动(thrashing)。如果一起使用的两个或多个对象在内存中的位置很远,抖动发生的可能性将会大大增加,因此对象实例的内存分配的位置也很重要。

分区用于确保分配给同时使用的对象的内存位于相邻位置。当需要某个对象时,另外相邻的对象也基本会用到,需要的所有对象同时调入内存的可能性就更大,当不需要时,又可以都同时调出内存,Cocoa中的NSZone类型是指定标识内存区的C结构的对象,+allocWithZone:(NSZone *)zone方法允许NSZone变量从指定分区分配内存。已达到减少抖动的目的。可见当年苹果的设计师们的良苦用心!!!

只是,分区是一个十分底层的东西,而且,随着硬件设备的发展,物理内存的不断增大,以及操作系统内存分配函数复杂性的提高,使用分区的最初目的已经逐渐消失了。自从Mac OS X 10.5上引入了垃圾回收机制后,苹果就不建议程序员使用allocWithZone了,事实上,cocoa框架也会忽略+allocWithZone:(NSZone *)zone指定的分区。苹果在文档中也提到,+allocWithZone:(NSZone *)zone仅仅是一个历史遗留设计了。                                                                                                                                                                                                                                                                                                                                  ----by wangzz