2018年のObjective-Cの書き方。 パート1

ほとんどのiOSプロジェクトは、部分的または完全にSwiftに切り替わります。 Swiftは優れた言語であり、iOS開発の未来はそこにあります。 しかし、この言語はツールキットと密接にリンクされており、Swiftツールキットには欠陥があります。


Swiftコンパイラには、クラッシュまたは不正なコードの生成を引き起こすバグがまだあります。 Swiftには安定したABIがありません。 そして、非常に重要なことは、Swiftプロジェクトがあまりにも長く続いていることです。


この点で、Objective-Cでの開発を継続するには、既存のプロジェクトの方が収益性が高い場合があります。 そして、Objective-Cは以前のようにはなりませんでした!


この一連の記事では、Objective-Cの便利な機能と改善点を紹介します。Objective-Cを使用すると、コードの記述がより快適になります。 Objective-Cで書く人は誰でも、自分にとって興味深いものを見つけるでしょう。



letvar


Objective-Cは、変数タイプを明示的に指定する必要がなくなりました__auto_typeでは、 __auto_type言語拡張が登場し、Objective-C ++でXcode 8型推論が使用可能になりました(C ++ 0Xの出現でautoキーワードを使用)。


まず、 letマクロとvarマクロを追加しlet


 #define let __auto_type const #define var __auto_type 

 //  NSArray<NSString *> *const items = [string componentsSeparatedByString:@","]; void(^const completion)(NSData * _Nullable, NSURLResponse * _Nullable, NSError * _Nullable) = ^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) { // ... }; //  let items = [string componentsSeparatedByString:@","]; let completion = ^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) { // ... }; 

Objective-Cクラスへのポインタの後にconstを記述する場合、これは受け入れがたい贅沢でしたが、 const暗黙的な指定( letを介した)は当たり前になっています。 ブロックを変数に保存する場合、違いは特に顕著です。


私たち自身のために、 letvarを使用してすべての変数を宣言するルールを開発しました。 変数がnil初期化される場合でも:


 - (nullable JMSomeResult *)doSomething { var result = (JMSomeResult *)nil; if (...) { result = ...; } return result; } 

唯一の例外は、各コードブランチで変数に値が割り当てられていることを確認する必要がある場合です。


 NSString *value; if (...) { if (...) { value = ...; } else { value = ...; } } else { value = ...; } 

この方法でのみ、ブランチの1つに値を割り当てることを忘れた場合、コンパイラの警告が表示されます。


最後に、タイプid変数にletおよびvarを使用するには、 auto-var-id警告を無効にする必要があります(プロジェクト設定の「その他の警告フラグ」に-Wno-auto-var-idを追加します)。


自動ブロックタイプの戻り値


コンパイラがブロックの戻り値の型を推測できることを知っている人はほとんどいません。


 let block = ^{ return @"abc"; }; // `block`   `NSString *(^const)(void)` 

