Bisakah saya menggunakan blok Objective-C sebagai properti?

321

Apakah mungkin untuk memiliki blok sebagai properti menggunakan sintaks properti standar?

Apakah ada perubahan untuk ARC ?

gurghet
sumber
1
Yah, karena itu akan sangat berguna. Saya tidak perlu tahu apa itu asalkan saya memiliki sintaks yang benar dan berperilaku seperti NSObject.
gurghet
5
Jika Anda tidak tahu apa itu, bagaimana Anda tahu itu akan sangat berguna?
Stephen Canon
5
Anda tidak boleh menggunakannya Jika Anda tidak tahu apa itu :)
Richard J. Ross III
5
@ Moshe di sini adalah beberapa alasan yang terlintas dalam pikiran. Blok lebih mudah diimplementasikan daripada kelas delegasi penuh, blok ringan, dan Anda memiliki akses ke variabel yang ada dalam konteks blok itu. Event Callback dapat dilakukan secara efektif menggunakan blok (cocos2d menggunakannya hampir secara eksklusif).
Richard J. Ross III
2
Tidak sepenuhnya terkait, tetapi karena beberapa komentar mengeluhkan sintaks blok "jelek", berikut adalah artikel hebat yang mendapatkan sintaksis dari prinsip pertama: nilsou.com/blog/2013/08/21/objective-c-blocks-syntax
paulrehkugler

Jawaban:

305
@property (nonatomic, copy) void (^simpleBlock)(void);
@property (nonatomic, copy) BOOL (^blockWithParamter)(NSString *input);

Jika Anda akan mengulangi blok yang sama di beberapa tempat menggunakan tipe def

typedef void(^MyCompletionBlock)(BOOL success, NSError *error);
@property (nonatomic) MyCompletionBlock completion;
Robert
sumber
3
Dengan xCode 4.4 atau lebih baru Anda tidak perlu mensintesis. Itu akan membuatnya lebih ringkas. Apple Doc
Eric
wow, saya tidak tahu itu, terima kasih! ... Meskipun saya sering melakukannya@synthesize myProp = _myProp
Robert
7
@ Robert: Anda beruntung lagi, karena tanpa meletakkan @synthesizedefault adalah apa yang Anda lakukan @synthesize name = _name; stackoverflow.com/a/12119360/1052616
Eric
1
@CharlieMonroe - Ya Anda mungkin benar, tetapi tidakkah Anda memerlukan implementasi dealloc untuk nil atau melepaskan properti blok tanpa ARC? (sudah lama sejak saya menggunakan non-ARC)
Robert
1
@impcaptor: Ya, ini dapat menyebabkan kebocoran memori jika Anda tidak melepaskannya di dealloc - sama seperti variabel lainnya.
Charlie Monroe
210

Berikut adalah contoh bagaimana Anda akan menyelesaikan tugas seperti itu:

#import <Foundation/Foundation.h>
typedef int (^IntBlock)();

@interface myobj : NSObject
{
    IntBlock compare;
}

@property(readwrite, copy) IntBlock compare;

@end

@implementation myobj

@synthesize compare;

- (void)dealloc 
{
   // need to release the block since the property was declared copy. (for heap
   // allocated blocks this prevents a potential leak, for compiler-optimized 
   // stack blocks it is a no-op)
   // Note that for ARC, this is unnecessary, as with all properties, the memory management is handled for you.
   [compare release];
   [super dealloc];
}
@end

int main () {
    @autoreleasepool {
        myobj *ob = [[myobj alloc] init];
        ob.compare = ^
        {
            return rand();
        };
        NSLog(@"%i", ob.compare());
        // if not ARC
        [ob release];
    }

    return 0;
}

Sekarang, satu-satunya hal yang perlu diubah jika Anda perlu mengubah jenis perbandingan adalah typedef int (^IntBlock)(). Jika Anda perlu mengirimkan dua objek ke sana, ubah ke ini:, typedef int (^IntBlock)(id, id)dan ubah blok Anda ke:

