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

推荐订阅源

爱范儿
爱范儿
Know Your Adversary
Know Your Adversary
Google DeepMind News
Google DeepMind News
A
Arctic Wolf
P
Privacy & Cybersecurity Law Blog
云风的 BLOG
云风的 BLOG
Stack Overflow Blog
Stack Overflow Blog
V
Visual Studio Blog
Project Zero
Project Zero
L
LangChain Blog
N
News and Events Feed by Topic
博客园 - Franky
Last Week in AI
Last Week in AI
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
T
The Blog of Author Tim Ferriss
宝玉的分享
宝玉的分享
Scott Helme
Scott Helme
T
The Exploit Database - CXSecurity.com
P
Proofpoint News Feed
Blog — PlanetScale
Blog — PlanetScale
www.infosecurity-magazine.com
www.infosecurity-magazine.com
W
WeLiveSecurity
月光博客
月光博客
博客园_首页
美团技术团队
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
腾讯CDC
Latest news
Latest news
WordPress大学
WordPress大学
cs.CV updates on arXiv.org
cs.CV updates on arXiv.org
Spread Privacy
Spread Privacy
Attack and Defense Labs
Attack and Defense Labs
量子位
L
LINUX DO - 热门话题
C
CERT Recently Published Vulnerability Notes
Webroot Blog
Webroot Blog
L
Lohrmann on Cybersecurity
aimingoo的专栏
aimingoo的专栏
T
Troy Hunt's Blog
Security Latest
Security Latest
小众软件
小众软件
Cloudbric
Cloudbric
Hacker News: Ask HN
Hacker News: Ask HN
S
Secure Thoughts
雷峰网
雷峰网
T
Threat Research - Cisco Blogs
H
Hacker News: Front Page
IT之家
IT之家
Simon Willison's Weblog
Simon Willison's Weblog

土土哥的Blog

好久没有更新博客=。= 阿里巴巴国际无线技术部 - 招人啦~求iOS、Android、Java 转-阿里巴巴国际无线技术部 - 在这里遇见最好的自己 反编译分析并模拟实现methodSignatureForSelector方法 反编译分析Xcode8的Bug, release下连续两次调用有二级指针参数的空方法会Crash 有趣的Autolayout示例5-Masonry实现 用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与内存分配 利用NSProxy实现消息转发-模块化的网络接口层设计-原创 Effective-Objective-C-读书笔记-Item-3 Effective-Objective-C-读书笔记-Item-2 Effective-Objective-C-读书笔记-Item-1 iOS项目的目录结构-原创 Android开源库-LinkTextView-原创 第一篇Blog
在对象dealloc的后期执行Task-开源库TTGDeallocTaskHelper
土土哥 · 2017-03-11 · via 土土哥的Blog

前言

最近更新了下以前写的TTGDeallocTaskHelper库,功能非常简单,就是在不改变原有代码的情况下,不用Runtime的Method Swizzling给任意对象添加任务Block,在对象dealloc的后期执行Block。使用时:

1
2
3
4

[object ttg_addDeallocTask:^(__unsafe_unretained id target, NSUInteger identifier) {

}];

Github地址:https://github.com/zekunyan/TTGDeallocTaskHelper

一张图说明原理:

总体结构

库的详细原理、限制、注意事项,为什么说“后期”执行,将在下文给出。

使用案例

先给出几个使用案例。

删除Notification监听

比如你给一个第三方类对象添加了Notification通知,要在对象release后删除Notification的监听,就可以:

1
2
3
4
[object ttg_addDeallocTask:^(__unsafe_unretained SomeClass *object, NSUInteger identifier) {

[[NSNotificationCenter defaultCenter] removeObserver:object];
}];

快速Debug内存泄漏

有时你想快速验证一下一个类有没有释放,存不存在内存泄漏,比如一个ViewController返回后,就可以这么做:

1
2
3
[someViewController ttg_addDeallocTask:^(__unsafe_unretained UIViewController *controller, NSUInteger identifier) {
NSLog(@"我正常释放了!哈哈哈!");
}];

事件打点

业务开发中,想监听某个事件什么时候销毁,就可以:

1
2
3
[someEvent ttg_addDeallocTask:^(__unsafe_unretained SomeEvent *event, NSUInteger identifier) {

}];

是不是很方便!没有对原有的代码做任何侵入。
接下来看看实现原理。

原理

在对象dealloc的时候做操作的四种办法

在对象dealloc的时候做操作,有好几种方法:

  • 继承:这个应该是最“正常”的方法了,继承对应的类,在子类中实现dealloc方法,执行相关的任务。缺点是要修改用到类的地方为自己定义的子类
  • Method Swizzling:方法交换,直接自己定义新的dealloc方法实现,实现任务的调用。但是坏处是所有的对象都会被替换,侵入性太大
  • ISA Swizzling:动态的生成对象的子类,在子类继承方法,实现调用任务,然后设置对象的isa指针,使其指向子类。这种方法的好处是只会影响到单个对象,但是系统KVO也是用这个方法,重复设置isa的话,会有问题。
  • 利用Associated Object:Associated Object会在对象释放时自动释放,所以可以用来“挂载”要执行的任务。

对比了上面的四种方法后,选定用Associated Object的方式实现TTGDeallocTaskHelper。

用Associated Object挂载任务