とても便利です。 特にReactiveObjCを使用してリアクティブコードを記述する場合。 ただし、戻り値の型を明示的に指定する必要があるいくつかの制限があります。


  1. ブロック内に異なるタイプの値をreturnが複数ある場合。


     let block1 = ^NSUInteger(NSUInteger value){ if (value > 0) { return value; } else { // `NSNotFound`   `NSInteger` return NSNotFound; } }; let block2 = ^JMSomeBaseClass *(BOOL flag) { if (flag) { return [[JMSomeBaseClass alloc] init]; } else { // `JMSomeDerivedClass`   `JMSomeBaseClass` return [[JMSomeDerivedClass alloc] init]; } }; 

  2. ブロック内にnilを返すreturnがある場合。


     let block1 = ^NSString * _Nullable(){ return nil; }; let block2 = ^NSString * _Nullable(BOOL flag) { if (flag) { return @"abc"; } else { return nil; } }; 

  3. ブロックがBOOLを返すBOOL


     let predicate = ^BOOL(NSInteger lhs, NSInteger rhs){ return lhs > rhs; }; 


Cで(したがってObjective-Cで)比較演算子を使用する式はint型です。 したがって、戻り型BOOLを常に明示的に指定するルールにすることをおBOOLます。


ジェネリックおよびfor...in


Objective-CのXcode 7では、ジェネリック(より正確には、軽量のジェネリック)が登場しました。 すでに使用されていることを願っています。 ただし、そうでない場合は、 WWDCセッションを見るか、 こちらまたはこちらで読むことできます


私たち自身のために、 idNSArray<id> * )であっても、常に汎用パラメーターを指定するルールを開発しました。 これにより、汎用パラメーターがまだ指定されていないレガシーコードを簡単に区別できます。


letおよびvarマクロを使用すると、 for...inループで使用できることが期待さfor...inます。


 let items = (NSArray<NSString *> *)@[@"a", @"b", @"c"]; for (let item in items) { NSLog(@"%@", item); } 

しかし、そのようなコードはコンパイルされません。 ほとんどの場合、 __auto_type for...inでサポートされfor...inいません__auto_type for...inNSFastEnumerationプロトコルを実装するコレクションfor...inのみ機能するためです。 また、Objective-Cのプロトコルでは、ジェネリックのサポートはありません。


この欠点を修正するには、 foreachマクロを作成してみてください。 最初に思い浮かぶこと:FoundationのすべてのコレクションにはobjectEnumeratorプロパティがあり、マクロは次のようになります。


 #define foreach(object_, collection_) \ for (typeof([(collection_).objectEnumerator nextObject]) object_ in (collection_)) 

ただし、 NSDictionaryおよびNSMapTableプロトコルメソッドは、値ではなくキーをNSFastEnumerationします( keyEnumeratorではなくobjectEnumeratorを使用する必要があります)。


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_)) 

今、私たちのコードはずっと良く見えます:


 //  for (MyItemClass *item in items) { NSLog(@"%@", item); } //  foreach (item, items) { NSLog(@"%@", item); } 

Xcodeのスニペット
 foreach (<#object#>, <#collection#>) { <#statements#> } 

ジェネリックとcopy / mutableCopy


Objective-Cで入力が存在しないもう1つの場所は、 -mutableCopyおよび-mutableCopy-copyWithZone:および-mutableCopyWithZone:メソッドと同様ですが、直接呼び出しません)。


明示的なキャストの必要性を回避するために、戻り値の型でメソッドを再宣言できます。 たとえば、 NSArray宣言は次のようになります。


 @interface NSArray<__covariant ObjectType> (TypedCopying) - (NSArray<ObjectType> *)copy; - (NSMutableArray<ObjectType> *)mutableCopy; @end 

 let items = [NSMutableArray<NSString *> array]; // ... //  let itemsCopy = (NSArray<NSString *> *)[items copy]; //  let itemsCopy = [items copy]; 

warn_unused_result


-copyおよび-mutableCopyを再宣言したため、これらのメソッドを呼び出した結果が使用されることを保証するのは良いことです。 これを行うために、Clangには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 

次のコードの場合、コンパイラーは警告を生成します。


 let items = @[@"a", @"b", @"c"]; [items mutableCopy]; // Warning: Ignoring return value of function declared with 'warn_unused_result' attribute. 

overloadable


Clangを使用してC言語(したがってObjective-C)で関数を再定義できることを知っている人はほとんどいません。 overloadable属性を使用すると、同じ名前の関数を作成できますが、異なるタイプの引数または異なる番号を使用できます。


オーバーライド可能な関数は、戻り値のタイプのみを異にすることはできません。


 #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); 

ボックス化された式


