对象初始化alloc和init嵌套调用的原因

在objective-c基础教程中有说到在初始化的时候务必要像下面这样的写法:

Car *car = [[Car alloc] init];

而不能这样写:

Car *car = [Car alloc];
[car init];

这是因为初始化方法返回的对象可能与分配的对象不同,而这都是类簇所造成。下面就介绍下什么是类簇。

类簇,是由一个抽象的父类及一组私有化(private)的具体子类组成。程序员只能用父类对外提供的接口(interface)来生成类簇中某个具体的子类对象。

象类Number是类簇中各子类的父类,而用灰色表示的各个子类,是私有子类(private subclass),私有子类的意思是,程序员无法访问到这些子类,也无法直接用子类生成对象,程序员只能通过Number这个抽象父类对外公开的接口来生成各个具体子类的对象。

为什么需要这样来设计呢?类簇解决的是什么问题呢?让我们还是用上面的例子来说明。

Char,UnsignedChar,Short,UnsignedShort,Int,UnsignedInt,LongInt这些子类,都可以存储数字,只不过是不同种类的数字,这些不同种类的数字实际上有很多共同的特点(比如可以从一种类型转换成另外一种,活着它们都可以转换成字符串(string)),那这些不同种类的数字,可以定义成一个类吗?这是不可以的,因为不同种类的数字,它们所占的内存空间并非一样大,如果将它们归并为一个类,效率是非常低的。基于这点现实的考虑,所以提出了类簇(Class Clusters)的设计模式来解决这个经常会出现的问题。其中抽象父类中来定义方法(可以实现比如不同子类间类型转换等等功能),而不定义属性。

引入类簇(Class Clusters)设计模式后,所有的子类均继承自一个抽象的父类(abstract superclass),而具体的子类又是私有的(private subclass),程序员只能通过其共同的抽象父类来生成子类对象,比如上图中的抽象父类是Number那么问题出现了,既然只能通过Number公开的接口来生成子类的对象,那么这个Number是怎么生成我们想要的具体子类对象呢?

为了解决这个问题,类簇中的抽象父类必须定义出生成类簇中不同子类对象的方法(method),程序员通过给抽象父类发送不同的消息,抽象父类返回消息所对应的子类的对象,这就是抽象父类的使命。拿NSNumber类举个例子,NSNumber是一个类簇的抽象父类,下面有Char,Int,Float,Double等这些子类,你可以通过给NSNumber发送如下消息来生成不同子类的对象。

编程语言 Objective-C 详解Objective-C 类簇Class-Clusters 转载请保留此行.谢谢.

下面是对NSArray类簇的详解

Class Clusters

Class Clusters(类簇)是抽象工厂模式在iOS下的一种实现,众多常用类,如NSString、NSArray、NSDictionary以及NSNumber都运作在这一模式下,它是接口简单性和扩展性的权衡体现,在我们完全不知情的情况下,偷偷隐藏了很多具体的实现类,只暴露出简单的接口。

NSArray的类簇

虽然官方文档中拿NSNumber说事儿,但Foundation并没有像图中描述的那样为每个number都弄一个子类,于是研究下NSArray类簇的实现方式。

__NSPlacehodlerArray

熟悉这个模式的同学很可能看过下面的测试代码,将原有的alloc+init拆开写:


1

2

3

4

id obj1 = [NSArray alloc]; // __NSPlacehodlerArray *

id obj2 = [NSMutableArray alloc];  // __NSPlacehodlerArray *

id obj3 = [obj1 init];  // __NSArrayI *

id obj4 = [obj2 init];  // __NSArrayM *

发现+ alloc后并非生成了我们期望的类实例,而是一个__NSPlacehodlerArray的中间对象,后面的- init或- initWithXXXXX消息都是发送给这个中间对象,再由它做工厂,生成真的对象。这里的__NSArrayI和__NSArrayM分别对应Immutable和Mutable(后面的I和M的意思)

于是顺着思路猜实现,__NSPlacehodlerArray必定用某种方式存储了它是由谁alloc出来的这个信息,才能在init的时候知道要创建的是可变数组还是不可变数组

于是乎很开心的去看了下*obj1的内存布局:

