iOS block 陷阱解析

小红书2020校招iOS笔试题解析:底层原理高频考点 iOS开发中,Runtime与内存管理是衡量工程师底层功底的关键。理解消息转发、Block捕获机制、ARC下的引用计数原理,不仅能解决线上崩溃与内存泄漏问题,更是大厂校招面试的高频考点。从Category与关联对象的区别,到GCD死锁与KVO底层实现,这些基础机制共同决定了App的稳定性与性能表现。无论是应届生准备校招笔试,还是工作数年的开发者回补基础,围绕核心模块的系统梳理都能显著提升问题排查与架构设计能力。本文以小红书2020年校招iOS笔试题为线索,逐题剖析其背后的原理与答题思路,帮助读者建立完整的i 阅读详情

一,前言

在前文《深入浅出Cocoa多线程编程之block与dispatch quene》中我介绍了 block 的一些基本语法以及如何和 GCD 结合的使用示例。block 是在 iOS 4 中引入的新特性,它和 C++ 11 中的 lamba 表达式概念相似,有时候也被称为闭包。经过一段时间的使用,我发现要用对用好 block 还是有不少需要注意的地方,今天就来八一八这些值得注意的事儿。

 本文源码下载:点此下载

 

二,block 注意事项

1,block 在实现时就会对它引用到的它所在方法中定义的栈变量进行一次只读拷贝,然后在 block 块内使用该只读拷贝。

如下代码:

复制代码
- (void)testAccessVariable
{
    NSInteger outsideVariable = 10;
    //__block NSInteger outsideVariable = 10;
    NSMutableArray * outsideArray = [[NSMutableArray alloc] init];
    
    void (^blockObject)(void) = ^(void){
        NSInteger insideVariable = 20;
        KSLog(@"  > member variable = %d", self.memberVariable);
        KSLog(@"  > outside variable = %d", outsideVariable);
        KSLog(@"  > inside variable = %d", insideVariable);
        
        [outsideArray addObject:@"AddedInsideBlock"];
    };
    
    outsideVariable = 30;
    self.memberVariable = 30;

    blockObject();
    
    KSLog(@"  > %d items in outsideArray", [outsideArray count]);
}
复制代码

输出结果为:

  > member variable = 30
  > outside variable = 10
  > inside variable = 20
  > 1 items in outsideArray

注意到没?outside 变量的输出值为10,虽然outside变量在定义 block 之后在定义 block 所在的方法 testAccessVariable 中被修改为 20 了。这里的规则就是:blockObject 在实现时会对 outside 变量进行只读拷贝,在 block 块内使用该只读拷贝。因此这里输出的是拷贝时的变量值 10。如果,我们想要让 blockObject 修改或同步使用 outside 变量就需要用 __block 来修饰 outside 变量。

__block NSInteger outsideVariable = 10;

 注意:

a),在上面的 block 中,我们往 outsideArray 数组中添加了值,但并未修改 outsideArray 自身,这是允许的,因为拷贝的是 outsideArray 自身。 

b),对于 static 变量,全局变量,在 block 中是有读写权限的,因为在 block 的内部实现中,拷贝的是指向这些变量的指针。

c), __block 变量的内部实现要复杂许多,__block 变量其实是一个结构体对象,拷贝的是指向该结构体对象的指针。

 

2,非内联(inline) block 不能直接访问 self,只能通过将 self 当作参数传递到 block 中才能使用,并且此时的 self 只能通过 setter 或 getter 方法访问其属性,不能使用句点式方法。但内联 block 不受此限制。

复制代码
typedef NSString* (^IntToStringConverter)(id self, NSInteger paramInteger);
- (NSString *) convertIntToString:(NSInteger)paramInteger
                 usingBlockObject:(IntToStringConverter)paramBlockObject
{
    return paramBlockObject(self, paramInteger);
}

typedef NSString* (^IntToStringInlineConverter)(NSInteger paramInteger);
- (NSString *) convertIntToStringInline:(NSInteger)paramInteger
                 usingBlockObject:(IntToStringInlineConverter)paramBlockObject
{
    return paramBlockObject(paramInteger);
}

