Главная
Блог разработчиков phpBB
 
+ 17 предустановленных модов
+ SEO-оптимизация форума
+ авторизация через соц. сети
+ защита от спама

Objective-c блоки и c лямбды

Anna | 2.07.2014 | нет комментариев
Надеюсь, что пост будет пригоден людям которые знакомы с лямбдами C , но хотят исследовать блоки Objective-C и напротив.
Тут я постарался описать синтаксис замыканий, механизмы захвата контекста, управление памятью и взаимодествие лямбд и блоков между собой.
Во всех примерах применялся Apple LLVM Compiler 4.2 (Clang). Для приведенного Obj-C кода не применяется ARC, т.к я придерживаюсь суждения, что нужно знать как работает non-ARC код, Дабы осознать как работает ARC.

Разделы:

  1. Синтаксис
  2. Завладение контекста
  3. Управление памятью
  4. Objective-C

Блоки в Objective-C — это реализация замыканий [2]. Блоки представляют собой неизвестные функции, которые могут захватывать контекст (нынешние стековое переменные и переменные-члены классов). Блоки во время выполнения представляются объектами, они являются аналогами лямбда выражений в C .

Лямбды в C — тоже являются реализацией замыканий и представляют собой безымянные локальные функции.

Синтаксис

Obj-C блоки


[3]

В текстовом виде

int multiplier = 7;
int (^myBlock)(int) = ^(int num) { return num * multiplier;};
NSLog(@”%d”, myBlock(4)); // выведет 28
  • ^ — данный символ говорит компилятору о том что переменая — блок
  • int — блок принимает один параметр типа int, и возвращает параметр типа int
  • multiplier — переменная которая приходит к нам из контекста (об этом больше детально в разделе “Захват контекста”)

Блоки имеют семантику указателя.
Блоки в Objective-C теснее крепко обнаружили свое использование как в стандартных фреймворках (Foundation, UIKit) так и в сторонних библиотеках (AFNetworkingBlocksKit).
Приведем пример в виде категории класса NSArray

Пример применения блока в NSArray


// имплементация категории
@implementation NSArray (Blocks)
// способ возвращает массив, элементы которого соответствуют предикату
- (NSArray*)subarrayWithPredicate:(BOOL(^)(id object, NSUInteger idx, BOOL *stop))predicte {
    NSMutableArray *resultArray = [NSMutableArray array];
    BOOL shouldStop = NO;
    for (id object in self) {
        if (predicte(object, [self indexOfObjectIdenticalTo:object], &shouldStop)) {
            [resultArray addObject:object];
        }
        if (shouldStop) {
            break;
        }
    }
    return [[resultArray copy] autorelease];
}
@end

// где-то в клиентском коде
    NSArray *numbers = @[@(5), @(3), @(8), @(9), @(2)];
    NSUInteger divisor = 3;
    NSArray *divisibleArray = [numbers subarrayWithPredicate:^BOOL(id object, NSUInteger idx, BOOL *stop) {
        BOOL shouldAdd = NO;
        // нам необходимы числа кратные 3
        NSAssert([object isKindOfClass:[NSNumber class]], @"object != number");
        // обратим внимание, что переменную divisor мы взяли из контекста
        if ([(NSNumber *)object intValue] % divisor == 0) {
            shouldAdd = YES;
        }
        return shouldAdd;
    }];
    NSLog(@"%@", divisibleArray); // выведет 3, 9

В первую очередь они отменно подходят для асинхронных операций, в этом дозволено удостовериться применяя AFNetworking, да и трудиться с ними в GCD — одно наслаждение.
Мы можем определять свои типы блоков, скажем:

Объявление типа блока

typedef int (^MyBlockType)(int number, id object);
С лямбды

Тот же самый код в виде лямбды
[11]

В текстовом виде

int multiplier = 7;
auto lambda = [&multiplier](int num) throw() -> int
{
     return multiplier * num;
};
lambda(4); // равно 28
  • [] — предисловие объявления лямбды, внутри — завладение контекста
  • &multiplier — захваченная переменная (& обозначает что захвачена по ссылке)
  • int — входной параметр
  • mutable — ключевое слово которое разрешает модифицировать переменные захваченные по значению
  • throw() — обозначает что лямбда не выбрасывает никаких исключений наружу

Приведем подобный пример выделения подмножества для лямбды

Извлечение подмножества из коллекции по предикату

template<class InputCollection, class UnaryPredicate>
void subset(InputCollection& inputCollection, InputCollection& outputCollection, UnaryPredicate predicate)
{
    typename InputCollection::iterator iterator = inputCollection.begin();
    for (;iterator != inputCollection.end();   iterator) {
        if (predicate(*iterator)) {
            outputCollection.push_back(*iterator);
        }
    }
    return;
}

