BlocksKit动态代理机制:A2DynamicDelegate深度解析

BlocksKit动态代理机制:A2DynamicDelegate深度解析

【免费下载链接】BlocksKit The Objective-C block utilities you always wish you had. 【免费下载链接】BlocksKit 项目地址: https://gitcode.com/gh_mirrors/bl/BlocksKit

A2DynamicDelegate是BlocksKit框架中实现动态代理机制的核心组件,通过Objective-C运行时技术和NSProxy设计模式,将传统delegate模式转换为基于block的编程范式。本文深入解析其架构原理、消息转发机制、与传统委托模式的对比、协议方法的块实现机制,并提供多个自定义动态代理的实践案例,全面展示这一技术的实现细节和应用价值。

A2DynamicDelegate架构原理

A2DynamicDelegate是BlocksKit框架中动态代理机制的核心组件,它通过巧妙的Objective-C运行时技术和NSProxy设计模式,实现了将传统的delegate模式转换为基于block的现代化编程范式。其架构设计体现了高度的模块化和扩展性,为开发者提供了优雅的API接口。

核心架构设计

A2DynamicDelegate采用分层架构设计,主要包含以下几个核心组件:

mermaid

消息转发机制

A2DynamicDelegate继承自NSProxy,这是其实现动态代理的基础。NSProxy作为根类,专门用于实现消息转发,其核心工作原理如下:

mermaid

选择器映射表设计

A2DynamicDelegate使用NSMapTable来管理选择器与block实现的映射关系,这种设计具有以下优势:

特性实现方式优势
键类型NSPointerFunctionsOpaqueMemory直接使用SEL指针,避免NSString转换开销
值类型NSPointerFunctionsStrongMemory强引用block,确保生命周期
相等比较自定义selectorsEqual函数使用sel_isEqual进行高效的SEL比较
描述函数自定义selectorDescribe函数便于调试和日志输出
// NSMapTable的自定义配置
+ (instancetype)bk_selectorsToStrongObjectsMapTable
{
    NSPointerFunctions *selectors = [NSPointerFunctions 
        pointerFunctionsWithOptions:NSPointerFunctionsOpaqueMemory|NSPointerFunctionsOpaquePersonality];
    selectors.isEqualFunction = selectorsEqual;
    selectors.descriptionFunction = selectorDescribe;
    
    NSPointerFunctions *strongObjects = [NSPointerFunctions 
        pointerFunctionsWithOptions:NSPointerFunctionsStrongMemory|NSPointerFunctionsObjectPersonality];
    
    return [[NSMapTable alloc] initWithKeyPointerFunctions:selectors 
                                      valuePointerFunctions:strongObjects 
                                                   capacity:1];
}

协议一致性验证

A2DynamicDelegate通过运行时API验证block实现与协议方法的兼容性:

static inline BOOL protocol_declaredSelector(Protocol *protocol, SEL selector)
{
    for (int i = 0; i < 4; i++) {
        BOOL required = 1 & (i);
        BOOL instance = 1 & (i >> 1);

        struct objc_method_description description = 
            protocol_getMethodDescription(protocol, selector, required, instance);
        if (description.name) {
            return YES;
        }
    }
    return NO;
}

这种方法确保了只有协议中声明的方法才能被动态代理处理,避免了运行时错误。

类方法代理支持

A2DynamicDelegate通过A2DynamicClassDelegate子类支持类方法的动态代理:

mermaid

方法签名生成

A2DynamicDelegate能够动态生成正确的方法签名,这是消息转发机制的关键:

- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
{
    A2BlockInvocation *invocation = nil;
    if ((invocation = [self.invocationsBySelectors bk_objectForSelector:aSelector]))
        return invocation.methodSignature;
    else if ([self.realDelegate methodSignatureForSelector:aSelector])
        return [self.realDelegate methodSignatureForSelector:aSelector];
    else if (class_respondsToSelector(object_getClass(self), aSelector))
        return [object_getClass(self) methodSignatureForSelector:aSelector];
    return [[NSObject class] methodSignatureForSelector:aSelector];
}

