「OC」小白书读书笔记——Block的相关知识(下)

【OC】block的实现 Block语法实际上是作为极普通的C语言代码来处理的。通过支持Block的编译器,含有Block语法的源代码转换为一般C语言编译器能够处理的源代码,并作为极为普通的C语言源代码被编译。这只不过是概念的问题,在实际编译时无法转换成我们能够理解的源码,但clang具有转换成我们可读源代码的功能。通过“-rewrite-objc”选项就能将含有block语法的源代码转换为C++的源代码。说是C++,其实尽是使用了struct结构,其本质是C语言的源代码。clang -rewrite-objc 源代码文件名。 阅读详情

「OC」小白书读书笔记——Block的相关知识(下)

引入

前面我们说到block当中捕获变量的底层原理,以及block对应的各种不同的类,现在开始讲一下block在大括号之中生成,却能够超出变量存在域可存在原因,我们看一下下面的例子

#import <Foundation/Foundation.h>

typedef int (^MultiplierBlock)(int);

// 定义一个函数,用于创建一个 block,并捕获局部变量 factor
MultiplierBlock createMultiplierBlock(int factor) {
    // 这里的 factor 是局部变量
    // block 捕获了 factor 的值,并在闭包中保存了一份拷贝
    MultiplierBlock block = ^(int number) {
        return factor * number;
    };
    
    // 返回前,将 block 从栈复制到堆上,确保它在函数返回后依然有效
    return [block copy];
}

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // 调用 createMultiplierBlock 创建一个 block,捕获的 factor 值为 5
        MultiplierBlock multiplier = createMultiplierBlock(5);
        
        // 在 main 函数中调用 multiplier,即使 createMultiplierBlock 中的 factor 早已不复存在
        int result = multiplier(4);
        NSLog(@"Result: %d", result); // 输出结果为 20 (5 * 4)
    }
    return 0;
}

Block 的存储转换

  • 默认情况下,block 在函数中创建时位于栈上,生命周期与局部变量类似。但在本例中,我们调用了 [block copy],将其复制到堆上。
  • 这样做保证了 block 能够在函数返回后继续存在,因为堆内存的生命周期不依赖于当前函数的作用域。

image-20250221105835990

image-20250221105900036

如同上图所示这个被copy到堆上的block就是_NSConcreteMallocBlock类,一般来说,在需要持有 block 时,只需要执行一次 copy 操作就足够了,多次调用 copy 不会导致重复复制,只是增加引用计数。

__block修饰符的作用

我们知道常规的局部变量,只能被block捕获其初始值,在修改之后并没办法对其捕获的值进行更新。我们故然可以使用静态局部变量来避免这个问题,不过因为静态局部变量在程序运行过程中是不会被释放的,所以还是要尽量少用。那还有什么别的方法来实现这个需求呢?这就是我们要讲的__block关键字。

若在1个Block中使用__block变量,则当该Block从栈复制到堆时,使用的所有__block变量也必定配置在栈上。这些__block变量也全部被从栈复制到堆。此时,Block持有__block变量。即使在该Block已复制到堆的情形下,复制Block也对所使用的__block变量没有任何影响。

image-20250221224757538

在多个Block中使用__block变量时,由于最初所有的Block都会被配置在栈上,所以__block变量也会配置在栈上。当任何一个Block从栈复制到堆时,__block变量会一并从栈复制到堆,并且被该Block持有。当其余的Block从栈复制到堆时,被复制的Block会持有__block变量,并增加__block变量的引用计数,如图所示。

image-20250222102013512

若是在堆上废弃那么对应的block也需要被释放,内容如下:

image-20250222110425108

__block不管是修饰基础数据类型还是修饰对象数据类型,底层都是将它包装成一个对象(我这里取个名字叫__blockObj),然后block结构体中有个指针指向__blockObj。既然是一个对象,那block内部如何对它进行内存管理呢?

当block在栈上时,block内部并不会对__blockObj产生强引用。

