简单VC内存检测

iOS tip

  • class_copyIvarList: 只是返回本类的实例变量,父类的实例变量不会返回。

  • NSArrayenumeration 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

复制代码

xcrun命令讲解

clang命令之后生成的文件

查看CF源码

当一个timer添加到runloop中的时候,timer就会被runloop强引用, 而timertarget会被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 blockglobal 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的根类。

rutime

  • 如何查找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结构中是含有copydispose函数的。

我们拿到了一个对象,在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权限修饰符

4、关联 association

猜你喜欢

转载自juejin.im/post/5c36dd4ff265da61483bce12