【Objective-C】Foundatio框架详解

Foundation框架是Cocoa编程、iOS编程的基础框架,ta包括字符串、集合、日前、时间等基础。

字符串(NSString与NSMutableString)

字符串就是一连串的字符序列,OC中有两个字符串:NSString代表字符序列不可变的字符串,而NSMutableString则代表字符序列可变的字符串。

创建字符串

参考NSString文档,NSString大致包括如下功能:

  • 创建字符串:创建字符串即可使用以init开头的实例方法,也可以使用string开头的类方法,当然也可以直接用**@""的形式**给出字符串直接量。
  • 读取文件或网络URL来初始化字符串。
  • 将字符串内容写入文件或URL
  • 获取字符串长度,既可获取字符串内包括的字符个数,也可获取字符串包括的字节个数
  • 获取字符串中的字符或字节,既可获取指定位置的字符,也可获取指定范围的字符。
  • 获取字符串对应的C风格字符串。
  • 连接字符串。
  • 分隔字符串。
  • 查找字符串内指定的字符和子串。
  • 替换字符串。
  • 比较字符串。
  • 字符串大小比较。
  • 对字符串中的字符进行大小写转换。

NSString文档中还介绍了大量功能和方法,下面将介绍NSString最常用的功能。

下面示范了创建NSString对象的几种方式:

#import <Foundation/Foundation.h>

int main(int argc, char* argv[]) {
  @autoreleasepool {
    unichar data[6] = {97, 98, 99, 100, 101, 102};
    NSString* str = [[NSString alloc] initWithCharacters: data length: 6];
    NSLog(@"%@", str);  //使用Unicode数值数组初始化字符串
    
    char* cstr = "Hello, iOS!";
    NSString* str2 = [NSString stringWithUTF8String: cstr];
    NSLog(@"%@", str);  //将C风格的字符串转换为NSString对象
    
    [str2 writeToFile: @"myFile.txt" atomically: YES encoding: NSUTF8StringEncoding error: nil];  //将字符串内容写入底层文件
    
    NSString* str3 = [NSString stringWithContentsOfFile: @"NSStringTest.m" encoding: NSUTF8StringEncoding error: nil];
    NSLog(@"%@", str3);  //读取文件内容,用文件内容初始化字符串
  }
}

上面程序中还定义了一个unichar数组,该数组也是一个基本类型,就是unsigned short的别名,OC使用typedef unsigned short unichar起的别名;由于支持typeof,OC可以随心所欲地新增类型。

NSString的常用功能

#import <Foundation/Foundation.h>

int main(int argc, char* argv[]) {
  @autoreleasepool {
    NSString* str = @"Hello";
    NSString* system = @"iOS";
    
    str = [str stringByAppendingString: @", iOS!"];  //在str后面追加固定的字符串
    NSLog(@"%@", str);  //原来的字符串对象并不改变,只是将新生成的字符串重新赋给str指针变量
    
    const char* cstr = [str UTF8String];  //获取字符串对应的C风格字符串
    NSLog(@"获取C字符串:%s", cstr);
    
    str = [str stringByAppendingFormat: @"%@是一个非常不错的系统.", system];  //在str后面追加带变量的字符串
    NSLog(@"%@", str);
    
    NSLog(@"str的字符个数为:%lu", [str length]);
    NSLog(@"str按UTF-8字符集解码后字节数为:%lu", [str lengthOfBytesUsingEncoding: UTF8StringEncoding]);
    
    NSString* s1 = [str substringToIndex: 10];
    NSLog(@"%@", s1);  //获取str的前10个字符组成的字符串
    NSString* s2 = [str substringFromIndex: 5];
    NSLog(@"%@", s2);  //获取str从第5个字符开始,与后面所有字符组成的字符串
    NSString* s3 = [str substringWithRange: NSMakeRange(5, 15)];
    NSLog(@"%@", s3);  //获取str从第5字符开始,与后面15个字符所组成的字符串
    
    NSRange pos = [str rangeOfString: @"iOS"];
    NSLog(@"iOS在str中开始出现的位置:%ld,长度为:%ld", pos.location, pos.length);  //获取iOS在str中出现的位置
    
    str = [str uppercaseString];  //将str所有字符转换为大写
    NSLog(@"%@", str);
  }
}

上段代码中使用了一个NSRange类型变量,NSrange并不是一个类,ta只是一个结构体,ta包括了locationlength两个unsigned int整型值,分别代表起始位置和长度。OC还提供了NSMakeRange()函数来创建NSRange变量。

实际上,后面还会见到OC提供的大量NSXxx结构体类型,而且OC也会提供对应的NSXxxMake()函数来创建对应的结构体变量。

可变字符串(NSMutableString)

NSString类是不可变的类,即一旦NSString对象被创建,包含在这个对象中的字符序列就是不可改变的,直至这个对象被销毁。

NSMutableString对象则代表一个字符序列可变的字符串,而且NSMutableString是NSString的子类,因此NSMutableString对象也可直接当成NSString对象使用。

当一个NSMutableString对象被创建以后,通过NSMutableString提供的appendFormat:appendStringdeleteCharactersInRange:insertString: atIndex:replaceCharactersInRange: withStringreplaceOccurrencesOfString: option: range:setString:方法即可改变该字符串所包含的字符序列。

下面使用NSMutableString来改变字符串:

#import <Foundation/Foundation.h>

int main(int argc, char* argv[]) {
  @autoreleasepool {
    NSString* system = @"iOS";
    NSMutableString* str = [NSMutableString stringWithString: @"Hello"];  //创建一个NSMutableString对象
    
    [str appendString: @", iOS!"];  //追加字符串
    [str appendFormat: @"%@是非常不错的操作系统.", system];
    NSLog(@"%@", str);  //追加带变量的字符串
    
    [str insertString: @"thgit.org" atIndex: 6];
    NSLog(@"%@", str);  //在指定位置插入字符串
    
    [str deleteCharactersInRange: NSMakeRange(6, 12)];
    NSLog(@"%@", str);  //删除从位置6开始、长度为12的所有字符串
    
    [str replaceCharactersInRange: NSMakeRange(6, 9) withString: @"Objective-C"];  //将从位置6开始、长度为9的所有字符替换成Objective-C
    NSLog(@"%@", str);
  }
}

