爬爬爬之路:OC语言(六) Block语法简介

回顾函数指针

在学习Block语法之前需要先回顾一下函数指针 因为Block语法与函数指针非常相似
函数指针, 顾名思义就是指向函数的指针

1. 函数指针的定义

如定义一个函数如下:

int sumValue(int num1, int num2) {

    return num1 + num2;
}

这本函数的类型为
int (int num1, int num2)(去除函数名 即是该函数的类型)
也可以省略形参名, 简写成
int (int, int)

我们知道定义一个指针的方法是 指针类型 + 指针变量名 = 初值 既int* p = NULL;
整型指针或者基本数据类型的指针类型都是 数据类型+* + 指针变量名 = 初值
指针通常会把指针类型的 * 和指针变量名贴在一起写 比如 int *p, double *p2

同理, 定义一个函数的指针也是 函数指针类型 + 函数指针变量名 = 初值
而函数指针类型的写法较之基本数据类型的指针类型有些不同.
需要在原来函数名的位置加上一个括号 , 括号里放一个’*’, 既表示该函数的函数指针类型

如果上面提到的函数sumValue
它的函数指针类型是:
int (*)(int num1, int num2)
或者省略形参名, 简写成
int (*)(int, int)
它的函数指针变量则可以写成
int (* p)(int num1, int num2) (在括号内, ‘*’ 后面加上指针变量名)
这样我们可以完整定义一个函数指针变量如下:
int (* p)(int num1, int num2) = NULL;
函数指针的赋值为 同类型的函数名.
比如可以在定义函数指针的同时初始化:
int (* p)(int num1, int num2) = sumValue;

2. 函数指针的调用方法

普通函数的调用方法如下:
int sum = sumValue(3, 5);
函数指针的调用方法相同, 用函数指针变量名替换方法名即可:
int sum = p(3, 5);
完整的调用方法如下:

int sumValue(int num1, int num2) {   // 在主函数外声明一个函数

    return num1 + num2;
}
int main() {                         // 在主函数中定义函数指针并调用
    int a = 10;
    int b = 12;
    int sum = p(a, b);
    printf("%d", sum);
    return 0;
}

Block语法

Block语法, 也称为块语法, 本质上Block是匿名函数 (没有名称的函数)
在其他语言中也有相似的语法, 称为闭包
Block语法和函数指针很相似
相似于函数指针中的*
Block的标志是 ^ (托字符)

1. Block的声明方式

  1. 用C语言的形式定义Block

    int (^myBlock)(int a, int b) = ^int (int a, int b) {
        return a + b;
    };

    block的声明规则也是 数据类型 + 变量名 = 初值(和前面的类型要一致), 类型前面加上一个托字符 初值部分是一个没有名字的函数体.
    别忘了最后要加上’;’!!!
    以上代码中Block语法的类型为: int (^)(int, int)
    Block的名字为myBlock
    Block的值为 ^int (int a, int b){...}

  2. 用OC语言的形式定义Block

    void (^PrintBlock)(NSString *) = ^void (NSString *str){
        NSLog(@"%@", str);
    };

    调用方法如下: (得在定义之后才能调用)
    PrintBlock(@"hello world");

    P.S可以直接通过Block的名字进行调用 注意的是block声明部分的参数名可以省略, 而初值部分的返回值可以省略, 但是初值部分的参数名不能省略. 建议把实现部分的返回值加上, 这样比较容易看懂

2. Block语法的运行调用顺序:

  1. 首先执行PrintBlock();语句.
  2. 再跳转到 void(^PrintBlock)(NSString *)实现部分的语句.
  3. 再跳回PrintBlock();语句, 程序结束
NSInteger num1 = 5;
NSInteger (^addBlock)(NSInteger) = ^NSInteger (NSInteger num2) {
    return num1 + num2;
};
num1 = 10;
NSInteger result = (addBlock(3));
NSLog(@"%ld", result);

