惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
WordPress大学
WordPress大学
博客园 - 【当耐特】
Engineering at Meta
Engineering at Meta
IT之家
IT之家
Apple Machine Learning Research
Apple Machine Learning Research
小众软件
小众软件
美团技术团队
S
SegmentFault 最新的问题
The GitHub Blog
The GitHub Blog
Martin Fowler
Martin Fowler
Recorded Future
Recorded Future
H
Help Net Security
aimingoo的专栏
aimingoo的专栏
Y
Y Combinator Blog
博客园_首页
A
About on SuperTechFans
MongoDB | Blog
MongoDB | Blog
阮一峰的网络日志
阮一峰的网络日志
V
Visual Studio Blog
D
DataBreaches.Net
人人都是产品经理
人人都是产品经理
大猫的无限游戏
大猫的无限游戏
B
Blog
The Register - Security
The Register - Security
I
InfoQ
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
雷峰网
雷峰网
Last Week in AI
Last Week in AI
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
月光博客
月光博客
酷 壳 – CoolShell
酷 壳 – CoolShell
Blog — PlanetScale
Blog — PlanetScale
N
Netflix TechBlog - Medium
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
有赞技术团队
有赞技术团队
Stack Overflow Blog
Stack Overflow Blog
Jina AI
Jina AI
Security Archives - TechRepublic
Security Archives - TechRepublic
Hacker News - Newest:
Hacker News - Newest: "LLM"
U
Unit 42
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
W
WeLiveSecurity
Latest news
Latest news
Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed
博客园 - 叶小钗
L
Lohrmann on Cybersecurity
博客园 - Franky
Recent Commits to openclaw:main
Recent Commits to openclaw:main
K
KPMG report finds enterprise disconnect between AI and its ROI | CIO

土土哥的Blog

好久没有更新博客=。= 阿里巴巴国际无线技术部 - 招人啦~求iOS、Android、Java 转-阿里巴巴国际无线技术部 - 在这里遇见最好的自己 反编译分析并模拟实现methodSignatureForSelector方法 反编译分析Xcode8的Bug, release下连续两次调用有二级指针参数的空方法会Crash 有趣的Autolayout示例5-Masonry实现 在对象dealloc的后期执行Task-开源库TTGDeallocTaskHelper 用QuartzCode快速实现一个收藏动画 开源项目-拼图验证控件TTGPuzzleVerify的实现 Swift开源Mac App - BingWallPaper 有趣的Autolayout示例4-Masonry实现 翻译-为什么objc_msgSend必须用汇编实现 API返回结果设计经验与总结 结合访问Out Parameters出现EXC_BAD_ACCESS的例子,反编译汇编解读__autoreleasing 总结一些iOS项目中组织代码的方法 对组件化与模块化的思考与总结 开源项目-TTGTagCollectionView 有趣的Autolayout示例3-Masonry实现 Swift开源项目: TTGEmojiRate的实现 Swift写的库-TTGSnackbar 有趣的Autolayout示例2-Masonry实现 解决iOS项目的版本兼容问题-结合宏、Category和Runtime 用Runtime的手段填充任意NSObject对象的nil属性 有趣的Autolayout示例-Masonry实现 UITextView编辑时插入自定义表情-续-自定义表情图片的大小 RPC框架Thrift例子-PHP调用C++后端程序 GCD使用经验与技巧浅谈 为GCD队列绑定NSObject类型上下文数据-利用__bridge_retained(transfer)转移内存管理权 Enum-枚举的正确使用-Effective-Objective-C-读书笔记-Item-5 @autoreleasepool-内存的分配与释放 有关宏定义的经验与技巧-简化代码-增强Log Effective-Objective-C-读书笔记-Item-4-如何正确定义常量 UITextView编辑时插入自定义表情-简单的图文混编 关于评论不见了=。= Entity和Model的不同-关于代码的数据层 一次审核被拒的经历-关于iCloud到底应该备份什么数据 Block类型变量-缓存Http请求与回调 提升UITableView性能-复杂页面的优化 NSString的Copy与内存分配 Effective-Objective-C-读书笔记-Item-3 Effective-Objective-C-读书笔记-Item-2 Effective-Objective-C-读书笔记-Item-1 iOS项目的目录结构-原创 Android开源库-LinkTextView-原创 第一篇Blog
利用NSProxy实现消息转发-模块化的网络接口层设计-原创
土土哥 · 2015-02-16 · via 土土哥的Blog

