字符串处理NSString

认识字符串NSString

  • NSString是一个Unicode编码、16位字符的字符序列
  • NSString被定义为类,引用类型,拷贝时具有引用语义。
  • 初始化方法:字面初始化、初始化器、工厂方法
  • NSSring拥有恒定性,所有的操作无法更改字符串本身,如果有更改,都是返回新值的形式。
  • NSString拥有共享机制,引用计数管理对其有特殊的管理规则

oc的NSString和c语言的区别

  • NSString是一个oc对象,OC字符串是一个字符串对象,字符串常量需要用@""包含。
  • C语言字符串是一个数据类型,用""包含
  • OC中的字符串以Unicode编码,C语言的字符串以字符的ASCII码形式。
  • 打印OC字符串用%@,打印C语言字符串用%s

三种初始化方法:

NSString *str1 = @"Hello World!";
//字面常量,加@才是OC的字符串

NSString *str2 = [[NSString alloc]initWithCString:"Hello World!" encoding:NSUTF8StringEncoding];
//用内存分配搭配初始化器

NSString *str3 = [NSString stringWithCString:"Hello World!" encoding:NSUTF8StringEncoding];
//工厂方法,是一个类方法,通过类方法内部返回一个对象

NSString *str4= @"Hello World!";

上面运行结果str1str4的存放地址是一样的(共享机制)。只要用字面常量创立的字符串才会共享

  • 判断相等isEqualToString值是否相等
  • str1 == str2比较指针(引用)是否相等

NSMutableString

  • NSMutableString具有可变性,NSString具有恒定性
  • NSMutableString不具有共享机制,NSString具有共享机制
  • NSMutableString不是在原有内存上直接增长,而是重新分配一个更大或更小的缓存容量存放字符
  • NSMutableString是NSString的子类
NSMutableString *mustr1 = [NSMutableString stringWithString: @"Hello,World!"];
//工场方法停止初始化

NSLog(@"mustr1:%p",mustr1);

NSMutableString *mustr2 = [NSMutableString stringWithString: @"Hello,World!"];
NSLog(@"mustr2:%p",mustr2);

NSString *str5=mustr1;

NSLog(@"str5:%@",str5);
[mustr1 appendString:@" Very Good!"];
NSLog(@"str5:%@",str5);
//NSMutableString的改动,使他的父类NSString发作了改动

存在的固有问题:通过mustr1的改动,使的父类NSString发生了改变(而上面提到NSString具有不变性)

NSMutableString的内存机制

缓存容量与增长

  • 字符串初始化后,会分配一个缓存容量capacity,其长度一般大于实际的字符串数量,当然也可以自己给它一个缓存容量
  • 当字符串增长时,如果实际需求大于capacity,其capacity会以两倍的方式指数增长,代价是:
    • 分配新的堆内存2*capacity
    • 将原来堆内存的内容拷贝到新内存,
    • 释放原来堆内存
  • 最佳实践:估计好capacity,预先分配好一定容量,避免以后capacity的增长

常见操作

  • NSString常见操作
    • 访问字符串:获取字符串、字符串长度、字面量、大小写转换
    • 查询字符串:定位子串、获取子串、是否包含子串、查询字符集
    • 其他操作:比较字符串、替换字符串、分解字符串
  • NSMutableString:
    • 添加字符串 [mustr3 appendString:@"Hello Objective"];
    • 删除字符串
    • 修改字符串

集合类型

数组

  • 数组是一个有序的元素序列,支持随机存取。索引从0开始,索引访问越界会抛出运行时异常。注意与C语言数组不同。
