美文网首页
【OC梳理】Category、Protocol、Block

【OC梳理】Category、Protocol、Block

作者: 忠橙_g | 来源:发表于2017-11-20 17:46 被阅读50次

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的实现

相关文章

网友评论

      本文标题:【OC梳理】Category、Protocol、Block

      本文链接:https://www.haomeiwen.com/subject/gvvjvxtx.html