总的思路是:

  1. 给对象添加Associated Object
  2. 在Associated Object里面保存要执行的任务Block
  3. 实现Associated Object的dealloc方法,在Associated Object释放的时候,批量执行之前添加的Block

图示如下:

总体结构

Associated Object - TaskModel

属性

上图中的TaskModel,就是库代码里面的TTGDeallocTaskModel类,包含三个属性:

1
2
3
@property (nonatomic, assign) pthread_mutex_t lock;
@property (nonatomic, strong) NSMutableDictionary *tasksDict;
@property (nonatomic, unsafe_unretained) id target;
  • tasksDict保存要执行的任务Block
  • lock锁用来保证多线程下操作tasksDict的安全性
  • target指向的是原对象,是unsafe_unretained的,保证始终不为nil,所以也建议不要在任务Block里面调用target的方法

添加任务Block

TTGDeallocTaskModeltasksDict就是用来保存要执行的任务Block的,为了能在添加了以后删除,所以要以Key-Value的形式保存。
Key的类型是NSUInteger,全局自增,保证唯一。

OSAtomicIncrement64实现线程安全的自增整型Key-identifier

为了保证自增的Key的线程安全,同时平衡性能,所以此处用OSAtomicIncrement64实现原子整型数自增:

1
2
3
4

static volatile int64_t globalIdentifier = 0;

NSUInteger newIdentifier = (NSUInteger)OSAtomicIncrement64(&globalIdentifier);

pthread_mutex_t保证操作字典的线程安全

为了保证NSMutableDictionary的多线程操作的安全,用pthread_mutex_t对添加Block的操作加锁:

1
2
3
pthread_mutex_lock(&_lock);
[_tasksDict setObject:[taskBlock copy] forKey:newIdentifierNumber];
pthread_mutex_unlock(&_lock);

删除任务Block

删除任务有两种,分别是删除一个identifier key对应的Block,和删除所有Block,同样,都要加锁保证线程安全:

1
2
3
4
5
6
7
8

pthread_mutex_lock(&_lock);

[_tasksDict removeObjectForKey:identifierNumber];

[_tasksDict removeAllObjects];

pthread_mutex_unlock(&_lock);

dealloc执行Block

最终在TaskModel被释放的时候,要批量执行所有添加的Block:

1
2
3
4
5
6
7
8
- (void)dealloc {

[_tasksDict enumerateKeysAndObjectsUsingBlock:^(NSNumber *identifier, TTGDeallocTaskBlock block, BOOL * _Nonnull stop) {
block(_target, identifier.unsignedIntegerValue);
}];

pthread_mutex_destroy(&_lock);
}

给NSObject添加Category

上面是TaskModel的部分,接下来是NSObject的。

给NSObject添加Categor NSObject (TTGDeallocTaskHelper),保存TTGDeallocTaskModel,封装调用。

封装-添加任务Block

Associated Object给对象添加TaskModel属性

objc_setAssociatedObject实现给任意对象添加对应的TaskModel对象。

@synchronized保证多线程下TaskModel的唯一性

首次添加任务Block,会创建TaskModel,为了保证多线程下的唯一性,所以创建的部分要用@synchronized保护起来。

所以,添加任务Block的代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
- (NSUInteger)ttg_addDeallocTask:(TTGDeallocTaskBlock)taskBlock {
if (!taskBlock) {

return TTGDeallocTaskIllegalIdentifier;
}

TTGDeallocTaskModel *model = nil;


@synchronized (self) {
model = objc_getAssociatedObject(self, &TTGDeallocTaskModelKey);
if (!model) {

model = [[TTGDeallocTaskModel alloc] initWithTarget:self];

objc_setAssociatedObject(self, &TTGDeallocTaskModelKey, model, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
}


NSUInteger newIdentifier = [model addTask:taskBlock];

return newIdentifier;
}

封装-删除Block

直接获取当前对象的TaskModel对象,调用相应的方法即可,不再贴代码。

限制与注意事项

原理和实现说完了,接下来是库的限制与注意事项。

NSObject对象的dealloc过程

先来看看Associated Object在dealloc的什么时候释放。

NSObject对象dealloc的具体细节,sunnyxx在他的《ARC下dealloc过程及.cxx_destruct的探究》已经说的很清楚了。简单来说,就是对象dealloc的时候,最终会调用到objc_destructInstance方法上:

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









void *objc_destructInstance(id obj)
{
if (obj) {
Class isa = obj->getIsa();

if (isa->hasCxxDtor()) {

object_cxxDestruct(obj);
}

if (isa->instancesHaveAssociatedObjects()) {

_object_remove_assocations(obj);
}


objc_clear_deallocating(obj);
}
return obj;
}

主要是3步:

  1. object_cxxDestruct释放对象的ivar、property
  2. _object_remove_assocations释放对象的Associated Object
  3. objc_clear_deallocating重置所有指向对象的weak指针为空

不建议在Block里面调用target原对象的方法

从dealloc的流程可知,Associated Object是在第二步被释放的,这个时候,原对象的属性已被release,并且,Associated Object对象自己的dealloc不一定在什么时候会被调用(比如加到了autorelease pool里面),所以,不建议在任务Block里面调用target原对象的方法。

最后

什么时候Associated Object的释放能在对象属性release之前就好了=。=