image

Der Autor

Matt Galloway ist ein iOS-Entwickler aus London. Im Jahre 2007 machte er am Pembroke College der Universität Cambridge seinen Abschluss mit einem Mastergrad in Ingenieurswissenschaften für die Fachgebiete Elektrotechnik und Informatik. Seit diesem Zeitpunkt programmiert er, und zwar hauptsächlich in Objective-C. Seit das erste SDK für iOS veröffentlicht wurde, arbeitet er als Entwickler für diese Plattform. Auf Twitter finden Sie ihn als @mattjgalloway. Darüber hinaus leistet er regelmäßig Beiträge für Stack Overflow (http://stackoverflow.com).

image

Zu diesem Buch – sowie zu vielen weiteren SmartBooks-Titeln – können Sie auch das entsprechende E-Book im PDF-Format herunterladen. Werden Sie dazu einfach Mitglied bei dpunkt.plus+: www.dpunkt.de/plus

Effektiv Objective-C 2.0 programmieren

52 Profi-Lösungen für bessere iOS- und OS-X-Programmierung

image

Matt Galloway

image

Effektiv Objective-C 2.0 programmieren

Bibliografische Information der Deutschen Nationalbibliothek

Die Deutsche Nationalbibliothek verzeichnet diese Publikation in der Deutschen Nationalbibliografie; detaillierte bibliografische Daten sind im Internet über http://dnb.d-nb.de abrufbar.

Copyright © dpunkt.verlag GmbH 2014, Wieblinger Weg 17, 69123 Heidelberg

ISBN:
Buch 978-3-944165-09-7
PDF 978-3-944165-71-4
ePub 978-3-944165-72-1

Projektleitung und Lektorat:

Gabriel Neumann

Fachgutachter:

Klaus Rodewig, Kaarst

Korrektorat:

Friederike Daenecke, Zülpich

Layout und Satz:

G&U Language & Publishing Services GmbH, www.gundu.com

Covergestaltung:

Johanna Voss, Florstadt

Herstellung:

Frank Heidt

Druck und Bindung:

M. P. Media-Print Informationstechnologie GmbH,

 

33100 Paderborn

Trotz sorgfältigem Lektorat schleichen sich manchmal Fehler ein.
Autoren und Verlag sind Ihnen dankbar für Anregungen und Hinweise!

SmartBooks

Ein Imprint der dpunkt.verlag GmbH

http://www.smartbooks.de

E-Mail: info@smartbooks.de

Authorized translation from the English language edition, entitled Effective Objective-C 2.0: 52 Specific Ways to Improve Your iOS and OS X Programs, 1st edition, 0321917014 by Galloway, Matt, published by Pearson Education, Inc, publishing as Addison-Wesley Professional, Copyright © 2013 Addison-Wesley Professional.

All rights reserved. No part of this book may be reproduced or transmitted in any form or by any means, electronic or mechanical, including photocopying, recording, or by any information retrieval system, without permission from Pearson Education, Inc. German language edition published by dpunkt.verlag GmbH, Copyright © 2014.

Autorisierte Übersetzung der englischsprachigen Originalausgabe mit dem Titel »Effective Objective-C 2.0: 52 Specific Ways to Improve Your iOS and OS X Programs« von Matt Galloway, ISBN 978-0321917010, erschienen bei Addison-Wesley Professional, ein Imprint von Pearson Education, Inc; Copyright © 2013.

Die vorliegende Publikation ist urheberrechtlich geschützt. Alle Rechte vorbehalten.

Die Verwendung der Texte und Abbildungen, auch auszugsweise, ist ohne die schriftliche Zustimmung des Verlags urheberrechtswidrig und daher strafbar. Dies gilt insbesondere für die Vervielfältigung, Übersetzung oder die Verwendung in elektronischen Systemen.

Es wird darauf hingewiesen, dass die im Buch verwendeten Soft- und Hardware-Bezeichnungen sowie Markennamen und Produktbezeichnungen der jeweiligen Firmen im Allgemeinen warenzeichen-, marken- oder patentrechtlichem Schutz unterliegen.

Alle Angaben und Programme in diesem Buch wurden mit größter Sorgfalt kontrolliert. Weder Autor noch Verlag können jedoch für Schäden haftbar gemacht werden, die in Zusammenhang mit der Verwendung dieses Buches stehen.

Besuchen Sie uns im Internet!
www.smartbooks.de
www.smartbooks.ch

Für Rosie

Übersicht

                  Vorwort

                  Danksagung

Kapitel 1 Machen Sie sich mit Objective-C vertraut

Kapitel 2 Objekte, Nachrichten und die Laufzeit

Kapitel 3 Interface- und API-Design

Kapitel 4 Protokolle und Kategorien

Kapitel 5 Speicherverwaltung

Kapitel 6 Blöcke und Grand Central Dispatch

Kapitel 7 Die System-Frameworks

                  Index

Inhaltsverzeichnis

Vorwort

Danksagung

1 Machen Sie sich mit Objective-C vertraut

Thema 1    Lernen Sie die Ursprünge von Objective-C kennen

Thema 2    Importieren Sie möglichst keine Header in Header

Thema 3    Bevorzugen Sie Literale gegenüber gleichwertigen Methoden

Thema 4    Verwenden Sie typisierte Konstanten statt der Präprozessordirektive #define

Thema 5    Verwenden Sie Aufzählungen für Zustände, Optionen und Statuscodes

2 Objekte, Nachrichten und die Laufzeit

Thema 6    Grundlagen von Propertys

Thema 7    Lesen Sie Instanzvariablen beim internen Zugriff vorzugsweise direkt

Thema 8    Wann sind Objekte gleich?

Thema 9    Verbergen Sie die Einzelheiten der Implementierung mit Klassenclustern

Thema 10  Hängen Sie benutzerdefinierte Daten mit verknüpften Objekten an bestehende Klassen an

Thema 11  Die Rolle von objc_msgSend

Thema 12  Nachrichtenweiterleitung

Thema 13  Debuggen Sie undurchsichtige Methoden mit Methodenswizzling

Thema 14  Was sind Klassenobjekte?

3 Interface- und API-Design

Thema 15  Vermeiden Sie Namenskonflikte mithilfe von Namenspräfixen

Thema 16  Verwenden Sie einen designierten Initialisierer

Thema 17  Implementieren Sie die description-Methode

Thema 18  Bevorzugen Sie unveränderbare Objekte

Thema 19  Vergeben Sie klare und einheitliche Namen

Thema 20  Stellen Sie den Namen von privaten Methoden ein Präfix voran

Thema 21  Das Fehlermodell von Objective-C

Thema 22  Das Protokoll NSCopying

4 Protokolle und Kategorien

Thema 23  Verwenden Sie Delegate- und Datenquellenprotokolle für die Kommunikation zwischen Objekten

Thema 24  Zerlegen Sie Klassenimplementierungen mit Kategorien in handhabbare Segmente

Thema 25  Stellen Sie Kategorienamen von Drittanbieterklassen immer ein Präfix voran

Thema 26  Vermeiden Sie Propertys in Kategorien

Thema 27  Verbergen Sie die Implementierungsdetails mit der Klassenerweiterungskategorie

Thema 28  Stellen Sie anonyme Objekte mit einem Protokoll bereit

5 Speicherverwaltung

Thema 29  Reference Counting

Thema 30  Vereinfachen Sie das Reference Counting mit ARC

Thema 31  Führen Sie in dealloc nur die Freigabe von Referenzen und das Aufräumen des Beobachterstatus durch

Thema 32  Vorsicht bei der Speicherverwaltung für ausnahmesicheren Code

Thema 33  Vermeiden Sie zyklische Verweise mithilfe schwacher Referenzen

Thema 34  Verringern Sie die maximale Speichernutzung mithilfe von Autorelease-Blöcken

Thema 35  Spüren Sie Probleme bei der Speicherverwaltung mithilfe von Zombies auf

Thema 36  Vermeiden Sie die Verwendung von retainCount

6 Blöcke und Grand Central Dispatch

Thema 37  Blöcke

Thema 38  Erstellen Sie typedefs für gebräuchliche Blocktypen

Thema 39  Verringern Sie die Codetrennung durch Handlerblöcke

Thema 40  Vermeiden Sie zyklische Verweise durch Blöcke, die auf die Besitzerobjekte verweisen

Thema 41  Verwenden Sie zur Synchronisierung Queues statt Sperren

Thema 42  Verwenden Sie GCD statt performSelector & Co.

Thema 43  Wann müssen Sie GCD verwenden und wann Operations-Queues?

Thema 44  Nutzen Sie die Vorteile der Plattformskalierung mithilfe von Dispatch-Gruppen

Thema 45  Verwenden Sie dispatch_once für die threadsichere einmalige Codeausführung

Thema 46  Vermeiden Sie dispatch_get_current_queue

7 Die System-Frameworks

Thema 47  Machen Sie sich mit den System-Frameworks vertraut

Thema 48  Verwenden Sie Blockaufzählungen statt for-Schleifen

Thema 49  Verwenden Sie Toll-Free Bridging für Collections mit benutzerdefinierter Speicherverwaltung

Thema 50  Verwenden Sie für Caches NSCache statt NSDictionary

Thema 51  Halten Sie Implementierungen von initialize und load schlank

Thema 52  Denken Sie daran, dass NSTimer eine starke Referenz hält

Index

Vorwort

Objective-C ist weitschweifig. Objective-C ist plump. Objective-C ist hässlich. All diese Meinungen über Objective-C habe ich schon gehört. Ich dagegen empfinde die Sprache als elegant, flexibel und schön. Damit sie so sein kann, müssen Sie allerdings nicht nur die Grundlagen, sondern auch die Eigenheiten, Fallstricke und Feinheiten kennen, und darum geht es in diesem Buch.

Über dieses Buch

Die Syntax von Objective-C können Sie mit diesem Buch nicht lernen. Es wird vorausgesetzt, dass Sie sie bereits kennen. Stattdessen soll Ihnen dieses Buch beibringen, wie Sie das ganze Potenzial der Sprache ausschöpfen, um guten Code zu schreiben. Objective-C ist dank seiner Wurzeln in Smalltalk außerordentlich dynamisch. Ein Großteil der Arbeit, die in anderen Sprachen gewöhnlich der Compiler ausführt, wird in Objective-C von der Laufzeitkomponente erledigt. Das kann dazu führen, dass Code beim Testen hervorragend funktioniert, in der Produktion aber auf seltsame Weise fehlschlägt, etwa bei der Verarbeitung ungültiger Daten. Die beste Lösung zur Vermeidung solcher Probleme besteht natürlich darin, guten Code zu schreiben.

In vielen der Themen dieses Buches geht es streng genommen nicht um Objective-C im engeren Sinne, sondern um Einrichtungen aus den Systembibliotheken, z. B. um Grand Central Dispatch aus libdispatch. Es werden auch viele Klassen aus dem Foundation-Framework behandelt, nicht zuletzt auch die Wurzelklasse NSObject, da moderne Objective-C-Programmierung gewöhnlich für die Plattformen OS X und iOS eingesetzt wird. Bei der Entwicklung für diese Betriebssysteme werden Sie zweifellos die System-Frameworks Cocoa bzw. Cocoa Touch verwenden.

Seit dem Aufkommen von iOS haben sich Scharen von Entwicklern auf die Programmierung in Objective-C gestürzt. Manche dieser Entwickler sind sind Neulinge, andere kommen von Java oder C++, wieder andere aus der Webentwicklung. Zu welcher Gruppe Sie auch immer gehören mögen, nehmen Sie sich die Zeit, die Sprache effektiv zu lernen. Dadurch können Sie Code schreiben, der effizienter ist, sich leichter warten lässt und weniger Fehler enthält.

Ich habe nur etwa sechs Monate gebraucht, um dieses Buch zu schreiben, aber Jahre der Erfahrung sind darin eingeflossen. Aus einer Laune heraus hatte ich einen iPod touch gekauft, und als das erste SDK veröffentlicht wurde, ent-schloss ich mich, ein wenig mit der Entwicklung herumzuspielen. Das führte dazu, dass ich meine erste »App« schrieb, die ich unter dem Namen Subnet Calc veröffentlichte und die weit häufiger heruntergeladen wurde, als ich mir ausgemalt hatte. Damit stand für mich fest, dass meine Zukunft in dieser wunderschönen Sprache lag, die ich neu kennengelernt hatte. Seit diesem Zeitpunkt erforsche ich Objective-C und führe auf meiner Website www.galloway.me.uk/ ein Blog darüber. Am meisten interessieren mich die internen Mechanismen, z. B. die Funktionsweise von Blöcken und ARC. Als ich die Gelegenheit erhielt, ein Buch über diese Sprache zu schreiben, ergriff ich meine Chance.

Um die Möglichkeiten dieses Buches ganz auszuschöpfen, sollten Sie darin herumstöbern und sich die Themen herauspicken, die für Sie besonders interessant sind oder die Aufgaben betreffen, an denen Sie gerade arbeiten. Alle Themen können unabhängig voneinander gelesen werden, wobei Querverweise zu verwandten Themen führen. In den einzelnen Kapiteln sind jeweils ähnliche Themen zusammengefasst, weshalb Sie sich an den Kapitelüberschriften orientieren können, um die Themen zu bestimmten Sprachmerkmalen zu finden.

Zielgruppe

Dieses Buch richtet sich an Entwickler, die ihre Kenntnisse in Objective-C vertiefen und Code schreiben möchten, der wartungsfreundlich und effizient ist und weniger Fehler enthält. Auch wenn Sie noch kein Objective-C-Entwickler sind, aber einen Hintergrund in anderen objektorientierten Sprachen wie Java oder C++ haben, können Sie hier etwas lernen. In diesem Fall ist es jedoch klug, sich zunächst eingehender mit der Syntax von Objective-C zu beschäftigen.

Der Inhalt dieses Buches

Dieses Buch dient nicht dazu, Ihnen die Grundlagen von Objective-C beizubringen, die Sie aus vielen anderen Büchern und Quellen lernen können. Stattdessen geht es darum, wie Sie diese Sprache effektiv einsetzen. Das Buch ist in Themen mit einer gut verdaulichen Menge an Informationen gegliedert, und diese Themen wiederum sind logisch zu Kapiteln geordnet:

Kapitel 1: Machen Sie sich mit Objective-C vertraut
Hier lernen Sie die wichtigsten allgemeinen Prinzipien der Sprache kennen.

Kapitel 2: Objekte, Nachrichten und die Laufzeit
Das Thema dieses Kapitels sind die Beziehungen und Wechselwirkungen zwischen den Objekten, die ein wichtiges Merkmal jeder objektorientierten Sprache bilden. Außerdem wird die Laufzeitkomponente genauer untersucht.

Kapitel 3: Interface- und API-Design
Code wird selten nur für den einmaligen Gebrauch geschrieben und dann nie wieder verwendet. Selbst wenn Sie ihn nicht veröffentlichen, werden Sie Ihren Code wahrscheinlich in mehr als einem Projekt einsetzen. In diesem Kapitel erfahren Sie, wie Sie Klassen schreiben, die in Objective-C nicht fremdkörperhaft wirken.

Kapitel 4: Protokolle und Kategorien
Protokolle und Kategorien sind wichtige Sprachmerkmale, die Sie beherrschen müssen. Ihre effektive Nutzung macht Ihren Code leichter lesbar, wartungsfreundlicher und weniger fehleranfällig. Dieses Kapitel hilft Ihnen dabei, die Dinge zu meistern.

Kapitel 5: Speicherverwaltung
Das Speicherverwaltungsmodell von Objective-C nutzt Reference Counting, was für Neulinge lange Zeit eine heikle Angelegenheit war, vor allem für diejenigen, die bereits in einer Sprache mit Garbage Collector programmiert haben. Die Einführung von Automatic Reference Counting (ARC) macht die Sache einfacher, aber Sie müssen trotzdem noch auf eine Menge wichtiger Dinge achten, damit Ihr Objektmodell korrekt ist und keine Speicherlecks aufweist. Dieses Kapitel fördert Ihre Wahrnehmung für häufig vorkommende Fallstricke der Speicherverwaltung.

Kapitel 6: Blöcke und Grand Central Dispatch
Blöcke sind lexikalische Closures für C, die von Apple eingeführt wurden. Sie werden in Objective-C häufig verwendet, um Dinge zu erreichen, für die sonst viel repetitiver Standardcode geschrieben werden müsste, und um eine Codetrennung herbeizuführen. Grand Central Dispatch (GCD) bietet eine einfache Schnittstelle für Multithreading. Blöcke werden dabei als GCD-Aufgaben betrachtet, die je nach verfügbaren Systemressourcen auch parallel ausgeführt werden können. Die Lektüre dieses Kapitels versetzt Sie in die Lage, diese beiden wichtigen Technologien anzuwenden.

Kapitel 7: Die System-Frameworks
Objective-C-Code schreiben Sie gewöhnlich für OS X oder iOS. In diesen Fällen steht Ihnen mit Cocoa bzw. Cocoa Touch der komplette Vorrat an System-Frameworks zur Verfügung. Dieses Kapitel gibt einen kurzen Überblick über die Frameworks und stellt einige ihrer Klassen genauer vor.

Wenn Sie irgendwelche Fragen, Kommentare oder Bemerkungen zu diesem Buch haben, können Sie sich gern (in englischer Sprache) an mich wenden.

Kontaktinformationen finden Sie auf der Website zu diesem Buch auf www.effectiveobjectivec.com.

Danksagung

Als ich gebeten wurde, ein Buch über Objective-C zu schreiben, war ich sofort ganz aufgeregt. Ich hatte bereits andere Bücher aus dieser Reihe gelesen, weshalb es mir klar war, dass es eine Herausforderung darstellt, ein Buch dieser Art für Objective-C zu verfassen. Mit der Hilfe vieler anderer Personen konnte dieses Buch aber verwirklicht werden.

Viele Anregungen für dieses Buch stammen aus den hervorragenden Blogs über Objective-C. Mike Ash, Matt Gallagher und »bbum« sind nur einige der Personen, deren Blogs ich lese. Sie haben mir geholfen, mit den Jahren die Sprache eingehend zu verstehen. Auch NSHipster von Matt Thompson bietet großartige Artikel, die mir Denkanstöße gegeben haben, während ich dieses Buch zusammenstellte. Auch die hervorragende Dokumentation von Apple erwies sich als äußerst nützlich.

Ohne die ausgezeichnete Betreuung und Wissensvermittlung während meiner Arbeit für MX Telecom wäre ich nicht in der Lage gewesen, dieses Buch zu schreiben. Vor allem habe ich Matthew Hodgson zu danken, der mir die Gelegenheit gab, auf der Grundlage eines ausgereiften C++-Codestamms die erste iOS-Anwendung der Firma zu schreiben. Das Wissen, das ich in diesem Projekt gewonnen habe, bildete die Grundlage für einen Großteil meiner nachfolgenden Arbeit.

Im Laufe der Jahre habe ich mit vielen außergewöhnlichen Kollegen zusammengearbeitet, mit denen ich später in Kontakt geblieben bin, sei es aus fachlichen Gründen oder um einfach ein Bierchen zu trinken und einen netten Schwatz zu halten. Alle haben mir dabei geholfen, dieses Buch zu schreiben.

Mit dem Team von Pearson habe ich fantastische Erfahrungen gemacht. Trina MacDonald, Olivia Basegio, Scott Meyers und Chris Zahn haben mir Hilfe und Ermutigung geboten, wenn ich sie brauchte. Sie ermöglichten es mir, das Buch ohne Ablenkung zu schreiben, und beantworteten all meine Fragen.

Die Fachgutachter, mit denen ich zusammenarbeiten durfte, waren außerordentlich hilfreich. Mit ihren Adleraugen haben sie den Inhalt dieses Buchs so weit verbessert, wie es nur möglich war. Sie können alle stolz auf die Genauigkeit sein, die sie bei der Untersuchung des Manuskripts an den Tag legten.

Ohne das Verständnis und die Unterstützung von Helen hätte ich dieses Buch nicht verfassen können. Unser erstes Kind wurde an dem Tag geboren, an dem ich mit dem Schreiben beginnen sollte, weshalb ich die Aufgabe natürlich kurzzeitig aufschob. Sowohl Helen als auch Rosie waren fantastisch dabei, mich während der ganzen Zeit in Schwung zu halten.

Kapitel 1
Machen Sie sich mit Objective-C vertraut

image

Mit einer ganz neuen Syntax erweitert Objective-C die Sprache C um objektorientierte Merkmale. Diese Syntax wird oft als weitschweifig bezeichnet, da sie eine ganze Menge eckiger Klammern umfasst und auch vor extrem langen Methodennamen nicht zurückschreckt. Der resultierende Quellcode ist zwar sehr gut lesbar, bietet aber seine Schwierigkeiten für C++- und Java-Entwickler.

Objective-C lässt sich schnell erlernen, weist aber viele Feinheiten auf, über die Sie sich im Klaren sein müssen, und es verfügt über Merkmale, die häufig übersehen werden. Manche Merkmale werden oft missbraucht oder nicht richtig verstanden, was zu Code führt, der sich nur schwer warten oder debuggen lässt. In diesem Kapitel geht es um die Grundlagen. Die weiteren Kapitel behandeln einzelne Gebiete der Sprache und der zugehörigen Frameworks.

Thema 1: Lernen Sie die Ursprünge von Objective-C kennen

Objective-C ähnelt anderen objektorientierten Sprachen wie C++ und Java, weist aber auch viele Unterschiede auf. Wenn Sie bereits Erfahrungen in solchen Sprachen haben, werden Ihnen viele der verwendeten Techniken und Muster vertraut vorkommen. Die Syntax allerdings dürfte fremdartig wirken, da sie anstelle von Funktionsaufrufen eine Benachrichtigungsstruktur verwendet. Objective-C hat sich aus der Sprache Smalltalk entwickelt, aus der das Konzept der Nachrichten (messaging) bildet. Den Unterschied zwischen Nachrichten und Funktionsaufrufen können Sie im folgenden Beispiel erkennen:

// Nachricht (Objective-C)
Object *obj = [Object new];
[obj performWith:parameter1 and:parameter2];

// Funktionsaufruf (C++)
Object *obj = new Object;
obj->perform(parameter1, parameter2);

Der Hauptunterschied besteht darin, dass in der Nachrichtenstruktur die Laufzeitkomponente entscheidet, welcher Code ausgeführt wird, während dies bei Funktionsaufrufen der Compiler tut. Nur wenn in dem Beispiel mit dem Funktionsaufruf ein Polymorphismus auftritt, wird zur Laufzeit in einer virtuellen Tabelle nachgeschlagen, was bei Nachrichten jedoch immer der Fall ist. Der Compiler kümmert sich nicht einmal darum, an welche Art von Objekt die Nachricht geht. Auch dies wird zur Laufzeit ermittelt, und zwar durch den Vorgang der dynamischen Bindung, die wir uns ausführlicher in Thema 11 ansehen.

Die Hauptarbeit erledigt in Objective-C die Laufzeitkomponente und nicht der Compiler. Sie enthält alle Datenstrukturen und Funktionen, die für die objektorientierten Merkmale der Sprache erforderlich sind. Darunter fallen beispielsweise auch alle Methoden zur Speicherverwaltung. Im Grunde handelt es sich bei der Laufzeitkomponente um Code, der den von Ihnen geschriebenen Code zusammenhält. Die Komponente hat die Form einer dynamischen Bibliothek, mit der Ihr Code verlinkt ist. Daher kann Ihre Anwendung bei einer Aktualisierung der Laufzeitkomponente sofort auf die damit einhergehenden Geschwindigkeitsverbesserungen zurückgreifen. Bei einer Sprache, die die meiste Arbeit bei der Kompilierung erledigt, muss die Anwendung neu kompiliert werden, um eine Leistungssteigerung der Laufzeitumgebung zu nutzen.

Objective-C ist eine Obermenge von C, weshalb darin alle Merkmale von C enthalten sind. Um mit Objective-C effektiv zu programmieren, müssen Sie daher die Grundprinzipien sowohl von C als auch von Objective-C verstehen. Insbesondere sind Kenntnisse über das Speichermodell von C hilfreich, um das Speichermodell von Objective-C und das Reference Counting zu begreifen. Dazu müssen Sie sich darüber im Klaren sein, dass ein Zeiger in Objective-C zur Bezeichnung eines Objekts dient. Wenn Sie eine Variable mit einem Verweis auf ein Objekt deklarieren, sieht der dazu erforderliche Code wie folgt aus:

NSString *someString = @"The string";

Diese Syntax ist fast unverändert von C übernommen. Hier wird die Variable some-String vom Typ NSString* deklariert, also als Zeiger auf ein NSString-Objekt. Alle Objective-C-Objekte müssen auf diese Weise deklariert werden, da der Arbeitsspeicher für Objekte stets auf dem Heap zugewiesen wird und niemals auf dem Stack. Es ist nicht möglich, ein Objective-C-Objekt auf dem Stack zuzuweisen:

NSString stackString;
// Fehler: Der Interfacetyp kann nicht statisch zugewiesen werden

Die Variable someString zeigt auf eine Stelle im Arbeitsspeicher, die auf dem Heap zugewiesen ist und ein NSString-Objekt enthält. Wenn Sie also eine weitere Variable erstellen, die auf denselben Speicherort zeigt, legen Sie damit keine Kopie an, sondern bekommen nur zwei Variablen, die beide auf dasselbe Objekt verweisen:

NSString *someString = @"The string";
NSString *anotherString = someString;

Es gibt hier nur eine Instanz von NSString, aber zwei Variablen, die auf sie zeigen. Sie haben den Typ NSString*, was bedeutet, dass im aktuellen Stack-Frame zwei Abschnitte des Arbeitsspeichers von der Größe eines Zeigers zugewiesen sind (vier Byte in einer 32-Bit-Architektur, acht Byte bei 64 Bit). Diese beiden Speicherabschnitte enthalten den gleichen Wert, nämlich die Speicheradresse der NSString-Instanz.

Diesen Aufbau können Sie in Abbildung 1–1 erkennen. Die für die NSString-Instanz gespeicherten Daten enthalten die erforderlichen Bytes zur Darstellung des eigentlichen Strings.

image

Abb. 1–1 Skizze des Arbeitsspeichers mit einer auf dem Heap zugewiesenen Instanz von NSString und zwei auf dem Stack zugewiesenen Zeigern darauf

Der auf dem Heap zugewiesene Arbeitsspeicher muss direkt verwaltet werden, während der Arbeitsspeicher für die Variablen auf dem Stack automatisch bereinigt wird, wenn der Stack-Frame, auf dem sie sich befinden, vom Stack genommen wird.

Die Verwaltung des Heap-Arbeitsspeichers ist in Objective-C aus Ihrem Verantwortungsbereich herausabstrahiert worden. Sie müssen nicht malloc und free verwenden, um Arbeitsspeicher für Objekte zuzuweisen und wieder freizugeben. Die Laufzeitumgebung von Objective-C übernimmt dies mithilfe ihrer Speicherverwaltungsarchitektur, des sogenannten Reference Counting (siehe Thema 29).

Manchmal stoßen Sie in Objective-C jedoch auch auf Variablen, die in ihrer Definition kein * aufweisen und möglicherweise Stack-Arbeitsspeicher nutzen. Solche Variablen halten keine Objective-C-Objekte fest. Ein Beispiel dafür ist CGRect aus dem CoreGraphics-Framework:

CGRect frame;
frame.origin.x = 0.0f;
frame.origin.y = 10.0f;
frame.size.width = 100.0f;
frame.size.height = 150.0f;

Ein CGRect ist eine C-Struktur mit folgender Definition:

struct CGRect {
CGPoint origin;

CGSize size;
};
typedef struct CGRect CGRect;

Diese Arten von Strukturen werden in Systemframeworks verwendet, in denen der Mehraufwand für die Verwendung von Objective-C-Objekten die Leistung beeinträchtigen könnte. Um Objekte zu erstellen, sind Vorgänge erforderlich, die bei der Verwendung von Strukturen nicht anfallen, beispielsweise die Zuweisung und Freigabe von Heap-Speicher. Daher werden gewöhnlich Strukturen von CGRect verwendet, wenn nur Daten von Nichtobjekttypen (wie int, float, double, char usw.) festgehalten werden müssen.

Bevor Sie beginnen, Objective-C-Code zu schreiben, sollten Sie viel über die Sprache C lesen und sich mit ihrer Syntax vertraut machen. Wenn Sie unvorbereitet mit Objective-C beginnen, können einige Teile der Syntax verwirrend wirken.

Thema 2: Importieren Sie möglichst keine Header in Header

Ebenso wie in C und C++ werden auch in Objective-C Header- und Implementierungsdateien verwendet. Für eine in Objective-C geschriebene Klasse wird gewöhnlich jeweils eine Datei jeder Art verwendet. Beide tragen den Namen der Klasse, wobei die Headerdatei die Endung .h aufweist und die Implementierungsdatei die Endung .m. Wenn Sie eine Klasse erstellen, sieht das also wie folgt aus:

// EOCPerson.h
#import <Foundation/Foundation.h>

@interface EOCPerson : NSObject
@property (nonatomic, copy) NSString *firstName;
@property (nonatomic, copy) NSString *lastName;
@end

// EOCPerson.m
#import "EOCPerson.h"

@implementation EOCPerson
// Implementierung der Methoden
@end

Der Import von Foundation.h ist für so ziemlich alle Klassen erforderlich, die Sie jemals in Objective-C erstellen werden. In allen anderen Fällen müssen Sie die grundlegende Headerdatei des Frameworks importieren, aus dem die Oberklasse Ihrer neuen Klasse stammt. Wenn Sie beispielsweise eine iOS-Anwendung schreiben, bilden Sie häufig Unterklassen von UIViewController. In den Headerdateien dieser Klassen müssen Sie UIKit.h importieren.

Die Klasse ist in diesem Zustand tadellos. Sie importiert zwar das gesamte Foundation-Framework, aber das macht nichts. Da diese Klasse von einer Klasse erbt, die zu Foundation gehört, ist es schließlich sehr wahrscheinlich, dass die Nutzer von EOCPerson große Teile davon verwenden werden. Das Gleiche gilt auch für eine Klasse, die von UIViewController erbt. Auch ihre Nutzer verwenden den Großteil von UIKit.

Nun kann es sein, dass Sie irgendwann eine neue Klasse namens EOCEmployer für einen Arbeitgeber erstellen und zu dem Schluss kommen, dass eine EOCPerson-Instanz auch über einen Arbeitgeber verfügen sollte. Daher fügen Sie eine Eigenschaft dafür hinzu:

// EOCPerson.h
#import <Foundation/Foundation.h>

@interface EOCPerson : NSObject
@property (nonatomic, copy) NSString *firstName;
@property (nonatomic, copy) NSString *lastName;
@property (nonatomic, strong) EOCEmployer *employer;
@end

Dabei besteht jedoch das Problem, dass die Klasse EOCEmployer beim Kompilieren von Klassen, die EOCPerson.h importieren, nicht sichtbar ist. Es wäre aber falsch, für alle Klassen, die EOCPerson.h importieren, auch den Import von EOCEmployer.h vorzuschreiben. Also wird oft folgende Zeile am Anfang von EOCPerson.h eingefügt:

#import "EOCEmployer.h"

Das funktioniert zwar, ist aber schlechter Stil. Wenn Sie irgendetwas kompilieren, das EOCPerson nutzt, müssen Sie nicht alle Einzelheiten über EOCEmployer kennen, sondern nur wissen, dass es eine solche Klasse gibt. Tatsächlich gibt es eine Möglichkeit, um dem Compiler genau das mitzuteilen:

@class EOCEmployer;

Dies ist eine sogenannte Vorausdeklaration der Klasse. Die resultierende Headerdatei für EOCPerson sieht damit wie folgt aus:

// EOCPerson.h
#import <Foundation/Foundation.h>

@class EOCEmployer;

@interface EOCPerson : NSObject
@property (nonatomic, copy) NSString *firstName;
@property (nonatomic, copy) NSString *lastName;
@property (nonatomic, strong) EOCEmployer *employer;
@end

Es ist dann die Implementierungsdatei von EOCPerson, in der die Headerdatei von EOCEmployer importiert wird, da sie alle Einzelheiten des Interface dieser Klasse kennen muss, um sie nutzen zu können. Damit sieht die Implementierungsdatei wie folgt aus:

// EOCPerson.m
#import "EOCPerson.h"
#import "EOCEmployer.h"

@implementation EOCPerson
// Implementierung der Methoden
@end

Durch die Verzögerung des Imports bis zu der Stelle, an der er erforderlich ist, können Sie den Umfang der Klassen beschränken, die ein Benutzer Ihrer Klasse importieren muss. Wenn wir in unserem Beispiel EOCEmployer.h in EOCPerson.h importieren, müsste jeder, der EOCPerson.h importiert, auch den gesamten Inhalt von EOCPEmployer.h importieren. Sollte sich die Kette der Importe noch weiter fortsetzen, importieren Sie am Ende viel mehr als vorgesehen, was die Kompilierungszeit mit Sicherheit verlängert.

Die Vorausdeklaration lindert auch das Problem, dass die beiden Klassen aufeinander verweisen. Überlegen Sie, was geschieht, wenn in der Headerdatei von EOCEmployer Methoden zum Hinzufügen und Entfernen von Angestellten (employees) definiert sind:

- (void)addEmployee:(EOCPerson*)person;
- (void)removeEmployee:(EOCPerson*)person;

Hier muss die Klasse EOCPerson aus den gleichen Gründen für den Compiler sichtbar sein wie im umgekehrten Fall. Wenn Sie dies jedoch dadurch zu erreichen versuchen, dass Sie den anderen Header in diesen Header importieren, hätten Sie ein Henne-oder-Ei-Problem geschaffen. Wird ein Header geparst, so importiert er den anderen, der wiederum den ersten importiert. Die Verwendung von #import statt #include führt zwar nicht zu einer Endlosschleife, aber immerhin dazu, dass eine der beiden Klassen nicht korrekt kompiliert wird. Wenn Sie mir nicht glauben, dann probieren Sie es selbst aus!

Manchmal ist es jedoch nötig, einen Header in einen Header zu importieren. Das kann beispielsweise bei einem Header der Fall sein, der die Oberklasse definiert, von der Ihre Klasse erben soll. Auch Protokolle, denen Ihre Klasse gehorchen soll, müssen vollständig definiert und nicht nur im Voraus deklariert werden. Der Compiler muss die Methoden sehen können, die das Protokoll definiert, und nicht nur wissen, dass es dieses Protokoll gibt, wie es bei einer Vorwärtsdeklaration der Fall wäre.

Betrachten Sie als Beispiel eine Klasse für Rechtecke, die von einer Klasse für allgemeine Formen erbt und mit einem Protokoll konform ist, durch das sie gezeichnet werden kann:

// EOCRectangle.h
#import "EOCShape.h"
#import "EOCDrawable.h"

@interface EOCRectangle : EOCShape <EOCDrawable>
@property (nonatomic, assign) float width;
@property (nonatomic, assign) float height;
@end

Der zusätzliche Import ist unvermeidlich. Aus diesem Grunde ist es angeraten, solche Protokolle jeweils in ihrer eigenen Headerdatei unterzubringen. Wenn EOCDrawable in einer umfangreicheren Headerdatei enthalten wäre, müssten Sie diese komplett importieren und dabei die zuvor beschriebenen Abhängigkeiten und Kompilierungszeitprobleme in Kauf nehmen.

Nicht alle Protokolle müssen aber in jeweils eigene Dateien gestellt werden. Das gilt beispielsweise für Delegate-Protokolle (siehe Thema 23). Solche Protokolle sind nur dann sinnvoll, wenn sie zusammen mit der Klasse definiert sind, für die sie als Delegate fungieren. In diesen Fällen bietet es sich an, die Implementierung des Delegates durch Ihre Klasse in die Klassenerweiterungskategorie zu stellen (siehe Thema 27). Damit können Sie den Import des Headers mit dem Delegate-Protokoll in der Implementierungsdatei vornehmen anstatt in der öffentlichen Headerdatei.

Sobald Sie irgendetwas in eine Headerdatei importieren, sollten Sie sich immer fragen, ob es wirklich nötig ist. Wenn der Import auch im Voraus deklariert werden kann, sollten Sie lieber das tun. Wird der Import für die Verwendung in einer Eigenschaft oder einer Instanzvariable oder zur Konformität mit einem Protokoll gebraucht, sollten Sie ihn die Klassenerweiterungskategorie verlagern (siehe Thema 27). Dadurch halten Sie die Kompilierungszeit so kurz wie möglich und verringern Abhängigkeiten, die zu Problemen bei der Wartung oder bei der Bereitstellung von Teilen Ihres Codes in öffentlichen APIs führen könnten.

Thema 3: Bevorzugen Sie Literale gegenüber gleichwertigen Methoden

Bei der Verwendung von Objective-C werden Sie immer wieder auf eine Handvoll Klassen stoßen, die alle zum Foundation-Framework gehören. Rein technisch gesehen ist es zwar nicht erforderlich, Foundation zu verwenden, um Objective-C-Code zu schreiben, aber in der Praxis werden Sie es gewöhnlich tun. Die allgegenwärtigen Klassen sind NSString, NSNumber, NSArray und NSDictionary, wobei schon aus den Namen abzulesen ist, für welche Datenstrukturen sie stehen.

Objective-C ist für eine wortreiche Syntax bekannt. Das stimmt zwar, doch seit der Einführung von Objective-C 1.0 gibt es auch eine sehr einfache Möglichkeit, um ein NSString-Objekt zu erstellen, nämlich das sogenannte Stringliteral, das wie folgt aussieht:

NSString *someString = @"Effective Objective-C 2.0";

Ohne diese Art von Syntax müssten Sie ein NSString-Objekt mit den üblichen Methodenaufrufen alloc und init zuweisen und initialisieren. Zum Glück wurde die Literalsyntax in den neuesten Versionen des Compilers auch auf Instanzen von NSNumber, NSArray und NSDictionary ausgedehnt. Mit dieser Syntax können Sie den Umfang des Quellcodes verringern und ihn besser lesbar gestalten.

Zahlenliterale

Manchmal müssen Sie einen Integer, eine Fließkommazahl oder einen booleschen Wert in ein Objective-C-Objekt packen. Dazu verwenden Sie die Klasse NSNumber, die mit verschiedenen Arten von Zahlen umgehen kann. Ohne die Literalsyntax erstellen Sie wie folgt eine Instanz davon:

NSNumber *someNumber = [NSNumber numberWithInt:1];

Dadurch wird ein Satz erstellt, der auf den Integer 1 gesetzt ist. Mit einem Literal geht das jedoch einfacher:

NSNumber *someNumber = @1;

Wie Sie sehen, ist die Literalsyntax viel kompakter. Das ist aber noch nicht alles. Die Syntax deckt auch alle anderen Arten von Daten ab, die NSNumber-Instanzen darstellen können. Betrachten Sie die folgenden Beispiele:

NSNumber *intNumber = @1;
NSNumber *floatNumber = @2.5f;
NSNumber *doubleNumber = @3.14159;
NSNumber *boolNumber = @YES;
NSNumber *charNumber = @'a';

Die Literalsyntax funktioniert auch bei Ausdrücken:

int x = 5;
float y = 6.32f;
NSNumber *expressionNumber = @(x * y);

Der Einsatz von Literalen für Zahlen ist äußerst praktisch. Dadurch wird die Verwendung von NSNumber-Objekten viel deutlicher, da die Deklaration nun hauptsächlich aus dem Wert statt aus den überflüssigen Syntaxelementen besteht.

Array-Literale

Arrays sind sehr häufig verwendete Datenstrukturen. Vor der Einführung von Literalen hätten Sie ein Array wie folgt erstellen müssen:

NSArray *animals =
    [NSArray arrayWithObjects:@"cat", @"dog", @"mouse", @"badger", nil];

Mit Literalen jedoch reicht die folgende Syntax:

NSArray *animals = @[@"cat", @"dog", @"mouse", @"badger"];

Dies ist schon eine deutlich einfachere Syntax, aber das ist noch nicht alles. Eine häufige Array-Operation besteht darin, das Objekt an einem bestimmten Index abzurufen. Auch dies wird durch Literale vereinfacht. Gewöhnlich müssten Sie die Methode objectAtIndex: verwenden:

NSString *dog = [animals objectAtIndex:1];

Bei der Verwendung von Literalen müssen Sie nur Folgendes schreiben:

NSString *dog = animals[1];

Dies wird als Indizierung bezeichnet und ist wie auch die anderen Aspekte der Literalsyntax kompakter und zeigt deutlicher, was vor sich geht. Außerdem sieht dies der Art und Weise ähnlicher, in der Arrays in anderen Sprachen indiziert werden.

Wenn Sie Arrays mit der Literalsyntax erstellen, müssen Sie jedoch einen besonderen Punkt beachten. Wenn eines der Objekte nil ist, wird eine Ausnahme ausgelöst, da die Literalsyntax im Grunde genommen nur eine angenehme Schreibweise dafür ist, ein Array zu erstellen und ihm alle Objekte innerhalb der eckigen Klammern hinzuzufügen. Die Ausnahmemeldung, die Sie erhalten, sieht wie folgt aus:

*** Terminating app due to uncaught exception
'NSInvalidArgumentException', reason: '***
-[__NSPlaceholderArray initWithObjects:count:]: attempt to
insert nil object from objects[0]'

Das bringt ein häufig auftretendes Problem beim Wechsel zur Literalsyntax ans Licht. Der folgende Code nutzt die beiden verschiedenen Schreibweisen, um damit je ein Array zu erstellen:

id object1 = /* … */;
id object2 = /* … */;
id object3 = /* … */;

NSArray *arrayA = [NSArray arrayWithObjects: object1, object2, object3, nil];
NSArray *arrayB = @[object1, object2, object3];

Stellen Sie sich nun vor, dass object1 und object3 auf gültige Objective-C-Objekte zeigen, object2 aber nil ist. Das Literal-Array arrayB führt dann zu einer Ausnahme. Dagegen wird arrayA nach wie vor erstellt, enthält jedoch nur object1. Das liegt daran, dass die Methode arrayWithObjects: Funktionen mit einer variablen Anzahl von Argumenten durchsucht, bis sie auf nil trifft, was schneller geschieht als erwartet.

Dieser feine Unterschied bedeutet, dass Literale viel sicherer sind. Es ist besser, eine Ausnahme auszulösen, die vielleicht zum Absturz der Anwendung führen könnte, als ein Array zu erstellen, das weniger Objekte enthält als erwartet. Wenn nil in ein Array aufgenommen wird, liegt das meistens an einem Fehler des Programmierers, und durch die Ausnahme lässt sich dieser Fehler leichter finden.

Dictionary-Literale

Dictionarys sind Datenstrukturen für Zuordnungen, die Schlüssel-Wert-Paare enthalten. Wie Arrays werden auch sie in Objective-C-Code häufig eingesetzt. Erstellt werden sie wie folgt:

NSDictionary *personData =
  [NSDictionary dictionaryWithObjectsAndKeys:
     @"Matt", @"firstName",
     @"Galloway",@"lastName",
     [NSNumber numberWithInt:28],
     @"age", nil];

Diese Schreibweise ist ziemlich verwirrend, da hier die Reihenfolge <Wert>, <Schlüssel>, <Wert>, <Schlüssel> verwendet wird, während Sie sich ein Dictionary gewöhnlich genau anders herum vorstellen, nämlich in Form von Schlüsseln, denen ein Wert zugeordnet ist. Daher ist dieser Code nicht gut verständlich. Auch hier bieten Literale wiederum die deutlichere Syntax:

NSDictionary *personData =
   @{@"firstName" : @"Matt",
   @"lastName" : @"Galloway",
   @"age" : @28};

Das ist nicht nur viel kompakter, hier stehen die Schlüssel auch vor den Werten, wie Sie es erwarten. Darüber hinaus zeigt das Zahlenliteral im Beispiel, wo diese Art von Zahlenliteralen praktisch ist. Bei den Werten und Schlüsseln muss es sich um Objective-C-Objekte handeln, weshalb Sie nicht einfach den Integer 28 speichern können, sondern ihn in eine Instanz von NSNumber packen müssen. Dank der Literalsyntax ist dafür nur ein einziges zusätzliches Zeichen erforderlich.

Wie bei Arrays wird auch in der Literalsyntax für Dictionarys eine Ausnahme ausgelöst, wenn irgendwelche Werte nil sind. Aber auch dies ist aus den gleichen Gründen wie zuvor eine gute Sache. Anstatt ein Dictionary mit fehlenden Werten zu erstellen, da die Methode dictionaryWithObjectsAndKeys: beim ersten Vorkommen von nil abbricht, wird eine Ausnahme ausgelöst.

Eine weitere Ähnlichkeit zu Arrays besteht darin, dass der Zugriff auf Dictionarys mit der Literalsyntax möglich ist. Die herkömmliche Vorgehensweise zum Abrufen eines Werts für einen bestimmten Schlüssel sieht wie folgt aus:

NSString *lastName = [personData objectForKey:@"lastName"];

Die gleichwertige Literalsyntax lautet:

NSString *lastName = personData[@"lastName"];

Auch hier ist die Menge an überflüssigen Syntaxelementen reduziert, sodass sich eine leicht zu lesende Codezeile ergibt.

Veränderbare Arrays und Dictionarys

Sie können die Werte an den Indizes von Arrays und den Schlüsseln in einem Dictionary durch Indizierung nicht nur abrufen, sondern auch festlegen, sofern das Objekt veränderbar ist. Mit den herkömmlichen Methoden sieht diese Festlegung wie folgt aus:

[mutableArray replaceObjectAtIndex:1 withObject:@"dog"];
[mutableDictionary setObject:@"Galloway" forKey:@"lastName"];

Durch Indizierung werden die Werte wie folgt festgelegt:

mutableArray[1] = @"dog";
mutableDictionary[@"lastName"] = @"Galloway";

Einschränkungen

Eine kleine Einschränkung der Literalsyntax besteht darin, dass es sich – außer bei Strings – bei der Klasse für das zu erstellende Objekt um eine Klasse aus dem Foundation-Framework handeln muss. Es gibt keine Möglichkeit, um stattdessen eine Instanz einer eigenen Unterklasse anzulegen. Wenn Sie das tun wollen, müssen Sie die Nichtliteralsyntax wählen. Da NSArray, NSDictionary und NSNumber aber Klassencluster sind (siehe Thema 9), werden nur selten Unterklassen von ihnen erstellt, was auch keine triviale Aufgabe ist. Außerdem sind die Standardimplementierungen meistens gut genug. Für Strings können Sie eine eigene Klasse verwenden, aber dazu müssen Sie eine Compileroption ändern. Von der Verwendung dieser Option wird jedoch abgeraten, denn sofern Sie nicht genau wissen, was Sie tun, sollten Sie ohnehin lieber NSString verwenden.

Außerdem können Sie im Fall von Strings, Arrays und Dictionarys mit der Literalsyntax nur unveränderbare Varianten erstellen. Brauchen Sie dagegen eine veränderbare Variante, müssen Sie eine veränderbare Kopie anfertigen:

NSMutableArray *mutable = [@[@1, @2, @3, @4, @5] mutableCopy];

Dies erfordert zwar einen zusätzlichen Methodenaufruf und erstellt ein zusätzliches Objekt, doch die Vorteile der Literalsyntax überwiegen diese Nachteile.

Thema 4: Verwenden Sie typisierte Konstanten statt der Präprozessordirektive #define

Beim Programmieren müssen Sie häufig Konstanten definieren. Betrachten Sie als Beispiel eine Klasse für eine Ansicht der Benutzeroberfläche, die sich selbst mithilfe einer Animation darstellt und verwirft. Eine typische Konstante, die Sie meistens aus der Klasse auslagern wollen, ist die Dauer der Animation. Anhand Ihrer Kenntnisse der Grundlagen von Objective-C und C werden Sie zur Definition dieser Konstante wahrscheinlich die folgende Vorgehensweise wählen:

#define ANIMATION_DURATION 0.3

Dies ist eine Präprozessordirektive. Alle Vorkommen des Strings ANIMATION_DURATON in Ihrem Quellcode werden durch 0.3 ersetzt. Das scheint genau das zu sein, was Sie tun wollen, aber diese Definition enthält keine Typangabe. Es ist zwar sehr wahrscheinlich, dass der Wert von etwas, das als eine Dauer (duration) deklariert ist, für eine Zeitangabe steht, aber das wird nirgendwo ausdrücklich gesagt. Außerdem ersetzt der Präprozessor blind jegliche Vorkommen von ANIMATION_DURATION. Wenn diese Deklaration in der Headerdatei steht, erfolgt diese Ersetzung auch in allen Dateien, die diesen Header importieren.

Um diese Probleme zu lösen, müssen Sie die Möglichkeiten des Compilers nutzen. Es gibt immer eine bessere Möglichkeit, um eine Konstante zu definieren, als eine Präprozessordirektive. Die folgende Zeile beispielsweise definiert eine Konstante vom Typ NSTimeInterval:

static const NSTimeInterval kAnimationDuration = 0.3;

NSTimeInterval