Die meisten iOS-Projekte wechseln teilweise oder vollständig zu Swift. Swift ist eine großartige Sprache und damit liegt die Zukunft der iOS-Entwicklung. Die Sprache ist jedoch untrennbar mit dem Toolkit verbunden, und das Swift-Toolkit weist Nachteile auf.
Der Swift-Compiler weist immer noch Fehler auf, die zum Absturz führen oder falschen Code generieren. Swift hat keinen stabilen ABI. Und vor allem laufen Swift-Projekte zu lange.
In dieser Hinsicht können bestehende Projekte rentabler sein, um die Entwicklung von Ziel-C fortzusetzen. Und Objective-C ist nicht mehr das, was es früher war!
In dieser Artikelserie zeigen wir Ihnen die nützlichen Funktionen und Verbesserungen von Objective-C, mit denen das Schreiben von Code viel angenehmer wird. Jeder, der in Objective-C schreibt, findet etwas Interessantes für sich.

let
und var
Objective-C muss keine Variablentypen mehr explizit angeben: In Xcode 8 wurde die __auto_type
, und bevor die Typinferenz von Xcode 8 in Objective-C ++ verfügbar war (unter Verwendung des Schlüsselworts auto
mit dem Aufkommen von C ++ 0X).
Fügen let
Makros let
und var
:
#define let __auto_type const #define var __auto_type
Wenn Sie früher const
nach einem Zeiger auf eine Objective-C-Klasse geschrieben haben, war dies ein unzulässiger Luxus, aber jetzt wurde die implizite Deklaration von const
(via let
) als selbstverständlich angesehen. Der Unterschied macht sich insbesondere beim Speichern eines Blocks in einer Variablen bemerkbar.
Für uns selbst haben wir eine Regel entwickelt, mit der let
und var
alle Variablen deklarieren können. Auch wenn eine Variable auf nil
initialisiert wird:
- (nullable JMSomeResult *)doSomething { var result = (JMSomeResult *)nil; if (...) { result = ...; } return result; }
Die einzige Ausnahme ist, wenn Sie sicherstellen müssen, dass einer Variablen in jedem Codezweig ein Wert zugewiesen wird:
NSString *value; if (...) { if (...) { value = ...; } else { value = ...; } } else { value = ...; }
Nur so erhalten wir eine Compiler-Warnung, wenn wir vergessen, einem der Zweige einen Wert zuzuweisen.
Und schließlich: -Wno-auto-var-id
let
und var
für Variablen vom Typ id
, müssen Sie die auto-var-id
Warnung deaktivieren (fügen Sie -Wno-auto-var-id
zu den "Anderen -Wno-auto-var-id
" in den Projekteinstellungen hinzu).
Rückgabewert vom Typ Auto Block
Nur wenige Leute wissen, dass der Compiler auf den Typ des Rückgabewerts eines Blocks schließen kann:
let block = ^{ return @"abc"; };
Es ist sehr bequem. Besonders wenn Sie reaktiven Code mit ReactiveObjC schreiben. Es gibt jedoch eine Reihe von Einschränkungen, unter denen Sie den Typ des Rückgabewerts explizit angeben müssen.
Wenn ein Block mehrere return
, die Werte unterschiedlichen Typs zurückgeben.
let block1 = ^NSUInteger(NSUInteger value){ if (value > 0) { return value; } else {
Wenn der Block eine return
, die nil
zurückgibt.
let block1 = ^NSString * _Nullable(){ return nil; }; let block2 = ^NSString * _Nullable(BOOL flag) { if (flag) { return @"abc"; } else { return nil; } };
Wenn der Block BOOL
.
let predicate = ^BOOL(NSInteger lhs, NSInteger rhs){ return lhs > rhs; };
Ausdrücke mit einem Vergleichsoperator in C (und damit in Objective-C) sind vom Typ int
. Daher ist es besser, die Regel so festzulegen, dass der Rückgabetyp BOOL
immer explizit angegeben BOOL
.
Generika und for...in
In Xcode 7 wurden in Objective-C Generika (oder besser gesagt Lightweight-Generika) angezeigt. Wir hoffen, dass Sie sie bereits verwenden. Wenn nicht, können Sie die WWDC-Sitzung ansehen oder hier oder hier lesen.
Für uns selbst haben wir eine Regel entwickelt, mit der generische Parameter immer angegeben werden, auch wenn es sich um id
( NSArray<id> *
). Somit ist es einfach, zwischen Legacy-Code zu unterscheiden, in dem noch keine generischen Parameter angegeben sind.
Mit den Makros let
und var
erwarten wir, dass wir sie in einer for...in
Schleife verwenden können:
let items = (NSArray<NSString *> *)@[@"a", @"b", @"c"]; for (let item in items) { NSLog(@"%@", item); }
Ein solcher Code wird jedoch nicht kompiliert. Höchstwahrscheinlich wurde __auto_type
in for...in
nicht unterstützt, da for...in
nur mit Sammlungen funktioniert, die das NSFastEnumeration
Protokoll implementieren. Und für Protokolle in Objective-C gibt es keine Unterstützung für Generika.
Versuchen Sie, Ihr foreach
Makro zu erstellen, um diesen Nachteil zu beheben. Das erste, was mir in den Sinn kommt: Alle Sammlungen in Foundation haben eine objectEnumerator
Eigenschaft, und das Makro könnte folgendermaßen aussehen:
#define foreach(object_, collection_) \ for (typeof([(collection_).objectEnumerator nextObject]) object_ in (collection_))
Bei NSDictionary
und NSMapTable
Protokollmethode NSMapTable
die Schlüssel und nicht die Werte (Sie müssten keyEnumerator
und nicht objectEnumerator
).
Wir müssen eine neue Eigenschaft deklarieren, die nur verwendet wird, um den Typ im Typ des Ausdrucks typeof
:
@interface NSArray<__covariant ObjectType> (ForeachSupport) @property (nonatomic, strong, readonly) ObjectType jm_enumeratedType; @end @interface NSDictionary<__covariant KeyType, __covariant ObjectType> (ForeachSupport) @property (nonatomic, strong, readonly) KeyType jm_enumeratedType; @end #define foreach(object_, collection_) \ for (typeof((collection_).jm_enumeratedType) object_ in (collection_))
Jetzt sieht unser Code viel besser aus:
Snippet für Xcode foreach (<#object#>, <#collection#>) { <#statements#> }
Generika und copy
/ mutableCopy
Ein weiterer Ort, an dem die Eingabe in Objective-C nicht verfügbar ist, sind die -mutableCopy
und -mutableCopy
(sowie die Methoden -copyWithZone:
und -mutableCopyWithZone:
wir jedoch nicht direkt aufrufen).
Um die Notwendigkeit expliziter Casts zu vermeiden, können Sie Methoden mit dem Rückgabetyp neu deklarieren. Für NSArray
wären NSArray
Erklärungen beispielsweise:
@interface NSArray<__covariant ObjectType> (TypedCopying) - (NSArray<ObjectType> *)copy; - (NSMutableArray<ObjectType> *)mutableCopy; @end
let items = [NSMutableArray<NSString *> array];
warn_unused_result
Da wir die -mutableCopy
-copy und -mutableCopy
erneut deklariert haben, wäre es schön zu garantieren, dass das Ergebnis des Aufrufs dieser Methoden verwendet wird. Zu diesem warn_unused_result
verfügt Clang über das Attribut warn_unused_result
.
#define JM_WARN_UNUSED_RESULT __attribute__((warn_unused_result))
@interface NSArray<__covariant ObjectType> (TypedCopying) - (NSArray<ObjectType> *)copy JM_WARN_UNUSED_RESULT; - (NSMutableArray<ObjectType> *)mutableCopy JM_WARN_UNUSED_RESULT; @end
Für den folgenden Code generiert der Compiler eine Warnung:
let items = @[@"a", @"b", @"c"]; [items mutableCopy];
overloadable
Nur wenige wissen, dass Sie mit Clang Funktionen in der C-Sprache (und damit in Objective-C) neu definieren können. Mit dem overloadable
Attribut können Sie Funktionen mit demselben Namen, jedoch mit unterschiedlichen Argumenttypen oder mit unterschiedlichen Nummern erstellen.
Überschreibbare Funktionen können sich nicht nur in der Art des Rückgabewerts unterscheiden.
#define JM_OVERLOADABLE __attribute__((overloadable))
JM_OVERLOADABLE float JMCompare(float lhs, float rhs); JM_OVERLOADABLE float JMCompare(float lhs, float rhs, float accuracy); JM_OVERLOADABLE double JMCompare(double lhs, double rhs); JM_OVERLOADABLE double JMCompare(double lhs, double rhs, double accuracy);
Boxed Ausdrücke
Bereits 2012 stellte Apple auf einer WWDC 413-Sitzung Literale für NSNumber
, NSArray
und NSDictionary
sowie Boxed-Ausdrücke vor. Weitere Informationen zu Literalen und Boxed-Ausdrücken finden Sie in der Clang-Dokumentation .
Mit Literalen und Boxed-Ausdrücken können Sie leicht ein Objekt erhalten, das eine Zahl oder einen Booleschen Wert darstellt. Um jedoch ein Objekt zu erhalten, das die Struktur umschließt, müssen Sie folgenden Code schreiben:
Hilfsmethoden und -eigenschaften sind für einige Klassen definiert (wie die +[NSValue valueWithCGPoint:]
-Methode und die CGPointValue
Eigenschaften), dies ist jedoch immer noch nicht so praktisch wie ein Box-Ausdruck!
Und 2015 hat Alex Denisov einen Patch für Clang erstellt, mit dem Sie Boxed-Ausdrücke verwenden können, um alle Strukturen in NSValue
.
Damit unsere Struktur Boxed-Ausdrücke unterstützt, müssen Sie nur das Attribut objc_boxable
für die Struktur hinzufügen.
#define JM_BOXABLE __attribute__((objc_boxable))
typedef struct JM_BOXABLE JMDimension { JMDimensionUnit unit; CGFloat value; } JMDimension;
Und wir können die @(...)
Syntax für unsere Struktur verwenden:
let dimension = (JMDimension){ ... }; let boxedValue = @(dimension);
Sie müssen -[NSValue getValue:]
Struktur noch über die Methode -[NSValue getValue:]
oder die Kategoriemethode -[NSValue getValue:]
.
CoreGraphics definiert sein eigenes Makro, CG_BOXABLE
, und Boxed-Ausdrücke werden bereits für die CGPoint
, CGSize
, CGVector
und CGRect
.
Für andere häufig verwendete Strukturen können wir selbst Unterstützung für Boxed-Ausdrücke hinzufügen:
typedef struct JM_BOXABLE _NSRange NSRange; typedef struct JM_BOXABLE CGAffineTransform CGAffineTransform; typedef struct JM_BOXABLE UIEdgeInsets UIEdgeInsets; typedef struct JM_BOXABLE NSDirectionalEdgeInsets NSDirectionalEdgeInsets; typedef struct JM_BOXABLE UIOffset UIOffset; typedef struct JM_BOXABLE CATransform3D CATransform3D;
Zusammengesetzte Literale
Ein weiteres nützliches Sprachkonstrukt ist das zusammengesetzte Literal . Zusammengesetzte Literale wurden in GCC als Spracherweiterung angezeigt und später zum C11-Standard hinzugefügt.
Wenn wir zuvor, nachdem wir den Aufruf von UIEdgeInsetsMake
, nur erraten konnten, welche Einrückung wir erhalten würden (wir mussten die Deklaration der UIEdgeInsetsMake
Funktion beobachten), spricht der Code mit zusammengesetzten Literalen für sich selbst:
Es ist noch bequemer, eine solche Konstruktion zu verwenden, wenn einige der Felder Null sind:
(CGPoint){ .y = 10 }
Natürlich können Sie in zusammengesetzten Literalen nicht nur Konstanten, sondern auch beliebige Ausdrücke verwenden:
textFrame = (CGRect){ .origin = { .y = CGRectGetMaxY(buttonFrame) + textMarginTop }, .size = textSize };
Schnipsel für Xcode (NSRange){ .location = <#location#>, .length = <#length#> } (CGPoint){ .x = <#x#>, .y = <#y#> } (CGSize){ .width = <#width#>, .height = <#height#> } (CGRect){ .origin = { .x = <#x#>, .y = <#y#> }, .size = { .width = <#width#>, .height = <#height#> } } (UIEdgeInsets){ .top = <#top#>, .left = <#left#>, .bottom = <#bottom#>, .right = <#right#> } (NSDirectionalEdgeInsets){ .top = <#top#>, .leading = <#leading#>, .bottom = <#bottom#>, .trailing = <#trailing#> } (UIOffset){ .horizontal = <#horizontal#>, .vertical = <#vertical#> }
Nullbarkeit
In Xcode 6.3.2 wurden in Objective-C Anmerkungen zur Nullbarkeit angezeigt . Apple-Entwickler haben sie hinzugefügt, um die Objective-C-API in Swift zu importieren. Wenn der Sprache jedoch etwas hinzugefügt wird, sollten Sie versuchen, es in Ihren Dienst zu stellen. Und wir werden erklären, wie wir die Nullbarkeit in einem Objective-C-Projekt verwenden und welche Einschränkungen bestehen.
Um Ihr Wissen aufzufrischen, können Sie sich die WWDC-Sitzung ansehen.
Als erstes haben wir begonnen, die NS_ASSUME_NONNULL_END
NS_ASSUME_NONNULL_BEGIN
/ NS_ASSUME_NONNULL_END
in alle .m
Dateien zu .m
. Um dies nicht von Hand zu tun, patchen wir Dateivorlagen direkt in Xcode.
Wir haben auch begonnen, die Nullfähigkeit für alle privaten Eigenschaften und Methoden korrekt festzulegen.
Wenn wir die Makros NS_ASSUME_NONNULL_BEGIN
/ NS_ASSUME_NONNULL_END
zu einer vorhandenen .m
Datei hinzufügen, fügen wir sofort die fehlenden nullable
, null_resettable
und _Nullable
in der gesamten Datei hinzu.
Alle nützlichen Warnungen des Nullfähigkeits-Compilers sind standardmäßig aktiviert. Es gibt jedoch eine extreme Warnung, die ich einschließen möchte: - -Wnullable-to-nonnull-conversion
(festgelegt in den "Anderen -Wnullable-to-nonnull-conversion
" in den Projekteinstellungen). Der Compiler generiert diese Warnung, wenn eine Variable oder ein Ausdruck mit einem nullbaren Typ implizit in einen nicht null Typ umgewandelt wird.
+ (NSString *)foo:(nullable NSString *)string { return string;
Leider __auto_type
diese Warnung für __auto_type
(und daher let
und var
) nicht. Der über __auto_type
abgeleitete __auto_type
verwirft die Annotation zur Nullbarkeit. Und nach dem Kommentar des Apple-Entwicklers in rdar: // 27062504 wird sich dieses Verhalten nicht ändern. Es wurde experimentell beobachtet, dass das Hinzufügen von _Nullable
oder _Nonnull
zu __auto_type
nichts beeinflusst.
- (NSString *)test:(nullable NSString *)string { let tmp = string; return tmp;
Um die nullable-to-nonnull-conversion
einer nullable-to-nonnull-conversion
zu unterdrücken nullable-to-nonnull-conversion
wir ein Makro geschrieben, das "Entpacken erzwingen". Idee aus dem RBBNotNil
Makro. Aufgrund des Verhaltens von __auto_type
es jedoch möglich, die Hilfsklasse loszuwerden.
#define JMNonnull(obj_) \ ({ \ NSCAssert(obj_, @"Expected `%@` not to be nil.", @#obj_); \ (typeof({ __auto_type result_ = (obj_); result_; }))(obj_); \ })
Ein Beispiel für die Verwendung des JMNonnull
Makros:
@interface JMRobot : NSObject @property (nonatomic, strong, nullable) JMLeg *leftLeg; @property (nonatomic, strong, nullable) JMLeg *rightLeg; @end @implementation JMRobot - (void)stepLeft { [self step:JMNonnull(self.leftLeg)] } - (void)stepRight { [self step:JMNonnull(self.rightLeg)] } - (void)step:(JMLeg *)leg {
Beachten Sie, dass zum Zeitpunkt des Schreibens die Warnung zur nullable-to-nonnull-conversion
nicht nullable-to-nonnull-conversion
nicht nullable-to-nonnull-conversion
funktioniert: Der Compiler versteht noch nicht, dass eine nullable
Variable nach Überprüfung auf Ungleichung nil
als nonnull
nil
interpretiert werden nonnull
.
- (NSString *)foo:(nullable NSString *)string { if (string != nil) { return string;
In Objective-C ++ - Code können Sie diese Einschränkung umgehen, indem Sie das Konstrukt if let
verwenden, da Objective-C ++ Variablendeklarationen im Ausdruck einer if
zulässt.
- (NSString *)foo:(nullable NSString *)stringOrNil { if (let string = stringOrNil) { return string; } else { return @""; } }
Nützliche Links
Es gibt auch eine Reihe bekannter Makros und Schlüsselwörter, die ich erwähnen möchte: das Schlüsselwort @available
, die Makros NS_DESIGNATED_INITIALIZER
, NS_UNAVAILABLE
, NS_REQUIRES_SUPER
, NS_NOESCAPE
, NS_ENUM
, NS_OPTIONS
(oder Ihre Makros für dieselben @ Attribute). Wir empfehlen Ihnen auch, sich den Rest der libextobjc- Bibliothek anzusehen .
→ Der Code für den Artikel ist in gist veröffentlicht .
Fazit
Im ersten Teil des Artikels haben wir versucht, über die Hauptfunktionen und einfachen Sprachverbesserungen zu sprechen, die das Schreiben und die Unterstützung von Objective-C-Code erheblich erleichtern. Im nächsten Teil werden wir zeigen, wie Sie Ihre Produktivität mithilfe von Enums wie in Swift (es handelt sich auch um Fallklassen; es handelt sich auch um algebraische Datentypen , ADTs) und die Möglichkeit der Implementierung von Methoden auf Protokollebene weiter steigern können.