^ (id obj1, id obj2)
{
    return rand();
};

Saya harap ini membantu.

EDIT 12 Maret 2012:

Untuk ARC, tidak ada perubahan khusus yang diperlukan, karena ARC akan mengelola blok untuk Anda selama mereka didefinisikan sebagai salinan. Anda tidak perlu mengatur properti ke nol di destructor Anda, baik.

Untuk bacaan lebih lanjut, silakan periksa dokumen ini: http://clang.llvm.org/docs/AutomaticReferenceCounting.html

Richard J. Ross III
sumber
158

Untuk Swift, cukup gunakan penutupan: contoh.


Dalam Objective-C:

@ properti (copy) batal

@property (copy)void (^doStuff)(void);

Sesederhana itu.

Berikut adalah dokumentasi Apple yang sebenarnya, yang menyatakan dengan tepat apa yang harus digunakan:

Apple doco.

Dalam file .h Anda:

// Here is a block as a property:
//
// Someone passes you a block. You "hold on to it",
// while you do other stuff. Later, you use the block.
//
// The property 'doStuff' will hold the incoming block.

@property (copy)void (^doStuff)(void);

// Here's a method in your class.
// When someone CALLS this method, they PASS IN a block of code,
// which they want to be performed after the method is finished.

-(void)doSomethingAndThenDoThis:(void(^)(void))pleaseDoMeLater;

// We will hold on to that block of code in "doStuff".

Ini file .m Anda:

 -(void)doSomethingAndThenDoThis:(void(^)(void))pleaseDoMeLater
    {
    // Regarding the incoming block of code, save it for later:
    self.doStuff = pleaseDoMeLater;

    // Now do other processing, which could follow various paths,
    // involve delays, and so on. Then after everything:
    [self _alldone];
    }

-(void)_alldone
    {
    NSLog(@"Processing finished, running the completion block.");
    // Here's how to run the block:
    if ( self.doStuff != nil )
       self.doStuff();
    }

Waspadai kode contoh yang kedaluwarsa.

Dengan sistem modern (2014+), lakukan apa yang ditunjukkan di sini. Sesederhana itu.

Fattie
sumber
Mungkin Anda juga harus mengatakan, bahwa sekarang (2016) boleh digunakan strongbukan copy?
Nik Kov
Bisakah Anda menjelaskan mengapa properti tidak nonatomicseperti praktik terbaik untuk sebagian besar kasus lainnya menggunakan properti?
Alex Pretzlav
WorkingwithBlocks.html dari Apple "Anda harus menentukan salinan sebagai atribut properti, karena ..."
Fattie
20

Demi anak cucu / kelengkapan ... Berikut adalah dua contoh LENGKAP tentang bagaimana menerapkan "cara melakukan sesuatu" yang sangat fleksibel dan serba bisa ini. @ Robert menjawab dengan singkat dan benar, tetapi di sini saya juga ingin menunjukkan cara untuk benar-benar "mendefinisikan" blok.

@interface       ReusableClass : NSObject
@property (nonatomic,copy) CALayer*(^layerFromArray)(NSArray*);
@end

@implementation  ResusableClass
static  NSString const * privateScope = @"Touch my monkey.";

- (CALayer*(^)(NSArray*)) layerFromArray { 
     return ^CALayer*(NSArray* array){
        CALayer *returnLayer = CALayer.layer
        for (id thing in array) {
            [returnLayer doSomethingCrazy];
            [returnLayer setValue:privateScope
                         forKey:@"anticsAndShenanigans"];
        }
        return list;
    };
}
@end

Bodoh? Iya. Berguna? Hells yeah. Berikut adalah cara pengaturan properti yang lebih "atomik" dan kelas yang sangat berguna ...

@interface      CALayoutDelegator : NSObject
@property (nonatomic,strong) void(^layoutBlock)(CALayer*);
@end

