среда, 4 января 2012 г.

MemCheck for iOS v 2.0 in english

Memory leaks are frequent problem in iOS projects. I have developed a lib which catches leaked objects and visualizes their relationships between each other.

All interactions occur through a Xcode console. Press pause in a Xcode debugger and input something like that:  

po [parser run:@"leaks saveGraph /Users/yourName/Documents/dot/mem.txt"]

The command generate mem.txt file for a dot tool (http://www.graphviz.org/). A dot must be installed. Then we can convert mem.txt to dot file by:

dot -Tdot mem.txt -o mem.dot && open ./mem.dot 

That will show graph with live and died objects from your application at current moment. Some examples:



An arrow from the DetailViewController to the UILabel means that an object of DetailViewController contains an object of UILabel. A dotted arrow shows when an object dead. Live objects have a white background, dead objects have a gray background.

About a parser command format and special filter (fromList) which remove all unnecessary objects you can read on memCheck-for-iOS git hub page.

Happy Using :)

воскресенье, 6 марта 2011 г.

MemCheck description in english

MemCheck helps to find memory leaks in your iOS applications. It can register alloc, retain and release calls for NS-objects and show detail information in console.

Sources

https://github.com/soniccat/memCheck-for-iOS

Connect the library

Add 6 files to your project:

NSMemCheckObject.h
NSMemCheckObject.m
NSMutableArray+MemCheck.h
NSMutableArray+MemCheck.m
NSObject+MemCheck.h
NSObject+MemCheck.m
add in the tail of your pch file

#define MEMTEST_ON

add in the top of the application: didFinishLaunchingWithOptions: function

#ifdef MEMTEST_ON
[NSObject turnMemCheckOn];
#endif

Using

When you app is running in any moment you can press the pause button and print in the Xcode's console:

po [memData allMem]

14 items

(
"2011-02-23 06:47:04 +0000 memCheckObject 0x4b45840 object 0x4b45530 stack 0x4b45a00 NSCountedSet",
"2011-02-23 06:47:04 +0000 memCheckObject 0x4e16040 object 0x4e15d00 stack 0x4e15c40 NSAutoreleasePool",
"2011-02-23 06:47:04 +0000 memCheckObject 0x4e10cc0 object 0x4b0de50 stack 0x4e14ef0 __NSPlaceholderDictionary",
"2011-02-23 06:47:04 +0000 memCheckObject 0x4b44ab0 object 0x4b44a90 stack 0x4b42d10 CALayerArray",
"2011-02-23 06:47:04 +0000 memCheckObject 0x4e16cd0 object 0x4e02b80 stack 0x4e16f40 __NSPlaceholderArray",
"2011-02-23 06:47:04 +0000 memCheckObject 0x4e151d0 object 0x4e14ec0 stack 0x4e16380 NSKeyValueMethodSetter",
"2011-02-23 06:47:04 +0000 memCheckObject 0x4e15040 object 0x4e15190 stack 0x4e16060 CALayer",
"2011-02-23 06:47:04 +0000 memCheckObject 0x4e0faf0 object 0x4e08960 stack 0x4e15d20 __NSPlaceholderSet",
"2011-02-23 06:47:04 +0000 memCheckObject 0x4e14de0 object 0x4b10790 stack 0x4e14f60 NSPlaceholderString",
"2011-02-23 06:47:04 +0000 memCheckObject 0x4e06c60 object 0x4b3fdc0 stack 0x4e106f0 NSAutoreleasePool",
"2011-02-23 06:47:04 +0000 memCheckObject 0x4b41cd0 object 0x4b41750 stack 0x4b43450 __NSPlaceholderDate",
"2011-02-23 06:47:04 +0000 memCheckObject 0x4b44100 object 0x4b440f0 stack 0x4b440a0 __NSPlaceholderTimeZone",
"2011-02-23 06:47:04 +0000 memCheckObject 0x4b43680 object 0x4e02b70 stack 0x4b438f0 __NSPlaceholderArray",
"2011-02-23 06:47:04 +0000 memCheckObject 0x4b41960 object 0x4b02420 stack 0x4b43750 NSObject"

)