日期与时间

OC为处理日期、时间提供了NSDateNSCalendar对象,还提供了日期格式器来处理日期与字符串之间的转换

日期与时间(NSDate)

NSDate对象代表日期与时间,OC既提供了类方法来创建NSDate对象,也提供了大量以init开头的方法来初始化NSDate对象。

下面介绍NSDate类的常见方法

#import <Foundation/Foundation.h>

int main(int argc, char* argv[]) {
  @autoreleasepool {
    NSDate* date1 = [NSDate date];
    NSLog(@"%@", date1);  //获取代表当前日期、时间的NSDate
    NSDate* date2 = [[NSDate alloc] initWithTimeIntervalSinceNow: 3600 * 24];
    NSLog(@"%@", date2);  //获取从当前时间开始,一天之后的日期
    
    NSDate* date3 = [[NSDate alloc] initWithTimeIntervalSinceNow: -3 * 3600 * 24];
    NSLog(@"%@", date3);  //获取从当前时间开始,3天之前的日期
    
    NSDate* date4 = [NSDate dateWithTimeIntervalSince1970: 3600 * 24 * 366 * 20];
    NSLog(@"%@", date4);  //获取从1970年1月1日开始,20年之后的日期
    
    NSLocale* cn = [NSLocale currentLocale];  //获取系统当前的NSLocale
    NSLog(@"%@", [date1 descriptionWithLocale: cn]);  //获取NSDate在当前NSLocale下对应的字符串
    
    //获取两个日期之间较早、较晚的时间
    NSDate* earlier = [date1 earlierDate: date2];
    NSDate* later = [date1 laterDate: date2];
    
    //比较两个日期,compare:方法返回NSComparisonResult枚举值
    //该枚举值包括NSOrderedAscending、NSOrderedSame和NSOrderedDescending三个值
    switch ([date1 compare: date3]) {
      case NSOrderedAscending:
        NSLog(@"date1位于date3之前");
        break;
      case NSOrderedSame:
        NSLog(@"date1与date3日期相等");
        break;
      case NSOrderedDescending:
        NSLog(@"date1位于date3之后");
        break;
    }
    
    NSLog(@"date1与date3之时间差%g秒", [date1 timeIntervalSinceData: date3]);  //获取两个时间之间的时间差
    NSLog(@"date2与现在时间差%g秒", [date2 timeIntertvalSinceNow]);  //获取指定时间与现在时间差
  }
}

日期格式器(NSDateFormatter)

对象复制

NSObject提供了copymutableCopy方法,通过这两个方法即可复制已有对象的副本

copy与mutableCopy方法

copy方法用于复制对象的副本。通常来说,copy方法总是返回对象的不可修改的副本,即使该对象本身是可修改的

mutableCopy方法用于复制对象的可变副本。通常来说,mutable方法总是返回该对象可修改的副本,即使被复制的对象本身是不可修改的,调用mutableCopy方法复制出来的副本也是可修改的。

无论如何,copy和mutableCopy返回的总是原对象的副本,当程序对复制的副本进行修改时,怨对象通常不会受到影响。

示例如下:

#import <Foundation/Foundation.h>

int main(int argc, char* argv[]) {
  @autoreleasepool {
    NSMutableString* book = [NSMutableString stringWithString: @"iOS"];
    
    NSMutableString* bookCopy = [book mutableCopy];  //复制book字符串的可变副本
    [bookCopy replaceCharactersInRange: NSMakeRange(2, 3) withString: @"Android"];
    NSLog(@"book的值为:%@", book);  //原字符串的值并没有改变
    NSLog(@"bookCopy的值为:%@", bookCopy);  //字符串副本发生了改变
    
    NSString* str = @"thgit";
    NSMutableString* strCopy = [str mutableCopy];  //复制str(不可变字符串)的可变副本
    [strCopy appendString: @".org"];
    NSLog(@"%@", strCopy);
    
    NSMutableString* bookCopy2 = [book copy];  //复制book字符串的不可变副本
    [bookCopy2 appendString: @"aa"];  //bookCopy2时不可修改的,因此此行的代码将会出现错误
  }
}

当程序复制对象的副本后,对副本所做的任何修改,对原始对象本身并没有任何影响。

NSCopying与NSMutableCopying协议

虽然NSObject提供了copy和mutableCopy方法,但自定义类并不能直接调用这两个方法来复制自身。

为保证一个对象可调用copy方法来复制自身的不可变副本,通常需要做如下事情:

  • 让该类实现NSCopying协议。
  • 让该类实现copyWithZone:方法。

与此同时,为了保证一个对象可以调用mutableCopy方法来复制自身的可变副本,通常需要做如下事情:

  • 让该类实现NSMutableCopying协议。
  • 让该类实现mutableCopyWithZone:方法。

当程序需要调用对象的copy(mutableCopy)方法来复制自身时,程序底层需要调用copyWithZone:mutableCopyWithZone:)方法来完成实际的复制工作,copy(mutableCopy)返回的实际上就是copyWithZone:mutableCopyWithZone:)方法的返回值

程序可先在THGDog类的接口部分声明实现NSCopying协议;然后在THGDog类的实现部分增加copyWithZone:方法:

- (id)copyWithZone: (NSZone*)zone {
  NSLog(@"--执行copywithZone--");
  THGDog* dog = [[[self class] allocWithZone: zone] init];
  dog.name = self.name;
  dog.age = self.age;
  return dog;
}

上面实现了copyWithZone:方法中重新创建了一个THGDog对象,并让该对象的所有属性值与被复制对象的属性值相等,最后返回这个新创建的对象,也就是返回该对象的副本。

copyWithZone: (NSZone*) zone方法中的zone参数与不同的存储区有关,通常无需过多地关心该参数,只要将zone参数传给copyWithZone:方法,即可创建该对象的副本。

#import <Foundation/Foundation.h>
#import "THGDog.h"

