iOS——Block one

块类似于匿名函数或闭包,在许多其他编程语言中也存在类似的概念。
可以访问上下文,运行效率高

Block

以下是块的一些基本知识:

  1. 块的定义:块是由一对花括号 {} 包围的代码片段,可以包含一段可执行的代码。块的定义使用 ^ 符号,并可以带有参数列表和返回类型。例如:
^{
    // 代码块的内容
}

  1. 块的类型:块也是一种数据类型,与函数类似。它们可以具有参数和返回值类型。可以使用 typedef 来定义块的类型。例如:
typedef returnType (^BlockTypeName)(parameterTypes);

其中 returnType 是块的返回类型,BlockTypeName 是块的类型名称,parameterTypes 是块的参数类型。
3. 块的赋值和调用:块可以赋值给变量,并且可以像函数一样进行调用。可以使用 = 运算符将块赋值给变量,然后使用该变量调用块。例如:

ReturnType (^blockName)(ParameterTypes) = ^ReturnType (Parameters) {
    // 块的内容
};
blockName(argumentValues); // 调用块

  1. 块的捕获变量:块可以捕获其定义范围内的变量,并在块内部访问这些变量。捕获的变量在块中形成了一个闭包,可以在块的[[生命周期]]内保持其状态。例如:
NSInteger outsideVariable = 10;
void (^block)(void) = ^{
    NSLog(@"Outside variable: %ld", (long)outsideVariable);
};
block(); // 输出:Outside variable: 10

在这个例子中,块捕获了外部的 outsideVariable 变量,并在块内部访问它。
默认情况下,为块所捕获的变量是不可以在块里面修改的,如果修改了outsideVariale的值,就会报错,声明变量的时候可以加上__block修饰符,这样就可以在块内修改了
外部变量如果是数组那种的话是可以使用方法来给数组添加内容的,因为此举没有改变对象原本的地址
Pasted image 20230725110306.png

  1. 块作为参数:块可以作为方法或函数的参数进行传递,从而实现回调和异步操作等功能。可以将块作为参数声明,并在调用方法或函数时传递块。例如:
- (void)performOperationWithCompletion:(void (^)(void))completionBlock {
    // 执行操作
    // 操作完成后调用块
    completionBlock();
}

// 调用方法,传递块作为参数
[self performOperationWithCompletion:^{
    NSLog(@"Operation completed!");
}];

在这个例子中,performOperationWithCompletion: 方法接受一个块作为参数,并在操作完成后调用该块。

#pragma mark **--修改为块所捕获的变量**

    NSArray *array = @[@0, @1, @2, @3, @4, @5];

    **__block** NSInteger count = 0;

    //块作为方法的参数

    [array enumerateObjectsUsingBlock:^(NSNumber *number, NSUInteger idx, **BOOL** *stop) {

            **if** ([number compare:@2] == NSOrderedAscending) {

                count++;

            }

        NSLog(@"%ld====%@",(**unsigned** **long**)idx, number);

    }];

    NSLog(@"%ld", (**long**)count);

    //内联块的用法,传给“numberateObjectsUsingBlock:"方法的块并未先赋给局部变量,而是直接在内联函数中调用了,如果块所捕获的变量类型是对象类型的话,那么就会自动保留它,系统在释放这个块的时候,也会将其一并释放。这就引出了一个与块有关的重要问题,块本身可以视为对象,在其他oc对象能响应的选择子中,很多块也可以响应,最重要的是,块本身也会像其他对象一样,有引用计数,为0时,块就回收了,同时也会释放块所捕获的变量,以便平衡捕获时所执行的保留操作

    //如果块定义在oc类的实例方法中,那么除了可以访问类的所有实例变量之外,还可以使用self变量,块总能修改实例变量,那么除了声明时无需添加__block。不过,如果通过读取或写入操作捕获了实例变量,那么也会自动把self给捕获了,因为实例变量是与self所指代的实例关联在一起的。

    // 需要注意的是(self也是一个对象,也会被保留),如果在块内部使用了实例变量,块会自动对self进行保留操作,以确保在块执行期间保持对象的有效性。但是,如果在块内部直接使用了self,并对其进行读取或写入操作,那么self也会被捕获,从而导致循环引用的问题。为了避免循环引用,可以在块内部使用__weak修饰符来避免对self进行保留操作。

如果某个实例在执行anInstanceMethod放法,那么self变量就会指向此实例。由于块里没有明确使用self变量,所以很容易就会忘记self变量其实也为块所捕获了。直接访问实例遍历和通过self来访问时等效的:
self->_anInstanceVariable = @“someThing”;