NSArray *array1=[NSArray arrayWithObjects:@"Shanghai",@"Beijing",@"New York",@"Paris", nil];
NSArray *array2=[[NSArray alloc] initWithObjects:@"Shanghai",@"Beijing",@"New York",@"Paris", nil];
NSArray *array3=@[@"Shanghai",@"Beijing",@"New York",@"Paris"];
  • 可以通过count方法访问数组中元素的个数,返回一个NSInteger.
  • NSArray被定义为class,引用类型,拷贝时具有引用语义。
  • NSArray的元素必须是对象,即NSObject的子类。
    • 如果为基本数值类型,须要用NSNumber 封装为对象类型后,才能放入数组中。
    • 如果为C语言结构类型,须要用NSValue 封装为对象类型后,才能放入数组中。
    • 数组元素可以是不同对象类型,可能会有类型不安全。
NSInteger number=100;
NSNumber *numberObject1= [NSNumber numberWithInteger:number ];
NSValue *pointObject = [NSValue value:&point withObjCType:@encode(Point)];
  • NSArray具有常量性:长度和元素指针都不能更改。但指针指向的对象内部可以更改。

NSArray内存模型

NSArray内存模型

数组遍历

  • 最快 for in 直接访问内存,优化索引检查,比for循环快5-10倍
  • 较慢 NSEnumeration遍历迭代的方法
NSEnumerator *enumerator = [array5 objectEnumerator];
BLNPoint* item;
while (item = [enumerator nextObject]){
item.x++;
item.y++;
}
  • for循环最慢

数组查找的方法

  • indexOfObject 查找是否存在“值相等”的对象(类型需要实现isEqual)。

    NSUInteger index1=[array5 indexOfObject:target];

  • indexOfObjectIdenticalTo 查找是否存在“指针想等”的对象。

    NSUInteger index2=[array5 indexOfObjectIdenticalTo:p3];

  • objectAtIndex:方法访问数组中的单个元素,返回给定索引处的单个元素。

  • lastObject:方法返回数组的最后一个元素。

数组排序

不改变原数组(常量性),返回新数组。

NSArray* sortArray1=[array1 sortedArrayUsingSelector:@selector(compare:)];

使用NSMutableArray

  • NSMutableArray支持更改数组长度和元素指针,为NSArray的子类。
  • capacity与NSMuatbalString一样
  • 估计好capacity预先分配一定容量,避免以后增长
  • 尽量避免使用insertObject:atIndex:removeObjectAtIndex:等操作,因为会改变数组元素序列,涉及大量内存拷贝操作,代价高。

set

  • NSSet 是一个无序的集合,其存储的对象不能重复。
  • 常量性:长度和元素指针都不能更改。但指针指向的对象内部可以更改。
  • 创建NSMutableSet时用initWithCapacity提前设置capacity。

Dictionary字典类型

  • NSDictionary是一个存储key-value的无序集合,key唯一,value可重复。

  • NSDictionary被定义为class,引用类型,拷贝时具有引用意义。

  • 常量集合 NSDictionary,可变集合: NSMutableDictionary:

  • 常量性:长度和元素指针都不能更改。但指针指向的对象内部可以更改。

  • 创建NSMutableDictionary 时用initWithCapacity 提前设置capacity

  • 支持Fast Enumeration和NSEnumerator遍历,前者较快。

  • 排序方法

    • sortedArrayUsingSortDescriptors:
    • sortedArrayUsingFunction:context:
    • sortedArrayUsingSelector:

其他类型

  • NSMumber类是用来封装基本数据类型。
    • 装箱(boxing)是一个基本类型的数据封装成对象的过程
    • 开箱(unboxing)从对象中提取基本类型的数据
+(NSNumber *) numberWithChar:(char) value;
+(NSNumber *) numberWithInt:(int) value;
+(NSNumber *) numberWithFloat:(float) value;
+(NSNumber *) numberWithBool:(BOOL) value;
//可以通过下面的实例方法重新获得基本类型数据:
-(char) charValue;
-(int) intValue;
-(float) floatValue;
-(BOOL) boolValue;
-(NSString *) stringValue;

  • NSValue可以封装任意值,可以使用NSValue将结构体放入NSArray和NSDictionary中。
    +(NSValue *) valueWithBytes: (const void *) value objCType:(const char *) type;

