Category
Category——类目,是对一个现有类的功能进行拓展,添加新的方法。其局限性在于不能添加成员变量,但可以通过runtime来添加属性。
参考文章:
OC中Category和Extension以及继承的用法和区别
深入理解Objective-C:Category
补充一点类目的作用:
1.当一个类需要多个程序员协同开发的时候,Category可以将同一个类根据用途分别放在不同的源文件中,从而便于程序员独立开发相应的方法集合。
2.在没有源代码的情况下可以用来修复BUG。
Protocol
Protocol——协议,它可以声明一些必须实现的方法和选择实现的方法。
- Protocol的作用
+用来声明一些方法
+也就说, 一个Protocol是由一系列的方法声明组成的
语法格式
- Protocol的定义
@protocol 协议名称
// 方法声明列表
@end
- 类遵守协议
- 一个类可以遵守1个或多个协议
- 任何类只要遵守了Protocol,就相当于拥有了Protocol的所有方法声明
@interface 类名 : 父类 <协议名称1, 协议名称2,…>
@end
- 示例
@protocol SportProtocol <NSObject>
- (void)playFootball;
- (void)playBasketball;
@end
#import "SportProtocol.h" // 导入协议
@interface Studnet :NSObject<SportProtocol> // 遵守协议
@end
@implementation Student
// 实现协议方法
- (void)playBasketball
{
NSLog(@"%s", __func__);
}
// 实现协议方法
- (void)playFootball
{
NSLog(@"%s", __func__);
}
@end
protocol和继承区别
- 继承之后默认就有实现, 而protocol只有声明没有实现
- 相同类型的类可以使用继承, 但是不同类型的类只能使用protocol
- protocol可以用于存储方法的声明, 可以将多个类中共同的方法抽取出来, 以后让这些类遵守协议即可
Protocol类型限制
-
设定情景:
- A希望找一个会做饭、洗衣服的女生做女朋友,有国企工作的优先。
- 满足条件的女生都可以向他发送消息
-
从题目中我们得到要求
- 会做饭
- 会洗衣服
- 有份好工作
@protocol WifeCondition<NSObject>
- (void)cooking;
- (void)washing;
- (void)job;
@end
// 如果没有遵守协议则会报警告
id<WifeCondition> wife = [[Personalloc] init];
代理设计模式
-
什么是代理设计模式
- 代理模式(Proxy)就是为一个对象创建一个替身,用来控制对当前对象的访问。目的就是为了在不直接操作对象的前提下对对象进行访问。
- 举例来说,一个人饿了,在网上订餐。
订餐平台安排送餐人员到餐馆取餐并送到他手中。
在这段时间中他继续忙其他的事情。
当送餐人员将食物送达,他就可以开始吃饭,填饱肚子。
在这个过程中,他无需知道订餐平台如何与参馆、送餐人员进行交互,只需要告诉订餐平台要吃什么并完成付款,然后等待接收送达的食品即可进行下一步操作。订餐平台就是他的代理,帮他完成购买食物的过程。
-
代理设计模式的场合:
- 当对象A发生了一些行为,想告知对象B(让对象B成为对象A的代理对象)
- 对象B想监听对象A的一些行为(让对象B成为对象A的代理对象)
- 当对象A无法处理某些行为的时候,想让对象B帮忙处理(让对象B成为对象A的代理对象)
2.代理设计模式示例
- 婴儿吃饭睡觉
// 协议
#import <Foundation/Foundation.h>
@class Baby;
@protocol BabyProtocol <NSObject>
- (void)feedWithBaby:(Baby *)baby;
- (void)hypnosisWithBaby:(Baby *)baby;
@end
#import "BabyProtocol.h"
@interface Baby : NSObject
// 食量
@property (nonatomic, assign) int food;
// 睡意
@property (nonatomic, assign) intdrowsiness;
// 饿
- (void)hungry;
// 睡意
- (void)sleepy;
@property (nonatomic, strong)id<BabyProtocol> nanny;
@end
@implementation Baby
- (void)hungry
{
self.food -= 5;
NSLog(@"婴儿饿了");
// 通知保姆
if ([self.nanny respondsToSelector:@selector(feedWithBaby:)]) {
[self.nanny feedWithBaby:self];
}
}
- (void)sleepy
{
self.drowsiness += 5;
NSLog(@"婴儿困了");
// 通知保姆
if ([self.nanny respondsToSelector:@selector(hypnosisWithBaby:)]) {
[self.nanny hypnosisWithBaby:self];
}
}
@end
// 保姆
@interface Nanny : NSObject<BabyProtocol>
@end
@implementation Nanny
- (void)feedWithBaby:(Baby *)baby
{
baby.food += 10;
NSLog(@"给婴儿喂奶, 现在的食量是%i", baby.food);
}
- (void)hypnosisWithBaby:(Baby *)baby
{
baby.drowsiness += 10;
NSLog(@"哄婴儿睡觉, 现在的睡意是%i", baby.drowsiness);
}
@end
Block
block的定义
block实际上是OC对于闭包的实现,其数据结构的定义如下:
struct Block_descriptor {
unsigned long int reserved;
unsigned long int size;
void (*copy)(void *dst, void *src);
void (*dispose)(void *);
};
struct Block_layout {
void *isa;
int flags;
int reserved;
void (*invoke)(void *, ...);
struct Block_descriptor *descriptor;
/* Imported variables. */
};
可以看到,block有以下构成:
isa 指针
:所有对象都有该指针,用于实现对象相关的功能。
flags
:用于按 bit 位表示一些 block 的附加信息。
reserved
:保留变量。
invoke
:函数指针,指向具体的 block 实现的函数调用地址。
descriptor
: 表示该 block 的附加描述信息,主要是 size 大小,以及 copy 和 dispose 函数的指针。
variables
:capture 过来的变量,block 能够访问它外部的局部变量,就是因为将这些变量(或变量的地址)复制到了结构体中。
在 Objective-C 语言中,一共有 3 种类型的 block:
_NSConcreteGlobalBlock 全局的静态 block,不会访问任何外部变量。
_NSConcreteStackBlock 保存在栈中的 block,当函数返回时会被销毁。
_NSConcreteMallocBlock 保存在堆中的 block,当引用计数为 0 时会被销毁。
当开启ARC时(一般都是),将只会有 NSConcreteGlobalBlock 和 NSConcreteMallocBlock 类型的 block。原本的 NSConcreteStackBlock 的 block 会被 NSConcreteMallocBlock 类型的 block 替代,可能是由于ARC能够自动管理生命周期,去掉之后比较方便实现吧。
Block的使用
/** 直接使用
* returnType : 返回值类型
* name : block命名
* arguments : 参数类型
* parameters : 参数
*/
<#returnType#> (^<#name#>) (<#arguments#>) = ^ (<#parameters#>) {
<#statements#>
};
/// 声明成别名使用
typedef <#returnType#>(^<#name#>)(<#arguments#>);
/// 作为属性
@property (copy, nonatomic) <#returnType#>(^<#name#>)(<#arguments#>);
@property (copy, nonatomic) <#name#> block;
为什么作为属性时要使用copy来修饰Block?
block本身是像对象一样可以retain,和release。但是,block在创建的时候,它的内存是分配在栈(stack)上,而不是在堆(heap)上。他本身的作于域是属于创建时候的作用域,一旦在创建时候的作用域外面调用block将导致程序崩溃。
使用retain也可以,但是block的retain行为默认是用copy的行为实现的,
因为block变量默认是声明为栈变量的,为了能够在block的声明域外使用,所以要把block拷贝(copy)到堆,所以说为了block属性声明和实际的操作一致,最好声明为copy。
Block对外部变量的引用
对于 block 外的变量引用,block 默认是将其复制到其数据结构中来实现访问的,如下图所示:

对于 __block 修饰的外部变量引用,block 是复制其引用地址来实现访问的,如下图所示:

如果需要在Block中修改外部变量的值,那么需要将它用 __block 来修饰(编译器会自动提示,fix一下就好了)。
关于保留环:
在使用Block时,如果不注意,很容易导致“保留环”(retain cycle)。
解决方法是使用强指针与弱指针搭配。
- (void)viewDidLoad {
[superviewDidLoad];
//通过定义弱指针,解决保留环的问题
__weaktypeof(self) weakSelf = self;
dispatch_async(dispatch_get_global_queue(0,0), ^{
//通过定义强指针,解决在block调用完之前,self被释放掉了,使得block的调用无效的问题
__strongtypeof(self) strongSelf = weakSelf;
NSLog(@"%@",strongSelf.string);
});
}
需要注意的是,类似UIView的block块动画这样的代码使用Block时,是不会产生保留环的,因为类对象使用Block时,Block仅仅作为方法的参数存在于方法内部,所以类对象没有持有这个Block
具体参考以下文章:
block应用与保留环
iOS开发中weak和strong的用法和错误示例
参考文章:
谈Objective-C block的实现
网友评论