OC Foundation框架 集合

Posted Billy Miracle

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了OC Foundation框架 集合相关的知识,希望对你有一定的参考价值。

NSSet集合里多个对象之间没有明显顺序,不允许包含相同的元素,如果试图把两个相同的元素放在同一个NSSet集合中,则只会保留一个元素。

NSSet的功能与用法

NSSet按Hash算法来储存元素,因此具有良好的存取和查找功能。NSSet不能保证元素的添加顺序,顺序可能发生变化。与NSArray相比,NSSet最大的区别是元素没有索引,因此不能根据索引来操作元素。
但是,NSSet与NSArray依然有大量的相似之处:

  • 都可通过count获取集合元素个数。
  • 都可通过快速枚举来遍历集合元素。
  • 都可以通过objectEnumerator方法获取NSEnumerator枚举器对集合元素进行遍历,由于NSSet无序,因此,提供反向迭代器没有意义。
  • 都提供了makeObjectsPerformSelector: 、makeObjectsPerformSelector:withObject:方法对集合元素整体调用某个方法,以及enumerateObjectsUsingBlock、enumerateObjectsWithOptions:usingBlock:对集合整体或部分元素迭代执行代码块。
  • 都提供了valueForKey:和setValue:forKey:方法对集合元素整体进行KVC编程。
  • 都提供了集合的所有元素和部分元素进行KVO编程的方法。

除了与NSArray相似的方法,NSSet包含了如下常用的方法:

  • setByAddingObject::向集合添加一个新元素,返回添加元素后的新集合。
  • setByAddingObjectsFromSet::使用NSSet添加多个元素,返回添加元素后的新集合。
  • setByAddingObjectsFromArray::使用NSArray添加多个元素,返回添加元素后的新集合。
  • allObjects::返回该集合中所有元素组成的NSArray
  • anyObject::返回某个元素,元素是不确定的,但该方法并不保证随机返回(只要一个NSSet没有发生改变,无论调用多少次,返回的总是同一个元素)。
  • containsObject::判断集合是否包含指定元素
  • member::判断该集合是否包含与该参数相等的元素,若包含,则返回相等的元素,否则返回nil。
  • objectsPassingTest::需要传入一个代码块对集合元素进行过滤,满足代码块条件的集合元素被保留下来并组成一个新的NSSet集合作为返回值。
  • objectsWithOptions:passingTest::与前一个方法的功能基本相似,只是可以额外地传入一个NSEnumerationOptions迭代选项参数。
  • isSubsetOfSet::判断当前NSSet集合是否为另一个集合的子集合。调用该方法需要传入另一集合。
  • intersectsSet::判断两个集合的元素是否有相同元素。也就是计算两个集合是否有交集。
  • isEqualToSet::判断两个集合的元素是否相等。

示例程序:

#import <Foundation/Foundation.h>
NSString* NSCollectionToString (id collection) {
    NSMutableString* result = [NSMutableString stringWithString:@"["];
    for (id obj in collection) {
        [result appendString: [obj description]];
        [result appendString:@", "];
    }
    NSInteger len = [result length];
    //去掉字符串最后两个字符
    [result deleteCharactersInRange: NSMakeRange(len - 2, 2)];
    [result appendString:@"]"];
    return result;
}
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        //用四个元素初始化NSSet集合
        //故意传入两个相等的元素,NSSet集合只会保留一个元素
        NSSet* set1 = [NSSet setWithObjects:@"Billy",
                       @"Amy",
                       @"Billy",
                       @"Peter and Jim",nil];
        //输出元素个数为3
        NSLog(@"set1集合中元素个数为:%ld", [set1 count]);
        NSLog(@"set1集合:%@", NSCollectionToString(set1));
        NSSet* set2 = [NSSet setWithObjects:@"Billy",
                       @"Alice",
                       @"John",nil];
        NSLog(@"set2集合:%@", NSCollectionToString(set2));
        //向set1集合添加单个元素,将添加元素后生成的新集合赋给set1
        set1 = [set1 setByAddingObject:@"Kitt"];
        NSLog(@"添加一个元素后:%@", NSCollectionToString(set1));
        //使用NSSet集合向set1集合中添加多个元素,相当于两个集合的并集
        NSSet* s = [set1 setByAddingObjectsFromSet: set2];
        NSLog(@"set1与set2并集:%@",NSCollectionToString(s));
        BOOL b =[set1 intersectsSet:set2];//计算是否有交集
        NSLog(@"set1与set2是否有交集:%d" , b);//输出1
        BOOL bo = [set2 isSubsetOfSet: set1];//判断set2是否是set1的子集
        NSLog(@"set2是否为set1的子集:%d", bo);//输出0
        //下面两行将取出相同的元素,但取出哪个不确定
        BOOL bb = [set1 containsObject:@"Amy"];
        NSLog(@"set1是否包含\\"Amy\\":%d", bb);//
        //使用代码块对集合元素进行过滤
        NSLog(@"set1取出一个元素:%@", [set1 anyObject]);
        NSLog(@"set1取出一个元素:%@", [set1 anyObject]);
        //
        NSSet* filteredSet = [set1 objectsPassingTest:
            ^(id obj, BOOL* stop){
                return (BOOL)([obj length] > 8);
            }];
        NSLog(@"set1中元素长度大于8的集合元素有:%@", NSCollectionToString(filteredSet));
    }
    return 0;
}

