zoukankan      html  css  js  c++  java
  • 对象初始化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的内存布局:

    03.jpg

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

    04.jpg

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

    那么问题来了,这个中间对象并没有储存任何信息诶(除了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

  • 相关阅读:
    《metasploit渗透测试魔鬼训练营》靶机演练之第五章实战案例KingView 6.53版本CVE-2011-0406漏洞
    《metasploit渗透测试魔鬼训练营》学习笔记第五章--网络服务渗透攻击
    《metasploit渗透测试魔鬼训练营》靶机演练之第五章实战案例Oracle数据库
    《metasploit渗透测试魔鬼训练营》学习笔记第四章—web应用渗透
    《metasploit渗透测试魔鬼训练营》学习笔记第三章----情报搜集
    Kali-linux Arpspoof工具
    Kali-linux攻击路由器
    Kali-linux在树莓派上破解无线网络
    Kali-linux使用Easy-Creds工具攻击无线网络
    20155225 2016-2017-2 《Java程序设计》第五周学习总结
  • 原文地址:https://www.cnblogs.com/HypeCheng/p/4487074.html
Copyright © 2011-2022 走看看