IntToStringConverter independentBlockObject = ^(id self, NSInteger paramInteger) {
    KSLog(@" >> self %@, memberVariable %d", self, [self memberVariable]);
    
    NSString *result = [NSString stringWithFormat:@"%d", paramInteger];
    KSLog(@" >> independentBlockObject %@", result);
    return result;
};

- (void)testAccessSelf
{
    // Independent
    //
    [self convertIntToString:20 usingBlockObject:independentBlockObject];
    
    // Inline
    //
    IntToStringInlineConverter inlineBlockObject = ^(NSInteger paramInteger) {
        KSLog(@" >> self %@, memberVariable %d", self, self.memberVariable);
        
        NSString *result = [NSString stringWithFormat:@"%d", paramInteger];
        KSLog(@" >> inlineBlockObject %@", result);
        return result;
    };
    [self convertIntToStringInline:20 usingBlockObject:inlineBlockObject];
}
复制代码

 

3,使用 weak–strong dance 技术来避免循环引用

在第二条中,我提到内联 block 可以直接引用 self,但是要非常小心地在 block 中引用 self。因为在一些内联 block 引用 self,可能会导致循环引用。如下例所示:

复制代码
@interface KSViewController ()
{
    id _observer;
}

@end

@implementation KSViewController

- (void)viewDidLoad
{
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
    
    KSTester * tester = [[KSTester alloc] init];
    [tester run];
    
    _observer = [[NSNotificationCenter defaultCenter]
                 addObserverForName:@"TestNotificationKey"
                 object:nil queue:nil usingBlock:^(NSNotification *n) {
                     NSLog(@"%@", self);
                 }];
}

- (void)dealloc
{
    if (_observer) {
        [[NSNotificationCenter defaultCenter] removeObserver:_observer];
    }
}
复制代码

在上面代码中,我们添加向通知中心注册了一个观察者,然后在 dealloc 时解除该注册,一切看起来正常。但这里有两个问题:

a) 在消息通知 block 中引用到了 self,在这里 self 对象被 block retain,而 _observer 又 retain 该 block的一份拷贝,通知中心又持有 _observer。因此只要 _observer 对象还没有被解除注册,block 就会一直被通知中心持有,从而 self 就不会被释放,其 dealloc 就不会被调用。而我们却又期望在 dealloc 中通过 removeObserver 来解除注册以消除通知中心对 _observer/block 的 retain。

b) 同时,_observer 是在 self 所在类中定义赋值,因此是被 self retain 的,这样就形成了循环引用。

上面的过程 a) 值得深入分析一下:

苹果官方文档中对 addObserverForName:object:queue:usingBlock: 中的 block 变量说明如下:

The block is copied by the notification center and (the copy) held until the observer registration is removed.

因此,通知中心会拷贝 block 并持有该拷贝直到解除 _observer 的注册。在 ARC 中,在被拷贝的 block 中无论是直接引用 self 还是通过引用 self 的成员变量间接引用 self,该 block 都会 retain self。

这两个问题,可以用 weak–strong dance 技术来解决。该技术在 WWDC 中介绍过:2011 WWDC Session #322 (Objective-C Advancements in Depth)

复制代码
    __weak KSViewController * wself = self;
    _observer = [[NSNotificationCenter defaultCenter]
                 addObserverForName:@"TestNotificationKey"
                 object:nil queue:nil usingBlock:^(NSNotification *n) {
                     KSViewController * sself = wself;
                     if (sself) {
                         NSLog(@"%@", sself);
                     }
                     else {
                         NSLog(@"<self> dealloc before we could run this code.");
                     }
                 }];
复制代码

下面来分析为什么该手法能够起作用。

首先,在 block 之前定义对 self 的一个弱引用 wself,因为是弱引用,所以当 self 被释放时 wself 会变为 nil;然后在 block 中引用该弱应用,考虑到多线程情况,通过使用强引用 sself 来引用该弱引用,这时如果 self 不为 nil 就会 retain self,以防止在后面的使用过程中 self 被释放;然后在之后的 block 块中使用该强引用 sself,注意在使用前要对 sself 进行了 nil 检测,因为多线程环境下在用弱引用 wself 对强引用 sself 赋值时,弱引用 wself 可能已经为 nil 了。

通过这种手法,block 就不会持有 self 的引用,从而打破了循环引用。

 

扩展:其他还需要注意避免循环引用的地方

