代理详解

简介: 本文纯属个人观点,如有错处,敬请指正,不胜感激。我们不管是在项目中,还是在面试过程中,总是免不了被问及循环引用的话题,这还是要归于ios的内存管理机制——引用计数。

本文纯属个人观点,如有错处,敬请指正,不胜感激。

我们不管是在项目中,还是在面试过程中,总是免不了被问及循环引用的话题,这还是要归于ios的内存管理机制——引用计数。而循环引用常常发生的情景其中之一就是代理和block。这里重点说说代理。

一、代理解决什么问题

场景:
有一个婴儿,他要吃饭才能维持生存,但是,他自己并不能照顾自己。有一个育儿师,她有照顾婴儿的能力,但是她缺少收入。这个时候,婴儿和育儿师之间可以达成协议,婴儿委托给育儿师照顾,婴儿付出报酬。

提取关键词: 婴儿(委托方)、育儿师(代理方)、协议

我们可以根据这个场景,设计程序。

先来定义协议:BabyDelegate.h

协议是声明了一组方法的文件。协议只提供方法的名字,不提供实现。这个跟生活中的协议也是很形象的。协议就是一个文件,这个文件既不会干活儿,也不会发报酬,它就是一个约定。方法相等于协议中的条款。有些方法是必须实现的,有些是可以实现可以不实现的。这都要视具体情况而定。

#import <Foundation/Foundation.h>

@protocol BabyDelegate <NSObject>

-(void)takeCareOfBaby;

@end

\

Baby类

Baby.h:


#import <Foundation/Foundation.h>
#import "BabyDelegate.h"

@interface Baby : NSObject
//遵循协议的成员变量
 @property(nonatomic,weak)id<BabyDelegate> delegate; 

-(void)eat;

@end

Baby.m

#import "Baby.h"

@implementation Baby

-(void)eat{
//因为该成员变量遵循了协议,所以这里可以调用协议中声明的方法。
//至于方法有没有实现,编译阶段是不关心的,只有运行阶段才会知道。
//一旦方法没有实现,会造成崩溃。所以这里需要加上判断最好。
   if ([_delegate respondsToSelector:@selector(takeCareOfBaby)]) {
        [_delegate takeCareOfBaby];
    }
}

@end

Baby类已经构建完成了。下面是BabySitter类。

BabySitter.h

#import <Foundation/Foundation.h>
#import "BabyDelegate.h"

//注意这里:要想实现协议中的方法,一定要遵守协议
@interface  BabySitter : NSObject<BabyDelegate>

@end

BabySitter.m

#import "BabySitter.h"

@implementation BabySitter

//实现协议中声明的方法
-(void)takeCareOfBaby{
    NSLog(@"照顾小宝宝");
}

@end

注意观察,Baby、BabySitter以及协议BabyDelegate之间的关系。

到目前为止,Baby、BabySitter分别跟BabyDelegate建立了联系,但是Baby和BabySitter还没有任何的联系。协议的三方都有了,最后就是协议的触发了。

- (void)viewDidLoad {
    [super viewDidLoad];
    
    //创建一个育儿师对象
    BabySitter * xiaohong = [[BabySitter alloc] init];
   //创建一个婴儿对象
    Baby * xiaoming = [[Baby alloc] init];
  //重点来了,把婴儿和育儿师建立联系
    xiaoming.delegate = xiaohong;
  //此时,婴儿可以吃饭了,育儿师也可以拿到报酬了
    [xiaoming eat];
}

在执行 [xiaoming eat]; 这句代码的时候,执行过程是这样的:

1.先到Baby类中查看是否有eat方法,发现有,进入eat方法内部执行。
2.在eat方法体内部,是一个代理。这个代理的类是xiaohong,也就是babySitter类,那么就去判断该代理中的takeCareOfBaby方法是否在babySitter中进行了实现。如果实现了,[_delegate respondsToSelector:@selector(takeCareOfBaby)]返回true,执行[_delegate takeCareOfBaby]。
3.现在[_delegate takeCareOfBaby]等同于[xiaohong takeCareOfBaby],因为此时delegate已经指向xiaohong了。
4.在BabySitter类中,找到takeCareOfBaby方法,执行方法体内的语句。结果就会打印出“照顾小宝宝”。

