• Apfeltalk ändert einen Teil seiner Allgemeinen Geschäftsbedingungen (AGB), das Löschen von Useraccounts betreffend.
    Näheres könnt Ihr hier nachlesen: AGB-Änderung
  • Viele hassen ihn, manche schwören auf ihn, wir aber möchten unbedingt sehen, welche Bilder Ihr vor Eurem geistigen Auge bzw. vor der Linse Eures iPhone oder iPad sehen könnt, wenn Ihr dieses Wort hört oder lest. Macht mit und beteiligt Euch an unserem Frühjahrsputz ---> Klick

Buch: Objective C 2.0 Thema: Speicherlecks (Verständnissprobleme))

Drobs

Carola
Registriert
23.05.08
Beiträge
115
Also ich bin grade dabei mich durch das Buch Objective C 2.0 von Stephen Kochan durchzuarbeiten, habe jetzt allerdings an einer Stelle arge Verständnissprobleme.

Und zwar geht es um einen Quelltext für eine Signum-Funktion. Das alles dreht sich um eine Klasse für gebrochene Zahlen (Fraction).
Hier mal der Quellcode um den es geht:

Code:
int main (int argc, const char * argv[]) {
    NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];

    Fraction *aFraction = [[Fraction alloc] init];
	Fraction *sum = [[Fraction alloc] init], *sum2;
	int i, n, pow2;
	
	[sum setTo: 0 over: 1]; //setzt den ersten Bruch auf 0
	
	NSLog(@"Gib einen Wert für n ein:");
	scanf("%i", &n);
	
	pow2 = 2;
	for (i = 1; i <= n; i++) {
		[aFraction setTo: 1 over: pow2];
		sum2 = [sum add: aFraction];
		[sum release];
		sum = sum2;
		pow2 *= 2;
	}
	
	NSLog(@"Blabla Ausgabe %g", [sum convertToNum]); //gibt den Bruch als Dezimalzahl aus 
	[aFraction release];
	[sum release];
	
    [pool drain];
    return 0;
}

Und die methode add:
Code:
-(Fraction *) add: (Fraction *) f
{
	Fraction *result = [[Fraction alloc] init];
	int resultNum, resultDenom;
	
	resultNum = (numerator * f.denominator) + (f.numerator * denominator);
	resultDenom = denominator * f.denominator;
	
	[result setTo:resultNum over:resultDenom];
	[result reduce]; //kürzt den Bruch
	
	return result;
}

Nun zu dem, was meine Frage hervorbringt, dass was er dazu geschrieben hat:

Das Ergebnis von add: wird sum2 zugewiesen, nicht sum, um Speicherlecks zu vermeiden. (Was würde passieren wenn sie das Ergebnis direkt sum zuwiesen?)
[...]
Jetzt sind nur zwei Objekte zum freigeben übrig: aFraction und das finale Fraction-Object in sum.

Nun meine Verständnissfragen:

a) Wenn ich das Ergebnis einfach in sum schreiben würde, warum würde ein Speicherleck entstehen? Wird das Objekt nicht einfach überschrieben? Und warum ist das anders, wenn ich es in sum2 überschreibe?

b) Warum muss am Ende sum2 nicht auch freigegeben werden?

Ich hoffe mir kann das einer erklären, vor allem da mir das doch relativ wichtig erscheint.
 

gKar

Maunzenapfel
Registriert
25.06.08
Beiträge
5.362
Code:
		sum2 = [sum add: aFraction];
		[sum release];

Nun: Ohne in die Referenz geguckt zu haben, erzeugt nach diesem Code zu urteilen die add-Methode von Fraction ein neues Fraction-Objekt (mit Retain-Count 1). Das alte Objekt (von sum referenziert) muss freigegeben werden, und daher ist es natürlich notwendig, das neue Objekt vorerst mit einem anderen Zeiger zu referenzieren, denn wenn man sum einfach überschriebe, hätte man ja keinen Zeiger mehr auf das alte Objekt und könnte es somit nicht mehr wieder freigeben.
Nachdem dann sum released wurde, kann der Zeiger sum anschließend wieder auf das neue Fraction-Objekt umgebogen werden (sum=sum2;).