更新

  • 2015-12-21: 其实用Category按照业务拆分网络请求接口的代码也是非常棒的,目前我自己就是这么做的=。=,文章就当做熟悉下NSProxy吧~

前言

之前在做项目的时候,所有业务的网络接口方法,全部都写在了一个文件里面,一开始还好,毕竟每个方法的代码也只是十几行,增加、修改也比较容易。但是随着接口的增多,这个文件慢慢居然超过了1000行,里面几十个方法都写在一起,实在是不好维护。

虽然保持这样也没有什么,多用用Cmd+F就能找到。但是,真是越看越不顺眼😳, 代码一定要有“美感”啊~😝

所以在查阅了大量文章后,终于想出了本文即将说明的办法,越发觉得Objective-C的“消息”机制是多么的赞~😚

Github 示例

贴上本文中的示例工程:https://github.com/zekunyan/HttpProxyExample

问题

先抛出问题。

一款互联网应用,免不了要跟服务器打交道,在iOS项目中,最有名的网络库应该就是AFNetworking了。所以,很多人就会利用AFnetworking提供的Get、Post等基本Http请求接口,封装自己的网络接口层代码,我自己在项目中也是这么做的。

但是,AFNetworking只是提供了Get、Post、Json传输等基本的Http请求方法,所以一旦落实到具体的业务相关的请求上,我们要为每个请求(URL)都写一个单独的接口方法。

那么,问题就来了

业务相关的接口那么多,举个例子,什么“通过用户ID获取用户基本信息”、“获取用户的所有评论”等,每个请求都是一个方法,这么多方法该怎么组织呢?全部放在一起?那这个接口类岂不是会非常乱?不放在一起?那岂不是会有很多个网络请求类?(至于要不要统一接口入口,我想这个根据项目来决定吧=。=)

需求

  • 所有网络接口都从统一的类调用,如HttpProxy。
  • 网络接口的具体实现,按照业务划分到不同的类中,如“UserHttpHandler”、“CommentHttpHandler”。

其实,按照面向对象的原则,就是接口代理类HttpProxy拥有若干个按照业务划分的接口(Interface),这些接口的所有方法组成了网络层的不同的Http请求。如下图:

请求示意图

那么,调用的时候,所有接口都用HttpProxy调用,如:

1
2
3
4
5

[[HttpProxy sharedInstance] getUserWithID:@100];


[[HttpProxy sharedInstance] getCommentsWithDate:date];

关键

根据前面的描述,我们可以得出,关键就是:消息转发(Message Forward)

Objective-C里面没有我们传统的“方法调用”,取而代之的是“消息”,所有的方法都是通过向对象发送“消息”实现调用的。而这个机制,也就为我们的实现提供了方便。

也就是说:我们要将发给“HttpProxy”的消息,让HttpProxy转发给真正能接受这个消息的对象,HttpProxy就是个代理

苹果已经给我们提供了这个“代理”类了-NSProxy。

NSProxy

什么是NSProxy:

  • NSProxy没有父类,是顶级类(根类),跟NSObject同等地位。
  • NSProxy和NSObject都实现了“NSObject Protocol”。
  • NSProxy设计时就是以“抽象类”设计的,专门为转发消息而生。

实现要求:

  1. 继承NSProxy的子类要实现自己的初始化方法,如“init”、“initWith”。
  2. 重写- forwardInvocation:- methodSignatureForSelector:方法,完成消息转发。

详细内容参考Apple的文档

实现

定义

先不管HttpProxy,咱们看看具体的接口,先举两个例子:

1
2
3
4
5
6
7
8
9
10
11


@protocol UserHttpHandler <NSObject>
- (void)getUserWithID:(NSNumber *)userID;
@end