输出:

set1集合中元素个数为:3
set1集合:[Billy, Peter and Jim, Amy]
set2集合:[Billy, Alice, John]
添加一个元素后:[Billy, Peter and Jim, Amy, Kitt]
set1与set2并集:[John, Amy, Billy, Peter and Jim, Alice, Kitt]
set1与set2是否有交集:1
set2是否为set1的子集:0
set1是否包含"Amy"1
set1取出一个元素:Billy
set1取出一个元素:Billy
set1中元素长度大于8的集合元素有:[Peter and Jim]

NSSet判断集合元素重复的标准

NSSet集合中存入一个元素,NSSet会调用该对象的hash方法来得到该对象的hashCode值,然后根据该hashCode值决定该对象在底层Hash表中的存储位置,如果位置不同,那么系统直接将它们保存在不同的位置。
如果两个元素的hashCode值相同,接下来通过isEqual:方法判断两个元素是否相等,若isEqual:方法返回NO,那么NSSet依然认为它们不相等,会把他们放在底层Hash表同一位置只是将在这个位置形成链;如果它们通过isEqual: 方法比较返回YES,那么认为两个元素相等,后面的元素添加失败。
简单地说,判断标准如下:

//  User.h

#import <Foundation/Foundation.h>

@interface User : NSObject

@property (nonatomic, copy) NSString* name;
@property (nonatomic, copy) NSString* pass;
-(id) initWithName: (NSString*) aName pass: (NSString*) aPass;
-(void) say: (NSString*) content;

@end
//  User.m
//  疯狂ios NSSet判断集合元素重复的标准
//
//  Created by 张博添 on 2021/6/2.
//

#import "User.h"

@implementation User

-(id) initWithName:(NSString *) name pass:(NSString *) pass {
    if (self = [super init]) {
        self->_name = name;
        self->_pass = pass;
    }
    return self;
}
-(void) say: (NSString*) content {
    NSLog(@"%@说:%@",self.name, content);
}
//重写isEqual: 方法,重写比较方法为
//如果两个User的name、pass相等,既可以认为它们相等
-(BOOL) isEqual: (id) other {
    if(self == other) {
        return YES;
    }
    if([other class] == User.class) {
        User* target = (User*) other;
        return [self.name isEqualToString: target.name] && [self.pass isEqualToString: target.pass];
    }
    return NO;
}
//重写description方法,可以看到User状态
-(NSString*) description {
    return [NSString stringWithFormat:@"<User[name=%@,pass=%@]>", self.name, self.pass];
}

@end
#import <Foundation/Foundation.h>
#import "User.h"