当block调用copy函数从栈拷贝到堆中时,它同时会将__blockObj也拷贝到堆上,并对__blockObj产生强引用。

在 Objective-C 中,_Block_object_assign 和 _Block_object_dispose 是与 block 内存管理相关的内部函数。它们用于处理 block 在运行时的内存管理,尤其是在涉及 block 捕获和块内对象引用时。

_Block_object_assign

作用:
_Block_object_assign 是用于将一个对象赋值给一个 block 中的局部变量(例如捕获的 __block 变量)。当 block 捕获一个对象时,它需要确保对这个对象的引用在 block 执行期间是有效的。通过 _Block_object_assign,block 会对捕获的对象进行管理,确保对象的生命周期与 block 的生命周期保持一致。

主要用途:

  • 赋值与引用计数管理:它会增加捕获对象的引用计数,以确保对象在 block 执行期间不会被提前释放。这样,block 内部对捕获对象的引用不会导致对象意外被销毁。
  • 引用传递:如果对象是 __block 变量,_Block_object_assign 确保 block 在内部维护正确的对象引用,而不会导致循环引用。

_Block_object_dispose

作用:
_Block_object_dispose 是用来释放 block 内部捕获的对象引用的函数。它会在 block 被销毁时,正确地减少捕获对象的引用计数,确保不会造成内存泄漏。

主要用途:

  • 释放捕获对象:当 block 执行完毕并且被销毁时,_Block_object_dispose 会减少捕获对象的引用计数,避免内存泄漏。
  • 清理操作:在 block 销毁时进行清理,确保在 block 使用的对象不再被引用时,能正确释放。

image-20250223002507169

__forwarding指针

block int val = 10;
void (^blk) (void) = ^{val = 1; }; 

在我们使用__block修饰符进行修饰变量的时候,我们将OC代码转化为C语言代码,可以看到代码量剧增,其中我们用__block修饰的变量名成功就就会变成一个结构体类型的变量,__Block_byref_val_0 结构体类型的自动变量,即栈上生成的__Block_byref_val_0结构体实例。这个结构声明如下:

struct __Block_byref_val_0 {
    void *isa;                                  // 标识符,一般不直接使用
    struct __Block_byref_val_0 *forwarding;       // 转发指针,始终指向最新的存储位置
    int flags;                                  // 标志位,用于记录状态
    int size;                                   // 结构体的大小
    int val;                                    // 被 __block 修饰的变量值
};

我们来讲一下这个__forwarding指针的作用:当 block 从栈复制到堆上后,局部 __block 变量也必须从栈复制到堆上,否则在 block 执行时会访问无效的内存。__forwarding 指针的更新正是实现这一过程的关键,它确保了 block 内所有对 __block 变量的引用都指向堆上的有效拷贝,而不会停留在即将销毁的栈内存中。

image-20250222120257273

这样,无论 block 在栈上还是堆上执行,所有对该 __block 变量的访问都通过 __forwarding 指针进行,确保访问的是最新、有效的内存区域。

循环引用

接下来将一个我认为比较重要的一个点就是关于block之中产生的循环引用问题

 self.str = @"aa";
 self.block = ^(void) {
        NSLog(@"%@",self.str);
    };
 self.block();

运行以上代码,我们可以看到编译器出现以下警告

image-20250222130228137

这条警告提示你在 block 中对 self 的捕获是强引用(strong capture),这可能会导致循环引用(retain cycle)。

以下是解决循环引用的几个方法

  • 1.weak-strong-dance(最常用的方法)
  • 2.__block修饰对象,同时置nil

强弱共舞

为解决这个问题,一个很简单也最常用的方法,就是使用weak关键词对self进行修饰

@interface ViewController ()
@property (nonatomic, copy) void (^block)(void);
@property (nonatomic, copy) NSString *name;
@end

- (void)viewDidLoad {
    [super viewDidLoad];  
    self.name = @"man";
    __weak typeof(self) weakSelf = self;
    self.block = ^(void){
        NSLog(@"%@",weakSelf.name);
    };
    self.block();
}

