当前位置: 首页 > news >正文

iOS——方法交换Method Swizzing

什么是方法交换

Method Swizzing是发生在运行时的,主要用于在运行时将两个Method进行交换,我们可以将Method Swizzling代码写到任何地方,但是只有在这段Method Swilzzling代码执行完毕之后互换才起作用。
利用Objective-C Runtimee的动态绑定特性,将一个方法的实现与另一个方法的实现进行交换。交换两个方法的实现一般写在分类的load方法里面,因为load方法会在程序运行前加载一次,而initialize方法会在类或者子类在 第一次使用的时候调用,当有分类的时候会调用多次。

在这里插入图片描述

方法交换的方式

  1. 获取方法的 SEL 和 IMP
    • 使用 class_getInstanceMethodclass_getClassMethod 函数获取方法的 Method 结构体。
    • Method 结构体中获取 SEL 和 IMP。
  2. 交换方法的 IMP
    • 使用 method_exchangeImplementations 函数交换两个方法的实现。
    • 或者使用 class_replaceMethod 函数替换方法的实现。
    // 类中获取oriSEL对应的方法实现Method oriMethod = class_getInstanceMethod(cls, oriSEL);// 获取swiSEL对应的方法实现Method swiMethod = class_getInstanceMethod(cls, swizzledSEL);// 将两个方法实现进行交换,method_exchangeImplementations(oriMethod, swiMethod);

在进行方法交换操作时,建议放在单例下进行,以确保该操作只执行一次,避免重复调用导致交换效果被反转,从而失去交换的目的。
通过上面的方法可以理解,交换的是两者的方法实现

方法交换的四个风险

直接使用 Runtime 的方法进行方法交换会有很多风险,RSSwizzle库里指出了四个典型的直接使用 Runtime 方法进行方法交换的风险。

  • 第一个风险是,需要在 +load 方法中进行方法交换。因为如果在其他时候进行方法交换,难以保证另外一个线程中不会同时调用被交换的方法,从而导致程序不能按预期执行。而在 +load 方法中执行方法交换,确保交换在类加载时完成,从而避免线程竞争和其他时机相关的问题。

  • 第二个风险是,被交换的方法必须是当前类的方法,不能是父类的方法,直接把父类的实现拷贝过来不会起作用。父类的方法必须在调用的时候使用,而不是方法交换时使用。方法交换只能作用于当前类的方法,不能影响父类的方法。

  • 第三个风险是,交换的方法如果依赖了 cmd,那么交换后,如果 cmd 发生了变化,就会出现各种奇怪问题,而且这些问题还很难排查。特别是交换了系统方法,你无法保证系统方法内部是否依赖了 cmd。(cmd参数表示当前调用的方法)

  • 第四个风险是,方法交换命名冲突。如果出现冲突,可能会导致方法交换失败。

load方法的特点
+load方法在类加载时调用,确保方法交换在任何实例方法调用之前完成。
一般情况下load方法在每个类中都只会调用一次。
+load方法自动调用,不会被多个线程同时调用,结合 dispatch_once 确保线程安全。
+load方法自动执行,减少开发者的工作量。

第三个风险详解

第三个风险的意思是,两个方法实现交换后,_cmd却不一定。

_cmd回顾
_cmd 是隐藏的参数,表示当前方法的selector,他和self表示当前方法调用的对象实例。
获取当前被调用方法: NSStringFromSelector(_cmd)

比如下面这个例子:
我们首先创建了一个ViewController类,在这个类中我们写出将被交换的原方法orimed,然后创建一个swizzled分类,在分类中写出交换后的方法