2012年のWWDC 413セッションで、 AppleはNSNumberNSArrayNSDictionaryリテラルとボックス化された式を導入しました。 リテラルおよびボックス化された式の詳細は、 Clangのドキュメントに記載されています


 //  @YES // [NSNumber numberWithBool:YES] @NO // [NSNumber numberWithBool:NO] @123 // [NSNumber numberWithInt:123] @3.14 // [NSNumber numberWithDouble:3.14] @[obj1, obj2] // [NSArray arrayWithObjects:obj1, obj2, nil] @{key1: obj1, key2: obj2} // [NSDictionary dictionaryWithObjectsAndKeys:obj1, key1, obj2, key2, nil] // Boxed expressions @(boolVariable) // [NSNumber numberWithBool:boolVariable] @(intVariable) // [NSNumber numberWithInt:intVariable)] 

リテラルとボックス化された式を使用すると、数値またはブール値を表すオブジェクトを簡単に取得できます。 しかし、構造をラップするオブジェクトを取得するには、いくつかのコードを記述する必要があります。


 //  `NSDirectionalEdgeInsets`  `NSValue` let insets = (NSDirectionalEdgeInsets){ ... }; let value = [[NSValue alloc] initWithBytes:&insets objCType:@encode(typeof(insets))]; // ... //  `NSDirectionalEdgeInsets`  `NSValue` var insets = (NSDirectionalEdgeInsets){}; [value getValue:&insets]; 

ヘルパーメソッドとプロパティは一部のクラス( +[NSValue valueWithCGPoint:]メソッドやCGPointValueプロパティなど)に対して定義されていますが、これはまだボックス化された式ほど便利ではありません!


そして2015年、 Alex Denisov Clangのパッチ作成し、ボックス化された式を使用してNSValue構造をラップできるようにしNSValue


構造がボックス化された式をサポートするには、構造のobjc_boxable属性を追加するだけです。


 #define JM_BOXABLE __attribute__((objc_boxable)) 

 typedef struct JM_BOXABLE JMDimension { JMDimensionUnit unit; CGFloat value; } JMDimension; 

そして、構造に@(...)構文を使用できます。


 let dimension = (JMDimension){ ... }; let boxedValue = @(dimension); //   `NSValue *` 

メソッド-[NSValue getValue:]またはカテゴリメソッドを使用し-[NSValue getValue:]構造を元に戻す必要があります。


CoreGraphicsは独自のマクロCG_BOXABLE定義し、ボックス式はCGPointCGSizeCGVectorおよびCGRectに対して既にサポートされています。


他の一般的に使用される構造については、ボックス式のサポートを独自に追加できます。


 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; 

複合リテラル


別の便利な言語構成体は、 複合リテラルです。 複合リテラルは、GCCで言語拡張機能として登場し、後にC11標準に追加されました。


以前にUIEdgeInsetsMake呼び出しにUIEdgeInsetsMake場合、どのインデントが得られるかを推測することしかできませんでした( UIEdgeInsetsMake関数の宣言を監視する必要がありUIEdgeInsetsMake )。


 //  UIEdgeInsetsMake(1, 2, 3, 4) //  (UIEdgeInsets){ .top = 1, .left = 2, .bottom = 3, .right = 4 } 

一部のフィールドがゼロの場合、このような構成を使用するとさらに便利です。


 (CGPoint){ .y = 10 } //  (CGPoint){ .x = 0, .y = 10 } (CGRect){ .size = { .width = 10, .height = 20 } } //  (CGRect){ .origin = { .x = 0, .y = 0 }, .size = { .width = 10, .height = 20 } } (UIEdgeInsets){ .top = 10, .bottom = 20 } //  (UIEdgeInsets){ .top = 20, .left = 0, .bottom = 10, .right = 0 } 

もちろん、複合リテラルでは、定数だけでなく任意の式も使用できます。


 textFrame = (CGRect){ .origin = { .y = CGRectGetMaxY(buttonFrame) + textMarginTop }, .size = textSize }; 

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#> } 

ヌル可能性


Xcode 6.3.2では、Objective-Cにnullabilityアノテーションが登場しました。 Apple開発者は、Objective-C APIをSwiftにインポートするためにそれらを追加しました。 しかし、言語に何かが追加された場合、サービスにそれを置くようにしてください。 また、Objective-Cプロジェクトでnullabilityを使用する方法とその制限について説明します。