int main(int argc, char* argv[]) {
  @autoreleasepool {
    THGDog* dog1 = [THGDog new];
    dog1.name = [NSMutableString stringWithString: @"旺财"];
    dog1.age = 20;
    
    THGDog* dog2 = [dog1 copy];  //复制副本
    dog2.name = [NSMutableString stringWithString: @"snoopy"];
    dog2.age = 12;
    
    NSLog(@"dog1的名字为:%@", dog1.name);
    NSLog(@"dog1的年龄为:%@", dog1.age);
    NSLog(@"dog2的名字为:%@", dog2.name);
    NSLog(@"dog2的年龄为:%@", dog2.age);
  }
}

前面介绍copy方法时提到,copy方法应该复制对象的不可变副本,那此处调用THGDog对象的copy方法复制后怎么依然是一个可变的THGDog对象呢?

此处的THGDog类没有提供对应的不可变类,自然也就无法复制不可变的THGDog对象。如果程序为THGDog提供了不可变类。当然还是应该让THGDog的copyWithZone:返回不可变THGDog对象。

需要指出的是,如果重写copyWithZone:方法时,其父类已经实现了NSCopying协议,并重写过copyWithZone:方法那么子类重写copyWithZone:方法应先调用父类的copy方法复制从父类继承的到的成员变量,然后对子类中定义的成员变量进行赋值

假如已经重写了copyWithZone:方法,那么子类重写这个方法的格式如下;

- (id)copywithZone: (NSZone*)zone {
  id obj = [super copy];
  //对子类定义的成员变量赋值
  ...
  return obj;
}

浅复制与深复制

浅复制(shallow copy)与浅复制(deep copy):

#import <Foundaiton/Foundation.h>
#import "THGDog.h"

int main(int argc, char* argv[]) {
  @autoreleasepool {
    THGDog* dog1 = [THGDog new];
    dog1.name = [NSMutableString stringWithString: @"旺财"];
    dog1.age = 20;
    
    THGDog* dog2 = [dog1 copy];  //复制副本
    [dog2.name replaceCharactersInRange: NSMakerange(0, 2) eithString: @"snoopy"];  //修改dog2的name属性值
    
    NSLog(@"dog2的name为:%@", dog2.name);
    NSLog(@"dog1的name为:%@", dog1.name);
  }
}

从上面的输出可以看到,虽然只是修改了dog2的那么属性值,但是为何dog1的name也发生了改变

接下来将通过示意图进行说明。程序创建了第一个THGDog对象,并使用dog1指针指向对象后的内存存储示意图如下图所示:

在这里插入图片描述

接下来程序复制了一个THGDog对象,查看copyWithZone:方法的代码,看到如下两行:

dog.name = self.name;
dog.age = self.age;

其中,dog代表复制出来的对象,此时程序将被复制对象的name复制给dog的name。注意,name只是一个指针变量,该变量中存放的只是字符串的地址,并不是字符串本身。这样赋值的效果是让dog对象的name属性与被复制对象的name属性指向同一个字符串,此时的效果如下图所示:

在这里插入图片描述

从图中可以看出,此时dog1、dog2两个指针分别指向两个不同的THGDog对象,但这两个THGDog对象的name属性都是指针,而且ta们都指向同一个NSMutableString对象。这样当程序修改任何一个THGDog的name属性值时,另一个THGDog对象的name属性值也会随着改变。

对于上图所示的这种复制方式:当对象的属性是指针变量时,如果程序只是复制该指针的地址,而不是真正复制指针所指向的对象,这种方式就被称为“浅复制”。对浅复制而言,在内存中复制了两个对象,这两个对象的指针变量将会指向同一个对象,也就是两个对象依然存在共用的部分

深复制则会采用与此不同的方式,深复制不仅会复制对象本身,而且会“递归”复制每个指针类型的属性。直到两个对象没有任何共用的部分

将上面THGDog的copyWithZone:改为如下形式。即可实现深复制:

- (id)copyWithZone: (NSZone*)zone {
  NSLog(@"--执行copyWithZone:--");
  THGDog* dog = [[[self class] allocWithZone: zone] init];
  dog.name = [self.name mutableCopy];
  dog.age = self.age;
  return dog;
}

这里并不是简单地将被复制对象的name属性值赋给新对象的name属性,而是先将原对象的name属性值复制了一份可变副本,再将该可变副本的值赋给新对象的name属性。这样就保证了原THGDog对象与新的THGDog对象之间没有任何共用部分,这就实现了深复制。

实现深复制,运行、编译后,将看到两个THGDog对象之间没有任何关联。

一般来说,深复制的实现难度大很多,尤其是当该对象包含大量指针类型的属性时,如果某些属性所引用的对象再次包含指针类型的属性,那么实现深复制会更加复杂。Foundation框架中的类大部分都实现了浅复制。

setter方法的复制选项

合成setter和getter方法时可以使用copy指示符,copy指示符就是指定当程序调用setter方法复制时,实际上是将传入参数的副本赋给程序的实例变量。

下面定义一个THGItem类。接口部分如下:

#import <Foundaiton/Foundation.h>

@interface THGItem : NSObject
@property (nonatomic, copy)NSMutableString* name;
@end

实现部分:

#import <Foundaiton/Foundation.h>
#import "THGItem.h"

int main(int argc, char* argv[]) {
  @autoreleasepool {
    THGItem* item = [THGItem new];
    item.name = [NSMutableString stringWithString: @"iOS"];
    [item.name appendingString: @"thgit"];
  }
}

这时程序会报错,提示不允许修改item的name属性值,这时因为程序定义name属性时使用了copy指示符,该指示符指定调用setName:方法(通过点语法赋值时,实际上是调用对应的setter方法),程序实际上还会使用参数的副本对name实例变量赋值。也就是说,setName:方法的代码如下:

- (void)setName: (NSMutableString*)aname {
  name = [aname copy];
}

程序赋给THGItem对象的name实例变量的值依然是不可变字符串。

定义合成getter、setter方法时并没有提供mutableCopy指示符。因此,即使定义实例变量时使用了可变类型,但只要使用copy指示符,实例变量实际得到的值总是不可变对象。

Objective-C集合概述

OC集合类是一些非常有用的工具类,ta可以用于存储多个数量不等的对象,并可以实现常用的数据结构,如栈、队列等。除此之外,OC集合还可用于保存具有映射关系的关联数组。OC的集合大致上可分为:NSArray、NSSet和NSDictionary三种体系,NSArray代表有序、可重复的集合;NSSet代表无序、不可重复的集合;NSDictionary则代表具有映射关系的集合。