int main(int argc, const char * argv[])
{
        int divisor = 3;
        std::vector<int> inputVector = {5, 3, 8, 9, 2};
        std::vector<int> outputVector;
        subset(inputVector, outputVector, [divisor](int number){return number % divisor == 0;});
        // выводим значения полученной коллекции
        std::for_each( outputVector.begin(), 
                       outputVector.end(), 
                       [](const int& number){std::cout << number << std::endl;} );
}

Завладение контекста

Obj-C блоки


Мы можем механически применять значения стековых переменных в блоках, если не изменяем их. Скажем, в приведенном выше примере, мы не указывали в объявлении блока, что мы хотим захватить переменнуюmultiplier (в различии от лямбды, в лямбде мы могли бы указать [&] Дабы захватить каждый контекст по ссылкам, либо [=] Дабы захватить каждый контекст по значению).
Мы легко брали ее значение по имени объявленном в теле функции. Если бы мы захотели изменить ее значение в теле блока, нам пришлось бы пометить переменную модификатором __block

Пример метаморфозы значения переменной из контекста

__block int first = 7;
void (^myBlock2)(int) = ^(int second) { first  = second;};
myBlock2(4);
NSLog(@"%d", first); // выведет 11


Для того Дабы отправлять сообщения объекту, указатель на тот, что мы передаем в блок, необходимости помечать указатель как __block нет. чай по сути, когда мы отправляем сообщение объекту — мы не изменяем его указатель.

Пример отправки сообщения объекту из контекста

NSMutableArray *array = [NSMutableArray array];
void (^myBlock3)() = ^() { [array addObject:@"someString"];};
myBlock3(); // добавит someString вarray


Но изредка, все же, нужно пометить указатель на объект с поддержкой __block, Дабы избежать утрат памяти. (Об этом подробнее в разделе “Управление памятью”)

С лямбды


Захваченные переменные указываются в определенном месте [5], а именно внутри квадратных скобок [ ]

  • [&] — обозначает что мы захватываем все символы по ссылке
  • [=] — все символы по значению
  • [a, &b] — a захвачена по значению, b захвачена по ссылке
  • [] — ничего не захвачено

К захвату контекста имеет отношение спецификатор mutable, он говорит о том,
что вы можете изменять копии переменных переданных по значению. Подробнее в дальнейшем разделе.

Управление памятью

Obj-C блоки


Блоки — это объекты, создаются они на стеке (в последситвие они могут быть перенесены в кучу (heap))
Блоки могут существовать в виде 3-х имплементаций [7].

  1. Когда мы не используем переменные из контекста (из стека) внутри блока — создается NSGlobalBlock, тот, что реализован в виде синглтона.
  2. Если мы используем контекстные переменные, то создается NSStackBlock, тот, что теснее не является синглтоном, но распологается на стеке.
  3. Если мы используем функцию Block_copy, либо хотим Дабы наш блок был сохранен внутри какого-то объекта помещенного в кучи, скажем как качество объекта: @property (nonatomic, copy) MyBlockType myBlock; то создается объект класса NSMallocBlock, тот, что захватывает и овладевает (овладевает == посылает сообщение retain) объектами переданными в контексте. Это дюже значимое качество, потому как может приводить к утратам памяти, если с ним обращаться небрежно. Блоки могут создавать циклы владения (retain cycle). Еще значимо подметить, что если мы будем применять значениеproperty в NSMallocBlock — ретейниться будет не само качество, а объект которому качество принадлежит.

Приведем пример цикла владения:
Возможен вы захотели сделать класс тот, что делает HTTP запрос с асинхронным API PKHTTPReuquest

Реализация PKHTTPReuquest

typedef void (^PKHTTPRequestCompletionSuccessBlock)(NSString *responseString);
typedef void (^PKHTTPRequestCompletionFailBlock)(NSError* error);

@protocol PKRequest <NSObject>

- (void)startRequest;

@end

@interface PKHTTPRequest : NSObject <PKRequest>

// designated initializer
- (id)initWithURL:(NSURL *)url
     successBlock:(PKHTTPRequestCompletionSuccessBlock)success
        failBlock:(PKHTTPRequestCompletionFailBlock)fail;

@end
@interface PKHTTPRequest () <NSURLConnectionDelegate>