这种分层查找机制确保了方法签名的正确性,优先使用block实现的方法签名,其次是realDelegate,最后是类本身或NSObject的默认实现。

A2DynamicDelegate的架构设计充分体现了Objective-C运行时的强大能力,通过NSProxy、NSMapTable、Protocol验证等技术的有机结合,实现了高效、安全、易用的动态代理机制,为BlocksKit框架提供了坚实的基础设施支持。

动态代理与传统委托模式对比

在Objective-C开发中,委托模式(Delegate Pattern)是一种极其常见的设计模式,而BlocksKit的A2DynamicDelegate则提供了一种创新的动态代理实现方式。让我们深入分析这两种模式的本质区别和各自的优劣势。

实现机制对比

传统委托模式

传统委托模式基于协议(Protocol)和对象引用,需要创建专门的委托类来实现协议方法:

// 传统委托实现
@interface MyViewController : UIViewController <UITableViewDelegate>
@end

@implementation MyViewController

- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
    return 44.0f;
}

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
    NSLog(@"Selected row: %ld", indexPath.row);
}

@end

// 设置委托
tableView.delegate = self;
A2DynamicDelegate动态代理

A2DynamicDelegate使用NSProxy和消息转发机制动态实现委托方法:

// 动态代理实现
A2DynamicDelegate *dd = tableView.bk_dynamicDelegate;

[dd implementMethod:@selector(tableView:heightForRowAtIndexPath:) 
         withBlock:^CGFloat(UITableView *tableView, NSIndexPath *indexPath) {
    return 44.0f;
}];

[dd implementMethod:@selector(tableView:didSelectRowAtIndexPath:) 
         withBlock:^(UITableView *tableView, NSIndexPath *indexPath) {
    NSLog(@"Selected row: %ld", indexPath.row);
}];

tableView.delegate = dd;

架构设计对比

让我们通过架构图来理解两种模式的核心差异:

mermaid

性能特征对比

特性维度传统委托模式A2DynamicDelegate动态代理
内存占用较低,固定对象引用较高,需要维护Block和映射表
执行速度直接方法调用,最快消息转发,稍有开销
灵活性需要预定义类和方法运行时动态添加/移除
代码量需要完整类实现简洁的Block语法
维护成本分散在不同文件中集中在一个作用域内

代码组织方式对比

传统委托的代码分散问题
// ViewController.h
@interface ViewController : UIViewController <UITableViewDelegate, UITableViewDataSource>

// ViewController.m
@implementation ViewController

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
    return self.data.count;
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    // 单元格配置代码
}

// 可能还有其他多个协议方法分布在文件中...
动态代理的代码集中优势
// 在同一个方法中集中配置所有委托行为
- (void)setupTableViewDelegate {
    A2DynamicDelegate *delegate = self.tableView.bk_dynamicDelegate;
    A2DynamicDelegate *dataSource = self.tableView.bk_dynamicDataSource;
    
    // 数据源方法
    [dataSource implementMethod:@selector(tableView:numberOfRowsInSection:)
                      withBlock:^NSInteger(UITableView *tableView, NSInteger section) {
        return self.data.count;
    }];
    
    [dataSource implementMethod:@selector(tableView:cellForRowAtIndexPath:)
                      withBlock:^UITableViewCell *(UITableView *tableView, NSIndexPath *indexPath) {
        // 单元格配置代码
    }];
    
    // 委托方法
    [delegate implementMethod:@selector(tableView:didSelectRowAtIndexPath:)
                    withBlock:^(UITableView *tableView, NSIndexPath *indexPath) {
        [self handleRowSelection:indexPath];
    }];
    
    self.tableView.delegate = delegate;
    self.tableView.dataSource = dataSource;
}

适用场景分析

传统委托更适合:
  1. 复杂业务逻辑:需要多个方法协同工作的场景
  2. 状态管理:委托对象需要维护内部状态的场景
  3. 代码复用:多个对象共享相同委托实现的场景
  4. 大型项目:需要严格类型检查和编译时验证的项目