下面是32位模拟器中的内存布局(64位太长不好看就临时改32位了- -),第一个箭头是*obj1,第二个是*obj2

我们知道,对象的前4字节(32位下)为isa指针,指向类对象地址,上图所示的0x0051E768就是__NSPlacehodlerArray类对象地址,可以从lldb下po这个地址来验证。

那么问题来了,这个中间对象并没有储存任何信息诶(除了isa外就都是0了),那它init的时候咋知道该创建什么呢?

经过研究发现,Foundation用了一个很贱的比较静态实例地址方式来实现,伪代码如下:


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

static __NSPlacehodlerArray *GetPlaceholderForNSArray() {

    static __NSPlacehodlerArray *instanceForNSArray;

    if (!instanceForNSArray) {

        instanceForNSArray = [[__NSPlacehodlerArray alloc] init];

    }

    return instanceForNSArray;

}

static __NSPlacehodlerArray *GetPlaceholderForNSMutableArray() {

    static __NSPlacehodlerArray *instanceForNSMutableArray;

    if (!instanceForNSMutableArray) {

        instanceForNSMutableArray = [[__NSPlacehodlerArray alloc] init];

    }

    return instanceForNSMutableArray;

}

// NSArray实现

+ (id)alloc

{

    if (self == [NSArray class]) {

        return GetPlaceholderForNSArray()

    }

}

// NSMutableArray实现

+ (id)alloc

{

    if (self == [NSMutableArray class]) {

        return GetPlaceholderForNSMutableArray()

    }

}

// __NSPlacehodlerArray实现

- (id)init

{

    if (self == GetPlaceholderForNSArray()) {

        self = [[__NSArrayI alloc] init];

    }

    else if (self == GetPlaceholderForNSMutableArray()) {

        self = [[__NSArrayM alloc] init];

    }

    return self;

}

Foundation不是开源的,所以上面的代码是猜测的,思路大概就是这样,可以这样验证下:


1

2

3

4

id obj1 = [NSArray alloc]; 

id obj2 = [NSArray alloc];

id obj3 = [NSMutableArray alloc];

id obj4 = [NSMutableArray alloc];

// 1和2地址相同,3和4地址相同,无论多少次都相同,且地址相差16位

静态不可变空对象

除此之外,Foundation对不可变版本的空数组也做了个小优化:


1

2

3

4

5

NSArray *arr1 = [[NSArray alloc] init];

NSArray *arr2 = [[NSArray alloc] init];

NSArray *arr3 = @[];

NSArray *arr4 = @[];

NSArray *arr5 = @[@1];

上边1-4号都指向了同一个对象,而arr5指向了另一个对象。

若干个不可变的空数组间没有任何特异性,返回一个静态对象也理所应当。

不仅是NSArray,Foundation中如NSString, NSDictionary, NSSet等区分可变和不可变版本的类,空实例都是静态对象(NSString的空实例对象是常量区的@"")

所以也给用这些方法来测试对象内存管理的同学提个醒,很容易意料之外的。

转载自:http://www.cocoachina.com/ios/20141219/10696.html
参考:

https://developer.apple.com/library/ios/documentation/general/conceptual/CocoaEncyclopedia/ClassClusters/ClassClusters.html

http://iphonedevwiki.net/index.php/Foundation.framework/Inheritance_hierarchy

时间: 2024-08-03 09:31:49

对象初始化alloc和init嵌套调用的原因的相关文章

C#4.0语法糖之第三篇: 参数默认值和命名参数 对象初始化器与集合初始化器

今天继续写上一篇文章C#4.0语法糖之第二篇,在开始今天的文章之前感谢各位园友的支持,通过昨天写的文章,今天有很多园友们也提出了文章中的一些不足,再次感谢这些关心我的园友,在以后些文章的过程中不断的完善以及自我提高,给各位园友们带来更好,更高效的文章. 废话就说到这里,下面正式进入我们的今天的C#4.0语法糖,今天给大家分享一下参数默认值.命名参数.对象初始化器和集合初始化器. 参数默认值和命名参数:方法的可选参数是.net 4.0最新提出的新的功能,对应简单的重载可以使用可选参数和命名参数混合

4.8static关键字,4.9嵌套类,4.10匿名累,4.11对象初始化器

