iOS中类簇的使用

作者: 伯陽 | 来源:发表于2018-10-07 14:58 被阅读92次
    • 这篇文章由很多平时的笔记积攒而成,看起来会有些杂乱,会有很多需要改进的地方,希望发现问题的朋友不吝赐教。

    类簇

    类簇是Foundation框架广泛使用的设计模式。类簇在公共抽象超类下对多个私有的具体子类进行分组。以这种方式对类进行分组简化了面向对象框架的公共可见体系结构,而不会降低其功能丰富度。类簇是基于抽象工厂设计模式的。
    我将苹果官方文档中的有关类簇的部分翻译了过来,

    抽象工厂

    抽象工厂模式是指当有多个抽象角色时,使用的一种工厂模式。抽象工厂模式可以向客户端提供一个接口,使客户端在不必指定产品的具体的情况下,创建多个产品族中的产品对象。很多人会混淆抽象工厂模式和工厂模式。实际上,两种的差别还是比较明显的,如下表。

    抽象工厂模式 工厂模式
    通过对象组合创建抽象产品 通过类继承创建抽象产品
    创建多系列产品 创建一种产品
    必须修改父类的接口才能支持新的产品 子类化创建者并重载工厂方法以创建新产品

    有关抽象工厂的东西我们先讲到这里,剩下的我们有时间再聊。

    再往下就是针对具体情况的分析了,这段比较冗长,不喜欢的可以直接跳到最后一部分。

    注:在开始前,点击它来查看子类的名称,有些子类实在是过于稀少,就不单独拿出来说明了。

    NSArray

    《Effective Objective-C 2.0》中有一段话:

    In the case of NSArray, when an instance is allocated, it’s an instance of another class that’s allocated (during a call to alloc), known as a placeholder array. This placeholder array is then converted to an instance of another class, which is a concrete subclass of NSArray.
    在使用了NSArray的alloc方法来获取实例时,该方法首先会分类一个属于某类的实例,此实例充当“占位数组”。该数组稍后会转为另一个类的实例,而那个类则是NSArray的实体子类。

    因为在之前的文章中陈述过这一段,代码略过,直接上结论:
    不管创建的事可变还是不可变的数组,在alloc之后得到的类都是__NSPlaceholderArray。而当我们init一个不可变的空数组之后,得到的是__NSArray0;如果有且只有一个元素,那就是__NSSingleObjectArrayI;有多个元素的,叫做 __NSArrayIinit出来一个可变数组的话,都是 __NSArrayM
    这里__NSSingleObjectArrayI,需要说明它的用意:

    __NSSingleObjectArrayI

    作为对比,__NSArrayI必须要实现

      • count
      • objectAtIndex:
        这两个个方法,但是我们可以非常显而易见的看出来,当数组只有一个数字的时候,是完全不需要这两个方法的。
        再深入一点的说明一下,__NSSingleObjectArrayI是不需要去记录字符串长度的。它会比__NSArrayI少8个字节的长度。苹果可能是为了优化性能考虑,从而在iOS8之后推出这个新的子类。

    另外需要说明的是,实际上,__NSArrayM本身只有7个方法,分别是:

      • count
      • objectAtIndex:
      • insertObject:atIndex:
      • removeObjectAtIndex:
      • addObject:
      • removeLastObject
      • replaceObjectAtIndex:withObject:

    所有其它高等级的抽象建立在它们的基础之上。例如 - removeAllObjects 方法简单地往回迭代,一个个地调用 - removeObjectAtIndex:。

    NSDictionary

    NSDictionary与NSArray类似,不管创建的事可变还是不可变的字典,在alloc之后得到的类都是__NSPlaceholderDictionary。而当我们init一个不可变的空数组之后,得到的是__NSDictionary0;如果有且只有一个元素,那就是__NSSingleEntryDictionaryI;有多个元素的,叫做 __NSDictionaryIinit出来一个可变数组的话,都是 __NSDictionaryM
    不过呢,我这里还调试出了一种很有趣的子类,__NSFrozenDictionaryM
    过程是

    ...
    @property  (nonatomic, copy) NSMutableDictionary *mutableDictionary;
    ...
        NSMutableDictionary *dictionary = [[NSMutableDictionary alloc]init];
       [dictionary setObject:@"aaa" forKey:@"name"];
       self.mutableDictionary = dictionary;
    ...
    

    在违反属性规范的情况下,获得了这个有趣的子类。不过不用担心,这个子类没什么特殊的作用,它仍然会被视为不可变字典。也就是说,对它进行改变的操作,会导致程序崩溃。崩溃信息如下:

    2018-08-25 09:23:45.214879+0800 setget[90992:17397034] -[__NSFrozenDictionaryM setObject:forKey:]: unrecognized selector sent to instance 0x6000000dde40
    2018-08-25 09:23:45.238002+0800 setget[90992:17397034] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[__NSFrozenDictionaryM setObject:forKey:]: unrecognized selector sent to instance 0x6000000dde40'
    

    其实在在NSArray中也有个对应的__NSFrozenArrayM,不过我没有找到触发条件。不过应该与它类似。

    NSSet

    好吧,NSSet的子类不过是NSDictionary换了个名字而已,不做细讲了。
    这里说明一下,__NSSingleObjectSetI不需要打扰实际的哈希表,因为只有一个对象需要担心。类似的方法containsObject:不需要遍历任何东西或查找任何东西,它可以简单地将参数与set / array / dictionary表示的单个对象进行比较。

    NSString

    重头戏来了!!!
    //以下的结论全部来自64位系统
    当我们测试创建NSString对象的时候,通过创建NSString不同的对象,并利用object_getClassName方法打印对象。

    NSString *str1 = @"biboyanggggggg";
    //str1: __NSCFConstantString 
    NSString *str2 = [NSString stringWithString:@"biboyanggggggg"];
    //str2: __NSCFConstantString 
    NSString *str3 = @"biboyang";
    //str3: __NSCFConstantString 
    NSString *str4 = [NSString stringWithFormat:@"biboyang"];
    //str4: NSTaggedPointerString 
    NSString *str5 = [NSString stringWithFormat:@"sa"];
    //str5: NSTaggedPointerString 
    NSString *str6 = [NSString stringWithFormat:@"123456789"];
    //str6: NSTaggedPointerString 
    NSString *str7 = [NSString stringWithFormat:@"1234567890"];
    //str7: __NSCFString 
    
    

    我们可以发现,这里出现了三个子类

    • __NSCFConstantString
    • __NSCFString
    • NSTaggedPointerString

    __NSCFConstantString

    它是一个字符串常量。它的引用计数非常大,是4294967295,它的意思是,这个属性,怎么都不会被释放。相同的对象,内存地址是相同的,可以直接使用==方法(但是,这个对象的指针的地址依然不同,还是两个不同的对象)。它在编译时就决定的,不能在运行时创建。
    更详细的可以查看念纪的这篇博客

    __NSCFString

    这个就是可变的NSString所属的子类了。不必多说。

    NSTaggedPointerString

    要从从iPhone5s开始说起,iPhone5s开始采用了64位处理器。在32位时代,一个指针大小是32位(4字节),而在64位时代翻倍,一个指针的大小变成了64位(8字节)。这样子,在处理某些小一点,短一点的NSString、NSNumber、NSDate对象的时候,会显得过于浪费效率。这个时候,苹果推出了Tagged Pointer技术。
    苹果将一个对象的指针拆分成了两部分,一部分直接保存数据,另一部分作为特殊标记(tag),表示这个是一个特别的指针。这样呢,就会将节省很多的时间,因为它不在需要正常创建对象的申请和创建空间,处理引用计数,以及直接读取(在objc_msgSend当中,Tagged Pointer会被识别出来,直接从指针中读取)。

    • 苹果之前说过,使用Tagged Pointer技术之后,在内存上读取的速度快了3倍,创建时的速度比以前快乐106倍。

    当然,这么做其实也是会有问题的,因为它并不是一个真正的对象,当你想要想其他普通的对象一样获取指针的时候,编译器直接就会报错(因为它也是在编译时创建的,而且压根没有isa指针)。
    编译器会告诉你正确的方法:改为使用object_getClass()

    总结

    类簇的优点在于:

    • 1.可以将抽象基类背后的复杂细节隐藏起来
    • 2.程序员不会需要记住各种创建对象的具体类实现,简化了开发成本,提高了开发效率
    • 3.便于进行封装和组件化
    • 4.减少了if else 这样缺乏扩展性的代码
    • 5.增加新功能支持不影响其他代码

    缺点也很明显:

    • 已有的类簇非常不好扩展!!!

    我们了解类簇的好处:

    • 出现bug时,可以通过崩溃报告中的类簇关键字,快速定位bug位置。
    • 在实现一些固定切并不需要经常修改的事物时,可以高效的选择类簇去实现。
      举个例子:针对不同版本,不同机型往往需要不同的设置,这时可以选择使用类簇;
    • app的设置页面这种并不需要经常修改的页面,可以使用类簇去创建大量重复的布局代码。

    资料来源

    Friday Q&A 2015-07-31: Tagged Pointer Strings

    相关文章

      网友评论

        本文标题:iOS中类簇的使用

        本文链接:https://www.haomeiwen.com/subject/ktulaftx.html