8.字符串处理
认识
->NSString是一个Unicode编码、16位字符的字符序列。
语法:NSString *someString = @"this is a string";
->NSString被定义为类,引用类型,拷贝时具有引用语义。
编译器遇到@并紧跟着包含在双引号中的字符串就会创建一个静态的包含所提供字符串的NSString对象。
->初始化方法:字面量初始化、初始化器、工厂方法。
NSString *str1 = @"Hello World!"; NSString *str2 = [[NSString alloc]initWithCString:"Hello World!" encoding:NSUTF8StringEncoding]; NSString *str3 = [NSString stringWithCString:"Hello World!" encoding:NSUTF8StringEncoding]; NSString *str4= @"Hello World!";
->NSString拥有恒定性,所有的操作无法更改字符串本身,如有更改,都是返回新值的形式。
NSLog(@"str1:%p",str1); NSLog(@"str2:%p",str2); NSLog(@"str3:%p",str3); NSLog(@"str4:%p",str4);
结果:
if( [str1 isEqualToString:str2]){ NSLog(@"str1 Value Equals str2"); }
if(str1==str2){
NSLog(@"str1 Ref Equals str2");
}
else{
NSLog(@"str1 Ref Not Equals str2");
}
if(str1==str4){
NSLog(@"str1 Ref Equals str4");
}
else{
NSLog(@"str1 Ref Not Equals str4");
}``
结果:
->NSString拥有共享机制,引用计数管理对其有特殊的管理规则。
NSString内存模型
NSString共享机制
NSMutableString
->NSMutableString具有可变性,NSString具有恒定性。
-> NSMutableString 为NSString的子类。
-> NSMutableString不具有在原有内存上直接增长,而是重新分配一个更大或更小的缓存容量存放字符。
-> NSMutableString不具有共享机制,NSString具有共享机制。
NSMutableString内存机制
缓存容量与增长
->字符串初始化后,会分配一个缓存容量capacity,其长度一般大于实际的字符数量。
->当字符串增长时,如果实际需求大于capacity,其capacity会以二倍的方式指数增长。伴随的代价:
-->1.分配新的堆内存 2*capacity
-->2.将原来堆内存上的内存拷贝到新内存
-->3.释放原来堆内存
->最佳实践,估计好capacity,预先分配好一定容量,避免以后capacity的增长
NSString常用操作
->NSString
-->1.访问字符串:获取字符串字符、字符串长度、字面量、大小写转换。
[str1 characterAtIndex:i]
str1 = str1.uppercaseString;//大写 str1 = str1.lowercaseString;//小写 str1 = str1.capitalizedString;//首字母大写
-->2.查询字符串:定位子串、获取子串、是否包含子串、查询字符集。
-->3.其他操作:比较字符串、替换字符串、分解字符串。
-> NSMutableString
-->添加字符串
-->删除字符串
-->修改字符串
NSMutableString *mustr3 =[NSMutableString stringWithCapacity:100];
[mustr3 appendString:@"Hello Objective"];
[mustr3 insertString:@"-C" atIndex:mustr3.length];
[mustr3 setString:@"Hi Objective"];
NSRange replaceRange = NSMakeRange(0, 2);
[mustr3 replaceCharactersInRange:replaceRange withString:@"Hello"];
NSRange deleteRange = NSMakeRange(5, 10);
[mustr3 deleteCharactersInRange:deleteRange];``
9.集合类型
集合类型 Collection Types
数组、Set、Dictionary
认识数组
->数组是一个有序的元素序列,支持随机存取。索引从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的子类。
@interface NSArray<__covariant ObjectType> : NSObject <NSCopying, NSMutableCopying, NSSecureCoding, NSFastEnumeration>
-->1.如果为基本数值类型,须要用NSNumber 封装为对象类型后,才能放入数组中。
NSInteger number=100; NSNumber *numberObject1= [NSNumber numberWithInteger:number ];
-->2.如果为C语言结构类型,须要用NSValue 封装为对象类型后,才能放入数组中。
NSValue *pointObject= [NSValue value:&point withObjCType:@encode(Point)];
-->3.数组元素可以是不同对象类型,可能会有类型不安全。
->NSArray具有常量性:长度和元素指针都不能更改。但指针指向的对象内部可以更改。
常用操作
->数组遍历
-->1.最快——Fast Enumeration,直接访问内存,优化索引检查,快5-10倍。
for ( BLNPoint* point in array5) { point.x++; point.y++; }
-->2.较慢——NSEnumeration遍历:索引检查+动态消息调用。
NSEnumerator *enumerator = [array5 objectEnumerator]; BLNPoint* item; while (item = [enumerator nextObject]) { item.x++; item.y++; }
-->3.最慢——For循环访问:索引检查+动态消息调用。
for (int i=0; i<array5.count; i++) { NSLog(@"array5[%d],%@",i,array5[i]); }
->数组查找
-->indexOfObject 查找是否存在“值相等”的对象(类型需要实现isEqual)。
NSUInteger index1=[array5 indexOfObject:target];
-->indexOfObjectIdenticalTo 查找是否存在“指针想等”的对象。
NSUInteger index2=[array5 indexOfObjectIdenticalTo:p3];
-objectAtIndex:
方法访问数组中的单个元素,返回给定索引处的单个元素。
-lastObject:
方法返回数组的最后一个元素。
->数组排序
-->不改变原数组(常量性),返回新数组。
NSArray* sortArray1=[array1 sortedArrayUsingSelector:@selector(compare:)];
使用NSMutableArray
->NSMutableArray 支持更改数组长度和元素指针。为NSArray 子类。
NSMutableArray *muArray1=[NSMutableArray arrayWithObjects:p1,p2,p3,p4, nil];
@interface NSMutableArray<ObjectType> : NSArray<ObjectType>
->NSMutableArray 初始化后,会分配一个缓存容量capacity,一般大于实际元素数量,当长度增长时,如实际需求大于capacity,其capacity会以近似二倍的方式指数增长。伴随代价:
-->1.分配新的堆内存 2*capacity
-->2.将原来堆内存上的元素拷贝到新内存
-->3.释放原来堆内存
->最佳实践:估计好capacity,预先分配一定容量,避免以后增长
int count=100;
NSMutableArray *muArray2=[NSMutableArray arrayWithCapacity:10];
for (int i=0; i< count; i++) {
BLNPoint *pt=[[BLNPoint alloc] initWithX:i*10 WithY:i*20];
[muArray2 addObject: pt];
}
->尽量避免使用 insertObject:atIndex: 和removeObjectAtIndex:等操作,因为会改变数组元素序列,涉及大量内存拷贝操作,代价高。
认识Set
->NSSet 是一个无序的集合,其存储的对象不能重复。
NSSet *set1 = [NSSet setWithObjects:@"Shanghai",@"Beijing",@"New York",@"Paris", nil];
->NSSet 被定义为class,引用类型,拷贝时具有引用语义。
->常量集合 NSSet ,可变集合:NSMutableSet:
-->1.常量性:长度和元素指针都不能更改。但指针指向的对象内部可以更改。
-->2.创建NSMutableSet时用initWithCapacity提前设置capacity。
NSMutableSet *set2 = [NSMutableSet setWithObjects:@"Shanghai",@"Beijing",@"New York",@"Paris", nil]; [set2 addObject:@"London"]; [set2 addObject:@"Paris"]; [set2 removeObject:@"Beijing"]; for(NSString* item in set2) { NSLog(@"%@", item); }
结果:
->支持Fast Enumeration 和 NSEnumerator遍历,前者较快。
认识 Dictionary
->NSDictionary是一个存储key-value的无序集合,key唯一,value可重复。
NSDictionary *dictionary1 = @{ @"Shanghai" : p1, @"Beijing" : p2, @"New York" : p3, @"Paris" : p4};
->NSDictionary被定义为class,引用类型,拷贝时具有引用意义。
->常量集合 NSDictionary,可变集合: NSMutableDictionary:
NSMutableDictionary *dictionary2 = [NSMutableDictionary dictionaryWithObjectsAndKeys: p1,@"Shanghai", p2,@"Beijing", p3,@"New York", p4,@"Paris", nil];
-->常量性:长度和元素指针都不能更改。但指针指向的对象内部可以更改。
-->创建NSMutableDictionary 时用initWithCapacity 提前设置capacity
->支持Fast Enumeration和NSEnumerator遍历,前者较快。
for(NSString* key in dictionary1) { id object=dictionary1[key]; NSLog(@"key:%@, object:%@", key, object); }
-objectsForKeys:notFoundMarker:
在给定键没有找到时返回给定对象。
-allKeys````-allObjects
获取字典的所有键值或所有对象。
排序
1.-sortedArrayUsingSortDescriptors:
2.-sortedArrayUsingFunction:context:
3.-sortedArrayUsingSelector:
其他类型
1.NSNumber
NSNumber类来封装基本数据类型。以下类方法创建新的NSNumber对象:
+(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;
装箱(boxing):将一个基本类型的数据封装成对象的过程。
开箱(unboxing):从对象中提取基本类型的数据。
OC不支持自动装箱功能。
NSValue
创建新的NSValue对象:
+(NSValue *) valueWithBytes: (const void *) value objCType:(const char *) type;
NSNumber是NSValue的子类,NSValue可以封装任意值。可以使用NSValue将结构体放入NSArray 和NSDictionary中。
NSRect rect = NSMakeRect(1,2,20,30); NSValue *value = [NSValue valueWithBytes:&rect objType:@encode(NSRect)]; [array addObject:value];
10.自动引用计数ARC
了解ARC
->自动引用计数(Automatic Reference Counting)是Objective-C默认的内存管理机制,其针对堆上的对象,由编译器自动生成操作引用计数的指令(retain或release),来管理对象的创建与释放。
->哪些对象受ARC管理:
-->1.OC对象指针
-->2.Block指针
-->使用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的调用延迟到自动释放池被释放时。
->推荐使用自动释放池(Autorelease Pool)Block,当其结束时,所有接受autorelease消息的对象将会被立即释放(即发送release消息)。
->AppKit和UIKit框架在处理每一次事件循环迭代时,都会将其放入一个Autorelease Pool中。大多数情况,无需程序员干预。
自动释放池(Autorelease pool)
什么时候需要手工管理Autorelease Pool
->编写的程序不基于UI框架,如命令行程序。
->在循环中创建大量临时对象,需要更早地释放,避免临时对象聚集导致内存峰值过大。
->在主线程之外创建新的线程,在新线程开始执行处,需要创建自己的Autorelease Pool.
->可以嵌套使用Autorelease Pool.
11.类型合同:协议
认识协议 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
->协议中无法包含实例变量成员
->协议中定义的属性本质上是访问器方法,编译器不会合成实例变量。
声明协议关键字
1.@required:表明其后所有的方法是实现该协议的必须方法。协议的默认行为,如果没有指定@required ,协议中声明的所有方法都默认是必须实现的。
2.@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;
->注意编译警告⚠️:
-->1遵守协议后却没有实现必选协议方法时,会出现警告提示。
-->2.协议类型变量被赋值非协议类型对象时,会出现警告提示。
->协议本质上是一种类型,可以作为声明类型,但不能创建实例。
void process1(id<Drawable> obj) { [obj draw]; NSLog(@"[%ld,%ld]",(long)obj.x,(long)obj.y); }
->检查协议类型
-->使用conformsToProtocol:检查对象是否实现了协议。
void process2(id obj){ if ([obj conformsToProtocol:@protocol(AProtocol) ]) { [obj methodA]; } }
更多协议形式
->协议继承
-->1.一个协议可以继承一个或多个协议
@protocol BProtocol <AProtocol> -(void)methodB; @end
-->2.实现子协议的类型,也必须实现父协议中约定的成员
@interface ClassB : NSObject<BProtocol> @end
@implementation ClassB -(void)methodA{ NSLog(@"ClassB methodA"); } -(void)methodB{ NSLog(@"ClassB methodB"); } @end
->协议组合
-->可以使用protocol<A,B,...>来组合多个协议
void process4(id<CProtocol,AProtocol> obj){ [obj methodA]; [obj methodC]; }
-->实现组合协议的类型,必须实现组合协议中的每一个协议
@implementation ClassC -(void)methodA{ NSLog(@"ClassC methodA"); } -(void)methodC{ NSLog(@"ClassC methodC"); } @end
->可选协议
-->协议的 某些成员可以定义为optional,不必实现
@optional -(void)moveToX:(NSInteger)x withY:(NSInteger)y;
了解常用协议
->NSObject:包含对象的常用操作,想等、字符串表示、哈希
@protocol NSObject
- (BOOL)isEqual:(id)object; @property (readonly) NSUInteger hash;
->NSCopying:支持复制的类型必须遵守该协议
@protocol NSCopying
- (id)copyWithZone:(nullable NSZone *)zone; @end
->NSMutableCopying:在NSCopying协议的基础上,支持复制数据的可变性
@protocol NSMutableCopying
- (id)mutableCopyWithZone:(nullable NSZone *)zone; @end
->NSFastEnumeration:实现快速枚举 for-in的类型采用
@protocol NSFastEnumeration
- (NSUInteger)countByEnumeratingWithState:(NSFastEnumerationState *)state objects:(id __unsafe_unretained [])buffer count:(NSUInteger)len; @end
->NSCoding协议:支持将对象图进行编码/解码以支持对象序列化
@protocol NSCoding
- (void)encodeWithCoder:(NSCoder *)aCoder;
- (nullable instancetype)initWithCoder:(NSCoder *)aDecoder; // NS_DESIGNATED_INITIALIZER @end
12.类别与扩展
类别 Category
->类别支持在没有源代码的情况下,基于某些特定的场合,为一个类增加功能。
-->1.可以添加
--->1.1类方法
--->1.2实例方法
--->1.3重写基类方法
-->2.不能添加
--->2.1属性
--->2.2实例变量
--->2.3已存在的同名方法
@interface BLNPoint(Drawing)
-(void)draw; -(void)setWeight:(NSInteger)weight; -(NSInteger)weight; @end
->命名规范
-->文件名:类名+扩展方法,如 NSString+Drawing.h/.m
NSString+Drawing.h
NSString+Drawing.m
使用类别
->使用场景
-->1.适合在没有源代码的情况下,向已经封装的类中添加方法。
@interface NSString(Drawing)
-->2.为一个类在某些特殊场景下增加功能
@interface BLNPoint(Drawing)
-(void)draw; -(void)setWeight:(NSInteger)weight; -(NSInteger)weight; @end
-->3.对于复杂的大型文件分割实现。
->添加类别
-->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实现文件内部访问,在类外不可以直接访问。
->扩展的主要用途在于信息隐藏,隐藏一些外部无需访问、而内部实现又需要使用的属性、方法:
-->1.类的主接口主要用于“对类外公开”的接口。
-->2.类的扩展接口用于“对类内可见”的接口。