Ohne große Objective-C-Erfahrung zu haben, erscheint mir das offensichtlich. Das sähe selbst mit einfacher Speicherverwaltung à la Pascal (ohne Retain-Counts) nicht anders aus.
 

Amin Negm-Awad

Süsser Pfaffenapfel
Registriert
01.03.07
Beiträge
665
Das liegt daran, dass er bei der Objekterzeugung mit +alloc-init-Crap arbeitet.
Code:
Fraction *sum = [[Fraction alloc] init
Das macht man zumindest auf dem Desktop schon seit Jahren nicht mehr.

Ich hoffe mal, dass das ein Beispiel dafür sein soll, wie man es möglichst nicht macht.
 

Tekl

Fairs Vortrefflicher
Registriert
01.06.05
Beiträge
4.630
Als außenstehender würde mich nun interessieren, warum man das auf dem Desktop nicht mehr macht und anderswo doch noch, vor allem wo? iPhone?
 

Thaddäus

Golden Noble
Registriert
27.03.08
Beiträge
18.388
Das liegt daran, dass er bei der Objekterzeugung mit +alloc-init-Crap arbeitet.
Code:
Fraction *sum = [[Fraction alloc] init
Das macht man zumindest auf dem Desktop schon seit Jahren nicht mehr.

Ich hoffe mal, dass das ein Beispiel dafür sein soll, wie man es möglichst nicht macht.



Wundert mich, dass du antwortest, wo er doch das Buch der Konkurrenz gekauft hat... :p :p
 

Tekl

Fairs Vortrefflicher
Registriert
01.06.05
Beiträge
4.630
Das war doch versteckte Werbung für sein Buch. ;)
 

Drobs

Carola
Registriert
23.05.08
Beiträge
115
Wundert mich, dass du antwortest, wo er doch das Buch der Konkurrenz gekauft hat... :p :p

Ist auch schwer ein Buch zu kaufen, von dem ich gestern das erste mal gehört habe.

Vllt. habe ich das Glück und die ominöse "bessere" Methode wird später noch angesprochen. Und wenn nicht, dann hoffe ich einfach mal auf den Hillegass.

@gKar
Danke, deine Erklärung hat mir das Verständniss doch wesentlich erleichtert.
 

Jamsven

London Pepping
Registriert
21.11.07
Beiträge
2.046
Ist auch schwer ein Buch zu kaufen, von dem ich gestern das erste mal gehört habe.

Vllt. habe ich das Glück und die ominöse "bessere" Methode wird später noch angesprochen. Und wenn nicht, dann hoffe ich einfach mal auf den Hillegass.

@gKar
Danke, deine Erklärung hat mir das Verständniss doch wesentlich erleichtert.

Ich denke er meint den convenience allocator
Wobei in dem konkreten Beispiel keine Event-Loop existiert und daher der AutoreleasePool kurz vor dem main return geleert wird.
Nun müsste man seinen eigenen ARP definieren und diesen sinnvoll leeren.
Der ARP ist nichts anderes als der Typ in der Disco, welcher nicht mehr gebrauchte Gläser einsammelt.

Mal ganz davon abgesehen kannst du natürlich auch mit dem Garbage Collector arbeiten, den musst du aber im Target aktivieren. Dann kannst du auch anObject=null; schreiben. :)
 

Amin Negm-Awad

Süsser Pfaffenapfel
Registriert
01.03.07
Beiträge
665
Das war doch versteckte Werbung für sein Buch. ;)

