はじめに
ブロックは、コードを簡素化するためにiOS 4.0およびMac OSX 10.6で初めて導入されました。 彼らはそれを減らし、デリゲートの依存関係を減らし、きれいで読みやすいコードを書くことができます。 しかし、明らかな利点にもかかわらず、多くの開発者は自分の行動の原理を完全に理解していないため、ブロックを使用しません。
ブロックの「何、どこ、いつ、なぜ」を見てみましょう。
これらの「ブロック」とは何ですか、なぜ重要なのですか?
内部から見ると、ブロックは将来のある時点で呼び出すことができるコードです。 ブロックはファーストクラスの関数なので、ブロックは通常のObjective-Cオブジェクトと言えます。 また、オブジェクトはパラメータとして渡され、関数とメソッドから返され、変数に割り当てられます。 Python、Ruby、Lispなどの言語では、ブロックは宣言後、状態をカプセル化するため、しばしばクロージャーと呼ばれます。 ブロックは、参照されるローカル変数の定数コピーを作成します。
後で何かを返すコードを呼び出す必要がある場合は、おそらくブロックではなくデリゲートまたはNSNotificationCenterを使用するでしょう。 そして、それはうまく動作しますが、コードの断片はどこにでも散らばります-タスクはある点から始まり、結果は別の点に処理されます。
ブロックは、タスクを1つの場所に保持するため、より優れています。
ブロックが必要なのは誰ですか?
あなたのために! しかし、真剣に、ブロックは皆のためであり、誰もがブロックを使用しています。 ブロックは未来ですので、すぐに学ぶことができます。 組み込みフレームワークの多くのメソッドは、既存の機能に基づいてブロックによって書き換えられるか、補足されます。
ブロックの使用方法は?
これは、ブロック構文をよく説明しているiOS Developer Libraryの写真です。
ブロックの説明の形式は次のとおりです。
return_type (^block_name)(param_type, param_type, ...)
すでに他のC言語でプログラミングを行っている場合、この^記号を除いて、この構造は非常によく知られています。 ^そしてブロックを意味します。 ^が「私はブロック」を意味することを理解している場合、おめでとうございます。ブロックに関する最も難しいことを理解しただけです。 ;]
パラメータの名前は現在必須ではありませんが、必要に応じて含めることができます。
以下はブロック宣言のサンプルです。
int (^add)(int,int)
次に、ブロックの説明。
^return_type(param_type param_name, param_type param_name, ...) { ... return return_type; }
実際、これがブロックの作成方法です。 ブロックの説明は宣言とは異なることに注意してください。 ^文字で始まり、名前を付けることができ、ブロック宣言のパラメーターのタイプと順序に対応する必要があるパラメーターが付随します。 コード自体が続きます。
ブロックの定義では、戻り値のタイプはオプションであり、コードから識別できます。 複数の戻り値がある場合、それらは同じタイプでなければなりません。
サンプルブロックの説明:
^(int number1, int number2){ return number1+number2 }
ブロックの説明と宣言を組み合わせると、次のコードが得られます。
int (^add)(int,int) = ^(int number1, int number2){ return number1+number2; }
この方法でブロックを使用することもできます:
int resultFromBlock = add(2,2);
次に、ブロックを使用しない同じコードとは対照的に、ブロックを使用するいくつかの例を見てみましょう。
例:NSArray
ブロックが配列でいくつかの操作を実行する原理をどのように変えるかを見てみましょう。 まず、ループの通常のコードを次に示します。
BOOL stop; for (int i = 0 ; i < [theArray count] ; i++) { NSLog(@"The object at index %d is %@",i,[theArray objectAtIndex:i]); if (stop) break; }
停止変数の値を設定する必要はありません。 ただし、ブロックを使用してこのメソッドの実装を確認すると、すべてが明確になります。 ブロックメソッドには「stop」変数が含まれており、いつでもループを停止できるため、コードの類似性のためにこの機能を単純に複製します。
ここで、高速列挙を使用して同じコードを見てみましょう。
BOOL stop; int idx = 0; for (id obj in theArray) { NSLog(@"The object at index %d is %@",idx,obj); if (stop) break; idx++; }
そして今、ブロックで:
[theArray enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop){ NSLog(@"The object at index %d is %@",idx,obj); }];
上記のコードでは、「stop」変数が何であるか疑問に思うかもしれません。 これは、プロセスを停止するためにブロック内でYESを割り当てることができる単なる変数です。 このパラメーターは、 enumerateObjectsUsingBlockメソッドで使用されるブロックの一部として定義されます。
上記のコードは簡単なものであり、その中のブロックの利点を確認することは非常に困難です。 しかし、私が注意したい2つのことがあります:
- シンプルさ 。 ブロックを使用すると、コードを記述することなく、オブジェクト、配列内のオブジェクトのインデックス、および停止変数にアクセスできます。 これによりコードが減少し、エラーの数が減少します。
- スピード 。 ブロック方式には、高速列挙方式よりもわずかに速度の利点があります。 私たちの場合、この利点はおそらく言及するには小さすぎますが、より複雑な場合には重要になります。
例:UIViewアニメーション
単一のUIViewで動作するシンプルなアニメーションを見てみましょう。 alphaパラメーターを0に変更し、ビューを上下に50ポイント移動してから、スーパービューからUIViewを削除します。 シンプルでしょ? ブロックなしのコード:
- (void)removeAnimationView:(id)sender { [animatingView removeFromSuperview]; } - (void)viewDidAppear:(BOOL)animated { [super viewDidAppear:animated]; [UIView beginAnimations:@"Example" context:nil]; [UIView setAnimationDuration:5.0]; [UIView setAnimationDidStopSelector:@selector(removeAnimationView)]; [animatingView setAlpha:0]; [animatingView setCenter:CGPointMake(animatingView.center.x+50.0, animatingView.center.y+50.0)]; [UIView commitAnimations]; }
ブロック方法:
- (void)viewDidAppear:(BOOL)animated { [super viewDidAppear:animated]; [UIView animateWithDuration:5.0 animations:^{ [animatingView setAlpha:0]; [animatingView setCenter:CGPointMake(animatingView.center.x+50.0, animatingView.center.y+50.0)]; } completion:^(BOOL finished) { [animatingView removeFromSuperview]; }]; }
これらの2つの方法をよく見ると、ブロック方法の3つの利点に気付くでしょう。
- コードを簡素化します 。 ブロックを使用すると、コールバックを完了するために完全に別のメソッドを定義したり、 beginAnimations / commitAnimationsを呼び出したりする必要がありません。
- すべてのコードは1つの場所にあります。 ブロックを使用すると、ある場所でアニメーションを開始し、別の場所でコールバックメソッドを開始する必要がありません。 アニメーション関連のコードはすべて1か所にあるため、簡単に読み書きできます。
- それでAppleは言う 。 Appleは、ブロックを使用しない既存の機能を書き直したため、可能であれば、ブロックベースの方法に切り替えることを公式に推奨しています。
ブロックを使用する場合
最善のアドバイスは、必要な場所でブロックを使用することだと思います。 おそらく、後方互換性を維持するために古いメソッドを使い続けたいと思うかもしれませんし、単にそれらに慣れているからでしょう。 しかし、同様の決定点に来るたびに、ブロックがコードを書くのにあなたの生活を楽にすることができるかどうか、そしてブロックアプローチでメソッドを使用する価値があるかどうかを考えてください。
もちろん、時間の経過とともに、ほとんどのフレームワークがブロックを使用して作成および書き換えられるため、ブロックの必要性がますます高まっていることに気付くでしょう。 したがって、将来的に完全装備されたブロックに対応するために、今すぐブロックを使用してください。
iOS Dinerに戻る:モデルクラスのカスタマイズ
最初の部分で立ち寄ったのと同じ場所から続けます。 最初の部分を読んでいない場合、またはメモリ内のすべてを更新する場合は、 ここからプロジェクトをダウンロードできます。
Xcodeでプロジェクトを開き、 Project Navigatorに切り替えます。 iOSDinerフォルダーを右クリックし、[ 新しいグループ ]を選択します。 それをモデルと呼びましょう。
作成されたモデルフォルダを右クリックして、 新規ファイル→Objective-Cクラス (またはCocoa Touchクラス)を選択します。 「IODItem」と呼び、 NSObjectを親クラスとして選択します。
上記の手順を繰り返して、 IODOrderクラスを作成します。
これで必要なすべてのクラスができました。 コードの時が来ました。
IODItemを構成する
IODItem.hを開きます 。 まず、 NSCopyingプロトコルをclassに追加する必要があります。 プロトコル
クラスが実装するメソッドに関するある種の「慣習」です。 プロトコルがクラスで宣言されている場合、同じクラスで、プロトコルに記述されている必須またはオプションのメソッドを定義する必要があります。 プロトコル宣言は次のとおりです。
@interface IODItem : NSObject <NSCopying>
次に、オブジェクトのプロパティを追加します。 各オブジェクトには、名前、価格、画像ファイル名があります。 IODItem.hは次のようになります。
#import <Foundation/Foundation.h> @interface IODItem : NSObject <NSCopying> @property (nonatomic,strong) NSString* name; @property (nonatomic,strong) float price; @property (nonatomic,strong) NSString* pictureFile; @end
次に、 IODItem.mに切り替えます。 ここに警告が表示されます。
この警告は、以前に追加したNSCopyingプロトコルに適用されます。 プロトコルはバインディングメソッドを記述できることを覚えていますか? したがって、 NSCopyingでは、
— (id)copyWithZone:(NSZone *)zone
を定義する必要があります。 このメソッドが追加されるまで、クラスは不完全と見なされます。 以下をIODItem.mの @endの前に追加します 。
- (id)copyWithZone:(NSZone *)zone { IODItem *newItem = [[IODItem alloc] init]; newItem.name = _name; newItem.price = _price; newItem.pictureFile = _pictureFile; return newItem; }
それだけです、警告は消えました。 このコードは、新しいIODItemオブジェクトを作成し、既存のオブジェクトのプロパティをコピーして、この新しいインスタンスを返します。
さらに、初期化メソッドが必要です。 オブジェクトの初期化時に、デフォルトのプロパティ値を簡単かつ迅速に設定できます。 IODItem.mに書き込みます 。
- (id)initWithName:(NSString *)name andPrice:(NSNumber *)price andPictureFile:(NSString *)pictureFile { if(self = [self init]) { _name = name; _price = price; _pictureFile = pictureFile; } return self; }
IODItem.hに戻り、ファイルを終了する前にプロトタイプメソッドを追加します(@end):
- (id)initWithName:(NSString*)inName andPrice:(float)inPrice andPictureFile:(NSString*)inPictureFile;
IODOrderを構成する
次に、別のクラスであるIODOrderに取り組みましょう 。 このクラスは、注文とそれに関連付けられた操作(オブジェクトの追加、削除、注文の総費用の計算、画面上の注文の内容の表示)になります。
IODOrder.hを開き、 インターフェイスの前にヘッダーファイルのインポートを追加します 。
#import "IODItem.h"
そして、 インターフェイスの下にプロパティを書きます:
@property (nonatomic,strong) NSMutableDictionary* orderItems;
これは、ユーザーが選択したオブジェクトが保存される辞書です。
ViewControllerのセットアップ
ViewController.mに切り替えて、プロパティを追加します。
#import "ViewController.h" #import "IODOrder.h" @interface ViewController () @property (assign, nonatomic) NSInteger currentItemIndex; @property (strong, nonatomic) NSMutableArray *inventory; @property (strong, nonatomic) IODOrder *order;
currentItemIndexプロパティは、インベントリで表示されているオブジェクトのインデックスを記憶します。 インベントリの意味は簡単に説明できます。これは、Webサービスから取得するIODItem要素の配列です。 order-クラスIODOrderのオブジェクト。ユーザーが選択したオブジェクトを格納します。
次に、 viewDidLoadメソッドで、 currentItemIndexとorderをリセットする必要があります 。
- (void)viewDidLoad { [super viewDidLoad]; self.currentItemIndex = 0; self.order = [[IODOrder alloc] init]; }
これで、 ViewController.mの開始は次のようになります。
#import "ViewController.h" #import "IODOrder.h" @interface ViewController () @property (assign, nonatomic) NSInteger currentItemIndex; @property (strong, nonatomic) NSMutableArray *inventory; @property (strong, nonatomic) IODOrder *order; @end @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; self.currentItemIndex = 0; self.order = [[IODOrder alloc] init]; }
プロジェクトをビルドします。 警告はありません。
インベントリをロードします
次に追加するretrieveInventoryItemsクラスのメソッドは、Webサービスからダウンロードされたインベントリをロードして処理します。
ご注意 クラスメソッドは「+」で始まり、クラスインスタンスメソッドは「-」で始まります。
IODItem.mで、#importディレクティブの直後に、次を追加します。
#define INVENTORY_ADDRESS @"http://adamburkepile.com/inventory/"
ご注意 Webサービスを自分でホストした場合は、アドレスを変更します。
次に、 retrieveInventoryItemsメソッドを追加します 。
+ (NSArray *)retrieveInventoryItems { // 1 — NSMutableArray *inventory = [[NSMutableArray alloc] init]; NSError *error = nil; // 2 — NSArray *jsonInventory = [NSJSONSerialization JSONObjectWithData:[NSData dataWithContentsOfURL:[NSURL URLWithString:INVENTORY_ADDRESS]] options:kNilOptions error:&error]; // 3 — [jsonInventory enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { NSDictionary *item = obj; [inventory addObject:[[IODItem alloc] initWithName:item[@"Name"] andPrice:item[@"Price"] andPictureFile:item[@"Image"]]]; }]; // 4 — return [inventory copy]; }
そして、これが最初のブロックです。 コードを詳しく見てみましょう。
- 最初に、返されるオブジェクトを格納する配列と、発生する可能性のあるエラーへのポインタを宣言しました。
- NSDataオブジェクトを使用してWebサーバーからデータをロードし、このNSDataオブジェクトをJSONサービスに渡して、ソースデータをObjective-Cタイプ(NSArray、NSDictionary、NSString、NSNumberなど)にデコードします。
- 次に、 enumerateObjectsUsingBlockが必要です。これは、オブジェクトをNSDictionaryからIODItemに「変換」するための前述のメソッドです。 jsonInventory配列でこのメソッドを呼び出し、配列の要素をNSDictionaryとしてIODItemオブジェクトの初期化メソッドに渡すブロックを使用して繰り返します。 次に、この新しいオブジェクトを返された配列に追加します。
- 最後に、在庫の配列が返されます。 可変バージョンを返したくないので、配列自体ではなく、配列のコピーを返していることに注意してください。 copyメソッドは不変のコピーを作成します。
次に、 IODItem.hを開いて、プロトタイプメソッドを追加します。
+ (NSArray*)retrieveInventoryItems;
ディスパッチキューとグランドセントラルディスパッチ
間違いなく知っておく必要があるもう1つのことは、 ディスパッチキューです。 ViewController.mに切り替えて、プロパティを追加します。
@property (strong, nonatomic) dispatch_queue_t queue;
ViewDidLoadメソッドの最後に、 次の行を記述します。
self.queue = dispatch_queue_create("com.adamburkepile.queue",nil);
dispatch_queue_createメソッドの最初のパラメーターはキューの名前です。 好きな名前を付けることができますが、この名前は一意でなければなりません。 したがって、Appleはこの名前に逆DNSスタイルを推奨しています。
次に、このキューを使用しましょう。 viewDidAppearに書き込みます。
self.ibChalkboardLabel.text = @"Loading inventory..."; self.inventory = [[IODItem retrieveInventoryItems] mutableCopy]; self.ibChalkboardLabel.text = @"Inventory Loaded\n\nHow can I help you?";
プロジェクトを実行します。 何かおかしいですよね? IODItemで定義されているretrieveInventoryItemsメソッドを使用して、Webサービスを呼び出し、インベントリオブジェクトを返し、それらを配列に入れます。
PHPスクリプトの最後の部分からの5秒の遅延を覚えていますか? しかし、プログラムを実行すると、「インベントリを読み込んでいます...」は表示されず、5秒待ってから「インベントリが読み込まれました」と表示されます。
問題はこれです。Webサービスへの呼び出しは、メインキューをブロックして「フリーズ」し、ラベルテキストを変更できないようにします。 メインラインに移動せずに、このような操作に使用できる別のラインがある場合は...停止します! すでにあります。 ここで、 Grand Central Dispatchとブロックが非常に簡単に役立ちます。 Grand Central Dispatchを使用すると、メインのキューをブロックすることなく、データ処理を別のキューに配置できます。 viewDidAppearの最後の2行を次の行に置き換えます。
dispatch_async(self.queue, ^{ self.inventory = [[IODItem retrieveInventoryItems] mutableCopy]; dispatch_async(dispatch_get_main_queue(), ^{ self.ibChalkboardLabel.text = @"Inventory Loaded\n\nHow can I help you?"; }); });
ここでは、何も返さず、パラメーターも持たない2つの異なるブロックを使用していることに注意してください。
プロジェクトを再度実行します。 これで正常に機能するようになりました。
テキストをラベルに割り当てるためにdispatch_asyncを呼び出す理由を不思議に思わないでしょうか? ラベルにテキストを配置すると、UI要素が更新され、UIに関連するすべてのものがメインキューで処理されます。 したがって、もう一度dispatch_asyncを呼び出しますが、メインキューのみを取得し、その中のブロックを実行します。
この方法は、操作に時間がかかり、UI要素を更新する必要がある場合に非常に一般的です。
Grand Central Dispatchはかなり複雑なシステムであり、この単純なレッスンではその役割を理解することは不可能です。 興味のある方は、 iOSのマルチスレッドとGrand Central Dispatch for Beginnersをご覧ください 。
追加の方法
Webサービスを使用して、インベントリをダウンロードおよび保存します。 ここで、保存されたインベントリをユーザーに表示する3つのヘルパーメソッドが必要です。
最初のメソッドfindKeyForOrderItem:をIODOrder.mに追加します 。 辞書からオブジェクトにアクセスするのに役立ちます。
- (IODItem *)findKeyForOrderItem:(IODItem *)searchItem { //1 - NSIndexSet *indexes = [[self.orderItems allKeys] indexesOfObjectsPassingTest:^BOOL(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { IODItem *key = obj; return ([searchItem.name isEqualToString:key.name] && searchItem.price == key.price); }]; //2 - if([indexes count] >=1) { IODItem *key = [self.orderItems allKeys][[indexes firstIndex]]; return key; } //3 - return nil; }
このメソッドが何をするのか見てみましょう。 しかし、これを行う前に、なぜそれが必要なのか説明しなければなりません。 IODOrderオブジェクトには、辞書orderItems (キーと値のペア)が含まれています。 キーはIODItemで、値はNSNumberです 。これは、そのようなIODItemがいくつ注文されたかを示します。
理論的にはすべてが問題ありませんが、 NSDictionaryクラスの特異な癖は、オブジェクトをキーとして割り当てたい場合、このオブジェクトを割り当てずに、そのオブジェクトのコピーを作成してキーとして使用することです。 これは、キーとして使用するオブジェクトがNSCopyingプロトコルに準拠する必要があることを意味します(これが、 IODItemでNSCopyingプロトコルを宣言した理由です)。
ディクショナリのorderItemsのキーとインベントリ配列のIODItemは同じオブジェクトではないため(同じプロパティがあります)、単純なキー検索を行うことはできません。 代わりに、各オブジェクトの名前と価格を比較して、一致するかどうかを判断する必要があります。 これが上記の方法の動作です。キーのプロパティを比較して正しいものを見つけます。
そして、このコードの機能は次のとおりです。
- ブロックの別の例は、 indexesOfObjectsPassingTest:メソッドを使用して名前と価格の一致を見つけるためにorderItemsディクショナリのキーを反復処理します。 ^の後にBOOLに注意してください。 これは戻り型です。 このメソッドは、ブロックを使用して配列を処理して2つのオブジェクトを比較し、ブロックに記述されているテストに合格したすべてのオブジェクトのインデックスを返します。
- ここでは、返される最初のインデックスを取得します
- 3.一致するキーが見つからなかった場合、nilを返します。
プロトタイプメソッドをIODOrder.hに追加することを忘れないでください。
- (IODItem*)findKeyForOrderItem:(IODItem*)searchItem;
ViewController.mに移動して、次のメソッドを追加します。
- (void)updateCurrentInventoryItem { if (self.currentItemIndex >=0 && self.currentItemIndex < [self.inventory count]) { IODItem* currentItem = self.inventory[self.currentItemIndex]; self.ibCurrentItemLabel.text = currentItem.name; self.ibCurrentItemLabel.adjustsFontSizeToFitWidth = YES; self.ibCurrentItemImageView.image = [UIImage imageNamed:[currentItem pictureFile]]; } }
currentItemIndexとインベントリ配列を使用して、このメソッドは各インベントリオブジェクトの名前と画像を表示します。
もう1つのメソッドを作成します。
- (void)updateInventoryButtons { if (!self.inventory || ![self.inventory count]) { self.ibAddItemButton.enabled = NO; self.ibRemoveItemButton.enabled = NO; self.ibNextItemButton.enabled = NO; self.ibPreviousItemButton.enabled = NO; self.ibTotalOrderButton.enabled = NO; } else { if (self.currentItemIndex <= 0) { self.ibPreviousItemButton.enabled = NO; } else { self.ibPreviousItemButton.enabled = YES; } if (self.currentItemIndex >= [self.inventory count]-1) { self.ibNextItemButton.enabled = NO; } else { self.ibNextItemButton.enabled = YES; } IODItem* currentItem = self.inventory[self.currentItemIndex]; if (currentItem) { self.ibAddItemButton.enabled = YES; } else { self.ibAddItemButton.enabled = NO; } if (![self.order findKeyForOrderItem:currentItem]) { self.ibRemoveItemButton.enabled = NO; } else { self.ibRemoveItemButton.enabled = YES; } if (![self.order.orderItems count]) { self.ibTotalOrderButton.enabled = NO; } else { self.ibTotalOrderButton.enabled = YES; } } }
このメソッドは、3つのヘルパーメソッドの中で最長ですが、かなり単純です。 プログラムのステータスをチェックし、ボタンをアクティブにするかどうかを決定します。
たとえば、 currentItemIndexがゼロの場合、 ibPreviousItemButtonはアクティブになりません。最初の要素の前に要素がないためです。 orderItemsに要素がない場合、 ibTotalOrderButtonボタンをアクティブにしないでください 。これは、金額を計算できる順序がないためです。
したがって、これら3つの方法を使用して、魔法を行うことができます。 ViewController.mの viewDidAppearに戻り 、最初に追加してみましょう。
[self updateInventoryButtons];
次に、ブロックをこれに置き換えます。
dispatch_async(queue, ^{ self.inventory = [[IODItem retrieveInventoryItems] mutableCopy]; dispatch_async(dispatch_get_main_queue(), ^{ [self updateInventoryButtons]; [self updateCurrentInventoryItem]; self.ibChalkboardLabel.text = @"Inventory Loaded\n\nHow can I help you?"; }); });
ビルドして実行します。
ああ、ここにハンバーガーが来ます。 しかし、残りの食べ物を見たいので、ボタンを機能させましょう。
ViewController.mにすでにあるibaLoadNextItem:およびibaLoadPreviousItem:メソッド。 いくつかのコードを追加します。
- (IBAction)ibaLoadPreviousItem:(UIButton *)sender { self.currentItemIndex--; [self updateCurrentInventoryItem]; [self updateInventoryButtons]; } - (IBAction)ibaLoadNextItem:(UIButton *)sender { self.currentItemIndex++; [self updateCurrentInventoryItem]; [self updateInventoryButtons]; }
これらのヘルパーメソッドのおかげで、 currentItemIndexの変更とUIの更新を伴うオブジェクト間の切り替えが非常に簡単になりました。 コンパイルして実行します。 これで、メニュー全体を見ることができます。
オブジェクトを削除して追加する
メニューはありますが、注文を受け付けるウェイターはいません。 つまり、追加ボタンと削除ボタンは機能しません。 さて、時が来ました。
IODOrderクラスに別のヘルパーメソッドが必要です。 I ODOrder.mでは 、次のように記述します。
- (NSMutableDictionary *)orderItems{ if (!_orderItems) { _orderItems = [[NSMutableDictionary alloc] init]; } return _orderItems; }
これは、 orderItemsプロパティの単純なgetterメソッドです。 orderItemsに既に何かがある場合、メソッドはオブジェクトを返します。 そうでない場合は、新しいディクショナリを作成し、 orderItemsを割り当てて返します。
次に、 orderDescriptionメソッドに取り組みます。 このメソッドは、チョークボードに出力するための線を提供します。 IODOrder.mに次のように記述します。
- (NSString*)orderDescription { // 1 - NSMutableString* orderDescription = [[NSMutableString alloc] init]; // 2 - NSArray* keys = [[self.orderItems allKeys] sortedArrayUsingComparator:^NSComparisonResult(id obj1, id obj2) { IODItem* item1 = (IODItem*)obj1; IODItem* item2 = (IODItem*)obj2; return [item1.name compare:item2.name]; }]; // 3 - [keys enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) { IODItem* item = (IODItem*)obj; NSNumber* quantity = (NSNumber*)[self.orderItems objectForKey:item]; [orderDescription appendFormat:@"%@ x%@\n", item.name, quantity]; }]; // 4 - return [orderDescription copy]; }
少し説明:
- これは、順序を説明する行です。各注文オブジェクトがそれに追加されます。
- このコードは、orderItemsディクショナリキーの配列を受け取り、sortedArrayUsingComparator:ブロックを使用してキーを名前でソートします。
- このコードは、キーのすでにソートされた配列を使用しており、すでによく知られているenumerateObjectsUsingBlock: 。各キーをIODItemに変換し、値(数量)を受け取り、orderDescriptionに行を追加します。
- 最後に、文字列orderDescriptionを返しますが、ここでもそのコピーのみを返します。
IODOrder.hに移動して、これら2つのメソッドのプロトタイプを追加します。
- (void)updateCurrentInventoryItem; - (void)updateInventoryButtons;
オブジェクトから注文説明行を取得できるようになったので、ViewController.mに切り替えて、呼び出すメソッドを追加します。
- (void)updateOrderBoard { if (![self.order.orderItems count]) { self.ibChalkboardLabel.text = @"No Items. Please order something!"; } else { self.ibChalkboardLabel.text = [self.order orderDescription]; } }
このメソッドは、順序内のオブジェクトの数を確認します。存在しない場合、文字列「No Items。何か注文してください!」それ以外の場合、メソッドはIODOrderクラスのorderDescriptionメソッドを使用して、注文を説明する文字列を表示します。これで、現在の順序に従ってボードを更新できます。viewDidAppearからブロックを更新します。
dispatch_async(self.queue, ^{ self.inventory = [[IODItem retrieveInventoryItems] mutableCopy]; dispatch_async(dispatch_get_main_queue(), ^{ [self updateOrderBoard]; //<---- [self updateInventoryButtons]; [self updateCurrentInventoryItem]; self.ibChalkboardLabel.text = @"Inventory Loaded\n\nHow can I help you?"; }); });
以下の数行のソースコードを書き直しただけなので、これは無意味に思えるかもしれませんが、正確さのためにこれを行うことができます。
次のメソッドは、オブジェクトを注文に追加します。移動しますIODOrder.m。
- (void)addItemToOrder:(IODItem*)inItem { // 1 - IODItem* key = [self findKeyForOrderItem:inItem]; // 2 - - if (!key) { [self.orderItems setObject:[NSNumber numberWithInt:1] forKey:inItem]; } else { // 3 - - NSNumber* quantity = self.orderItems[key]; int intQuantity = [quantity intValue]; intQuantity++; // 4 - [self.orderItems removeObjectForKey:key]; [self.orderItems setObject:[NSNumber numberWithInt:intQuantity] forKey:key]; } }
私が説明する:
- 前に追加したメソッドを使用して、orderItemのキーを見つけます。オブジェクトが見つからない場合、単にnilを返すことに注意してください。
- オブジェクトが順序で見つからなかった場合は、1に等しい量で追加します。
- オブジェクトが見つかった場合、その番号を見て1増やします。
- 最後に、元のレコードを削除し、更新された数量で新しいバージョンを記録します。
方法removeItemFromOrder:ほぼ同じaddItemToOrder: 。ではIODOrder.mの書き込み:
- (void)removeItemFromOrder:(IODItem*)inItem { // 1 - IODItem* key = [self findKeyForOrderItem:inItem]; // 2 - , if (key) { // 3 - , NSNumber* quantity = self.orderItems[key]; int intQuantity = [quantity intValue]; intQuantity--; // 4 - [[self orderItems] removeObjectForKey:key]; // 5 - 0 if (intQuantity) [[self orderItems] setObject:[NSNumber numberWithInt:intQuantity] forKey:key]; } }
注文からオブジェクトを削除する場合、その注文でオブジェクトが見つかった場合にのみ何かをする必要があることに注意してください。見つかった場合は、その量を見て、1ずつ減り、オブジェクトが辞書から削除され、0より大きい場合はそこに新しいオブジェクトを挿入します。IODOrder.hに
移動して、プロトタイプを追加します。
- (void)addItemToOrder:(IODItem*)inItem; - (void)removeItemFromOrder:(IODItem*)inItem;
ViewController.mで、オブジェクトを削除および追加するためのボタンのコードを記述できます。
- (IBAction)ibaRemoveItem:(UIButton *)sender { IODItem* currentItem = self.inventory[self.currentItemIndex]; [self.order removeItemFromOrder:currentItem]; [self updateOrderBoard]; [self updateCurrentInventoryItem]; [self updateInventoryButtons]; } - (IBAction)ibaAddItem:(UIButton *)sender { IODItem* currentItem = self.inventory[self.currentItemIndex]; [self.order addItemToOrder:currentItem]; [self updateOrderBoard]; [self updateCurrentInventoryItem]; [self updateInventoryButtons]; }
両方のメソッドで行う必要があるのは、インベントリ配列から現在のオブジェクトを取得し、それをaddItemToOrder:またはremoveItemFromOrder:に渡し、UIを更新することだけです。
プロジェクトをビルドしてビルドします。これで、オブジェクトを追加および削除でき、ボードが更新されます。
UIAnimation
プログラムを少し復活させましょう。ibaRemoveItem:およびibaAddItemMethod:を変更します。
- (IBAction)ibaRemoveItem:(UIButton *)sender { IODItem* currentItem = [self.inventory objectAtIndex:self.currentItemIndex]; [self.order removeItemFromOrder:currentItem]; [self updateOrderBoard]; [self updateCurrentInventoryItem]; [self updateInventoryButtons]; UILabel* removeItemDisplay = [[UILabel alloc] initWithFrame:self.ibCurrentItemImageView.frame]; removeItemDisplay.center = self.ibChalkboardLabel.center; removeItemDisplay.text = @"-1"; removeItemDisplay.textAlignment = NSTextAlignmentCenter; removeItemDisplay.textColor = [UIColor redColor]; removeItemDisplay.backgroundColor = [UIColor clearColor]; removeItemDisplay.font = [UIFont boldSystemFontOfSize:32.0]; [[self view] addSubview:removeItemDisplay]; [UIView animateWithDuration:1.0 animations:^{ removeItemDisplay.center = [self.ibCurrentItemImageView center]; removeItemDisplay.alpha = 0.0; } completion:^(BOOL finished) { [removeItemDisplay removeFromSuperview]; }]; } - (IBAction)ibaAddItem:(UIButton *)sender { IODItem* currentItem = [self.inventory objectAtIndex:self.currentItemIndex]; [self.order addItemToOrder:currentItem]; [self updateOrderBoard]; [self updateCurrentInventoryItem]; [self updateInventoryButtons]; UILabel* addItemDisplay = [[UILabel alloc] initWithFrame:self.ibCurrentItemImageView.frame]; addItemDisplay.text = @"+1"; addItemDisplay.textColor = [UIColor whiteColor]; addItemDisplay.backgroundColor = [UIColor clearColor]; addItemDisplay.textAlignment = NSTextAlignmentCenter; addItemDisplay.font = [UIFont boldSystemFontOfSize:32.0]; [[self view] addSubview:addItemDisplay]; [UIView animateWithDuration:1.0 animations:^{ [addItemDisplay setCenter:self.ibChalkboardLabel.center]; [addItemDisplay setAlpha:0.0]; } completion:^(BOOL finished) { [addItemDisplay removeFromSuperview]; }]; }
コードはたくさんありますが、簡単です。最初の部分はUILabelを作成し、そのプロパティを公開します。2番目は、作成されたラベルを移動するアニメーションです。これは、ブロックのあるUIViewアニメーションの例です。これについては、レッスンの冒頭で説明しました。
プロジェクトを実行します。 これで、ボタンをクリックしてオブジェクトを追加および削除すると、アニメーションを見ることができます。
金額の取得(合計)
IODOrder.mに追加する必要がある最後の方法は、注文額を計算する方法です。
- (float)totalOrder { // 1 - __block float total = 0.0; // 2 - float (^itemTotal)(float,int) = ^float(float price, int quantity) { return price * quantity; }; // 3 - [self.orderItems enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) { IODItem* item = (IODItem*)key; NSNumber* quantity = (NSNumber*)obj; int intQuantity = [quantity intValue]; total += itemTotal([item.price floatValue], intQuantity); }]; // 4 - return total; }
コードを見てみましょう:
- , . __block . . __block , , , . , . .
- , , .
- enumerateKeysAndObjectsUsingBlockブロックを介したこのコード:orderItemsディクショナリ内のすべてのオブジェクトを反復処理し、前のブロックを使用して各オブジェクトの合計を検索し、この値を順序全体の合計に追加します(これが、順序変数に__blockキーワードを必要とする理由です-ブロック内で変更します)。
- 注文の合計金額を返します。
IODOrder.hに戻り、プロトタイプを追加します。
- (float)totalOrder;
最後に行うことは、アプリケーションに金額計算を追加することです。すべての汚い仕事をすることによって行われTOTALORDER、我々は唯一のボタンを押すことにより、ユーザーの金額が表示されます合計のを。ibaCalculateTotalメソッドに入力します。
- (IBAction)ibaCalculateTotal:(UIButton *)sender { float total = [self.order totalOrder]; UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"Total" message:[NSString stringWithFormat:@"$%0.2f",total] preferredStyle:UIAlertControllerStyleAlert]; UIAlertAction *cancelButton = [UIAlertAction actionWithTitle:@"Close" style:UIAlertActionStyleDefault handler:^(UIAlertAction * action) { [alert dismissViewControllerAnimated:YES completion:nil]; }]; [alert addAction:cancelButton]; [self presentViewController:alert animated:YES completion:nil]; }
ここで、注文の金額を考慮して、画面に表示します。
以上です! プロジェクトを実行します。
ブロックチートシート
終了する前に、役立つブロックのリストを示します。
NSArray
- enumerateObjectsUsingBlockは、おそらくオブジェクトを反復処理するために最も頻繁に使用されるブロックです。
- enumerateObjectsAtIndexes:usingBlock: -同じようenumerateObjectsUsingBlock、唯一可能な一定期間内のオブジェクトをソートします。
- indexsOfObjectsPassingTest: -ブロックは、ブロックによって記述されたテストに合格したオブジェクトのインデックスのセットを返します。オブジェクトの特定のグループを見つけるのに役立ちます。
NSD辞書
- enumerateKeysAndObjectsUsingBlock: -辞書の要素を反復処理します。
- keysOfEntriesPassingTest: -ブロックで記述されたテストに合格したオブジェクトのキーのセットを返します。
ウイビュー
- animateWithDuration:animations:-UIAnimationのブロック。単純なアニメーションに役立ちます。
- animateWithDuration:completion: -別のUIAnimationブロック。アニメーションの最後にコードを呼び出すための2番目のブロックが含まれています。
グランドセントラル派遣
- dispatch_asyncは、非同期GCDコードのメイン関数です。
独自のブロックを作成する
以下は、カスタムブロックを作成するためのコードサンプルです。
// , - (void)doMathWithBlock:(int (^)(int, int))mathBlock { self.label.text = [NSString stringWithFormat:@"%d", mathBlock(3, 5)]; } // - (IBAction)buttonTapped:(id)sender { [self doMathWithBlock:^(int a, int b) { return a + b; }]; }
ブロックはObjective-Cオブジェクトであるため、将来の参照のためにプロパティに保存できます。これは、何らかの非同期タスク(ネットワーク関連など)を完了した後にメソッドを呼び出す場合に便利です。
// @property (strong) int (^mathBlock)(int, int); // - (void)doMathWithBlock:(int (^)(int, int))mathBlock { self.mathBlock = mathBlock; } // - (IBAction)buttonTapped:(id)sender { [self doMathWithBlock:^(int a, int b) { return a + b; }]; } // ... - (IBAction)button2Tapped:(id)sender { self.label.text = [NSString stringWithFormat:@"%d", self.mathBlock(3, 5)]; } }
最後に、typedefを使用してコードを簡素化できます。
//typedef typedef int (^MathBlock)(int, int); // , typedef @property (strong) MathBlock mathBlock; // - (void)doMathWithBlock:(MathBlock) mathBlock { self.mathBlock = mathBlock; } // - (IBAction)buttonTapped:(id)sender { [self doMathWithBlock:^(int a, int b) { return a + b; }]; } // ... - (IBAction)button2Tapped:(id)sender { self.label.text = [NSString stringWithFormat:@"%d", self.mathBlock(3, 5)]; }
ブロックと完了
最後のヒント。ブロックを取るメソッドを使用すると、Xcodeがブロックを自動補完できるため、時間を節約し、起こりうるエラーを防ぐことができます。
たとえば、次を入力します。
NSArray * array; [array enum
オートコンプリートプログラムはenumerateObjectsUsingBlockを検出します。オートコンプリートするにはEnterキーを押します。次に、もう一度Enterキーを押してブロックを自動補完します。結果は次のとおりです。
[array enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) { code }
コードの代わりにコードを記述し、メソッド呼び出しを閉じ、できれば、すべてを手動で入力するよりはるかに簡単です。
[array enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) { // Do something }];
次はどこへ行きますか?
プロジェクトコードはこちらからダウンロードできます。gitに精通している場合は、ここからプロジェクトをすべてのステップでコミットできます。この単純なアプリケーションを作成する過程で、ブロックのシンプルさとパワーを感じ、プロジェクトでそれらを使用するためのアイデアを得たと思います。
これがあなたのブロックの知り合いだった場合-あなたは大きな一歩を踏み出しました。マルチスレッド、ネットワークなどで機能するには、ブロックを学習する必要があります。
ブロックの詳細については、次を参照してください。