NSString* NSCollectionToString (id collection) {
    NSMutableString* result = [NSMutableString stringWithString:@"["];
    for (id obj in collection) {
        [result appendString: [obj description]];
        [result appendString:@", "];
    }
    NSInteger len = [result length];
    //去掉字符串最后两个字符
    [result deleteCharactersInRange: NSMakeRange(len - 2, 2)];
    [result appendString:@"]"];
    return result;
}
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        NSSet* set = [NSSet setWithObjects:
            [[User alloc] initWithName:@"Billy" pass:@"1234"],
            [[User alloc] initWithName:@"Amy" pass:@"2345"],
            [[User alloc] initWithName:@"Billy" pass:@"1234"],
            [[User alloc] initWithName:@"John" pass:@"4567"],
            [[User alloc] initWithName:@"Lary" pass:@"5678"],
            nil];
        NSLog(@"set集合元素个数:%ld", [set count]);
        NSLog(@"%@", NSCollectionToString(set));
    }
    return 0;
}

留意到上面集合中的第1、3个User对象的name、pass相等,因此,他们的isEqual:方法会返回YES,但并未重写hash方法,因此它们的hashCode值不相等,NSSet依然认为它们不相等,会同时存储这两个元素,输出如下:

set集合元素个数:5
[<User[name=Billy,pass=1234]>, <User[name=Lary,pass=5678]>, <User[name=Billy,pass=1234]>, <User[name=Amy,pass=2345]>, <User[name=John,pass=4567]>]

为User类重写hash方法,重写hash方法时根据name、pass两个成员变量的值进行计算。重写代码如下:

//重写hash方法,重写的标准是
//如果两个User的name、pass相等,那么hash方法返回值相等
-(NSUInteger) hash {
    NSLog(@"===hash===");
    NSUInteger nameHash = self.name == nil ? 0 : [self.name hash];
    NSUInteger passHash = self.pass == nil ? 0 : [self.pass hash];
    return nameHash *31 + passHash;
}

只要两个User的name、pass的hash返回值相等,那么这两个对象的返回值就相等即只要两个字符串包含的字符序列相同,那么hash方法返回值就相等。
再次运行,可以看到:

===hash===
===hash===
===hash===
===hash===
===hash===
set集合元素个数:4
[<User[name=John,pass=4567]>, <User[name=Lary,pass=5678]>, <User[name=Billy,pass=1234]>, <User[name=Amy,pass=2345]>]

注意⚠️:如果需要把一个对象放入NSSet中,若重写该对象对应类的isEqual:方法,也应该重写其hash方法。其规则是:如果两个对象通过isEqual:方法比较返回YES,那么这两个对象的hash方法的返回值也应该相同。
如果两个对象通过isEqual:方法比较返回YES,但这两个对象的hash方法返回不同的hashCode值时,则将导致这两个对象保存在Hash表不同位置,从而使两个对象都添加成功,这就与NSSet集合规则矛盾。
如果两个对象的hash方法返回的hashCode值相同,但它们通过isEqual:方法比较返回NO时更麻烦,底层会在这个位置用链式结构保存对象,而NSSet访问集合元素时也是根据hashCode值快速定位的,如果定位到指定位置发现该位置形成了链,那么NSSet就需要逐一检索链上的每一个对象,这样就大大降低了NSSet的访问性能。
hash方法十分重要,下面给出重写hash方法的基本规则:

  • 程序运行时,同一个对象多次调用hash方法应该返回相同的值
  • 当两个对象通过isEqual:方法比较返回YES时,这两个对象的hash方法应返回相等的值
  • 对象中作为isEqual:方法比较标准的成员变量,都应该用来计算hashCode值。

下面给出重写hash方法的一般步骤

  1. 把对象内每个有意义的成员变量计算出一个int类型的hashCode值。
  2. 用第一步计算出来的多个hashCode值组合计算出一个hashCode值返回,例如:

return [f1 hash] + [f2 hash];
为了避免直接相加产生偶然相等,可以通过为各实例变量的hashCode值乘以任意一个质数后再相加,例如:
return [f1 hash] * 31 + [f2 hash];

NSMutableSet的功能与用法

NSMutableSet继承了NSSet,它代表一个集合元素可变的NSSet集合。由于它可以动态地添加集合元素,因此,创建NSMutableSet集合时可以指定底层Hash表的初始容量。
NSMutableSet主要在NSSet基础上增加了添加、删除元素的方法,并增加了对集合计算交集、并集、差集的方法。

  • addObject::向集合中添加单个元素。
  • removeObject::从集合中删除单个元素。
  • removeAllObjects:删除所有元素。
  • addObjectsFromArray::使用NSArray数组作为参数,向NSSet集合中添加参数数组中的所有元素。
  • unionSet::计算并集。
  • minusSet::计算差集。
  • intersectSet::计算交集。
  • setSet::用后一个集合元素替换已有集合中的所有元素。

