Objective-Cパート1でのブロックとその使用について

OS X 10.6およびiOS 4.0では、Appleはブロックのサポートを発表しました。これは基本的にクロージャーです。 iOSの開発のコンテキスト、Objective-C(つまり、 gcなしで動作する)のブロックについて詳しく説明します。
iOSバージョンを使用するには <4.0、 ESBlockRuntimeまたはPLBlocksを適用できます。

理論について簡単に


ブロックインスタンス、ブロックタイプ、およびブロックリテラル自体は、^演算子を使用して示されます。次に例を示します。

typedef int (^MyBlock)( int );

int multiplier = 7;
MyBlock myBlock = ^( int num) {
return num * multiplier;
};


* This source code was highlighted with Source Code Highlighter .

または

int multiplier = 7;
int (^myBlock)( int ) = ^( int num) {
return num * multiplier;
};


* This source code was highlighted with Source Code Highlighter .

ブロックの呼び出しは、通常の関数の呼び出しに似ています。 たとえば、次のように:

myBlock( 3 )

* This source code was highlighted with Source Code Highlighter .

ブロックの主な機能は、作成されたコンテキストを保存する機能です。 上記の例では、「myBlock」は常に数値に7を掛けます。これはどのように機能しますか?

ブロックコンテキスト変数のタイプ

1.プリミティブ型Cおよび構造体、ブロックは定数として保存されます。 例:


int multiplier = 7;
int (^myBlock)( int ) = ^( int num) {
return num * multiplier;
};
multiplier = 8;
NSLog( @"%d" , myBlock( 3 ) );


* This source code was highlighted with Source Code Highlighter .

プリント-24ではなく21。

2. __blockキーワードで定義された変数は可変です。 これは、そのような変数の値をヒープにコピーすることで機能し、各ブロックはこの変数へのリンクを保存します。 例:

__block int multiplier = 7;
int (^myBlock)( int ) = ^( int num) {
return num * multiplier;
};
multiplier = 8;
NSLog( @"%d" , myBlock( 3 ) );


* This source code was highlighted with Source Code Highlighter .

プリント-21ではなく24。

3.変数-参照カウント(id、NSObject)を持つオブジェクトへのポインター。 ブロックがヒープにコピーされるときに、Retainが呼び出されます。 例:

NSDate* date = [ [ NSDate alloc ] init ];

void (^printDate)() = ^() {
NSLog( @"date: %@" , date );
};

//
printDate = [ [ printDate copy ] autorelease ];

[ date release ];

printDate();


* This source code was highlighted with Source Code Highlighter .

ここでは、日付オブジェクトの保持は、作成中ではなく、ブロックがヒープにコピーされたときに正確に行われるという事実に注目したいと思います。 たとえば、このコードは「EXC_BAD_ACCESS」から落ちます

NSDate* date = [ [ NSDate alloc ] init ];

void (^printDate)() = ^() {
NSLog( @"date: %@" , date );
};

[ date release ];

//
printDate = [ [ printDate copy ] autorelease ];

printDate();


* This source code was highlighted with Source Code Highlighter .

4.変数-__blockキーワードで宣言された参照カウント(id、NSObject)を持つオブジェクトへのポインター。 ブロックをヒープにコピーするときに、Retain は呼び出さません 。 例:
__block NSDate* date = [ [ NSDate alloc ] init ];

void (^printDate)() = ^() {
// date
NSLog( @"date: %@" , date );
};

// , date retain
printDate = [ [ printDate copy ] autorelease ];

[ date release ];

printDate();


* This source code was highlighted with Source Code Highlighter .

これは通常、循環参照を回避するために使用されます。 例:
@ interface SomeClass : NSObject

//
@property ( nonatomic, copy ) SimpleBlock block;

@end

@implementation SomeClass

@synthesize block = _block;

-( void )dealloc
{
[ _block release ];

[ super dealloc ];
}

-( void )methodB
{
}

-( void )methodA
{
__block SomeClass* self_ = self;
// ( ) - ,
self.block = ^()
{
// retain self_
[ self_ methodB ];
};
}

@end


* This source code was highlighted with Source Code Highlighter .

ブロックはNSObjectクラスのインスタンスです(これらのオブジェクトの特定のクラスは定義されていません)。そのため、NSObjectクラスのメソッド(ブロックのコピー、保持、解放、自動解放)を使用することができます。 しかし、なぜこれが必要なのでしょうか?

ブロックとメモリ管理

デフォルトでは、ブロックインスタンスは、想定されるようにヒープ上ではなく、スタック上に作成されます。 したがって、必要に応じて、ブロックに対して遅延呼び出しを行います。最初にブロックをヒープにコピーする必要があります。

NSObjectクラスの拡張機能に「performAfterDelay:」メソッドがあり、指定されたブロックを遅延して実行するとします。

@implementation NSObject (BlocksExtensions)

-( void )callSelfBlock
{
void * self_ = self;
ESSimpleBlock block_ = (ESSimpleBlock)self_;
block_();
}

-( void )performAfterDelay:( NSTimeInterval )delay_
{
[ self performSelector: @selector( callSelfBlock ) withObject: nil afterDelay: delay_ ];
}

@end


* This source code was highlighted with Source Code Highlighter .

そして、実際には、呼び出し:
NSDate* date = [ NSDate date ];

void (^printDate)() = ^() {
NSLog( @"date: %@" , date );
};

[ printDate performAfterDelay: 0.3 ];


* This source code was highlighted with Source Code Highlighter .

そのようなコードはアプリケーションを「ダンプ」します。これは、呼び出しの時点でスタックブロックが破壊され、ブロックが呼び出された場所のランダムメモリにアクセスするためです。 このコードは:
void (^printDate)() = ^() {
NSLog( @"date: %@" , [ NSDate date ] );
};

[ printDate performAfterDelay: 0.3 ];


* This source code was highlighted with Source Code Highlighter .

うまくいきます。 その理由は何ですか? 最後のブロックは外部変数を参照しないため、コピーを作成する必要がないことに注意してください。 この場合、コンパイラはいわゆるグローバルブロックを作成します。 プログラムにはそのようなブロックのインスタンスが1つだけあり、その有効期間はアプリケーションの有効期間によって制限されます。 したがって、GlobalBlockはシングルトーンオブジェクトと見なすことができます。

ブロック変数のタイプ

そして、要約すると。 ブロックには、グローバル(ステートレス)、ローカルまたはスタック、およびヒープ上のブロック(MallocBlock)の3つのタイプがあります。 したがって、グローバルブロックのcopy、retain、release、およびautoreleaseメソッドは何も実行しません。 retainメソッドは、スタックブロックに対しても何もしません。 Mallocブロックの場合、copyメソッドはNSObjectの保持として機能します。

そしてもちろん、コピーメソッドが追加された前の例の改訂版:
@implementation NSObject (BlocksExtensions)

-( void )callSelfBlock
{
void * self_ = self;
ESSimpleBlock block_ = (ESSimpleBlock)self_;
block_();
}

-( void )performAfterDelay:( NSTimeInterval )delay_
{
// , - afterDelay:
self = [ [ self copy ] autorelease ];
[ self performSelector: @selector( callSelfBlock ) withObject: nil afterDelay: delay_ ];
}

@end


* This source code was highlighted with Source Code Highlighter .

続き: 「ブロックとObjective-Cパート2での使用について」

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


All Articles