动态代理更适合:
  1. 简单回调:只需要处理少数几个方法的场景
  2. 快速原型:需要快速实现功能验证的场景
  3. 代码简洁:希望减少文件数量和代码分散度的场景
  4. 闭包风格:偏好函数式编程和Block语法的开发团队

内存管理对比

mermaid

传统委托使用弱引用避免循环引用,而A2DynamicDelegate需要特别注意Block中的self引用问题:

// 正确使用weakself避免循环引用
__weak typeof(self) weakSelf = self;
[delegate implementMethod:@selector(tableView:didSelectRowAtIndexPath:)
                withBlock:^(UITableView *tableView, NSIndexPath *indexPath) {
    __strong typeof(weakSelf) strongSelf = weakSelf;
    [strongSelf handleSelection:indexPath];
}];

开发体验对比

从开发者的角度来看,两种模式提供了截然不同的编程体验:

传统委托的开发流程:

  1. 定义协议方法
  2. 创建委托类
  3. 实现所有必需方法
  4. 设置委托关系
  5. 在多个文件间跳转维护

动态代理的开发流程:

  1. 获取动态代理实例
  2. 使用Block实现所需方法
  3. 设置委托关系
  4. 所有代码集中在一个作用域

这种对比明显展示了A2DynamicDelegate在开发效率和代码组织方面的优势,特别是在快速迭代和简单场景中表现突出。

通过以上全面的对比分析,我们可以清楚地看到A2DynamicDelegate动态代理机制在简化代码、提高开发效率方面的显著优势,同时也理解了传统委托模式在复杂场景下的不可替代性。开发者可以根据具体项目需求灵活选择最适合的模式。

协议方法的块实现机制

BlocksKit的A2DynamicDelegate核心创新在于将传统的协议方法委托模式转换为基于block的声明式编程范式。这种机制通过运行时动态代理技术,实现了协议方法与Objective-C块的无缝桥接,为开发者提供了更加直观和灵活的委托处理方式。

块方法注册与存储架构

A2DynamicDelegate使用NSMapTable来管理选择器与块实现的映射关系,这种设计既保证了类型安全又提供了高效的查找性能:

// NSMapTable的自定义配置
+ (instancetype)bk_selectorsToStrongObjectsMapTable
{
    NSPointerFunctions *selectors = [NSPointerFunctions pointerFunctionsWithOptions:
        NSPointerFunctionsOpaqueMemory|NSPointerFunctionsOpaquePersonality];
    selectors.isEqualFunction = selectorsEqual;
    selectors.descriptionFunction = selectorDescribe;
    
    NSPointerFunctions *strongObjects = [NSPointerFunctions pointerFunctionsWithOptions:
        NSPointerFunctionsStrongMemory|NSPointerFunctionsObjectPersonality];
    
    return [[NSMapTable alloc] initWithKeyPointerFunctions:selectors 
                                      valuePointerFunctions:strongObjects 
                                                   capacity:1];
}

这种映射表的设计具有以下特点:

  • 选择器作为不透明指针:避免不必要的对象封装,提高性能
  • 强引用对象存储:确保块实现不会被意外释放
  • 自定义相等性判断:使用sel_isEqual进行精确的选择器匹配

方法实现的核心流程

当调用implementMethod:withBlock:方法时,系统执行以下关键步骤:

mermaid

具体的实现代码如下:

- (void)implementMethod:(SEL)selector withBlock:(id)block
{
    NSCAssert(selector, @"Attempt to implement or remove NULL selector");
    BOOL isClassMethod = self.isClassProxy;

    if (!block) {
        [self.invocationsBySelectors bk_removeObjectForSelector:selector];
        return;
    }

    // 获取协议方法的类型描述
    struct objc_method_description methodDescription = 
        protocol_getMethodDescription(self.protocol, selector, YES, !isClassMethod);
    if (!methodDescription.name) 
        methodDescription = protocol_getMethodDescription(self.protocol, selector, NO, !isClassMethod);

    A2BlockInvocation *inv = nil;
    if (methodDescription.name) {
        // 使用协议定义的方法签名
        NSMethodSignature *protoSig = [NSMethodSignature signatureWithObjCTypes:methodDescription.types];
        inv = [[A2BlockInvocation alloc] initWithBlock:block methodSignature:protoSig];
    } else {
        // 自动推断块签名
        inv = [[A2BlockInvocation alloc] initWithBlock:block];
    }

    [self.invocationsBySelectors bk_setObject:inv forSelector:selector];
}