14 is all registered objects, which exist in memory at this moment. The list starts with last allocated object.
an address after memCheckObject point to wrapper object
an address after object point to allocated object
an address after stack point to alloc callstack
in end of line allocated an object's class name is displayed

po [memData top:N]

return only N last allocated objects

po address_of_object

display a defaul object's description

po address_of_callstack

display a stack like that

<_nscallstackarray>(
0 CoreFoundation 0x00da9be9 __exceptionPreprocess + 185,
1 libobjc.A.dylib 0x00efe5c2 objc_exception_throw + 47,
2 inFoundation 0x00002796 +[NSObject(memCheck) myAllocFunc] + 918,
3 inFoundation 0x00001c81 -[inFoundationAppDelegate application:didFinishLaunchingWithOptions:] + 161,
4 UIKit 0x002b31fa -[UIApplication _callInitializationDelegatesForURL:payload:suspended:] + 1163,
...
)

po [address_of_wrapper_object history]

display a callstacks for alloc, retain and release functions, ordered by date. It shows like that:

ALLOC:

2011-02-23 06:26:56 +0000

(

0 CoreFoundation 0x00dacbe9 __exceptionPreprocess + 185

1 libobjc.A.dylib 0x00f015c2 objc_exception_throw + 47

2 memCheck 0x0000310f +[NSObject(memCheck) myAllocFunc] + 831

3 memCheck 0x000024d2 -[inFoundationAppDelegate application:didFinishLaunchingWithOptions:] + 98

4 UIKit 0x002b61fa -[UIApplication _callInitializationDelegatesForURL:payload:suspended:] + 1163

...

)

RETAIN:

2011-02-23 06:26:56 +0000

(

0 CoreFoundation 0x00dacbe9 __exceptionPreprocess + 185

1 libobjc.A.dylib 0x00f015c2 objc_exception_throw + 47

2 memCheck 0x000034cf -[NSObject(memCheck) myRetainFunc] + 335

3 CoreFoundation 0x00cbf0bc CFRetain + 92

4 CoreFoundation 0x00da5db5 +[__NSArrayI __new::] + 117

5 CoreFoundation 0x00d188a3 +[NSArray arrayWithObject:] + 67

6 memCheck 0x00002508 -[inFoundationAppDelegate application:didFinishLaunchingWithOptions:] + 152

7 UIKit 0x002b61fa -[UIApplication _callInitializationDelegatesForURL:payload:suspended:] + 1163

...

)

RETAIN:

2011-02-23 06:26:56 +0000

(

0 CoreFoundation 0x00dacbe9 __exceptionPreprocess + 185

1 libobjc.A.dylib 0x00f015c2 objc_exception_throw + 47

2 memCheck 0x000034cf -[NSObject(memCheck) myRetainFunc] + 335

3 memCheck 0x00002538 -[inFoundationAppDelegate application:didFinishLaunchingWithOptions:] + 200

4 UIKit 0x002b61fa -[UIApplication _callInitializationDelegatesForURL:payload:suspended:] + 1163

...

)

RELEASE:

2011-02-23 06:26:56 +0000

(

0 CoreFoundation 0x00dacbe9 __exceptionPreprocess + 185

1 libobjc.A.dylib 0x00f015c2 objc_exception_throw + 47

2 memCheck 0x000037ee -[NSObject(memCheck) myReleaseFunc] + 302

3 memCheck 0x0000258d -[inFoundationAppDelegate application:didFinishLaunchingWithOptions:] + 285

4 UIKit 0x002b61fa -[UIApplication _callInitializationDelegatesForURL:payload:suspended:] + 1163

...

)

RETAIN:

2011-02-23 06:26:56 +0000

(

0 CoreFoundation 0x00dacbe9 __exceptionPreprocess + 185

1 libobjc.A.dylib 0x00f015c2 objc_exception_throw + 47

2 memCheck 0x000034cf -[NSObject(memCheck) myRetainFunc] + 335

3 CoreFoundation 0x00cbf0bc CFRetain + 92

4 CoreFoundation 0x00da5db5 +[__NSArrayI __new::] + 117

5 CoreFoundation 0x00d188a3 +[NSArray arrayWithObject:] + 67

6 memCheck 0x000025ae -[inFoundationAppDelegate application:didFinishLaunchingWithOptions:] + 318

7 UIKit 0x002b61fa -[UIApplication _callInitializationDelegatesForURL:payload:suspended:] + 1163

...

)

