字符串 NSString
OC的字符串具有共享性,和恒定性。
<pre><code>
//其实这两个变量都指向了同一个地址
NSString *str1 = @"Hello World!";
NSString *str4 = @"Hello World!";
</pre></code>为了避免内存浪费,编译器会做优化,将对象的指针指向同一个地方。但是,这种情况只会在字面量初始化时出现
字符串的初始化:
<pre><code>NSString *str1 =@"Hello World!";//字面量初始化
NSString*str2 = [[NSStringalloc]initWithCString:"Hello
World!" encoding:NSUTF8StringEncoding];
//初始化器初始化
NSString*str3 = [NSStringstringWithCString:"Hello
World!" encoding:NSUTF8StringEncoding];
//工厂初始化
//其中,工厂初始化方法,
是一个类方法,通过类方法内部返回一个新的对象
</pre></code>
字符串基本操作
<pre><code>
[str1 stringByAppendingString: @"Hello World"] //加字符串
[str1 stringByReplacingCharactersInRange: @"Hello World"]//替换字符串
[str1 isEqualToString:str2]// 比较值是否相等
str1 == str2
比较指针是否相等
</pre></code>
<pre><code>
for(int i =0 ;i < [str1 length];i
++){
NSLog(@"%c",[str1 characterAtIndex:i]); //遍历字符串
}
str1 = str1.uppercaseString;//大写
str1 = str1.lowercaseString;//小写
str1 = str1.capitalizedString;//首字母大写
NSRange range = [str1 rangeOfString:@"Hello"];//查找字符串某处是否包含其它字符,返回location和length。
NSLog(@"location: %lu,length:%lu",range.location,range.length);
NSString* substr = [str1 substringFromIndex:6];//从索引6开始取他的子字符串
BOOLyOrN = [str1 hasPrefix:@"Hello"];//判断是否有这个字符串前缀
NSString* format=[NSString stringWithFormat:@"[%d,
%d]",100,200];//格式化字符串方法
</pre></code>
<pre><code>[str1 stringByAppendingString:@" Yes!"];
//伪更改,不会去真正的更改str1所指的堆上的值本身,只会返回一个新值,
str1=[str1 stringByAppendingString:@" Yes!"];
//所以要重新给str1
赋值。
</pre></code> 如果在st1
的字符串后面再加一点字符串,这个字符串也不会更改,这是字符串的恒定性,无法改变字符串本身。
还有一个可变字符串 NSMutableString
NSMutableString
具有可变性,它的值是可以被更改的,因此它也不具备共享性。
注意:
它是NSString
的子类
所以当用NSMutableString
定义一个字符串空间mstr1
,
我们可以把mstr1
赋值给NSString
类型的str1
,
因为mstr1
的值是NSMutableString
类型,具有可变性,
所有当用appendString
去更改mstr1
的值时,str1
的值也会跟着改变,
这就违反了NSString
的恒定性。
所以针对这个就要用拷贝属性[copy str1]
去赋值
<pre><code>
NSMutableString mustr3 =[NSMutableStringstringWithCapacity: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];//删除字符串
</pre></code>
以上这些操作都会直接改变字符串的值
Sizeof(str1)
指的是指针的大小
Str1.length
指的是字符串值的长度
集合类型
数组 Array
OC的数组被定义为一个class,和C的数组不同,
而当访问越界时,会报错。
数组的初始化有三种方法:
<pre><code>
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"];
//字面量方法
</pre></code>
在工厂和初始化器最后的nil
表示输入结束。
array里的元素必须是对象(NSObject的子类)
如果想在array里放值,那么必须用NSNumber封装成类对象再放入array
NSNumber *numberObject1 =[NSNumber numberWithInteger:number ];
NSInteger
是整数值类型,跟随CPU架构变换长度
当用字面量初始化时要加@
,用u
结尾。
NSNumber *numberObject2=@300u;//使用字面常量初始
<pre><code>
Point point;
point.h=100;
point.v=200;
NSValue *pointObject= [NSValue value:&point withObjCType:@encode(Point)];
//使用NSValue将struct包装为对象 再放入数组
</pre></code>
如果想放空值得话,可以用下面的方法
NSNull *nsnull = [NSNull null];
因为每一个数组元素的对象类型可以不一样,所以在取元素的时候,可能存在类型不安全
数组具有常量性:
数组的 长度 和 元素指针 都不可以被更改(一但这个数组初始化完毕后,长度和元素指针全都不可以更改) 但是元素指针所指向的对象内部可以更改
可变数组 NSMutableArray
NSMutableArray是NSArray的子类
它的初始化方式也和NSArray一样
因为是可变数组,它可以执行以下操作
[muArray1 addObject:p5];
在数组结尾增加一个元素
[muArray1 removeObjectAtIndex:2];
移除指定索引上的元素
[muArray1 insertObject:p6 atIndex:1];
在指定索引上插入元素
muArray1[0]=p7;
替换指定索引的元素
和可变字符串一样,可变数组在初始化后,会分配一个缓存容量Capacity
,一般大于实际元素数量,当实际容量大于Capacity
时,Capacity
会以两倍的方式增长。
最好的方法是在初始化初期就预估分配合理的空间给数组。
intcount=100; NSMutableArray*muArray2=[NSMutableArrayarrayWithCapacity:count];
在实际操作过程中,尽量避免使用removeObjectAtIndex:
和insertObject: atIndex:
因为会改变数组序列,涉及大量的内存拷贝操作,代价太大。
数组的遍历
<pre><code>
for( BLNPoint* point inarray5)
{
point.x++;
point.y++;
}
//快速遍历法 for-in 所有遍历方法中,速度最快,因为它直接访问内存,优化了索引检查。
</pre></code>如果不知道数组元素的类型的话,要写成id*point
或者NSObject* point
<pre><code>NSEnumerator *enumerator = [array5 objectEnumerator];
BLNPoint* item;
while(item = [enumerator nextObject])
{
item.x++;
item.y++;
}
//迭代枚举法,相比于for-in会慢一点
</pre></code>array5
上面会有一个 objectEnumerator
方法得到一个NSEnumerator
对象类型,那这个对象类型去调用 nextObject
方法 ,再把返回的值赋给 用NSEnumerator
创建的对象 item
,当没有元素的时候,他的值就是nil
,也就不会再继续while
循环。
<pre><code>for(inti=0; i<array5.count; i++)
{
NSLog(@"array5[%d],%@",i,array5[i]);
}
for循环遍历,最慢的方法。
</pre></code>
所有遍历方法中,推荐使用for-in。
数组查找:
<pre><code>BLNPoint* target=[[BLNPointalloc] initWithX:33WithY:63];
NSUIntegerindex1=[array5 indexOfObject:target];
// 在 array5 中查找是否有和 tearget 值相同的元素,并且返回索引
NSUIntegerindex2=[array5 indexOfObjectIdenticalTo:p3];
// 这个方法只能用数组内的元素名查找,并返回索引
NSLog(@"find at%lu", index1);
NSLog(@"find at
%lu", index2);
</pre></code>
数组排序:
<pre><code>NSArray* sortArray1=[array1 sortedArrayUsingSelector:@selector(compare:)];</pre></code>我们指定了一个方法 compare:
它可以实现数组里元素的比较
因为无法改变原数组,所以要取它的返回值给 sortArray1
它是通过返回一个新数组来表达排序的结果
set集合
NSSet
和数组不一样,是一个无序集合,存储的对象不能重复
被定义为class
,引用类型,拷贝时有引用语句
有常量集合NSSet
,可变集合NSMutableSet
,和数组一样。
初始化方式也和数组类似
<pre><code>NSSet*set1 =
[NSSetsetWithObjects:@"Shanghai",@"Beijing",@"New York",@"Paris", nil];
NSMutableSet*set2 =
[NSMutableSetsetWithObjects:@"Shanghai",@"Beijing",@"New York",@"Paris", nil];
</pre></code>
Capacity
set
容量⬇️
<pre><code>int count=100;
NSMutableSet*muArray2=[NSMutableSetinitWithCapacity:count];
</pre></code>
set
的一下操作
<pre><code>
[set2 addObject:@"London"];//加元素
[set2 removeObject:@"Beijing"];//移除元素
NSLog(@"set2 count:%lu", set2.count);
</pre></code>
<pre><code>
if([set2 containsObject:@"Shanghai"])//判断是否包含某个对象
</pre></code>
字典
Dictionary 字典 是一个存储Key-Value
的无序集合
Key
唯一,value
可重复
和Array
、Set
一样,
有常量字典NSDictionary
和可变字典NSMutableDictionary
。
初始化方式也和数组差不多
字面常量初始化:
value
在 Key
之前,中间用:
隔开<pre><code>
NSDictionary *dictionary1 = @{
@"Shanghai": p1,
@"Beijing": p2,
@"NewYork" : p3,
@"Paris": p4 };
</pre></code>
工厂方法:
Value
在key
之后,中间用" ,
"隔开
<pre><code>
NSMutableDictionary*dictionary2 =
[NSMutableDictionarydictionaryWithObjectsAndKeys:
p1,@"Shanghai",
p2,@"Beijing",
p3,@"New York",
p4,@"Paris",
nil];
</pre></code>
<pre><code>
BLNPoint* result1=[dictionary1 objectForKey:@"Beijing"];
BLNPoint* result2=dictionary1[@"Shanghai"];
</pre></code>可以用这种方式查询Key
来得到对应的value
。
** tip **:所有的可变集合都是其对应的一个常量集合的子类。
自动引用计数ARC
ARC 是OC的默认内存管理机制,针对堆上的对象,由编译器自动生成操作ARC指令(retina
或release
)来管理对象的创建和释放。
retina
和release
在后期的高级编程内容里会深入了解
受ARC管理的有:
- OC的对象指针
- Block 指针
- Attribute((NSObject))定义的typedef
不受ARC管理的有:
- 值类型(如C语言的结构)
- 使用其他方式分配的堆对象(如malloc)
- 非内存资源
实际上,指针本身不受ARC管理,因为ARC管理的是堆上的对象,而指针是存放在栈上的。所有ARC管理的实际上是指针所指向的那个对象。
而值类型的对象也是存放在栈上,所以也不受ARC管理。
引用计数管理
引用计数+1的操作 (retain
操作)
当将对象执行对其他对象的
- 赋值
- 传参
- 加入集合操作
引用计数-1 (release
操作)
- 全局(局部)变量被赋值为nil 或其他值
- 属性被赋值为nil或其他值
- 将对象从集合中删除
自动释放池 AutoRelease Pool
release
会导致对象立即释放,如果频繁的release
,会造成琐碎的内存管理负担。这时候,用autorelease
可以将release
的调用推迟到autorelease
被释放时。
Appkit
和 UIkit
框架在每一次事件迭代时,都会将这个代码放入autoreleasepool
中。大多数情况都不需要人为干预。
在main
函数内,编译器都会在开始加上一个autorelease
的块当程序运行到autorelease
结束时,所有引用了release
的对象都会被立即释放
<pre><code>
int main(int argc, const char * argv[]) {
@autoreleasepool{
NSLog(@"-------ARC Demo: ----------");
arcDemo();
//当函数内执行完后,会引用release,然后这个释放信号会暂存在autorelease内
NSLog(@"-------Autorelease Pool Demo: ----------");
poolDemo();
}
//当这个autorelease执行完后,就会被释放,随之存在其中的release也会被立即释放。
return0;
}
</pre></code>每一个事件处理,都是一个autoreleasepool
的建立和释放
需要手工管理autorelease pool
的情况
- 编写的程序不基于UI框架(如命令行程序)
- 在循环中创建了大量临时变量,需要提早释放,避免临时对象聚集而导致内存峰值过高。
- 在主线程之外创建了新的线程,需要自己手工添加autorelease pool 块
- 可以嵌套使用
<pre><code>
void poolDemo(){
@autoreleasepool{
for (int i = 0; i < 10; i++) {
__unused BLNRectangle *rect = [[BLNRectangle alloc]init];
}
}
}
</pre></code>
__unused
修饰符,通知编译器,如果这个变量未被使用就不参与编译(消除黄色警告团)
协议protocol
协议是一种约定,它只提供外部描述,不提供具体实现,所以只在.h文件内写,不用去写.m文件
协议里可以放
- 属性
- 实例方法
- 类方法
- 初始化器和析构器(一般不放)
但是不能放实例变量。编译器会自动生成setter和getter方法,但是不会合成实例变量。
定义协议
<pre><code>@protocol Drawable
//内容
@end
</pre></code>
使用协议
<pre><code>
@interface BLNPoint : NSObject<Drawable>//把协议名放中括号里
//内容
@end
</pre></code>
** 必选协议required **
** 可选协议optional **
协议里的成员默认都是@required
的,当一个类遵守协议时,必须要实现协议内的所有成员。.h
文件里不用去再次声明协议内的方法,只需要在.m
文件内实现就可以了。
** 但是属性必须再次声明 **。如果不声明的话,就没法自动合成实例变量。
如果要自己写一个@required的话,那个在这个@required后面的成员就是必选成员了
与之相对的@optional表示的是可选协议。他之后的成员会被认为成可选协议
如果遵守协议但是没有去实现协议方法,会出现警告运行时就会报错。
协议类型变量被赋值非协议类型对象时,会出现警告
比如这样赋值就会报错process1(@"x");
,process1
是协议类型的函数
协议可以作为变量声明类型,但是不能创建实例void process1(id<Drawable> obj)
有时不确定这个对象是否遵守了协议,那么可以用这段代码来验证这个对象背后的类型是否遵守了协议。
<pre><code>
void process2(id obj){
if([obj conformsToProtocol:@protocol(AProtocol) ]) {
[obj methodA];
}
</pre></code>
一个协议也可以继承多个协议。
实现子协议的类型,也必须实现父类协议的成员。
也可以遵守多个协议
<pre><code>
@interface ClassC : NSObject<AProtocol,CProtocol>
@end
</pre></code>
常用的协议
NSObject
:包含对象的常用操作,相等、字符串表示、哈希。
NSCopying
:支持复制的类型必须遵守该协议。
NSMutableCopying
:在NSCopying的基础上,支持复制数据的可变性。
NSFastEnumeration
:实现快速美剧for-in的类型采用该协议。
NSCoding
:支持将对象图进行编码/解码以支持对象序列化
类别与扩展
类别category
类别就不知道源代码的情况下,想给原有的类再添加了一些成员,并去实现他们
定义类
@interface BLNPoint : NSObject
定义类别
@interface BLNPoint(Drawing)
** tip **:category
的实现文件不写在class
的实现文件内,而是写在category
的实现文件内
命名规范:
文件名 == class
名+category
名
category
可以添加的成员
- 类方法
- 实例方法
- 重写基类方法
category
不能添加的成员
- 属性
- 实例变量
- 已存在的同名方法(已经被定义过的)
加了属性的话,编译能通过,但是在运行的时候就会报错(坑爹啊这是)
不能加属性,实际上是因为不能加属性背后的那个实例变量。
虽然不能加属性,但是可以去定义一个setter
和一个getter
访问器方法。
-(void)setWeight:(NSInteger)weight;
-(NSInteger)weight;
调用class
内的实例变量
适用场景:
- 在没有源代码的情况下,向已经封装的类里添加方法
- 在一些特殊场合下
- 对复杂的大型文件分割实现
都一下位置可以添加category
- 自己创建的
- 系统的
- 第三方库
扩展extension
和category
最大的区别在于,这是在有class
的源代码的情况下在class
内添加,可以看做是一个没有名字的class
,不用定义在 .h
文件,直接在需要扩展的.m
文件内定义.
<pre><code>
@interface Circle ()//加个小括号,写在.m 文件里
{
NSString* _name;
}
@property (readwrite )NSInteger radius;//修改读写属性
@property
NSIntegercenter;//添加属性
-(float)getDiameter;//实例方法
+(void)process:(Circle*) circle;//类方法
@end
</pre></code>
如果在主接口定义了文件的读写属性,那么在扩展内可以更改读写属性,
有一点需要注意的是,只能往更高的权限更高,** 不能把权限改低。 **
注意:
最重要的一点区别,扩展的成员只针对类(那个 .m
文件)内可以访问,在类外不能访问扩展!
扩展的主要用途就是信息隐藏,可以把一些外部无需访问,但是类内又要用到的成员私有化。
类的主接口用于 ** 对外公开访问 **
类的扩展用于 ** 对内访问 **