Xcode - iOS -Speicherleck, das mich verrückt macht.
-
27-10-2019 - |
Frage
Der Code hier ist eine modale Ansicht, die vom RootViewController gestartet wurde und ein Video mit einem Miniaturfilmstrip unter dem Film angezeigt und dann an den Film zeitgesteuert wurde.
Es funktioniert alles, aber es gibt ein Speicherleck / mangelnde Veröffentlichung, den ich einfach nicht sehen kann, um drei Tage lang zu suchen, um es zu reparieren. Es ist an der Zeit, um Hilfe zu bitten ...
Wenn ich das nsnotificationCenter deaktiviere, indem ich es kommentiere (hervorgehoben in der .m), habe ich keine Probleme in Bezug auf das Gedächtnis und behalte den zeitgesteuerten Text. Aber ich habe auch keine Miniaturansichten. Ich habe versucht, einzufügen [[NSNotificationCenter alloc] removeObserver:self];
An zahlreichen Punkten, um zu sehen, ob dies für mich loswerden wird. Aber leider ohne Erfolg.
Ich habe auch versucht, den "Hintergrundtimer" zu veröffentlichen, aber es ist nicht übermäßig beeindruckt, wenn ich versuche zu kompilieren und zu rennen.
Im Wesentlichen gibt es das erste Mal, dass ich die modale Sichtweise lade, keine Probleme und alles scheint großartig zu sein - wenn ich sie mit dem schließe -(IBAction)close:(id)sender;
Es scheint, dass etwas nicht veröffentlicht wird, da ich das nächste Mal die gleiche Seite starte. Die Speicherverwendung steigt um etwa 30% (ungefähr die Menge, die von der Miniaturansichtserzeugung verwendet wird) und erhöht sich bei jeder Wiederaufnahme der Major-Menge um ungefähr die gleiche Menge Modale Ansicht.
Bitte beachten Sie, dass ich ein Neuling dafür bin und der Fehler für diejenigen von Ihnen, die wissen, wahrscheinlich einen blutigen dummen. Aber im Interesse, dieses Projekt durchzuführen, werde ich gerne jeden Missbrauch nehmen, wenn Sie Lust auf mich werfen.
Hier ist der Code:
.h
#import <UIKit/UIKit.h>
#import <MediaPlayer/MPMoviePlayerController.h>
#import "ImageViewWithTime.h"
#import "CommentView.h"
@interface SirloinVideoViewController_iPad : UIViewController {
UIView *landscapeView;
UIView *viewForMovie;
MPMoviePlayerController *player;
UILabel *onScreenDisplayLabel;
UIScrollView *myScrollView;
NSMutableArray *keyframeTimes;
NSArray *shoutOutTexts;
NSArray *shoutOutTimes;
NSTimer *backgroundTimer;
UIView *instructions;
}
-(IBAction)close:(id)sender;
-(IBAction)textInstructions:(id)sender;
@property (nonatomic, retain) IBOutlet UIView *instructions;
@property (nonatomic, retain) NSTimer *theTimer;
@property (nonatomic, retain) NSTimer *backgroundTimer;
@property (nonatomic, retain) IBOutlet UIView *viewForMovie;
@property (nonatomic, retain) MPMoviePlayerController *player;
@property (nonatomic, retain) IBOutlet UILabel *onScreenDisplayLabel;
@property (nonatomic, retain) IBOutlet UIScrollView *myScrollView;
@property (nonatomic, retain) NSMutableArray *keyframeTimes;
-(NSURL *)movieURL;
- (void) playerThumbnailImageRequestDidFinish:(NSNotification*)notification;
- (ImageViewWithTime *)makeThumbnailImageViewFromImage:(UIImage *)image andTimeCode:(NSNumber *)timecode;
- (void)handleTapFrom:(UITapGestureRecognizer *)recognizer;
@end
.m
#import "SirloinVideoViewController_iPad.h"
#import "SirloinTextViewController.h"
@implementation SirloinVideoViewController_iPad
@synthesize theTimer, backgroundTimer, viewForMovie, player,
onScreenDisplayLabel, myScrollView, keyframeTimes, instructions;
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
}
return self;
[nibNameOrNil release];
[nibBundleOrNil release];
}
- (IBAction)close:(id)sender{
[self.parentViewController dismissModalViewControllerAnimated:YES];
[player stop];
[player release];
[theTimer invalidate];
[theTimer release];
[backgroundTimer invalidate];
[SirloinVideoViewController_iPad release];
}
—
-(IBAction)textInstructions:(id)sender {
SirloinTextViewController *vController = [[SirloinTextViewController alloc] initWithNibName:nil bundle:nil];
[self presentModalViewController:vController animated:YES];
[vController release];
}
- (void)viewDidLoad {
[super viewDidLoad];
keyframeTimes = [[NSMutableArray alloc] init];
shoutOutTexts = [[NSArray
arrayWithObjects:
@"1. XXXXXXXXXXXX",
@"2. XXXXXXXXXXXX",
@"3. XXXXXXXXXXXX",
@"4. XXXXXXXXXXXX",
@"5. XXXXXXXXXXXX",
@"6. XXXXXXXXXXXX"
@"7. XXXXXXXXXXXX",
@"8. XXXXXXXXXXXX",
@"9. XXXXXXXXXXXX",
@"10. XXXXXXXXXXXX",
@"11. XXXXXXXXXXXX",
@"12. XXXXXXXXXXXX",
@"13. XXXXXXXXXXXX",
@"14. XXXXXXXXXXXX",
@"15. XXXXXXXXXXXX",
nil] retain];
shoutOutTimes = [[NSArray
arrayWithObjects:
[[NSNumber alloc] initWithInt: 1],
[[NSNumber alloc] initWithInt: 73],
[[NSNumber alloc] initWithInt: 109],
[[NSNumber alloc] initWithInt: 131],
[[NSNumber alloc] initWithInt: 205],
[[NSNumber alloc] initWithInt: 250],
[[NSNumber alloc] initWithInt: 337],
[[NSNumber alloc] initWithInt: 378],
[[NSNumber alloc] initWithInt: 402],
[[NSNumber alloc] initWithInt: 420],
[[NSNumber alloc] initWithInt: 448],
[[NSNumber alloc] initWithInt: 507],
[[NSNumber alloc] initWithInt: 531],
[[NSNumber alloc] initWithInt: 574],
nil] retain];
self.player = [[MPMoviePlayerController alloc] init];
self.player.contentURL = [self movieURL];
self.player.view.frame = self.viewForMovie.bounds;
self.player.view.autoresizingMask =
UIViewAutoresizingFlexibleWidth |
UIViewAutoresizingFlexibleHeight;
[self.viewForMovie addSubview:player.view];
backgroundTimer = [NSTimer scheduledTimerWithTimeInterval:0.5f target:self selector:@selector(timerAction:) userInfo:nil repeats:YES];
[self.view addSubview:self.myScrollView];
//I am pretty sure that this is the culprit - Just not sure why...
[[NSNotificationCenter defaultCenter]
addObserver:self
selector:@selector(movieDurationAvailable:)
name:MPMovieDurationAvailableNotification
object:theTimer];
//Could be wrong, but when commented out I don't have the memory issues
}
—
- (NSInteger)positionFromPlaybackTime:(NSTimeInterval)playbackTime
{
NSInteger position = 0;
for (NSNumber *startsAt in shoutOutTimes)
{
if (playbackTime > [startsAt floatValue])
{
++position;
}
}
return position;
}
-(NSURL *)movieURL
{
NSBundle *bundle = [NSBundle mainBundle];
NSString *moviePath =
[bundle
pathForResource:@"sirloin"
ofType:@"m4v"];
if (moviePath) {
return [NSURL fileURLWithPath:moviePath];
} else {
return nil;
}
}
NSTimeInterval lastCheckAt = 0.0;
- (void)timerAction: theTimer
{
int count = [shoutOutTimes count];
NSInteger position = [self positionFromPlaybackTime:self.player.currentPlaybackTime];
NSLog(@"position is at %d", position);
if (position > 0)
{
--position;
}
if (position < count)
{
NSNumber *timeObj = [shoutOutTimes objectAtIndex:position];
int time = [timeObj intValue];
NSLog(@"shout scheduled for %d", time);
NSLog(@"last check was at %g", lastCheckAt);
NSLog(@"current playback time is %g", self.player.currentPlaybackTime);
if (lastCheckAt < time && self.player.currentPlaybackTime >= time)
{
NSString *shoutString = [shoutOutTexts objectAtIndex:position];
NSLog(@"shouting: %@", shoutString);
CommentView *cview = [[CommentView alloc] initWithText:shoutString];
[self.instructions addSubview:cview];
[shoutString release];
}
}
lastCheckAt = self.player.currentPlaybackTime;
}
// Override to allow orientations other than the default portrait orientation.
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation {
return YES;
}
-(void)removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath {
[[NSNotificationCenter defaultCenter] removeObserver:MPMovieDurationAvailableNotification];
[[NSNotificationCenter defaultCenter] removeObserver:MPMoviePlayerThumbnailImageRequestDidFinishNotification];
[keyPath release];
}
- (void) movieDurationAvailable:(NSNotification*)notification {
float duration = [self.player duration];
[[NSNotificationCenter defaultCenter]
addObserver:self
selector:@selector(playerThumbnailImageRequestDidFinish:)
name:MPMoviePlayerThumbnailImageRequestDidFinishNotification
object:nil];
NSMutableArray *times = [[NSMutableArray alloc] init];
for(int i = 0; i < 20; i++) {
float playbackTime = i * duration/20;
[times addObject:[NSNumber numberWithInt:playbackTime]];
}
[self.player
requestThumbnailImagesAtTimes:times
timeOption: MPMovieTimeOptionExact];
}
- (void) playerThumbnailImageRequestDidFinish:(NSNotification*)notification {
NSDictionary *userInfo = [notification userInfo];
NSNumber *timecode =
[userInfo objectForKey: MPMoviePlayerThumbnailTimeKey];
UIImage *image =
[userInfo objectForKey: MPMoviePlayerThumbnailImageKey];
ImageViewWithTime *imageView =
[self makeThumbnailImageViewFromImage:image andTimeCode:timecode];
[myScrollView addSubview:imageView];
UITapGestureRecognizer *tapRecognizer =
[[UITapGestureRecognizer alloc]
initWithTarget:self action:@selector(handleTapFrom:)];
[tapRecognizer setNumberOfTapsRequired:1];
[imageView addGestureRecognizer:tapRecognizer];
[tapRecognizer release];
[image release];
[imageView release];
}
- (void)handleTapFrom:(UITapGestureRecognizer *)recognizer {
ImageViewWithTime *imageView = (ImageViewWithTime *) recognizer.view;
self.player.currentPlaybackTime = [imageView.time floatValue];
}
- (ImageViewWithTime *)makeThumbnailImageViewFromImage:(UIImage *)image andTimeCode:(NSNumber *)timecode {
float timeslice = self.player.duration / 3.0;
int pos = [timecode intValue] / (int)timeslice;
float width = 75 *
((float)image.size.width / (float)image.size.height);
self.myScrollView.contentSize =
CGSizeMake((width + 2) * 13, 75);
ImageViewWithTime *imageView =
[[ImageViewWithTime alloc] initWithImage:image];
[imageView setUserInteractionEnabled:YES];
[imageView setFrame:CGRectMake(pos * width + 2, 0, width, 75.0f)];
imageView.time = [[NSNumber alloc] initWithFloat:(pos * timeslice)];
return imageView;
[myScrollView release];
}
- (void)dealloc {
[player release];
[viewForMovie release];
[onScreenDisplayLabel release];
[keyframeTimes release];
[instructions release];
[shoutOutTexts release];
[shoutOutTimes release];
[super dealloc];
}
@end
Diese App ist bereits stark mit UIWebView (was einfach nur sux) verwendet, also versuche ich, die Dinge richtig zu machen und es richtig zu machen.
Lösung
Sie haben ein paar mehr Probleme als eines Leck.
Der erste
ist in deinem initWithNibName:bundle:
Da du dort nichts Nützliches tust: Lass es ganz los! (Außerdem veröffentlichen Sie keine Argumente, die an Ihre Methoden übergeben werden! Zum Glück haben Sie diese Veröffentlichungen in Zeilen platziert, die nicht erreichbar sind, dh nach der Rückkehrerklärung ...)
Nächste Methode, nächste Probleme
- Warum senden Sie?
release
zu einem Klassenobjekt? Nicht! Das ist falsch an viele Ebenen. - Sie haben beschlossen, Eigenschaften für Ihre Timer zu erstellen. Das ist per se nichts Schlimmes. Aber warum verwenden Sie dann die Ivars direkt hier? Ich würde Sie nachdrücklich ermutigen, umzusetzen
setTheTimer:
undsetBackgroundTimer:
Um die Invalidierung zu bewältigen und ordnungsgemäß freizugeben und einfach tunself.theTimer = nil; self.backgroundTimer = nil;
hier. Das würde die Asymmetrie auch beim Umgang mit diesen Dingen beheben. (Übrigens: Thetimer ist nicht eine solche Ein toller Name für einen Ivar ... besonders wenn es gibt Ein weiterer Ivar, das ist ein Timer!)
textInstructions:
Sieht unbekannt aus, aber ...
viewDidLoad
hat noch einige Probleme
- Es läuft einen MPMovieplayerController:
Das@property
wird es behalten, also müssen Sie das ausgleichenalloc
hier. backgroundTimer
hat eine entsprechende@property
Dies wird als beibehalten: Sie verstoßen hier gegen diesen API -Vertrag, da Sie den Timer nur dem IVAR zuweisen. Verwendenself.backgroundTimer = ...
stattdessen.- Aus all dem Code, den Sie gepostet haben, scheint es mir, dass Passieren
theTimer
Als letzte Argument in Ihrem Anruf zu-[NSNotificationCenter addObserver:selector:name:object:]
ist eine ausgefallene Art, hereinzugebennil
als dieser Parameter. Welches ist irgendwie gut, weil normalerweiseNSTimer
Posts nicht zu vieleMPMovieDurationAvailableNotification
s. In der Tat, wie ich nicht sehen kanntheTimer
verwendet werden, außer inclose:
: Könnte es sein, dass dies nur ein nutzloser Überrest ist, bevor Sie die eingeführt habenbackgroundTimer
Ivar/@Property? (Nun, es gibt ein weiteres Vorkommen einer Variablen dieses Namens, aber sie sollte von einer großen Fett -Compiler -Warnung begleitet werden ...) - Implementieren Sie in irgendeiner Weise
viewDidUnload
? Wenn ja, tut es:self.player = nil;
?[shoutOutTexts release], shoutOutTexts = nil;
?[shoutOutTimes release], shoutOutTimes = nil;
?self.keyframeTimes = nil;
?[[NSNotificationCenter defaultCenter] removeObserver:self name: MPMovieDurationAvailableNotification object:nil];
?self.backgroundTimer = nil;
? (Annahme,setBackgroundTimer:
Veröffentlichungen und ungültig der alte Wert)
- Aktualisieren Ich habe diesen beim ersten Versuch verpasst: Du leckst 15
NSNumber
s hier. Verwenden[NSNumber numberWithInt:]
anstelle von alloc/init im Setup vonshoutOutTimes
.
Eine kleine Bemerkung auf movieURL
, was Sie in den folgenden Einzeiler verwandeln können:
-(NSURL*)movieURL {
return [[NSBundle mainBundle] URLForResource:@"sirloin" withExtension:@"m4v"];
}
Und dann das
NSTimeInterval lastCheckAt = 0.0;
innerhalb des globalen Umfangs. Aus Ihrer Verwendung davon: Ivar plz?!? Eins?
Weitere Probleme später. Ich muss mir zuerst etwas zu essen bringen.
Zweiter Teil
Jetzt gehen wir in timerAction:
Das erste Problem ist nicht zu schwer, insbesondere in diesem bestimmten Kontext -, aber Sie sollten sich bewusst sein -[NSArray count]
Gibt ein zurück NSUInteger
und das das U ist kein Tippfehler, sondern eine Bezeichnung, dass dieser Wert nicht signiert ist. Sie werden sicherlich nicht auf Probleme mit der Unterzeichnung in dieser App stoßen und tun bei anderen Gelegenheiten selten, aber Wenn Sie tun es, sie machen wirklich funky Fehler aus und Sie sollten sich der Auswirkungen bewusst sein ...
Das eigentliche Problem mit dieser Methode ist jedoch, dass Sie es sind auslaufen CommentView
per Iteration während zur gleichen Zeit - Überabstimmung NSString
. Die Tatsache, dass Sie in erster Linie String -Literale verwendet haben (die niemals mitgeteilt werden) (dh wenn Sie ShoutoutTimes initialisiert haben), spart hier Ihren Hintern völlig.
Next Up: removeObserver:forKeyPath:
Du Ja wirklich Sollte diese wirklich schlechte Angewohnheit, Parameter freizusetzen, loswerden, die an Ihre Methoden übergeben werden!
Abgesehen davon, dass Sie die Gesamtheit dieser Methode loswerden!
Zuallererst removeObserver:forKeyPath:
ist eine Methode aus der NSKeyValueObserving
Informelles Protokoll und spielt eine völlig andere Rolle als das, was Sie (Ab-) verwenden, um es hier zu erreichen. Zweitens ist es eine dieser Methoden, bei denen es unerlässlich ist, sich durchzusetzen super
wenn - auf jeden Fall - Sie Ja wirklich müssen es außer Kraft setzen. (Nun, außer als Sie überschrieben waren addObserver:forKeyPath:options:context:
Auch und es sollte auch sein, dass Sie es nicht tun sollten, wenn Sie nicht so unangemessen sind, dass Sie wirklich wissen, was Sie sind, wenn Sie nicht sind, wenn Sie immer geplant werden- On-Use-Kvo.)
movieDurationAvailable:
Wie Evan sagte, du austritt times
hier. Gehen Sie für seinen Vorschlag oder - stattdessen - machen Sie es NSMutableArray *times = [NSMutableArray array];
Und du bist hier fertig.
playerThumbnailImageRequestDidFinish:
Sie besitzen nicht image
, Veröffentlichen Sie es also nicht!
Persönlich würde ich die Ansicht beenden (dh das Erkenntnis hinzufügen und solche Dinge tun), bevor ich sie in die Aussichts-hierarchie hinzufüge, aber das ist völlig eine Frage des Geschmacks ...
makeThumbnailImageViewFromImage:andTimeCode:
... leckt an NSNumber
(verwenden [NSNumber numberWithFloat:(pos * timeslice)]
anstelle von alloc/initWithFloat:
-dance) und verhindert, dass Sie aufgrund von Übervertrieben abstürzen myScrollView
nach der bedingungslosen Rückgabeerklärung, die ihr direkt vorausgeht (Puh!). Während wir gerade dabei sind: Benennen Sie diese Methode in beide newThumbnailImageView...
Damit Sie diesen Code in einem Jahr oder so erneut überprüft werden, wissen Sie das sofort [imageView release];
unten playerThumbnailImageRequestDidFinish:
Es ist wirklich notwendig, ohne die Implementierung dieser Methode anzusehen.
Alternativ könnten Sie es umbenennen thumbnailImageView...
und ändern Sie die Rückgabeerklärung in return [imageView autorelease];
. Bonus: eine Linie weniger in playerThumbnailImageRequestDidFinish:
als die [imageView release];
Dann wird es veraltet.
dealloc
Hinzufügen [[NSNotificationCenter defaultCenter] removeObserver:self];
ganz oben.
Der Rest sieht gut aus. (Obwohl ich es seltsam finde, dass der Ivar landscapeView
wird niemals/nirgendwo außer seiner Erklärung erwähnt.)
Zusammenfassung
Lesen Sie die Abschnitte Speicherverwaltungsregeln und Autorelease Nochmals aus dem "Speicherverwaltungs -Programmierhandbuch" von Apple ". Sie sind reines Gold!
Andere Tipps
Du ließ nie frei times
in movieDurationAvailable:
:
NSMutableArray *times = [[NSMutableArray alloc] init];
Du solltest benutzen autorelease
Wenn Sie es an die Methode weitergeben:
[self.player requestThumbnailImagesAtTimes:[times autorelease] timeOption: MPMovieTimeOptionExact];