typedef void(^SomeBlock) (void);
@property (nonatomic, copy) BlockName someBlock;
`- (void)anInstanceMethod {
     self.someBlock = ^ {
          _anInstanceVariable = @"someThing";
     }
     
 }

self也是个对象,因而块在捕获它时也会将其保留。如果self所指代的那个对象同时也保留了块,那么这种情况就会导致“保留环”
修改为以下代码即可:

typedef void(^SomeBlock) (void);
@property (nonatomic, copy) BlockName someBlock;
__weak typeof(self)weakSelf = self;
`- (void)anInstanceMethod {
     self.someBlock = ^ {
          weakSelf.anInstanceVariable = @"someThing";
     }
     
 }

块的本质

带有自动变量(局部变量)的匿名函数。

  • block本质上也是一个OC对象,它内部也有个isa指针
  • block是封装了函数调用以及函数调用环境的OC对象
  • block是封装函数及其上下文的OC对象
  • !Pasted image 20230726144818.png

block变量捕获

为了保证block内部能够正常访问外部的变量,block有个变量捕获机制
Pasted image 20230725094004.png

block变量与c语言变量完全相同,可以作为以下用途:

  • 自动变量
  • 函数参数
  • 静态变量
  • 静态全局变量
  • 全局变量

BLOCK的三种类型

block的类型,取决于isa指针,可以通过调用class方法或者isa指针查看具体类型,最终都是继承自NSBlock类型

__ NSGlobalBlock __ ( _ NSConcreteGlobalBlock ) 对象存储在数据区
__ NSStackBlock __ ( _ NSConcreteStackBlock ) 对象存储在栈区
__ NSMallocBlock __ ( _ NSConcreteMallocBlock )对象存储在堆区

Pasted image 20230726151722.png

捕获了自动变量block就是栈类型
没有捕获就是数据区
不存在一创建就在堆区的,堆区的意义可以理解为和autorelease一样:延长作用域 Stack类型的Block进行了copy操作之后变成了堆区

  • 堆:动态分配内存,需要程序员自己申请,程序员自己管理
  • 栈:自动分配内存,自动销毁,先入后出,栈上的内容存在自动销毁的情况

NSGlobalBlock&NSStackBlock&NSMallocBlock

如果一个block没有访问外部局部变量,或者访问的是全局变量,或者 静态局部变量,此时的blcok就是一个全局block,并且储存在全局区

- (**void**)NSGlobalBlock {

    //block1没有引用到局部变量

    **int** a = 10;

    **void** (^block)(**void**) = ^{

         NSLog(@"hello world");

    };

    NSLog(@"block:%@", block);

  

    //    block2中引入的是静态变量

    **static** **int** a1 = 20;

    **void** (^block1)(**void**) = ^{

        NSLog(@"hello - %d",a1);

    };

  

    NSLog(@"block:%@", block);

  

  

}

- (**void**)NSStackBlock {

    **__block** **int** a = 10;

    **static** **int** a1 = 20;

    **void** (^**__weak** block)(**void**) = ^{

        NSLog(@"hello - %d",a);

        NSLog(@"hello - %d",a1);

    };

    NSLog(@"block:%@", block);

  

}

- (**void**)NSMallocBlock {

    **int** a = 10;

    **void** (^block1)(**void**) = ^{

        NSLog(@"%d",a);

    };

    NSLog(@"block1:%@", block1);

  

    **__block** **int** b = 10;

    **void** (^block2)(**void**) = ^{

        NSLog(@"%d",b);

    };

  

    NSLog(@"block2:%@", block2);

  

}

//block继承与nsobject

- (**void**)blockFromNSObject {

    **void** (^block1)(**void**) = ^{

        NSLog(@"block1");

    };

    NSLog(@"%@",[block1 class]);

    NSLog(@"%@",[[block1 class] superclass]);

    NSLog(@"%@",[[[block1 class] superclass] superclass]);

    NSLog(@"%@",[[[[block1 class] superclass] superclass] superclass]);

    NSLog(@"%@",[[[[[block1 class] superclass] superclass] superclass] superclass]);

  

}

Pasted image 20230725105743.png

  • 上述代码输出了block1的类型,也证实了block是对象,最终继承NSObject

栈区block和堆区block的区别

- (**void**)diffStackAndHip {

    **__block** **int** a = 10;

    **__block** **int** b = 20;

    NSLog(@"a:%p---b:%p", &a, &b);

    **void** (^**__weak** block)(**void**) = ^{

        NSLog(@"hello - %d---%p",a, &a);

        a++;

    };

    **void** (^block1)(**void**) = ^{

        NSLog(@"hello - %d---%p",b, &b);

        b++;

    };

    block();

    block1();

    NSLog(@"block:%@---block1:%@", block, block1);

    NSLog(@"a:%d---b:%d", a, b);

    NSLog(@"a:%p---b:%p", &a, &b);
}

