首页 > 系统 > iOS > 正文

iOS 代理和block的理解

2019-11-09 18:42:09
字体:
来源:转载
供稿:网友
首先两者作用是一样的,都是进行单一回调。不通的是,delegate是个对象,然后用过一个对象自己调用代理协议函数来完成整个流程。block是传递一个函数指针,利用函数指针执行来进行回调。还有在内存管理上需要注意,delegate不需要保存引用。block对引用数据有copy的处理。

1.block类型-存储代码块的类型

在异步编程时常需要进行函数回调,在C#中会用匿名委托或者lambda表达式讲一个操作作为参数进行传递.ObjC中是使用对于闭包的实现,在块状中我们可以持有或引用局部变量. 同时利用Block可以将一个操作作为参数进行传递;

blcok用法:

定义:返回值类型 ( ^变量名 ) ( 形参类型 );赋值:变量名=^(形参){代码块+形参变量};使用:变量(实参); 

例: 

int (^myBlcok)(int ,int)=^(int m,int n){ return m+n; }; //无参数时大括号前()可省略 myBlock(10,5); //调用块,省略了接受块返回值;

总结:经过简单了解C与OC;发现从最小的一个变量到表达式再到一个函数,其实只起两点作用: 值(返回值) 与 功能(行为,方法,作用).所以说一行代码,按它是使用了值 还是 功能来解读比较容易理解. 

Block做使用场景:

如果回调方法比较少,1~2,最好不要超过3个,这个时候使用block比较合适如果回调方法非常多,同时又不用每一个方法都必须实现,这个时候用delegate会比较方便!

block传值的循环引用问题:

只有当block直接或间接的被self持有时,在block使用self时才需要替换为weak self。如果在 Block 内需要多次 访问 self,则需要使用 strongSelf。

__weak __typeof__(self) weakSelf = self; dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ __strong __typeof(self) strongSelf = weakSelf; [strongSelf doSomething]; [strongSelf doOtherThing]; });

typedef block格式

类似函数指针,直接在定义格式之前加 typedef关键字,之后变量名就是类型的别名了.typedef viod (^别名)(形参);一般以后需要使用block作为函数方法的参数时,为方便最好用别名.而在block作用返回值时,一定需要别名,因为编译器不能识别做此时类型做何种解释.延伸:经过测试,block在编译时按代码顺序,而运行时按调用顺序(变量作用域)

使用例子:KCButton.h

#import <Foundation/Foundation.h>@class KCButton;typedef void(^KCButtonClick)(KCButton *);@interface KCButton : NSObject#pragma mark - 属性@property (nonatomic,copy) KCButtonClick onClick;#pragma mark 点击方法-(void)click;@end

KCButton.m

#import "KCButton.h"@implementation KCButton-(void)click{ NSLog(@"Invoke KCButton's click method."); if (_onClick) { _onClick(self); }}@end

main.m

KCButton *button=[[KCButton alloc]init]; button.onClick=^(KCButton *btn){ NSLog(@"Invoke onClick method.The button is:%@.",btn); }; [button click]; /*结果: Invoke KCButton's click method. Invoke onClick method.The button is:<KCButton: 0x1006011f0>. */

block访问外部变量

block内部可以访问外部局部变量,但是此时是const copy方式,地址不同,相当于值传递,只读的.如果外部定义时加前缀__block时,内部可改变外部局变值.block内部如果创建了和外部同名的变量,会屏蔽外部作用域.此时内部的变量也存在栈区;原因:block本质是代码块,ARC下创建的时候在堆区,此时代码只是单纯储存,没有功能;当调用的时候,相当于代码增加到main中,这样代码块中创建的变量就跟正常的一样; (block调用完成内部变量即释放,而堆区的只在释放block时一起释放).如果是静态变量(static修饰局变,生命周期延长,存储在数据区(同初始化的全局))和全局变量.地址传递.此时block存储在全局区.常量字符串@"abc",加__block会引用常量变量(如:a变量,a = @"abc",内部可以任意修改a 指向的内容)的地址。不加block就是@"abc"本身地址,不可变;

三种类型block

根据block在内存中的位置"NSGlobaBlock"类似函数,存于代码区--全局block"NNStackBlock"栈区,函数返回后的Block--栈"NSMallocBlock"堆block--堆block内没有使用外部变量或是只使用了全局/静态变量时.存于全局代码区,为全局block;---(ARC和MRC下一致)当使用外部变量时MRC下,block代码存于栈区;如果此外部变量A存于栈区,那么A会被copy到block分配的栈区;如果A是存于堆区,那么A在block块内与快外相同.ARC下,block代码存于堆区.如果此外部变量A存于栈区,那么A会被copy到block分配的堆区;如果A是存于堆区,那么A在block块内与快外相同.如果需要修改外部变量,需要在变量前面声明__Block;当使用下划线Block修饰外部变量时:MRC下,无论变量A存于栈还是堆区,A在block块内与快外相同;ARC下,如果此外部变量A存于栈区,那么A会被转移而不是复制到堆区;如果A是存于堆区,那么A在block块内与快外相同.

