本节来学习一下OC中对基本数据类型以及代码块的封装。
1、Block封装
在OC中,采用Block代码块来对c语言函数进行封装,以扩充c函数的适用范围,我们用c语言定义一个sum函数:
在OC中,采用Block代码块来对c语言函数进行封装,以扩充c函数的适用范围,我们用c语言定义一个sum函数:
int sum(int a, int b){
return a + b;
}
这是一个标准的c函数,包含了输入参数和返回类型,我们看看Block封装后的样子:
int (^sumBlk)(int,int)=^(int a, int b){
return a + b;
}; // 留意这里的分号
在这里我们创建了一个名为sumBlk的代码块,他也能接收两个参数,并具有返回类型,无参数的代码块怎么定义呢?像下面这样:
void (^helloWorldBlk)()=^{ // 这里的小括号不能省略
NSLog(@"Hello World!");
};
对于Block代码块,采用和c函数相似的调用方式:
int a = sumBlk(10,20); // 含参Block
helloWorldBlk(); // 无参Block
我们这么大废周折的把函数封装起来,这样有什么意义呢?
a、首先要了解的是,Block代码块本身是一个OC对象,遵循OC的垃圾回收机制,有利于存储空间的分配和管理;
b、c函数使用上一般是和主函数并列的,而Block代码块可以直接在主函数或者其他函数中使用的,可以访问函数体内代码块之外的变量,如下所示:
int main()
{
int a=1;
__block int b=1;
void (^getBlk)() = ^{
int c = a; // 可以访问外部变量
int d = b;
// a = 2; // 报错,不可修改外部变量
b = 2; // 外部__block类型可以修改
};
return 0;
}
在Block内部不能修改外部的变量(__block修饰的除外),只可以访问,在代码块内部实际上是copy了一个const类型的变量,这保证了代码块的独立性,是面向对象的一大特点。
c、Block代码块可以作为另一个代码块或者方法、函数等的参数使用,如下所示:
// 定义blk_type类型Block
typedef void (^blk_type)();
blk_type blk = ^(blk_type blk){
NSLog(@"This is a block!");
};
// 定义含有blk1_type类型参数的Block
void (^blk1)(blk_type)=^(blk_type blk){
// 共用代码
blk();
// 公用代码
};
// 通过blk2调用blk1类型Block
blk1(blk);
// 通过blk2调用一段代码
blk1(^{
// 自定义代码
});
在代码编写过程中,我们会遇到一类代码,他们开头和结尾相同,中间的部分不同,我们通过上面的方式进行代码块的嵌套,可以实现将公共方法提取出来,形成一个单独的模块,然后传入自定义代码块的以实现不同的功能。如Foundation框架中的设置动画的代码:
// 公共代码
[UIView beginAnimations:nil context:nil];
[UIView setAnimationDuration:speed];
// 动作的具体内容
// 公共代码
[UIView commitAnimations];
我们就可以将公共部分提取出来,形成一个方法,再根据具体的情况进行调用:
- (void)actWithBlock:(void (^)())block atSpeed:(NSTimeInterval)speed{
// 公共代码
[UIView beginAnimations:nil context:nil];
[UIView setAnimationDuration:speed];
// 传入代码块
block();
// 公共代码
[UIView commitAnimations];
}
这样以来,我们创建类似的代码片段的时候,只需调用该方法即可:
[MyViewController actWithBlock:^{
// 自定义代码
} atSpeed:kRotateSpeed];
2、NSArray和NSMutableArray
在c语言中我们采用数组来保存类型相同的多个对象,多个对象也可以通过数组的方式来管理,如下:
NSObject *obj[3];
obj[0] = [NSObject new]; // 元素可以是不同类型
obj[1] = [NSObject new]; // 元素可以是不同类型
obj[2] = [NSObject new]; // 元素可以是不同类型
我们也可以采用OC数组的方式来管理(元素也可以不同):
// 方式① 注意最后需要一个nil参数
NSArray *arr1 = [[NSArray alloc] initWithObjects:obj1, obj2, obj3, nil];
// 方式② 简化方式
NSArray *arr2 = @[obj1, obj2, obj3];
NSArray已经创建之后是不能修改的,他可以采用以下的方式进行读取:
// 方式① 方法
[arr1 objectAtIndex:2];
// 方式② 简化
arr1[2];
NSArray本身是一个对象,遵循对象的的内存管理,并具有一些对象方法,可以方便我们对对象的操作。例如:
// 比较两个对象是否相同
[arr1 isEqualTo:arr2];
// 获取内部数据的index值
[arr1 indexOfObject:obj2];
// 是否包含对象
[arr1 containsObject:obj3];
方法还有很多,在这里不一一介绍。
NSMutableArray类是NSArray的一个子类,他在NSArray的基础上,扩展了内部成员可变的特性:
NSMutableArray *marr1 = [[NSMutableArray alloc] init];
// 添加对象
[marr1 addObject:obj1]; // obj1
// 通过NSArray添加
[marr1 addObjectsFromArray:arr1]; // obj1 obj1 obj2 obj3
// 插入对象
[marr1 insertObject:obj3 atIndex:1]; // obj1 obj3 obj1 obj2 obj3
// 交换
[marr1 exchangeObjectAtIndex:1 withObjectAtIndex:3]; // obj1 obj2 obj1 obj3 obj3
// 删去
[marr1 removeObject:obj1]; // obj2 obj3 obj3
NSMutableArray不能使用@[]的方式进行简化,但是可以使用[index]的方式来访问,NSMutableArray和NSArray之间可以相互转化:
// 通过NSArray创建
NSMutableArray *marr2 = [NSMutableArray arrayWithArray:arr1];
// copy自NSMutableArray
NSArray *arr3 = [marr2 copy];
很明显,采用NSMutableArray有其可以更改的优势,但是相应的要占有更多的资源,在一些不需要改动的场合,采用NSArray更为合适,有一点需要注意的是,这两个方法都可以copy自己,NSMutableArray复制出的是一个NSArray,而NSArray只是复制出一个指针,因为他本身不能变化,也就没必要复制出新的对象。
我们可以采用下面的方式来遍历OC数组的每一个元素:
for (id obj in arr1) {
NSLog(@"%@", obj);
}
这个方式的优点是不需要知道OC数组内部有多少数据。
3、NSSet和NSMutableSet
NSSet是一种特殊的OC数组,他的特点是在各个元素的存储不连续,没有相对关系,采用hash方式(散列算法)来压缩存储元素,保证一个Set里不存在两个相同的对象,同时,该方式另一个好处是内部元素的检索速度很快。
// NSSet的创建
NSSet *set1 =[NSSet setWithObjects:obj1, obj2, obj3, nil];
NSSet *set2 =[NSSet setWithArray:arr1];
NSSet *set3 = [NSSet setWithSet:set1];
NSSet对象内部的元素不能直接引索,他有以下几种使用方法:
// 提取所有元素(返回一个NSArray)
[set1 allObjects];
// 任意取一个
[set1 anyObject];
// 判断是否包含对象
[set1 containsObject:obj1];
// 判断是否是另一个Set的子集
[set1 isSubsetOfSet:set2];
// 判断是否包含另一个Set
[set1 intersectsSet:set2];
NSMutableSet是NSSet的子类,他也具有可以任意修改的特点,创建和使用如下:
// 创建可变set
NSMutableSet *mset1 = [NSMutableSet setWithArray:arr1]; // obj1 obj2 obj3
NSSet *set4 = [NSSet setWithObject:obj2];
// 两个set合并
[mset1 unionSet:set4]; // obj1 obj2 obj3 (不变)
// 两个set相减
[mset1 minusSet:set4]; // obj1 obj3
// 求交集
[mset1 intersectsSet:set4]; // obj2
// 删除元素
[mset1 removeObject:obj2]; // 空
[mset1 removeAllObjects]; // 全部删除
Set数据也可以进行元素的遍历:
for (id obj in mset1) {
NSLog(@"%@", obj);
}
4、NSDictionary和NSMutableDictionary
OC字典是对c字典的封装,我们直接看代码:
// 方式① 创建
NSDictionary *dic1 = [NSDictionary dictionaryWithObjects:@[obj1, obj2, obj3] forKeys:@[@"o1", @"o2", @"o3"]];
// 方式② 创建
NSDictionary *dic2 = @{@"o1": obj1,
@"o2": obj2,
@"o3": obj3};
字典也可以通过一个外部的文件创建,我们会在后面学习,调用:
[dic1 objectForKey:@"o1"];
dic1[@"o1"];
NSMutableDictionary是NSDictionart的子类,他可以动态添加删除字典元素:
// 通过NSDictionary创建
NSMutableDictionary *mdic1 = [NSMutableDictionary dictionaryWithDictionary:dic1];
// 添加,如果key相同则是覆盖
[mdic1 setObject:obj3 forKey:@"o4"];
OC字典也可以遍历内部元素:
for (id obj in dic1) {
NSLog(@"%@", obj);
}
需要注意的是,OC字典内部的元素是处于无序状态的,他们在存储上,没有先后顺序,这一点和OC数组不同,需要注意。
4、NSString和NSMutableString
NSString在前面已经接触过了,他是OC对c字符串的封装,他也是不可以变化的,创建方式如下:
// 普通创建
NSString *str1 = [NSString stringWithFormat:@"I am %d years old!", 10];
// 简化创建
NSString *str2 = @"Happy new year!";
NSMutableString也是NSString的子类,他是可以变化的,因此:
// 创建
NSMutableString *mstr1 = [NSMutableString stringWithString:str1]; // I am 10 years old now!
// 尾部添加
[mstr1 appendString:str2]; // I am 10 years old now!Happy new year!
// 删除
NSRange r1 = [mstr1 rangeOfString:@" now"];
[mstr1 deleteCharactersInRange:r1]; // I am 10 years old!Happy new year!
// 修改
NSRange r2={17,1};
[mstr1 replaceCharactersInRange:r2 withString:@", "]; // I am 10 years old, Happy new year!
// 更换
[mstr1 setString:@"a oc string"]; // a oc string
使用OC字符串,相比c字符串还有很多实用的功能,我们在用到的时候再学习。
5、NSValue和NSNumber
NSValue类是OC语言对结构体或其他结构数据的封装,实现方式如下:
// 定义结构体类型 Student
typedef struct {
char *name;
int age;
} Student;
// 创建结构体 stu1
Student stu1 = {"Xiaoming", 18};
// 结构体封装
NSValue *v1 = [NSValue value:&stu1 withObjCType:@encode(Student)];
// NSValue *v2 = [NSValue valueWithBytes:&stu1 objCType:@encode(Student)]; // 效果一样
// 创建 stu2 结构体用来接收数据
Student stu2;
// 将数据取出到stu2
[v1 getValue:&stu2];
// 访问
NSLog(@"%s", stu2.name);
NSLog(@"%d", stu2.age);
NSNumber是NSValue的子类,可以存储普通数据类型,我们看看怎么创建NSNumber对象:
// 通过c基本数据类型创建数字对象
NSNumber *n1 = [NSNumber numberWithBool:YES];
NSNumber *n2 = [NSNumber numberWithChar:'A'];
NSNumber *n3 = [NSNumber numberWithFloat:1.2345f];
NSNumber *n4 = [NSNumber numberWithInt:12];
NSNumber对象一经创建,是无法修改的,我们可以通过以下的方式来读取:
n1.intValue;
n1.charValue;
n1.floatValue;
n1.stringValue;
利用这些方法,可以实现类似类型转换的功能,其中stringValue可以将一个数值转化为字符串对象。
在OC中,我们把c数据类型封装成为对象,就可以按照对象的方式来处理,比如放在NSArray、NSDictionary等中,方便数据的处理。