自动引用计数ARC

ARC

  • 自动引用计数(Automatic Reference Counting)是Objective-C默认的内存管理机制,其针对堆上的对象,由编译器自动生成操作引用计数的指令(retain或release),来管理对象的创建与释放。
  • 哪些对象受ARC管理:
    1. OC对象指针
    2. Block指针
    3. 使用attribute((NSObject))定义的typedef
  • 哪些对象不受ARC管理:
    1. 值类型(简单值类型,C语言struct)
    2. 使用其他方式分配的堆对象(如使用malloc分配)
    3. 非内存资源

引用计数管理

  • 新创建(使用alloc,new,copy等)一个引用类型对象,引用计数为1
    BLNPoint *p1 = [[BLNPoint alloc]init];
    BLNRectangle *rect = [[BLNRectangle alloc]init];
  • 对象引用计数增1——retain操作:
    1. 将对象引用赋值给其他变量或常量。
      BLNPoint *p2 = p1;
    2. 将对象引用赋值给其他属性或实例变量。
      rect.center = p1;
    3. 将对象传递给函数参数,或者返回值。
      draw(p1);
    4. 将对象加入集合中。
      array=[[NSMutableArray alloc]initWithCapacity:10];
      [array addObject:p1];
  • 对象引用计数减1——release操作:
    1. 将局部变量或全局变量赋值为nil或其他值。
      p1 = nil;
      p2 = nil;
    2. 将属性赋值为nil或其他值。
      rect.center = nil;
    3. 实例属性所在的对象被释放。
    4. 参数或局部变量离开函数。
    5. 将对象从集合中删除。
      [array removeObjectAtIndex:0];
  • 引用计数变为0时,内存自动被释放

自动释放池(Autorelease Pool)

  • release会导致对象立即释放。如果频繁对对象进行release,可能会造成琐碎的内存管理负担。autorelease可以将release的调用延迟到自动释放池被释放时。

  • 推荐使用自动释放池(AutoreleasePool)Block,当其结束时,所有接受autorelease消息的对象将会被立即释放(即发送release消息)。

  • AppKit和UIKit框架在处理每一次事件循环迭代时,都会将其放入一个Autorelease Pool中。大多数情况,无需程序员干预。

  • 什么时候需要手工管理Autorelease Pool

    • 编写的程序不基于UI框架,如命令行程序。
    • 在循环中创建大量临时对象,需要更早地释放,避免临时对象聚集导致内存峰值过大。
    • 在主线程之外创建新的线程,在新线程开始执行处,需要创建自己的Autorelease Pool.
    • 可以嵌套使用Autorelease Pool.

类型合同:协议

认识协议 Protocol

  • 协议:类型的合同约定,只描述外部接口,不提供具体实现。
  • 协议可以包含以下成员:
    1. 属性
    2. 实例方法
    3. 类方法
    4. 初始化器-不常用
    5. 析构器-不常用
@protocol Drawable
@property NSInteger x;
@property NSInteger y;
-(void)draw;
+(void)createShape;
@optional
-(void)moveToX:(NSInteger)x withY:(NSInteger)y;
@end
  • 协议中无法包含实例变量成员
  • 协议中定义的属性本质上是访问器方法,编译器不会合成实例变量。

协议关键字

  • @required:表明其后所有的方法是实现该协议的必须方法。协议的默认行为,如果没有指定@required ,协议中声明的所有方法都默认是必须实现的。
  • @optional:表明实现类时可以选择性实现该方法。实现了该协议的类可以选择不实现任何在@optional后所有声明的方法。

使用协议

  • 一个类遵守协议,需要实现协议约定的的所有@required成员
@interface BLNPoint : NSObject<Drawable>
@implementation BLNPoint
-(void)draw
{
NSLog(@"%@",self);
}
+(void)createShape{
NSLog(@"Create a Shape");
}
-(void)moveToX:(NSInteger)x withY:(NSInteger)y
{
self.x = x;
self.y = y;
}
@end
  • 协议中的属性须在实现类的.h文件中声明(编译器合成实例变量需要)。
