IOS中self.xx 和 _xx 的区别

本文深入探讨了Objective-C与Swift中实例变量与property的使用方式及区别,通过实例演示了两者在类定义、访问、生命周期等方面的不同,并强调了setter和getter方法在管理实例变量时的作用。

如果引用的话不会有区别,是同一个指针;

如果赋值是有区别的 self.xx = oo  首先把xx.retaincount -1,然后retain oo  _xx复制直接指向oo 不存在retain这一

步步。其实,前者调用该类的setter或getter方法,后者直接获取自己的实例变量。property 和 instance variable 是有区

别的。

前者声明后,如果没有readonly修饰的话,该类获得两个方法,一个是setter和getter。property声明后,该类会获

得一个同名但前面多了一个下划线的实例变量。setter和getter是访问这个实例变量的方法。

在类的m文件里可以直接用实例变量名来访问自身的实例变量,但是setter和getter方法不会被调用。外部想用该类的实

例变量需要用getter和setter方法。 

import socket import struct import argparse import random import string import time import threading from collections import defaultdict " python mdns_responder.py --iface_ip 192.168.0.100 " MDNS_ADDR = "224.0.0.251" MDNS_PORT = 5353 class DNSError(Exception): """DNS错误基类""" pass def generate_random_service_id(length=8): """生成8位随机服务ID(大写字母+数字)""" return ''.join(random.choices(string.ascii_uppercase + string.digits, k=length)) def generate_random_hostname_suffix(length=8): """生成随机主机名后缀(数字)""" return ''.join(random.choices(string.digits, k=length)) def parse_dns_name(data: bytes, offset: int) -> tuple: """ 解析DNS名称(支持压缩指针) 返回: (域名, 新偏移量) """ labels = [] traversed = set() while offset < len(data): length = data[offset] offset += 1 if length == 0: # 结束标记 break # 处理压缩指针 if length & 0xC0 == 0xC0: if offset >= len(data): raise DNSError("压缩指针不完整") ptr_offset = ((length & 0x3F) << 8) | data[offset] offset += 1 if ptr_offset in traversed: break traversed.add(ptr_offset) if ptr_offset >= len(data): break name_part, _ = parse_dns_name(data, ptr_offset) labels.append(name_part) break # 处理普通标签 end = offset + length if end > len(data): break labels.append(data[offset:end].decode('utf-8', 'ignore')) offset = end return '.'.join(labels), offset def build_dns_name(name: str, compress_dict: dict, base_offset: int) -> bytes: """ 构建DNS名称(支持压缩) compress_dict: {域名: 偏移量} base_offset: 当前数据起始偏移 """ encoded = b'' parts = name.split('.') for i in range(len(parts)): partial = '.'.join(parts[i:]) # 应用压缩 if len(partial) > 3 and partial in compress_dict: ptr = compress_dict[partial] if ptr < 0x4000: encoded += struct.pack('!H', 0xC000 | ptr) return encoded # 添加标签 label = parts[i] if not 1 <= len(label) <= 63: raise DNSError(f"无效标签长度: {len(label)}") encoded += bytes([len(label)]) + label.encode('utf-8') # 注册压缩点 current = '.'.join(parts[i:]) pos = base_offset + len(encoded) - len(label) - 1 if len(current) > 3 and current not in compress_dict: compress_dict[current] = pos encoded += b'\x00' return encoded def create_service_response(transaction_id: int, service_type: str, instance_name: str, host_name: str, ip: str, port: int) -> bytes: """ 创建单服务响应包 """ compress_dict = {} parts = [] current_offset = 12 # DNS头部长12字节 # === DNS头部 === flags = 0x8400 # QR=1, AA=1 qdcount = 0 # 无查询部分 ancount = 3 # PTR + SRV + A header = struct.pack("!HHHHHH", transaction_id, flags, qdcount, ancount, 0, 0) parts.append(header) # === PTR记录 === ptr_name = build_dns_name(service_type, compress_dict, current_offset) ptr_data = build_dns_name(instance_name, compress_dict, current_offset + len(ptr_name) + 10) ptr_record = ( ptr_name + struct.pack("!HHIH", 12, 1, 4500, len(ptr_data)) + # TYPE=PTR, CLASS=IN, TTL=120 ptr_data ) parts.append(ptr_record) current_offset += len(ptr_record) # === SRV记录 === srv_name = build_dns_name(instance_name, compress_dict, current_offset) srv_data = struct.pack("!HHH", 0, 0, port) # 优先级, 权重, 端口 srv_data += build_dns_name(host_name, compress_dict, current_offset + len(srv_name) + 10 + len(srv_data)) srv_record = ( srv_name + struct.pack("!HHIH", 33, 1, 120, len(srv_data)) + # TYPE=SRV srv_data ) parts.append(srv_record) current_offset += len(srv_record) # === A记录 === a_name = build_dns_name(host_name, compress_dict, current_offset) a_data = socket.inet_aton(ip) a_record = ( a_name + struct.pack("!HHIH", 1, 1, 120, 4) + # TYPE=A a_data ) parts.append(a_record) return b''.join(parts) def create_pair_response(transaction_id: int, airplay_instance: str, raop_instance: str, host_name: str, ip: str) -> bytes: """ 创建服务对响应包(AirPlay + RAOP) """ compress_dict = {} parts = [] current_offset = 12 # === DNS头部 === flags = 0x8400 qdcount = 0 ancount = 5 # 2 PTR + 2 SRV + 1 A header = struct.pack("!HHHHHH", transaction_id, flags, qdcount, ancount, 0, 0) parts.append(header) # === AirPlay PTR === ptr_ap_name = build_dns_name("_airplay._tcp.local", compress_dict, current_offset) ptr_ap_data = build_dns_name(airplay_instance, compress_dict, current_offset + len(ptr_ap_name) + 10) ptr_ap_record = ptr_ap_name + struct.pack("!HHIH", 12, 1, 120, len(ptr_ap_data)) + ptr_ap_data parts.append(ptr_ap_record) current_offset += len(ptr_ap_record) # === AirPlay SRV === srv_ap_name = build_dns_name(airplay_instance, compress_dict, current_offset) srv_ap_data = struct.pack("!HHH", 0, 0, 5000) srv_ap_data += build_dns_name(host_name, compress_dict, current_offset + len(srv_ap_name) + 10 + len(srv_ap_data)) srv_ap_record = srv_ap_name + struct.pack("!HHIH", 33, 1, 120, len(srv_ap_data)) + srv_ap_data parts.append(srv_ap_record) current_offset += len(srv_ap_record) # === RAOP PTR === ptr_raop_name = build_dns_name("_raop._tcp.local", compress_dict, current_offset) ptr_raop_data = build_dns_name(raop_instance, compress_dict, current_offset + len(ptr_raop_name) + 10) ptr_raop_record = ptr_raop_name + struct.pack("!HHIH", 12, 1, 120, len(ptr_raop_data)) + ptr_raop_data parts.append(ptr_raop_record) current_offset += len(ptr_raop_record) # === RAOP SRV === srv_raop_name = build_dns_name(raop_instance, compress_dict, current_offset) srv_raop_data = struct.pack("!HHH", 0, 0, 7000) srv_raop_data += build_dns_name(host_name, compress_dict, current_offset + len(srv_raop_name) + 10 + len(srv_raop_data)) srv_raop_record = srv_raop_name + struct.pack("!HHIH", 33, 1, 120, len(srv_raop_data)) + srv_raop_data parts.append(srv_raop_record) current_offset += len(srv_raop_record) # === 共享A记录 === a_name = build_dns_name(host_name, compress_dict, current_offset) a_data = socket.inet_aton(ip) a_record = a_name + struct.pack("!HHIH", 1, 1, 120, 4) + a_data parts.append(a_record) return b''.join(parts) class MDNSResponder: def __init__(self, iface_ip: str): """mDNS响应器初始化""" self.iface_ip = iface_ip self.services = [] # 所有服务对 self.service_map = defaultdict(list) # 服务类型到实例的映射 self.running = False # 创建socket self.sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP) self.sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) # 多播设置 self.sock.setsockopt(socket.IPPROTO_IP, socket.IP_MULTICAST_TTL, 255) self.sock.setsockopt(socket.IPPROTO_IP, socket.IP_MULTICAST_IF, socket.inet_aton(iface_ip)) try: self.sock.bind(("", MDNS_PORT)) mreq = struct.pack("!4s4s", socket.inet_aton(MDNS_ADDR), socket.inet_aton(iface_ip)) self.sock.setsockopt(socket.IPPROTO_IP, socket.IP_ADD_MEMBERSHIP, mreq) print(f"[mDNS] Socket bound to {MDNS_ADDR}:{MDNS_PORT}") except Exception as e: raise RuntimeError(f"Socket初始化失败: {e}") def generate_services(self, start_ip: str, count: int): """生成服务对(每对包含AirPlayRAOP服务)""" base_ip, start_num = start_ip.rsplit('.', 1) start_num = int(start_num) for i in range(count): # 生成递增IP地址 ip = f"{base_ip}.{start_num + i}" # 生成唯一服务ID(8位字符) service_id = generate_random_service_id() # 生成主机名(主机名后缀为8位数字) host_suffix = generate_random_hostname_suffix() host_name = f"host-{host_suffix}.local" # AirPlay实例名 airplay_instance = f"{service_id}._airplay._tcp.local" # RAOP实例名(遵循Apple规范) raop_instance = f"{service_id}@AirPlay._raop._tcp.local" # 添加到服务列表 self.services.append({ "ip": ip, "host_name": host_name, "airplay_instance": airplay_instance, "raop_instance": raop_instance }) # 添加到服务映射 self.service_map["_airplay._tcp.local"].append( (airplay_instance, host_name, ip, 5000) ) self.service_map["_raop._tcp.local"].append( (raop_instance, host_name, ip, 7000) ) print(f"[生成服务] 已创建 {count} 对服务") def start(self): """启动mDNS响应器""" self.running = True print(f"[mDNS响应器] 在 {self.iface_ip} 上启动") while self.running: try: data, addr = self.sock.recvfrom(2048) if len(data) < 12: continue # 处理数据包 self.handle_packet(data, addr) except Exception as e: if self.running: # 防止关闭时的报错 print(f"处理报文错误: {e}") def handle_packet(self, data: bytes, addr: tuple): """处理接收到的mDNS查询(支持多问题查询)""" # 解析头部 transaction_id = struct.unpack("!H", data[0:2])[0] flags = struct.unpack("!H", data[2:4])[0] qdcount = struct.unpack("!H", data[4:6])[0] # 问题数量 # 只处理查询请求 if flags & 0x8000: return offset = 12 # DNS头部后开始解析问题 services_to_respond = set() # 收集需要响应的服务类型 # 循环读取所有问题 for _ in range(qdcount): try: # 解析域名 qname, new_offset = parse_dns_name(data, offset) # 读取类型类(各2字节) if new_offset + 4 > len(data): break qtype, qclass = struct.unpack("!HH", data[new_offset:new_offset+4]) offset = new_offset + 4 # 只处理PTR查询(qtype=12)且类为IN(qclass=1) if qtype != 12 or qclass != 1: continue service_type = qname.rstrip('.').lower() if service_type in self.service_map: services_to_respond.add(service_type) except DNSError as e: # 跳过当前问题,继续下一个 continue # 对每个需要响应的服务类型,发送响应 for service_type in services_to_respond: print(f"[查询] 来自 {addr[0]} 查询 {service_type}") for instance_info in self.service_map[service_type]: instance_name, host_name, ip, port = instance_info try: response = create_service_response( transaction_id, service_type, instance_name, host_name, ip, port ) self.sock.sendto(response, (MDNS_ADDR, MDNS_PORT)) except Exception as e: print(f"构建响应错误: {e}") def stop(self): """停止响应器""" self.running = False self.sock.close() print("[mDNS响应器] 已停止") class ServiceAnnouncer: def __init__(self, iface_ip: str, services: list): """服务广播器初始化""" self.iface_ip = iface_ip self.services = services self.running = False # 创建广播socket self.sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP) self.sock.setsockopt(socket.IPPROTO_IP, socket.IP_MULTICAST_TTL, 255) self.sock.setsockopt(socket.IPPROTO_IP, socket.IP_MULTICAST_IF, socket.inet_aton(iface_ip)) def start(self): """启动服务广播""" self.running = True print("[广播器] 开始周期性广播服务") broadcast_count = 0 while self.running: try: broadcast_count += 1 broadcast_time = time.strftime("%Y-%m-%d %H:%M:%S") print(f"\n[广播 #{broadcast_count}] 开始于 {broadcast_time}") # 广播每个服务对 for idx, service in enumerate(self.services): transaction_id = random.randint(0, 0xFFFF) try: response = create_pair_response( transaction_id, service["airplay_instance"], service["raop_instance"], service["host_name"], service["ip"] ) self.sock.sendto(response, (MDNS_ADDR, MDNS_PORT)) print(f" 已广播服务对 #{idx+1}/{len(self.services)}: {service['ip']}") except Exception as e: print(f" 广播错误: {e}") # 等待下次广播 sleep_interval = 120 # 2分钟 for _ in range(sleep_interval): if not self.running: break time.sleep(1) except Exception as e: print(f"广播错误: {e}") def stop(self): """停止广播""" self.running = False self.sock.close() print("[广播器] 已停止") def main(): parser = argparse.ArgumentParser(description="AirPlay & RAOP mDNS 批量响应器") parser.add_argument("--iface_ip", required=True, help="网络接口IP") parser.add_argument("--start_ip", default="192.168.0.2", help="起始IP地址 (默认: 192.168.0.2)") parser.add_argument("--count", type=int, default=100, help="服务对数量 (默认: 100)") args = parser.parse_args() # 创建响应器 responder = MDNSResponder(args.iface_ip) # 生成服务 responder.generate_services(args.start_ip, args.count) # 启动响应线程 responder_thread = threading.Thread(target=responder.start, daemon=True) responder_thread.start() print("服务已启动,按Ctrl+C退出...") try: # 主线程等待中断信号 while True: time.sleep(1) except KeyboardInterrupt: print("正在停止...") # 设置停止标志(如果类中有) responder.running = False # 也可以调用stop方法,但注意线程可能阻塞在recvfrom或sleep中 # 所以设置标志后,还需要中断这些阻塞(例如关闭socket) responder.stop() # 等待线程结束(如果有必要) responder_thread.join(timeout=1.0) print("已退出") if __name__ == "__main__": main()
10-25
这里是m文件 应该是这里面具体写了吧 #import "TPBCommonTableController.h" #import "TPBTableTextSectionHeader.h" #import "TPBTableTextSectionFooter.h" #import "TPBDesignKit.h" #import "UIView+TPBExpanded.h" #import "MASConstraint+TPBExpanded.h" #import "TPBTableSectionModel.h" #import "TPBCommonInputView.h" #import "TPBCustomTableCellBaseModel.h" #import "TPBCustomViewTableCell.h" #import "TPBTitleSubtitleTableCell.h" #import "TPBSwitchTableCell.h" #import "TPBListButtonTableCell.h" #import "TPBProgressTableCell.h" #import "TPBCommonInputTableCell.h" #import "TPBCheckTableCell.h" #import "TPBJumpSelectTableCell.h" #import "TPBMenuSelectTableCell.h" #import "TPBListInputTableCell.h" #import "TPBTitleActionTableCell.h" @interface TPBCommonTableController () < TPBTableCommonInputDelegate, TPBTableListInputDelegate > @property (nonatomic, assign) UITableViewStyle tableViewStyle; @property (nonatomic, strong) UIView *tpbSearchBarPlaceholderView; @property (nonatomic, strong) TPBTableSearchHeader *tpbInnerSearchBar; @property (nonatomic, strong) TPBEmptyView *emptyView; @property (nonatomic, strong) TPKeyboardAvoidingTableView *tableView; @property (nonatomic, strong) MASConstraint *emptyCenterYConstraint; @property (nonatomic, strong) MASConstraint *searchBarTopConstraint; @property (nonatomic, strong) MASConstraint *searchBarHeightConstraint; @end @implementation TPBCommonTableController - (instancetype)init { if (@available(iOS 13.0, *)) { return [self initWithTableViewStyle:UITableViewStyleInsetGrouped]; } else { return [self initWithTableViewStyle:UITableViewStyleGrouped]; } } - (instancetype)initWithTableViewStyle:(UITableViewStyle)style { if (self = [super initWithNibName:nil bundle:nil]) { _tableViewStyle = style; _hideCellSeparator = NO; } return self; } - (instancetype)initWithCoder:(NSCoder *)coder { if (self = [super initWithCoder:coder]) { UITableViewStyle style = UITableViewStyleGrouped; if (@available(iOS 13.0, *)) { style = UITableViewStyleInsetGrouped; } _tableViewStyle = style; _hideCellSeparator = NO; } return self; } - (instancetype)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil { if (self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil]) { UITableViewStyle style = UITableViewStyleGrouped; if (@available(iOS 13.0, *)) { style = UITableViewStyleInsetGrouped; } _tableViewStyle = style; _hideCellSeparator = NO; } return self; } - (void)dealloc { [self.view tpbRemoveForKeyboardEvent]; } - (void)tpbSetupInitialData { [super tpbSetupInitialData]; self.keyboardBehavior = TPBCommonTableKeyboardBehaviorDismissOnly; } - (void)tpbSetupSubviews { [super tpbSetupSubviews]; [self.tableView insertSubview:self.emptyView atIndex:0]; [self.view addSubview:self.tableView]; [self.view addSubview:self.tpbInnerSearchBar]; } - (void)tpbMakeConstraint { [super tpbMakeConstraint]; [self.emptyView mas_remakeConstraints:^(MASConstraintMaker *make) { make.leading.trailing.equalTo(self.mas_tpSafeAreaLayoutGuide); self.emptyCenterYConstraint = make.centerY.equalTo(self.mas_tpSafeAreaLayoutGuide); }]; [self.tableView mas_remakeConstraints:^(MASConstraintMaker *make) { make.top.equalTo(self.mas_tpSafeAreaLayoutGuide); make.leading.trailing.equalTo(self.mas_tpSafeAreaLayoutGuide); make.bottom.equalTo(self.view); }]; CGFloat searchHeight = [self.tpbInnerSearchBar preferredHeight]; [self.tpbInnerSearchBar mas_remakeConstraints:^(MASConstraintMaker *make) { self.searchBarTopConstraint = make.top.equalTo(self.mas_tpSafeAreaLayoutGuide).offset(0); make.leading.trailing.equalTo(self.mas_tpSafeAreaLayoutGuide); self.searchBarHeightConstraint = make.height.equalTo(@(searchHeight)); }]; } - (void)tpbBindActions { [super tpbBindActions]; TPBWeakSelf [self tpbAddContentSizeDidChangeConfig:^(id _Nonnull object, TPBDynamicContentManager * _Nonnull manager) { TPBStrongSelf dispatch_async(dispatch_get_main_queue(), ^{ [_self updateTableHeader]; [_self.tableView reloadData]; }); } notifyWhenRegister:NO]; } - (void)viewWillLayoutSubviews { [super viewWillLayoutSubviews]; [self.view bringSubviewToFront:self.tpbInnerSearchBar]; [self updateSearchBarPosition]; } #pragma mark - Public - (void)setSectionArray:(NSArray<TPBTableSectionModel *> *)sectionArray { _sectionArray = sectionArray; [self.tableView reloadData]; } - (void)setSearchBarShow:(BOOL)searchBarShow { _searchBarShow = searchBarShow; self.tpbInnerSearchBar.hidden = !searchBarShow; [self updateTableHeader]; } - (NSString *)searchPlaceholder { return self.tpbInnerSearchBar.searchPlaceholder; } - (void)setSearchPlaceholder:(NSString *)searchPlaceholder { self.tpbInnerSearchBar.searchPlaceholder = searchPlaceholder; } - (void)updateSearchKey:(NSString *)searchKey { [self.tpbInnerSearchBar updateSearchKey:searchKey]; } - (void)setCustomTableHeaderView:(UIView *)customTableHeaderView { _customTableHeaderView = customTableHeaderView; [self updateTableHeader]; } - (void)setKeyboardBehavior:(TPBCommonTableKeyboardBehavior)keyboardBehavior { _keyboardBehavior = keyboardBehavior; switch (keyboardBehavior) { case TPBCommonTableKeyboardBehaviorDismissOnly: [self.view tpbRegisterForKeyboardEvent]; break; case TPBCommonTableKeyboardBehaviorDismissAndRespondClick: [self.view tpbRemoveForKeyboardEvent]; break; default: break; } } - (void)focusToFirstEligibleElement { NSIndexPath *indexPath = nil; for (NSUInteger section = 0; section < self.sectionArray.count; section++) { TPBTableSectionModel *sectionModel = self.sectionArray[section]; for (NSUInteger row = 0; row < sectionModel.cellModelArray.count; row++) { TPBBaseTableCellModel *cellModel = sectionModel.cellModelArray[row]; if (cellModel.isAutoFocusEnabled) { indexPath = [NSIndexPath indexPathForRow:row inSection:section]; break; } } if (indexPath != nil) break; } if (indexPath == nil) return; UITableViewCell *cell = [self.tableView cellForRowAtIndexPath:indexPath]; if (cell == nil) return; if ([TPBA11yHelper isVoiceOverOn]) { UIAccessibilityPostNotification(UIAccessibilityScreenChangedNotification, cell); } else { [self.tableView scrollToRowAtIndexPath:indexPath atScrollPosition:UITableViewScrollPositionTop animated:YES]; } } #pragma mark - UITableViewDataSource & UITableViewDelegate // 注册自定义Cell - (void)registerCustomCellWithCellModel:(TPBCustomTableCellBaseModel *)customCellModel tableView:(UITableView *)tableView cellIdentifier:(NSString *)cellIdentifier { if (customCellModel.cellClass != nil) { BOOL isCellClassValid = [customCellModel.cellClass isSubclassOfClass:[UITableViewCell class]]; BOOL isValid = isCellClassValid; NSAssert(isCellClassValid, @"TPBCustomBaseTableCellModel's cellClass「%@」is not Subclass of UITableViewCell!", NSStringFromClass(customCellModel.cellClass)); if (!isValid) { return; } // 注册Cell Class [tableView registerClass:customCellModel.cellClass forCellReuseIdentifier:cellIdentifier]; } else if (customCellModel.cellNib != nil) { // 注册Cell Nib [tableView registerNib:customCellModel.cellNib forCellReuseIdentifier:cellIdentifier]; } } - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { return self.sectionArray.count; } - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { if (section < 0 || self.sectionArray.count <= section) { return 0; } TPBTableSectionModel *sectionModel = self.sectionArray[section]; return sectionModel.cellModelArray.count; } - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { if (indexPath.section < 0 || self.sectionArray.count <= indexPath.section) { return [[UITableViewCell alloc] init]; } TPBTableSectionModel *sectionModel = self.sectionArray[indexPath.section]; if (indexPath.row < 0 || sectionModel.cellModelArray.count <= indexPath.row) { return [[UITableViewCell alloc] init]; } BOOL isRTL = self.view.effectiveUserInterfaceLayoutDirection == UIUserInterfaceLayoutDirectionRightToLeft; BOOL isLast = indexPath.row == sectionModel.cellModelArray.count - 1; TPBBaseTableCellModel *model = sectionModel.cellModelArray[indexPath.row]; switch (model.cellType) { case TPBTableCellTypeCustomCell: { if ([model isKindOfClass:[TPBCustomTableCellBaseModel class]]) { TPBCustomTableCellBaseModel *cellModel = (TPBCustomTableCellBaseModel *)model; NSString *cellIdentifier = [cellModel effectiveCellIdentifier:isRTL]; UITableViewCell *cell; if (TPBA11yHelper.isVoiceOverOn && cellModel.cellClass != nil) { cell = [[cellModel.cellClass alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellIdentifier]; } if (cell == nil) { cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier]; } if (cell == nil) { // 若无法获取Cell,说明可能是未注册自定义Cell,则注册自定义Cell之后尝试重新取值 [self registerCustomCellWithCellModel:cellModel tableView:tableView cellIdentifier:cellIdentifier]; cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier]; } if (cellModel.cellConfigCallback) { cellModel.cellConfigCallback(tableView, indexPath, cell, cellModel); } if ([cell isKindOfClass:[TPBBaseTableViewCell class]]) { TPBBaseTableViewCell *baseCell = (TPBBaseTableViewCell *)cell; [baseCell updateBottomSeparatorShow:!self.hideCellSeparator && !isLast && !cellModel.hideCellSeparator]; } return cell; } } break; case TPBTableCellTypeCustomView: { if ([model isKindOfClass:[TPBCustomViewTableCellModel class]]) { TPBCustomViewTableCellModel *cellModel = (TPBCustomViewTableCellModel *)model; NSString *cellIdentifier = [TPBCustomViewTableCell cellIdentifier:isRTL]; TPBCustomViewTableCell *cell; cell = [[TPBCustomViewTableCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellIdentifier]; if (cell == nil) { cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier]; } [cell updateCellModel:cellModel]; [cell updateBottomSeparatorShow:!self.hideCellSeparator && !isLast && !cellModel.hideCellSeparator]; return cell; } } break; case TPBTableCellTypeTitleSubtitle: { if ([model isKindOfClass:[TPBTitleSubtitleTableCellModel class]]) { TPBTitleSubtitleTableCellModel *cellModel = (TPBTitleSubtitleTableCellModel *)model; NSString *cellIdentifier = [TPBTitleSubtitleTableCell cellIdentifier:isRTL]; TPBTitleSubtitleTableCell *cell; if (TPBA11yHelper.isVoiceOverOn) { cell = [[TPBTitleSubtitleTableCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellIdentifier]; } if (cell == nil) { cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier]; } [cell updateCellModel:cellModel]; [cell updateBottomSeparatorShow:!self.hideCellSeparator && !isLast && !cellModel.hideCellSeparator]; return cell; } } break; case TPBTableCellTypeSwitch: { if ([model isKindOfClass:[TPBSwitchTableCellModel class]]) { TPBSwitchTableCellModel *cellModel = (TPBSwitchTableCellModel *)model; NSString *cellIdentifier = [TPBSwitchTableCell cellIdentifier:isRTL]; TPBSwitchTableCell *cell; if (TPBA11yHelper.isVoiceOverOn) { cell = [[TPBSwitchTableCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellIdentifier]; } if (cell == nil) { cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier]; } [cell updateCellModel:cellModel]; [cell updateBottomSeparatorShow:!self.hideCellSeparator && !isLast && !cellModel.hideCellSeparator]; return cell; } } break; case TPBTableCellTypeListButton: { if ([model isKindOfClass:[TPBListButtonTableCellModel class]]) { TPBListButtonTableCellModel *cellModel = (TPBListButtonTableCellModel *)model; NSString *cellIdentifier = [TPBListButtonTableCell cellIdentifier:isRTL]; TPBListButtonTableCell *cell; if (TPBA11yHelper.isVoiceOverOn) { cell = [[TPBListButtonTableCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellIdentifier]; } if (cell == nil) { cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier]; } [cell updateCellModel:cellModel]; [cell updateBottomSeparatorShow:!self.hideCellSeparator && !isLast && !cellModel.hideCellSeparator]; return cell; } } break; case TPBTableCellTypeProgress: { if ([model isKindOfClass:[TPBProgressTableCellModel class]]) { TPBProgressTableCellModel *cellModel = (TPBProgressTableCellModel *)model; NSString *cellIdentifier = [TPBProgressTableCell cellIdentifier:isRTL]; TPBProgressTableCell *cell; if (TPBA11yHelper.isVoiceOverOn) { cell = [[TPBProgressTableCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellIdentifier]; } if (cell == nil) { cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier]; } [cell updateCellModel:cellModel]; [cell updateBottomSeparatorShow:!self.hideCellSeparator && !isLast && !cellModel.hideCellSeparator]; return cell; } } break; case TPBTableCellTypeCommonInput: { if ([model isKindOfClass:[TPBCommonInputTableCellModel class]]) { TPBCommonInputTableCellModel *cellModel = (TPBCommonInputTableCellModel *)model; NSString *cellIdentifier = [TPBCommonInputTableCell cellIdentifier:isRTL]; TPBCommonInputTableCell *cell; if (TPBA11yHelper.isVoiceOverOn) { cell = [[TPBCommonInputTableCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellIdentifier]; } if (cell == nil) { cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier]; } cell.inputDelegate = self; [cell updateCellModel:cellModel]; [cell updateBottomSeparatorShow:!self.hideCellSeparator && !isLast && !cellModel.hideCellSeparator]; return cell; } } break; case TPBTableCellTypeCheck: { if ([model isKindOfClass:[TPBCheckTableCellModel class]]) { TPBCheckTableCellModel *cellModel = (TPBCheckTableCellModel *)model; BOOL isCustomizedCellHeight = cellModel.height.isCustomHeight; NSString *cellIdentifier = [TPBCheckTableCell cellIdentifier:isRTL]; TPBCheckTableCell *cell; if (TPBA11yHelper.isVoiceOverOn) { cell = [[TPBCheckTableCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellIdentifier]; } if (cell == nil) { cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier]; } [cell updateCellModel:cellModel isCustomizedCellHeight:isCustomizedCellHeight]; [cell updateBottomSeparatorShow:!self.hideCellSeparator && !isLast && !cellModel.hideCellSeparator]; return cell; } } break; case TPBTableCellTypeJumpSelect: { if ([model isKindOfClass:[TPBJumpSelectTableCellModel class]]) { TPBJumpSelectTableCellModel *cellModel = (TPBJumpSelectTableCellModel *)model; NSString *cellIdentifier = [TPBJumpSelectTableCell cellIdentifier:isRTL]; TPBJumpSelectTableCell *cell; if (TPBA11yHelper.isVoiceOverOn) { cell = [[TPBJumpSelectTableCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellIdentifier]; } if (cell == nil) { cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier]; } [cell updateCellModel:cellModel]; [cell updateBottomSeparatorShow:!self.hideCellSeparator && !isLast && TPBIsEmptyString(cellModel.title) && !cellModel.hideCellSeparator]; return cell; } } break; case TPBTableCellTypeMenuSelect: { if ([model isKindOfClass:[TPBMenuSelectTableCellModel class]]) { TPBMenuSelectTableCellModel *cellModel = (TPBMenuSelectTableCellModel *)model; NSString *cellIdentifier = [TPBMenuSelectTableCell cellIdentifier:isRTL]; TPBMenuSelectTableCell *cell; if (TPBA11yHelper.isVoiceOverOn) { cell = [[TPBMenuSelectTableCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellIdentifier]; } if (cell == nil) { cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier]; } [cell updateCellModel:cellModel]; [cell updateBottomSeparatorShow:!self.hideCellSeparator && !isLast && !cellModel.hideCellSeparator]; return cell; } } break; case TPBTableCellTypeListInput: { if ([model isKindOfClass:[TPBListInputTableCellModel class]]) { TPBListInputTableCellModel *cellModel = (TPBListInputTableCellModel *)model; NSString *cellIdentifier = [TPBListInputTableCell cellIdentifier:isRTL]; TPBListInputTableCell *cell; if (TPBA11yHelper.isVoiceOverOn) { cell = [[TPBListInputTableCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellIdentifier]; } if (cell == nil) { cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier]; } cell.inputDelegate = self; [cell updateCellModel:cellModel]; [cell updateBottomSeparatorShow:!self.hideCellSeparator && !isLast && !cellModel.hideCellSeparator]; return cell; } } break; case TPBTableCellTypeTitleAction: { if ([model isKindOfClass:[TPBTitleActionTableCellModel class]]) { TPBTitleActionTableCellModel *cellModel = (TPBTitleActionTableCellModel *)model; NSString *cellIdentifier = [TPBTitleActionTableCell cellIdentifier:isRTL]; TPBTitleActionTableCell *cell; if (TPBA11yHelper.isVoiceOverOn) { cell = [[TPBTitleActionTableCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellIdentifier]; } if (cell == nil) { cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier]; } [cell updateCellModel:cellModel]; [cell updateBottomSeparatorShow:!self.hideCellSeparator && !isLast && !cellModel.hideCellSeparator]; return cell; } } break; } return [[UITableViewCell alloc] init]; } - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath { if (indexPath.section < 0 || self.sectionArray.count <= indexPath.section) { return UITableViewAutomaticDimension; } TPBTableSectionModel *sectionModel = self.sectionArray[indexPath.section]; if (indexPath.row < 0 || sectionModel.cellModelArray.count <= indexPath.row) { return UITableViewAutomaticDimension; } TPBBaseTableCellModel *cellModel = sectionModel.cellModelArray[indexPath.row]; if (cellModel.height.isCustomHeight) { return cellModel.height.customHeight; } return UITableViewAutomaticDimension; } // Header - (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section { if (section < 0 || self.sectionArray.count <= section) { return [UIView new]; } TPBTableSectionModel *sectionModel = self.sectionArray[section]; if (sectionModel.customHeaderView != nil) { return sectionModel.customHeaderView; } if (TPBIsEmptyString(sectionModel.headerTitle) && sectionModel.headerAction == nil) { return [UIView new]; } TPBTableTextSectionHeader *headerView = [TPBTableTextSectionHeader new]; [headerView updateSectionModel:sectionModel]; return headerView; } - (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section { if (section < 0 || self.sectionArray.count <= section) { return TPBDesign.list.sectionHeaderHeight; } TPBTableSectionModel *sectionModel = self.sectionArray[section]; if (sectionModel.sectionHeaderHeight.isCustomHeight) { return sectionModel.sectionHeaderHeight.customHeight; } if (sectionModel.customHeaderView != nil) { return UITableViewAutomaticDimension; } if (TPBIsEmptyString(sectionModel.headerTitle) && sectionModel.headerAction == nil) { if (self.searchBarShow && section == 0) { return 10; } return TPBDesign.list.sectionHeaderHeight; } return UITableViewAutomaticDimension; } // Footer - (UIView *)tableView:(UITableView *)tableView viewForFooterInSection:(NSInteger)section { if (section < 0 || self.sectionArray.count <= section) { return [UIView new]; } TPBTableSectionModel *sectionModel = self.sectionArray[section]; if (sectionModel.customFooterView != nil) { return sectionModel.customFooterView; } if (TPBIsEmptyString(sectionModel.footerTitle) && sectionModel.footerAction == nil) { return [UIView new]; } TPBTableTextSectionFooter *footerView = [TPBTableTextSectionFooter new]; [footerView updateSectionModel:sectionModel]; return footerView; } - (CGFloat)tableView:(UITableView *)tableView heightForFooterInSection:(NSInteger)section { if (section < 0 || self.sectionArray.count <= section) { return TPBDesign.list.sectionFooterHeight; } TPBTableSectionModel *sectionModel = self.sectionArray[section]; if (sectionModel.sectionFooterHeight.isCustomHeight) { return sectionModel.sectionFooterHeight.customHeight; } if (sectionModel.customFooterView != nil) { return UITableViewAutomaticDimension; } if (TPBIsEmptyString(sectionModel.footerTitle) && sectionModel.footerAction == nil) { return TPBDesign.list.sectionFooterHeight; } return UITableViewAutomaticDimension; } // TableViewCell点击 - (BOOL)tableView:(UITableView *)tableView shouldHighlightRowAtIndexPath:(NSIndexPath *)indexPath { [tableView deselectRowAtIndexPath:indexPath animated:NO]; if (indexPath.section < 0 || self.sectionArray.count <= indexPath.section) { return NO; } TPBTableSectionModel *sectionModel = self.sectionArray[indexPath.section]; if (indexPath.row < 0 || sectionModel.cellModelArray.count <= indexPath.row) { return NO; } TPBBaseTableCellModel *cellModel = sectionModel.cellModelArray[indexPath.row]; switch (cellModel.cellType) { case TPBTableCellTypeCustomCell: case TPBTableCellTypeCustomView: case TPBTableCellTypeTitleSubtitle: case TPBTableCellTypeSwitch: case TPBTableCellTypeCheck: case TPBTableCellTypeListButton: case TPBTableCellTypeJumpSelect: case TPBTableCellTypeMenuSelect: case TPBTableCellTypeProgress: case TPBTableCellTypeTitleAction: return YES; case TPBTableCellTypeCommonInput: case TPBTableCellTypeListInput: return NO; } } - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { [tableView deselectRowAtIndexPath:indexPath animated:NO]; if (indexPath.section < 0 || self.sectionArray.count <= indexPath.section) { return; } TPBTableSectionModel *sectionModel = self.sectionArray[indexPath.section]; if (indexPath.row < 0 || sectionModel.cellModelArray.count <= indexPath.row) { return; } if (self.keyboardBehavior == TPBCommonTableKeyboardBehaviorDismissAndRespondClick) { [self tpbHideKeyboard]; } TPBBaseTableCellModel *model = sectionModel.cellModelArray[indexPath.row]; switch (model.cellType) { case TPBTableCellTypeCustomCell: { if ([model isKindOfClass:[TPBCustomTableCellBaseModel class]]) { TPBCustomTableCellBaseModel *cellModel = (TPBCustomTableCellBaseModel *)model; if (cellModel.didSelectCellCallback) { cellModel.didSelectCellCallback(cellModel, indexPath); } } } break; case TPBTableCellTypeCustomView: { if ([model isKindOfClass:[TPBCustomViewTableCellModel class]]) { TPBCustomViewTableCellModel *cellModel = (TPBCustomViewTableCellModel *)model; if (cellModel.didSelectCellCallback) { cellModel.didSelectCellCallback(cellModel, indexPath); } } } break; case TPBTableCellTypeTitleSubtitle: { if ([model isKindOfClass:[TPBTitleSubtitleTableCellModel class]]) { TPBTitleSubtitleTableCellModel *cellModel = (TPBTitleSubtitleTableCellModel *)model; if (cellModel.didSelectCellCallback) { cellModel.didSelectCellCallback(cellModel, indexPath); } } } break; case TPBTableCellTypeSwitch: { if ([model isKindOfClass:[TPBSwitchTableCellModel class]]) { TPBSwitchTableCellModel *cellModel = (TPBSwitchTableCellModel *)model; if (cellModel.switchDidClickHotZoneCallback) { cellModel.switchDidClickHotZoneCallback(); } } } break; case TPBTableCellTypeListButton: { if ([model isKindOfClass:[TPBListButtonTableCellModel class]]) { TPBListButtonTableCellModel *cellModel = (TPBListButtonTableCellModel *)model; if (cellModel.actionEnabled && cellModel.didSelectCellCallback) { cellModel.didSelectCellCallback(cellModel, indexPath); } } } break; case TPBTableCellTypeCheck: { if ([model isKindOfClass:[TPBCheckTableCellModel class]]) { TPBCheckTableCellModel *cellModel = (TPBCheckTableCellModel *)model; if (cellModel.checkEnabled && cellModel.didSelectCellCallback) { cellModel.didSelectCellCallback(cellModel, indexPath); } } } break; case TPBTableCellTypeJumpSelect: { if ([model isKindOfClass:[TPBJumpSelectTableCellModel class]]) { TPBJumpSelectTableCellModel *cellModel = (TPBJumpSelectTableCellModel *)model; if (cellModel.didSelectCellCallback) { cellModel.didSelectCellCallback(cellModel, indexPath); } } } case TPBTableCellTypeTitleAction: { if ([model isKindOfClass:[TPBTitleActionTableCellModel class]]) { TPBTitleActionTableCellModel *cellModel = (TPBTitleActionTableCellModel *)model; if (cellModel.didSelectCellCallback) { cellModel.didSelectCellCallback(cellModel, indexPath); } } } break; case TPBTableCellTypeMenuSelect: break; case TPBTableCellTypeProgress: case TPBTableCellTypeCommonInput: case TPBTableCellTypeListInput: break; } } //- (BOOL)tableView:(UITableView *)tableView canEditRowAtIndexPath:(NSIndexPath *)indexPath{ // if (indexPath.row < 0 || indexPath.row >= self.cloudDeviceList.count) { // return NO; // } // TPBDMECDevice *ecDevice = self.cloudDeviceList[indexPath.row]; // return [self canForgetWithDevice:ecDevice]; //} //- (NSArray<UITableViewRowAction *> *)tableView:(UITableView *)tableView editActionsForRowAtIndexPath:(NSIndexPath *)indexPath { // if (indexPath.row < 0 || indexPath.row >= self.cloudDeviceList.count) { // return @[]; // } // TPBDMECDevice *ecDevice = self.cloudDeviceList[indexPath.row]; // TPBWeakSelf; // UITableViewRowAction *deleteAction = [UITableViewRowAction rowActionWithStyle:UITableViewRowActionStyleDestructive title:gControllerCloudAccess.controllerCloudAccessForget handler:^(UITableViewRowAction *action, NSIndexPath *indexPath){ // TPBStrongSelf; // [_self popAlertControlForUnbindActionWithECDevice:ecDevice]; // }]; // deleteAction.backgroundColor = [UIColor tpbRed]; // return @[deleteAction]; //} #pragma mark - UIScrollViewDelegate - (void)scrollViewDidScroll:(UIScrollView *)scrollView { [self updateSearchBarPosition]; } #pragma mark - TPBTableSearchHeaderDelegate - (void)tpbTableSearchHeaderHideKeyboard { [self tpbHideKeyboard]; } #pragma mark - TPBTableCommonInputDelegate - (void)tableCommonInputFieldDidClickReturn:(TPBCommonInputView *)inputView textField:(UITextField *)textField { [self handleUserClickReturnForTextField:textField]; } #pragma mark - TPBTableListInputDelegate - (void)tableListInputFieldDidClickReturn:(TPBListInputTableCell *)inputCell textField:(UITextField *)textField { [self handleUserClickReturnForTextField:textField]; } #pragma mark - Private - (void)updateSearchBarPosition { CGFloat tableY = self.tableView.frame.origin.y; CGRect convertedRect = [self.tpbSearchBarPlaceholderView convertRect:self.tpbSearchBarPlaceholderView.bounds toView:self.view]; CGFloat diffY = convertedRect.origin.y - tableY; CGFloat targetConstant = MAX(0, diffY); if (self.searchBarTopConstraint.tpbConstant != targetConstant) { self.searchBarTopConstraint.tpbConstant = targetConstant; } } // 用户点击UITextField键盘Return - (void)handleUserClickReturnForTextField:(UITextField *)textField { if (textField.returnKeyType != UIReturnKeyNext) { [textField resignFirstResponder]; return; } if ([self.tableView focusNextTextField]) { } else { [textField resignFirstResponder]; } } - (void)updateTableHeader { if (self.searchBarShow) { CGFloat searchBarHeight = [self.tpbInnerSearchBar preferredHeight]; self.tpbSearchBarPlaceholderView.frame = CGRectMake(0, 0, self.view.bounds.size.width, searchBarHeight); self.tableView.tableHeaderView = self.tpbSearchBarPlaceholderView; self.searchBarHeightConstraint.tpbConstant = searchBarHeight; } else if (self.customTableHeaderView) { self.tableView.tableHeaderView = self.customTableHeaderView; } else { UIView *view = [[UIView alloc] initWithFrame:CGRectMake(0, 0, self.view.bounds.size.width, CGFLOAT_MIN)]; self.tableView.tableHeaderView = view; } } #pragma mark - Property - (TPBEmptyView *)emptyView { if (!_emptyView) { _emptyView = [TPBEmptyView new]; _emptyView.hidden = YES; } return _emptyView; } - (UIView *)tpbSearchBarPlaceholderView { if (!_tpbSearchBarPlaceholderView) { _tpbSearchBarPlaceholderView = [UIView new]; } return _tpbSearchBarPlaceholderView; } - (TPBTableSearchHeader *)tpbInnerSearchBar { if (!_tpbInnerSearchBar) { CGRect frame = CGRectMake(0, 0, self.view.bounds.size.width, 62); _tpbInnerSearchBar = [[TPBTableSearchHeader alloc] initWithFrame:frame]; _tpbInnerSearchBar.delegate = self; _tpbInnerSearchBar.hidden = YES; _tpbInnerSearchBar.backgroundColor = [UIColor tpbBackground]; } return _tpbInnerSearchBar; } - (UITableView *)tableView { if (!_tableView) { _tableView = [[TPKeyboardAvoidingTableView alloc] initWithFrame:CGRectZero style:self.tableViewStyle]; if (@available(iOS 13.0, *)) { _tableView.automaticallyAdjustsScrollIndicatorInsets = YES; } if (@available(iOS 15.0, *)) { UIView *view = [[UIView alloc] initWithFrame:CGRectMake(0, 0, self.view.bounds.size.width, CGFLOAT_MIN)]; _tableView.tableHeaderView = view; _tableView.sectionHeaderTopPadding = 0; } _tableView.backgroundColor = [UIColor clearColor]; _tableView.estimatedRowHeight = 72; _tableView.estimatedSectionHeaderHeight = 68; _tableView.showsHorizontalScrollIndicator = NO; _tableView.keyboardDismissMode = UIScrollViewKeyboardDismissModeOnDrag; _tableView.separatorStyle = UITableViewCellSeparatorStyleNone; _tableView.sectionIndexColor = [UIColor tpbTableSectionIndexColor]; _tableView.delegate = self; _tableView.dataSource = self; NSArray<Class> *cellClassArray = @[ [TPBCustomViewTableCell class], [TPBTitleSubtitleTableCell class], [TPBSwitchTableCell class], [TPBProgressTableCell class], [TPBCommonInputTableCell class], [TPBCheckTableCell class], [TPBJumpSelectTableCell class], [TPBMenuSelectTableCell class], [TPBListInputTableCell class], [TPBListButtonTableCell class], [TPBTitleActionTableCell class] ]; for (Class cls in cellClassArray) { if ([cls isSubclassOfClass:[TPBBaseTableViewCell class]]) { NSString *cellIdentifier = [cls cellIdentifier]; [_tableView registerClass:cls forCellReuseIdentifier:cellIdentifier]; NSString *rtlCellIdentifier = [cls cellIdentifier:YES]; [_tableView registerClass:cls forCellReuseIdentifier:rtlCellIdentifier]; NSString *ltrCellIdentifier = [cls cellIdentifier:NO]; [_tableView registerClass:cls forCellReuseIdentifier:ltrCellIdentifier]; } } } return _tableView; } @end
最新发布
11-29
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值