po [address_of_wrapper_object retains] and po [address_of_wrapper_object releases]

return callstacks for all retain or release calls, started with last.

po [memData markHeap]

save a current date which divide the allocated object list on earlier and later groups

po [memData showHeaps]

show allocated groups divided by saved dates

po [memData objectsForHeap:n]

return an allocated object list in selected group

Обновление memCheck for iOS v 1.1

Добавил команды для работы c heap-ами на подобии Allocations Instrument

po [memData markHeap]
сохраняет текущую дату разбивая тем самым список объектов на созданных до и после

po [memData showHeaps]
выводит список групп из выделенных объектов разбитый с помощью markHeap

po [memData objectsForHeap:n]выводит список созданных объектов для указанной группы

Обновленное описание - http://gaolife.blogspot.com/2011/02/iphone.html

github репозиторий - https://github.com/soniccat/memCheck-for-iOS

вторник, 22 февраля 2011 г.

Обновление memCheck for iOS

Теперь можно запросить callstak-и до всех release и retain вызовов с помощью po [0xaddress retains] и po [0xaddress releases] соответственно. Для отображения всех вызово в хронологическом порядке для одного объекта добавил команду po [0xaddress history].

Обновленное описание - http://gaolife.blogspot.com/2011/02/iphone.html

github репозиторий - https://github.com/soniccat/memCheck-for-iOS

воскресенье, 20 февраля 2011 г.

Мультитекстурирование в OpenGL ES на iPhone


Пример мультитекстурирования на OpenGL ES в iPhone. Уже не помню как там что работает, главное что работает :)

Новая версия библиотеки для поиска утечек памяти в iOS приложениях

Краткое описание

В процессе разработки приложений для iPhone очень часто возникают проблемы с утечками памяти, увы стандартные инструменты не сильно помогают в их устранении, поэтому я написал эту библиотеку. С ее помощью можно узнать сколько объектов в текущий момент находится в памяти, какого они класса и в где были созданы.

Исходники

https://github.com/soniccat/memCheck-for-iOS

Подключение

добавить все 6 файлов к проекту
NSMemCheckObject.h
NSMemCheckObject.m
NSMutableArray+MemCheck.h
NSMutableArray+MemCheck.m
NSObject+MemCheck.h
NSObject+MemCheck.m

добавить в метод application: didFinishLaunchingWithOptions:

#ifdef MEMTEST_ON

[NSObject turnMemCheckOn];

#endif

вниз вашего .pch

#define MEMTEST_ON

Использование

В момент когда нужно отобразить занимаемую память нажать на паузу в Debugger-е и ввести в gdb

po [memData allMem]
будет ответ вида

14 items

(
"2011-02-23 06:47:04 +0000 memCheckObject 0x4b45840 object 0x4b45530 stack 0x4b45a00 NSCountedSet",
"2011-02-23 06:47:04 +0000 memCheckObject 0x4e16040 object 0x4e15d00 stack 0x4e15c40 NSAutoreleasePool",
"2011-02-23 06:47:04 +0000 memCheckObject 0x4e10cc0 object 0x4b0de50 stack 0x4e14ef0 __NSPlaceholderDictionary",
"2011-02-23 06:47:04 +0000 memCheckObject 0x4b44ab0 object 0x4b44a90 stack 0x4b42d10 CALayerArray",
"2011-02-23 06:47:04 +0000 memCheckObject 0x4e16cd0 object 0x4e02b80 stack 0x4e16f40 __NSPlaceholderArray",
"2011-02-23 06:47:04 +0000 memCheckObject 0x4e151d0 object 0x4e14ec0 stack 0x4e16380 NSKeyValueMethodSetter",
"2011-02-23 06:47:04 +0000 memCheckObject 0x4e15040 object 0x4e15190 stack 0x4e16060 CALayer",
"2011-02-23 06:47:04 +0000 memCheckObject 0x4e0faf0 object 0x4e08960 stack 0x4e15d20 __NSPlaceholderSet",
"2011-02-23 06:47:04 +0000 memCheckObject 0x4e14de0 object 0x4b10790 stack 0x4e14f60 NSPlaceholderString",
"2011-02-23 06:47:04 +0000 memCheckObject 0x4e06c60 object 0x4b3fdc0 stack 0x4e106f0 NSAutoreleasePool",
"2011-02-23 06:47:04 +0000 memCheckObject 0x4b41cd0 object 0x4b41750 stack 0x4b43450 __NSPlaceholderDate",
"2011-02-23 06:47:04 +0000 memCheckObject 0x4b44100 object 0x4b440f0 stack 0x4b440a0 __NSPlaceholderTimeZone",
"2011-02-23 06:47:04 +0000 memCheckObject 0x4b43680 object 0x4e02b70 stack 0x4b438f0 __NSPlaceholderArray",
"2011-02-23 06:47:04 +0000 memCheckObject 0x4b41960 object 0x4b02420 stack 0x4b43750 NSObject"

)