4.8主要讲了static关键字,并且具体介绍了静态字段,静态属性,静态方法,静态类,静态构造方法,单例模式,具体是采用什么方式访问的. 4.9介绍了一个概念叫嵌套类,类定义在另一个类的内部,由于嵌套类被声明的位置比较特殊,致使其访问权限与引用方式与普通类有所区别. 4.10匿名类:使用匿名类的方式创建实例,可以将一组只读属性封装到单个对象中. 4.11对象初始化器:对象初始化器可以有效解决一个类中属性过多的问题. 原文地址:https://www.cnblogs.com/ma214zq/p/1

实例化类对象中alloc和inti的区别

在OC中,实例化一个类对象需要通过调用alloc和init两个系统既定方法进行初始化,比如: Fraction *frac=[[Fraction alloc]init]; 两者的区别如下: 1.alloc方法保证对象所对应的类里定义的所有实例变量都变成初始状态,但并没有使该对象本身进行初始化: 2.init方法用于初始化类要实例化的对象,它可以返回一个值,即被初始化的对象. ps:当然实例化一个对象还可以采用一种简便的方式,比如: Fractiion *frac=[Fraction new];

objective-C 对象初始化/属性

第十章 对象的初始化 两种创建对象的方法: 1. [类名 new]  2. [ [类名 alloc ] init ];  其中第二种方法是应该熟练使用的方法. 初始化对象: 对于继承了NSObject的类来说,调用超累的init方法可以让NSObject执行它所需的所有操作,以便于对象能够响应消息并处理保留计数器.而对于从其他类继承的类,通过这种方法可以实现自身的全新初始化. 与分配对应的操作时初始化,初始化从操作系统取得一块内存用于存储对象,init方法(执行初始化操作的方法)一般都会返回正在

Objective-C基础学习笔记——对象初始化

obj中创建新对象有两种方式:[classname new]和[[classname alloc] init].两种方法等价,Cocoa惯例是使用alloc和init. 1.分配对象: allocation是一个新对象诞生过程,从OS获得一块内存并指定为存放对象的实例变量的位置.同时alloc方法还将这块内存区域全部初始化为0.BOOL初始化为NO,int初始化为0,float初始化为0.0,指针初始化为nil. 然后init初始化之后才能使用,C++和Java中使用构造函数在单次操作中执行对象

iOS 8:关键字new与alloc、init

关键字:new  alloc  init 使用new关键字实例化的对象,在分配后自动调用init方法,返回一个可立即使用的对象,缺点是无法调用自定义init方法,因为重复初始化会导致不可预知问题. 使用alloc,虽然还要再调用一次init,但是,可以调用自定义init方法. 总结: 如果只是[[类 alloc] init],那么效果与[类 new]完全相同. 参考: http://blog.csdn.net/folish_audi/article/details/39890825

Java对象初始化详解

出处:http://blog.jobbole.com/23939/ 在Java中,一个对象在可以被使用之前必须要被正确地初始化,这一点是Java规范规定的.本文试图对Java如何执行对象的初始化做一个详细深 入地介绍(与对象初始化相同,类在被加载之后也是需要初始化的,本文在最后也会对类的初始化进行介绍,相对于对象初始化来说,类的初始化要相对简单一 些). 1.Java对象何时被初始化 Java对象在其被创建时初始化,在Java代码中,有两种行为可以引起对象的创建.其中比较直观的一种,也就是通常所

阿里巴巴面试题--Java对象初始化

转自 http://blog.csdn.net/ysjian_pingcx/article/details/19605335 Java对象初始化 这是一道阿里巴巴的关于Java对象初始化的面试题,堪称经典,代码很简单(编写格式做了些修改),但是需要面试者对Java中对象初始化有一个透彻的认识,那么通过这道面试题,对我有点启发,所以希望在这里分享给大家,希望能给迷惘的初学者一起指引,下面我们直入主题,先看看代码: 1 public class InitializeDemo { 2 private

Objective -C Object initialization 对象初始化

Objective -C Object initialization 对象初始化 1.1 Allocating Objects  分配对象 Allocation is the process by which a new object is born. allocation 是新对象诞生的过程. Sending the alloc message to a class causes that class to allocate a chunk of memory large enough to