OC集合就像一种容器,程序可以把多个对象(实际上是对象的指针,但习惯上都称对象)“丢进”该容器中。

OC提供了集合类。集合类主要负责保存其他数据,因此,集合类也被称为容器类。OC集合分别由NSArray、NSSet、NSDictionary这三个类蔟代表,实际编程时面向NSArray(及其子类NSMuatbleArray)、NSSet(及其子类NSMutableSet)、NSDictionary(及其子类NSDictionary)编程,程序创建的也可能是ta们的子类的实例。

集合类和数组不一样数组元素既可以是基本类型的值,也可以是对象(实际上保存的是独享的指针变量);而集合里只能保存对象(实际上只是保存对象的指针变量,但通常认为集合里保存的是对象)。

下图显示了这三种集合的示意图。

在这里插入图片描述

如果访问NSArray集合中的元素,则可以直接根据元素的索引来访问

如果需要访问NSDictionary集合中的元素,则可以根据每项元素的key值来访问其value

如果希望访问NSSet集合中的元素,则只能根据元素本身来访问(这也是NSSet集合里元素不允许重复的原因)

数组(NSArray与NSMutableArray)

NSArray代表元素有序、可重复的一个集合,集合默认按元素的添加顺序设置元素的索引,集合中每个元素都有其对应的顺序索引。

NSArray的功能与用法

NSArray分别提供了类方法和实例方法来创建NSArray,两种创建方式需要传入的参数基本相似,只是类方法以array开头,而实例方法则以init开头

  • array:创建一个不包含任何元素的空NSArray。
  • arrayWithContentsOfFile:/initWithContentsOfFile::读取文件内容来创建NSArray。
  • arrayWithObject:/initWithObject::创建只包含指定元素的NSArray。
  • arrayWithObjects:/initwithObjects::创建包含指定的N个元素的NSArray。

除此之外,还可使用如下简化语法来创建NSArray对象:

@[元素1, 元素2, 元素3, ...]

一旦得到NSArray对象,接下来就可以调用ta的方法来操作NSArray集合。NSArray集合最大的特点是集合元素有索引,参考NSArray类的文档,可以看到NSArray集合的方法大致包含如下几类:

  • 查询集合元素在NSArray中的索引。
  • 根据索引值取出NSArray集合中的元素。
  • 对集合元素整体调用方法。
  • 对NSArray集合进行排序。
  • 取出NSArray集合中的部分集合组成新集合。
#import <Foundation/Foundation.h>

int main(int argc, char* argv[]) {
  @autoreleasepool {
    NSArray* array = [NSArray arrayWithObjects: @"iOS", @"Android", @"Ajax", @"XML", @"Swift", nil];
    NSLog(@"第一个元素:%@", [array objectAtIndex: 0]);
    NSLog(@"索引为1的元素:%@", [array objectAtIndex: 1]);
    NSLog(@"最后一个元素:%@", [array lastObject]);
    
    NSArray* arr1 = [array objectsAtIndexes: [NSIndexSet indexSetWithIndexesInRange: NSMakeRange(2, 3)]];
    NSLog(@"%@", arr1);  //获取从索引为2开始的元素开会,以及后面3个元素组成的新集合 ??
    
    NSLog(@"Android的位置为:%ld", [array indexOfObject: @"Android"]);  //获取元素在集合中的位置
    NSLog(@"在2~5范围Android的位置为:%ld", [array indexOfObject: @"Android" inRange: NSMakeRange(2, 3)]);  //获取元素在集合指定范围中的位置
    
    //原NSArray本身并没有改变,只是将新返回的NSArray赋给array
    array = [array arrayByAddingObject: @"LJY"];  //向数组的最后追加一个元素
    array = [array arrayByAddingObjectsFromArray: [NSArray arrayWithObjects: @"JY", @"YJ", nil]];  //向array数组的最后追加另一个数组的所有元素
    for (int i = 0; i < array.count; ++i) {
      NSLog(@"%@", [array objectAtIndex: i]);  //也可简写为array[i]
    }
    
    NSArray* arr2 = [array subarrayWithRange: NSMakeRange(5, 3)];  //获取array数组中索引为5~8处的所有元素 ??
    [arr2 writeToFile: @"myFile.txt" atomically: YES];
  }
}

创建NSArray对象时可直接传入多个元素,其中最后一个nil表示NSArray元素结束,其实这个nil元素并不会存入NSArray集合中

上面程序还用到了一个NSIndexSet集合,这个集合与NSSet集合的功能基本相似,区别只是NSIndexSet集合主要用于保存索引值,因此,ta的集合元素都是NSUInteger对象

[array objectAtIndex: i];array[i];的作用是相同的,但后者的这种用法只能在iOS5.0以上的系统中使用,当程序调用array[i]下标形式的语法来访问元素时,实际上就是调用NSArray的objectAtIndexSubscript:方法进行访问。

运行上面的程序,将看到如下结果:

在这里插入图片描述

有一处是一串数字,程序试图返回“Android”字符串对象在Array集合中NSRange(2, 3)范围的位置,实际上,该范围内并未包含该字符串对象,因此程序返回9223372036854775807,这是常量NSNotFound的值。

接下来,程序采用两种方法向NSArray集合后面追加了元素:

  • 使用arrayByAddingObject:方法追加单个元素。
  • 使用arrayWithObjects:方法将另一个数组中所有的元素追加到原数组的后面。

不管使用上面哪种方法,对于原有NSArray对象都不会产生任何修改(因为NSArray集合本身是不能修改的),程序只是返回一个新的NSArray对象

从上面的输出结果来看,当程序直接输出NSArray集合时,虽然会输出集合元素,但对于中文字符,则转为形如“\U75af”格式的Unicode表示形式,这种形式虽然可以正确地表示中文字符,但阅读起来并不方便。

最后,程序获取了原NSArray对象中的NSRange(5, 3)范围的子集合,接着将这个子集合的内容写入myFile.txt文件。

上面NSArray中需要大量判断指定元素位于NSArray集合中的索引,这就涉及一个标准:NSArray怎么判断集合是否包含指定元素呢?