但如果在block的基础上还嵌套了一个block,那很有可能在执行block中的嵌套时,weak修饰的对象已经被置为nil。这时候我们如何去解决这个问题呢?我们可以使用 __strong 在内部 block 中对弱引用进行一次临时提升,能确保在整个内部 block 执行期间,对象的引用计数保持在至少 1,从而保证对象不会被提前释放。以下例子:

// MyObject.h
#import <Foundation/Foundation.h>

@interface MyObject : NSObject
@property (nonatomic, copy) void (^block)(void);
@end

// MyObject.m
#import "MyObject.h"

@implementation MyObject

- (instancetype)init {
    self = [super init];
    if (self) {
        NSLog(@"MyObject init");
    }
    return self;
}

- (void)dealloc {
    NSLog(@"MyObject dealloc");
}

@end

// main.m
#import <Foundation/Foundation.h>
#import "MyObject.h"

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // 创建对象并设置 block
        MyObject *obj = [[MyObject alloc] init];
        __weak typeof(obj) weakObj = obj;
        obj.block = ^{
            NSLog(@"Inside block: %@", weakObj);
            dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
                NSLog(@"Inside delayed block: %@", weakObj);
            });
        };
        // 执行 block,此时 obj 仍存在
        obj.block();
    }
    
    // 此时 obj 已经超出 autoreleasepool 的范围,应该被销毁
    // 为了观察延迟 block 的输出,我们让主运行循环等待一段时间
    [[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:3]];
    return 0;
}

我们来看以上程序,在延时的block之中,我们并没有使用__strong对weakObj进行再持有得到的以下结果

image-20250222172215405

现在修改一下代码:

#import <Foundation/Foundation.h>
#import "MyObject.h"
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // 创建对象并设置 block
        MyObject *obj = [[MyObject alloc] init];
        __weak typeof(obj) weakObj = obj;
        obj.block = ^{
            __strong typeof(obj) strongObj = weakObj;
            NSLog(@"Inside block: %@", strongObj);
            dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
                NSLog(@"Inside delayed block: %@", strongObj);
            });
        };
        // 执行 block,此时 obj 仍存在
        obj.block();
    }
    
    // 此时 obj 已经超出 autoreleasepool 的范围,应该被销毁
    // 为了观察延迟 block 的输出,我们让主运行循环等待一段时间
    [[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:3]];
    return 0;
}

image-20250222172326437

其中strongObj是一个临时变量,在block的作用域内,当block执行完就会释放strongObj,这种方式属于打破self对block的强引用,依赖于中间者模式,属于自动置为nil,也就是自动释放。

__block修饰变量后置nil

#import <Foundation/Foundation.h>
#import "MyObject.h"
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // 创建对象并设置 block
        MyObject *obj = [[MyObject alloc] init];
        __block typeof(obj) blockObj = obj;
        obj.block = ^{
            NSLog(@"Inside block: %@", blockObj);
            dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
                NSLog(@"Inside delayed block: %@", blockObj);
                blockObj = nil;
            });
        };
        // 执行 block,此时 obj 仍存在
        obj.block();
    }

    [[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:3]];
    return 0;
}

可以看到我们将对象赋值给一个__block修饰的变量,在block结束后对blockObj进行手动释放

参考文章

OC中block的底层实现原理

OC底层知识点之 - Block底层原理

oc block详解 oc block详解 我们知道在Block使用中,Block内部能够读取外部局部变量的值。但我们需要改变这个变量的值时,我们需要给它附加上__block修饰符。__block另外一个比较多的使用场景是,为了避免某些情况下Block循环引用的问题,我们也可以给相应对象加上__block 修饰符。今天为大家详细讲解Block实现的详细过程 block的实际结构 block的类型 block是如何捕捉变量 阅读详情

相关推荐

重学OC第二十三篇:block

