简单VC内存检测
栏目: Objective-C · 发布时间: 5年前
内容简介:现在只检测OC对象应该查询利用
-
class_copyIvarList
: 只是返回本类的实例变量,父类的实例变量不会返回。 -
在
NSArray
的enumeration block
中,return
并不能阻止其循环,只有*stop = YES
可以保证退出循环遍历
NSArray *array = @[@"1", @"2"]; [array enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { NSLog(@"%@", obj); return; }]; NSLog(@"hahahha"); ///// 依然会输出每个元素,在打印hahaha 复制代码
需要检测的项
1、ivar list
现在只检测OC对象
2、timer (NSTimer, Dispatch_Source, displayLink)
应该查询
利用 clang
来进行前端编译,看是否可以知道一些端倪
1、根据clang --help 命令来查看clang的用法,但是命令太多我们可以使用 clang --help | grep Object 来缩小我们查看的范围,这样就可以一目了然的查看应该需要哪个命令对源文件进行转变 2、-rewrite-objc Rewrite Objective-C source to C++ 根据查找到线索我们开始对main.m文件进行编译 clang -rewrite-objc main.m 很遗憾的是报错了: warning: include path for stdlibc++ headers not found; pass '-std=libc++' on the command line to use the libc++ standard library instead [-Wstdlibcxx-not-found] main.m:9:9: fatal error: 'UIKit/UIKit.h' file not found #import <UIKit/UIKit.h> 解决: 经过网上查找使用一下命令:clang -x objective-c -rewrite-objc -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator.sdk main.m -x 接下来输入的文件是什么类型的语言 -isysroot 指定系统路径 如果觉命令长可以使用 别名 (alias),,在~/.bash_profile文件中声明: alias rewriteoc=clang -x objective-c -rewrite-objc -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator.sdk source ~/.bash_profile 很遗憾的是当我们运行的时候并没有我们想要的文件,在从网上查找 //指定真机的sdk xcrun -sdk iphoneos clang -rewrite-objc main.m 指定模拟器sdk xcrun -sdk iphonesimulator clang -rewrite-objc main.m 指定模拟器具体的sdk xcrun -sdk iphonesimulator10.3 clang -rewrite-objc main.m 复制代码
clang
命令之后生成的文件
查看 CF源码
当一个 timer
添加到 runloop
中的时候, timer
就会被 runloop
强引用, 而 timer
的 target
会被 timer
所强引用,那么现在问题我们怎么样找见这个强引用在什么地方。我们通过查看CF源码,很大的几率是存放在 info
中,但是 info
是一个 void *
指针。所以我们应该查看一下这个info存放了什么?
CFRunLoopTimerRef timerRef = (__bridge CFRunLoopTimerRef)timer; CFRunLoopTimerContext cxt; CFRunLoopTimerGetContext(timerRef, &cxt); void * info = cxt.info; //打印的是每个字节存放的数字 //我们会发现从第二个字节开始,后8个字节是target的地址 for (int i = 1;i < 100; i ++) { char a = *((char *)(info + i -1)); printf("%x ", a); if (i != 0 && i % 8 == 0) { printf("\n"); } } 复制代码
根据上面代码我们把 info
转换成一个 struct
typedef struct mc_info { char a; void * objc; //表示target }mc_info; 复制代码
3、block
- 简单介绍block的在代码中形式:
void(^block)(void) = ^(void){NSLog(@"%d", a);};
其实变量block
是一个对象指针,我们可以通过objc_getClass()
或者[block class]
的方法来查看他是否一个对象。
// 这段代码没有crash,并且返回了值。这就表明了block是一个对象指针。而且系统是吧block包装成了一个对象 void(^block)(void) = ^(void){NSLog(@"%d", a);}; Class cls = [block class]; 复制代码
- 请看接下来一个问题:那么我们怎么判断一个指针是一个 BLOCK 对象指针呢?那可以很自然的想到我们在判断一个对象是不是
NSObject
的方法:isKindOfClass:
。这方法的需要一个参数Class
,那么我们怎么样找BLOCK
对应的Class,那我们可以不可以就用上面的[block class]
来作为参数呢?答应是否定的。为什么呢?因为OC中有 类簇 的概念。我们也知道block有三种类型malloc block
,global block
stack block
。这三个类是兄弟关系,他们是继承与一个 根类 ,那么我们现在就是要找到这个 根类 来作为isKindOfClass
的参数。
void(^block)(void) = ^(void){NSLog(@"%d", a);}; Class cls = [block class]; while( cls != NULL && class_getSuperclass(cls) != [NSObject class]) { cls = class_getSuperclass(cls); } NSLog(@"%@", cls); 复制代码
如果说你是一个指针对象,那么最后的 super class
一定是 [NSObject class]
,你可以通过runtime的那张表可以看出来。所以继承与 NSOject
的那个类一定是BLOCK的根类。
- 如何查找block是否引用我们检测的对象?
查看circle代码,太难了。 学习的点:
.cxx_destruct
这是编译器帮忙加上的代码,这个代码就是帮助释放实例变量的。例如我们MRC的环境下
- (void)dealloc { release(_name); } 复制代码
而 .cxx_destruct
就相当于执行上面的代码。stack overflow, stack overflow网站中有点错误,就是利用clang生成MRC代码参数错误,再次纠正一下 clang -fno-objc-arc main.m -o test -framework foundation
class—dump
用来查找反编译文件下载地址,可以把 class-dump
文件放在 ~/bin
文件下,并且把 ~/bin
文件放在 $PATH
环境变量里面。
vim ~/.bash_profile #粘贴下面语句到bash_profile文中 export PATH='~/bin:$PATH' 复制代码
通过一个类文件作为实验
发现:当类没有strong修饰的实例变量的时候,这个函数是没有的。
lldb 的命令,通过 watchpoint
来查询实例变量是否会被修改
watchpoint set variable self->_b // self->_b,代表某个实例变量,我们可以不通过KVO进行观察了 复制代码
当block结构中含有含有指针实例变量(__block修饰的),非基本类型(int , bool)。flag 是 570425344
, 而其他是 0
,当非0的时,block中desc结构中是含有 copy
和 dispose
函数的。
我们拿到了一个对象,在OC中对象都是用指针来表示的。明白一个概念当指针进行加减法操作的时候锁增加或者减少的字节数是 被加数(被减数)乘以 当前指针所指向的字节数 ,在 Circle
这个程序中,首先把一个存放指针的数组伪装成一个对象,为什么是存放指针的数组呢?因为我们的对象都是指针应用的,而且也有内存对齐的原则。这个数组进行 释放 ,如果其中某个元素被释放了,那么这个元素所在的idx,就是这个对象中强引用实例变量的相对对象地址的偏移量。之后我们在把这个对象伪装成数组,用idx进行指针偏移。如果对一个指向对象的指针进行偏移呢?因为开始我们用的是一个存放指针的数组,那么我们在进行偏移的时候,也要把对象指针转成成一个存放指针的数组,也就是 void **p
,让后我们用 p+ idx
可以获取到强引用的实例变量指针,之后我们进行取值 *(p + idx)
,这样我们就可以得到实例变量了。
如何向一个对象的实例变量赋值,通知指针的方式:
@interface Person : NSObject @property (nonatomic, strong) NSString *name; @property (nonatomic, assign) int a; @end { Person *person = [[Person alloc] init]; unsigned int count; Ivar *ivar = class_copyIvarList(Person.class, &count); for (int i = 0 ; i < count; i ++) { Ivar currentIvar = ivar[i]; // 获取改实例变量相对于对象指针的偏移量,这个偏移量表示的几个字节 ptrdiff_t offset = ivar_getOffset(currentIvar); NSLog(@"ivar name - %@, offset-- %d", [NSString stringWithUTF8String:ivar_getName(currentIvar)], offset); // ivar name - _a, offset-- 8 偏移8个字节 ivar name - _name, offset-- 16 偏移16个字节 } id person_void = (id)person; char *a = (__bridge void *)person_void + 8; *a = 10; // 指明不进行强引用,不进行内存管理 __unsafe_unretained id * b = (__unsafe_unretained id *)((__bridge void *)person + 16); NSObject *name = [NSString stringWithUTF8String:"123"]; *b = name; NSLog(@"person : %@---- ", person.name); } 复制代码
1、当我们相对一个指针进行偏移的,这时候我们应该知晓我们想要偏移多少个字节,这样我们就把这个指针转化什么类型的指针
2、当用malloc申请一片内存,而非使用new alloc 这种方式生成的时候,我们在把这个 void *
指针转向 OC对象指针的时候,我们一定加入 unsafe_unreatined
权限修饰符
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:- 如何检测C++内存泄漏
- Spring 内存木马检测思路
- 如何检测及预防C++内存泄漏
- 内存二三事: Xcode 内存图、Instruments 可视化检测循环引用
- rails - 为rails消耗的内存做检测 memory leak 检测工具 derailed_benchmarks
- Unity手游开发札记——ToLua#集成内存泄露检查和性能检测工具
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。