标准只有一条:知由某个集合元素与被查找元素通过isEqual:方法比较返回YES时,才可认为该NSArray集合包含该元素,并不需要两个元素是同一个元素。

下面对NSArray的比较机制进行证实,先定义一个THGUser类,该类的接口部分如下:

#import <Foundation/Foundation.h>

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

接下来THGUser类将会重写isEqual:方法,重写该方法的比较标准是,如果两个THGUser的name、pass相等,即可认为ta们相等。THGUser类实现部分如下:

#import "THGUser.h"

@implementation THGUser
- (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);
}

//两个THGUser的name、pass相等,即可认为ta们相等
- (BOOL)isEqual: (id)other {
  if (self == other) {
    return YES;
  }
  if ([other class] == THGUser.class) {
    THGUser* target = (THGUser*)other;
    return [self.name isEqualToString: target.name] && [self.pass isEqualToString: target.pass];
  }
  return NO;
}

//重写description方法,可以直接看到THGUser对象的状态
- (NSString*)description {
    return [NSString stringWithFormat: @"<[THGUser[name=%@, pass=%@]]>", self.name, self.pass];
}

程序希望直接看到THGUser对象的内部状态,因此,程序还为THGUser类重写了description方法。

下面示范了在NSArray集合中查找指定的THGUser对象:

#import <Foundation/Foundation.h>
#import "THGUser.h"

int main(int argc, char* argv[]) {
  @autoreleasepool {
    NSArray* array = @[ [[THGUser alloc] initWithName: @"sun" pass: @"123"], [[THGUser alloc] initWithName: @"bai" pass@"345"], [[THGUser alloc] initWithName: @"zhu" pass: @"654"], [[THGUser alloc] initWithName: @"tang" pass: @"178"], [[THGUser alloc] initWithName: @"niu" pass: @"155"] ];
    
    //查找指定新THGUser对象在集合中的索引
    THGUser* newUser = [[THGUser alloc] initWithName: @"zhu" pass: @"654"];
    NSUInteger pos = [array indexOfObject: newUser];
    NSLog(@"newUser的位置为:%ld", pos);  //2
  }
}

上面程序中开始处使用了简化语法来创建NSArray对象创建NSArray时直接指定了集合元素

对集合元素整体调用方法

NSArray允许对所有集合中所有的元素或部分元素整体调用方法,如果只是简单地调用集合元素的方法,则可以通过NSArray的如下两种方法来实现:

  • makeObjectsPeformSelector:依次调用NSArray集合中每个元素的指定方法,该方法需要传入一个SEL参数,用于指定调用哪个方法。
  • makeObjectsPerformSelector: withObject::依次调用NSArray集合每个元素的指定方法,该方法的第一个SEL参数用于指定调用哪个方法,第二个参数用于调用集合元素的方法时传入参数

如果希望对集合中的所有元素进行隐式遍历并使用集合元素来执行某段代码,则可以通过NSArray的如下方法来完成:

  • enumerateObjectsUsingBlock::遍历集合中的所有元素,并依次使用元素来执行指定的代码块
  • enumerateObjectsWithOptions: usingBlock::遍历集合中的所有元素,并依次使用元素来执行指定的代码块。该方法可以额外传入一个参数,用于控制遍历的选项,如反向遍历
  • enumerateObjectsAtIndexes: options: usingBlock::遍历集合中指定范围内的元素,并依次使用元素来执行指定的代码块。该方法可传入一个选项参数,用于控制遍历的选项,如反向遍历。

上面的3个方法都需要传入一个代码块参数,该代码块必须带3个参数:

  • 第1个参数代表正在遍历的集合元素。
  • 第2个参数代表正在遍历的集合元素的索引。
  • 第3个参数就是用于遍历集合元素的代码块。

下面示范了如何对集合元素整体调用方法:

#import <Foundation/Foundation.h>
#import "THGUser.h"

int main(int argc, char* argv[]) {
  @autoreleasepool {
    NSArray* array = @[[[THGUser alloc] initWithName: @"sun" pass: @"123"], [[THGUser alloc] initWithName: @"bai" pass: @"345"], [[THGUser alloc] initWithName: @"zhu" pass: @"654"], [[THGUser alloc] initWithName: @"tang" pass: @"178"], [[THGUser alloc] initWithName: @"niu" pass: @"155"]];  //使用简化语法创建NSArray对象
    
    [array makeObjectsPerformSelector: @selector(say:) withObject: @"下午好,NSArray真强大!"];  //第一个参数为集合整体元素调用的方法,第二个为被调用的方法的参数
    NSString* content = @"3GiOS";
    
    //迭代集合内指定范围内的元素,并使用该元素来执行代码块
    //代码块的第三个参数用于控制是否停止遍历,将该参数设置为NO即可停止遍历
    [array enumerateObjectsAtIndexes: [NSIndexSet indexSetWithIndexesInRange: NSMakeRange(2, 2)] options: NSEnumerationReverse usingBlock: ^(id obj, NSUInteger idx, BOOL* stop) {
      NSLog(@"正在处理第%ld个元素:%@", idx, obj);
      [obj say: content];
    }];
  }
}

NSArray既可对集合中所有的元素整体调用某个方法,也可通过NSIndexSet来控制只对集合中部分元素迭代调用指定的代码块。虽然程序只是调用NSArray的方法,但程序在执行这些方法时,底层实际上会使用循环迭代处理调用每个集合元素,因此,这些方法都可称为迭代方法

对NSArray进行排序

NSArray提供了大量的方法对集合元素进行排序,这些排序方法都以sorted开头,最常用的排序方法如下:

  • sortedArrayUsingFuncton: context::该方法使用排序函数对集合元素进行排序,该排序函数必须返回NSOrderedDescendingNSOrderedAscendingNSOrderedSame这些枚举值,用于代表集合元素的大小。该方法返回一个排序好的新NSArray对象。
  • sortedArrayUsingSeletor::该方法使用集合元素自身的方法对集合元素进行排序,集合元素的该方法必须返回NSOrderedDescendingNSOrderedAscendingNSOrderedSame这些枚举值,用于代表集合元素的大小。该方法返回一个排序好的新NSArray对象。
  • sortedArrayUsingComparator::该方法使用代码块对集合元素进行排序,该代码块必须返回NSOrderedDescendingNSOrderedAscendingNSOrderedSame这些枚举值,用于代表集合的大小。该方法返回一个排序好的新NSArray对象。