@implementation CALayoutDelegator
- (id) init { 
   return self = super.init ? 
         [self setLayoutBlock: ^(CALayer*layer){
          for (CALayer* sub in layer.sublayers)
            [sub someDefaultLayoutRoutine];
         }], self : nil;
}
- (void) layoutSublayersOfLayer:(CALayer*)layer {
   self.layoutBlock ? self.layoutBlock(layer) : nil;
}   
@end

Ini menggambarkan pengaturan properti blok melalui accessor (meskipun di dalam init, sebuah praktik yang tidak pasti.) Vs contoh pertama mekanisme "nonatomik" "pengambil". Dalam kedua kasus ... implementasi "hardcoded" selalu dapat ditimpa, misalnya .. a l ..

CALayoutDelegator *littleHelper = CALayoutDelegator.new;
littleHelper.layoutBlock = ^(CALayer*layer){
  [layer.sublayers do:^(id sub){ [sub somethingElseEntirely]; }];
};
someLayer.layoutManager = littleHelper;

Juga .. jika Anda ingin menambahkan properti blok dalam suatu kategori ... katakanlah Anda ingin menggunakan Blok alih-alih beberapa "aksi" target / aksi sekolah lama ... Anda bisa menggunakan nilai yang terkait dengannya, well .. kaitkan blok.

typedef    void(^NSControlActionBlock)(NSControl*); 
@interface       NSControl            (ActionBlocks)
@property (copy) NSControlActionBlock  actionBlock;    @end
@implementation  NSControl            (ActionBlocks)

- (NSControlActionBlock) actionBlock { 
    // use the "getter" method's selector to store/retrieve the block!
    return  objc_getAssociatedObject(self, _cmd); 
} 
- (void) setActionBlock:(NSControlActionBlock)ab {

    objc_setAssociatedObject( // save (copy) the block associatively, as categories can't synthesize Ivars.
    self, @selector(actionBlock),ab ,OBJC_ASSOCIATION_COPY);
    self.target = self;                  // set self as target (where you call the block)
    self.action = @selector(doItYourself); // this is where it's called.
}
- (void) doItYourself {

    if (self.actionBlock && self.target == self) self.actionBlock(self);
}
@end

Sekarang, ketika Anda membuat tombol, Anda tidak perlu mengatur beberapa IBActiondrama .. Asosiasikan saja pekerjaan yang harus dilakukan saat penciptaan ...

_button.actionBlock = ^(NSControl*thisButton){ 

     [doc open]; [thisButton setEnabled:NO]; 
};

Pola ini dapat diterapkan LEBIH dan LEBIH ke API Kakao. Gunakan properti untuk membawa bagian-bagian yang relevan dari kode Anda lebih dekat bersama-sama , menghilangkan paradigma delegasi berbelit-belit , dan leverage kekuatan benda-benda di luar itu hanya bertindak sebagai "wadah" bodoh.

Alex Gray
sumber
Alex, contoh yang bagus. Anda tahu, saya bertanya-tanya tentang nonatomik. Pikiran?
Fattie
2
Sangat jarang bahwa "atom" akan menjadi hal yang benar untuk dilakukan pada sebuah properti. Ini akan menjadi hal yang sangat aneh untuk mengatur properti blok di satu utas dan membacanya di utas lainnya secara bersamaan , atau untuk mengatur properti blok secara simultan dari beberapa utas. Jadi biaya "atomik" vs "nonatomik" tidak memberi Anda keuntungan nyata.
gnasher729
8

Tentu saja Anda bisa menggunakan blok sebagai properti. Tetapi pastikan mereka dinyatakan sebagai @ properti (copy) . Sebagai contoh:

typedef void(^TestBlock)(void);

@interface SecondViewController : UIViewController
@property (nonatomic, copy) TestBlock block;
@end

Dalam MRC, blok yang menangkap variabel konteks dialokasikan dalam tumpukan ; mereka akan dirilis ketika bingkai tumpukan dihancurkan. Jika mereka disalin, blok baru akan dialokasikan di heap , yang dapat dieksekusi nanti setelah bingkai tumpukan poped.