@property (nonatomic, copy) PKHTTPRequestCompletionSuccessBlock succesBlock;
@property (nonatomic, copy) PKHTTPRequestCompletionFailBlock failBlock;
@property (nonatomic, retain) NSURL *url;
@property (nonatomic, retain) NSURLConnection *connection;
@property (nonatomic, retain) NSMutableData *data;

@end

@implementation PKHTTPRequest

#pragma mark - initialization / deallocation

// designated initializer
- (id)initWithURL:(NSURL *)url
     successBlock:(PKHTTPRequestCompletionSuccessBlock)success
        failBlock:(PKHTTPRequestCompletionFailBlock)fail {
    self = [super init];
    if (self != nil) {
        self.succesBlock = success;
        self.failBlock = fail;
        self.url = url;
        NSURLRequest *request = [NSURLRequest requestWithURL:self.url];
        self.connection = [[[NSURLConnection alloc] initWithRequest:request
                                                           delegate:self
                                                   startImmediately:NO] autorelease];
    }
    return self;
}

- (id)init {
    NSAssert(NO, @"Use desiganted initializer");
    return nil;
}

- (void)dealloc {
    self.succesBlock = nil;
    self.failBlock = nil;
    self.url = nil;
    self.connection = nil;
    self.data = nil;
    [super dealloc];
}

#pragma mark - public methods

- (void)startRequest {
    self.data = [NSMutableData data];
    [self.connection start];
}

#pragma mark - NSURLConnectionDelegate implementation

- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
    [self.data appendData:data];
}

- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error {
    self.failBlock(error);
    self.data = nil;
}

- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
    self.succesBlock([NSString stringWithUTF8String:self.data.bytes]);
    self.data = nil;
}

@end

А потом вы захотели сделать определенный запрос для определенного API вашего сервераPKGetUserNameRequest тот, что работает с PKHTTPReuquest

Реализация PKGetUserNameRequest

typedef void (^PKGetUserNameRequestCompletionSuccessBlock)(NSString *userName);
typedef void (^PKGetUserNameRequestCompletionFailBlock)(NSError* error);

@interface PKGetUserNameRequest : NSObject <PKRequest>

- (id)initWithUserID:(NSString *)userID
        successBlock:(PKGetUserNameRequestCompletionSuccessBlock)success
           failBlock:(PKGetUserNameRequestCompletionFailBlock)fail;

@end
NSString *kApiHost = @"http://someApiHost.com";
NSString *kUserNameApiKey = @"username";

@interface PKGetUserNameRequest ()

@property (nonatomic, retain) PKHTTPRequest *httpRequest;

- (NSString *)parseResponse:(NSString *)response;

@end

@implementation PKGetUserNameRequest

#pragma mark - initialization / deallocation

- (id)initWithUserID:(NSString *)userID
        successBlock:(PKGetUserNameRequestCompletionSuccessBlock)success
           failBlock:(PKGetUserNameRequestCompletionFailBlock)fail {
    self = [super init];
    if (self != nil) {
        NSString *requestString = [kApiHost stringByAppendingFormat:@"?%@=%@", kUserNameApiKey, userID];
        self.httpRequest = [[[PKHTTPRequest alloc] initWithURL:[NSURL URLWithString:requestString]
                                                  successBlock:^(NSString *responseString) {
                                                      // роковая оплошность - обращение к self
                                                      NSString *userName = [self parseResponse:responseString];
                                                      success(userName);
                                                  } failBlock:^(NSError *error) {
                                                      fail(error);
                                                  } ] autorelease];
    }
    return self;
}

- (id)init {
    NSAssert(NO, @"Use desiganted initializer");
    return nil;
}

- (void)dealloc {
    self.httpRequest = nil;
    [super dealloc];
}

#pragma mark - public methods

- (void)startRequest {
    [self.httpRequest startRequest];
}

#pragma mark - private methods

- (NSString *)parseResponse:(NSString *)response {
    /* ...... */
    return userName;
}

@end

Оплошность в этой строчке NSString *userName = [self parseResponse:responseString]; — когда мы вызываем что-то у self в Malloc блоке, self ретейнится, образовался дальнейший цикл в графе владения:

Избежать этого дозволено было сделав на стеке промежуточный указатель на self с модификатором __block, вот так:

Пример обрыва цикла владения

// разрываем цикл владения
__block PKGetUserNameRequest *selfRequest = self;
self.httpRequest = [[[PKHTTPRequest alloc] initWithURL:[NSURL URLWithString:requestString]
                                          successBlock:^(NSString *responseString) {
                                              NSString *userName = [selfRequest parseResponse:responseString];
                                              success(userName);
                                          } failBlock:^(NSError *error) {
                                              fail(error);
                                          } ] autorelease];

