Чи можна використовувати блоки 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.