где 14 это кол-во объектов создание которых было зарегистрировано (вверху самый новый), после memCheckObject идет адрес на объект который сохраняет данные о выделенных объектах, после object идет адрус объекта, после stack адрес на получение стека до места где объект был создан и в конце имя класса самого объекта

po [memData top:10]
возвращает список из последних 10 выделенных объектов, число можно менять на какое вздумается

po 0x4b02420
вернет описание объекта

po 0x4b43750
Вернет стек до места создания:

<_nscallstackarray>(
0 CoreFoundation 0x00da9be9 __exceptionPreprocess + 185,
1 libobjc.A.dylib 0x00efe5c2 objc_exception_throw + 47,
2 inFoundation 0x00002796 +[NSObject(memCheck) myAllocFunc] + 918,
3 inFoundation 0x00001c81 -[inFoundationAppDelegate application:didFinishLaunchingWithOptions:] + 161,
4 UIKit 0x002b31fa -[UIApplication _callInitializationDelegatesForURL:payload:suspended:] + 1163,
...
)

значимая строчка для нас это 4ая, именно в application: didFinishLaunchingWithOptions объект и был создан

Самый простой алгоритм поиска утечек памяти следующий. Запоминаем количество выделенных объектов, заходим в контроллер, жмем back, смотрим сколько объектов теперь, если их стало больше то уже внимательнее смотрим на callstack n верхних элементов, где n это количество новых объектов.

po [0x4b41960 history]
возвращает callstack-и до вызовов alloc, retain и release начиная с самого старого
ALLOC:

2011-02-23 06:26:56 +0000

(

0 CoreFoundation 0x00dacbe9 __exceptionPreprocess + 185

1 libobjc.A.dylib 0x00f015c2 objc_exception_throw + 47

2 memCheck 0x0000310f +[NSObject(memCheck) myAllocFunc] + 831

3 memCheck 0x000024d2 -[inFoundationAppDelegate application:didFinishLaunchingWithOptions:] + 98

4 UIKit 0x002b61fa -[UIApplication _callInitializationDelegatesForURL:payload:suspended:] + 1163

...

)

RETAIN:

2011-02-23 06:26:56 +0000

(

0 CoreFoundation 0x00dacbe9 __exceptionPreprocess + 185

1 libobjc.A.dylib 0x00f015c2 objc_exception_throw + 47

2 memCheck 0x000034cf -[NSObject(memCheck) myRetainFunc] + 335

3 CoreFoundation 0x00cbf0bc CFRetain + 92

4 CoreFoundation 0x00da5db5 +[__NSArrayI __new::] + 117

5 CoreFoundation 0x00d188a3 +[NSArray arrayWithObject:] + 67

6 memCheck 0x00002508 -[inFoundationAppDelegate application:didFinishLaunchingWithOptions:] + 152

7 UIKit 0x002b61fa -[UIApplication _callInitializationDelegatesForURL:payload:suspended:] + 1163

...

)

RETAIN:

2011-02-23 06:26:56 +0000