@protocol CommentHttpHandler <NSObject>
- (void)getCommentsWithDate:(NSDate *)date;
@end

好的,接口有了,我们的HttpProxy类应该“实现”了这两个接口。
然后,最好是单例类,所以还要有个获取单例的方法。
最后,还需要一个向HttpProxy注册具体实现了接口Protocol的方法。

所以,HttpProxy应该是这个样子的:

1
2
3
4
5
6
7
8
9
10
11
12



@interface HttpProxy : NSProxy <UserHttpHandler, CommentHttpHandler>


+ (instancetype)sharedInstance;


- (void)registerHttpProtocol:(Protocol *)httpProtocol handler:(id)handler;

@end

找到消息对应的实现类对象

如何在HttpProxy做消息转发时,找到某个消息对应的真正的实现类对象呢?

最好的办法就是保存每个接口方法到其实现类对象的映射,可以用Dictionary保存,关系如下图:

image

所以,registerHttpProtocol:handler:方法的职责就是:

  1. 遍历Protocol的所有方法(利用Objective-C的Runtime)。
  2. 保存Protocol所有方法到实现类的对象的映射关系。(用方法的字符串表示作为key,实现类对象为value)

所以,HttpProxy应该持有一个Dictionary的实例,用于保存映射关系,HttpProxy的实现部分如:

1
2
3
4
5
6


@interface HttpProxy ()

@property(strong, nonatomic) NSMutableDictionary *selToHandlerMap;
@end

注册方法实现如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
- (void)registerHttpProtocol:(Protocol *)httpProtocol handler:(id)handler {
unsigned int numberOfMethods = 0;


struct objc_method_description *methods = protocol_copyMethodDescriptionList(
httpProtocol, YES, YES, &numberOfMethods);


for (unsigned int i = 0; i < numberOfMethods; i++) {
struct objc_method_description method = methods[i];
[_selToHandlerMap setValue:handler forKey:NSStringFromSelector(method.name)];
}
}

实现消息的转发

我们已经可以注册接口、保存映射关系了,剩下的就是重写NSProxy的两个方法,以实现消息的转发,至于这两个方法具体作用是什么,读者可以自行查阅相关资料。如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30



- (NSMethodSignature *)methodSignatureForSelector:(SEL)sel {


NSString *methodsName = NSStringFromSelector(sel);


id handler = [_selToHandlerMap valueForKey:methodsName];


if (handler != nil && [handler respondsToSelector:sel]) {
return [handler methodSignatureForSelector:sel];
} else {
return [super methodSignatureForSelector:sel];
}
}


- (void)forwardInvocation:(NSInvocation *)invocation {
NSString *methodsName = NSStringFromSelector(invocation.selector);
id handler = [_selToHandlerMap valueForKey:methodsName];

if (handler != nil && [handler respondsToSelector:invocation.selector]) {
[invocation invokeWithTarget:handler];
} else {
[super forwardInvocation:invocation];
}
}

Example

看看如何使用HttpProxy:

1
2
3
4
5
6
7

[[HttpProxy sharedInstance] registerHttpProtocol:@protocol(UserHttpHandler) handler:[UserHttpHandlerImp new]];
[[HttpProxy sharedInstance] registerHttpProtocol:@protocol(CommentHttpHandler) handler:[CommentHttpHandlerImp new]];


[[HttpProxy sharedInstance] getUserWithID:@100];
[[HttpProxy sharedInstance] getCommentsWithDate:[NSDate new]];

总结

所有的代码及示例都提交到Github了,HttpProxyExample

总的来说,就是利用Objective-C的“消息”机制,继承NSProxy抽象类,实现自己定义的转发机制,将网络接口层的各个方法的实现与声明分离,提升项目代码的可维护性,更加模块化。如下图表示:

image

以上,就是我自己在项目中,利用NSProxy设计并实现的网络接口层结构。💪

从了解NSProxy、Runtime到设计、实现出这个网络层架构,着实花了点时间,收获真是不少。其实像这种纯代码层次的重构,很多人会觉得“吃力不讨好”😨,但我觉得,正是这样,才能真正让自己提升。

代码是具有美感的!嗯!😝

参考