OC基础(七)OC中的block、协议、代理、分类
block
虽然很早就学习过block方面的知识,但一直没怎么用过,说明自己还是个菜鸟中的菜鸟,看到大牛们的代码优雅又简洁,各种炫酷的功能很多都有block的影子,所以下决心好好学习这方面的内容.以后有关于block中的所有问题都会总结在这里,方便加深对OC中block的理解,也为学习swift的闭包打下基础.
block是一个数据类型
- block是一个数据类型 所以可以声明一个block类型的变量
- block类型的变量中专门存储一段代码,这段代码可以有参数和返回值
block变量的声明
-
格式:
返回类型(^block变量的名称)(参数列表);
例如:void (^myBlock1)();
表示声明了一个block类型的变量叫做myBlock1 这个变量中只能存储没有返回值没有参数的代码段
int(^myBlock2)(int num1,int num2);
同理 -
注意:声明block变量的时候要指定这个block可以存储的代码段的返回值和参数描述,一旦指定,这个block就只能存储这样的代码段了,其他格式代码段无法存储.
初始化block
- 写一个符合block要求的代码段,存储到block变量中就可以
- 代码段格式
^返回值类型(参数列表){ 代码段; };
例如:^void(){ NSLog(@"I love you"); };
这是个无参无返回值的代码段 - 声明block变量的同时初始化
void (^myBlock)() = ^void() { NSLog(@"I love you"); };
- 执行代码段
myBlock();
block简写
- 没有返回值或参数,代码段的返回值void/参数()都可以省略,声明不可省略
void(^myBlock)() = ^{ NSLog(@"I love you"); };
- 声明block时候,如果有指定参数,声明的时候可以只写类型不写参数名称
int (^myBlock3)(int,int) = ^int(int num1,int num2) {
int num3 = num1 + num2;
return num3;
};
- 无论代码段是否有返回值,在定义代码段的时候都可以省略.如果在写代码段的时候省略了返回类型,系统会自动确定返回值类型.
int(^myBlock4)(int,int) = ^(int num1,int num2) { int num3 = num1 + num2; return num3;
};
5. 用typedef简化block变量的复杂定义,将一个长类型定义为一个短类型
typedef void(^NewType)();
NewType block1;
NewType block2;
NewType block3;
>#### 关于block块访问外部变量的问题
* 在block代码块的内部可以`取得`定义在外部变量的值,定义在外部的局部变量和全局变量
* 但是只能修改全局变量额值,不能修改定义在外部的局部变量的值
![block内部无法修改定义在外部的局部变量的值](http:https://img.haomeiwen.com/i4105709/43b0b18c89da8740.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
* 如果想修改,就加一个__block 来修饰即可
>#### block作为函数的参数
1. 基本:
void test(void(^block)()) {
NSLog(@"~~~~~~~~");
block();//这个block可以用typedef来定义,会更清晰
NSLog(@"~~~~~~");
}
运行的时候 :
先声明一个block
void(^myBlock1)() = ^{
NSLog(@"哈哈");
};
//再调用函数的时候传入这个block;
test(myBlock1);
也可以直接调用,写在函数中
test(^{
NSLog(@"哈哈");
});
2. **终极复杂block作为参数的调用**
定义的时候:
void test2(int(^block)(int num1,int num2)) {
NSLog(@"第一步,执行这个函数体内部自己的方法");
NSLog(@"甚至可以得出两个值");
//传给block中的参数,并执行这个block
int num3 = block(2,3);
//再使用这个block的返回值.
//这种传值方式虽然很绕,但是很酷.
NSLog(@"%d",num3);
}
使用时
//在使用这个函数的时候,直接提前写好他的参数--一个代码段.并且写出如何得到代码段的返回值,好让函数在内部的时候使用这个返回值.
test2(^int(int num1, int num2) {
return num1 + num2;
});
block当做函数参数传递的效果:
可以将调用者自己写的一段代码传递到函数中去执行!
3. oc中block作为方法的参数与在函数中差不多,只是个别写法不同而已.
例如:
//定义方法
-(void)ocTestWithBlock:(int (^)(int a,int b))block {
int c = block(1,2);
}
//实现方法
[self ocTestWithBlock:^int(int a, int b) {
return a-b;
}];
可以看出,唯一的不同是在定义方法时,block名称不在^后面,而是定义在整个参数类型的后面.
跟`(NSString *)name` 是一样一样的.
总结: 什么时候block可以作为方法/函数的参数?
当方法内部需要执行一个功能,但这个功能具体的实现函数内部不确定,这个时候就用block让调用者将这个功能的实现自己传递进来.
>#### block作为函数的返回值
例子
如果将block作为函数的返回值时,用typedef来简写
![屏幕快照 2017-03-11 下午3.35.53.png](http:https://img.haomeiwen.com/i4105709/e6f00cf1416ec780.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
>#### block与函数的异同
1. 相同点
都是封装一段代码
2.不同
* block 是一个数据类型,本质上与函数不同
* block 可以作为函数参数,但函数不可以.
### 协议
##### 协议:protocol.
作用:
1). 专门用来声明一大堆方法. (不能声明属性,也不能实现方法,只能用来写方法的声明).
2). 只要某个类遵守了这个协议.就相当于拥有这个协议中的所有的方法声明.而不用自己去定义.
##### 协议的声明.
@protocol 协议名称 <NSObject>
方法的声明;
@end
新建1个协议的方式. NewFile OC-File - protocol
协议的文件名: .h 并且只有1个.h文件.
在协议中,只能用来声明方法,协议的作用:就是专门用来写方法声明的.
##### 类遵守协议.
协议就是用来写方法声明的,就是用来被类遵守的.
如果想要让1个类,拥有协议中定义的所有的方法声明.那么就让这个类遵守这个协议.
类只要遵守1个协议,那么这个类就拥有了这些协议中定义的所有的方法的声明了.
@interface 类名 : 父类名 <协议名称>
@end
: 表示继承.
<> 表示遵守的协议.
当1个类,遵守了1个协议,那么就相当于这个类拥有了协议中定义的所有的方法的声明.
这个类只是拥有了这个协议中的方法的声明而已. 没有实现.所以 这个类,就应该实现协议中的方法.
如果类不实现协议中的方法,其实也不会报错.编译器只是会报警告.
但是当创建对象,来调用这个没有实现的协议中的方法的时候,就会报错.
##### 类是单继承. 但是协议可以多遵守.
1个类只能有1个父类
但是1个类可以同时遵守多个个协议.
@interface 类名 : 父类名 <协议名称1,协议名称2......>
@end
当1个类遵守了某个协议.就相当于这个类拥有了这份协议中的所有的方法的声明.
但是仅仅只是有方法的声明而已,没有是实现,要类自己去实现.
如果类不实现.编译器不会报错. 只是给1个警告.
当我们创建对象,如果不调用这个协议方法,就不会报错.
如果要调用这个协议方法,那就会报错.
##### @required 与 @optional
@required 与 @optional这两个修饰符是专门用来修饰协议中的方法的.
在协议中,如果方法的声明被@required修饰,那么遵守这个协议的类必须要实现这个方法,否则编译器会发出警告.
在协议中,如果方法的声明被@optional修饰,那么遵守这个协议的类如果不实现这个方法.编译器也不会报警告.
其实,无论是@required还是@optional你都可以不实现. 编译器是不会报错的. 仍然可以编译 运行.
唯一的区别就是: 当遵守协议的类不实现协议中的方法的时候,@required会给1个警告. @optional警告都木有.
这两个关键字的主要作用:在于**程序员沟通**,告诉遵守协议的类 哪些方法是必须要实现的,
因为这些方法我会调用.
**默认的是@required**
##### 协议可以从另外1个协议继承,并且可以多继承.
协议可以继承另外1个协议. A 协议 继承了 B协议. 那么A协议中不仅有自己的方法的声明,还有B协议中的方法的声明.
如果有1个类遵守了A协议,那么这个类就拥有了, A、B协议中的所有的方法的声明.
协议之间继承的语法格式
@protocol A协议名称 <B协议名称>
@end
代表A协议继承自B协议, A协议中既有自己的方法声明,也有B协议中的方法声明.
NSOBject: 这是1个类. 是所有的OC类的基类. 这个类是苹果早就定义好得.
NSOBject: 这也是1个协议. 也是苹果早就定义好得. 这个协议被NSObject类遵守.
所以,所有的OC对象都拥有这个协议中的所有的方法.
这个协议我们也叫做**基协议.**
写协议的规范: 任何1个协议,必须要间接的或者直接的去遵守这个NSObject基协议.
协议的名称可以和类的名称相同:
##### @protocol类型限制.
1). 要求某个指针保存的是遵守指定协议的对象.
`NSObject<myProtocol> *obj;`
`id<myProtocol> *obj;`
2). 要求某个指针变量保存的是继承了某个类,并遵守了指定协议的对象.
3). 属性案例: 男孩子的女朋友.
4). 为什么要求对象遵守协议?
因为我要调用对象的这个方法 你只有遵守了这个协议才有这个方法.
5). 协议与继承,
8.案例: 婴儿饿了要哭 困了要睡 保姆开照顾. 老师也能当保姆.
### 协议的基本使用
##### 协议与协议之间可以相互继承.
1). 继承的语法:
@protocol 协议名称 <父协议名称>
@end
2). 效果:
子协议中不仅有自己的方法的声明,还有父协议中的所有的方法的声明.
如果1个类遵守了某份协议,那么这个类就拥有这个协议和这个协议的父协议中的所有的方法声明.
###### 介绍1个东西. NSObject
在Foundation框架中,有1个类 叫做NSObject 是所有OC类的基类.
在Foundation框架中,有1个协议.叫做NSObject.
NSObject协议被NSObject类遵守.所以,NSObject协议中的所有的方法 全部的OC类都拥有了.
这么说,所有的OC类都遵守了NSObject协议. NSObject协议叫做基协议.
类的名称可以和协议的名称一致.
1. 写协议的规范:
要求所有的协议都必须直接的或者间接的从NSObject基协议继承.
### 协议的类型限制
##### 请声明1个指针.这个指针可以指向任意的对象,但是要求指向的对象要遵守指定的协议.
如果不遵守 最起码要报1个警告.
要求声明1个指针 指向1个遵守了学习协议的对象, 否则最起码要给哥哥1个警告.
NSObject<协议名称> *指针名;
这个时候,这个指针可以指向遵守了指定协议的任意对象. 否则就会报1个警告.
NSObject<StudyProtocol> *obj = [Student new];
当然了完全也可以使用id指针.
id<协议名称> 指针名;
id<StudyProtocol> id1 = [Student new];
##### 声明1个指针变量,要求这个指针变量指向的对象必须遵守多个协议.
`NSObject<StudyProtocol,SBProtocol> *obj1 = [Student new];`
`id<StudyProtocol,SBProtocol> obj1 = [Student new];`
1). 遵守了某个协议的类,就相当于这个类拥有了这个协议所定义的行为.
2). 因为我要调用这个对象中的协议方法.
只有类遵守了协议,这个类中一定才会有协议方法.
### 代理设计模式
##### 什么是代理模式.
传入的对象,代替当前类完成了某个功能,称为代理模式.
##### 利用协议实现代理模式的主要思路.
1). 定义1个协议.里面声明代理类需要实现的方法列表. 比如这里的1个代理类需要实现wash cook方法.
2). 创建1个代理类(比如猪猪) 遵守上面的代理协议 并实现方法
3). 在需要代理的类中,定义1个对象属性 类型为id 且遵守代理协议的属性.
4). 在代理的类中,调用代理对象的方法.
**代理模式:**
有1个对象中有1个属性, 这个属性的可以是任意的对象,但是这个对象必须具有指定的行为.
这个时候就可以使用协议.
将行为定义在代理之中.
对象的属性的类型 id<协议>
只要遵守了这个协议的对象都可以作为这个类的代理.
##### 代理设计模式的场合
1). 当对象A发生了一些事情,想告知对象B 让对象B成为对象A的代理.
2). 对象B想监听对象A的一些行为. 让B称为A的代理,
3). 当对象A无法处理某些场景的时候,想让对象B帮忙处理.
婴儿饿了就会哭 哭的时候要有1个人喂他吃奶.
困了就要睡.困的时候就要1个人去哄他睡觉.
用代理设计模式.为婴儿找1个可以照顾它的人.
Baby
属性:
姓名
年龄.
照顾他的人.
行为:
哭
吃奶
睡觉
犯困的行为.
可以照顾这个小孩子的人要求:
喂奶 哄孩子睡觉.
### 分类
##### 分类.
类别、类目、category
##### 写1个学生类:类中有很多个方法.
吃 喝 拉 撒 睡.... 基本行为
学习、敲代码、写书.... 学习
玩Dota 玩LOL 玩CF.... 玩
爬山、跑步、踢足球..... 运动
......
如果将这些方法都写在同1个类模块中.当然完全是可以的.
如果全都写在一个模块中,就显的很臃肿. 后期难以维护和管理.
默认情况下1个类独占1个模块.这个是将所有的成员都写在这1个模块中.就很难管理.
我们的想法: 那就让1个类占多个模块.将功能相似的方法定义在同1个模块中.
这样的好处: 方便维护和管理.
如何将1个类分成多个模块呢?
##### 分类:
1). 顾名思义: 将1个类分为多个模块.
2). 如何为1个类添加分类.
3). 会生成1个.h 和1个.m的模块.
a. 模块的文件名: 本类名+分类名.h 本类名+分类名.m
4). 添加的分类也分为声明和实现.
@interface 本类名 (分类名)
@end
代表不是新创建1个类.而是对已有的类添加1个分类. 小括弧中写上这个分类的名字.
因为1个类可以添加多个分类 为了区分每1个分类.所以分类要取名字.
@implementation Student (分类名)
@end
这是分类的实现.
##### 分类的使用.
如果要访问分类中定义的成员,就要把分类的头文件引进来.
##### 分类的作用: 将1个类分为多个模块.
##### 使用分类注意的几个地方:
1. **分类只能增加方法,不能增加属性**
2. 在分类之中可以写@property 但是不会自动生成私有属性. 也不会自动生成getter setter的实现.
只会生成getter setter的声明.
所以,你就需要自己写getter 和 setter的声明. 也需要自己定义属性 这个属性就必须在本类中.
3. **在分类的方法实现中不可以直接访问本类的真私有属性(**定义在本类的@implementation之中)
但是可以调用本类的getter setter来访问属性.
本类的@property生成的私有属性,只可以在本类的实现中访问.
分类中不能直接访问私有属性/真私有属性.
分类可以使用 getter setter 来访问.
4. 分类中可以存在和本类同名方法的.
当分类中有和本类中同名的方法的时候,**优先调用分类的方法**.哪怕没有引入分类的头文件.
如果多个分类中有相同的方法,优先调用**最后编译的分类**.
什么时候需要使用分类.
当1个类的方法很多很杂的时候. 当1个类很臃肿的时候.
那么这个时候我们就可以使用分类. 将这个类分为多个模块.将功能相似的方法写在同1个模块之中.
### 非正式协议
1. 分类的作用在于可以将我们写类分为多个模块.
可以不可以为系统的类写1个分类呢?
**为系统自带的类写分类** 这个就叫做非正式协议.
2. 分类的第2个作用:
为1个已经存在的类添加方法.
1. NSString类都挺好的. 就是差了1个方法.这个时候就可以给他写个分类
**所以现在有两个分类的作用**
1). **将臃肿的类分为多个模块 方便管理.**
2). **扩展1个类.
### 延展
##### Extension概念
1). 是1个特殊的分类. 所以延展也是类的一部分.
2). 特殊之处:
a. 延展这个特殊的分类没有名字.
b. 只有声明没有实现.和本类共享1个实现.
##### 延展的语法
语法;
@interface 本类名 ()
@end
没有实现. 和本类共享1个实现.
##### 为类添加延展的步骤
只有1个.h文件. 文件名称: 本类名_取得文件名.h
这个文件中只有延展的声明.
`@interface Person ()`
`@end`
##### 延展的基本使用.
1). 延展的本质是1个分类. 作为本类的一部分.
只不过是1个特殊的分类
没有名字.
2). 延展只有声明,没有单独的实现. 和本类共享一个实现.
##### 延展和分类的区别
1). 分类有名字.延展没有名字 是1个匿名的分类.
2). 每1个分类都有单独的声明和实现. 而延展只有声明 没有单独的实现 和本类共享1个实现,
3). 分类中只能新增方法. 而延展中任意的成员都可以写.
4). 分类中可以写@property 但是只会生成getter setter的声明.
延展中写@property 会自动生成私有属性 也会生成getter setter的声明和实现.
##### 延展的应用场景.
1). 要为类写1个私有的@property.
生成getter、setter方法只能在类的内部访问 不能在外部访问.
其实,我们可以想: @property生成私有属性、生成getter setter的实现,不要声明.
2) **延展100%的情况下不会独占1个文件. 都是将延展直接写在本类的实现文件中.**
这个时候,写在延展中的成员,就相当于是这个类的私有成员.只能在本类的实现中访问.
外部不能访问.
3). 什么时候使用延展?
当我们想要为类定义私有成员的时候,就可以使用延展. 将延展定义在这个类的实现文件中.
如果想要为类写1个真私有属性,虽然我们可以定义在@implementation之中.但是不要这么写 这样很不规范.
写1个延展.将这个私有属性定义在延展中.
如果要为类写1个私有方法,建议将声明写在延展中, 实现写在本类的实现中. 提供代码的阅读性
如果想要为类写1个私有的@property 就直接写在延展就可以了.
4). **延展天生就是来私有化类的成员的.**
如果类的成员只希望在类的内部访问,那么就将其定义在延展中.
如果类的成员允许被外界访问 定义在本类的@interface中.
**至此,成员变量书写的位置最终版本确定。