现在我们基本可以回答以下几个问题了。

  1. 为什么Baby类中
    @property(nonatomic,weak)id<BabyDelegate> delegate;
    需要使用id泛型进行声明呢?

因为直到 xiaoming.delegate = xiaohong;这句话执行之前,delegate的类型都是不确定的。delegate可以指向任意类型。另一方便,无论实现代理的对象是什么类型,都必须要先遵循协议。

  1. 代理使用strong一定会循环引用?

答案是否定的。
为什么呢?
首先要明白什么是循环引用。
要说循环引用就不得不说ios的内存管理方法或者叫垃圾回收机制——引用计数。
简要说,当一个对象被创建出来之后(不管是怎么创建出来的),至少有一个强指针指向它,它的引用计数都是一个大于0的数。当有别的强引用指针指向这个对象的时候,该对象的引用计数就会增加1。当一个强引用指针从这个对象上移除的时候,这个对象的引用计数就会减1 。直到所有的指针都移除的时候,这个对象的引用计数就等于0了。这个时候,系统就会把这个对象回收掉了。至于系统是什么时候知道这个对象的引用计数等于0的,什么时候回收的,这又是另外的问题了。这里不做深入探讨了。

这里再插入一个小概念:指针和对象。

指针和对象密切相关,但是又是完全不同的东西。

创建个简单的Person类:
//
//  Person.h
//  TestOC
//
//  Created by iOS on 2018/4/20.
//  Copyright © 2018年 weiman. All rights reserved.
//

#import <Foundation/Foundation.h>

@interface Person : NSObject

@property(nonatomic,copy) NSString * name;
@property(nonatomic,assign) NSInteger age;

- (void)play;

- (void)work;

@end

//
//  Person.m
//  TestOC
//
//  Created by iOS on 2018/4/20.
//  Copyright © 2018年 weiman. All rights reserved.
//

#import "Person.h"

@implementation Person

-(void)play {
    NSLog(@"周末一起去玩耍");
}

-(void)work {
    NSLog(@"工作中。。。");
}

@end

main中:

//
//  main.m
//  TestOC
//
//  Created by iOS on 2018/4/20.
//  Copyright © 2018年 weiman. All rights reserved.
//

#import <Foundation/Foundation.h>
#import "Person.h"

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // insert code here...
        NSLog(@"Hello, World!");
        
        Person * tom = [[Person alloc]init];
        tom.name = @"Tom";
        
        Person * happy = tom;
        NSLog(@"%@, %@",tom, happy);
    }
    return 0;
}

看这句:
Person * tom = [[Person alloc]init];

从运算符来看,这是一句赋值语句。赋值语句的执行顺序是从右到左。也就是说,先执行 [[Person alloc]init]。
再来分析这句话:
[[Person alloc]init]其实执行了两个方法,一个是类方法alloc,定义在父类NSObject中。另一个是对象方法init,也是定义在父类NSObject中。
[Person alloc]:开辟一段内存空间。
init:对[Person alloc]出来的对象进行初始化。例如给对象age赋初始值0.
(系统如何给person对象开辟的空间,开辟多大空间,空间里都有什么,是如何排列的,初始化的时候都做了什么,成员变量和方法是如何被初始化的,这些问题还有待研究。)

[[Person alloc]init]这句话执行完成之后,我们现在有一个对象了,这个对象是Person类型的,没有指针指向它。

现在再来看赋值语句左边的语句:Person * tom
这一句话是声明了一个Person类型的 指针,指针的名字叫做tom。

最后执行赋值,把右边创建出来的对象地址赋值给指针tom,也就是把指针tom指向了这个创建出来的Person对象。可以使用这个指针访问这个对象的可见的属性和方法。

