FBRetainCycleDetector
是FaceBook
开源的用于检测强引用循环的工具。默认是在DEBUG
环境中启用,当然你也可以通过设置RETAIN_CYCLE_DETECTOR_ENABLED
以始终开启。
FBRetainCycleDetector
的使用非常简单。创建检测器,将可疑的对象添加到检测器中,然后调用开始检测即可。如果需要经常使用,可以考虑写一个单例,避免频繁的创建销毁。
- (void)retainCycleDetectWithObject:(id)object {
if (!object) {
return;
}
// 创建检测器
FBRetainCycleDetector *detector = [[FBRetainCycleDetector alloc] init];
// 添加检测对象
[detector addCandidate:object];
// 开始检测
NSSet *retainCycles = [detector findRetainCyclesWithMaxCycleLength:10];
NSLog(@"%@",retainCycles);
}
下面我们以上面的三行代码为突破口,深入探究一下FBRetainCycleDetector
到底是如何运作的
- (instancetype)init
{
return [self initWithConfiguration:
[[FBObjectGraphConfiguration alloc] initWithFilterBlocks:FBGetStandardGraphEdgeFilters()
shouldInspectTimers:YES]];
}
我们发现它的init
方法里面创建了一个配置类FBObjectGraphConfiguration
。该方法共有两个参数,一个是元素类型为FBGraphEdgeFilterBlock
的过滤数组,一个是是否检测NSTimer
。这个过滤数组相当于是配置一些规则,告诉检测器哪些对象不需要检测。
FBGraphEdgeFilterBlock
的定义如下。
typedef FBGraphEdgeType (^FBGraphEdgeFilterBlock)(FBObjectiveCGraphElement *_Nullable fromObject, NSString *_Nullable byIvar, Class _Nullable toObjectOfClass);
创建FBGraphEdgeFilterBlock
FBGraphEdgeFilterBlock FBFilterBlockWithObjectToManyIvarsRelation(Class aCls,
NSSet<NSString *> *ivarNames) {
return ^(FBObjectiveCGraphElement *fromObject,
NSString *byIvar,
Class toObjectOfClass){
if (aCls &&
[[fromObject objectClass] isSubclassOfClass:aCls]) {
// If graph element holds metadata about an ivar, it will be held in the name path, as early as possible
if ([ivarNames containsObject:byIvar]) {
return FBGraphEdgeInvalid;
}
}
return FBGraphEdgeValid;
};
}
FBGraphEdgeFilterBlock
的返回值为FBGraphEdgeType
类型,分别是Valid
和Invalid
,相当于一个BOOL
值,判断对象有效或者无效。由此可见该Block的作用就是对传入的对象进行过滤。
了解了这一点,我们就可以创建自己的过滤规则了,举个例子:
NSMutableArray<FBGraphEdgeFilterBlock> *filterBlocks = FBGetStandardGraphEdgeFilters().mutableCopy;
// 自定义过滤规则,不检测UITableView的delegate和dataSourse属性
FBGraphEdgeFilterBlock block = FBFilterBlockWithObjectToManyIvarsRelation([UITableView class], [NSSet setWithArray:@[@"_delegate",@"_dataSourse",]]);
[filterBlocks addObject:block];
[[FBObjectGraphConfiguration alloc] initWithFilterBlocks:filterBlocks.copy shouldInspectTimers:YES];
// 创建检测器
FBRetainCycleDetector *detector = [[FBRetainCycleDetector alloc] initWithConfiguration:config];
// 添加检测对象
[detector addCandidate:object];
// 开始检测
NSSet *retainCycles = [detector findRetainCyclesWithMaxCycleLength:10];
这里设置的过滤Block
将在下一步添加检测对象的时候调用。
- (void)addCandidate:(id)candidate
{
FBObjectiveCGraphElement *graphElement = FBWrapObjectGraphElement(nil, candidate, _configuration);
if (graphElement) {
[_candidates addObject:graphElement];
}
}
我们添加进去的OC对象,在内部都被转化成了FBObjectiveCGraphElement
。里面大部分操作都使用是FBObjectiveCGraphElement
对象。在生成该对象的FBWrapObjectGraphElement()
方法内部,先调用了_ShouldBreakGraphEdge
,进而直接调用开始配置的过滤Block
。如果被过滤,直接返回。对于需要检测对象,根据类型分别转化为FBObjectiveCBlock
、FBObjectiveCNSCFTimer
、FBObjectiveCObject
,对应OC里面的block
、NSTimer
、Object
。他们都是FBObjectiveCGraphElement
的子类,这几个类型主要区别在于-(NSSet *)allRetainedObjects
方法的实现不同,而这个方法也是比较核心的方法,其功能是获取对象强引用的所有对象。具体是 怎么获取的,我们先跳过,后面详细介绍。下面先继续我们检测逻辑。
检测过程就是遍历第二步创建的FBObjectiveCGraphElement
数组,进行DFS深度搜索。然后调用每个节点的FBObjectiveCGraphElement
对象的-(NSSet *)allRetainedObjects
方法,拿到所有强引用的对象进行判断是否循环引用。
我们来通过注释,详细的解读下面的代码。
- (NSSet<NSArray<FBObjectiveCGraphElement *> *> *)_findRetainCyclesInObject:(FBObjectiveCGraphElement *)graphElement
stackDepth:(NSUInteger)stackDepth
{
// 存放检测结果
NSMutableSet<NSArray<FBObjectiveCGraphElement *> *> *retainCycles = [NSMutableSet new];
// 初始化深度搜索树中的一个节点
FBNodeEnumerator *wrappedObject = [[FBNodeEnumerator alloc] initWithObject:graphElement];
// 根据需要检测的对象进行深度搜索
// We will be doing DFS over graph of objects
// 保存当前DFS搜索树中的搜索路径
// Stack will keep current path in the graph
NSMutableArray<FBNodeEnumerator *> *stack = [NSMutableArray new];
// 记录搜索路径中访问过的对象
// To make the search non-linear we will also keep
// a set of previously visited nodes.
NSMutableSet<FBNodeEnumerator *> *objectsOnPath = [NSMutableSet new];
// 加入根节点,从根节点开始搜索
// Let's start with the root
[stack addObject:wrappedObject];
// 判断是否已经搜索完毕
while ([stack count] > 0) {
// 搜索过程会创建很多对象,占用大量内存
/* Algorithm creates many short-living objects. It can contribute to few hundred megabytes memory jumps if not handled correctly, therefore we're gonna drain the objects with our autoreleasepool.*/
@autoreleasepool {
// Take topmost node in stack and mark it as visited
// 访问搜索栈中的最上面一个节点
FBNodeEnumerator *top = [stack lastObject];
/* 避免重复遍历
1.如果不包含(未检测|已检测通过),判断对象地址是否被记录(是否被检测过)。已检测通过的会被移除objectsOnPath数组
1.1不包含且已经记录说明已检测通过,没有引用循环,继续下一个
1.2不包含且未被记录,则记录地址开始进一步的检测
*/
// We don't want to retraverse the same subtree
if (![objectsOnPath containsObject:top]) {
if ([_objectSet containsObject:@([top.object objectAddress])]) {
[stack removeLastObject];
continue;
}
// 这里记录地址 是为了避免不必要的引用
// Add the object address to the set as an NSNumber to avoid
// unnecessarily retaining the object
[_objectSet addObject:@([top.object objectAddress])];
}
[objectsOnPath addObject:top];
/* Take next adjecent node to that child. Wrapper object can persist iteration state. If we see that node again, it will give us new adjacent node unless it runs out of them */
/*
这个方法非常的巧妙,会获取节点(1)本身引用的所有子节点(1.1,1.2,1.3),然后添加到枚举器里遍历,并返回栈顶的一条。
如果该子节点不存在引用循环,他将被加到stack变成一个父节点(1.1)。
下次遍历就会获取该子节点引用的所有深一层的子节点(1.1.1),如此循环,这里默认的最大深度是10。
整个子节点遍历完之后,stack又回到了(1)。
再下一次nextObject会拿到最开始那个节点(1)的第二个子节点(1.2),重复上面的操作。
- (FBNodeEnumerator *)nextObject
{
if (!_object) {
return nil;
} else if (!_retainedObjectsSnapshot) {
// 获取所有引用的对象,并过滤封装为FBObjectiveCGraphElement集合
_retainedObjectsSnapshot = [_object allRetainedObjects]; // NSSet
_enumerator = [_retainedObjectsSnapshot objectEnumerator]; // NSEnumerator
}
FBObjectiveCGraphElement *next = [_enumerator nextObject];
if (next) {
return [[FBNodeEnumerator alloc] initWithObject:next];
}
return nil;
}
*/
// 寻找下一个未访问的节点
FBNodeEnumerator *firstAdjacent = [top nextObject];
// 如果存在未访问到的节点
if (firstAdjacent) {
// 当前节点还有相邻的节点没有被访问,会在当前节点及其子节点遍历完之后访问到
// Current node still has some adjacent not-visited nodes
BOOL shouldPushToStack = NO;
// Check if child was already seen in that path
// 如果该节点已经存在被访问过的对象中,说明构成了retain cycle
if ([objectsOnPath containsObject:firstAdjacent]) {
// We have caught a retain cycle
// Ignore the first element which is equal to firstAdjacent, use firstAdjacent
// we're doing that because firstAdjacent has set all contexts, while its
// first occurence could be a root without any context
NSUInteger index = [stack indexOfObject:firstAdjacent];
NSInteger length = [stack count] - index;
if (index == NSNotFound) {
// 对象已经被释放
// Object got deallocated between checking if it exists and grabbing its index
shouldPushToStack = YES;
} else {
// 拿到引用循环的部分
NSRange cycleRange = NSMakeRange(index, length);
NSMutableArray<FBNodeEnumerator *> *cycle = [[stack subarrayWithRange:cycleRange] mutableCopy];
[cycle replaceObjectAtIndex:0 withObject:firstAdjacent];
// 1. Unwrap the cycle
// 2. Shift to lowest address (if we omit that, and the cycle is created by same class,
// we might have duplicates)
// 3. Shift by class (lexicographically)
// 对结果的处理,为了判断是不是同一个环
[retainCycles addObject:[self _shiftToUnifiedCycle:[self _unwrapCycle:cycle]]];
}
} else {
// 节点未检测到引用循环,继续下一层
// Node is clear to check, add it to stack and continue
shouldPushToStack = YES;
}
// 如果在检测深度内,继续下一层
if (shouldPushToStack) {
if ([stack count] < stackDepth) {
[stack addObject:firstAdjacent];
}
}
} else {
// 没有子节点或者前部子节点都已经检测完,出栈
// Node has no more adjacent nodes, it itself is done, move on
[stack removeLastObject];
[objectsOnPath removeObject:top];
}
}
}
return retainCycles;
}
光看代码确实不那么清晰明了,我们再结合下面的图加深理解
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dMaFdNmz-1594262740501)(evernotecid://27FDE1C0-9B09-42C5-8296-1384110986C8/appyinxiangcom/25025916/ENResource/p2)]
前面说到获取一个对象所持有的所有对象是通过-(NSSet *)allRetainedObjects
方法实现的,三种子类都会先调用父类的-(NSSet *)allRetainedObjects
方法,然后再自己处理。
下面是父类的实现
- (NSSet *)allRetainedObjects
{
NSArray *retainedObjectsNotWrapped = [FBAssociationManager associationsForObject:_object];
NSMutableSet *retainedObjects = [NSMutableSet new];
for (id obj in retainedObjectsNotWrapped) {
FBObjectiveCGraphElement *element = FBWrapObjectGraphElementWithContext(self,
obj,
_configuration,
@[@"__associated_object"]);
if (element) {
[retainedObjects addObject:element];
}
}
return retainedObjects;
}
内部调用了[FBAssociationManager associationsForObject:]
方法,进而调用associations()
获取关联对象。也就是说父类只获取关联对象。
获取关联对象的具体流程如下:
[FBAssociationManager hook]
方法,修改系统objc_setAssociatedObject
和objc_removeAssociatedObjects
的实现,从而监听关联对象的调用+ (void)hook
{
#if _INTERNAL_RCD_ENABLED
std::lock_guard<std::mutex> l(*FB::AssociationManager::hookMutex);
rcd_rebind_symbols((struct rcd_rebinding[2]){ // 重新绑定
{
"objc_setAssociatedObject",
(void *)FB::AssociationManager::fb_objc_setAssociatedObject,
(void **)&FB::AssociationManager::fb_orig_objc_setAssociatedObject
},
{
"objc_removeAssociatedObjects",
(void *)FB::AssociationManager::fb_objc_removeAssociatedObjects,
(void **)&FB::AssociationManager::fb_orig_objc_removeAssociatedObjects
}}, 2);
FB::AssociationManager::hookTaken = true;
#endif //_INTERNAL_RCD_ENABLED
}
FBAssociationManager
都会将其添加到_associationMap
里面记录。这里只记录强引用的关联对象。 void _threadUnsafeSetStrongAssociation(id object, void *key, id value) {
if (value) {
auto i = _associationMap->find(object);
ObjectAssociationSet *refs;
if (i != _associationMap->end()) {
refs = i->second;
} else {
refs = new ObjectAssociationSet;
(*_associationMap)[object] = refs;
}
refs->insert(key);
} else {
_threadUnsafeResetAssociationAtKey(object, key);
}
}
associations()
方法的时候,从_associationMap
里获取追踪到的关联对象返回。hook
就无法追踪关联对象,所以如果想要检测关联对象,需要手动调用一次[FBAssociationManager hook]
方法,如果不检测关联对象,可以不调用。 NSArray *associations(id object) {
std::lock_guard<std::mutex> l(*_associationMutex);
if (_associationMap->size() == 0 ){
return nil;
}
auto i = _associationMap->find(object);
if (i == _associationMap->end()) {
return nil;
}
auto *refs = i->second;
NSMutableArray *array = [NSMutableArray array];
for (auto &key: *refs) {
id value = objc_getAssociatedObject(object, key);
if (value) {
[array addObject:value];
}
}
return array;
}
具体实现内容较多,有兴趣的可以参考如何实现 iOS 中的 Associated Object,里面对FBRetainCycleDetector
实现关联对象这块做了详细的解读。
下面我再看看子类的处理过程
FBObjectiveCObject
内部依次调用了FBGetObjectStrongReferences()
->FBGetStrongReferencesForClass()
->FBGetClassReferences()
核心的获取方法是最后一步,前面两个放法是对获取结果的封装及处理。FBGetClassReferences()
函数里面利用到了runtime(class_copyIvarList)
机制去获取属性列表,并且封装为FBIvarReference
类型。如果遇到属性是struct
类型还需单独进行处理。获得所有引用后,再通过class_getIvarLayout
来提取强引用并返回。最后通过FBIvarReference
的objectReferenceFromObject:
方法获取引用对象并封装添加到检测数组里(这一步会过滤掉值为nil
的对象)
NSArray<id<FBObjectReference>> *FBGetClassReferences(Class aCls) {
NSMutableArray<id<FBObjectReference>> *result = [NSMutableArray new];
unsigned int count;
Ivar *ivars = class_copyIvarList(aCls, &count);
for (unsigned int i = 0; i < count; ++i) {
Ivar ivar = ivars[i];
FBIvarReference *wrapper = [[FBIvarReference alloc] initWithIvar:ivar];
if (wrapper.type == FBStructType) {
std::string encoding = std::string(ivar_getTypeEncoding(wrapper.ivar));
NSArray<FBObjectInStructReference *> *references = FBGetReferencesForObjectsInStructEncoding(wrapper, encoding);
[result addObjectsFromArray:references];
} else {
[result addObject:wrapper];
}
}
free(ivars);
return [result copy];
}
FBObjectiveCObject
的- (NSSet *)allRetainedObjects
实现
- (NSSet *)allRetainedObjects
{
Class aCls = object_getClass(self.object);
if (!self.object || !aCls) {
return nil;
}
NSArray *strongIvars = FBGetObjectStrongReferences(self.object, self.configuration.layoutCache);
NSMutableArray *retainedObjects = [[[super allRetainedObjects] allObjects] mutableCopy];
for (id<FBObjectReference> ref in strongIvars) {
// object_getIvar(object, _ivar);
id referencedObject = [ref objectReferenceFromObject:self.object];
if (referencedObject) {
NSArray<NSString *> *namePath = [ref namePath];
FBObjectiveCGraphElement *element = FBWrapObjectGraphElementWithContext(self,
referencedObject,
self.configuration,
namePath);
if (element) {
[retainedObjects addObject:element];
}
}
}
...
return retainedObjects;
}
获取Block
持有的强引用的方法是通过FBGetBlockStrongReferences()
来实现的,而FBGetBlockStrongReferences()
是对_GetBlockStrongLayout
获取结果的封装。在介绍_GetBlockStrongLayout
之前,我们需要先简单了解一下Block
,他其实是一个结构体,其结构大概是这样的。
struct BlockLiteral {
void *isa;
int flags;
int reserved;
void (*invoke)(void *, ...);
struct BlockDescriptor *descriptor;
};
struct BlockDescriptor {
unsigned long int reserved;
unsigned long int size;
void (*copy_helper)(void *dst, void *src);
void (*dispose_helper)(void *src);
const char *signature;
};
BlockLiteral
结构体中有一个 isa
指针,而对 isa
了解的人也都知道,这里的 isa
指向了Block
的类。
BlockDescriptor
结构体中有两个函数指针,copy_helper
用于 Block
的拷贝,dispose_helper
用于 Block
的 dispose
也就是 Block
在析构的时候会调用这个函数指针,销毁自己持有的对象,而这个原理也是区别强弱引用的关键,因为在 dispose_helper
会对强引用发送 release
消息,对弱引用不会做任何的处理。
有了这些了解,我们在来详细解读_GetBlockStrongLayout
方法。
static NSIndexSet *_GetBlockStrongLayout(void *block) {
struct BlockLiteral *blockLiteral = block;
/**
BLOCK_HAS_CTOR - Block has a C++ constructor/destructor, which gives us a good chance it retains
objects that are not pointer aligned, so omit them.
!BLOCK_HAS_COPY_DISPOSE - Block doesn't have a dispose function, so it does not retain objects and
we are not able to blackbox it.
如果 block 有 C++ 的构造器/析构器,说明它持有的对象很有可能没有按照指针大小对齐,我们很难检测到所有的对象
如果 block 没有 dispose 函数,说明它无法 retain 对象,也就是说我们也没有办法测试其强引用了哪些对象
*/
if ((blockLiteral->flags & BLOCK_HAS_CTOR)
|| !(blockLiteral->flags & BLOCK_HAS_COPY_DISPOSE)) {
return nil;
}
// 从 BlockDescriptor 取出 dispose_helper 及大小
void (*dispose_helper)(void *src) = blockLiteral->descriptor->dispose_helper;
const size_t ptrSize = sizeof(void *);
// Figure out the number of pointers it takes to fill out the object, rounding up.
// 获取 block 持有的指针的数量
const size_t elements = (blockLiteral->descriptor->size + ptrSize - 1) / ptrSize;
// Create a fake object of the appropriate length.
// 初始化两个包含 elements 个 FBBlockStrongRelationDetector 实例的数组,其中第一个数组用于传入 dispose_helper,第二个数组用于检测 _strong 是否被标记为 YES
void *obj[elements];
void *detectors[elements];
for (size_t i = 0; i < elements; ++i) {
// 用于从 dispose_helper 接收消息;它的实例在接收 release 消息时,并不会真正的释放,只会将标记 _strong 为 YES
// 因为这个文件重写了 release 方法,所以要在非 ARC 下编译
FBBlockStrongRelationDetector *detector = [FBBlockStrongRelationDetector new];
obj[i] = detectors[i] = detector;
}
/*
伪造Block持有的对象,然后将其释放。
在自动释放池中执行 dispose_helper(obj),释放 block 持有的对象,
因为 dispose_helper 会调用 release 方法,
我们重写 FBBlockStrongRelationDetector 的 release 方法,
根据release 是都被调用,来标记 _strong 属性,判断是否为强引用
将强引用对象对应索引加入数组,最后再调用 trueRelease 真正的释放对象
*/
@autoreleasepool {
/*
猜测析构过程是查找原对象是否是强引用,如果是强引用,调用release,
而实际上是伪造对象接收了release消息,不仅自己没释放,还记录为强引用
等获取到所有强引用的位置关系之后,才真正释放掉伪造对象
(仅仅是个人猜测,大家可以查看源码深入了解,参考资料后面也贴出来了)
*/
dispose_helper(obj);
}
// Run through the release detectors and add each one that got released to the object's
// strong ivar layout.
// 返回强引用的位置关系集合,最后通过位置关系,获取对应的强引用对象
NSMutableIndexSet *layout = [NSMutableIndexSet indexSet];
for (size_t i = 0; i < elements; ++i) {
FBBlockStrongRelationDetector *detector = (FBBlockStrongRelationDetector *)(detectors[i]);
if (detector.isStrong) {
[layout addIndex:i];
}
// Destroy detectors
[detector trueRelease];
}
return layout;
}
更详细的内容,请参考iOS 中的 block 是如何持有对象的
这里使用了一个黑盒技术。创建一个对象来假扮想要调查的Block。因为知道Block的接口,知道在哪可以找到Block持有的引用。伪造的对象将会拥有“释放检测(release detectors)”来代替这些引用。释放检测器是一些很小的对象,这里是FBBlockStrongRelationDetector,它们会观察发送给它们的释放消息。当持有者想要放弃它的持有的时候,这些消息会发送给强引用对象。当释放伪造的对象的时候,可以检测哪些检测器接收到了这些消息。只要知道哪些索引在伪造的对象的检测器中,就可以找到原来Block中实际持有的对象。(出自:FBRetainCycleDetector源码分析)
target
和userInfo
,通过CFRunLoopTimerGetContext
获取target
和userInfo
即可。可以看到检测引用循环的效率并不高,而且只能检测某个确定的对象,比较鸡肋。所以我们应该尽可能的避免引用循环,避免内存泄漏。此外可以通过xcode
自带工具Memory Graph Debugger
来检测,这种方法可能会带来误判,需要review
代码来确认
1.CF对象
ARC不能管理Core Foundation
对象的生命周期。所以CF对象需要自己管理内存。注意以creat
,copy
作为关键字的函数都是需要释放内存的,注意配对使用。比如:CGColorCreate<-->CGColorRelease
// (ABRecordRef)person 需要自己释放
NSString *name = (__bridge NSString *)ABRecordCopyCompositeName((ABRecordRef)person); //获取联系人姓名
CFRelease
或者相关方法来释放对象。需要注意的是,在调用该函数前要对对象进行NULL
检查// dictionaryRef 需要自己释放
CFDictionaryRef dictionaryRef = (__bridge_retained CFDictionaryRef)self.dict
2.NSTimer
_timer
会持有target
,如果target
也持有_timer
,会造成引用循环
[_timer invalidate];
_timer = nil;
3.通知
The return value is retained by the system, and should be held onto by the caller in order to remove the observer with removeObserver: later, to stop observation.
- (void)addObserver {
// system ->returnValue ->defaultCenter ->Block ->self
// ->Block--------------------↑
// 上面的引用内存是该方法做的注释。要求我们手动移除观察者
self.observer = [[NSNotificationCenter defaultCenter] addObserverForName:@"NotificationCycle" object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification * _Nonnull note) {
NSLog(@"%s, %@", __func__, self);
}];
// 推荐使用此方法
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(doSomething:) name:@"NotificationUserChange" object:nil];
}
移除通知
- (void)removeObserver {
[[NSNotificationCenter defaultCenter] removeObserver:self.observer name:@"NotificationCycle" object:nil];
[[NSNotificationCenter defaultCenter] removeObserver:self];
self.observer = nil;
}
4.Block
异步执行 需要在Block
内部,将weakSelf
转成strongSelf
__weak typeof(self) weakSelf = self;
self.changeBlcok = ^{
[weakSelf doSomething]; // 非异步操作 weakSelf调用可以执行
/** Block内异步执行
self在Block完全执行完之前已经释放,weakSelf也会释放
__strong修饰的strongSelf是Block内部的一个局部变量,也就是说作用域仅在Block内部,一旦跳出作用域,那么我们强制产生的临时“循环引用”就会被打破
*/
__strong typeof(weakSelf) strongSelf = weakSelf;
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[NSThread sleepForTimeInterval:5];
[strongSelf doSomething]; // weakSelf调用不会被执行
});
};
5.NSURLSession delegate
The session object keeps a strong reference to the delegate until your app exits or explicitly invalidates the session. If you do not invalidate the session by calling the invalidateAndCancel or finishTasksAndInvalidate method, your app leaks memory until it exits.
NSURLSession
的 delegate
被强引用,使用完需要调用invalidateAndCancel
或finishTasksAndInvalidate
dispatch_queue_t queue = dispatch_queue_create("SessionQueue", DISPATCH_QUEUE_SERIAL);
[NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration] delegate:self delegateQueue:queue];
6.地图类处理
- (void)clearMapView{
self.mapView = nil;
self.mapView.delegate =nil;
self.mapView.showsUserLocation = NO;
[self.mapView removeAnnotations:self.annotations];
[self.mapView removeOverlays:self.overlays];
[self.mapView setCompassImage:nil];
}
7.大次数循环导致内存暴涨,该循环内产生大量的临时对象,需在循环中创建自己的autoReleasePool,及时释放占用内存大的临时变量
for (int i = 0; i < 100000; i++) {
// 字符串所占内存极小,不明显
@autoreleasepool {
NSString *string = @"Abc";
string = [string lowercaseString];
string = [string stringByAppendingString:@"xyz"];
NSLog(@"%@", string);
}
}
8.malloc的使用
使用较少,一般使用CommonCrypto
库加密用的多一些。正常情况下我们在函数中使用malloc
一般都会对应free
,但在使用将malloc
申请的内存作为返回值的函数时,很有可能遗忘对内存的释放。建议在使用返回指针的函数时要特别注重这类问题,同时函数的文档中也需要特别指出返回值需要调用者手动释放,避免调用者遗忘。
同时调用者在进行free
之前,需要对指针进行NULL
检测
9.NSURLSessionTask及其子类
在生成NSURLSessionTask
及其子类对象时,该对象会处于挂起状态,此时该对象会一直常驻内存,若代码失去对该对象的引用,那么就会造成内存泄漏。
在代码对NSURLSessionTask
及其子类对象失去引用前,需要为该对象调用cancel
或resume
方法,使之脱离挂起状态。