@property  NSInteger x;
@property NSInteger y;
  • 注意编译警告
    • 遵守协议后却没有实现必选协议方法时,会出现警告提示。
    • 协议类型变量被赋值非协议类型对象时,会出现警告提示。
  • 协议本质上是一种类型,可以作为声明类型,但不能创建实例。
  • 检查协议类型
    –>使用conformsToProtocol:检查对象是否实现了协议。
void process2(id obj){
if ([obj conformsToProtocol:@protocol(AProtocol) ]) {
[obj methodA];
}
}

更多协议形式

  • 协议继承

    • 一个协议可以继承一个或多个协议
    • 实现子协议的类型,也必须实现父协议中约定的成员
  • 协议组合

    • 可以使用protocol<A,B,…>来组合多个协议
    • 实现组合协议的类型,必须实现组合协议中的每一个协议
  • 可选协议

    • 协议的 某些成员可以定义为optional,不必实现
  • 常用协议

    • NSObject:包含对象的常用操作,想等、字符串表示、哈希
    • NSCopying:支持复制的类型必须遵守该协议
    • NSFastEnumeration:实现快速枚举 for-in的类型采用
    • NSCoding协议:支持将对象图进行编码/解码以支持对象序列化

类别与扩展

类别Category

  • 类别支持在没有源代码的情况下,基于某些特定的场合,为一个类增加功能。
  • 可以添加
    • 类方法
    • 实例方法
    • 重写基类方法
  • 不能添加
    • 属性
    • 实例变量
    • 已存在的同名方法
@interface BLNPoint(Drawing)
-(void)draw;
-(void)setWeight:(NSInteger)weight;
-(NSInteger)weight;
@end
  • 命名规范
    • 文件名:类名+扩展方法,如 NSString+Drawing.h/.m

使用场景

  • 适合在没有源代码的情况下,向已经封装的类中添加方法。
    @interface NSString(Drawing)
  • 为一个类在某些特殊场景下增加功能
@interface BLNPoint(Drawing)
-(void)draw;
-(void)setWeight:(NSInteger)weight;
-(NSInteger)weight;
@end
  • 对于复杂的大型文件分割实现。
  • 添加类别
    1. 自己创建的类
    2. 系统的类
    3. 第三方库

扩展extension

  • 扩展支持在编译时,有类的源代码的前提下,向类添加功能。可以将扩展看做匿名的类别。
  • 接口定义在.m文件中@implementation前声明,实现代码仍然在@implementation内实现。
@interface Circle ()
{
NSString * _name;
}
@property (readwrite )NSInteger radius;//修改读写属性
@property NSInteger center;//添加属性
-(float)getDiameter;//实例方法
+(void)process:(Circle*) circle;//类方法
@end
  • 扩展支持添加以下成员:
    1. 添加属性。
    2. 添加实例成员。
    3. 添加类方法。
    4. 添加实例方法。
    5. 改写属性的读写属性。

使用扩展

  • 扩展实现的成员都只能在.m实现文件内部访问,在类外不可以直接访问。
  • 扩展的主要用途在于信息隐藏,隐藏一些外部无需访问、而内部实现又需要使用的属性、方法:
    • 类的主接口主要用于“对类外公开”的接口。
    • 类的扩展接口用于“对类内可见”的接口。

OC泛型

  • 泛型是程序设计语言的一种特性,他主要是为了限制类型的,比如OC中的数组,你可以限制他里面装的是NSString类型。

  • 泛型的基本格式

    • 泛型声明格式:在声明类的时候,在类型后面<泛型名称>
    • 泛型定义格式:放在限制的类型后面<类型>
  • 泛型的好处

    • 提高程序员开发规范,让程序员一眼就可以看出该使用什么类型
    • 限制类型,不允许装入其它的类型
    • 可以使用点语法