簡單VC記憶體檢測
-
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-objcRewrite 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
許可權修飾符