文章目录一、Block是什么?1.1 __block二、Block源码分析 一、Block是什么? int main(int argc, const char * argv[]) { int a = 0; void (^aBlock)(void) = ^{ NSLog(@"%d", a); }; aBlock(); } 用clang转为cpp Block是带有自动变量(局部变量)的匿名函数,但在OC中Block本质上就是一个对象(__main_block_impl_0结构体类比obj

Rain and Tears 899

OC底层探索(二十三)Block用法及原理

Block的使用 return_type表示返回的对象/关键字等(可以是void,并省略) blockName表示block的名称 var_type表示参数的类型(可以是void,并省略) varName表示参数名称 return_type表示返回的对象/关键字等(可以是void,并省略) blockName表示block的名称 var_type表示参数的类型(可以是void,并省略) Block声明及定义语法 varName表示参数名称 return_type (^blockNam

正在注销账号的博客 1107

akshare 布林通道策略

import datetime import pandas as pd import backtrader as bt import matplotlib.pyplot as plt from datetime import datetime import matplotlib import akshare as ak %matplotlib inline class Boll_strategy(bt.Strategy): #自定义参数,每次买入1800手 params=(('size'.

qq_26742269的博客 5335

OC基础 Block

什么是Blocks Blocks是C语言的扩充功能。可以概括为:带有自动变量(局部变量)的匿名函数。 所谓的匿名函数,就是没有名字的函数,在C语言中是不允许的。 在计算机科学中,这种概念也称为:“闭包Closure”、“lambda计算”等。 而且一般而言,函数或者方法里是不能又出现一个函数的,但是Blocks作为一种数据类型,是可以的。通常Blocks用于封装代码。因为即便是写在函数体内部,不去调用它,就不会执行。 Blocks的声明 //无参数 无返回值 void (^block1)(void)

乐事派的专栏 2071

大模型备案避坑指南:如何设计符合TC260标准的评估测试题集?

本文深入探讨了如何设计符合TC260标准的大模型评估测试题集,以顺利通过备案。文章从解构标准核心逻辑出发,详细阐述了题集设计的工程化流程、核心原则与常见误区,并提供了模拟真实对抗的高阶测试策略与度量分析方法,旨在帮助企业构建一套能真实反映模型安全水位、有效规避风险的评估体系。

kafka6courier的博客 660

海思HiMPP开发避坑指南:MMZ内存与系统内存傻傻分不清?一个例子讲透

本文深入解析海思HiMPP开发中MMZ内存与系统内存的关键差异与应用场景,帮助开发者避免常见的内存混淆问题。通过实际案例和性能对比,详细介绍了MMZ内存的特性和优化策略,提升多媒体处理效率。

weixin_30772105的博客 467

OC - Block 详解

1.Block 的使用 Block 是什么? 块,封装了函数调用以及调用环境的 OC 对象, Block 的声明 // 1. @property (nonatomic, copy) void(^myBlock1)(void); // 2.BlockType:类型别名 typedef void(^BlockType)(void); @property (nonatomic, copy) BlockT...

weixin_42350379的博客 1472

MOSFET开关损耗全面讲解:从原理到优化策略

深入剖析MOSFET开关损耗的产生机制,结合实际应用场景,讲解如何通过驱动设计和电路优化降低损耗,提升系统效率。重点关注mosfet在高频开关下的表现与热管理。

weixin_29476595的博客 1057

OC Block 简介

在维基中闭包的定义如下: 在计算机科学中,闭包(英语:Closure),又称词法闭包(Lexical Closure)或函数闭包(function closures),是引用了自由变量的函数。这个被引用的自由变量将和这个函数一同存在,即使已经离开了创造它的环境也不例外。所以,有另一种说法认为闭包是由函数和与其相关的引用环境组合而成的实体。闭包在运行时可以有多个实例,不同的引用环境和相同的函数组合

ios 学徒 1006

python实现马丁策略(股市) (附完整源码)

python实现马丁策略(股市) (附完整源码)

希望我的博客,能帮上你解决学习中工作中所遇到的问题 603

GD32F470移植LWIP协议,PHY芯片DP83848I

也就是说前24位是有规定的,其中48位MAC中的前几位也是有着对应的含义的。2、旧版LWIP(如1.4.1)存在DHCP状态机卡死问题,建议升级至2.1.3+。

m0_61973119的博客 1587

「OC」小白书读书笔记——Block的相关知识(上)

在之前的OC学习之中使用了很多关于Block的内容,包括但不限于网络请求后的回调操作,block的跨界面传值等,但是本人还是对block这个概念比较模糊,于是就在网上学习一些相关的知识进行补充前面我们知道,block的结构体之中时候isa指针的,这其实意味着,block其实是作为一个类存在的,那我们就来探究一下block的类。NSLog(@"block1的类:%@",[block1 class]);NSLog(@"block2的类:%@",[^{} class]);

aa2002aa的博客 966

Intellij IDEA 使用教程(一)Jdk安装和配置

一.JDK下载 这里下载的是Java SE 8u181 64位版本。可以根据自己电脑配置来选择不同版本。 官网下载JDK 二.安装JDK 双击运行jdk-8u181-windows-x64.exe安装程序 选择安装目录,然后点击下一步 等待安装 安装JRE,就是java的运行环境,可以安装在java目录下 等待安装 安装完成。点击关闭。 三.java环境变量配置 关于环境变量的配置,...

岁月不饶人,我亦未曾饶过岁月 4052

iOS OC基础知识笔记

iOS OC基础知识笔记 一个NSObject对象占用多少内存? 系统分配了16个字节给NSObject对象(通过malloc_size函数获得) 但NSObject对象内部只使用了8个字节的空间(64bit环境下,可以通过class_getInstanceSize函数获得) #import <objc/runtime.h> #import <malloc/malloc.h&...

WJ的博客 1484

YOLO26涨点改进 | YOLO联合Mamba火热发论文改进篇 | CVPR 2025 | 引入EfficientViM 新型轻量级视觉Mamba,高效捕捉全局依赖,含二次创新、适合目标检测任务

本文提出EfficientViM,一种基于HSM-SSD的新型轻量级视觉Mamba架构,用于改进YOLO26目标检测模型。该模块通过压缩隐藏状态、多阶段特征融合和内存优化设计,在降低计算复杂度的同时提升检测精度。实验表明,EfficientViM在ImageNet-1K上实现了最佳的速度-精度平衡,相比SHViT模型性能提升0.7%且速度更快。文章详细介绍了模块原理、网络结构、核心代码实现及YOLO26集成方法,提供两种改进方案,适用于资源受限环境下的实时目标检测任务。

Ai缝合怪、助力小伙伴高效发论文 172

Camera2Demo

在Camera2 的基础上进行封装,使得Camera2一堆初始化逻辑与Activity分离,降低耦合度,完成预览拍照并保存的操作

Object-C高级编程读书笔记(2)——Block的实质

按照OC高级编程书中所说,所谓Block实质就是Object-C对象。 如何理解这句话呢?应该从OC的类对象所拥有的特征入手,如果Block同样拥有这些特征,那么Block当然也就是OC的对象。 Object-C对象 在OC中,我们可以用id类型来统一代表所有的OC对象类型。那OC运行时又是如何知道id所指向的对象类型具体是那种呢? 我们知道,在OC中,基本上所有的类,最终都有一个相同的父

无忘无往 2432

win10 跳过U8环境检测的IE WebControls.rar

win10 安装u8的时候 提示IE WebControls 不通过,这个工具可以欺骗u8的检查。 跳过U8环境检测的IE WebControls。提示导入失败后,重新检测通过。

zblock 结构_OC 的 Block 简析1 - 三种类型

本文主要介绍 Block 的三种类型。一、Block 是什么?Blocks are Objective-C objects, which means they can be added to collections likeNSArrayorNSDictionary.As an optimization, block storage starts out on the stack—just ...

weixin_39555415的博客 413

C# WinForm 基于DirectShow.dll编写的程序,可边预览边保存视频,音频和图片

基于微软的DirectShow.dll的二次开发1.自动识别连接的摄像头跟麦克风且可以对其进行选择(默认选中第一个)2.可录制wmv和avi两种格式的视频并且都带有音频,avi可以录制的分辨率和帧数进行选择,wmv默认进行了淸晰度优化如不喜欢可去除代码中有相关提示3.可在预览点开之后进行拍照,也可边录制视频边拍照4.添加了视频和音频的压缩功能(酌情使用)这次二次开发发现C#对DirectShow的相关信息太少,这次做完也不敢藏私共享出来,还望大家指正

NTscan扫描工具

ntscan是一个扫描器,可以扫描没有密码和弱密码的共享,先到配置文件中填写猜测的用户名和密码,扫描过程中右侧出现的是扫描日志。

浅谈OC中Block的本质

Block简介 block是将函数及其执行上下文封装起来的一个对象 在block实现的内部,有很多变量,因为block也是一个对象 其中包含了诸如isa指针,imp指针等对象变量,还有储存其截获变量的对象等 原文博客地址: 浅谈OC中Block的本质 定义和使用 block根据有无参数和有无返回值有以下几种简单使用方式 // 无参数无返回值 void (^ BlockOne)(void) =...

RunningKing 1426

OC中Block相关问题总结

1.Block中相互引用问题解决方案: __weak ThirdViewController *weakSelf = self; __weak typeof(self) weakSelf = self; 2.为什么Block使用copy而不用strong @property(nonatomic, copy)void (^block

ft6206的博客 312

OC面试题 - block相关汇总

block出现之后除了官方原有,几乎很少人再用delegate模式了,很多api也扩展了block模式,block最常见的面试分为: 1.block的基本使用方式 2.block的原理,本质上block是个啥 3.衍生出来的循环引用问题产生的原因以及解决方式 4.__block和__weak的区别 5.block捕获机制 ...

yokan_de_s的博客 459

认识OC中的Block

block是什么? block本质是一个OC对象,内部也有isa指针 block是封装了函数调用以及函数调用环境的OC对象 (block内部用到block外部对象也会封装到block内部) 在main.m中写一个block,并在arm64下编译成c++代码。 #import <Foundation/Foundation.h> int main(int argc, const ch...

Max_Zeong的博客 354

OC总结 【OC基础语法相关知识】

m是OC源文件扩展名,入口点也是main函数,第一个OC程序: 1 #import <Foundation/Foundation.h> 2 3 int main(int argc, const char * argv[]) 4 5 { 6 7 theme manager 8 9 @autoreleasepool { 1...

dehuai19940321的博客 356

oc总结 --oc基础语法相关知识

来源:http://www.cnblogs.com/wellsoho/p/4351042.html m是OC源文件扩展名,入口点也是main函数,第一个OC程序: #import int main(int argc, const char * argv[]) { theme manager     @autoreleasepool {

Hugo.Lee的博客 683

oc——block

oc——block

mardax的专栏 440

OC 的 block 学习 3,__block

吹牛逼:

dengjiangszhan的博客 116

OC中的block

Block是将(函数)及其(执行上下文)封装起来的(对象) // MCBlock.m - (void)method { int multiplier = 6; int (^Block)(int) = ^int(int num) { return num * multiplier; }; Block(2); } 源码解析,使用 clang -rewrite-objc file.m 命令查看编译之后的文件内容 结构体构造函数中第一个参数是函数指针,第二个参数block的描述,第三个参数是blo.

More reading and learning to become excellent 546
上一篇: 「OC」小白书读书笔记——Block的相关知识(上)
下一篇: 「OC」源码学习——alloc与init的实现
小鹿撞出了脑震荡
博客等级 码龄3年 1107粉丝 97原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值