Могу ли я использовать блоки Objective-C в качестве свойств?

Можно ли иметь блоки как свойства, используя стандартный синтаксис свойств?

Есть ли какие-либо изменения для ARC?


person gurghet    schedule 14.10.2010    source источник
comment
Ну потому что это очень пригодилось бы. Мне не нужно было бы знать, что это такое, если у меня правильный синтаксис и он ведет себя как NSObject.   -  person gurghet    schedule 14.10.2010
comment
Если вы не знаете, что это такое, откуда вы знаете, что это будет очень удобно?   -  person Stephen Canon    schedule 14.10.2010
comment
@Stephen Потому что я их много использую :)   -  person gurghet    schedule 14.10.2010
comment
Не стоит их использовать, если не знаешь, что это такое :)   -  person Richard J. Ross III    schedule 14.10.2010
comment
Зачем тебе это нужно?   -  person Moshe    schedule 03.02.2012
comment
@Moshe, вот несколько причин, которые приходят на ум. Блоки легче реализовать, чем полный класс делегата, блоки легкие, и у вас есть доступ к переменным, которые находятся в контексте этого блока. Обратные вызовы событий могут быть эффективно выполнены с помощью блоков (cocos2d использует их почти исключительно).   -  person Richard J. Ross III    schedule 04.02.2012
comment
Не полностью связано, но поскольку некоторые комментарии жалуются на уродливый синтаксис блока, вот отличная статья, которая выводит синтаксис из первых принципов: nilsou.com/blog/2013/08/21/objective-c-blocks-syntax   -  person paulrehkugler    schedule 24.09.2013
comment
Чисто для записи, этот вопрос очень старый, и некоторые люди спрашивали, зачем использовать блоки в качестве свойств? Думаю, сейчас он очень часто используется Apple; это обычное дело. Кстати, отличная ссылка, Пол спасибо.   -  person Fattie    schedule 15.04.2014


Ответы (8)


@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;
person Robert    schedule 23.10.2012
comment
С xCode 4.4 или новее вам не нужно синтезировать. Это сделает его еще более кратким. Apple Doc - person Eric; 08.11.2012
comment
вау, я этого не знал, спасибо! ... Хотя я часто делаю @synthesize myProp = _myProp - person Robert; 08.11.2012
comment
@Robert: Вам снова повезло, потому что без указания @synthesize по умолчанию используется то, что вы делаете @synthesize name = _name; stackoverflow.com/a/12119360/1052616 - person Eric; 12.11.2012
comment
ARC не является условием для приведенного выше примера. Прекрасно работает и с кодом, отличным от ARC. - person Charlie Monroe; 01.04.2013
comment
@CharlieMonroe - Да, вероятно, вы правы, но разве вам не нужна реализация dealloc до nil или освобождение свойства блока без ARC? (давно я не использовал не-ARC) - person Robert; 01.04.2013
comment
Да, вы делаете - как и все остальное - NSString, NSArray и т. Д. Блоки на самом деле являются просто экземплярами частного класса, который является оболочкой вокруг структуры C ++, фактически реализующей поведение блоков. - person Charlie Monroe; 20.04.2013
comment
Может ли это вызвать утечку памяти? - person imcaptor; 27.04.2013
comment
@CharlieMonroe, это неверно. Они не используют структуру C ++, а просто старую простую структуру C. См. ABI, используемый clang здесь: clang.llvm.org/docs/Block- ABI-Apple.html - person Richard J. Ross III; 13.06.2013
comment
@ RichardJ.RossIII: Это интересно, поскольку, когда вы запускаете clang -rewrite-objc test.m, вы получаете структуру __SomeClass__secondMethod_withTwoArguments__block_impl_0, которая имеет конструктор C ++ (SomeClass - это образец класса ObjC, sedondMethod: withTwoA arguments: withTwoA в этом классе), и когда вы затем видите объявление в методе, используется точный конструктор C ++ ... Это означает, что либо документ не обновлен, параметр -rewrite-objc неверен, либо Apple использует другой структуры, когда разрешен C ++ (не, что исходный файл был бы ObjC ++). - person Charlie Monroe; 15.06.2013
comment
@imcaptor: Да, это может вызвать утечку памяти, если вы не освободите его в dealloc - как и с любой другой переменной. - person Charlie Monroe; 15.06.2013
comment
@ Чарли, я думаю, ты не понимаешь, что делает rewrite-objc. Он преобразует objc в C ++, а не в C, по крайней мере, в соответствии с это. - person Richard J. Ross III; 15.06.2013
comment
@ RichardJ.RossIII - AFAIK, это в основном связано с тем, что Apple начинает довольно активно использовать функции C ++ во всем коде времени выполнения, и они используют библиотеку размотки C ++ для исключений во время выполнения 2.0 - -rewrite-objc действительно должен быть результатом clang затем используется для компиляции просто с использованием компилятора C (++) (вывод этапа в процессе компиляции, где компилятор заменяет синтаксис ObjC на C / C ++) ... - person Charlie Monroe; 15.06.2013
comment
@CharlieMonroe Я был бы согласен с вами, если бы сгенерированная сборка была согласована, что, насколько я могу судить, не включает деструкторы C ++. - person Richard J. Ross III; 15.06.2013
comment
@ RichardJ.RossIII - не нужно включать деструкторы C ++, поскольку все, что он делает, - это простое присваивание переменным (без копирования или выделения памяти). На самом деле нет разницы между структурами C и C ++ как таковыми (по крайней мере, в этом случае), и методы C ++ для структур просто упрощают работу с ними, и AFAIK компилятор в любом случае встраивает методы структуры C ++, так что это просто синтаксический сахар, поэтому нет необходимости в дополнительных функциях, таких как my_struct_init, и компилятор проверяет, чтобы структура была инициализирована перед ее использованием ... - person Charlie Monroe; 17.06.2013
comment
Будет намного чище, если вы удалите суффикс (void) и вместо этого используете void (^simpleBlock)() - person Daniel Galasko; 08.07.2015
comment
извините, что откопал это, но почему @property (nonatomic) MyCompletionBlock completion; без копии здесь? Я помню, что дефолт силен. И блок должен использовать копию. Это последовательно здесь - person Wingzero; 24.02.2018
comment
привет @Wingzero - ответ просто неправильный; это было неправильно 6 лет назад и неправильно сейчас. Документ Apple дает и объясняет лучший подход. - person Fattie; 04.05.2018
comment
@Fattie извини? Не могли бы вы дать ссылку? Спасибо - person Wingzero; 05.05.2018
comment
в ответе ниже @Wingzero - person Fattie; 05.05.2018