(

0 CoreFoundation 0x00dacbe9 __exceptionPreprocess + 185

1 libobjc.A.dylib 0x00f015c2 objc_exception_throw + 47

2 memCheck 0x000034cf -[NSObject(memCheck) myRetainFunc] + 335

3 memCheck 0x00002538 -[inFoundationAppDelegate application:didFinishLaunchingWithOptions:] + 200

4 UIKit 0x002b61fa -[UIApplication _callInitializationDelegatesForURL:payload:suspended:] + 1163

...

)

RELEASE:

2011-02-23 06:26:56 +0000

(

0 CoreFoundation 0x00dacbe9 __exceptionPreprocess + 185

1 libobjc.A.dylib 0x00f015c2 objc_exception_throw + 47

2 memCheck 0x000037ee -[NSObject(memCheck) myReleaseFunc] + 302

3 memCheck 0x0000258d -[inFoundationAppDelegate application:didFinishLaunchingWithOptions:] + 285

4 UIKit 0x002b61fa -[UIApplication _callInitializationDelegatesForURL:payload:suspended:] + 1163

...

)

RETAIN:

2011-02-23 06:26:56 +0000

(

0 CoreFoundation 0x00dacbe9 __exceptionPreprocess + 185

1 libobjc.A.dylib 0x00f015c2 objc_exception_throw + 47

2 memCheck 0x000034cf -[NSObject(memCheck) myRetainFunc] + 335

3 CoreFoundation 0x00cbf0bc CFRetain + 92

4 CoreFoundation 0x00da5db5 +[__NSArrayI __new::] + 117

5 CoreFoundation 0x00d188a3 +[NSArray arrayWithObject:] + 67

6 memCheck 0x000025ae -[inFoundationAppDelegate application:didFinishLaunchingWithOptions:] + 318

7 UIKit 0x002b61fa -[UIApplication _callInitializationDelegatesForURL:payload:suspended:] + 1163

...

)

po [0x4b41960 retains] и po [0x4b41960 releases]
возвращают callstack-и для вызовов retain и release соответственно, начиная с самого раннего

po [memData markHeap]
сохраняет текущую дату разбивая тем самым список объектов на созданных до и после

po [memData showHeaps]
выводит список групп из выделенных объектов разбитый с помощью markHeap

po [memData objectsForHeap:n]
выводит список созданных объектов для указанной группы

Ограничения

работают только с iOS 4.0 и выше ( из за доступа к стеку )

Описание устройства

По прочтению умных статей c NSBlog :
http://www.mikeash.com/pyblog/friday-qa-2009-01-23.html
http://www.mikeash.com/pyblog/friday-qa-2009-03-13-intro-to-the-objective-c-runtime.html
http://www.mikeash.com/pyblog/friday-qa-2009-03-27-objective-c-message-forwarding.html

я понял как работает obj_msgSend, и что IMP это обычный указатель на функцию который можно подменить у любого метода NS объекта. В итоге сохранив старый IMP мы можем в вызове собственного метода вызвать базовый, что нельзя было сделать с помощью категорий.

При инициализации я подменяю базовые методы своими с помощью method_exchangeImplementations:

typedef id (*OverrideMemCheckPrototipe)(id,SEL);

#define ALLOC_METHOD_EXCHANGE method_exchangeImplementations(classAllocMethod, classMyAllocMethod)

#define RETAIN_METHOD_EXCHANGE method_exchangeImplementations(classRetainMethod, classMyRetainMethod)

+ (void)turnMemCheckOn

{

if( memData == nil )

memData = [[NSMutableArray allocWithZone:nil] init];

//alloc

classAllocMethod = class_getClassMethod([NSObject class], @selector(alloc) );

classAllocImp = method_getImplementation(classAllocMethod);

classMyAllocMethod = class_getClassMethod([NSObject class], @selector(myAllocFunc) );

classMyAllocImp = method_getImplementation(classMyAllocMethod);

//dealloc

classDeallocMethod = class_getInstanceMethod([NSObject class], @selector(dealloc) );

classDeallocImp = method_getImplementation(classDeallocMethod);

classMyDeallocMethod = class_getInstanceMethod([NSObject class], @selector(myDeallocFunc) );

classMyDeallocImp = method_getImplementation(classMyDeallocMethod);

//retain

classRetainMethod = class_getInstanceMethod([NSObject class], @selector(retain) );

classRetainImp = method_getImplementation(classRetainMethod);

classMyRetainMethod = class_getInstanceMethod([NSObject class], @selector(myRetainFunc) );

classMyRetainImp = method_getImplementation(classMyRetainMethod);

//release

classReleaseMethod = class_getInstanceMethod([NSObject class], @selector(release) );

classReleaseImp = method_getImplementation(classReleaseMethod);

classMyReleaseMethod = class_getInstanceMethod([NSObject class], @selector(myReleaseFunc) );

classMyReleaseImp = method_getImplementation(classMyReleaseMethod);

ALLOC_METHOD_EXCHANGE;

method_exchangeImplementations(classDeallocMethod, classMyDeallocMethod);

RETAIN_METHOD_EXCHANGE;

method_exchangeImplementations(classReleaseMethod, classMyReleaseMethod);

}