Pasted image 20230725110848.png

  • 通过结果我们看到,首先block的地址是在栈区,而block1的地址是在堆区,而栈block引用的变量a的地址并没有变化,而堆block1引用的变量b的地址也相应变成了堆区`0x6,并且后面使用的b的地址都是堆区上的。
    #栈block存放在栈区,对局部变量引用只拷贝局部变量的地址,而堆block存放在堆区,并且直接将局部变量拷贝了一份到堆空间。
- (**void**)diffStackAndHip2 {

    NSObject *objc = [NSObject new];

    NSLog(@"%@---%ld",objc, CFGetRetainCount((**__bridge** CFTypeRef)(objc)));// 1

    // block 底层源码

    // 捕获 + 1

    // 堆区block

    // 栈 - 内存 -> 堆  + 1

    **void**(^strongBlock)(**void**) = ^{ // 1 - block -> objc 捕获 + 1 = 2

        NSLog(@"%@---%ld",objc, CFGetRetainCount((**__bridge** CFTypeRef)(objc)));

    };

    strongBlock();

  

    **void**(^**__weak** weakBlock)(**void**) = ^{ // + 1

        NSLog(@"%@---%ld",objc, CFGetRetainCount((**__bridge** CFTypeRef)(objc)));

    };

    weakBlock();

    **void**(^mallocBlock)(**void**) = [weakBlock copy];

    mallocBlock();

  

}

Pasted image 20230725111249.png

奇怪为什么堆区block里面的对象引用计数加2呢?而后面的mallocBlock只加1呢?
首先objc在strongBlock里面必然会拷贝一份到堆区,所以会加1,但是他是从当前函数的栈区拷贝吗?并不是,对于堆区Block一开始编译时是栈block这时候objc对象地址拷贝了一份引用计数加1,后面从栈block变成堆block,又拷贝了一份引用计数又加1,所以这时候是3,weakBlock是栈block仅拷贝了一份,所以引用计数加1,这时候是4,mallocBlock从weakblock拷贝了一份,所以引用计数再加1,这时候是5,相当于strongBlock = weakblock + void(^mallocBlock)(void) = [weakBlock copy];

- (**void**)diffStackAndHip3 {

    NSObject *a = [NSObject alloc];

    NSLog(@"1---%@--%p", a, &a);

    **void**(^**__weak** weakBlock)(**void**) = **nil**;

    {

        // 栈区

        **void**(^**__weak** strongBlock)(**void**) = ^{

            NSLog(@"2---%@--%p", a, &a);

        };

        weakBlock = strongBlock;

        strongBlock();

        NSLog(@"3 - %@ - %@",weakBlock,strongBlock);

    }

    weakBlock();

    NSLog(@"4---%@--%p", a, &a);

  

}

Pasted image 20230725114806.png

  • 当前是栈区strongBlock的赋值给外面的栈区weakBlock因为都是存放在栈空间的,只有当前函数结束才会被销毁,随意这边weakBlock调用并不会有什么问题。如果换成堆区block就不一样了。
  • 这边的a对象在weakBlock()调用时是nil,通过上面打印可以看出a对象在进入到strongblock里,&a拷贝了一份,拷贝的这一份地址指向的跟外面一样,但是当strongblock出了{}刚才复制的对象就要销毁了,(栈区的对象在函数结束的时候就会被销毁,函数本身会在作用域结束的时候被销毁)尽管strongblock对象不再了,但是其指向的内存空间还在,销毁之前给了外面的weakBlock,同理a也一样,对象(此时a指向的内容)不在了,但是内存空间却还在
    #这边建议打断点看一看运行流程,方便理解
- (**void**)diffStackAndHip4 {

    NSObject *a = [NSObject alloc];

    NSLog(@"1---%@--%p", a, &a);

    **void**(^**__weak** weakBlock)(**void**) = **nil**;

    {

        // 栈区

        **void**(^**__strong** strongBlock)(**void**) = ^{

            NSLog(@"2---%@--%p", a, &a);

        };

        weakBlock = strongBlock;

        strongBlock();

        NSLog(@"3 - %@ - %@",weakBlock,strongBlock);

    }

//  weakBlock();//测试的时候取消注释

    NSLog(@"4---%@--%p", a, &a);

  

}

Pasted image 20230725115803.png

  • 为什么呢?因为在{}里面的堆区strongBlock出了大括号就会被销毁,此时你去调用这个block就会崩溃
  • 注意:这边weakBlock为什么也是__NSMallocBlock__,其实weakBlock相当于是指针,此时指向的是一个堆上的内存所以是__NSMallocBlock__
    Pasted image 20230725120024.png
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值