Person * happy = tom;
理解上上面的概念,这句话就好理解了,把tom指针赋值给happy指针,就是happy指针也指向了tom指向的对象的地址,此时,该对象已经有两个指针引用了。

由于tom和happy这两个指针变量都是局部变量,在方法体执行完之后,这两个指针都会被销毁,当两个指针都销毁的时候,这个Person的对象的引用计数就变成0了,现在系统就把这块空间回收了,这个对象也就销毁了。

哎呀呀呀······扯的有点远了。。。

说了那么多,到底和循环引用有什么关系呢?对象释放的前提是对象本身没有强引用。循环引用会发生就是因为,两个对象中分别持有对方的强引用指针。导致这两个对象都不能释放,这两块内存都不能回收,造成资源浪费,也称为内存泄漏。

内存泄漏为什么总是跟代理相关?

因为代理中持有别的对象的指针,也就是这句:

@property(nonatomic,weak)id<BabyDelegate> delegate;

如果delegate是一个强引用,而实现代理的BabySitter中恰好也有Baby的强引用指针。这个时候,Baby和BabySitter的对象就会造成循环引用。注意,关键来了,循环引用是一个双向的强引用,仅仅把代理声明成strong不一定就会造成循环引用,还要满足另一个条件:代理者中也有被代理者的强引用,这样才会循环引用。
所以,代理声明称strong的不一定会循环引用。

但是,为了安全起见(我们也不知道代理是谁,代理者中会不会有被代理者的强引用指针存在),还是把代理声明成weak的。

  1. 代理为什么不用assign声明?

weak和assign都是一种“非拥有关系”的指针。通过这两种关键字修饰的变量,都不会改变被引用对象的引用计数。但是,不同的是,在一个对象被释放之后,weak修饰的指针变量会被置为nil,而assign不会。在OC中,向nil发送消息不会崩溃。而使用assign,在对象被释放后,在使用这个指针调用被释放对象,也就是向这个对象发送消息,就会有崩溃出现。

这篇短短的文章断断续续写了好几次,终于写完了。

目录
相关文章
|
Web App开发 数据安全/隐私保护
如何在AdsPower中设置易路代理YiLuProxy?
如何在AdsPower中设置易路代理YiLuProxy?
216 0
|
2月前
|
数据采集
芝麻代理、快代理、神龙代理、小象代理…如何挑选适合的代理IP?
本文介绍了如何选择适合项目需求的代理IP服务。首先,需明确具体应用场景和需求,不同场景对代理IP的要求各异。其次,选择合适的代理类型,如HTTP、HTTPS或SOCKS5。稳定性和速度是核心要素,需关注代理IP的稳定性指标和网络延迟。成本方面,应综合考量性价比,并进行实际测试。最后,选择提供优质服务支持的供应商,以确保问题能够及时解决。通过这些步骤,可以找到最适合项目的代理IP服务。
|
2月前
|
设计模式 缓存 JavaScript
什么是代理对象
【9月更文挑战第3天】什么是代理对象
125 0
|
5月前
|
JavaScript API
怎么进行代理
怎么进行代理
61 1
|
4月前
|
Java Spring
AopContext.currentProxy();为什么能获取到代理对象
AopContext.currentProxy();为什么能获取到代理对象
195 0
|
6月前
|
安全 网络安全 数据安全/隐私保护
socks5代理如何工作?socks5代理可以用来做什么?
本文介绍了socks5代理,一种能传输二进制数据的协议代理,提供更好的安全性和灵活性。它在客户端和服务器间作为中介,支持加密通信,适用于突破网络限制、保护隐私和加速访问。选择socks5代理应考虑稳定性、速度、安全性和透明性,以及对多协议的支持。
|
6月前
|
安全 Linux 数据安全/隐私保护
代理ip全局代理是什么且如何设置
代理ip全局代理是什么且如何设置
163 0
|
6月前
|
存储 Python
ProxyBroker-代理IP管理
ProxyBroker-代理IP管理
|
数据采集 机器学习/深度学习
IP代理
爬虫系列文章
2073 0