#import "ViewController.h"
#import "ViewController+swizzled.h"
#import <objc/runtime.h>@interface ViewController ()
@property (assign, nonatomic) int ticketsCount;
@end@implementation ViewController+ (void)load {static dispatch_once_t onceToken;dispatch_once(&onceToken, ^{SEL oriSEL = @selector(orimed);SEL swiSEL = @selector(swizzledSelector);Method oriMethod = class_getInstanceMethod([self class], oriSEL);Method swiMethod = class_getInstanceMethod([self class], swiSEL);method_exchangeImplementations(oriMethod, swiMethod);});
}- (void)viewDidLoad {[super viewDidLoad];[self orimed];
}- (void) orimed {NSLog(@"交换前的方法");
}
#import "ViewController.h"NS_ASSUME_NONNULL_BEGIN
@interface ViewController (swizzled)- (void) swizzledSelector;@end
NS_ASSUME_NONNULL_END
#import "ViewController+swizzled.h"@implementation ViewController (swizzled)- (void)swizzledSelector {NSLog(@"方法已交换");//然后我们在这个方法中打印当前方法的selectorNSLog(@"%@", NSStringFromSelector(_cmd));
}@end

打印的结果:
在这里插入图片描述

我们的代码明明执行了swizzledSelector中的代码,为什么打印出的_cmd还是orimed呢?
这是因为方法交换本质上是互换了两个方法的实现,而不是选择器,这段代码实际上是将orimed的SEL指向了swizzleSelector方法的imp,所以执行swizzledSelector的代码实现时,返回的_cmd(方法的selector)为orimed。
因此如果交换的方法依赖于 cmd 来决定行为,可能会导致日志输出的信息不符合实际调用的方法。

方法交换的实际用法

先给要替换的方法的类添加一个Category,然后在Category中的+(void)load方法中添加Method Swizzling方法,我们用来替换的方法也写在这个Category中。就像上面那个例子一样。
由于load类方法是程序运行时这个类被加载到内存中就调用的一个方法,执行比较早,在每个类中都只会调用一次,并且不需要我们手动调用。

注意:

  • Swizzling应该总在+load中执行
  • Swizzling应该总是在dispatch_once中执行
  • Swizzling在+load中执行时,不要调用[super load]。如果多次调用了[super load],可能会出现“Swizzle无效”的假象。
  • 为了避免Swizzling的代码被重复执行,我们可以通过GCD的dispatch_once函数来解决,利用dispatch_once函数内代码只会执行一次的特性。

方法交换的API

方案一:
提供了更精细的控制,可以选择性地添加、替换方法,并能处理方法不存在的情况。

//获取某个类的实例方法。
//cls:目标类。name:方法的选择器(selector)。
class_getInstanceMethod(Class _Nullable cls, SEL _Nonnull name)
//获取方法的实现(IMP)
//m:方法(Method)
method_getImplementation(Method _Nonnull m) 
//向类中添加一个方法及其实现。
//cls:目标类。name:方法的选择器。imp:方法的实现。types:方法的类型编码(Type encoding)。
class_addMethod(Class _Nullable cls, SEL _Nonnull name, IMP _Nonnull imp, const char * _Nullable types) 
//替换类中的方法实现。如果该方法不存在,则添加这个方法。
//cls:目标类。name:方法的选择器。imp:新的方法实现。types:方法的类型编码。
class_replaceMethod(Class _Nullable cls, SEL _Nonnull name, IMP _Nonnull imp, const char * _Nullable types) 

方案二:
直接交换两个方法的实现,步骤简单,但是少了一些灵活性。

//交换两个方法的实现。
method_exchangeImplementations(Method _Nonnull m1, Method _Nonnull m2) 

案例分析

案例一:递归调用

我们现在在上面原先代码的基础上修改一下:

#import "ViewController+swizzled.h"@implementation ViewController (swizzled)- (void)swizzledSelector {NSLog(@"方法已交换");//这里递归调用一下swizzledSelector方法[self swizzledSelector];
}@end

运行结果:
在这里插入图片描述