我们知道 先运行NSInteger result = (addBlock(3));在跳转到Block的实现部分, return语句执行后再转回NSLog(@"%ld", result);语句
按照这个思路, result的值应该等于13. 然而结果打印出来是8. 也就是说num1在Block中并没有被重新赋值为10.
这是因为Block是存放在栈中.
运行从上至下, 先把num1放入栈中位置下标记为0, 再把Block的声明部分压入栈中下标记为1. Block实现部分的num1, num2继续压栈至下标为2, 3的位置.num2由用户传入, num1搜索到同名的在栈下标为0位置的num1, 将其值复制给Block中的num1.
运行到num1 = 10的时候, 将下标记为0的num1的值改为10. 而下标为Block中的指向数值5的指针没有被改写重指向为数值10.

所以运行结果为8.

NSInteger num1 = 5;
NSInteger (^addBlock)(NSInteger);
num1 = 10;
addBlock = ^NSInteger (NSInteger num2) { // 将实现部分写在变量重写赋值后
    return num1 + num2;
};

NSInteger result = (addBlock(3));
NSLog(@"%ld", result);

将addBlock的实现部分分离, 写在num1重赋值后的地方.
结果为13

3. Block的好处

  1. 可以在另一个函数的内部声明并调用Block
  2. 可以随时给Block重新赋值.如以上语句, 需要在Block内部打印两次.不需要在原先定义的Block定义中改写程序,可以直接给Block的变量名重新赋值即可.然后再次调用的时候, 调用的是最后一次赋值的值.
  3. 当把Block当作一个参数传入方法中 非常灵活 可以灵活掌控Block的实现 也就是 传入的Block实现的是什么功能 这个方法就实现什么功能

这意味着 可以在不同场合 根据不同的需求, 给Block重新赋值, 要求Block完成该场合下适应的功能 而不需要先绞尽脑汁给Block函数写一个不同场合下都适用的方法

注意: 函数是存放在栈区的 Block语法本质上是一种函数 所以也是存放在栈区的 这和OC类中的方法不同, 类中的方法是存在堆区的

4. Block对参数的改变

由于Block可以在函数中定义, 常常就会涉及到函数中的变量.
函数中的变量分为两种, 局部变量和全局变量

  1. OC中不允许在Block内部直接修改局部变量的值的 但是可以访问局部变量

  2. 如果需要修改局部变量 需要将局部变量用__block(双下划线)进行修饰

  3. Block是可以修改全局变量的值

如:

NSInteger num = 5;
NSInteger (^myBlock)() = ^NSInteger () {
    num =10;
    return num;
};
NSInteger result = (myBlock(3));
NSLog(@"%ld", result);

发现报错, 因为num是局部变量, 不能在Block实现部分修改值
若要修改, 需要将num在声明时用__block修饰

__block NSInteger num = 5;
NSInteger (^myBlock)() = ^NSInteger () {
    num =10;
    return num;
};
NSInteger result = (myBlock(3));
NSLog(@"%ld", result);

结果修改成功

如果NSInteger num = 5;是声明了一个全局变量(在main前定义). 则不需要添加关键字__block 一样可以进行修改
Block是可以修改全局变量的值

5. Block排序

之前的文章中介绍过数组的- (void)sortUsingSelector:(SEL)comparator; 方法和compare:方法
但是这个方法的弊端是对比字符串的时候只能一个字符一个字符的比较, 且只能升序排序.

可以用带有Block语法的排序方法来灵活的解决比较的两者对象的具体参数和 排序方法
- (void)sortUsingComparator:(NSComparator)cmptr NS_AVAILABLE(10_6, 4_0);

sortUsingComparator:^NSComparisonResult(id obj1, id obj2)
实现部分:
NSComparisonResult为系统定义的枚举类型, 其中存放的值只有3个

typedef NS_ENUM(NSInteger, NSComparisonResult) {
    NSOrderedAscending = -1L,
    NSOrderedSame, 
    NSOrderedDescending 
};

