Posso usar blocos de Objective-C como propriedades?

321

É possível ter blocos como propriedades usando a sintaxe da propriedade padrão?

Existem alterações para o ARC ?

gurghet
fonte
1
Bem, porque seria muito útil. Eu não precisaria saber o que é, desde que eu tenha a sintaxe correta e se comporte como um NSObject.
gurghet
5
Se você não sabe o que é, como você sabe que seria muito útil?
Stephen Canon
5
Você não deve usá-los se você não sabe o que eles são :)
Richard J. Ross III
5
@ Moshe, aqui estão algumas razões que vêm à mente. Os blocos são mais fáceis de implementar do que uma classe delegada completa, os blocos são leves e você tem acesso a variáveis ​​que estão no contexto desse bloco. Os retornos de chamada de eventos podem ser feitos efetivamente usando blocos (o cocos2d os utiliza quase que exclusivamente).
Richard J. Ross III
2
Não está completamente relacionado, mas como alguns dos comentários se queixam da sintaxe de bloco "feia", aqui está um ótimo artigo que deriva a sintaxe dos primeiros princípios: nilsou.com/blog/2013/08/08/21/objective-c-blocks-syntax
Paulrehkugler # 24/13

Respostas:

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

Se você repetir o mesmo bloco em vários lugares, use um tipo def

typedef void(^MyCompletionBlock)(BOOL success, NSError *error);
@property (nonatomic) MyCompletionBlock completion;
Robert
fonte
3
Com o xCode 4.4 ou mais recente, você não precisa sintetizar. Isso tornará ainda mais conciso. Apple Doc
Eric
uau, eu não sabia disso, obrigado! ... Embora eu muitas vezes fazem@synthesize myProp = _myProp
Robert
7
@Robert: Você está com sorte novamente, porque sem colocar @synthesizeo padrão é o que você está fazendo @synthesize name = _name; stackoverflow.com/a/12119360/1052616
Eric
1
@CharlieMonroe - Sim, você provavelmente está certo, mas não precisa de uma implementação desalocada para nada ou liberar a propriedade do bloco sem o ARC? (sua sido um tempo desde que eu usei não-ARC)
Robert
1
@ imcaptor: Sim, pode causar vazamento de memória caso você não a libere em desalocação - assim como qualquer outra variável.
Charlie Monroe
210

Aqui está um exemplo de como você realizaria essa tarefa:

#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;
}

Agora, a única coisa que precisaria ser alterada se você precisasse alterar o tipo de comparação seria o typedef int (^IntBlock)(). Se você precisar passar dois objetos para ele, mude para:: typedef int (^IntBlock)(id, id)e altere seu bloco para:

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

Eu espero que isso ajude.

EDIT 12 de março de 2012:

Para o ARC, não são necessárias alterações específicas, pois o ARC gerenciará os blocos para você, desde que sejam definidos como cópia. Também não é necessário definir a propriedade nula no seu destruidor.

Para mais informações, consulte este documento: http://clang.llvm.org/docs/AutomaticReferenceCounting.html

Richard J. Ross III
fonte
158

Para Swift, basta usar fechamentos: exemplo.


No Objetivo-C:

@property (cópia) void

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

É simples assim.

Aqui está a documentação real da Apple, que indica exatamente o que usar:

Doco da Apple.

No seu arquivo .h:

// 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".

Aqui está o seu arquivo .m:

 -(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();
    }

Cuidado com o código de exemplo desatualizado.

Com sistemas modernos (2014 ou superior), faça o que é mostrado aqui. É simples assim.

Fattie
fonte
Talvez você deva também dizer que agora (2016) está tudo bem em strongvez de usar copy?
Nik Kov
Você pode explicar por que a propriedade não deve ser nonatomicdiferente das práticas recomendadas para a maioria dos outros casos usando propriedades?
precisa saber é o seguinte
WorkingwithBlocks.html da Apple "Você deve especificar cópia como atributo da propriedade, porque ..."
Fattie 15/11/16
20

Por uma questão de posteridade / completude… Aqui estão dois exemplos COMPLETOS de como implementar essa "maneira de fazer as coisas" ridiculamente versátil. A resposta de Robert é alegremente concisa e correta, mas aqui também quero mostrar maneiras de realmente "definir" os blocos.