可以看出,并没有发生递归调用。反而只是打印出了原方法的内容,这是为什么呢?
这是因为进行了方法交换,所以调用方法swizzledSelector,会找到orimed的方法实现,而swizzledSelector中有调用swizzledSelector,而此时它的方法实现已经指向了orimed。见下图:
在这里插入图片描述

案例二:交换父类的方法

有如下代码:首先,我们创建一个FatherViewController类,该类有一个fatherMethod方法,然后该类有一个子类SonViewController,子类同样有一个sonMethod方法。在子类的实现中,我们将父类的fatherMethod和子类的sonMethod方法进行交换,然后在ViewController中调用父类的fatherMethod方法:

#import "FatherViewController.h"@interface FatherViewController ()
@end@implementation FatherViewController- (void)viewDidLoad {[super viewDidLoad];// Do any additional setup after loading the view.
}- (void)fatherMethod {NSLog(@"父类的方法");
}@end
#import "SonViewController.h"
#import <objc/runtime.h>@interface SonViewController ()@end@implementation SonViewController+ (void)load {static dispatch_once_t onceToken;dispatch_once(&onceToken, ^{SEL sonSEL = @selector(sonMethod);SEL fatherSEL = @selector(fatherMethod);Method sonMed = class_getInstanceMethod([self class], sonSEL);Method fatherMed = class_getInstanceMethod([self class], fatherSEL);method_exchangeImplementations(sonMed, fatherMed);});
}- (void)viewDidLoad {[super viewDidLoad];}- (void) sonMethod {//递归调用[self sonMethod];NSLog(@"子类的方法");
}@end

在ViewController中,使用子类的实例对象调用父类的方法:

- (void)viewDidLoad {[super viewDidLoad];[[[SonViewController alloc] init] fatherMethod];
}

执行结果:

在这里插入图片描述

可以得出,我们成功完成了在子类中和父类的方法进行交换。递归调用也没有出错。

但是如果此时我们在ViewController中使用父类的实例对象调用父类的方法呢?
我们现在修改ViewController中的代码:

- (void)viewDidLoad {[super viewDidLoad];[[[FatherViewController alloc] init] fatherMethod];
}

得到的结果却是:
在这里插入图片描述
代码运行时发生了错误,这是因为,使用父类的实例对象调用父类的方法时,由于发生了方法交换,因此父类执行的是子类的方法实现。在子类的方法实现中又调用了sonMethod方法,但是问题来了,此时实现子类方法的调用者是父类的实例对象,父类的实例对象中压根没有sonMethod方法的实现,这就导致了找不到sonMethod方法,因而产生了错误。

在开发中,如果进行方法交换,一定要确保方法已经实现,否则会出现本例中啃爹的现象(方法交换,而父类没有方法的实现,导致报错)。所以在进行相关方法交换时,尽量避免涉及到其父类或者其子类的方法。

方法交换的应用

统计ViewController加载次数并打印

