zoukankan      html  css  js  c++  java
  • 【转】谈ObjC对象的两段构造模式

    转自:http://www.cocoachina.com/bbs/read.php?tid=132156

    前言

    Objective-c语言在申请对象的时,需要使用两段构造(Two Stage Creation)的模式。一个对象的创建,需要先调用alloc方法或allocWithZone方法,再调用init方法或initWithSomething方法。如下是一个NSString对象的创建示例:

     
    复制代码
    1. NSString * str = [[NSString alloc] initWithString:@"http://blog.devtang.com"];



    由于该语言的对象创建方法和大多数其它语言(如C、C++、Java、JavaScript)都不一样,所以引起了我的好奇。是什么原因促使Objective-C做了这种设计,而又是什么原因促使大多数其它语言都采用"new"方法来一次性创建对象呢?

    在看了[《Cocoa Design Patterns》]一书(顺便吐槽一下该书中文版翻译质量不高,建议看英文版),并且做了一些调研之后,我将总结分享给大家,欢迎大家讨论。

    对象的创建

    我们先来看看在对象的创建过程中,alloc和init到底做了哪些事情。

    alloc方法
    根据苹果的官方文档。当对象创建时,cocoa会从应用程序的虚拟地址空间上为该对象分配足够的内存。cocoa会遍历该对象所有的成员变量,通过成员变量的类型来计算所需占用的内存。

    当我们通过alloc或allocWithZone方法创建对象时,cocoa会返回一个未”初使化“过的对象。在这个过程中,cocoa除了上面提到的申请了一块足够大的内存外,还做了以下3件事:

     1. 将该新对象的引用计数(Retain Count)设置成1。
     2. 将该新对象的isa成员变量指向它的类对象。
     3. 将该新对象的所有其它成员变量的值设置成零。(根据成员变量类型,零有可能是指nil或Nil或0.0)

    isa成员变量是在NSObject中定义的,所以保证Cocoa的所有对象都带有此成员变量。借助该变量可以实现Cocoa对象在运行时的自省(Introspection)功能。

    init方法

    大部分情况下,我们都不希望所有成员变量都是零,所以init方法会做真正的初使化工作,让对象的成员变量的值符合我们程序逻辑中的初始化状态。例如,NSMutableString可能就会额外再申请一块字符数组,用于动态修改字符串。

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

     
    复制代码
    1. NSString * s = [NSString alloc];[s init]; // 这儿init返回的地址可能会变。s原本的指针地址可能是无效的地址。



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

     
    复制代码
    1. NSString * s = [[NSString alloc] init];



    new

    在后来,苹果也引入了类方法:new。但是由于历史原因,init方法是实例方法而非类方法,所以作为类方法的new,只能简单地等价于 alloc + init,不能指定init的参数,所以用处不大。苹果在设计上也禁止多次调用init方法,例如如下代码会抛出 NSInvalidArgumentException。

     
    复制代码
    1. NSString * str = [NSString new];str = [str initWithString:@"Bar"];



    为什么这么设计

    说回来文章开始时提出来问题,为什么苹果要这么设计而其它语言不这么设计?

    上面提到,alloc其实不只干了申请内存的事情,还做了:
     1. 内存管理的事情,设置Retain Count。
     2. 运行时自省的功能,设置isa变量。
     3. 非逻辑性的初使化功能,设置所有成员变量为零。

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

    在《Cocoa Design Patterns》一书也提到,早期苹果是建议程序员使用 allocWithZone来管理内存分配的,每个NSZone表示一块内存分区,allowWithZone方法可以允许对象从指定分区分配内存。了解到这段历史后,也不难理解苹果这么设计的原因了。因为在这种情况下,alloc要处理的情况复杂,和init放到一起不合适。

    而对于大多数出生在90年代的语言来说(例如Java,JavaScript,C#),由于内存具体的分配方案都不需要程序员操心了,所以就不需要单独为内存分配实现一个alloc方法了。

    补充

    allocWithZone被废弃
    自从Mac OS X 10.5上引入了垃圾回收机制后,苹果就不建议程序员使用allocWithZone了,事实上,cocoa框架也会忽略allocWithZone指定的分区。苹果在文档中也[提到](https://developer.apple.com/library/mac/#documentation/cocoa/Reference/Foundation/Classes/NSObject_Class/Reference/Reference.html#//apple_ref/occ/clm/NSObject/allocWithZone:),allocWithZone仅仅是一个历史遗留设计了。下图是苹果的文档截图:



    Objective-C的历史

    Objective-C是一门非常老的语言。如果你查阅文档,你会发现它和C++出生在同一时代(两种语言的发行年份都是[1983年](http://en.wikipedia.org/wiki/Stepstone)),都是作为C语言的面向对象的接班人被推出。当然,最终C++胜出。由于历史久远,Objective-C也无法有太多优秀的语言做参考,所以,有很多历史遗留的设计。在2007年苹果公司发布了Obj-C 2.0, 对其进行了大量改进。

    在最近几年的WWDC大会上,每年苹果都会对Objective-C和其对应的LLVM编译器进行改进,例如WWDC2011推出的ARC,WWDC2012推出的Object Literals等。所以现在使用Objective-C做开发已经非常舒服了。期待苹果给开发者带来更多惊喜。 

  • 相关阅读:
    changing a pointer rather than erasing memory cells
    验证码识别 edge enhancement 轮廓增强 region finding 区域查找
    Manipulating Data Structures
    passing parameters by value is inefficient when the parameters represent large blocks of data
    Aliasing 走样
    Artificial Intelligence Research Methodologies 人工智能研究方法
    Thread safety
    include pointers as a primitive data type
    flat file
    functional cohesion
  • 原文地址:https://www.cnblogs.com/ygm900/p/2880709.html
Copyright © 2011-2022 走看看