Category 的一些事

 

来源:伯乐在线 - Tsui YuenHong

链接:http://ios.jobbole.com/90422/

点击 → 申请加入伯乐在线专栏作者

 

新增实践部分:偏方 Hook 进某些方法来添加功能

 

Category – 简介

 

Category(类别)是 Objective-C 2.0 添加的新特性(十年前的新特性 ?)。其作用可以扩展已有的类, 而不必通过子类化已有类,甚至也不必知道已有类的源码,还有就是分散代码,使已有类的体积大大减少,也利于分工合作。

 

在苹果开源项目中,我们可以下载相关的源码来查看 category 的资料。

 

在 AFNetworking 和 SDWebImage 中也大量用到 category 来扩展已有类和分散代码。

 

关于 category 的定义可以在 objc-runtime-new.h 中找到。由其定义可以看出 category 可以正常实现功能有:添加实例方法、类方法、协议、实例属性。( 在后面的实践中,发现类属性也是可以添加的 )

 

struct category_t {

    const char *name;

    classref_t cls;

    struct method_list_t *instanceMethods;

    struct method_list_t *classMethods;

    struct protocol_list_t *protocols;

    struct property_list_t *instanceProperties;

 

    method_list_t *methodsForMeta(bool isMeta) {

        if (isMeta) return classMethods;

        else return instanceMethods;

    }

 

    property_list_t *propertiesForMeta(bool isMeta) {

        if (isMeta) return nil; // classProperties;

        else return instanceProperties;

    }

};

 

随便说一句,本文并不主要注重 category 的实现细节和工作原理。关于细节的方面可以看相关文章 深入理解Objective-C:Category(上)  深入理解Objective-C:Category(下) 和 结合 category 工作原理分析 OC2.0 中的 runtime 。

 


 

Category – 能做什么

 

首先,我们先来创建一个 Person 类以及 Person 类的 category,可以看得出 category 的文件名就是 已有类名+自定义名。

 

// Person.h

@interface Person : NSObject

 

@property (nonatomic, copy) NSString *name;

 

+ (void)run;

- (void)talk;

 

@end

 

// Person.m

@implementation Person

 

// 原实例方法

- (void)talk{

    NSLog(@"\n我是原实例方法\n我是%@",self.name);

}

 

// 原类方法

+ (void)run{

    NSLog(@"\n我是原类方法\n我是跑得很快的的香港记者");

}

 

@end

 

// Person+OtherSkills.h

@interface Person (OtherSkills){

    //⚠️ instance variables may not be placed in categories

    //int i;

    //NSString *str;

}

 

// 添加实例属性

@property (nonatomic, copy) NSString *otherName;

// 添加类属性

@property (class, nonatomic, copy) NSString *clsStr;

 

// 重写已有类方法

+ (void)run;

- (void)talk;

 

// 为已有类添加方法

- (void)logInstProp;

+ (void)logClsProp;

 

// Person+OtherSkills.m

static NSString *_clsStr = nil;

static NSString *_otherName = nil;

 

@implementation Person (OtherSkills)

 

@dynamic otherName;

 

// 重写类方法

+ (void)run{

    // 警告⚠️ Category is implementing a method which will also be implemented by its primary class

    NSLog(@"\n我是重写方法\n我是跑得很快的的香港记者");

}

 

// 重写实例方法

- (void)talk{

    // 警告⚠️ Category is implementing a method which will also be implemented by its primary class

    NSLog(@"\n我是重写方法\n我是会谈笑风生的%@",self.otherName);

}

 

// 输出实例属性

- (void)logInstProp{

    NSLog(@"\n输出实例属性\n我是会谈笑风生的%@",self.otherName);

}

 

// 输出类属性

+ (void)logClsProp{

    NSLog(@"\n输出类属性\n我是会谈笑风生的%@",self.clsStr);

}

 

+ (NSString *)clsStr{

    return _clsStr;

}

 