类型安全与签名验证

A2DynamicDelegate在块注册过程中执行严格的类型安全检查:

检查类型实现机制错误处理
选择器有效性NSCAssert(selector, ...)断言失败,开发阶段立即暴露问题
协议方法存在性protocol_getMethodDescription支持可选和必需方法的自动检测
签名兼容性A2BlockInvocation初始化验证抛出NSInvalidArgumentException异常

方法调用转发机制

当代理对象收到方法调用时,A2DynamicDelegate通过以下流程进行处理:

mermaid

具体的转发实现:

- (void)forwardInvocation:(NSInvocation *)outerInv
{
    SEL selector = outerInv.selector;
    A2BlockInvocation *innerInv = nil;
    if ((innerInv = [self.invocationsBySelectors bk_objectForSelector:selector])) {
        [innerInv invokeWithInvocation:outerInv];
    } else if ([self.realDelegate respondsToSelector:selector]) {
        [outerInv invokeWithTarget:self.realDelegate];
    }
}

响应链完整性保障

为了确保动态代理对象能够正确响应所有协议方法,A2DynamicDelegate实现了完整的响应链机制:

- (BOOL)respondsToSelector:(SEL)selector
{
    return [self.invocationsBySelectors bk_objectForSelector:selector] ||
           class_respondsToSelector(object_getClass(self), selector)   ||
           (protocol_declaredSelector(self.protocol, selector) && 
            [self.realDelegate respondsToSelector:selector]);
}

这种三重检查机制确保了:

  1. 块实现优先:如果存在块实现,优先响应
  2. 类方法支持:支持类方法的动态代理
  3. 后备委托:在没有块实现时 fallback 到真实委托

块签名的智能处理

A2BlockInvocation负责处理块签名与方法签名的转换,其核心能力包括:

功能实现方式应用场景
自动签名推断+methodSignatureForBlock:未在协议中明确定义的方法
签名验证-initWithBlock:methodSignature:协议方法的类型安全调用
参数适配运行时类型编码解析处理不同类型的参数传递

这种机制使得开发者可以灵活地使用block来实现协议方法,无需担心类型不匹配的问题,同时享受编译时的类型安全检查。

通过这种精妙的块实现机制,A2DynamicDelegate不仅提供了优雅的API设计,更重要的是建立了可靠的类型安全体系,使得基于block的委托编程既简单又安全。

自定义动态代理实践案例

A2DynamicDelegate的强大之处在于其灵活性和可扩展性,开发者可以基于实际需求创建自定义的动态代理实现。下面通过几个具体的实践案例来展示如何在实际项目中应用A2DynamicDelegate。

自定义网络请求代理

在网络请求场景中,我们经常需要处理各种回调,使用A2DynamicDelegate可以极大地简化代码结构:

// 自定义网络请求处理器
@interface CustomNetworkHandler : A2DynamicDelegate
@end

@implementation CustomNetworkHandler

- (void)handleRequestSuccess:(NSData *)data response:(NSURLResponse *)response {
    // 自定义处理逻辑
    NSLog(@"请求成功,数据长度: %lu", (unsigned long)data.length);
    
    // 可以调用原始的block实现
    id successBlock = [self blockImplementationForMethod:@selector(connection:didReceiveData:)];
    if (successBlock) {
        void (^block)(NSURLConnection *, NSData *) = successBlock;
        block(nil, data);
    }
}

@end

