Чи можна використовувати блоки Objective-C як властивості?


321

Чи можливо мати блоки як властивості, використовуючи стандартний синтаксис властивості?

Чи є якісь зміни для ARC ?


1
Ну, бо це було б дуже корисно. Мені не потрібно було б знати, що це таке, якщо у мене є синтаксис право, і він поводиться як NSObject.
— гургет

5
Якщо ви не знаєте, що це таке, як ви знаєте, що це було б дуже зручно?
— Стівен Канон

5
Ви не повинні їх використовувати, якщо ви не знаєте, що вони є :)
— Річард Дж. Росс III

5
@Moshe Ось деякі причини, які приходять в голову. Блоки реалізуються легше, ніж повний клас делегатів, блоки легкі, і у вас є доступ до змінних, які знаходяться в контексті цього блоку. Зворотні виклики подій можна зробити ефективно за допомогою блоків (cocos2d використовує їх майже виключно).
— Річард Дж. Росс III

2
Не повністю пов’язані, але оскільки деякі коментарі скаржаться на "потворний" блок-синтаксис, ось чудова стаття, яка виводить синтаксис із перших принципів: nilsou.com/blog/2013/08/21/objective-c-blocks-syntax
— paulrehkugler

Відповіді:


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

Якщо ви будете повторювати один і той же блок в декількох місцях, використовуйте тип def

typedef void(^MyCompletionBlock)(BOOL success, NSError *error);
@property (nonatomic) MyCompletionBlock completion;

3
З xCode 4.4 або новішою версією вам не потрібно синтезувати. Це зробить його ще більш стислим. Apple Doc
— Ерік

Нічого, я цього не знав, дякую! ... Хоча я часто це роблю@synthesize myProp = _myProp
— Роберт

7
@Robert: Вам знову пощастило, адже без встановлення @synthesizeза замовчуванням це те, що ви робите @synthesize name = _name; stackoverflow.com/a/12119360/1052616
— Ерік

1
@CharlieMonroe - Так, напевно, ти маєш рацію, але чи не потрібна реалізація угоди для скасування чи звільнення властивості блоку без ARC? (минув час, оскільки я використовував не-ARC)
— Роберт,

1
@imcaptor: Так, це може спричинити протікання пам'яті у випадку, якщо ви не випустите її у взаємодії - як і у будь-якій іншій змінній.
— Чарлі Монро

210

Ось приклад того, як ви могли б виконати таке завдання:

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

Тепер, єдине, що потрібно змінити, якщо вам потрібно змінити тип порівняння, було б typedef int (^IntBlock)(). Якщо вам потрібно передати йому два об'єкти, змініть це на це: typedef int (^IntBlock)(id, id)та змініть свій блок на:

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

Я сподіваюся, що це допомагає.

EDIT 12 березня 2012 року:

Для ARC не потрібні конкретні зміни, оскільки ARC буде керувати блоками для вас до тих пір, поки вони будуть визначені як копія. Вам також не потрібно встановлювати властивість на нуль у вашому деструкторі.

Для більш детального ознайомлення перегляньте цей документ: http://clang.llvm.org/docs/AutomaticReferenceCounting.html


158

Для Swift просто використовуйте закриття: приклад.


В Objective-C:

@ властивість (копія) недійсна

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

Це так просто.

Ось фактична документація Apple, де чітко зазначено, що використовувати:

Apple doco.

У вашому .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".

Ось ваш .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();
    }

Остерігайтеся застарілого прикладу коду.

З сучасними (2014+) системами виконайте те, що тут показано. Це так просто.


Можливо, ви також повинні сказати, що зараз (2016) це нормально використовувати strongзамість copy?
— Нік Ков

Чи можете ви пояснити, чому властивість не має nonatomicвідрізнятися від найкращих практик для більшості інших випадків використання властивостей?
— Олексій Прецлав

WorkingwithBlocks.html від Apple "Ви повинні вказати копію як атрибут властивості, тому що ..."
— Fattie

20