+ (void)setClsStr:(NSString *)clsStr{

    _clsStr = clsStr;

}

 

- (NSString *)otherName{

    return _otherName;

}

 

- (void)setOtherName:(NSString *)otherName{

    _otherName = otherName;

}

 

创建完代码之后,下面我们来看看 category 到底能干什么。

 

顺便一提,我是在网上看到很多文章说 category 不能添加属性,这是说法是不对的,如 Person+OtherSkills.h 中就添加了一个 otherName 的属性。正确的说法应该是 category 不能添加实例变量,否则编译器会报错 instance variables may not be placed in categories。正常情况下,因为 category 不能添加实例变量,也会导致属性的 setter & getter 方法不能正常工作。( 当然,可以利用 Runtime 为 category 动态关联属性,最后会介绍两种使 category 属性正常工作的方法)

 

category 可以为已有类添加实例属性。

 

如 Person+OtherSkills.h 中就添加了一个 otherName 的属性。可以出来能正常工作。

 

// 运行代码

Person *p1 = [[Person alloc] init];

 

// 实例属性

p1.otherName = @"小花";

[p1 logInstProp];

 

p1.otherName = @"小明";

[p1 logInstProp];

 

// 输出结果

2016-09-11 09:45:09.935 category[37281:1509791]

输出实例属性

我是会谈笑风生的小花

2016-09-11 09:45:09.936 category[37281:1509791]

输出实例属性

我是会谈笑风生的小明

 

category 可以为已有类添加类属性。

 

虽然,category_t 中是没有定义 clssProperties,但是根据实际操作却显示 category 的确可以为已有类添加类属性并且成功执行。

 

// 运行代码

Person.clsStr = @"小东";

[Person logClsProp];

 

// 输出结果

2016-09-11 09:45:09.936 category[37281:1509791]

输出类属性

我是会谈笑风生的小东

 

category 可以为已有类添加实例方法和类方法。

 

在上面的两个例子中已经体现了 category 可以为已有类添加实例方法和类方法。这里将讨论加入 category 重写了已有类的方法会怎么样,在创建的代码中我们已经重写了 run 和 talk 方法,那这时我们来调用看看。

 

// 运行代码

// 调用类方法

[Person run];

// 调用实例方法    

Person *p1 = [[Person alloc] init];

[p1 talk];

 

// 输出结果

2016-09-11 11:22:05.817 category[37733:1562534]

我是重写方法

我是跑得很快的的香港记者

2016-09-11 11:22:05.817 category[37733:1562534]

我是重写方法

我是会谈笑风生的(null)

 

可以看得出来,这时候无论是已有类中的类方法和实例方法都可以被 category 替换到其中的重写方法,即使我现在是没有导入 Person+OtherSkills.h 。这就带来一个很严重的问题,如果在 category 中不小心重写了已有类的方法将导致原方法无法正常执行。所以使用 category 添加方法时候请注意是否和已有类重名了,正如 《 Effective Objective-C 2.0 》 中的第 25 条所建议的:

 

在给第三方类添加 category 时添加方法时记得加上你的专有前缀

 

然而,因为 category 重写方法是并不是替换掉原方法,而是往已有类中继续添加方法,所以还是有机会去调用到原方法。这里利用 class_copyMethodList 获取 Person 类的全部类方法和实例方法。

 

// 获取 Person 的方法列表

unsigned int personMCount;

// 获取实例方法

//Method *personMList = class_copyMethodList([Person class], &personMCount);

// 获取类方法

Method *personMList = class_copyMethodList(object_getClass([Person class]), &personMCount);

NSMutableArray *mArr = [NSMutableArray array];

 

// 这里是倒序获取,所以 mArr 第一个方法对应的是 Person 类中最后一个方法