面试题:block的@property参数(内存管理参数)为什么要用copy:如果不用copy,此时不论ARC还是MRC都是栈Bolck,栈block会提前释放,导致无法继续使用;可以copy到堆区手动管理内存.(而字符串copy是防止字符串如果是非常量的,外部可变,造成非预估的结果;)

block在MRC下得内存隐患(NNStacKBlock)

Block_copy将block及内部变量拷贝到堆区.使用完毕用Blok_release(block变量)释放此堆区空间;


block使用技巧

block结构快速显示:inlineBlock...(也可右下角自定义快速显示其他格式)Block作为方法参数时,最好把参数列表部分加上,这样后面调用方法时,会自动有格式;做方法参数时,需要加上返回值类型;做返回值时,先定义别名,最后别忘记执行返回值;方法中,void(^)()表示block类型同int,做参和返回值;做实例变量@property (nonatomic, copy) void(^变量名)()get点语法获取block类型实例变量时,自动执行,后面需加();

2.protocl协议的概念及使用

在ObjC中使用@protocol定义一组方法规范. 实现此协议的类 也必须实现对应的方法. 面向对象的语言接口本身是对象行为描述的 协议规范, 也就是说ObjC中@protocol和其他语言的接口定义是类似的, 只是ObjC的@interface关键字已经用于定义类了, 因此不会像C# 和java中那样使用interface来定义接口;

1.定义:

只声明而不实现方法 ,而让遵守此协议的类实现协议声明的方法;在.h 中

@protocol 协议名 <系统协议>,<其他协议2>... //方法声明列表 @end
2.类遵守协议:

类声明文件中:

@interface 类名:父类<协议1,协议2>@end
3.实现协议:

只需在此类的.m文件中实现协议声明方法即可(根据协议定义时@option的关键字,有些必须实现,有些选择实现);

4.应用:
协议只声明多个方法,不实现, 不能定义属性;只要遵守协议,就可以拥有协议的所有方法声明;一个类遵守协议而实现的方法,声明和实现都会遗传给子类,而没有实现的方法声明会遗传给子类,即协议可以 继承 ;协议和类可以多对多,而一个协议又可以继承其他一个或多个协议.所有协议默认遵守NSObject的基协议,其中声明了很多基本方法.协议中有两个关键字:@required(默认)必须实现的. @optional 选择实现;作用域类似@public等关键字作用域

两大作用

1. 做类型限制:程序员间交流

格式:id<协议名>obj;//表明 如果要给obj赋值,则赋值的对象必须遵守"协议"才可以赋值.不满足会有警告;即id<协议名> obj=[类1 new],必须类1要遵守此协议;id 可换成类名,记得加*;引申的,在对象做实例变量时也可限制,例:@prperty Dog<协议> *dog

2.代理设计模式使两个不同意义的事物分离解耦.一种常见编程思想:有些事自己不好做,找代理做;传入的对象,代替当前类完成摸个功能;原理:对象关联关系的类型限制(A对象拥有遵守特定协议的对象B)白手套作为主子的实例变量;应用场合:

当A发生了一些行为(ˇˍˇ) 想~告诉B或B想监听对象A的一些行为 A无法处理某些行为的时候,让B帮忙;

思路:

先定义一个协议.定义代理类,遵守此协议.主类定义限定此协议类型的属性.主类中要有行为 来触发代理.

设置代理.

用父类还是id<协议>

不使用父类的原因:如果抽象一个父类的话, 还是有局限性, 因为很多时候, 不同类是无法抽象出共同的父类的. , 不能多继承

3.Category

作用:在不修改原类的情况下扩展其他功能目的:对类方法进行归类,便于分模块开发大型类,团队协作.声明:@interface 类名(分类名)实现:@implementation 类名使用原类直接调用

注意:1)分类不能扩展任何新的实例变量; 

2)分类可以访问原类的成员变量;(相当于原类方法调用) 3)如果分类中存在和原类同名方法,优先使用分类(功能更新); 4)如果多个分类都有同名方法,调用最后一个参与编译的分类同名方法;在Compile Sources 文件编译可以看到的的顺序; 
分类的非正式协议:(面试题)

就是NSObject(和系统类框架)的类别(分类)即系统类的分类,增加了所有类的功能;类别接口中指定的方法可能会或者可能不会被框架类实际地实现,而是被子类重写. 

分类的延展(匿名分类)Extendsion

延展是分类一个特例,匿名.且新添加的方法一样要予以实现

@interface MyClass () { float value; //可以新增实例变量 }-(void)setValue:(float)newValue;@end

使用:

可以在原类.h直接声明,原类.m中实现;不需要自己的.m文件;主要用于新增类的私有方法和私有变量.()
发表评论 共有条评论
用户名: 密码:
验证码: 匿名发表