Для нащадків / повноти… Ось два ПОВНИХ приклади того, як реалізувати цей смішно універсальний «спосіб робити речі». @ Відповідь Роберта блаженно стисла і правильна, але тут я хочу також показати способи фактично "визначити" блоки.

@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

Нерозумно? Так. Корисно? Пекло так. Ось інший, "більш атомний" спосіб встановлення властивості .. і клас, який смішно корисний ...

@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

Це ілюструє встановлення властивості блоку через аксесуар (хоч і всередині init, спірний практичний процес ..) проти механізму "неатомічного" "отримання" першого прикладу. У будь-якому випадку ... "твердо кодовані" реалізації завжди можуть бути перезаписані, наприклад, a lá ..

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

Також .. якщо ви хочете додати властивість блоку в категорію ... скажіть, що ви хочете використовувати блок замість якоїсь старої школи цілі / дії "дії" ... Ви можете просто використовувати пов'язані значення, ну .. пов'язати блоки.

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

Тепер, коли ви робите кнопку, вам не доведеться налаштовувати якусь IBActionдраму. Просто пов'язуйте роботу, яку потрібно виконати при створенні ...

_button.actionBlock = ^(NSControl*thisButton){ 

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

Цю схему можна застосувати НАДОЛЬШО та НАДЕЖ до API какао. Використовуйте властивість , щоб принести відповідні частини коди ближче один до одного , усунути заплутані парадигми делегування і важелі влади об'єктів за що просто діють як німі «контейнери».


Алекс, чудовий асоційований приклад. Знаєте, мені цікаво про неатомічний. Думки?
— Fattie

2
Дуже рідко, щоб "атомний" був би правильним для власності. Було б дуже дивно встановити властивість блоку в одному потоці і прочитати його в інший потік одночасно або встановити властивість блоку одночасно з декількох потоків. Тож вартість "атомних" проти "неатомних" не дає вам реальних переваг.
— gnasher729

8

Звичайно, ви можете використовувати блоки як властивості. Але переконайтеся, що вони оголошені як @property (копія) . Наприклад:

typedef void(^TestBlock)(void);

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

У MRC блоки, що фіксують змінні контексту, розподіляються в стек ; вони будуть випущені, коли рамка стека буде знищена. Якщо вони будуть скопійовані, в купі буде виділено новий блок , який можна буде виконати пізніше після того, як кадр стека буде пропущений.


Саме так. Ось фактичний doco Apple про те, чому саме вам слід використовувати копію та більше нічого. developer.apple.com/library/ios/documentation/cocoa/conceptual/…
— Fattie

7

Відмова від відповідальності

Це не є «хорошою відповіддю», оскільки це питання явно задають для ObjectiveC. Коли Apple представила Swift на WWDC14, я хотів би поділитися різними способами використання блоку (або закриття) у Swift.

Привіт, Свіфт

Вам пропонується багато способів передачі блоку, еквівалентного функції в Swift.

Я знайшов трьох.

Щоб зрозуміти це, я пропоную вам протестувати на майданчику цей маленький фрагмент коду.

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)

Швидкий, оптимізований для закриття

Оскільки Swift оптимізований для асинхронного розвитку, Apple більше працювала над закриттями. Перший полягає в тому, що підпис функції можна зробити, щоб вам не довелося переписувати її.

Доступ парами за номерами

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

Параметри, що виводяться з імен

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

Замикання на заднім ході

Цей особливий випадок працює лише в тому випадку, якщо блок є останнім аргументом, він називається трейлінг закриття

Ось приклад (об'єднаний з підписаним підписом, щоб показати потужність Swift)

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

Нарешті:

Використовуючи всю цю силу, я б міг змішати висновок про закриття та висновок типу (з називанням для читабельності)

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!")
    }
}

0

Привіт, Свіфт

Доповнення, що відповів @Francescu.

Додавання додаткових параметрів:

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)

-3

Ви можете дотримуватися наведеного нижче формату і можете використовувати testingObjectiveCBlockвластивість у класі.

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

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

Для отримання додаткової інформації дивіться тут


2
Чи справді ця відповідь додає щось більше до інших вже відповідей?
— Річард Дж. Росс III
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.