for (int i = personMCount - 1; i >= 0; i--) {

 

   SEL sel = NULL;

   IMP imp = NULL;

 

   Method method = personMList[i];

   NSString *methodName = [NSString stringWithCString:sel_getName(method_getName(method))

                                             encoding:NSUTF8StringEncoding];

   [mArr addObject:methodName];

 

   if ([@"run" isEqualToString:methodName]) {

       imp = method_getImplementation(method);

       sel = method_getName(method);

       ((void (*)(id, SEL))imp)(p1, sel); // 这里的 sel 有什么用呢 ?!

       //break;

   }

}

 

free(personMList);

 

其中输出的类方法和实例方法分别如下,显示原方法的确可以被调用。

不过我这里有个疑问,使用 imp 时第二个参数 sel 到底有什么用呢?

 

2016-09-11 11:52:44.795 category[37893:1582677]

我是原类方法

我是跑得很快的的香港记者

2016-09-11 11:52:44.796 category[37893:1582677]

我是重写方法

我是跑得很快的的香港记者

2016-09-11 11:52:44.796 category[37893:1582677] (

    run, // 原方法

    run, // 重写方法

    "setClsStr:",

    logClsProp,

    clsStr

)

 

2016-09-11 11:54:14.545 category[37927:1584029]

我是原实例方法

我是(null)

2016-09-11 11:54:14.545 category[37927:1584029]

我是重写方法

我是会谈笑风生的(null)

