Ya, tetapi Anda harus menggunakan kategori.
Sesuatu seperti:
@interface UIControl (DDBlockActions)
- (void) addEventHandler:(void(^)(void))handler
forControlEvents:(UIControlEvents)controlEvents;
@end
Penerapannya akan sedikit lebih rumit:
#import <objc/runtime.h>
@interface DDBlockActionWrapper : NSObject
@property (nonatomic, copy) void (^blockAction)(void);
- (void) invokeBlock:(id)sender;
@end
@implementation DDBlockActionWrapper
@synthesize blockAction;
- (void) dealloc {
[self setBlockAction:nil];
[super dealloc];
}
- (void) invokeBlock:(id)sender {
[self blockAction]();
}
@end
@implementation UIControl (DDBlockActions)
static const char * UIControlDDBlockActions = "unique";
- (void) addEventHandler:(void(^)(void))handler
forControlEvents:(UIControlEvents)controlEvents {
NSMutableArray * blockActions =
objc_getAssociatedObject(self, &UIControlDDBlockActions);
if (blockActions == nil) {
blockActions = [NSMutableArray array];
objc_setAssociatedObject(self, &UIControlDDBlockActions,
blockActions, OBJC_ASSOCIATION_RETAIN);
}
DDBlockActionWrapper * target = [[DDBlockActionWrapper alloc] init];
[target setBlockAction:handler];
[blockActions addObject:target];
[self addTarget:target action:@selector(invokeBlock:) forControlEvents:controlEvents];
[target release];
}
@end
Beberapa penjelasan:
- Kami menggunakan kelas "khusus internal" yang disebut
DDBlockActionWrapper
. Ini adalah kelas sederhana yang memiliki properti blok (blok yang ingin kita panggil), dan metode yang hanya memanggil blok itu.
- The
UIControl
kategori hanya instantiates salah satu pembungkus ini, memberikan blok yang akan dipanggil, dan kemudian memberitahu dirinya untuk menggunakan wrapper dan yang invokeBlock:
metode sebagai target dan tindakan (seperti biasa).
- The
UIControl
kategori menggunakan obyek terkait untuk menyimpan array DDBlockActionWrappers
, karena UIControl
tidak mempertahankan target. Larik ini untuk memastikan bahwa blok ada saat mereka seharusnya dipanggil.
Kami harus memastikan bahwa DDBlockActionWrappers
pembersihan ketika objek dihancurkan, jadi kami melakukan peretasan yang buruk dengan swizzling -[UIControl dealloc]
yang baru yang menghapus objek terkait, dan kemudian memanggil dealloc
kode asli . Rumit, rumit. Sebenarnya, objek terkait dibersihkan secara otomatis selama deallocation .
Terakhir, kode ini diketik di browser dan belum dikompilasi. Mungkin ada beberapa hal yang salah dengannya. Jarak tempuh Anda mungkin berbeda.
objc_implementationWithBlock()
danclass_addMethod()
memecahkan masalah ini dengan cara yang sedikit lebih efisien daripada menggunakan objek terkait (yang menyiratkan pencarian hash yang tidak seefisien pencarian metode). Mungkin perbedaan kinerja yang tidak relevan, tetapi ini adalah alternatif.imp_implementationWithBlock
?objc_implementationWithBlock()
. :)UITableViewCell
akan menghasilkan duplikasi tindakan target yang diinginkan karena setiap target baru adalah instance baru dan target sebelumnya tidak dibersihkan untuk peristiwa yang sama. Anda harus membersihkan target terlebih dahulufor (id t in self.allTargets) { [self removeTarget:t action:@selector(invokeBlock:) forControlEvents:controlEvents]; } [self addTarget:target action:@selector(invokeBlock:) forControlEvents:controlEvents];
Balok adalah obyek. Berikan blok Anda sebagai
target
argumen, dengan@selector(invoke)
sebagaiaction
argumen, seperti ini:id block = [^{NSLog(@"Hello, world");} copy];// Don't forget to -release. [button addTarget:block action:@selector(invoke) forControlEvents:UIControlEventTouchUpInside];
sumber
invoke
metode pada Blok benda tidak umum dan tidak dimaksudkan untuk digunakan dalam mode ini.nil
bukan@selector(invoke)
.Tidak, penyeleksi dan blok bukanlah tipe yang kompatibel di Objective-C (sebenarnya, keduanya sangat berbeda). Anda harus menulis metode Anda sendiri dan meneruskan pemilihnya.
sumber
Mengambil semua jawaban yang sudah disediakan, jawabannya adalah Ya tetapi sedikit pekerjaan diperlukan untuk menyiapkan beberapa kategori.
Saya merekomendasikan menggunakan NSInvocation karena Anda dapat melakukan banyak hal dengan ini seperti dengan pengatur waktu, disimpan sebagai objek dan dipanggil ... dll ...
Inilah yang saya lakukan, tetapi perhatikan bahwa saya menggunakan ARC.
Pertama adalah kategori sederhana di NSObject:
.h
@interface NSObject (CategoryNSObject) - (void) associateValue:(id)value withKey:(NSString *)aKey; - (id) associatedValueForKey:(NSString *)aKey; @end
.m
#import "Categories.h" #import <objc/runtime.h> @implementation NSObject (CategoryNSObject) #pragma mark Associated Methods: - (void) associateValue:(id)value withKey:(NSString *)aKey { objc_setAssociatedObject( self, (__bridge void *)aKey, value, OBJC_ASSOCIATION_RETAIN ); } - (id) associatedValueForKey:(NSString *)aKey { return objc_getAssociatedObject( self, (__bridge void *)aKey ); } @end
Berikutnya adalah kategori di NSInvocation untuk disimpan dalam satu blok:
.h
@interface NSInvocation (CategoryNSInvocation) + (NSInvocation *) invocationWithTarget:(id)aTarget block:(void (^)(id target))block; + (NSInvocation *) invocationWithSelector:(SEL)aSelector forTarget:(id)aTarget; + (NSInvocation *) invocationWithSelector:(SEL)aSelector andObject:(__autoreleasing id)anObject forTarget:(id)aTarget; @end
.m
#import "Categories.h" typedef void (^BlockInvocationBlock)(id target); #pragma mark - Private Interface: @interface BlockInvocation : NSObject @property (readwrite, nonatomic, copy) BlockInvocationBlock block; @end #pragma mark - Invocation Container: @implementation BlockInvocation @synthesize block; - (id) initWithBlock:(BlockInvocationBlock)aBlock { if ( (self = [super init]) ) { self.block = aBlock; } return self; } + (BlockInvocation *) invocationWithBlock:(BlockInvocationBlock)aBlock { return [[self alloc] initWithBlock:aBlock]; } - (void) performWithTarget:(id)aTarget { self.block(aTarget); } @end #pragma mark Implementation: @implementation NSInvocation (CategoryNSInvocation) #pragma mark - Class Methods: + (NSInvocation *) invocationWithTarget:(id)aTarget block:(void (^)(id target))block { BlockInvocation *blockInvocation = [BlockInvocation invocationWithBlock:block]; NSInvocation *invocation = [NSInvocation invocationWithSelector:@selector(performWithTarget:) andObject:aTarget forTarget:blockInvocation]; [invocation associateValue:blockInvocation withKey:@"BlockInvocation"]; return invocation; } + (NSInvocation *) invocationWithSelector:(SEL)aSelector forTarget:(id)aTarget { NSMethodSignature *aSignature = [aTarget methodSignatureForSelector:aSelector]; NSInvocation *aInvocation = [NSInvocation invocationWithMethodSignature:aSignature]; [aInvocation setTarget:aTarget]; [aInvocation setSelector:aSelector]; return aInvocation; } + (NSInvocation *) invocationWithSelector:(SEL)aSelector andObject:(__autoreleasing id)anObject forTarget:(id)aTarget { NSInvocation *aInvocation = [NSInvocation invocationWithSelector:aSelector forTarget:aTarget]; [aInvocation setArgument:&anObject atIndex:2]; return aInvocation; } @end
Berikut cara menggunakannya:
NSInvocation *invocation = [NSInvocation invocationWithTarget:self block:^(id target) { NSLog(@"TEST"); }]; [invocation invoke];
Anda dapat melakukan banyak hal dengan pemanggilan dan Metode Objective-C standar. Misalnya, Anda dapat menggunakan NSInvocationOperation (initWithInvocation :), NSTimer (scheduleTimerWithTimeInterval: invocation: repeates :)
Intinya adalah mengubah blok Anda menjadi NSInvocation lebih fleksibel dan dapat digunakan sebagai berikut:
NSInvocation *invocation = [NSInvocation invocationWithTarget:self block:^(id target) { NSLog(@"My Block code here"); }]; [button addTarget:invocation action:@selector(invoke) forControlEvents:UIControlEventTouchUpInside];
Sekali lagi ini hanyalah satu saran.
sumber
Sayangnya tidak sesederhana itu.
Secara teori, dimungkinkan untuk mendefinisikan fungsi yang secara dinamis menambahkan metode ke kelas
target
, meminta metode tersebut mengeksekusi isi blok, dan mengembalikan selektor sesuai kebutuhan olehaction
argumen. Fungsi ini dapat menggunakan teknik yang digunakan oleh MABlockClosure , yang, dalam kasus iOS, bergantung pada implementasi kustom libffi, yang masih eksperimental.Anda lebih baik menerapkan tindakan sebagai metode.
sumber
Pustaka BlockKit di Github (juga tersedia sebagai CocoaPod) memiliki fitur bawaan ini.
Lihatlah file header untuk UIControl + BlocksKit.h. Mereka telah menerapkan ide Dave DeLong jadi Anda tidak perlu melakukannya. Beberapa dokumentasi ada di sini .
sumber
Seseorang akan memberi tahu saya mengapa ini salah, mungkin, atau dengan sedikit keberuntungan, mungkin tidak, jadi saya akan belajar sesuatu, atau saya akan membantu.
Saya baru saja melempar ini bersama-sama. Ini sangat mendasar, hanya pembungkus tipis dengan sedikit pengecoran. Sebuah kata peringatan, mengasumsikan blok yang Anda panggil memiliki tanda tangan yang benar untuk mencocokkan pemilih yang Anda gunakan (yaitu jumlah argumen dan jenis).
// // BlockInvocation.h // BlockInvocation // // Created by Chris Corbyn on 3/01/11. // Copyright 2011 __MyCompanyName__. All rights reserved. // #import <Cocoa/Cocoa.h> @interface BlockInvocation : NSObject { void *block; } -(id)initWithBlock:(void *)aBlock; +(BlockInvocation *)invocationWithBlock:(void *)aBlock; -(void)perform; -(void)performWithObject:(id)anObject; -(void)performWithObject:(id)anObject object:(id)anotherObject; @end
Dan
// // BlockInvocation.m // BlockInvocation // // Created by Chris Corbyn on 3/01/11. // Copyright 2011 __MyCompanyName__. All rights reserved. // #import "BlockInvocation.h" @implementation BlockInvocation -(id)initWithBlock:(void *)aBlock { if (self = [self init]) { block = (void *)[(void (^)(void))aBlock copy]; } return self; } +(BlockInvocation *)invocationWithBlock:(void *)aBlock { return [[[self alloc] initWithBlock:aBlock] autorelease]; } -(void)perform { ((void (^)(void))block)(); } -(void)performWithObject:(id)anObject { ((void (^)(id arg1))block)(anObject); } -(void)performWithObject:(id)anObject object:(id)anotherObject { ((void (^)(id arg1, id arg2))block)(anObject, anotherObject); } -(void)dealloc { [(void (^)(void))block release]; [super dealloc]; } @end
Benar-benar tidak ada hal ajaib yang terjadi. Hanya banyak downcasting
void *
dan typecasting ke tanda tangan blok yang dapat digunakan sebelum menjalankan metode ini. Jelas (seperti denganperformSelector:
dan metode terkait, kemungkinan kombinasi input terbatas, tetapi dapat diperpanjang jika Anda memodifikasi kode.Digunakan seperti ini:
BlockInvocation *invocation = [BlockInvocation invocationWithBlock:^(NSString *str) { NSLog(@"Block was invoked with str = %@", str); }]; [invocation performWithObject:@"Test"];
Ini menghasilkan:
Digunakan dalam skenario tindakan target, Anda hanya perlu melakukan sesuatu seperti ini:
BlockInvocation *invocation = [[BlockInvocation alloc] initWithBlock:^(id sender) { NSLog(@"Button with title %@ was clicked", [(NSButton *)sender title]); }]; [myButton setTarget:invocation]; [myButton setAction:@selector(performWithObject:)];
Karena target dalam sistem target-action tidak dipertahankan, Anda perlu memastikan objek pemanggilan hidup selama kontrol itu sendiri berjalan.
Saya tertarik untuk mendengar apa pun dari seseorang yang lebih ahli daripada saya.
sumber
invocation
tidak pernah dirilisSaya perlu memiliki tindakan yang terkait dengan UIButton dalam UITableViewCell. Saya ingin menghindari penggunaan tag untuk melacak setiap tombol di setiap sel yang berbeda. Saya pikir cara paling langsung untuk mencapai ini adalah dengan mengaitkan "aksi" blok ke tombol seperti ini:
[cell.trashButton addTarget:self withActionBlock:^{ NSLog(@"Will remove item #%d from cart!", indexPath.row); ... } forControlEvent:UIControlEventTouchUpInside];
Implementasi saya sedikit lebih sederhana, berkat @bbum untuk menyebutkan
imp_implementationWithBlock
danclass_addMethod
, (meskipun tidak diuji secara ekstensif):#import <objc/runtime.h> @implementation UIButton (ActionBlock) static int _methodIndex = 0; - (void)addTarget:(id)target withActionBlock:(ActionBlock)block forControlEvent:(UIControlEvents)controlEvents{ if (!target) return; NSString *methodName = [NSString stringWithFormat:@"_blockMethod%d", _methodIndex]; SEL newMethodName = sel_registerName([methodName UTF8String]); IMP implementedMethod = imp_implementationWithBlock(block); BOOL success = class_addMethod([target class], newMethodName, implementedMethod, "v@:"); NSLog(@"Method with block was %@", success ? @"added." : @"not added." ); if (!success) return; [self addTarget:target action:newMethodName forControlEvents:controlEvents]; // On to the next method name... ++_methodIndex; } @end
sumber
Tidakkah bekerja memiliki NSBlockOperation (iOS SDK +5). Kode ini menggunakan ARC dan ini adalah penyederhanaan dari Aplikasi yang saya gunakan untuk menguji ini (tampaknya berfungsi, setidaknya tampaknya, tidak yakin apakah saya membocorkan memori).
NSBlockOperation *blockOp; UIView *testView; -(void) createTestView{ UIView *testView = [[UIView alloc] initWithFrame:CGRectMake(0, 60, 1024, 688)]; testView.backgroundColor = [UIColor blueColor]; [self.view addSubview:testView]; UIButton *btnBack = [UIButton buttonWithType:UIButtonTypeRoundedRect]; [btnBack setFrame:CGRectMake(200, 200, 200, 70)]; [btnBack.titleLabel setText:@"Back"]; [testView addSubview:btnBack]; blockOp = [NSBlockOperation blockOperationWithBlock:^{ [testView removeFromSuperview]; }]; [btnBack addTarget:blockOp action:@selector(start) forControlEvents:UIControlEventTouchUpInside]; }
Tentu saja, saya tidak yakin seberapa bagus ini untuk penggunaan nyata. Anda perlu menjaga referensi ke NSBlockOperation tetap hidup atau menurut saya ARC akan membunuhnya.
sumber