@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

Boba? Sim. Útil?Infernos, sim. Aqui está uma maneira diferente e "mais atômica" de definir a propriedade ... e uma classe que é ridiculamente útil…

@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

Isso ilustra a configuração da propriedade do bloco por meio do acessador (embora dentro de init, uma prática discutivelmente arriscada ..) versus o mecanismo "não-atômico" "getter" do primeiro exemplo. Em ambos os casos ... as implementações "codificadas" sempre podem ser substituídas, por exemplo .. a lá ..

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

Além disso ... se você deseja adicionar uma propriedade de bloco em uma categoria ... diga que deseja usar um bloco em vez de uma ação / ação antiga de ação / ação ... Você pode apenas usar valores associados para, bem .. associe os blocos.

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

Agora, quando você aperta um botão, não precisa criar um IBActiondrama. Basta associar o trabalho a ser feito na criação ...

_button.actionBlock = ^(NSControl*thisButton){ 

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

Esse padrão pode ser aplicado OVER e OVER às APIs do cacau. Usar propriedades para trazer as partes relevantes do seu código mais juntos , eliminar paradigmas delegação complicadas , e alavancar o poder de objetos além do que apenas agindo como "recipientes" mudos.

Alex Gray
fonte
Alex, ótimo exemplo associado. Você sabe, eu estou pensando sobre o não atômico. Pensamentos?
Gordo
2
É muito raro que "atômico" seja a coisa certa a fazer para uma propriedade. Seria muito estranho definir uma propriedade de bloco em um segmento e lê-lo em outro segmento ao mesmo tempo , ou definir a propriedade do bloco simultaneamente a partir de vários segmentos. Portanto, o custo de "atômico" vs. "não atômico" não oferece vantagens reais.
gnasher729
8

Claro que você pode usar blocos como propriedades. Mas verifique se eles são declarados como @property (cópia) . Por exemplo:

typedef void(^TestBlock)(void);

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

No MRC, os blocos que capturam variáveis ​​de contexto são alocados na pilha ; eles serão liberados quando o quadro da pilha for destruído. Se eles forem copiados, um novo bloco será alocado no heap , que poderá ser executado posteriormente depois que o quadro da pilha for exibido.

Mindy
fonte
Exatamente. Aqui está o documento real da Apple sobre exatamente por que você deve usar a cópia e nada mais. developer.apple.com/library/ios/documentation/cocoa/conceptual/…
Fattie
7

Disclamer

Isso não pretende ser "a boa resposta", pois esta pergunta solicita explicitamente o ObjectiveC. Quando a Apple introduziu o Swift na WWDC14, eu gostaria de compartilhar as diferentes maneiras de usar blocos (ou fechamentos) no Swift.

Olá, Swift

Você tem várias maneiras oferecidas para passar um bloco equivalente à função no Swift.

Eu encontrei três.

Para entender isso, sugiro que você teste no playground esse pequeno pedaço de código.

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)

Rápido, otimizado para fechamentos

Como o Swift é otimizado para desenvolvimento assíncrono, a Apple trabalhou mais em fechamentos. A primeira é que a assinatura da função pode ser inferida para que você não precise reescrevê-la.

Acessar parâmetros por números

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

Inferência de parâmetros com nomeação

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

Trailing Closure

Esse caso especial funciona apenas se o bloco for o último argumento, chamado de fechamento final

Aqui está um exemplo (mesclado com assinatura inferida para mostrar o poder do Swift)

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

Finalmente:

Usando todo esse poder, o que eu faria é misturar o fechamento à direita e a inferência de tipo (com nomeação para facilitar a leitura)

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
fonte
0

Olá, Swift

Complementando o que @Francescu respondeu.

Adicionando parâmetros extras:

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
fonte
-3

Você pode seguir o formato abaixo e pode usar a testingObjectiveCBlockpropriedade na classe

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

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

Para mais informações, dê uma olhada aqui

Sujith Thankachan
fonte
2
Essa resposta realmente acrescenta mais alguma coisa às outras respostas já fornecidas?
Richard J. Ross III