Вот пример того, как вы могли бы выполнить такую ​​задачу:

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

Надеюсь, это поможет.

ИЗМЕНИТЬ 12 марта 2012 г .:

Для ARC особых изменений не требуется, так как ARC будет управлять блоками за вас, пока они определены как копии. Вам также не нужно устанавливать свойство равным nil в деструкторе.

Для получения дополнительной информации ознакомьтесь с этим документом: http://clang.llvm.org/docs/AutomaticReferenceCounting.html

person Richard J. Ross III    schedule 14.10.2010

Для Swift просто используйте замыкания: пример.


В Objective-C:

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

@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+) делайте то, что показано здесь. Это так просто.

person Fattie    schedule 24.12.2013
comment
Может быть, вам также стоит сказать, что теперь (2016 г.) можно использовать strong вместо copy? - person Nik Kov; 11.10.2016
comment
Можете ли вы объяснить, почему свойство не должно nonatomic отличаться от лучших практик для большинства других случаев использования свойств? - person Alex Pretzlav; 16.11.2016
comment
WorkingwithBlocks.html от Apple < i> Вы должны указать копию в качестве атрибута свойства, потому что ... - person Fattie; 16.11.2016

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

@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]; 
};

Этот шаблон можно применить OVER и OVER к какао API. Используйте свойства, чтобы сблизить соответствующие части вашего кода, устранить запутанные парадигмы делегирования и усилить возможности объектов, помимо того, что они просто действуют как тупые «контейнеры».

person Alex Gray    schedule 31.05.2013
comment
Алекс, отличный попутный пример. Вы знаете, мне интересно, что такое неатомное. Мысли? - person Fattie; 10.02.2014
comment
Очень редко atomic будет правильным решением для собственности. Было бы очень странно устанавливать свойство блока в одном потоке и читать его в другом потоке одновременно или устанавливать свойство блока одновременно из нескольких потоков. Таким образом, стоимость атомарного и неатомного не дает вам никаких реальных преимуществ. - person gnasher729; 31.03.2014

Конечно, вы можете использовать блоки как свойства. Но убедитесь, что они объявлены как @property (copy). Например:

typedef void(^TestBlock)(void);

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

В MRC блоки, захватывающие переменные контекста, размещаются в стеке; они будут освобождены, когда фрейм стека будет уничтожен. Если они будут скопированы, новый блок будет размещен в куче, который может быть выполнен позже, после того, как будет заполнен кадр стека.

person Mindy    schedule 16.07.2015
comment
Точно. Вот настоящая документация Apple о том, почему вам следует использовать копию и ничего больше. developer.apple.com/library/ios/ документация / какао / концептуальный / - person Fattie; 12.11.2015

Заявитель

Это не должно быть «хорошим ответом», поскольку этот вопрос явно задается 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, оптимизирован для закрытия

Поскольку 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!")
    }
}
person Francescu    schedule 17.06.2014

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

В дополнение к тому, что ответил @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)
person Gil Beyruth    schedule 21.07.2014

Вы можете следовать приведенному ниже формату и использовать свойство testingObjectiveCBlock в классе.

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

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

Дополнительную информацию см. здесь

person Sujith Thankachan    schedule 24.01.2014
comment
Действительно ли этот ответ добавляет что-то еще к другим уже предоставленным ответам? - person Richard J. Ross III; 30.01.2014