使用 Instruments 面板的 iPhone 新手开发人员:我的泄漏在哪里?
更新
我已经更新了planets.m/h文件以反映评论者提出的建议(谢谢bbum和DarkDust。 我的工厂方法现在都调用 autorelease 并且看起来像这样:
- (Planet *) initWithName: (NSString *)name;
+ (Planet *) planetWithNameAndMoons:(NSString *)name moons:(id)firstObj, ... NS_REQUIRES_NIL_TERMINATION;
+ (Planet *) planetWithName:(NSString *)name;
+ (Planet *) planetWithResultSet:(FMResultSet *)resultSet;
现在我的 init 现在看起来像这样
- (id)init {
self = [super init];
self.Id = 0;
self.SupportsLife = NO;
self.Moons = [NSMutableArray array];
return self;
}
我在我的第二个控制器中从根调用planetWithName。这就是我使用它的方式
- (Planet *)newPlanet
{
int i = 0;
Planet *planet = nil;
NSMutableDictionary *criteria = [NSMutableDictionary new];
do {
i += 1;
[criteria setValue: [NSString stringWithFormat:@"Planet %d", i] forKey: @"Name"];
planet = [[PlanetRepo SharedRepo] Find: criteria];
} while (planet != nil); // if a planet is found with that name, keep looking...
planet = [Planet planetWithName: (NSString *)[criteria valueForKey:@"Name"]];
[planet retain];
[criteria release];
criteria = nil;
return planet;
}
这是我的 PlanetRepo 类(注意,它环绕 FMDB) PlanetRepo.h
#import <Foundation/Foundation.h>
#import "Planet.h"
@interface PlanetRepo : NSObject {
FMDatabase * _repoSource;
}
+ (PlanetRepo *) SharedRepo;
- (Planet *)Find:(NSDictionary *)criteria;
- (NSMutableArray *)GetAll;
- (Planet *)GetById:(id ) Id;
- (BOOL) Save:(Planet *) planet;
- (BOOL) Delete: (Planet *) planet;
- (BOOL) Open:(NSString *)repoSource;
- (void) Close;
@end
@interface PlanetRepo (Private)
- (NSError *) CreateDatabaseIfNeeded:(NSString *)databasePath;
@end
Planet.m
#import "FMDatabase.h"
#import "PlanetRepo.h"
#import "Planet.h"
@implementation PlanetRepo
+ (PlanetRepo *)SharedRepo
{
static PlanetRepo *_sharedRepo;
@synchronized(self)
{
if (!_sharedRepo)
_sharedRepo = [[super alloc] init];
}
return _sharedRepo;
}
+ (id) alloc {
return [self SharedRepo];
}
- (BOOL) Open: (NSString *)repoSource
{
NSArray *paths = NSSearchPathForDirectoriesInDomains( NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *databasePath = [documentsDirectory stringByAppendingPathComponent: repoSource];
NSError *error = [self CreateDatabaseIfNeeded: databasePath];
BOOL result = (error == nil);
if(result)
{
_repoSource = (FMDatabase *)[FMDatabase databaseWithPath:databasePath];
[_repoSource retain];
result = [_repoSource open];
if (result)
NSLog(@"Planet Repo open successful!");
else
{
NSLog(@"Could not open Planet Repo.");
[self Close];
}
}
else
NSLog(@"Could not copy db.", [error localizedDescription]);
return result;
}
- (void) Close
{
[_repoSource release];
_repoSource = nil;
}
- (void) dealloc
{
[self Close];
[super dealloc];
}
- (Planet *)Find:(NSDictionary *)criteria
{
if (criteria == nil) return nil;
const NSUInteger criteriaCount = [criteria count];
if (criteriaCount == 0) return nil;
NSMutableString *temp = [NSMutableString stringWithString: @"SELECT Id, Name, SupportsLife FROM planet WHERE"];
NSMutableArray *values = [[NSMutableArray alloc] initWithCapacity:criteriaCount];
int i = 0;
for (id key in criteria)
{
[values addObject: [criteria objectForKey:key]];
NSLog(@"key: %@, value: %@", key, [values objectAtIndex:i] );
[temp appendString: [NSString stringWithFormat:@" %@=?", key ] ];
if ( i < (criteriaCount - 1) )
[temp appendString: @","];
i++;
}
NSString *sql = [NSString stringWithString:temp];
NSLog(@"sql: [%@]", sql);
FMResultSet *resultSet = [_repoSource executeQuery:sql withArgumentsInArray: values ];
Planet *planet = nil;
if ( [resultSet hasAnotherRow] )
planet = [Planet planetWithResultSet: resultSet];
[values release];
[resultSet close];
return planet;
}
-(NSMutableArray *)GetAll
{
NSMutableArray* resultsArray = [[NSMutableArray new] autorelease];
FMResultSet *resultSet = [_repoSource executeQuery:@"SELECT Id, Name, SupportsLife FROM planet ORDER BY Name ASC"];
while ([resultSet next])
[resultsArray addObject: [Planet planetWithResultSet:resultSet] ];
[resultSet close];
return resultsArray;
}
- (Planet *) GetById:(id) Id
{
if( ![_repoSource open]) return NULL;
Planet *planet = NULL;
FMResultSet *resultSet = [_repoSource executeQuery:@"SELECT Id, Name, SupportsLife FROM planet WHERE Id=?" withArgumentsInArray:Id];
if ([resultSet next])
planet = [Planet planetWithResultSet:resultSet];
[resultSet close];
return planet;
}
- (BOOL) Save:(Planet *) planet
{
if( ![_repoSource open]) return NO;
[_repoSource beginTransaction];
BOOL result = NO;
if (planet.Id == 0)
{
result = [_repoSource executeUpdate: @"INSERT INTO \"planet\" (Name,SupportsLife) values (?,?);", planet.Name, planet.SupportsLife];
planet.Id = [_repoSource lastInsertRowId];
}
else
result = [_repoSource executeUpdate: @"UPDATE \"planet\" SET Name=?, SupportsLife=? WHERE Id=?",
planet.Name,
[NSNumber numberWithBool:planet.SupportsLife],
[NSNumber numberWithInt:planet.Id]];
if (result == YES)
[_repoSource commit];
else {
[_repoSource rollback];
NSLog( @"%@", [_repoSource lastErrorMessage] );
}
return result;
}
- (BOOL) Delete: (Planet *) planet
{
if( ![_repoSource open]) return NO;
[_repoSource beginTransaction];
BOOL result = [_repoSource executeUpdate: @"DELETE FROM \"planet\" WHERE Id=?", [NSNumber numberWithInt:planet.Id]];
if (result == YES)
[_repoSource commit];
else {
[_repoSource rollback];
NSLog( @"%@", [_repoSource lastErrorMessage] );
}
return result;
}
- (NSError *) CreateDatabaseIfNeeded:(NSString *)databasePath
{
NSFileManager *fileManager = [NSFileManager defaultManager];
BOOL doesDBExist = [fileManager fileExistsAtPath: databasePath];
if (doesDBExist == NO )
{
NSString* dbFileName = [databasePath lastPathComponent];
//the database does not exist, so we will copy it to the users document directory...
NSString *sourceDbPath = [[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:dbFileName];
NSError *error;
if (![fileManager copyItemAtPath:sourceDbPath toPath:databasePath error:&error])
{
NSLog(@"Could not copy db.", [error localizedDescription]);
return [error autorelease];
}
}
return nil;
}
@end
原始
我最近一直在努力寻找目标。它是一种很好的语言,但作为一个 .Net 人员,这种内存管理的东西简直要了我的命! :)
我有一个示例类,名为 Planet,当我运行仪表板时,我得到以下内容:
https ://i.sstatic.net/QYdxB.jpg (图片)
Leaked Object | Address | Size | Responsible Library | Responsible Frame
--------------+-----------+----------+---------------------+-------------------
Planet | 0x7136380 | 32 Bytes | NavTest | +[Planet newWithName:]
Planet | 0x712da20 | 32 Bytes | NavTest | -[Planet init]
Planet | 0x5917a20 | 32 Bytes | Foundation | -[NSPlaceholderString
我认为这意味着我在 [Planet init] 中创建数组时遇到问题?
我也在这里列出了我的类文件。我非常感谢对此的任何帮助。
行星.h
#import <Foundation/Foundation.h>
#import "FMDatabase.h"
@class Planet;
@interface Planet : NSObject { }
@property (nonatomic, assign) int Id;
@property (nonatomic, copy) NSString *Name;
@property (nonatomic, retain) NSMutableArray *Moons;
@property (nonatomic, assign) BOOL SupportsLife;
- (Planet *)initWithName: (NSString *)name;
+ (Planet *) newWithNameAndMoons:(NSString *)name moons:(id)firstObj, ... NS_REQUIRES_NIL_TERMINATION;
+ (Planet *) newWithName:(NSString *)name;
+ (Planet *) readPlanetFrom:(FMResultSet *)resultSet;
@end
行星.m
#import "Planet.h"
#import "FMDatabase.h"
@implementation Planet
@synthesize Id;
@synthesize Name;
@synthesize Moons;
@synthesize SupportsLife;
- (NSString *) description {
return [NSString stringWithFormat:@"ID: %d\nName: %@", Id ,Name];
}
- (void) dealloc {
[Moons release];
[Name release];
Moons = nil;
Name = nil;
[super dealloc];
}
- (id)initWithName:(NSString *)aName {
self = [self init];
if (self) self.Name = aName;
return self;
}
- (id)init {
self = [super init];
self.Id = 0;
self.SupportsLife = NO;
NSMutableArray *tmp = [NSMutableArray new];
self.Moons = tmp;
[tmp release];
tmp = nil;
return self;
}
+ (Planet *) newWithName:(NSString *)name {
return [[self alloc] initWithName: name];
}
+ (Planet *)newWithNameAndMoons:(NSString *)name moons:(id)firstObj, ... {
Planet *planet = [self newWithName: name];
va_list args;
va_start(args, firstObj);
for (id arg = firstObj; arg != nil; arg = va_arg(args, id))
{
[planet.Moons addObject: arg];
[arg release];
}
va_end(args);
return planet;
}
+ (Planet *) readPlanetFrom:(FMResultSet *)resultSet
{
Planet *planet = [[self alloc] init];
planet.Id = [resultSet intForColumn:@"Id"];
planet.Name = [resultSet stringForColumn:@"Name"];
planet.SupportsLife = [resultSet boolForColumn: @"SupportsLife"];
return [planet autorelease];
}
@end
Update
I have update the planets.m/h files to reflect the suggestions made by the commentors (thank you bbum and DarkDust.
my factory methods now all call autorelease and look like this:
- (Planet *) initWithName: (NSString *)name;
+ (Planet *) planetWithNameAndMoons:(NSString *)name moons:(id)firstObj, ... NS_REQUIRES_NIL_TERMINATION;
+ (Planet *) planetWithName:(NSString *)name;
+ (Planet *) planetWithResultSet:(FMResultSet *)resultSet;
and now my init now looks like this
- (id)init {
self = [super init];
self.Id = 0;
self.SupportsLife = NO;
self.Moons = [NSMutableArray array];
return self;
}
I am calling planetWithName in my 2nd controller off the root. This is how I am using it
- (Planet *)newPlanet
{
int i = 0;
Planet *planet = nil;
NSMutableDictionary *criteria = [NSMutableDictionary new];
do {
i += 1;
[criteria setValue: [NSString stringWithFormat:@"Planet %d", i] forKey: @"Name"];
planet = [[PlanetRepo SharedRepo] Find: criteria];
} while (planet != nil); // if a planet is found with that name, keep looking...
planet = [Planet planetWithName: (NSString *)[criteria valueForKey:@"Name"]];
[planet retain];
[criteria release];
criteria = nil;
return planet;
}
And here is my PlanetRepo class (note, it is wrapping around FMDB)
PlanetRepo.h
#import <Foundation/Foundation.h>
#import "Planet.h"
@interface PlanetRepo : NSObject {
FMDatabase * _repoSource;
}
+ (PlanetRepo *) SharedRepo;
- (Planet *)Find:(NSDictionary *)criteria;
- (NSMutableArray *)GetAll;
- (Planet *)GetById:(id ) Id;
- (BOOL) Save:(Planet *) planet;
- (BOOL) Delete: (Planet *) planet;
- (BOOL) Open:(NSString *)repoSource;
- (void) Close;
@end
@interface PlanetRepo (Private)
- (NSError *) CreateDatabaseIfNeeded:(NSString *)databasePath;
@end
Planet.m
#import "FMDatabase.h"
#import "PlanetRepo.h"
#import "Planet.h"
@implementation PlanetRepo
+ (PlanetRepo *)SharedRepo
{
static PlanetRepo *_sharedRepo;
@synchronized(self)
{
if (!_sharedRepo)
_sharedRepo = [[super alloc] init];
}
return _sharedRepo;
}
+ (id) alloc {
return [self SharedRepo];
}
- (BOOL) Open: (NSString *)repoSource
{
NSArray *paths = NSSearchPathForDirectoriesInDomains( NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *databasePath = [documentsDirectory stringByAppendingPathComponent: repoSource];
NSError *error = [self CreateDatabaseIfNeeded: databasePath];
BOOL result = (error == nil);
if(result)
{
_repoSource = (FMDatabase *)[FMDatabase databaseWithPath:databasePath];
[_repoSource retain];
result = [_repoSource open];
if (result)
NSLog(@"Planet Repo open successful!");
else
{
NSLog(@"Could not open Planet Repo.");
[self Close];
}
}
else
NSLog(@"Could not copy db.", [error localizedDescription]);
return result;
}
- (void) Close
{
[_repoSource release];
_repoSource = nil;
}
- (void) dealloc
{
[self Close];
[super dealloc];
}
- (Planet *)Find:(NSDictionary *)criteria
{
if (criteria == nil) return nil;
const NSUInteger criteriaCount = [criteria count];
if (criteriaCount == 0) return nil;
NSMutableString *temp = [NSMutableString stringWithString: @"SELECT Id, Name, SupportsLife FROM planet WHERE"];
NSMutableArray *values = [[NSMutableArray alloc] initWithCapacity:criteriaCount];
int i = 0;
for (id key in criteria)
{
[values addObject: [criteria objectForKey:key]];
NSLog(@"key: %@, value: %@", key, [values objectAtIndex:i] );
[temp appendString: [NSString stringWithFormat:@" %@=?", key ] ];
if ( i < (criteriaCount - 1) )
[temp appendString: @","];
i++;
}
NSString *sql = [NSString stringWithString:temp];
NSLog(@"sql: [%@]", sql);
FMResultSet *resultSet = [_repoSource executeQuery:sql withArgumentsInArray: values ];
Planet *planet = nil;
if ( [resultSet hasAnotherRow] )
planet = [Planet planetWithResultSet: resultSet];
[values release];
[resultSet close];
return planet;
}
-(NSMutableArray *)GetAll
{
NSMutableArray* resultsArray = [[NSMutableArray new] autorelease];
FMResultSet *resultSet = [_repoSource executeQuery:@"SELECT Id, Name, SupportsLife FROM planet ORDER BY Name ASC"];
while ([resultSet next])
[resultsArray addObject: [Planet planetWithResultSet:resultSet] ];
[resultSet close];
return resultsArray;
}
- (Planet *) GetById:(id) Id
{
if( ![_repoSource open]) return NULL;
Planet *planet = NULL;
FMResultSet *resultSet = [_repoSource executeQuery:@"SELECT Id, Name, SupportsLife FROM planet WHERE Id=?" withArgumentsInArray:Id];
if ([resultSet next])
planet = [Planet planetWithResultSet:resultSet];
[resultSet close];
return planet;
}
- (BOOL) Save:(Planet *) planet
{
if( ![_repoSource open]) return NO;
[_repoSource beginTransaction];
BOOL result = NO;
if (planet.Id == 0)
{
result = [_repoSource executeUpdate: @"INSERT INTO \"planet\" (Name,SupportsLife) values (?,?);", planet.Name, planet.SupportsLife];
planet.Id = [_repoSource lastInsertRowId];
}
else
result = [_repoSource executeUpdate: @"UPDATE \"planet\" SET Name=?, SupportsLife=? WHERE Id=?",
planet.Name,
[NSNumber numberWithBool:planet.SupportsLife],
[NSNumber numberWithInt:planet.Id]];
if (result == YES)
[_repoSource commit];
else {
[_repoSource rollback];
NSLog( @"%@", [_repoSource lastErrorMessage] );
}
return result;
}
- (BOOL) Delete: (Planet *) planet
{
if( ![_repoSource open]) return NO;
[_repoSource beginTransaction];
BOOL result = [_repoSource executeUpdate: @"DELETE FROM \"planet\" WHERE Id=?", [NSNumber numberWithInt:planet.Id]];
if (result == YES)
[_repoSource commit];
else {
[_repoSource rollback];
NSLog( @"%@", [_repoSource lastErrorMessage] );
}
return result;
}
- (NSError *) CreateDatabaseIfNeeded:(NSString *)databasePath
{
NSFileManager *fileManager = [NSFileManager defaultManager];
BOOL doesDBExist = [fileManager fileExistsAtPath: databasePath];
if (doesDBExist == NO )
{
NSString* dbFileName = [databasePath lastPathComponent];
//the database does not exist, so we will copy it to the users document directory...
NSString *sourceDbPath = [[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:dbFileName];
NSError *error;
if (![fileManager copyItemAtPath:sourceDbPath toPath:databasePath error:&error])
{
NSLog(@"Could not copy db.", [error localizedDescription]);
return [error autorelease];
}
}
return nil;
}
@end
Original
I have been working on picking up objective c lately. Its a nice language but as a .Net guy this memory management stuff is killing me! :)
I have a sample class, called Planet, and when I run through instrument panel I get the following:
https://i.sstatic.net/QYdxB.jpg (image)
Leaked Object | Address | Size | Responsible Library | Responsible Frame
--------------+-----------+----------+---------------------+-------------------
Planet | 0x7136380 | 32 Bytes | NavTest | +[Planet newWithName:]
Planet | 0x712da20 | 32 Bytes | NavTest | -[Planet init]
Planet | 0x5917a20 | 32 Bytes | Foundation | -[NSPlaceholderString
I think this means I have a problem when I am creating an array in the [Planet init]?
I am listing my class file here as well. I would very much appreciate any help on this.
Planet.h
#import <Foundation/Foundation.h>
#import "FMDatabase.h"
@class Planet;
@interface Planet : NSObject { }
@property (nonatomic, assign) int Id;
@property (nonatomic, copy) NSString *Name;
@property (nonatomic, retain) NSMutableArray *Moons;
@property (nonatomic, assign) BOOL SupportsLife;
- (Planet *)initWithName: (NSString *)name;
+ (Planet *) newWithNameAndMoons:(NSString *)name moons:(id)firstObj, ... NS_REQUIRES_NIL_TERMINATION;
+ (Planet *) newWithName:(NSString *)name;
+ (Planet *) readPlanetFrom:(FMResultSet *)resultSet;
@end
Planet.m
#import "Planet.h"
#import "FMDatabase.h"
@implementation Planet
@synthesize Id;
@synthesize Name;
@synthesize Moons;
@synthesize SupportsLife;
- (NSString *) description {
return [NSString stringWithFormat:@"ID: %d\nName: %@", Id ,Name];
}
- (void) dealloc {
[Moons release];
[Name release];
Moons = nil;
Name = nil;
[super dealloc];
}
- (id)initWithName:(NSString *)aName {
self = [self init];
if (self) self.Name = aName;
return self;
}
- (id)init {
self = [super init];
self.Id = 0;
self.SupportsLife = NO;
NSMutableArray *tmp = [NSMutableArray new];
self.Moons = tmp;
[tmp release];
tmp = nil;
return self;
}
+ (Planet *) newWithName:(NSString *)name {
return [[self alloc] initWithName: name];
}
+ (Planet *)newWithNameAndMoons:(NSString *)name moons:(id)firstObj, ... {
Planet *planet = [self newWithName: name];
va_list args;
va_start(args, firstObj);
for (id arg = firstObj; arg != nil; arg = va_arg(args, id))
{
[planet.Moons addObject: arg];
[arg release];
}
va_end(args);
return planet;
}
+ (Planet *) readPlanetFrom:(FMResultSet *)resultSet
{
Planet *planet = [[self alloc] init];
planet.Id = [resultSet intForColumn:@"Id"];
planet.Name = [resultSet stringForColumn:@"Name"];
planet.SupportsLife = [resultSet boolForColumn: @"SupportsLife"];
return [planet autorelease];
}
@end
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(2)
尽我所能,我无法发现这里的问题。我只有一个非常疯狂的猜测:你是否有一个单独的线程来分配行星?例如通过
performSelectorInBackground:
?如果是这样,您首先需要设置一个 NSAutoreleasePool 并在方法结束时将其耗尽。如果这确实是问题所在,您会看到类似*** _NSAutoreleaseNoPool(): Object 0x1234560 of class Foo autoreleased with no pool in place - just Leaking
的消息不过,我有一条评论:您使用 < code>new 非常不常见。
new
是[[SomeClass alloc] init]
的同义词。然而,它的使用在 Objective-C 中非常不常见,几乎已被弃用。 。因此,
更常见的写法是:
或更好:
Try as I might, I can't spot a problem here. I only have a very wild guess: do you have a separate thread where the planet is allocated ? E. g. through
performSelectorInBackground:
? If so, you first need to set up a NSAutoreleasePool and drain it at the end of the method. If that really is the problem you would see messages like*** _NSAutoreleaseNoPool(): Object 0x1234560 of class Foo autoreleased with no pool in place - just leaking
Still, I have a comment: Your use of
new
is quite uncommon.new
is a synonym for[[SomeClass alloc] init]
. However, its use is very uncommon in Objective-C, almost deprecated.So instead of
It's more common to write:
or better yet:
一些问题:
实例变量和属性应该以小写字母开头(为了与类名消除歧义)
没有需要 - 但没有坏处 - 在您的
init
方法中分配tmp = nil;
(需要if(self) {...} test,顺便说一句)。
您可以通过说
self.moons = [NSMutableArray array]; 来避免使用
tmp
变量;这可能是泄漏的来源是以下方法:
那确实应该是这样的:
另外,这:
应该可能是这样的:
在没有看到更多代码的情况下,很难确切地说泄漏来自哪里。在打开保留/释放跟踪的情况下,在分配工具下运行代码。选择一个泄漏的行星并查看所有保留/释放事件。会有额外保留。
Some issues:
instance variables and properties should start with lower case letters (to disambiguate from class names)
there is no need -- but no harm -- in assigning
tmp = nil;
in yourinit
method (which needs theif(self) {...}
test, btw).you could avoid the use of a
tmp
variable by sayingself.moons = [NSMutableArray array];
This is a likely source of leaks is the following method:
That really should be something like:
Also, this:
Should probably be this:
Without seeing more code, it is hard to say exactly where the leak is coming from. Run the code under the Allocations instrument with retain/release tracking turned on. Pick one of the leaking Planets and look at all the retain/release events. There will be an extra retain.