实际上,sortedArrayUsingComparator:方法时sortedArrayUsingFunction: context:方法的简化版本。

下面示范了对NSArray集合元素进行排序:

#import <Foundation/Foundation.h>

NSComparsionResult intSort(id num1, id num2, void* context) {
  int v1 = [num1 intvalue];
  int v2 = [num2 intValue];
  if (v1 < v2) {
    return NSOrderedAscending;
  } else if (v1 > v2) {
    return NSOrderedDescending;
  } else {
    return NSOrderedSame;
  }
  
  int main(int argc, char* argv[]) {
    @autoreleasepool {
      NSArray* array1 = @[@"Objective-C", @"C", @"C++", @"Ruby", @"Perl", @"Swift"];  //初始化一个元素为NSString的对象
      array1 = [array1 sortedArrayUsingSelector: @selcetor(compare:)];  //使用集合元素的compare:方法进行排序
      NSLog(@"%@", array);
      
      NSArray* array2 = @[[NSNumber numberWithint: 20], [NSNumber numberWithInt: -8], [NSNumber numberWithInt: 50], [NSNumber numberWithInt: 19]];  //初始化一个元素为NSNumber的NSArray对象
      array2 = [array2 sortedArrayUsingFunction: intSort context: nil];
      NSLog(@"%@", array2);
      
      NSArray* array3 = [array2 sortedArrayUsingComparator: ^(id obj1, obj2) {
        if ([obj1 intValue] > [obj2 intValue]) {
          return NSOrderedDescending;
        } else if ([obj1 intValue] < [obj2 intValue]) {
          return NSOrderedAscending;
        } else {
          return NSOrderedSame;
        }
      }];  //使用代码块对集合进行排序
      NSLog(@"%@", array3);
    }
  }
}

上面程序分别示范了对NSArray进行排序的3种方法,其中第一种方法使用NSString自身的compare:方法进行排序。这时因为NSString自身已经实现了compare:方法,这意味着NSString对象本身就可以比较大小——NSString比较大小的方法是根据字符对应的编码来进行的

后两种方法通过调用函数或代码块来比较大小代码块相当于一个匿名函数,因此后面两种方法本质是一样的,ta们都可通过自定义的比较规则来比较集合元素的大小——不管集合元素本身是否可比较大小,只要程序通过比较函数或代码块定义自己的比较规则即可。

集合元素本身可比较大小,而且直接利用集合元素比较大小的的方法进行排序的方式也被称为自然排序;对于通过比较函数或代码块来制定自定义比较规则的方式,则被称为定制排序

使用枚举器遍历NSArray集合元素

对于NSArray对象,除了可根据集合元素的索引来遍历集合元素之外,还可以调用NSArray对象的如下两个方法来返回枚举器:

  • objectNumerator:返回NSArray集合的顺序枚举器。
  • reverseObjectNumerator:返回NSArray集合的逆序枚举器。

上面两个方法都返回一个NSEnumerator枚举器,该枚举器只包含如下两个方法:

  • allObjects:获取被枚举集合中的所有元素。
  • nextObjects:获取被枚举集合中的下一个元素。

一般来说,借助nextObjects方法即可对集合元素进行枚举:程序可采用循环不断获取nextObjects方法的返回值,直到该方法的返回值为nil结束循环。

下面示范了使用NSEnumerator遍历集合元素:

#import <Foundation/Foundation.h>

int main(int argc, char* argv[]) {
  @autoreleasepool {
    NSArray* array = [NSArray arrayWithContentsOfFile: @"myFile.txt"];  //读取前面写入磁盘的文件,用文件内容来初始化NSArray集合
    NSEnumerator* en = [array objectEnumerator];  //获取NSArray的顺序枚举器
    id object;
    while (object = [en nextObject]) {
      NSLog(@"%@", object);
    }
    NSLog(@"——————下面逆序遍历——————");
    en = [array reverseObjextEnumerator];  //获取NSArray的逆序枚举器
    while (object = [en nextObject]) {
      NSLog(@"%@", object);
    }
  }
}

快速枚举(for-in)

OC还提供了一种快速枚举的方法来遍历集合(包括NSArray、NSSet、NSDictionary等集合),使用快速枚举遍历集合元素时,无须获得集合的长度,也无须根据索引来访问集合元素,即可快速枚举自动遍历集合的每个元素。

快速枚举的语法如下:

for (type varibleName in collection) {
    //varibleName自动迭代访问每个元素...
}

type是集合元素的类型,varibleName是一个形参名,快速枚举将自动将集合元素依次赋给变量。

如果使用快速枚举来遍历NSDictionary对象,那么快速枚举中循环技术器将依次代表NSDictionary的每个key的值。

下面对快速枚举遍历NSArray集合元素进行示范:

#import <Foudnaiton/Foundaiton.h>

int main(int argc, char* argv[]) {
  @autoreleasepool {
    NSArray* array = [NSArray arrayWithContentsOfFile: @"myFile.txt"];
    for (id object in array) {
      NSLog(@"%@", object);
    }
  }
}

快速枚举的本质是一个for-each循环,for-each循环和普通循环不同的是,ta无须循环条件,也无须迭代语句,这些部分都由系统来完成,for-each循环自动迭代数组的每个元素,当每个元素被迭代一次后,for-each循环自动结束

可变数组(NSMutableArray)

NSArray代表集合元素不可变的集合,一旦NSArray创建成功,程序就不能向集合中添加新的元素,不能删除集合中已有的元素,也不能替换集合元素。

NSArray只是保存对象的指针,因此,NSArray只保证这些指针变量中的地址不能改变,但指针变量所指向的对象是可以改变的。

NSArray有一个子类:NSMutableArray,因此ta可作为NSArray使用。与此同时,ta代表的是一个集合元素可变的集合,,因此,程序可以像集合中添加新的元素,可以删除集合中已有的元素,也可以替换集合元素。