知識を更新するために、 WWDCセッションを見ることができます。


最初にしたことは、すべての.mファイルでNS_ASSUME_NONNULL_BEGIN / NS_ASSUME_NONNULL_END書き込みを開始することでした。 これを手動で行わないために、ファイルテンプレートにXcodeで直接パッチを適用します。


また、すべてのプライベートプロパティとメソッドに対してnullabilityを設定し始めました。


NS_ASSUME_NONNULL_BEGIN / NS_ASSUME_NONNULL_ENDマクロを既存の.mファイルに追加すると、ファイル全体に欠落しているnullablenull_resettable 、および_Nullableがすぐに追加されます。


すべての有用なnullabilityコンパイラ警告はデフォルトで有効になっています。 しかし、私が含めたい極端な警告が1つあります。 -Wnullable-to-nonnull-conversion (プロジェクト設定の「その他の警告フラグ」で設定)。 コンパイラーは、null許容型の変数または式が暗黙的に非null型にキャストされたときにこの警告を生成します。


 + (NSString *)foo:(nullable NSString *)string { return string; // Implicit conversion from nullable pointer 'NSString * _Nullable' to non-nullable pointer type 'NSString * _Nonnull' } 

残念ながら、 __auto_type (およびletおよびvar )については、この警告は機能しません。 __auto_typeを介して推定される型では、 __auto_typeアノテーションは破棄されます。 そして、 rdar:// 27062504の Apple開発者のコ​​メントから判断すると、この動作は変わりません。 _Nullableまたは_Nonnull__auto_typeしても何にも影響しないことが実験的に確認されています。


 - (NSString *)test:(nullable NSString *)string { let tmp = string; return tmp; //   } 

nullable-to-nonnull-conversionを抑制するために、「強制nullable-to-nonnull-conversion解除」するマクロを作成しました。 RBBNotNilマクロから取ったアイデア。 しかし、 __auto_typeの動作により、補助クラスを__auto_typeができました。


 #define JMNonnull(obj_) \ ({ \ NSCAssert(obj_, @"Expected `%@` not to be nil.", @#obj_); \ (typeof({ __auto_type result_ = (obj_); result_; }))(obj_); \ }) 

JMNonnullマクロの使用例:


 @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 { // ... } @end 

執筆時点では、 nullable-to-nonnull-conversion警告はnullable-to-nonnull-conversionは機能しません。コンパイラは、不等式nilチェックした後のnullable変数がnonnullとして解釈できることをまだ理解していません。


 - (NSString *)foo:(nullable NSString *)string { if (string != nil) { return string; // Implicit conversion from nullable pointer 'NSString * _Nullable' to non-nullable pointer type 'NSString * _Nonnull' } else { return @""; } } 

Objective-C ++コードでは、 if let構文を使用してこの制限を回避できます。これは、Objective-C ++がifで変数宣言を許可するためif


 - (NSString *)foo:(nullable NSString *)stringOrNil { if (let string = stringOrNil) { return string; } else { return @""; } } 

便利なリンク


私が言及したいより多くのよく知られているマクロとキーワードもあります: @availableキーワード、マクロNS_DESIGNATED_INITIALIZERNS_UNAVAILABLENS_REQUIRES_SUPERNS_NOESCAPENS_ENUMNS_OPTIONS (または同じ属性の独自のマクロ)およびライブラリ@keypathマクロ。 また、 libextobjcライブラリの残りを確認することをお勧めします。



→記事のコードはgistに投稿されています。


おわりに


記事の最初の部分では、Objective-Cコードの作成とサポートを大幅に促進する、言語の主な機能と簡単な改善についてお話しました。 次の部分では、Swiftのように列挙型を使用して生産性をさらに向上させる方法(ケースクラス、 代数データ型 、ADT)およびプロトコルレベルでメソッドを実装する可能性を示します。



Source: https://habr.com/ru/post/J431236/


All Articles