与此类似的情况还有 NSTimer。苹果官方文档中提到"Note in particular that run loops retain their timers, so you can release a timer after you have added it to a run loop.",同时在对接口

+ (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)seconds target:(id)target selector:(SEL)aSelector userInfo:(id)userInfo repeats:(BOOL)repeats

的 target 说明文档中提到:

The object to which to send the message specified by aSelector when the timer fires. The target object is retained by the timer and released when the timer is invalidated.

结合这两处文档说明,我们就知道只要重复性 timer 还没有被 invalidated,target 对象就会被一直持有而不会被释放。因此当你使用 self 当作 target 时,你就不能期望在 dealloc 中 invalidate timer,因为在 timer 没有被invalidate 之前,dealloc 绝不会被调用。因此,需要找个合适的时机和地方来 invalidate timer,但绝不是在 dealloc 中。 

 

4,block 内存管理分析

block 其实也是一个 NSObject 对象,并且在大多数情况下,block 是分配在栈上面的,只有当 block 被定义为全局变量或 block 块中没有引用任何 automatic 变量时,block 才分配在全局数据段上。 __block 变量也是分配在栈上面的。

在 ARC 下,编译器会自动检测为我们处理了 block 的大部分内存管理,但当将 block 当作方法参数时候,编译器不会自动检测,需要我们手动拷贝该 block 对象。幸运的是,Cocoa 库中的大部分名称中包含”usingBlock“的接口以及 GCD 接口在其接口内部已经进行了拷贝操作,不需要我们再手动处理了。但除此之外的情况,就需要我们手动干预了。

复制代码
- (id) getBlockArray
{
    int val = 10;
    return [[NSArray alloc] initWithObjects:
            ^{ KSLog(@"  > block 0:%d", val); },    // block on the stack
            ^{ KSLog(@"  > block 1:%d", val); },    // block on the stack
            nil];
    
//    return [[NSArray alloc] initWithObjects:
//            [^{ KSLog(@"  > block 0:%d", val); } copy],    // block copy to heap
//            [^{ KSLog(@"  > block 1:%d", val); } copy],    // block copy to heap
//            nil];
}

- (void)testManageBlockMemory
{
    id obj = [self getBlockArray];
    typedef void (^BlockType)(void);
    BlockType blockObject = (BlockType)[obj objectAtIndex:0];
    blockObject();
}
复制代码

执行上面的代码中,在调用 testManageBlockMemory 时,程序会 crash 掉。因为从 getBlockArray 返回的 block 是分配在 stack 上的,但超出了定义 block 所在的作用域,block 就不在了。正确的做法(被屏蔽的那段代码)是在将 block 添加到 NSArray 中时先 copy 到 heap 上,这样就可以在之后的使用中正常访问。

在 ARC 下,对 block 变量进行 copy 始终是安全的,无论它是在栈上,还是全局数据段,还是已经拷贝到堆上。对栈上的 block 进行 copy 是将它拷贝到堆上;对全局数据段中的 block 进行 copy 不会有任何作用;对堆上的 block 进行 copy 只是增加它的引用记数。

如果栈上的 block 中引用了__block 类型的变量,在将该 block 拷贝到堆上时也会将 __block 变量拷贝到堆上如果该 __block 变量在堆上还没有对应的拷贝的话,否则就增加堆上对应的拷贝的引用记数。

爱奇艺秋招iOS笔试题深度解析:KVO、Runtime与多线程核心考点全梳理 Objective-C是iOS开发的基石,其中的KVO、Block与Runtime机制,决定了App能否稳定高效运行。KVO的底层依赖Runtime动态创建子类并重写setter,理解这一原理才能应对内存管理、循环引用等一系列工程问题。ARC的引入简化了引用计数,但weak与strong的搭配、Block对self的强捕获,依旧是高频面试考点。内存管理之外,GCD的多线程同步、RunLoop对UI流畅度的影响,同样是iOS性能优化的关键环节。从语言特性到运行时机制,从UI事件传递到系统版本适配,这些知识点既 阅读详情

相关推荐

网易iOS校招笔试题解析:Runtime、内存管理与多线程核心考点