Либо же, дозволено было перенести блоки из сигнатуры способа инициализации в способ startRequest,
startRequestwithCompaltion:fail:, и ретейнить блоки только на время выполнения запроса.

Приведем иной пример неправильного memory management с блоками, пример взят из видео лекции [7]

Оплошность с NSStackBlock

void addBlockToArray(NSMutableArray* array) {
    NSString *string = @"example string";
    [array addObject:^{
        printf("%@\n", string);
    }]; 
}

void example() {
     NSMutableArray *array = [NSMutableArray array];
    addBlockToArray(array);
    void (^block)() = [array objectAtIndex:0];
    block();
}

Если бы мы скопировали блок в кучу (heap) и передали вверх по стеку, ошибки бы не произошло.
Также данный пример не вызовет ошибки в ARC коде.

С лямбды

Реализация лямбд в runtime, может быть специфична в различных компиляторах. Говорят, что управление памятью для лямбд не дюже описано в эталоне.[9]
Разглядим распространенную имплементацию.
Лямбды в C — это объекты незнакомого типа, создающиеся на стеке.
Лямбды которые не захватывают никакого контекста, могут быть приведены к указателю на функцию, но все же это не обозначает, что они сами являются легко указателями на функцию. Лямбда — это обыкновенный объект, с конструктором и деструктором, выдающийся на стеке.

Исключительным методом переместить лямбду на heap является приведение ее к типу std::function 

Пример перемещения лямбды в heap

auto lamb = []() {return 5;};
auto func_lamb_ptr = new std::function<int()>(lamb);

Сейчас дозволено передать функцию в переменную-член какого-нибудь объекта.

mutable позже объявления доводов лямбды говорит о том, что вы можете изменять значения копий переменных, захваченных по значению (Значение подлинной переменной изменяться не будет). Скажем, если бы мы определили лямбду так: auto lambda = [multiplier](int num) throw() mutable то могли бы изменять значение multiplier внутри лямбды, но multipler объявленный в функции не изменился. Больше того, измененное значение multiplier сохраняется от вызова к вызову данного экземпляра лямбды. Дозволено представить это так: в экземпляре лямбды (в объекте) создаются переменные члены соответствующие переданным параметрам. Здесь необходимо быть осмотрительнее, потому что если мы скопируем экземпляр лямбды и вызовем ее, то эти переменные-члены не изменятся в подлинной лямбде, они изменятся только в скопированной. Изредка необходимо передавать лямбды обернутыми в std::ref. Obj-C блоки не предаставляют такой вероятности «из коробки».

Objective-C


Так как Objecitve-C комбинирует в себе как Objective-C так и C , в нем дозволено единовременно применять лямбды и блоки. Как же лямбды и блоки относятся друг к другу?

  1. Мы можем присвоить блоку лямбду.

    Пример

        void (^block_example)(int);
        auto lambda_example = [](int number){number  ; NSLog(@"%d", number);};
        block_example = lambda_example;
        block_example(10); // log 11
    
  2. Мы можем присвоить блок объетку std::functionТут стоит подметить что Objective-C и C имеют различные политики управления памятью, и хранение блока в std::function может приводить к «висячим» ссылкам.
  3. Мы не можем присвоить лямбде блок.У лямбды не определен оператор copy-assignment. Следственно мы не можем присвоить ей ни блок ни даже саму себя.

    Оплошность присвоения

    int main() {
        auto lambda1 = []() -> void { printf("Lambda 1!\n"); };
        lambda1 = lambda1;  // error: use of deleted function ‘main()::<lambda()>& main()::<lambda()>::operator=(const main()::<lambda()>&)’
        return 0;
    }
    


Следует сказать, что операции между лямбдами и блоками довольно экзотичны, я скажем ни разу не встречал сходственные присвоения в планах.

Ссылки по теме

  1. О лямбдах C 
  2. Замыкания
  3. О блоках Apple
  4. Сопоставление лямбд и блоков на англ
  5. Доки C лямбд
  6. О блоках
  7. Хорошее видео о блоках
  8. Вопрос об организации памяти C лямбд на stackoverflow.com
  9. Вопрос про имплементацию C лямбд в runtime
  10. О взаимодействии лямбд и блоков
  11. Синтаксис лямбд
  12. О взаимодействии Objective-C и C 
  13. Методы встраивания C в Objective-C планы

 

Источник: programmingmaster.ru

 

Оставить комментарий
Форум phpBB, русская поддержка форума phpBB
Рейтинг@Mail.ru 2008 - 2017 © BB3x.ru - русская поддержка форума phpBB