// 使用示例
NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:@"https://api.example.com/data"]];
NSURLConnection *connection = [[NSURLConnection alloc] initWithRequest:request delegate:nil startImmediately:NO];

// 获取自定义动态代理
CustomNetworkHandler *handler = [connection bk_dynamicDelegateForProtocol:@protocol(NSURLConnectionDelegate)];

// 实现自定义方法
[handler implementMethod:@selector(connection:didReceiveData:) withBlock:^(NSURLConnection *connection, NSData *data) {
    NSLog(@"接收到数据: %@", [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]);
}];

// 设置代理
connection.delegate = handler;
[connection start];

自定义表格数据源

对于复杂的表格视图,可以创建专门的数据源代理来处理各种数据逻辑:

// 自定义表格数据源
@interface CustomTableViewDataSource : A2DynamicDelegate

@property (nonatomic, strong) NSArray<NSArray *> *sectionData;

@end

@implementation CustomTableViewDataSource

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
    if (section < self.sectionData.count) {
        return self.sectionData[section].count;
    }
    return 0;
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    static NSString *cellIdentifier = @"CustomCell";
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier];
    if (!cell) {
        cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:cellIdentifier];
    }
    
    if (indexPath.section < self.sectionData.count && 
        indexPath.row < self.sectionData[indexPath.section].count) {
        cell.textLabel.text = self.sectionData[indexPath.section][indexPath.row];
    }
    
    return cell;
}

@end

// 使用流程
mermaid
flowchart TD
    A[创建UITableView] --> B[初始化CustomTableViewDataSource]
    B --> C[配置sectionData数据]
    C --> D[实现特定方法的block回调]
    D --> E[设置tableView.dataSource]
    E --> F[刷新表格数据]

自定义手势识别器代理

手势识别器的代理方法通常需要处理多个回调,使用自定义代理可以更好地组织代码:

// 自定义手势代理
@interface CustomGestureDelegate : A2DynamicDelegate

@property (nonatomic, weak) UIView *targetView;
@property (nonatomic, assign) CGPoint startLocation;

@end

@implementation CustomGestureDelegate

- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer {
    NSLog(@"手势即将开始");
    return YES;
}

- (void)handlePanGesture:(UIPanGestureRecognizer *)gesture {
    CGPoint translation = [gesture translationInView:self.targetView];
    
    switch (gesture.state) {
        case UIGestureRecognizerStateBegan:
            self.startLocation = self.targetView.center;
            break;
        case UIGestureRecognizerStateChanged:
            self.targetView.center = CGPointMake(self.startLocation.x + translation.x, 
                                               self.startLocation.y + translation.y);
            break;
        default:
            break;
    }
}

@end

// 使用示例
UIPanGestureRecognizer *panGesture = [[UIPanGestureRecognizer alloc] init];
CustomGestureDelegate *gestureDelegate = [panGesture bk_dynamicDelegateForProtocol:@protocol(UIGestureRecognizerDelegate)];

gestureDelegate.targetView = draggableView;
[panGesture addTarget:gestureDelegate action:@selector(handlePanGesture:)];
[draggableView addGestureRecognizer:panGesture];

自定义文本输入代理

对于复杂的文本输入验证和处理,可以创建专门的文本代理:

// 自定义文本输入代理
@interface CustomTextInputDelegate : A2DynamicDelegate

@property (nonatomic, strong) NSCharacterSet *allowedCharacterSet;
@property (nonatomic, assign) NSInteger maxLength;

@end

@implementation CustomTextInputDelegate

- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string {
    
    // 长度限制
    if (self.maxLength > 0) {
        NSInteger newLength = textField.text.length + string.length - range.length;
        if (newLength > self.maxLength) {
            return NO;
        }
    }
    
    // 字符集验证
    if (self.allowedCharacterSet) {
        NSCharacterSet *characterSet = [NSCharacterSet characterSetWithCharactersInString:string];
        if (![self.allowedCharacterSet isSupersetOfSet:characterSet]) {
            return NO;
        }
    }
    
    return YES;
}

@end