在移动开发中,理解编程语言的底层运行机制与系统框架设计是成为高级工程师的关键。以Objective-C为例,其Runtime消息转发、内存管理中的引用计数与弱引用表、多线程下的GCD死锁与Block存储区域等概念,共同构成了iOS开发的核心知识体系。掌握这些原理不仅有助于写出高性能且稳定的代码,也是应对大厂技术面试、笔试中高频题目的基础。从日常的App性能优化、网络请求到界面流畅度保障,底层原理的工程价值无处不在。以网易校招iOS试卷为例,系统梳理了笔试中常见的算法题、OC特性、多线程、Runloop以及系

weixin_30859423的博客 317

[深入浅出Cocoa]Block编程值得注意的那些事儿

[深入浅出Cocoa]Block编程值得注意的那些事儿 罗朝辉 (http://blog.youkuaiyun.com/kesalin/) 本文遵循“署名-非商业用途-保持一致”创作公用协议 一,前言 在前文《深入浅出Cocoa多线程编程之block与dispatch quene》中我介绍了 block 的一些基本语法以及如何和 GCD 结合的使用示例。block 是在 iOS 4 中...

dawuafang 216

Netblock:能够阻止单个或多个目标的wifi访问的python脚本

#关于# Netblock是一个python脚本,可以阻止通过ARP欺骗从互联网访问连接到同一网关的受害者。 Netblock能够阻止同一网络上的一个,多个或所有受害者。 #如何使用# Netblock在CLI中运行。 通过执行以下命令安装脚本: sudo sh install.sh 通过在CLI中键入netblock来执行Netblock。 这是运行时netblock的外观: -------------------- Connected ips: 0) 10.12.26.1 00:00:0c:07:ac:1a 1) 10.12.26.2 c8:4c:75:6a:55:40 2) 10.12.26.3 c8:4c:75:6a:55:80 3) 10.12.26.7 d0:57:4c:53:7d:42 4) 10.12.26.9

ext4 ext4_map_blocks

struct ext4_map_blocks { ext4_fsblk_t m_pblk; ext4_lblk_t m_lblk; unsigned int m_len; unsigned int m_flags; }; /*  * The ext4_map_blocks() function tries to look up the reques

星空探索 1545

TSMC 65nm PDK衬底文件转换避坑记:从.itf到.itd,解决ADS导入乱码与金属层缺失

本文详细解析了TSMC 65nm PDK衬底文件从.itf到.itd转换过程中的常见问题及解决方案,包括ADS导入乱码与金属层缺失的修复方法。通过环境准备、文件定位、转换操作和问题诊断的完整流程,帮助工程师高效完成衬底文件转换,确保电磁仿真精度。

weixin_30855099的博客 301

基于分位数回归的静态CoVaR计算 案例与代码

原文链接:https://bbs.pinggu.org/thread-7925441-1-1.html 一、静态CoVaR模型介绍 二、资料简介 实现对相关期刊论文进行论文重现,解决实证分析中的技术操作问题。里面包含了每一步详细的步骤,可以方便的利用这个手册解决大部分利用分位数回归计算静态CoVaR的论文的模型实现问题。即从数据下载到模型实现一整条操作步骤。 关键词:【分位数回归】【Co...

ChenQihome9的博客 7503

使用内存文件映射MappedByteBuffer读超大文件可能会遇到的问题

使用内存文件映射MappedByteBuffer读超大文件可能会遇到的问题 这种方式存在一个致命问题就是依然没法读取超大文件(大于 Integer.MAX_VALUE),因为 FileChannel 的 map 方法中 size 参数会有大小限制,源码中发现该参数值大于Integer.MAX_VALUE 时会直接抛出IllegalArgumentException("Size exceeds Integer.MAX_VALUE") 异常,所以对于特别大的文件其依然不适合。本质上是由于 java.nio..

liinux-Talk is cheap,show me the code. 1989

STM32CubeMX实战:TIM定时器中断配置避坑指南(附100ms精准定时代码)

本文深入解析STM32CubeMX配置TIM定时器中断的常见误区与避坑技巧,重点阐述时钟源、预分频器与自动重装载值的精准计算方法。通过实战案例,提供一份可直接复用的100ms精准定时中断代码模板,并分享调试验证方法,帮助开发者构建稳定可靠的嵌入式系统心跳。

grafana8visual的博客 500

常见的闭包代码陷阱

闭包深入理解 如果一个函数在定义的词法环境外运行并记住了定义时的词法环境,这样的现象就可以称作函数闭包 function f(x=0){ var count=x; function getCount(){ return count++; } return getCount } var func = f(10); console.log(func()); // 10 代码如下 let f = ()=

yiyueqinghui的博客 865

Stata实战:用双向固定效应模型分析房价,从数据清洗到结果解读(附完整代码)

本文详细介绍了如何使用Stata实现房价数据的双向固定效应模型分析,从数据清洗到结果解读的全流程。通过具体代码示例,帮助读者掌握面板模型的应用技巧,特别适合经济学和金融学研究者进行房价影响因素分析。

weixin_31528001的博客 277

深入解析i2cdetect:从基础命令到高级读写操作

本文深入解析Linux下i2cdetect工具,从基础安装、总线扫描到高级读写操作。作为i2c-tools的核心组件,i2cdetect是硬件调试的得力侦探,能快速识别I2C总线上的设备地址。文章还涵盖了i2cdump、i2cset、i2cget等进阶工具,并介绍了如何在C程序中调用读写接口进行嵌入式开发,是掌握I2C通信调试的实用指南。

weixin_29189987的博客 371

block引发的陷阱

  block在项目的开发中使用时非常频繁的,苹果官方也极力推荐使用block。其实,究其本质,block就是指向结构体的指针(可利用运行时机制查看底层生成的c代码)。然而在使用block时会存在很多陷阱(主要是内存泄露),这些都是必须要注意的。接下来举个简单的实例: 假设一个类,拥有两个属性:block和name; 1 // 2 // SZBlocTest.h 3...

dmsz98807的博客 210

数据结构:五岔路口交通管理红绿灯设计

我们可以知道这是一个“图”的着色问题,首先要考虑,为什么可行路线是13条?当然可以通过穷举得出,如果用公式:则是: 或者 因为一条路线必须包括一出一进两个方向 第一种答案的解法: 一、固定E为入口,则有C_4^1=4的可能; 二、除去E(因为E只有一个入向),剩下ABCD四个路口,然后因为C只有一个出方向,所以从剩下 的ABD中选择一个入口,即C_3^1; 三、再从剩下的三个出口任选

潘猫猫的世界 1万+

Block回调理解

block应用实例如下:https://blog.youkuaiyun.com/huangfei711/article/details/73928495 Block循环引用的问题: bVC.callBackBlock = ^(NSString *text){ NSLog(@"text is %@",text); self.label.text = text; }; 代码self.label.text = text;,在 Block 中引用 se...

weixin_40282090的博客 356

企业级大模型落地:私有化部署的安全合规全流程实战指南

摘要:本文针对企业在大模型私有化部署中面临的数据泄露、合规风险及系统稳定性难题,提出一套覆盖数据安全、模型合规、审计追踪与灾备方案的全流程解决方案。通过双加密管道(国密SM4传输加密+AES-GCM存储加密)、SGX可信执行环境等技术,实现数据全生命周期保护;结合等保2.0三级要求与《生成式AI服务管理暂行办法》,提供合规性实施细则;并给出本地GPU集群、混合云、边缘推理盒子三种企业级部署方案及成本模型。

专注于人工智能、软件开发、工控自动化、工厂数字化及智能化等领域,希望和大家共同进步! 907

【目标跟踪】多目标跟踪的评价工具motmetrics怎么使用?

多目标跟踪,通过motmetrics测试评价对应的视频。

聿默的博客 5368

block-循环引用

在ARC机制下,app的内存管理由操作系统进行管理,不需要程序员手动的管理内存,方便了开发.尽管,自动释放内存很方便,但是并非绝对安全,绝对不会产生内存泄露。大部分导致iOS对象无法按预期释放的一个无形杀手是——循环引用。循环引用可以简单理解为A引用了B,而B又引用了A,双方都同时保持对方的一个强引用,导致任何时候引用计数都不为0,始终无法释放。 下面...

weixin_30940783的博客 164

jdk-8u144-linux-x64.tar.gz.

该资源为搭建Linux系统Java环境用,资源的后缀为tar.gz格式的压缩包

京东校招iOS笔试题复盘:高频考点、内存管理与手写编程题全解析

在iOS开发岗位的校招笔试中,除了考察Objective-C基础语法,更看重对内存管理、多线程、Runtime等底层机制的理解。以京东2019校招笔试为例,试卷围绕Block循环引用、GCD队列特性、Runloop模式切换等高频考点展开,要求开发者不仅会调用API,还能解释其原理与陷阱。编程题则聚焦链表反转、数组去重等基本功,考察手写代码的规范性与边界处理能力。从基础语言到系统机制,再到组件化与工程化实践,系统掌握这些核心技术点,能够帮助求职者更从容地应对大厂笔试与后续面试。本文结合真题复盘与工程经验,梳理

weixin_34015336的博客 410

IEM模型,地表粗糙度模拟

利用微波反演土壤水分时候,利用IEM模型可以实现对随机地表的模拟

假人有限元模型

假人模型,车辆碰撞假人有限元模型,适合做乘员安全的研究者利用

爱奇艺iOS校招笔试复盘:底层原理考点与答题思路

在移动开发领域,iOS工程师的面试与笔试往往绕不开对底层原理的考察。从Objective-C的对象模型到运行时消息机制,再到内存管理与多线程并发,这些基础能力直接决定了工程师能否应对复杂业务场景下的性能优化与架构设计。理解内存管理中的引用计数、Block对变量的捕获规则,以及RunLoop的运行模式,能帮助开发者写出更稳定、更高效的代码。在实际工程中,视频类App的列表流畅度、网络请求与缓存策略都依赖这些底层知识的扎实掌握。爱奇艺2020校招iOS方向笔试正是以此为重心,通过选择题、简答题和编程题全面检验候

weixin_33600816的博客 294

android 蓝牙通讯demo

android 下蓝牙通讯demo,实现了发现蓝牙,使能蓝牙,通过蓝牙发送消息。

iOS内存管理面试全解:从引用计数到循环引用实战

移动开发中,内存管理是iOS开发者必须掌握的核心技能,它决定了应用的稳定性和性能表现。iOS采用引用计数机制管理对象生命周期,区别于Java的垃圾回收,ARC在编译期自动插入内存管理代码,而weak、strong、copy等修饰符则体现了不同持有策略的设计意图。理解对象的内存布局、Tagged Pointer以及Block的内存类型,是分析循环引用和内存泄漏的基础。在实际工程中,循环引用常见于Block、Delegate和Timer场景,而autoreleasepool的合理使用能有效控制峰值内存。通过Xc

weixin_33836874的博客 193

iOS开发进阶:3个CompletionHandler核心技巧,彻底优化你的回调机制

在iOS应用开发中,回调机制是我们处理异步操作的核心技术之一。今天,我们将深入探讨如何通过**CompletionHandler**来优雅地优化回调机制,提升代码的可读性和维护性。无论是网络请求、用户通知还是IM即时通讯场景,CompletionHandler都能让你的代码更加清晰和高效。😊 ## 📱 为什么需要优化回调机制? 在iOS开发中,我们经常需要处理异步操作,比如网络请求、文件读

gitblog_00613的博客 392

2015搜狗iOS笔试真题解析:从内存管理到Block的核心考点

移动开发面试中,底层原理的考察从未缺席,尤其是内存管理中的引用计数、autorelease机制,以及Block捕获变量与循环引用等关键概念。理解这些原理不仅是应对笔试的基础,更直接影响App在性能敏感场景下的稳定性。从属性修饰符的正确选择,到GCD主线程死锁的分析思路,这些技术点贯穿于iOS开发者的日常排障与代码优化中。本文以搜狗经典笔试真题为样本,系统梳理高频考点与解题思路,帮助开发者从原理层面构建完整的知识框架,从容应对大厂面试中的语言特性、多线程与运行时机制等核心问题。

weixin_32667955的博客 210

IOS ARC跟block那点事

【ARC的特性】   ARC下,所有NSObject类型指针,   1. 默认为__strong类型   2. 可以显示的指定为__weak类型,__weak类型指针在所指向对象销毁后会自动置为nil   3. __autorelesing类型用于inout参数类型   ARC下,当一个函数返回一个NSObject指针时,编译器会帮我们实现autorelease调用。

Ale 432
上一篇: Interface builder was unable to determine the type of xxx.xib
下一篇: IOS成长之路-检测耳机插入/拔出
光速之年
博客等级 码龄12年 31粉丝 97原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值