Blödsinn, das habe ich schon geschrieben, bevor ich überhaupt nur an ein Buch dachte. Schau mal in die Wiki des OS-X-Entwicklerforums. Information hilft ja bei dem Abbau von Vorurteilen. Übrigens verdiene ich nichts an dem Buch, wenn man die Oppertunitätskosten bedenkt. Insofern habe ich von Werbung nichts. Wenn ich Geld verdienen will, schreibe ich kein Buch.

Inzwischen steht es auch in der Doku. Nur scheinen manche "alte Granden" einfach nur das Zeug herunterzuschreiben, weil sie das schon immer so gemacht haben.
 
Zuletzt bearbeitet:

Amin Negm-Awad

Süsser Pfaffenapfel
Registriert
01.03.07
Beiträge
665
Als außenstehender würde mich nun interessieren, warum man das auf dem Desktop nicht mehr macht
Weil du eine offene Ressource hinterlässt. Zuweilen vergisst man dann einfach die zu schließen, manchmal geht das auch gar nicht.
Code:
for( … ) 
   NSString* helper = [[NSString alloc] init];
   …
   if( … ) {
      break;
   }
   …
}
Entsprechendes gilt für Exceptionblöcke, in denen du dann alle zwischenzeitlich angehäuften Instanzen beseitigen darfst. Viel Spaß!

Auch das Code-Beispiel ist ein Beispiel. Natürlich wäre die erneute Zuweisung völlig ungefährlich, wenn man den ARP verwendet. Dass dies dem Autor einen besonderen Hinweis wert ist, sagt alles. Stellt sich nur die Frage, warum er nicht gleich auf die Möglichkeit des ARP hinweist. Er kann ja notfalls auf später verweisen, wenn er es da noch nicht besprechen will.

Außerdem erschwerst du die Erzeugung in besonderen Fällen wie Twintoning. Überlege dir mal eine Implementierung von -initWithString: (NSString). Dann wirst du selbst sehen, was du dir dabei einhandelst.

und anderswo doch noch, vor allem wo? iPhone?
Ja

Hintergrund ist die Speicherknappheit. Grundsätzlich leben Objekte im ARP länger, das ist also potentiell verschwendend. Daher empfehlen auf dem iPhone noch einige, mit -release gleich wieder freizugeben. Ich halte die Verwendung eines eigenen ARP allerdings für besser, siehe oben.
 
Zuletzt bearbeitet:

Amin Negm-Awad

Süsser Pfaffenapfel
Registriert
01.03.07
Beiträge
665
Nun: Ohne in die Referenz geguckt zu haben, erzeugt nach diesem Code zu urteilen die add-Methode von Fraction ein neues Fraction-Objekt (mit Retain-Count 1). Das alte Objekt (von sum referenziert) muss freigegeben werden, und daher ist es natürlich notwendig, das neue Objekt vorerst mit einem anderen Zeiger zu referenzieren, denn wenn man sum einfach überschriebe, hätte man ja keinen Zeiger mehr auf das alte Objekt und könnte es somit nicht mehr wieder freigeben.
Nachdem dann sum released wurde, kann der Zeiger sum anschließend wieder auf das neue Fraction-Objekt umgebogen werden (sum=sum2;).

Ohne große Objective-C-Erfahrung zu haben, erscheint mir das offensichtlich. Das sähe selbst mit einfacher Speicherverwaltung à la Pascal (ohne Retain-Counts) nicht anders aus.
Du siehst das richtig, nur noch eine Bemerkung:

Der Code verstößt auch noch gegen die Naming-Conventions. Eine solche "-add:"-Methode müsste etwas addieren, nicht aber eine neue Instanz erzeugen. Richtig müsste die -fractionByAddingFraction: oder ähnlich heißen. Dann müsste man auch nicht herumraten.

Aber in Büchern verstehe ich den Hang zu kurzen Namen. Der Platz ist einfach extrem beschränkt. Nur: Wenn es gerade darauf ankommt, sollte man das richtig machen.
 