NSMutableArray代表集合元素可变的集合,而NSMutableArray底层采用传统数组来容纳集合元素,因此,创建NSMutableArray时可通过参数指定底层数组的初始容量。

NSMutableArray主要新增了如下方法:

  • 添加集合元素的方法:以add开头。
  • 删除集合元素的方法:以remove开头。
  • 替换集合元素的方法:以replace开头。
  • 对集合本身排序的方法:以sort开头。

NSMuableArray同样提供了sortUsingSelector:sortUsingComparator:sortUsingFunction: context:这三个方法,这三个方法与前面介绍的NSArray提供的三个排序方法的用法基本相似,区别是NSArray的排序方法是返回一个新的、排序好的NSArray对象,而NSMutableArray的排序方法则对集合本身排序

下面示范了如何改变NSMutableArray集合中的元素:

#import <Foundation/Foudnation.h>

NSString* NSCollectionToString(NSArray* array) {
 for (id obj in array) {
   [result appendString: [obj description]];
   [result appendString: @", "];
 }
  
  NSUInteger len = [result length];  //获取字符串长度
  [result deleteCharactersInRange: NSMakeRange(len - 2, 2)];  //删除字符串最后的两个字符", "
  [result appendString: @"]"];
  return result;
}

int main(int argc, char* argv[]) {
  @autoreleasepool {
    NSMutableArray* array = [NSMutableArray arrayWithContensOfFile: @"myFile.txt"];  //用文件内容初始化NSMutableArray集合
    
    [array addObject: @"iOS"];  //向集合最后添加一个元素
    NSLog(@"最后追加一个元素后:%@", NSCollectionToString(array));
    
    [array addObjectsFromArray: [MSArray arrayWithObjects: @"Kevince", @"Jakey", nil]];  //使用NSArray向集合尾部添加多个元素
    NSLog(@"最后追加两个元素后:%@", NSCollectionToString(array));
    
    [array insertObject: @"Ajax" atIndex: 2];  //向集合指定位置插入一个元素
    NSLog(@"在索引为2处插入一个元素后:%@", NSCollectionToString(array));
    
    [array insertObjects: [NSArray arrayWithObjects: @"Jack", @"Rose", nil] atIndexes: [NSIndexSet indexSetWithIndexesInRange: NSMakeRange(3, 2)]];  //使用NSArray向集合指定位置插入多个元素
    NSLOG(@"插入多个元素后:%@", NSCollectionToString(array));
    
    [array removeLastObject];  //删除集合的最后一个元素
    NSLog(@"删除最后一个元素:%@", NSCollectionToString(array));
    
    [array removeObjectAtIndex: 5];  //删除集合中指定索引处的元素
    NSLog(@"删除索引为5处的元素后:%@", NSCollectionToString(array));
    
    [array removeObjectsInRange: NSMakeRange(2, 3)];  //删除索引为2~4处的元素
    NSLog(@"删除索引为2~4处的元素后:%@", NSCollectionToString(array));
    
    [array replaceObjectAtIndex: 2 withObject: @"Android"];  //替换索引为2处的元素
    NSLog(@"替换索引为2处的元素后:%@", NSCollectionToString(array));
  }
}

上面先定义了一个NSCollectionToString()函数,该函数可以把NSArray集合转换为字符串,这样方便调试时看到NSArray集合中的元素。

不难看出,NSMutableArray与NSArray相比,最大的区别就是增加了用于“增加元素”、“删除元素”、“替换元素”的3种方法

集合(NSSet与NSMutableSet)

字典(NSDictionary与NSMutableDictionary)

NSDictionary用于保存具有映射关系的数据,因此,NSDictionary集合里保存着两组值一组值用于保存NSDictionary里的key,另一组值用于保存NSDictionary里的value。注意,key和value都可以是任何指针类型的数据,NSDictionary的key不允许重复

下图是NSDictionary里的数据结构:

000000000000000000000000

从图中可以看出,如果把NSDictionary里的所有key放在一起,ta们就组成一个NSSet集合(所有的key没有顺序,key与key之间不能重复),实际上,NSDictionary确实包含了一个allKeys方法,用于返回NSArray——这说明该方法经过了进一步转换,ta已经把NSet集合转换成了NSArray集合。

NSDictionary的功能与用法

NSDictionary集合由多个key-value对组成,因此创建NSDictionary时需要同时指定多个key-value对。NSDictionary分别提供了类方法和实例方法来创建NSDictionary,两种创建方式需要传入的参数基本相似,只是类方法以dictionary开头,而实例方法则以init开头。下面是创建NSDicitonary对象*的几类常见的方法:

  • dictionary:创建一个不包含任何key-value对的NSDictionary。
  • dictionaryWithContentsOfFile: /initWithContentsOfFile::读取指定文件的内容,使用指定的文件内容来初始化NSDictionary。该文件通常是由NSDictionary输出生成的。
  • dictionaryWithDictionary: /initWithDictionary::使用已有的NSDictionary包含的key-value对来初始化NSdictionary对象。
  • dictionaryWithObject: forKey::使用单个key-value对来创建NSDictonary对象。
  • dictionaryWithObjects: forKeys: /initWithObjects: forKeys::使用两个NSArray分别指定key、value集合,可以创建包含多个key-value对的NSDictionary。
  • dictionaryWithObjectsAndKeys: /initWithObjectsAndKeys::调用该方法时,需要按value1, key2, value2, key2, ..., nil的格式传入多个key-value对。

除此之外,还可以使用如下简化语法来创建NSDictionary对象:

@{key1: value1, key2: value2, ....}