2016-09-11 11:54:14.545 category[37927:1584029] (

    "setName:",

    name,

    ".cxx_destruct",

    "setOtherName:",

    logInstProp,

    tanxiaofengsheng,

    otherName,

    talk, //原方法

    talk  //重写方法

 


 

category 可以为已有类添加协议。

 

这里先添加一个新的 category,负责处理他谈笑风生的行为,和写个协议让他上电视。

 

// Person+Delegate.h

#import "Person.h"

 

// 添加协议

@protocol PersonDelegate

 

- (void)showInTV;

 

@end

 

@interface Person (Delegate)

 

// 添加 delegate

@property (nonatomic, weak) id delegate;

 

- (void)tanxiaofengsheng;

 

@end

 

// Person+Delegate.m

#import "Person+Delegate.h"

#import

 

@implementation Person (Delegate)

 

- (id)delegate{

    return objc_getAssociatedObject(self, @selector(delegate));

}

 

- (void)setDelegate:(id)delegate{

    objc_setAssociatedObject(self, @selector(delegate), delegate, OBJC_ASSOCIATION_ASSIGN);

}

 

- (void)tanxiaofengsheng{

    for (int i = 0 ; i

 

在相应的代理里面添加 showInTV 的方法

 

// 运行代码

Person *p1 = [[Person alloc] init];

p1.delegate = self;

 

// 开始谈笑风生了

[p1 tanxiaofengsheng];

 

// ShowInTV 方法的实现

- (void)showInTV{

    UIImageView *imageView = [[UIImageView alloc] initWithFrame:CGRectMake(100, 100, 150, 150)];

    imageView.image = [UIImage imageNamed:@"naive.jpg"];

    [self.view addSubview:imageView];

}

 

这样就利用 category 为已有类添加了协议。

 

关于 category 的基本应用就介绍到这里了。下面就来分享一下 category 的实践中的使用。

 


 

Category – 实践

 

偏方:Hook 进某些方法来添加功能

 

一般来说,为原方法添加功能都是利用 Runtime 来 Method Swizzling。不过这里也有个奇淫技巧来实现同样的功能,例如我要在所有 VC 的 - (void)viewDidLoad 里面打印一个句话,就可以用 category 重写已有类的方法,因为 category 重写方法不是通过替换原方法来实现的,而是在原方法列表又增添一个新的同名方法,这就创造了机会给我们重新调用原方法了。

 

// 待 Hook 类

// ViewController.m

// 待替换方法 无参

- (void)viewDidLoad {

    [super viewDidLoad];

    [self testForHook:@"Hello World"];

    NSLog(@"执行原方法");

}

 

// 待替换方法 有参

- (void)testForHook:(NSString *)str1{

    NSLog(@"%@",str1);

}

 

// category 实现方法

// ViewController+HookOriginMethod.m

// category 重写原方法

- (void)viewDidLoad {

    NSLog(@"HOOK SUCCESS! \n--%@-- DidLoad !",[self class]);

    IMP imp = [self getOriginMethod:@"viewDidLoad"];

    ((void (*)(id, SEL))imp)(self, @selector(viewDidLoad));

}

 

// category 重写原方法

- (void)testForHook:(NSString *)str1{

    NSLog(@"HOOK SUCCESS \n--%s-- 执行",_cmd);

    IMP imp = [self getOriginMethod:@"testForHook:"];

    ((void (*)(id, SEL, ...))imp)(self, @selector(testForHook:), str1);

}

 

// 获取原方法的 IMP

- (IMP)getOriginMethod:(NSString *)originMethod{

    // 获取 Person 的方法列表

    unsigned int methodCount;

    // 获取实例方法

    Method *VCMethodList = class_copyMethodList([self class], &methodCount);

 

    IMP imp = NULL;

 

    // 这里是倒序获取,所以 mArr 第一个方法对应的是 Person 类中最后一个方法

    for (int i = methodCount - 1; i >= 0; i--) {

 

        Method method = VCMethodList[i];

        NSString *methodName = [NSString stringWithCString:sel_getName(method_getName(method))

                                                  encoding:NSUTF8StringEncoding];

 

        if ([originMethod isEqualToString:methodName]) {

            imp = method_getImplementation(method);

            break;

        }

    }

 

    free(VCMethodList);

    return imp;

}

 

// 执行代码

// ViewController.m

- (void)viewDidLoad {

    [super viewDidLoad];

    [self testForHook:@"Hello World"];

    NSLog(@"执行原方法");

}

 

// 输出结果

2016-09-12 23:00:15.887 category[63655:2375379] HOOK SUCCESS! 

--ViewController-- DidLoad !

2016-09-12 23:00:15.888 category[63655:2375379] HOOK SUCCESS 

--testForHook:-- 执行

2016-09-12 23:00:15.889 category[63655:2375379] Hello World

2016-09-12 23:00:15.889 category[63655:2375379] 执行原方法

 

查看输出结果,可以看得出来我们的 Hook 掉 viewDidLoad 来实现打印成功了。

 


 

UIButton 实现点击事件可以“传参”。

 

一般创建UIButton的时候都会使用 addTarget ...这个方法来为button添加点击事件,不过这个方法有个不好的地方就是无法传自己想要的参数。例如下面代码中声明了str,我的意图是点击button就使控制台或者屏幕显示str的内容。如果按照这样来写的我想到的解决办法就是将str设置为属性或者成员变量,不过这样都是比较麻烦而且不直观的(代码分散)。

 

NSString *str = @"hi";

UIButton *button = [[UIButton alloc] initWithFrame:CGRectMake(100, 250, 150, 100)];

button.backgroundColor = [UIColor redColor];

[button addTarget:self action:@selector(click:) forControlEvents:UIControlEventTouchDown];

[self.view addSubview:button];

 

// 点击事件

- (void)click:(UIButton *)button{

    ...    

}

 

我想到较好的解决办法应该在创建button,就为它设置具体的点击响应事件。实现方法就是为 UIButton 添加 block 属性或者添加可传入 block 的方法。具体代码如下:

 

// UIButton+Category.h

#import

 

typedef void(^ActionHandlerBlock)(void);

 

@interface UIButton (Category)

 

// 点击响应的 block

@property (nonatomic, copy) ActionHandlerBlock actionHandlerBlock;

 

// 设置 UIButton 的点击事件

- (void)kk_addActionHandler: (ActionHandlerBlock )actionHandlerBlock ForControlEvents:(UIControlEvents )controlEvents;

 

@end

 

// UIButton+Category.m

#import "UIButton+Category.h"

#import

 

static const void *kk_actionHandlerBlock = &kk_actionHandlerBlock;

 

@implementation UIButton (Category)

 

- (void)kk_addActionHandler:(ActionHandlerBlock)actionHandler ForControlEvents:(UIControlEvents)controlEvents{

 

    // 关联 actionHandler

    objc_setAssociatedObject(self, kk_actionHandlerBlock, actionHandler, OBJC_ASSOCIATION_COPY_NONATOMIC);

 

    // 设置点击事件

    [self addTarget:self action:@selector(handleAction) forControlEvents:controlEvents];

}

 

// 处理点击事件

- (void)handleAction{

 

    ActionHandlerBlock actionHandlerBlock = objc_getAssociatedObject(self, kk_actionHandlerBlock);

 

    if (actionHandlerBlock) {

        actionHandlerBlock();

    }

}

 

- (ActionHandlerBlock)actionHandlerBlock{

    return objc_getAssociatedObject(self, @selector(actionHandlerBlock));

}

 

- (void)setActionHandlerBlock:(ActionHandlerBlock)actionHandlerBlock{

    objc_setAssociatedObject(self, @selector(actionHandlerBlock), actionHandlerBlock, OBJC_ASSOCIATION_COPY_NONATOMIC);

}

 

@end

 

那现在我们来看看调用的结果,例如我现在想要的点击事件是 button 颜色随机变换。

 

UIButton *button = [[UIButton alloc] initWithFrame:CGRectMake(100, 250, 150, 100)];

button.backgroundColor = [UIColor redColor];

[self.view addSubview:button];

 

// 1. 通过实例方法传入 block 来修改  

UIButton *button2 = [[UIButton alloc] initWithFrame:CGRectMake(100, 400, 150, 100)];

button2.backgroundColor = [UIColor redColor];

[button2 kk_addActionHandler:^{

   button.backgroundColor = [UIColor colorWithRed:arc4random_uniform(256) / 255.0 green:arc4random_uniform(256) / 255.0 blue:arc4random_uniform(256) / 255.0 alpha:1.0];

} ForControlEvents:UIControlEventTouchDown];

[self.view addSubview:button2];

 

// 2. 通过修改 block 属性来修改

UIButton *button3 = [[UIButton alloc] initWithFrame:CGRectMake(100, 550, 150, 100)];

button3.backgroundColor = [UIColor redColor];

button3.actionHandlerBlock = ^{

   button.backgroundColor = [UIColor colorWithRed:arc4random_uniform(256) / 255.0 green:arc4random_uniform(256) / 255.0 blue:arc4random_uniform(256) / 255.0 alpha:1.0];

};

[button3 addTarget:self action:@selector(click:) forControlEvents:UIControlEventTouchUpInside];

[self.view addSubview:button3];

 

 

// 响应事件

- (void)click:(UIButton *)button{

    if (button.actionHandlerBlock) {

        button.actionHandlerBlock();

    }

}

 

显然,方法1和方法2在这个例子中实现的效果是相同的。不过,在不同场合这两个方法适用的范围也不同。

 

  1. 直接调用实例方法传入 block 会使代码更加简洁和集中,但不适合 block 需要传值的情景。

  2. 相反,设置 block 属性要在 @selector() 中的方法中调用 block,比较麻烦,不过在需要的情况下可以传入合适的参数。

 

p.s. 以后会继续补充实践部分。

 

最后说一下,两种使 category 属性正常工作的方法:

 

  1. 因为 category 不能创建实例变量,那就直接使用静态变量,如最开始为 ohterName 和clsStr 属性设置 setter & getter的做法。

  2. 使用objc_setAssociatedObject,其中 key 的选择有以下几种,个人比较喜欢第四种。

  • static char *key1; // SDWebImage & AFNetworking 中的做法,比较简单,而且 &key1 肯定唯一。key 取 &key1

  • static const char * const key2 = "key2"; // 网上看到的做法,指针不可变,指向内容不可变,但是这种情况必须在赋值确保 key2 指向内容的值是唯一。key 取 key2。

  • static const void *key3 = &key3; // 最取巧的方法,指向自己是为了不创建额外空间,而 const 修饰可以确保无法修改 key3 指向的内容。key 取 key3。

  • key 取 @selector(属性名),最方便,输入有提示,只要你确保属性名添加上合适的前缀就不会出问题。

     

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/457554.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

商品综合评价排名

店内有很多产品,而且包含但不局限于以下指标:浏览量、访客数、平均停留时长、详情页跳出率、下单转化率、下单支付转化率、支付转化率、下单金额、下单商品件数、下单买家数、支付金额、支付商品件数、加购件数、访客平均价值、收藏人数、客单价、搜索支…

Ajax实现原理详解

Ajax:Asynchronous javascript and xml,实现了客户端与服务器进行数据交流过程。使用技术的好处是:不用页面刷新,并且在等待页面传输数据的同时可以进行其他操作。 这就是异步调用的很好体现。首先得了解什么是异步和同步的概念。…

SpringJDBC解析3-回调函数(update为例)

PreparedStatementCallback作为一个接口,其中只有一个函数doInPrepatedStatement,这个函数是用于调用通用方法execute的时候无法处理的一些个性化处理方法,在update中的函数实现: protected int update(final PreparedStatementCr…

System.InvalidOperationException : 不应有 Response xmlns=''。

xml如下&#xff1a; <?xml version"1.0" encoding"UTF-8"?> <Response version"2"><datacash_reference>4700203048783633</datacash_reference><information>Failed to identify the card scheme of the supp…

Navicat Premium连接SQL Server

Navicat Premium连接SQL Server 步骤&#xff1a; 激活SQL Server 服务配置SQL Server网络配置连接SQL Server 激活SQLServer服务 直接搜索 计算机管理 点 服务和应用程序&#xff0c; 点 SQL Server配置管理器&#xff0c; 双击第一个SQL Server服务 不出意外的话&#xf…

processon完全装逼指南

一、引言 作为一名IT从业者&#xff0c;不仅要有扎实的知识储备&#xff0c;出色的业务能力&#xff0c;还需要具备一定的软实力。软实力体现在具体事务的处理能力&#xff0c;包括沟通&#xff0c;协作&#xff0c;团队领导&#xff0c;问题的解决方案等&#xff0c;这些能力在…

解决svn log显示no author,no date的方法之一

只要把svnserve.conf中的anon-access read 的read 改为none&#xff0c;也不需要重启svnserve就行 sh-4.1# grep "none" /var/www/html/svn/pro/conf/svnserve.conf ### and "none". The sample settings below are the defaults. anon-access none转载…

解决larave-dompdf中文字体显示问题

0、使用MPDF dompdf个人感觉没有那么好用&#xff0c;最终的生产环境使用的是MPDF&#xff0c;github上有文档说明。如果你坚持使用&#xff0c;下面是解决办法。可以明确的说&#xff0c;中文乱码是可以解决的。 1、安装laravel-dompdf依赖。 Packagist&#xff1a;https://pa…

mfc程序转化为qt_小峰的QT学习笔记

我的专业是输电线路&#xff0c;上个学期&#xff0c;我们开了一门架空线路设计基础的课&#xff0c;当时有一个大作业是计算线路的比载&#xff0c;临界档距&#xff0c;弧垂最低点和安装曲线。恰逢一门结课考试结束&#xff0c;大作业ddl快到&#xff0c;我和另外两个同专业的…

【IDEA 2016】intellij idea tomcat jsp 热部署

刚开始用IDEA&#xff0c;落伍的我&#xff0c;只是觉得IDEA好看。可以换界面。想法如此的low。 真是不太会用啊&#xff0c;弄好了tomcat。程序启动竟然改动一下就要重启&#xff0c;JSP页面也一样。 IDEA可以配置热部署&#xff0c;打开tomcat配置页面&#xff0c;将红框处&a…

设计模式11---组合模式(Composite Pattern)

一、组合模式定义 将对象组合成树形结构以表示“部分-整体”的层次结构&#xff0c;使得用户对单个对象和组合对象的使用具有一致性。Compose objects into tree structures to represent part-whole hierarchies. Composite lets clients treat individual objects and compos…

python实现Redis订阅发布

Redis 发布订阅 Redis 发布订阅可以用在像消息通知&#xff0c;群聊&#xff0c;定向推送&#xff0c;参数刷新加载等业务场景 发布订阅模型有三个角色&#xff1a; 发布者&#xff08;Publisher&#xff09;订阅者(Subscriber)频道(channel) 每个订阅者可以订阅多个频道&am…

iOS开发UI篇—xib的简单使用

一、简单介绍 xib和storyboard的比较&#xff0c;一个轻量级一个重量级。 共同点&#xff1a; 都用来描述软件界面 都用Interface Builder工具来编辑 不同点: Xib是轻量级的&#xff0c;用来描述局部的UI界面 Storyboard是重量级的&#xff0c;用来描述整个软件的多个界面&…

【云栖计算之旅】线下沙龙第2期精彩预告:Docker在云平台上的最佳实践

Docker是一个开源的应用容器引擎&#xff0c;提供了一种在安全、可重复的环境中自动部署软件的方式&#xff0c;允许开发者将他们的应用和依赖包打包到一个可移植的容器中&#xff0c;然后发布到任何流行的Linux机器上&#xff0c;也可以实现虚拟化。容器完全使用沙箱机制&…

mysql int类型的长度值

整数类型的存储和范围(来自mysql手册) 类型字节最小值最大值(带符号的/无符号的)(带符号的/无符号的)TINYINT1-1281270255SMALLINT2-3276832767065535MEDIUMINT3-83886088388607016777215INT4-2147483648214748364704294967295BIGINT8-92233720368547758089223372036854775807…

龙王我当定了(一个在QQ刷龙王的脚本)

自从学了python&#xff0c;龙王再也没丢过&#xff0c;就是经常被打, QQ 和 TIM 都可以&#xff0c;发送时要把聊天窗口打开。 # 如果import报错&#xff0c;那可以pip下载这几个模块试一试 import win32gui import win32con import win32clipboard as w import random from…

navicat for mysql 数据库备份与还原

一, 首先设置, 备份保存路径 工具 -> 选项 点开 其他 -> 日志文件保存路径 二. 开始备份 备份分两种, 一种是以sql保存, 一种是保存为备份 SQL保存 右键点击你要备份的数据库, -> 转储SQL文件 选择位置和文件名 开始转储 导入 建议 删除所有表 或 重新建数据库 同导出…

DES的原理及python实现

DES加密算法原理及实现 DES是一种对称加密算法【即发送者与接收者持有相同的密钥】&#xff0c;它的基本原理是将要加密的数据划分为n个64位的块&#xff0c;然后使用一个56位的密钥逐个加密每一个64位的块&#xff0c;得到n个64位的密文块&#xff0c;最后将密文块拼接起来得…

华为手机充满有提醒吗_2020手机充电速度排名:最快21分钟充满,华为第15名

5G手机扎堆出现&#xff0c;中国5G基站数量也是不断增多&#xff0c;中国移动曾经表态&#xff0c;2020年底将会在全国地级市覆盖5G网络&#xff0c;全民5G时代终于到来&#xff01;从目前国内手机出货量数据来看&#xff0c;5G手机占比已经达到了六成以上&#xff0c;国产5G手…

关于移动手机端富文本编辑器qeditor图片上传改造

日前项目需要在移动端增加富文本编辑&#xff0c;上网找了下&#xff0c;大多数都是针对pc版的&#xff0c;不太兼容手机&#xff0c;当然由于手机屏幕小等原因也限制富文本编辑器的众多强大功能&#xff0c;所以要找的编辑器功能必须是精简的。 找了好久&#xff0c;发现qedit…