#import "UIViewController+Logging.h"
#import <objc/runtime.h>@implementation UIViewController (Logging)+ (void)load
{swizzleMethod([self class], @selector(viewDidAppear:), @selector(swizzled_viewDidAppear:));
}- (void)swizzled_viewDidAppear:(BOOL)animated
{// call original implementation[self swizzled_viewDidAppear:animated];// LoggingNSLog(@"%@", NSStringFromClass([self class]));
}void swizzleMethod(Class class, SEL originalSelector, SEL swizzledSelector)
{// the method might not exist in the class, but in its superclassMethod originalMethod = class_getInstanceMethod(class, originalSelector);Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);// class_addMethod will fail if original method already existsBOOL didAddMethod = class_addMethod(class, originalSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod));// the method doesn’t exist and we just added oneif (didAddMethod) {class_replaceMethod(class, swizzledSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod));}else {method_exchangeImplementations(originalMethod, swizzledMethod);}}

防止UI控件短时间多次激活事件

有时候会有这种需求,项目中的写好的按钮要求不能连续点击,这时候最方便的方法就是使用方法交换将系统的sendAction:to:forEvent:方法替换为自定义的swizzled_sendAction:to:forEvent:方法。在自定义方法中判断是否需要拦截点击事件。
UIControl+Limit.m:

#import "UIControl+Limit.h"
#import <objc/runtime.h>static const char *UIControl_acceptEventInterval="UIControl_acceptEventInterval";
static const char *UIControl_ignoreEvent="UIControl_ignoreEvent";@implementation UIControl (Limit)#pragma mark - acceptEventInterval
- (void)setAcceptEventInterval:(NSTimeInterval)acceptEventInterval
{objc_setAssociatedObject(self,UIControl_acceptEventInterval, @(acceptEventInterval), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}-(NSTimeInterval)acceptEventInterval {return [objc_getAssociatedObject(self,UIControl_acceptEventInterval) doubleValue];
}#pragma mark - ignoreEvent
-(void)setIgnoreEvent:(BOOL)ignoreEvent{objc_setAssociatedObject(self,UIControl_ignoreEvent, @(ignoreEvent), OBJC_ASSOCIATION_ASSIGN);
}-(BOOL)ignoreEvent{return [objc_getAssociatedObject(self,UIControl_ignoreEvent) boolValue];
}#pragma mark - Swizzling
+(void)load {Method a = class_getInstanceMethod(self,@selector(sendAction:to:forEvent:));Method b = class_getInstanceMethod(self,@selector(swizzled_sendAction:to:forEvent:));method_exchangeImplementations(a, b);//交换方法
}- (void)swizzled_sendAction:(SEL)action to:(id)target forEvent:(UIEvent*)event
{if(self.ignoreEvent){NSLog(@"btnAction is intercepted");return;}if(self.acceptEventInterval>0){self.ignoreEvent=YES;[self performSelector:@selector(setIgnoreEventWithNo)  withObject:nil afterDelay:self.acceptEventInterval];}[self swizzled_sendAction:action to:target forEvent:event];
}-(void)setIgnoreEventWithNo{self.ignoreEvent=NO;
}@end

ViewController.m:

-(void)setupSubViews{UIButton *btn = [UIButton new];btn =[[UIButton alloc]initWithFrame:CGRectMake(100,100,100,40)];[btn setTitle:@"btnTest"forState:UIControlStateNormal];[btn setTitleColor:[UIColor redColor]forState:UIControlStateNormal];btn.acceptEventInterval = 3;[self.view addSubview:btn];[btn addTarget:self action:@selector(btnAction)forControlEvents:UIControlEventTouchUpInside];
}- (void)btnAction{NSLog(@"btnAction is executed");
}

防奔溃处理:数组越界问题

在实际项目中,有时会因为数组越界导致崩溃,需要一个解决方案来防止这种情况,即使数组越界也不会崩溃。
通过方法交换(Swizzling)替换NSArray的objectAtIndex:方法,添加防越界处理逻辑。

无痕埋点

相关文章:

iOS——方法交换Method Swizzing

什么是方法交换 Method Swizzing是发生在运行时的&#xff0c;主要用于在运行时将两个Method进行交换&#xff0c;我们可以将Method Swizzling代码写到任何地方&#xff0c;但是只有在这段Method Swilzzling代码执行完毕之后互换才起作用。 利用Objective-C Runtimee的动态绑定…...

【有啥问啥】大模型应用中的哈希链推理任务

大模型应用中的哈希链推理任务 随着人工智能技术的快速发展&#xff0c;尤其是大模型&#xff08;如GPT、BERT、Vision Transformer等&#xff09;的广泛应用&#xff0c;确保数据处理和模型推理的透明性与安全性变得愈发重要。哈希链推理任务作为一种技术手段&#xff0c;能够…...

DevExpress WinForms v24.1新版亮点:功能区、数据编辑器全新升级

DevExpress WinForms拥有180组件和UI库&#xff0c;能为Windows Forms平台创建具有影响力的业务解决方案。DevExpress WinForms能完美构建流畅、美观且易于使用的应用程序&#xff0c;无论是Office风格的界面&#xff0c;还是分析处理大批量的业务数据&#xff0c;它都能轻松胜…...

FreeRTOS内部机制学习01(任务创建的细节以及任务调度的内部机制)

文章目录 前言&#xff1a;首先要谢谢韦东山老师的无私奉献&#xff0c;让我学到了很多东西&#xff0c;我做这个笔记是害怕我会忘记&#xff0c;所以就记录了下来&#xff0c;希望对大家有帮助&#xff01;关于寄存器CPU内部的寄存器这些寄存器到底要保存一些什么&#xff1f;…...

CANoe突然出现Trace窗口筛选项无法显示的问题

原因&#xff1a;和最近window的推送的补丁包有关 同事通过网上的操作&#xff0c;一顿操作猛如虎&#xff0c;卸载掉了这个插件&#xff0c;结果电脑文件夹无法打开和闪退。 IT的同事通过cmd命令也无法恢复。 dism /online /cleanup-image /scanhealth dism /online /cleanu…...

Linux日志-sar日志

作者介绍&#xff1a;简历上没有一个精通的运维工程师。希望大家多多关注作者&#xff0c;下面的思维导图也是预计更新的内容和当前进度(不定时更新)。 Linux 系统中的日志是记录系统活动和事件的重要工具&#xff0c;它们可以帮助管理员监视系统状态、调查问题以及了解系统运行…...

全国计算机二级考试C语言篇3——选择题

C语言部分——C语言概述 1.程序模块化的优点 程序模块化的优点在于它可以使程序的开发、维护和复用变得更简单。下面是一些主要的优点&#xff1a; 降低复杂度&#xff1a;模块化可以将复杂的问题分解成更小的、更易管理的部分。 可维护性&#xff1a;模块化使得代码更易于维护…...

Python实现混合蛙跳算法

博客目录 引言 什么是混合蛙跳算法&#xff08;Shuffled Frog Leaping Algorithm, SFLA&#xff09;&#xff1f;混合蛙跳算法的应用场景为什么使用混合蛙跳算法&#xff1f; 混合蛙跳算法的原理 混合蛙跳算法的基本概念蛙群分组与局部搜索全局混洗与更新混合蛙跳算法的流程 …...

印度再现超级大片,豪华阵容加顶级特效

最近&#xff0c;印度影坛再次掀起了风潮&#xff0c;一部名为《毗湿奴降临》的神话大片强势登陆各大影院&#xff0c;上映首周票房就飙升至105亿卢比&#xff0c;成功占据了票房榜首的位置。之后&#xff0c;这部电影也在北美上映&#xff0c;海外市场的表现同样不俗&#xff…...

Git使用经验总结6-删除远端历史记录

删除远端的历史记录但是不影响最新的仓库内容是笔者一直想实现的功能&#xff0c;有两个很不错的用处&#xff1a; 有的历史提交不慎包含了比较敏感的信息&#xff0c;提交的时候没注意&#xff0c;过了一段时间才发现。这个时候已经有了很多新的历史提交&#xff0c;无法再回…...

Linux 下查找运行中的 Java 进程及 .jar 文件位置

在 Linux 环境中&#xff0c;有时我们需要查找正在运行的 Java 进程以及它们对应的 .jar 文件位置。本文将介绍如何使用命令行工具来实现这一目标。 前言 在 Linux 系统中&#xff0c;我们经常需要监控正在运行的应用程序&#xff0c;特别是在出现问题时&#xff0c;了解应用程…...

Openwrt 安装 AX210 无线网卡

安装 TTYD 我安装的是官方原版的 Openwrt&#xff0c;首先需要安装 YYTD 来从网页控制 Openwrt。 安装驱动 参考这个链接&#xff0c;跟着做。 iwlwifi-firmware-ax210 不要直接拷贝粘贴&#xff0c;CSDN 复制文字最后面有网站添加的信息。 lspci opkg update opkg instal…...

在VitePress中进行页面链接:最佳实践与实例

在使用VitePress构建静态网站时&#xff0c;页面之间的链接是必不可少的。本文将介绍如何在VitePress中正确链接页面&#xff0c;包括内部页面和外部非VitePress页面的链接方法&#xff0c;并通过实例代码进行详细解释。 一、链接VitePress内部页面 在VitePress中&#xff0c…...

Qt/C++百度地图/高德地图/天地图/腾讯地图/谷歌地图/加载绘图工具栏

一、前言说明 在地图中提供一个绘图工具栏&#xff0c;可以便捷的在地图上添加各种覆盖物&#xff0c;比如折线、多边形、矩形、圆形等&#xff0c;然后可以获取这些覆盖物的路径以及中心点等属性。这里有几个小插曲&#xff0c;比如百度地图gl版本默认不提供这个功能&#xf…...

Vue2 与 Vue3 的区别有哪些

Vue 2 和 Vue 3 在许多方面都有显著的区别&#xff0c;包括性能、API 设计、功能特性等。以下是它们主要的区别&#xff1a; 1. 响应式系统 Vue 2: 基于 Object.defineProperty: Vue 2 使用 Object.defineProperty 来实现响应式数据。这种方法在处理对象属性时有一定的局限性…...

加锁造成的线程优先级反转

优先级反转(Priority Inversion),也称优先级翻转,一般是在优先级不同的多线程环境中发生。在桌面操作系统中,线程的优先级不是太重要,因此较少见优先级反转的现象。但是,优先级反转是实时操作系统(RTOS)中一个常见的问题,特别是在采用优先级调度算法的系统中。这个问…...

【日常记录-Java】SpringBoot中使用无返回值的异步方法

Author&#xff1a;赵志乾 Date&#xff1a;2024-09-05 Declaration&#xff1a;All Right Reserved&#xff01;&#xff01;&#xff01; 1. 简介 在SpringBoot中&#xff0c;使用Async注解可以很方便地标记一个方法为异步执行。好处是调用者无需等待这些方法完成便可继续执…...

【深度学习】多层感知机的从零开始实现与简洁实现

可以说&#xff0c;到现在我们才真正接触到深度网络。最简单的深度网络称为多层感知机。 多层感知机由多层神经元组成&#xff0c;每一层与它的上一层相连&#xff0c;从中接收输入&#xff1b;同时每一层也与它的下一层相连&#xff0c;影响当前层的神经元。 和以前相同&…...

4、Django Admin对自定义的计算字段进行排序

通常&#xff0c;Django会为模型属性字段&#xff0c;自动添加排序功能。当你添加计算字段时&#xff0c;Django不知道如何执行order_by&#xff0c;因此它不会在该字段上添加排序功能。 如果要在计算字段上添加排序&#xff0c;则必须告诉Django需要排序的内容。你可以通过在…...

rsync搭建全网备份

rsync搭建全网备份 1. 总体概述1.1 目标1.2 简易指导图1.3 涉及工具或命令1.4 环境 2. 实施2.1 配置备份服务器2.2 备份文件准备2.3 整合命令2.4 扩展功能 1. 总体概述 1.1 目标 本次搭建目标&#xff1a; 每天定时把服务器数据备份到备份服务器备份完成后进行校验把过期数据…...

HTML怎么标注输入格式示例_HTML placeholder展示格式模板【技巧】

不能。placeholder属性值仅支持纯文本&#xff0c;HTML标签如<small>会被原样显示&#xff0c;不解析&#xff1b;它不支持样式、子元素或换行&#xff0c;且无法替代label实现无障碍访问&#xff0c;需用浮动label等结构替代。placeholder 里能写 HTML 吗不能。placehol…...

Minecraft源码反编译终极指南:DecompilerMC完整使用教程

Minecraft源码反编译终极指南&#xff1a;DecompilerMC完整使用教程 【免费下载链接】DecompilerMC This repository allows you to decompile any minecraft version that was published after 19w36a without any 3rd party mappings, you just need to execute the script o…...

告别乱码!用.editorconfig一劳永逸解决Unity团队编码冲突(附完整配置)

告别乱码&#xff01;用.editorconfig一劳永逸解决Unity团队编码冲突&#xff08;附完整配置&#xff09; 团队协作开发Unity项目时&#xff0c;最令人头疼的问题之一莫过于编码格式不统一导致的乱码。想象一下这样的场景&#xff1a;你精心编写的C#脚本在同事的电脑上打开时&a…...

BaiduPCS-Web完全指南:彻底告别百度网盘限速的终极解决方案

BaiduPCS-Web完全指南&#xff1a;彻底告别百度网盘限速的终极解决方案 【免费下载链接】baidupcs-web 项目地址: https://gitcode.com/gh_mirrors/ba/baidupcs-web 还在为百度网盘令人绝望的下载速度而烦恼吗&#xff1f;当你急需下载重要文件时&#xff0c;几十KB/s的…...

Windows USB开发新范式:使用UsbDk突破系统限制实现设备独占访问

Windows USB开发新范式&#xff1a;使用UsbDk突破系统限制实现设备独占访问 【免费下载链接】UsbDk Usb Drivers Development Kit for Windows 项目地址: https://gitcode.com/gh_mirrors/us/UsbDk 痛点识别&#xff1a;传统USB开发面临的挑战 当你尝试在Windows平台上…...

Qwen3-0.6B-FP8从部署到应用:完整流程详解,新手必看

Qwen3-0.6B-FP8从部署到应用&#xff1a;完整流程详解&#xff0c;新手必看 你是不是刚接触AI模型&#xff0c;看着各种复杂的部署命令和配置就头疼&#xff1f;想快速体验一个能聊天、能推理、还能帮你写东西的智能助手&#xff0c;但又担心自己的电脑配置不够&#xff0c;或…...

终极PrismLauncher架构解析:从源码到实践的Minecraft启动器构建指南

终极PrismLauncher架构解析&#xff1a;从源码到实践的Minecraft启动器构建指南 【免费下载链接】PrismLauncher A custom launcher for Minecraft that allows you to easily manage multiple installations of Minecraft at once (Fork of MultiMC) 项目地址: https://gitc…...

别再只调参了!用Open3D搞定点云凹凸检测,从数学原理到Python代码的保姆级避坑指南

点云凹凸检测实战&#xff1a;从数学本质到Open3D高效实现 点云处理中的凹凸缺陷检测一直是工业质检、三维重建等领域的核心难题。很多工程师习惯性地把问题归结为"参数没调好"&#xff0c;却忽略了背后的数学本质和算法选择逻辑。本文将带您跳出调参陷阱&#xff0…...

3秒定位文件:Linux文件搜索效率提升10倍的秘密武器

3秒定位文件&#xff1a;Linux文件搜索效率提升10倍的秘密武器 【免费下载链接】fsearch A fast file search utility for Unix-like systems based on GTK3 项目地址: https://gitcode.com/gh_mirrors/fs/fsearch 在Linux系统中&#xff0c;文件搜索往往是效率瓶颈的重…...

解决IDE性能瓶颈与代码补全效率问题:TabNine AI引擎架构优化与生产环境部署实践

解决IDE性能瓶颈与代码补全效率问题&#xff1a;TabNine AI引擎架构优化与生产环境部署实践 【免费下载链接】TabNine AI Code Completions 项目地址: https://gitcode.com/gh_mirrors/ta/TabNine TabNine是一款基于人工智能的全语言代码自动补全工具&#xff0c;通过深…...