一旦得到NSDictionary对象,接下来就可以通过方法来访问该集合所包含的key或value。NSDictionary提供了如下方法:

  • count:该方法返回该NSDictionary所包含的key-value对的数量。
  • allKeys:该方法返回该NSDictionary所包含的全部key。
  • allKeysForObject::该方法返回指定value对应的全部key。
  • allValues:该方法返回该NSDictionary所包含的全部value。
  • objectForKey::该方法获取该NSDicitonary中指定对应的value。
  • objectForKeyedSubscript::通过该方法的支持,允许NSDictionary通过下标法来吼去指定key对应的value。
  • valueforKey::该方法获取该NSDicitonary中指定key对应的value。
  • keyEnumerator:该方法返回用于遍历该NSDictionary中指定key对应的value。
  • objectEnumerator:该方法返回用于遍历该NSDictionary所有value的NSEnumerator对象。
  • enumerateKeysAndObjectsUsingBlock::使用指定的代码块来迭代执行该集合中所有的key-value对。
  • enumerateKeysAndObjectsWithOptions: usingBlock::使用指定的代码块来迭代执行该集合中所有的key-value对。该方法可以传入一个额外的NSEnumerationoOptions参数。
  • writeToFile: atomically::将该NSDictionary对象的数据写入指定文件。

下面为了能直接看到NSDictionary中包含的key-value对的详情,为NSDictionary扩展了一个print类别,在该类别中为NSDictionary扩展了一个print方法,用于打印NSDictionary中key-value对的详情。该类别的接口部分代码如下:

#import <Foudantion/Foundation.h>

@interface NSDictionary (print)
- (void)print;
@end

NSDictionary的print类别的实现部分如下:

#import "NSDictionary+print.h"

@implementation NSDictionary (print)
- (void)print {
  NSMutableString* result = [NSMutableString stringWithString: @"{"];
  //使用快速枚举语法来遍历NSDictionary
  //循环计数器将依次等于该NSDcitionary的每个key
  for (id key in self) {
    [result appendString: [key description]];
    [result appendString: @"="];
    [result appendString: [self[key] description]];  //使用下标法根据key来获取对应的value
    [result appendString: @", "];
  }
  NSUInteger len = [result length];  //获取字符串长度
  [result deleteCharactersInRange: NSMakeRange(len - 2, 2)];
  [result appendString: @"}"];
  NSlog(@"%@", result);
}
@end

上面示范了NSDictionary的两个基本用法,程序可使用快速枚举来遍历NSDictionary的所有key。除此之外,程序也可根据key来获取NSDictionary中对应的value。涌过key来获取value有如下两种语法:

  • 调用NSDictionary的objectForKey:方法即可根据key来获取对应的value。
  • 直接使用下标法根据key来获取对应的value。

也就是说,如下两行代码的功能是相同的:

[dictionary objectForKey: key];
dictionary[key];

当程序调用dictionary[key]下标形式的语法来获取value时,实际上就是调用NSDictionary的objectForKeyedSubscript:方法进行访问。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
前言在互联网时代,分布式应用、系统变得越来越多,我们在使用 .Net 技术构建分布式系统的时候,需要使用到一些组件或者是助手库来帮助我们提高生产力以及应用程序解耦,但是纵观.Net圈,能够符合要求的这样的组件并不是 很多,并且没有一个通用的抽象组件能够将这些接口集成起来,今天就为大家介绍一款开源的组件库 Foundatio,他们同样出自于Exceptionless团队之手,下面就来看看吧。目录Foundatio 介绍Getting Started缓存队列锁消息工作任务文件存储度量日志示例程序源码总结Foundatio 介绍GitHub : https://github.com/exceptionless/FoundatioFoundatio 是一个插件式的,松耦合的一套构建分布式应用的程序库,出自于Exceptionless团队。Foundatio 同时支持 .NET Framework 4.6 和 .NET Core。通过 Foundatio 我可以获得哪些帮助呢?如果你是面对接口(抽象)构建的程序,你可以很容易的对接口实现进行切换。具有友好的依赖注入,还在使用 .Net Framework的朋友可以体验一下,它具有比Autofac,Unity等更简易的操作和更友好的接口。可以更加方便的使用缓存了,Foundatio帮助我们封装了很多缓存的客户端实现,比如RedisCache、InMemoryCache、ScopedCache等等。消息总线,你不必自己构建或者使用复杂且昂贵的NServiceBus了,很多时候我们仅仅需要的是一个可以在本地或者云上运行的简单的消息总线。存储,现在你可以很方便的通过一致的接口来使用分布式存储了,包括内存文件存储、文件夹文件存储,Azure文件存储,AWS S3文件存储。Foundatio 主要包含以下模块:缓存(Caching)队列(Queues)锁(Locks)消息(Messaging)工作任务(Jobs)文件存储(File Storage)度量(Metrics)日志(Logging)这些组件都以NuGet包的形式提供出来供我们很方便的使用,下面依次来看看每一个组件的用途和使用方法吧。Getting Started缓存缓存是一种空间换时间的技术,你可以通过缓存来快速的获取一些数据。Foundatio Cache 提供了一致的接口ICacheClient 来很容易的存储或者读取缓存数据,并且提供了4中不同的缓存客户端的实现。他们分别是:1、InMemoryCacheClient:内存缓存的实现,这种缓存的生命周期为当前进程, 有一个MaxItems属性,可以设置最多缓存多少条数据。2、HybridCacheClient:InMemoryCacheClient 具有相同的实现,但是此接口提供、IMessageBus 可以用来跨线程之间的同步。3、RedisCacheClient:一个 Redis 客户端的实现。4、RedisHybridCacheClient:一个RedisCacheClient 、InMemoryCacheClient 的混合实现,通过RedisMessageBus来保持内存缓存跨线程之间的同步。注意:如果本地缓存的项已经存在,在调用Redis进行序列化保存的时候可能会有性能问题。5、ScopedCacheClient:传入ICacheClient和scope,scope 可以设置一个字符串,来制定一个缓存键前缀,这样可以很方便的进行批量存储和删除。例子:using Foundatio.Caching; ICacheClient cache = new InMemoryCacheClient(); await cache.SetAsync("test", 1); var value = await cache.GetAsync("test");队列提供了一个先进,先出的消息管道,Foundatio 提供了一个IQueue接口,并且拥有 4 种不同的队列实现。1、InMemoryQueue:一个内存队列实现,队列的生命周期为当前进程。2、RedisQueue:一个 Redis 队列实现。3、AzureServiceBusQueue:一个基于Azure的服务消息队列实现。4、AzureStorageQueue:一个基于Azure的存储队列实现。例子:using Foundatio.Queues; IQueue queue = new InMemoryQueue();await queue.EnqueueAsync(new SimpleWorkItem {     Data = "Hello"});var

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值