简单VC内存检测

iOS tip

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

  • NSArrayenumeration block中,return并不能阻止其循环,只有*stop = YES能够保证退出循环遍历ios

NSArray *array = @[@"1", @"2"];
[array enumerateObjectsUsingBlock:^(id  _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
        NSLog(@"%@", obj);
        return;
}];
NSLog(@"hahahha");
///// 依然会输出每一个元素,在打印hahaha
复制代码

须要检测的项

一、ivar list

如今只检测OC对象c++

二、timer (NSTimer, Dispatch_Source, displayLink)

应该查询objective-c

利用clang来进行前端编译,看是否能够知道一些端倪vim

一、根据clang --help 命令来查看clang的用法,可是命令太多咱们可使用
clang --help | grep Object 来缩小咱们查看的范围,这样就能够一目了然的查看应该须要哪一个命令对源文件进行转变

二、-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命令以后生成的文件bash

查看CF源码app

当一个timer添加到runloop中的时候,timer就会被runloop强引用, 而timertarget会被timer所强引用,那么如今问题咱们怎么样找见这个强引用在什么地方。咱们经过查看CF源码,很大的概率是存放在 info中,可是info是一个void *指针。因此咱们应该查看一下这个info存放了什么?iphone

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;

复制代码

三、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);
}
复制代码

一、当咱们相对一个指针进行偏移的,这时候咱们应该知晓咱们想要偏移多少个字节,这样咱们就把这个指针转化什么类型的指针

二、当用malloc申请一片内存,而非使用new alloc 这种方式生成的时候,咱们在把这个void * 指针转向 OC对象指针的时候,咱们必定加入unsafe_unreatined权限修饰符

四、关联 association

相关文章
相关标签/搜索