其中NSOrderedAscending的值为-1, NSOrderedSame值为0, NSOrderedDescending值为1.

obj1, obj2为数组中存放的对象, 我们需要做的是填写两者的关系
实现原理: 自动遍历数组, 对数组中的两个元素进行比较, 比较方式由Block语法中自定义的方式, 根据计算的返回值进行排序. 若某个条件的返回值为1, 则自动完成两者交换的步骤, 否者不交换. 直至遍历比较到最后一个元素结束, 完成排序为止.

由于看不到苹果官方的源码, 这里用冒泡排序举例. obj1相当于 array[j], obj2相当于array[j + 1]. 具体比较两者的哪个属性, 哪个值则是由程序员自定义.

具体方法实现如下:

// 实现降序排序:
NSMutableArray *array = [NSMutableArray arrayWithObjects:@"1",@"7", @"6", @"5", @"9", @"11", @"10", nil];

    [array sortUsingComparator:^NSComparisonResult(id obj1, id obj2) {
        NSString *str1 = obj1;           // 此时array数组里保存的是字符串对象 用字符串类型接收
        NSString *str2 = obj2;
        NSInteger num1 = [str1 integerValue];     // 将字符串转化成NSInteger 便于比较
        NSInteger num2 = [str2 integerValue];
        if (num1 < num2) {
            return 1;   // 或者写成return NSOrderedDescending;
        }
        return 0;       // 或者写成return NSOrderedSame;
    }];

    NSLog(@"%@", array);
// 实现升序排序:
    NSMutableArray *array = [NSMutableArray arrayWithObjects:@"1",@"7", @"6", @"5", @"9", @"11", @"10", nil];

    [array sortUsingComparator:^NSComparisonResult(id obj1, id obj2) {
        NSString *str1 = obj1;
        NSString *str2 = obj2;
        NSInteger num1 = [str1 integerValue];
        NSInteger num2 = [str2 integerValue];
        if (num1 > num2) {
            return 1;
        }
        return 0;
    }];

    NSLog(@"%@", array);

这里不得不吐槽一句, 由于官方定义的NSComparisonResult枚举类型, 内部的三个定义号的值命名是根据降序排序来定义的. 且此排序方法只对返回值1敏感. 既只有返回值为1时才实现交换操作.
然而若是要完成升序排序的时候, 用系统的写法也是return NSOrderedDescending;
就显得十分违和.非常蛋疼. 本人强迫症, 在这里就直接返回1和0, 就不根据官方定义的名称了.

当然也可以在排序方法前(必须是之前)自定义一个Block语法, 然后方法调用的时候把自定的Block方法名当作参数传给sortUsingComparator:方法. 实现如下:

    NSComparisonResult (^myCompare)(id obj1, id obj2) = ^ NSComparisonResult (id obj1, id obj2) {

        NSString *str1 = obj1;
        NSString *str2 = obj2;
        NSInteger num1 = [str1 integerValue];
        NSInteger num2 = [str2 integerValue];
        if (num1 > num2) {            // 升序排序 若小于号 则实现降序排序
            return 1;
        }
        return 0;
    };
    [array sortUsingComparator:myCompare]; 

字面量初始化

字面量初始化 也称语法糖
字面量使用的初始化方法 都是类方法初始化

未使用字面量使用字面量
NSString *str = [NSString stringWithUTF8String:”Hello World”];NSString *str = @”Hello World”;
array = [NSArray arrayWithObjects:a, b, c, nil];array = @[a, b, c];
[array objectAtIndex:1]array[1]
dict = [NSDictionary dictionaryWithObjects:@[o1, o2, o3] forKeys:@[k1, k2, k3]];dict = @{k1:o1, k2:o2, k3:o3};
[NSDictionary dictionaryWithObjectsAndKeys:v1, k1, v2, k2, nil]@{k1:v1, k2:v2}
[dictionary valueForKey:k]dictionary[k]

字⾯量创建的对象是便利构造的,且是不可变的。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值