ここでは、クロージャの構文、コンテキストをキャプチャするメカニズム、メモリ管理、ラムダとブロックの相互作用について説明しようとしました。
すべての例でApple LLVM Compiler 4.2(Clang)を使用しました。 ARCは、Obj-Cコードには使用されません。ARCがどのように機能するかを理解するために、非ARCコードがどのように機能するかを知っておく必要があるからです。
セクション:
Objective-Cのブロックは 、 クロージャー[2]の実装です。 ブロックは、コンテキスト(現在のスタック変数とクラスメンバー変数)をキャプチャできる匿名関数です。 実行時のブロックはオブジェクトによって表され、C ++のラムダ式の類似物です。
C ++のラムダもクロージャの実装であり、匿名のローカル関数を表します。
構文
Obj-Cブロック
[3]
テキスト形式で
int multiplier = 7; int (^myBlock)(int) = ^(int num) { return num * multiplier;}; NSLog(@”%d”, myBlock(4)); // 28
- ^-この記号は、変数がブロックであることをコンパイラーに伝えます
- int-ブロックはint型のパラメーターを1つ受け入れ、int型のパラメーターを返します
- 乗数-コンテキストから取得される変数(これについては、「コンテキストのキャプチャ」セクションを参照してください)
ブロックにはポインターセマンティクスがあります。
Objective-Cのブロックは、すでに標準フレームワーク(Foundation、UIKit)とサードパーティライブラリ( AFNetworking 、 BlocksKit )の両方でアプリケーションをしっかりと発見しています。
NSArrayクラスクラスの形式で例を示します
NSArrayのブロック使用例
// @implementation NSArray (Blocks) // , - (NSArray*)subarrayWithPredicate:(BOOL(^)(id object, NSUInteger idx, BOOL *stop))predicte { NSMutableArray *resultArray = [NSMutableArray array]; BOOL shouldStop = NO; for (id object in self) { if (predicte(object, [self indexOfObjectIdenticalTo:object], &shouldStop)) { [resultArray addObject:object]; } if (shouldStop) { break; } } return [[resultArray copy] autorelease]; } @end // - NSArray *numbers = @[@(5), @(3), @(8), @(9), @(2)]; NSUInteger divisor = 3; NSArray *divisibleArray = [numbers subarrayWithPredicate:^BOOL(id object, NSUInteger idx, BOOL *stop) { BOOL shouldAdd = NO; // 3 NSAssert([object isKindOfClass:[NSNumber class]], @"object != number"); // , divisor if ([(NSNumber *)object intValue] % divisor == 0) { shouldAdd = YES; } return shouldAdd; }]; NSLog(@"%@", divisibleArray); // 3, 9
まず、これらは非同期操作に最適です。AFNetworkingを使用してこれを確認できます。GCDで作業するのは楽しいことです。
たとえば、ブロックタイプを定義できます。
ブロックタイプ宣言
typedef int (^MyBlockType)(int number, id object);
C ++ラムダ
同じラムダコード
[11]
テキスト形式で
int multiplier = 7; auto lambda = [&multiplier](int num) throw() -> int { return multiplier * num; }; lambda(4); // 28
-
[]
-ラムダ宣言の開始、内部-コンテキストキャプチャ -
&multiplier
-キャプチャされた変数(&
参照によりキャプチャされた手段) -
int
入力パラメーター -
mutable
値でキャプチャされた変数を変更できるキーワード -
throw()
-ラムダが外部に例外をスローしないことを意味します
ラムダのサブセットを選択する同様の例を示します
述語コレクションからサブセットを取得します
template<class InputCollection, class UnaryPredicate> void subset(InputCollection& inputCollection, InputCollection& outputCollection, UnaryPredicate predicate) { typename InputCollection::iterator iterator = inputCollection.begin(); for (;iterator != inputCollection.end(); ++iterator) { if (predicate(*iterator)) { outputCollection.push_back(*iterator); } } return; } int main(int argc, const char * argv[]) { int divisor = 3; std::vector<int> inputVector = {5, 3, 8, 9, 2}; std::vector<int> outputVector; subset(inputVector, outputVector, [divisor](int number){return number % divisor == 0;}); // std::for_each( outputVector.begin(), outputVector.end(), [](const int& number){std::cout << number << std::endl;} ); }
コンテキストキャプチャ
Obj-Cブロック
変更しない場合、ブロック内のスタック変数の値を自動的に使用できます。 たとえば、上記の例では、
multiplier
変数をキャプチャすることをブロック宣言で示していません(ラムダとは異なり、ラムダでは
[&]
を指定してリンクでコンテキスト全体をキャプチャしたり、
[=]
コンテキスト全体をキャプチャしたりできます)値で)。
関数の本体で宣言された名前で値を取得しました。 ブロックの本体で値を変更する場合は、変数に
__block
修飾子を付ける必要があります
コンテキストから変数値を変更する例
__block int first = 7; void (^myBlock2)(int) = ^(int second) { first += second;}; myBlock2(4); NSLog(@"%d", first); // 11
ブロックに渡すポインタであるオブジェクトにメッセージを送信するために、ポインタを
__block
としてマークする必要はありません。 実際、オブジェクトにメッセージを送信するとき、そのポインターは変更しません。
コンテキストからオブジェクトにメッセージを送信する例
NSMutableArray *array = [NSMutableArray array]; void (^myBlock3)() = ^() { [array addObject:@"someString"];}; myBlock3(); // someString array
ただし、場合によっては、メモリリークを回避するために、
__block
オブジェクトへのポインタをマークする必要があります。 (詳細については、「メモリ管理」セクションを参照してください)
C ++ラムダ
キャプチャされた変数は、特定の場所[5] 、つまり角括弧[]内に示されます
-
[&]
-すべての文字を参照によりキャプチャすることを意味します -
[=]
-値によるすべての文字 -
[a, &b]
b
値によってキャプチャされ、b
参照によってキャプチャされます -
[]
-何もキャプチャされません
ミューテーターは、コンテキストのキャプチャーに関連しています。
値によって渡される変数のコピーを変更できること。 詳細は次のセクションで。
メモリ管理
Obj-Cブロック
ブロックはオブジェクトであり、スタック上に作成されます(後でヒープに転送できます)
ブロックは、3つの実装[7]の形で存在できます。
- ブロック内のコンテキスト(スタック)の変数を使用しない場合、
NSGlobalBlock
が作成され、シングルトンとして実装されます。 - コンテキスト変数を使用すると、
NSStackBlock
が作成されます。これはもはやシングルトンではなく、スタック上にあります。 -
Block_copy
関数を使用する場合、またはオブジェクトプロパティとして、ヒープに配置されたオブジェクト内にブロックを保存する場合:@property (nonatomic, copy) MyBlockType myBlock;
次に、クラスNSMallocBlock
オブジェクトNSMallocBlock
ます。これは、コンテキストで渡されたオブジェクトをキャプチャし、マスター(マスター==メッセージretain
送信)します。 これは非常に重要なプロパティです。注意して処理すると、メモリリークが発生する可能性があるためです。 ブロックは保持サイクルを作成できます。 また、NSMallocBlock
でproperty
の値を使用する場合、保持されるのはプロパティ自体ではなく、プロパティが属するオブジェクトであることに注意することも重要です。
所有権サイクルの例を次に示します。
API PKHTTPReuquest
非同期
API PKHTTPReuquest
HTTP
リクエストを行うクラスを作成したいとしましょう
PKHTTPReuquestの実装
typedef void (^PKHTTPRequestCompletionSuccessBlock)(NSString *responseString); typedef void (^PKHTTPRequestCompletionFailBlock)(NSError* error); @protocol PKRequest <NSObject> - (void)startRequest; @end @interface PKHTTPRequest : NSObject <PKRequest> // designated initializer - (id)initWithURL:(NSURL *)url successBlock:(PKHTTPRequestCompletionSuccessBlock)success failBlock:(PKHTTPRequestCompletionFailBlock)fail; @end
@interface PKHTTPRequest () <NSURLConnectionDelegate> @property (nonatomic, copy) PKHTTPRequestCompletionSuccessBlock succesBlock; @property (nonatomic, copy) PKHTTPRequestCompletionFailBlock failBlock; @property (nonatomic, retain) NSURL *url; @property (nonatomic, retain) NSURLConnection *connection; @property (nonatomic, retain) NSMutableData *data; @end @implementation PKHTTPRequest #pragma mark - initialization / deallocation // designated initializer - (id)initWithURL:(NSURL *)url successBlock:(PKHTTPRequestCompletionSuccessBlock)success failBlock:(PKHTTPRequestCompletionFailBlock)fail { self = [super init]; if (self != nil) { self.succesBlock = success; self.failBlock = fail; self.url = url; NSURLRequest *request = [NSURLRequest requestWithURL:self.url]; self.connection = [[[NSURLConnection alloc] initWithRequest:request delegate:self startImmediately:NO] autorelease]; } return self; } - (id)init { NSAssert(NO, @"Use desiganted initializer"); return nil; } - (void)dealloc { self.succesBlock = nil; self.failBlock = nil; self.url = nil; self.connection = nil; self.data = nil; [super dealloc]; } #pragma mark - public methods - (void)startRequest { self.data = [NSMutableData data]; [self.connection start]; } #pragma mark - NSURLConnectionDelegate implementation - (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data { [self.data appendData:data]; } - (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error { self.failBlock(error); self.data = nil; } - (void)connectionDidFinishLoading:(NSURLConnection *)connection { self.succesBlock([NSString stringWithUTF8String:self.data.bytes]); self.data = nil; } @end
そして、
PKGetUserNameRequest
動作する
PKGetUserNameRequest
サーバーの特定のAPIに対する特定のリクエストを作成し
PKHTTPReuquest
PKGetUserNameRequestの実装
typedef void (^PKGetUserNameRequestCompletionSuccessBlock)(NSString *userName); typedef void (^PKGetUserNameRequestCompletionFailBlock)(NSError* error); @interface PKGetUserNameRequest : NSObject <PKRequest> - (id)initWithUserID:(NSString *)userID successBlock:(PKGetUserNameRequestCompletionSuccessBlock)success failBlock:(PKGetUserNameRequestCompletionFailBlock)fail; @end
NSString *kApiHost = @"http://someApiHost.com"; NSString *kUserNameApiKey = @"username"; @interface PKGetUserNameRequest () @property (nonatomic, retain) PKHTTPRequest *httpRequest; - (NSString *)parseResponse:(NSString *)response; @end @implementation PKGetUserNameRequest #pragma mark - initialization / deallocation - (id)initWithUserID:(NSString *)userID successBlock:(PKGetUserNameRequestCompletionSuccessBlock)success failBlock:(PKGetUserNameRequestCompletionFailBlock)fail { self = [super init]; if (self != nil) { NSString *requestString = [kApiHost stringByAppendingFormat:@"?%@=%@", kUserNameApiKey, userID]; self.httpRequest = [[[PKHTTPRequest alloc] initWithURL:[NSURL URLWithString:requestString] successBlock:^(NSString *responseString) { // - self NSString *userName = [self parseResponse:responseString]; success(userName); } failBlock:^(NSError *error) { fail(error); } ] autorelease]; } return self; } - (id)init { NSAssert(NO, @"Use desiganted initializer"); return nil; } - (void)dealloc { self.httpRequest = nil; [super dealloc]; } #pragma mark - public methods - (void)startRequest { [self.httpRequest startRequest]; } #pragma mark - private methods - (NSString *)parseResponse:(NSString *)response { /* ...... */ return userName; } @end
この行のエラーは
NSString *userName = [self parseResponse:responseString];
-Mallocブロックのselfで何かを呼び出すと、self retuns、所有権グラフに次のループが形成されます。
これを回避するには、次のように、__ block修飾子を使用してスタック上のselfへの中間ポインターを作成します。
テニュアサイクルを破る例
// __block PKGetUserNameRequest *selfRequest = self; self.httpRequest = [[[PKHTTPRequest alloc] initWithURL:[NSURL URLWithString:requestString] successBlock:^(NSString *responseString) { NSString *userName = [selfRequest parseResponse:responseString]; success(userName); } failBlock:^(NSError *error) { fail(error); } ] autorelease];
または、初期化メソッドの署名から
startRequest
メソッドにブロックを転送できます。
startRequestwithCompaltion:fail:
リクエストの期間中のみブロックを保持します。 その後、
__block
修飾子なしで実行できます。 これは別の問題を解決します:上記の例では、ブロックが弱い通信を提供するため、ブロックが呼び出されるまでに(要求が完了するまでに)、タイプPKGetUserNameRequestのオブジェクトが既に存在しなくなる(deallocメソッドが呼び出される)危険があります。 そして、
selfRequest
は
selfRequest
ポインターでハングし、クラッシュを引き起こします。
ブロックを使用した誤ったメモリ管理の別の例、 ビデオ講義からの例[7]
NSStackBlockのエラー
void addBlockToArray(NSMutableArray* array) { NSString *string = @"example string"; [array addObject:^{ printf("%@\n", string); }]; } void example() { NSMutableArray *array = [NSMutableArray array]; addBlockToArray(array); void (^block)() = [array objectAtIndex:0]; block(); }
ブロックをヒープにコピーしてスタックを渡した場合、エラーは発生しませんでした。
また、この例ではARCコードにエラーは発生しません。
C ++ラムダ
ランタイムでのラムダの実装は、コンパイラによって異なる場合があります。 彼らは、ラムダのメモリ管理は標準ではあまり記述されていないと言います。 [9]
一般的な実装を検討してください。
C ++のラムダは、スタック上に作成される不明なタイプのオブジェクトです。
コンテキストをキャプチャしないラムダは関数ポインターにキャストできますが、それでもラムダ自体が関数へのポインターであることを意味するわけではありません。 ラムダは、スタック上で際立っているコンストラクタとデストラクタを持つ通常のオブジェクトです。
ヒープ内でラムダを移動する例を次に示します
ヒープ内でラムダを移動する例
// №1 auto lamb = []() {return 5;}; auto func_lamb_ptr = new std::function<int()>(lamb); // №2: auto lamb = []() {return 5;}; auto* p = new decltype(lamb)(lamb); // №3: template <typename T> T* heap_alloc(T const& value) { return new T(value); } auto* p = heap_alloc([]() {return 5;}); // №4: std::vector<decltype(lamb)> v; v.push_back(lamb);
これで、オブジェクトのメンバー変数に関数を渡すことができます。
ラムダ引数を宣言した後は可変であるため、値によってキャプチャされた変数のコピーの値を変更できることを意味します(元の変数の値は変更されません)。 たとえば、次のようにラムダを定義した場合:
auto lambda = [multiplier](int num) throw() mutable
ラムダ内の
multiplier
値を変更できますが、関数で宣言された
multiplier
は変更されていません。 さらに、変更された
multiplier
値は、このラムダインスタンスの呼び出し
multiplier
保存されます。 このように想像できます。ラムダインスタンス(オブジェクト)で、渡されたパラメーターに対応する変数メンバーが作成されます。 ここで注意する必要があります。ラムダのインスタンスをコピーして呼び出すと、これらのメンバー変数は元のラムダでは変更されず、コピーされたものでのみ変更されるためです。 時々、
std::ref
ラップされたラムダを渡す必要があります。 Obj-Cブロックは、すぐに使用できるような機会を提供しません。
Objective-C ++
Objecitve-C ++はObjective-CとC ++の両方を組み合わせているため、ラムダとブロックを同時に使用できます。 ラムダとブロックはどのように相互に関連していますか?
- ブロックにラムダを割り当てることができます。
例void (^block_example)(int); auto lambda_example = [](int number){number++; NSLog(@"%d", number);}; block_example = lambda_example; block_example(10); // log 11
- ブロックをstd :: functionに割り当てることができます
ここで、Objective-CとC ++には異なるメモリ管理ポリシーがあり、std::function
ブロックを格納するとリンクがダングリングstd::function
可能性があることに注意してください。
- ラムダブロックを割り当てることはできません。
Lambdaには、コピー割り当てステートメントが定義されていません。 したがって、ブロックまたは自分自身を割り当てることはできません。
割り当てエラーint main() { auto lambda1 = []() -> void { printf("Lambda 1!\n"); }; lambda1 = lambda1; // error: use of deleted function 'main()::<lambda()>& main()::<lambda()>::operator=(const main()::<lambda()>&)' return 0; }
ラムダとブロックの間の操作は非常にエキゾチックであると言わなければなりません。たとえば、プロジェクトでそのような割り当てを見たことはありません。