前言

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
2
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
2
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指定的分区。苹果在文档中也提到,allocWithZone仅仅是一个历史遗留设计了。下图是苹果的文档截图:

Objective-C的历史

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

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

http://blog.devtang.com/blog/2013/01/13/two-stage-creation-on-cocoa/

谈ObjC对象的两段构造模式的更多相关文章

  1. cocos2d-x中的二段构造模式

    学习cocos2d-x的过程中,会发现很多对象都通过一个静态函数create来创建.比方以下的一个样例 #define CREATE_FUNC (__TYPE__) \ static __TYPE__ ...

  2. C++解析(15):二阶构造模式

    0.目录 1.构造函数与半成品对象 2.二阶构造 3.小结 1.构造函数与半成品对象 关于构造函数: 类的构造函数用于对象的初始化 构造函数与类同名并且没有返回值 构造函数在对象定义时自动被调用 问题 ...

  3. C++ 二阶构造模式

    1.如何判断构造函数的执行结果? 构造函数没有返回值,所以不能通过返回值来判断是构造函数是否构造成功. 如果给构造函数强行加入一个返回值,用来表示是否构造成功.这样确实能够反映出构造的结果,但是不够优 ...

  4. IIS在ASP.NET Core下的两种部署模式

    KestrelServer最大的优势体现在它的跨平台的能力,如果ASP.NET CORE应用只需要部署在Windows环境下,IIS也是不错的选择.ASP.NET CORE应用针对IIS具有两种部署模 ...

  5. java web学习总结(二十九) -------------------JavaBean的两种开发模式

    SUN公司推出JSP技术后,同时也推荐了两种web应用程序的开发模式,一种是JSP+JavaBean模式,一种是Servlet+JSP+JavaBean模式. 一.JSP+JavaBean开发模式 1 ...

  6. ASP.Net的两种开发模式

    一.ASP.Net的两种开发模式 1.1 ASP.Net WebForm的开发模式 (1)处理流程 在传统的WebForm模式下,我们请求一个例如http://www.aspnetmvc.com/bl ...

  7. cocos2dx中对象的两步初始化

    笔者进来开始研究cocos2d这个非常火爆的游戏引擎,在一番折腾后,总算在win7系统下把windows和android平台搭建好了.当然接下来是从官方示例中最简单的HelloCpp项目开始.笔者使用 ...

  8. javaweb学习总结(二十一)——JavaWeb的两种开发模式

    SUN公司推出JSP技术后,同时也推荐了两种web应用程序的开发模式,一种是JSP+JavaBean模式,一种是Servlet+JSP+JavaBean模式. 一.JSP+JavaBean开发模式 1 ...

  9. 两种构造 String 的方法效率比较

    直接上代码吧: package mm_test; /** * @Function: TODO ADD FUNCTION. <br/> * @Date: 2016年4月14日 下午8:25: ...

随机推荐

  1. Codeforces 911E - Stack Sorting

    911E - Stack Sorting 思路: 用栈来模拟,能pop就pop,记下一个需要pop的数为temp,那么如果栈非空,栈顶肯定大于temp,那么加入栈 栈顶值-1 到 temp 的值,否则 ...

  2. The Front-End Checklist

    做个记录,摘自Front-End Performance Checklist HTML Minified HTML: The HTML code is minified, comments, whit ...

  3. Windows7安装JDK的环境变量设置javac不是内部命令或外部命令

    转自:http://bbs.gfan.com/android-5941970-1-1.html Windows7安装JDK的环境变量设置 Windows7 X64安装“jdk-6u26-windows ...

  4. python打印ms

    ct打印的是时间戳,时间戳的小数点后前三位为ms  eg:1555644362.055328   ms = 055 import time ct = time.time() local_time = ...

  5. 23 正则表达式和re模块

    一.正则1.字符组 [a-zA-Z0-9]字符组中的 [^a] 除了字符组的 2. 3. 4. 二.re模块 re.S 设置 .的换行 obj=re 1.ret=re.search(正则,conten ...

  6. C# 导出HTML为Excel

    最近在项目中需要Excel导出,有多个Sheet页,每个Sheet页的内容比较多,且不是规整的表格,绑定值是个比较麻烦的事,便考虑直接将HTML转换为Excel文件进行导出. 一.使用JS方法将HTM ...

  7. ECharts学习(1)--toolbox(工具栏)

    1. toolbox:这是ECharts中的工具栏.内置有导出图片.数据视图.动态类型切换.数据区域缩放.重置五个工具. 2. toolbox中的属性,不包含五个工具.里面最主要的就是feature这 ...

  8. c++中的const函数

    const变量的基础:(这里给出一个小例子) const *p://*p不可以改 int *const p://p不可以改 const int *const p//二者都不可以改 正文: 在C++中, ...

  9. ASP.NET的内置对象 —— Request 对象

    Request 对象最大的用途在于提交表单信息. (可获取页面间传递的值.客户端的 IP 地址等) 3.2.2 获取页面间传送的值 获取页面传送参数值是 Request 对象最广泛的应用之一. ASP ...

  10. OC MRC之多对象之间管理(代码分析)

    #import <Foundation/Foundation.h> @interface Book : NSObject { int _price; } - (void)setPrice: ...