2
votes

I going over and trying to learn JavaScript from Objective-C, and I'm curious if having a method has a parameter type is possible in Objective-C. Below is an example of the findIndex() JavaStript function that takes a returns the index of the first element in an array that pass a test (provided as a function). What would an Objective-C implementation of this look like with blocks, if this is even possible?

If I am writing a class category on NSArray, how would I pass in a block to a method and execute that condition on itself (NSArray). If it's not possible, I'd love to know why.

    var ages = [3, 10, 18, 20];

    function checkAdult(age) {
        return age >= 18;
    }

    function myFunction() {
        document.getElementById("demo").innerHTML = ages.findIndex(checkAdult);
    }

Here is one helpful tidbit I found on another StackOverFlow question that might help with the block syntax. A the definition of the findIndex() function that I'm trying to implement.

Block Syntax

  1. Block as a method parameter Template

    - (void)aMethodWithBlock:(returnType (^)(parameters))blockName {
            // your code
    }
    

    Example

    -(void) saveWithCompletionBlock: (void (^)(NSArray *elements, NSError *error))completionBlock{
            // your code
    }
    
  2. Block as a method argument Template

    [anObject aMethodWithBlock: ^returnType (parameters) {
        // your code
    }];
    

    Example

    [self saveWithCompletionBlock:^(NSArray *array, NSError *error) {
        // your code
    }];
    
  3. Block as a local variable** Template

    returnType (^blockName)(parameters) = ^returnType(parameters) {
        // your code
    };
    

    Example

    void (^completionBlock) (NSArray *array, NSError *error) = ^void(NSArray *array, NSError *error){
        // your code
    };
    
  4. Block as a typedef Template

    typedef returnType (^typeName)(parameters);
    
    typeName blockName = ^(parameters) {
        // your code
    } 
    


FindIndex() Definition

Array.prototype.findIndex ( predicate [ , thisArg ] )

NOTE predicate should be a function that accepts three arguments and returns a value that is coercible to the Boolean value true or false. findIndex calls predicate once for each element of the array, in ascending order, until it finds one where predicate returns true. If such an element is found, findIndex immediately returns the index of that element value. Otherwise, findIndex returns -1.

2

2 Answers

2
votes

Corresponding code in Objective-C:

@implementation NSArray (MyExtension)

- (NSUInteger)findIndexWithPredicate:(BOOL (^)(id element, NSUInteger index, NSArray* array))predicate {
    NSUInteger result = NSNotFound;
    if (NULL != predicate) {
        for (NSUInteger i = 0; i < self.count; i++) {
            if (predicate(self[i], i, self)) {
                result = i;
                break;
            }
        }
    }
    return result;
}

@end

Note that I've changed default return value from -1 to NSNotFound, because index is unsigned.

Method call:

NSUInteger index = [@[@3, @10, @18, @20] findIndexWithPredicate:^BOOL(id element, NSUInteger index, NSArray *array) {
    return [element integerValue] >= 18;
}];

You can avoid extra parameters, defined in JavaScript first index:

@implementation NSArray (MyExtension)

- (NSUInteger)findIndexWithPredicate:(BOOL (^)(id element))predicate {
    NSUInteger result = NSNotFound;
    if (NULL != predicate) {
        for (NSUInteger i = 0; i < self.count; i++) {
            if (predicate(self[i])) {
                result = i;
                break;
            }
        }
    }
    return result;
}

@end

NSUInteger index = [@[@3, @10, @18, @20] findIndexWithPredicate:^BOOL(id element) {
    return [element integerValue] >= 18;
}];

You can achieve same result without writing custom category, using enumerateObjectsUsingBlock: method:

[@[@3, @10, @18, @20] enumerateObjectsUsingBlock:^(id  _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
    if ([obj integerValue] >= 18) {
        *stop = YES;
        index = idx;
    }
}];

Also, it's possible to use alternatives to blocks, like C function pointers. But blocks give much more flexibility.

0
votes

I did work out a similar solution to Boris:

NSArray *ages = @[@"3", @"10", @"18", @"20"];

BOOL(^parameter)(NSInteger integer) = ^BOOL(NSInteger integer) {

    if (integer >= 18) {
        return YES;
    }

    return NO;
};

NSInteger(^block)(BOOL(^parameter)(), NSArray *array) = ^NSInteger(BOOL(^parameter)(), NSArray *array) {

    for (NSUInteger i = 0; i < array.count; i++) {
        NSInteger arrayInteger = [array[i] intValue];

        if (parameter(arrayInteger) == YES) {
            return i;
        }
    }

    return -1;
};

NSInteger result = block(parameter, ages);