В своем методе я вызываю базовый передавая как и obj_msgSend первые два параметра id и SEL чтобы получить новый объект, если он новый то сохраняю его в глобальный массив memData в самое начало не увеличивая счетчика ссылок. Для получения callstack-а генерируется исключение и тут же ловится. На производительности в симуляторе это сказывается не сильно. ALLOC_METHOD_EXCHANGE вызывается для того чтобы при создании NSMemCheckObject и NSException не уйти в рекурсию.

+ (id) myAllocFunc

{

//call base implement

OverrideMemCheckPrototipe f = (OverrideMemCheckPrototipe)classAllocImp;

id newPt = f(self,@selector(myAllocFunc));

@synchronized( [NSObject class] )

{

if( ![newPt isKindOfClass:[NSMemCheckObject class]] )

{

BOOL found = NO;

for( NSMemCheckObject* obj in memData )

if( obj.pointerValue == newPt )

{

found = YES;

break;

}

if( !found )

{

ALLOC_METHOD_EXCHANGE;

NSMemCheckObject* addObj = [[[NSMemCheckObject alloc] initWithPointer:newPt] autorelease];

[memData insertObject:addObj atIndex:0];

//hack to get call stack

@try

{

@throw [NSException exceptionWithName:@"memTestException"

reason:@"get call stack"

userInfo:nil];

}

@catch (NSException * e)

{

addObj.callStack = [e callStackSymbols];

}

ALLOC_METHOD_EXCHANGE;

}

}

}

return newPt;

}

В реализации myDeallocFunc происходит простое удаление элемента из массива

- (void)myDeallocFunc

{

@synchronized( [NSObject class] )

{

int i = [memData count]-1;

while( i>=0 )

{

if( ((NSMemCheckObject*)[memData objectAtIndex:i]).pointerValue == self )

{

[memData removeObjectAtIndex:i];

//ShowMemData();

break;

}

--i;

}

}

//call base implement

OverrideMemCheckPrototipe f = (OverrideMemCheckPrototipe)classDeallocImp;

f(self,@selector(myDeallocFunc));

}

В myRetainFunc необходимо проверять какая имплементация находится в alloc, чтобы правильно подменить на оригинальную:
- (id)myRetainFunc

{

@synchronized( [NSObject class] )

{

BOOL needAllocExchange = ( method_getImplementation(classAllocMethod) == classMyAllocImp );


if( needAllocExchange )

ALLOC_METHOD_EXCHANGE;

RETAIN_METHOD_EXCHANGE;


NSMemCheckObject* addObj = [memData memCheckObjectByPointer:self];

//hack to get call stack

if( addObj )

{

@try

{

@throw [NSException exceptionWithName:@"memTestException"

reason:@"get call stack"

userInfo:nil];

}

@catch (NSException * e)

{

NSMemCheckRetainReleaseInfo* info = [[NSMemCheckRetainReleaseInfo alloc] init];

info.date = [NSDate date];

info.callStack = [e callStackSymbols];

[addObj.retainCallStackArray addObject:info];

[info release];

}

}

if( needAllocExchange )

ALLOC_METHOD_EXCHANGE;

RETAIN_METHOD_EXCHANGE;

}

//call base implement

OverrideMemCheckPrototipe f = (OverrideMemCheckPrototipe)classRetainImp;

return f(self,@selector(myRetainFunc));

}