簡單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

相關文章
相關標籤/搜索