示例程序:

#import <Foundation/Foundation.h>
NSString* NSCollectionToString (id collection) {
    NSMutableString* result = [NSMutableString stringWithString:@"["];
    for (id obj in collection) {
        [result appendString: [obj description]];
        [result appendString:@", "];
    }
    NSInteger len = [result length];
    //去掉字符串最后两个字符
    [result deleteCharactersInRange: NSMakeRange(len - 2, 2)];
    [result appendString:@"]"];
    return result;
}
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        //创建一个初始容量为10的set集合
        NSMutableSet* set = [NSMutableSet setWithCapacity: 10];
        [set addObject:@"Billy"];
        NSLog(@"添加一个元素后:%@", NSCollectionToString(set));
        [set addObjectsFromArray:[NSArray arrayWithObjects:@"Amy",@"Bob",@"Teresa",@"Peter", nil]];
        NSLog(@"使用NSArray添加四个元素后:%@", NSCollectionToString(set));
        [set removeObject:@"Bob"];
        NSLog(@"删除一个元素后:%@", NSCollectionToString(set));
        //再创建一个集合
        NSSet* set2 = [NSSet setWithObjects:@"Polly",@"Billy", nil];
        [set unionSet: set2];
        NSLog(@"并集:%@",NSCollectionToString(set));
        //[set minusSet: set2];
        //NSLog(@"差集:%@",NSCollectionToString(set));
        //[set intersectSet: set2];
        //NSLog(@"交集:%@",NSCollectionToString(set));
        //[set setSet: set2];
        //NSLog(@"用set2集合元素替换set集合元素:%@",NSCollectionToString(set));
    }
    return 0;
}

大家可以通过运行注释掉的程序自行尝试一下输出结果。

NSCountedSet的功能与用法

NSCountedSet是NSMutable的子类,它为每个元素额外维护一个添加次数的状态,添加元素时,若不包含该元素,则添加该元素,并将添加次数变为1,若已经包含,则不重复添加,将添加次数加1。
程序删除元素时,NSCountedSet只是将添加次数减1,只有当添加次数变0时,该元素才会真正被删除。

  • countFoeObject::获取指定元素的添加次数。

示例程序:

#import <Foundation/Foundation.h>
NSString* NSCollectionToString (id collection) {
    NSMutableString* result = [NSMutableString stringWithString:@"["];
    for (id obj in collection) {
        [result appendString: [obj description]];
        [result appendString:@", "];
    }
    NSInteger len = [result length];
    //去掉字符串最后两个字符
    [result deleteCharactersInRange: NSMakeRange(len - 2, 2)];
    [result appendString:@"]"];
    return result;
}
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        NSCountedSet* set = [NSCountedSet setWithObjects:@"Billy",@"Amy",@"Billy",@"Teresa", nil];
        //输出集合元素
        NSLog(@"%@", NSCollectionToString(set));
        //获取指定元素的添加次数
        NSLog(@"\\"Billy\\"的添加次数为:%ld", [set countForObject:@"Billy"]);
        //删除元素
        [set removeObject:@"Billy"];
        NSLog(@"删除\\"Billy\\"1次后结果:%@",NSCollectionToString(set));
        NSLog(@"删除\\"Billy\\"1次后添加次数:%ld", [set countForObject:@"Billy"]);
        [set removeObject:@"Billy"];
        NSLog(@"删除\\"Billy\\"2次后结果:%@",NSCollectionToString(set));
    }
    return 0;
}

结果是:

[Billy, Amy, Teresa]
"Billy"的添加次数为:2
删除"Billy"1次后结果:[Billy, Amy, Teresa]
删除"Billy"1次后添加次数:1
删除"Billy"2次后结果:[Amy, Teresa]

以上是关于OC Foundation框架 集合的主要内容,如果未能解决你的问题,请参考以下文章

34-oc Foundation简介

OC Foundation框架 集合

OC 知识:Foundation 框架及相关类详尽总结

OC Foundation框架 数组

OC_框架学习第一天

OC Foundation框架 字典