Mindy
sumber
Persis. Berikut ini adalah dokumen Apple yang sebenarnya tentang mengapa Anda harus menggunakan salinan dan tidak ada yang lain. developer.apple.com/library/ios/documentation/cocoa/conceptual/…
Fattie
7

Disclamer

Ini tidak dimaksudkan sebagai "jawaban yang baik", karena pertanyaan ini diajukan secara eksplisit untuk ObjectiveC. Ketika Apple memperkenalkan Swift di WWDC14, saya ingin berbagi cara berbeda untuk menggunakan blok (atau penutupan) di Swift.

Halo, Swift

Anda memiliki banyak cara yang ditawarkan untuk melewati blok yang setara dengan fungsi di Swift.

Saya menemukan tiga.

Untuk memahami ini, saya sarankan Anda untuk menguji di bagian kecil ini kode.

func test(function:String -> String) -> String
{
    return function("test")
}

func funcStyle(s:String) -> String
{
    return "FUNC__" + s + "__FUNC"
}
let resultFunc = test(funcStyle)

let blockStyle:(String) -> String = {s in return "BLOCK__" + s + "__BLOCK"}
let resultBlock = test(blockStyle)

let resultAnon = test({(s:String) -> String in return "ANON_" + s + "__ANON" })


println(resultFunc)
println(resultBlock)
println(resultAnon)

Cepat, dioptimalkan untuk penutupan

Karena Swift dioptimalkan untuk pengembangan asinkron, Apple bekerja lebih banyak pada penutupan. Yang pertama adalah fungsi signature dapat disimpulkan sehingga Anda tidak perlu menulis ulang.

Akses params dengan angka

let resultShortAnon = test({return "ANON_" + $0 + "__ANON" })

Params menyimpulkan dengan memberi nama

let resultShortAnon2 = test({myParam in return "ANON_" + myParam + "__ANON" })

Penutupan Trailing

Kasus khusus ini hanya berfungsi jika blok adalah argumen terakhir, itu disebut trailing closure

Berikut ini sebuah contoh (digabung dengan tanda tangan yang disimpulkan untuk menunjukkan kekuatan Swift)

let resultTrailingClosure = test { return "TRAILCLOS_" + $0 + "__TRAILCLOS" }

Akhirnya:

Menggunakan semua kekuatan ini apa yang saya lakukan adalah mencampur trailing closure dan ketik inferensi (dengan penamaan agar mudah dibaca)

PFFacebookUtils.logInWithPermissions(permissions) {
    user, error in
    if (!user) {
        println("Uh oh. The user cancelled the Facebook login.")
    } else if (user.isNew) {
        println("User signed up and logged in through Facebook!")
    } else {
        println("User logged in through Facebook!")
    }
}
Francescu
sumber
0

Halo, Swift

Melengkapi apa yang dijawab @Francescu.

Menambahkan parameter ekstra:

func test(function:String -> String, param1:String, param2:String) -> String
{
    return function("test"+param1 + param2)
}

func funcStyle(s:String) -> String
{
    return "FUNC__" + s + "__FUNC"
}
let resultFunc = test(funcStyle, "parameter 1", "parameter 2")

let blockStyle:(String) -> String = {s in return "BLOCK__" + s + "__BLOCK"}
let resultBlock = test(blockStyle, "parameter 1", "parameter 2")

let resultAnon = test({(s:String) -> String in return "ANON_" + s + "__ANON" }, "parameter 1", "parameter 2")


println(resultFunc)
println(resultBlock)
println(resultAnon)
Gil Beyruth
sumber
-3

Anda dapat mengikuti format di bawah ini dan dapat menggunakan testingObjectiveCBlockproperti di kelas.

typedef void (^testingObjectiveCBlock)(NSString *errorMsg);

@interface MyClass : NSObject
@property (nonatomic, strong) testingObjectiveCBlock testingObjectiveCBlock;
@end

Untuk info lebih lanjut lihat di sini

Sujith Thankachan
sumber
2
Apakah jawaban ini benar-benar menambahkan sesuatu yang lebih ke jawaban lain yang sudah disediakan?
Richard J. Ross III