// 配置表格
table
| 属性 | 类型 | 说明 |
|------|------|------|
| allowedCharacterSet | NSCharacterSet | 允许输入的字符集 |
| maxLength | NSInteger | 最大输入长度限制 |
| targetView | UIView | 关联的视图对象 |

复合代理模式

在某些复杂场景中,可能需要同时处理多个协议的代理方法:

// 复合代理处理器
@interface CompositeDelegate : A2DynamicDelegate

@property (nonatomic, weak) id originalDelegate;
@property (nonatomic, strong) NSMutableDictionary *handlerBlocks;

@end

@implementation CompositeDelegate

- (instancetype)initWithProtocol:(Protocol *)protocol {
    self = [super initWithProtocol:protocol];
    if (self) {
        _handlerBlocks = [NSMutableDictionary dictionary];
    }
    return self;
}

- (void)addHandlerForSelector:(SEL)selector block:(id)block {
    NSString *selectorString = NSStringFromSelector(selector);
    self.handlerBlocks[selectorString] = [block copy];
    
    [self implementMethod:selector withBlock:^(id sender, ...) {
        id block = self.handlerBlocks[selectorString];
        if (block) {
            // 调用存储的block
            ((void(^)(id))block)(sender);
        }
    }];
}

- (BOOL)respondsToSelector:(SEL)aSelector {
    return [super respondsToSelector:aSelector] || 
           [self.originalDelegate respondsToSelector:aSelector];
}

- (void)forwardInvocation:(NSInvocation *)anInvocation {
    if ([self.originalDelegate respondsToSelector:anInvocation.selector]) {
        [anInvocation invokeWithTarget:self.originalDelegate];
    } else {
        [super forwardInvocation:anInvocation];
    }
}

@end

性能优化建议

在使用自定义动态代理时,需要注意以下性能优化点:

  1. 避免过度创建:重用代理实例,特别是在表格视图等频繁使用的场景中
  2. 合理使用block:注意block的内存管理,避免循环引用
  3. 方法签名缓存:对于频繁调用的方法,可以缓存方法签名提高性能
  4. 线程安全:在多线程环境中使用时确保线程安全
// 性能优化示例
static NSCache *delegateCache;

+ (void)initialize {
    delegateCache = [[NSCache alloc] init];
    delegateCache.countLimit = 20; // 限制缓存数量
}

- (id)bk_optimizedDynamicDelegateForProtocol:(Protocol *)protocol {
    NSString *cacheKey = [NSString stringWithFormat:@"%@_%@", 
                         NSStringFromClass([self class]), 
                         NSStringFromProtocol(protocol)];
    
    id delegate = [delegateCache objectForKey:cacheKey];
    if (!delegate) {
        delegate = [self bk_dynamicDelegateForProtocol:protocol];
        [delegateCache setObject:delegate forKey:cacheKey];
    }
    
    return delegate;
}

通过这些实践案例,我们可以看到A2DynamicDelegate在自定义代理实现中的强大灵活性。无论是简单的回调处理还是复杂的业务逻辑,都可以通过创建自定义的动态代理来优雅地实现,大大提高了代码的可维护性和可读性。

总结

A2DynamicDelegate作为BlocksKit框架的核心组件,通过巧妙的运行时技术和NSProxy设计模式,成功实现了将传统委托模式转换为基于block的现代化编程范式。其分层架构设计、高效的消息转发机制、类型安全的块实现方法,以及灵活的扩展性,为开发者提供了优雅而强大的动态代理解决方案。通过与传统委托模式的全面对比和多个实践案例的展示,我们可以看到A2DynamicDelegate在简化代码结构、提高开发效率方面的显著优势,特别是在需要快速原型开发和代码集中的场景中表现突出。这一技术不仅展示了Objective-C运行时的强大能力,更为iOS/macOS开发提供了创新的编程范式选择。

【免费下载链接】BlocksKit The Objective-C block utilities you always wish you had. 【免费下载链接】BlocksKit 项目地址: https://gitcode.com/gh_mirrors/bl/BlocksKit

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值