Drobs

Carola
Registriert
23.05.08
Beiträge
115
Abgesehen davon, dass man es besser machen sollte, müsste man in dieser Version nicht noch sum2 freigeben? (Frage B)
 

Jamsven

London Pepping
Registriert
21.11.07
Beiträge
2.046
Nein, weil ja bei der letzten Iteration von der for Schleife sum=sum2 gesetzt wurde.
Danach bricht die Schleife ab und sum wird released.
sum=sum2 kopert nicht das Objekt, sondern dessen Speicheradresse.
Dabei wird der retain counter auch nicht inkrementiert.

Du kannst übrigends den retain count mit
Code:
[objektname retainCount]
abfragen. Daran kannst du sehen wie viel mal released werden muss. Vergiss aber dann bei anderen Programmen den ARP nicht. Der hat dann auch counts, welche du nicht beachten musst.
 
Zuletzt bearbeitet:

Amin Negm-Awad

Süsser Pfaffenapfel
Registriert
01.03.07
Beiträge
665
Abgesehen davon, dass man es besser machen sollte, müsste man in dieser Version nicht noch sum2 freigeben? (Frage B)

Wenn man die Einhaltung der Naming-Conventions unterstellt: Nein.

Grndsätzlich gilt, dass nur wenige Methoden eine offene Referenz liefern dürfen, nämlich
+alloc…
-copy…
-mutableCopy…
+new

Bei allen anderen Methoden erhältst du keine Inhaberschaft an dem Rückgabewert. Daher darfst du ihn auch nicht freigeben.

Diese Regel kann zu Problemen führen, wenn man sich folgende Situation vor Augen führt:
Code:
Person* person = … 
NSString* oldName = person.name;
person.name = @"Amin";
Da an oldName keine Inhaberschaft besteht, ist das Ding jetzt verschwunden. Aus diesem Dilemma existieren verschiedene Wege:

1. Rückgabe im ARP
Code:
- (NSString*)name {
   return [[name retain] autorelease];

2. ARP im Sender
Du kannst das auch in der versendenden Methode machen:
Code:
Person* person = … 
NSString* oldName = [[person.name retain] autorelease];
person.name = @"Amin";

3. Kopieren
Code:
Person* person = … 
NSString* oldName = [NSString stringWithString:person.name];
person.name = @"Amin";

4. Aufpasse
:)
 

Tekl

Fairs Vortrefflicher
Registriert
01.06.05
Beiträge
4.630
Das mit der Buchwerbung war ja mehr ironisch gemeint. Mir ist schon klar, dass Schreiben meist kein Gewinngeschäft und mehr Liebhaberei ist, kenne es aus eigener Erfahrung.

Danke auch für die Antwort auf meine Detailfrage. Mehr als das Prinzip habe ich aber nicht verstanden und ich muss sagen, dass mich die Syntax von ObjC ganz schön abschreckt. Bleibe wohl doch bei meinen Skriptsprachen, da kann ich’s so schreiben wie ich denke und muss nicht unzählige Dinge schreiben, an die ich nicht denke. Alleine das Beispiel mit dem oldName ist extrem gruselig. Kein Wunder, dass Software so voller Bugs ist, wenn so simple Dinge so kompliziert sind.

Gibt’s eigentlich eine Möglichkeit mit einer anderen Skriptsprache als dem ebenso gruseligen AppleScript eine native und relative performante Cocoa-Anwendung zu entwickeln wo man letztendlich ein Kompilat ausliefert? Nein, RealBasic ist nicht performant, um das gleich mal vorweg zu nehmen.
 

Amin Negm-Awad

Süsser Pfaffenapfel
Registriert
01.03.07
Beiträge
665
Na ja, es gibt einige Bridges. Aber Syntax ist doch Gewöhnung. Lasse dich davon nicht abschrecken, sondern versuche es mal ein paar Monate.