腾讯云这么自信续费比首年翻好几倍

原来一年一百多的轻量服务器,续费竟然要每年 630 多,挺自信的哈

续费 轻量 自信 服务器11 条回复 • 2021-07-21 09:26:42 +08:00
cherbim 1
cherbim 34 天前
都是白嫖首年,真香一直续费,建议转移到无忧轻量上,15 元 /月
eason1874 2
eason1874 34 天前
都这样,原价贵得很,这是给代理利润空间。

用量大找官方的销售顾问,用量小找代理,不想带第三方就自己时不时看看活动,买特价机器,领代金券续费。
AoEiuV020 3
AoEiuV020 34 天前
新用户白菜价是惯例了,谷歌甚至倒贴,
kop1989 4
kop1989 34 天前
首年优惠的钱就是靠逐渐垒高你的迁移成本抠出来的。
而且还有个点,但凡你的程序体系里出现一丁点非他们生态的东西。(比如域名管理、dns 解析、对象存储),出现事故他们就会无脑往这些产品上泼脏水。
jookr 5
jookr 34 天前
双 11 一次续多年就没这么烦恼了
xumng123 6
xumng123 34 天前 via iPhone
我也没部署啥值钱的应用,就一个 zerotier moon,当时买这个就是怕被套路,还有国外的做备份
love 7
love 34 天前
我就搞不懂,为啥云计算要搞代理呢?为啥要让中间商赚差价呢?阿里云应该都听说过啊不存在酒香巷子深的问题。

国外亚马逊 /微软云有这种事吗?
kebamt 8
kebamt 34 天前 via iPhone
@AoEiuV020 谷歌之前是 1 年免费+300 美元的,现在变成了 90 天+300 美元
xumng123 9
xumng123 34 天前 via iPhone
@love 我也搞不懂
NewYear 10
NewYear 34 天前
因为本身成本就不低。

huang119412 11
huang119412 34 天前
阿里云 400,嫖了 6 年 1 核 2g1m 共享型

有没有续费相对便宜一些的云服务器

发现阿里云 /腾讯云各种新人 /老用户的新购活动挺多的,上车后到期续费都死贵死贵的,有没有适合长期续费相对便宜一些的呢?

需求不高,1C2G/2C4G 都可以。

12 条回复    2021-07-27 20:49:50 +08:00

zhurong
    1

zhurong   35 天前

老用户也有优惠的但不会像新用户那么便宜了, 加 vx: beautwill
hazy
    2

hazy   35 天前

腾讯轻量无忧,1C/2G/5M 15 元 /月,2C/4G/8M 56 元 /月,续费同价,每三个月还可以领一张 20 元的域名续费券,详细可以看看我的*近回复…
wupeaking
    3

wupeaking   35 天前

前段时间买了 ucloud, 新用户有优惠。 想着所有的云厂商续费都贵的离谱。果断买了 3 年。280 元。
3dwelcome
    4

3dwelcome   35 天前

换个思路,买台廉价的云服务器,然后家里 24 小时开一台强力电脑,用 socket rpc 实时挂载服务到云上,提供各种计算服务。

这样你就拥有了一台又便宜,又强力的云服务器。

ReferenceE
    5

ReferenceE   35 天前 via Android

Virmach,老商家了,稳定性不够
不过一年也就掉线 3 小时之内吧?
但是是真的便宜,包括 black Friday
yanzhiling2001
    6

yanzhiling2001   35 天前

常逛逛 loc 论坛,会见到很多便宜小鸡,跑路也很多。
Mosugar
    7

Mosugar   34 天前

目前在用腾讯云腾讯轻量无忧,1C/2G/5M 180 一年。续费不涨价。
istevenshen
    8

istevenshen   34 天前

@hazy @Mosugar 感谢,轻量无忧除了建站外会不会担心爆流量?
hazy
    9

hazy   34 天前

@istevenshen 单向流量计算,正常使用没问题,如果担心的话可以通过 api 设置一个流量到达 95%自动关机的脚本
neuron42
    10

neuron42   33 天前 via Android

virmach 。或者直接家里自建

模拟 GIWIGI 客户端认证过程

模拟 GIWIGI 客户端认证过程

这不是破解!

模拟 giwifi 客户端的登录验证过程,仅用于学术交流。

分别使用 python 和 Golang 实现了 GIWIFI 客户端的认证过程,使用时需要先用抓包工具抓取一下 giwi 认证过程中的流量信息,在代码中填入对应的信息即可。

GitHub: https://github.com/Pililink/GIWIFI-demo-Login

有问题可以提 issues

关于 Django 执行单独文件时导入内部包的问题

关于 Django 执行单独文件时导入内部包的问题

.
├── django_web
│   ├── RealTimeData
│   │   ├── init.py
│   │   ├── admin.py
│   │   ├── apps.py
│   │   ├── migrations
│   │   │   ├── 0001_initial.py
│   │   │   ├── init.py
│   │   ├── models.py
│   │   ├── sql_api_test.html
│   │   ├── templates
│   │   │   └── RealTimeData
│   │   │   └── index.html
│   │   ├── tests.py
│   │   ├── urls.py
│   │   └── views.py
│   ├── init.py
│   ├── db.sqlite3
│   ├── django_web
│   │   ├── init.py
│   │   ├── asgi.py
│   │   ├── settings.py
│   │   ├── urls.py
│   │   └── wsgi.py
│   └── manage.py
└── main.py

这是文件结构,同时 main.py 处在一个 django_web 的文件夹下。
现在的问题是:当我单独执行 main.py 文件时,导入 RealTimeData 的模块出现 ModuleNotFoundError 。

然而在配置 DJANGO_SETTINGS_MODULE 时引用 settings 文件是没问题的。sys.path 也包含了 main.py 的父目录的路径。

print(sys.path)
os.environ.setdefault(‘DJANGO_SETTINGS_MODULE’, ‘django_web.django_web.settings’)
django.setup()

from django_web.RealTimeData.models import RowRecord

现在才发现提问题不给报错信息是多么愚蠢,程序在 django.setup()那一步就已经出错。
”’ (django_env) mac@Luxis-MacBook-Pro django_web % python -m main Traceback (most recent call last): File “/Users/mac/opt/miniconda3/envs/django_env/lib/python3.8/runpy.py“, line 194, in _run_module_as_main return _run_code(code, main_globals, None, File “/Users/mac/opt/miniconda3/envs/django_env/lib/python3.8/runpy.py“, line 87, in _run_code exec(code, run_globals) File “/Users/mac/Documents/gitee_dir/django_web/main.py“, line 13, in <module> django.setup() File “/Users/mac/opt/miniconda3/envs/django_env/lib/python3.8/site-packages/django/init.py”, line 24, in setup apps.populate(settings.INSTALLED_APPS) File “/Users/mac/opt/miniconda3/envs/django_env/lib/python3.8/site-packages/django/apps/registry.py“, line 91, in populate app_config = AppConfig.create(entry) File “/Users/mac/opt/miniconda3/envs/django_env/lib/python3.8/site-packages/django/apps/config.py“, line 212, in create mod = import_module(mod_path) File “/Users/mac/opt/miniconda3/envs/django_env/lib/python3.8/importlib/init.py”, line 127, in import_module return _bootstrap._gcd_import(name[level:], package, level) File “<frozen importlib._bootstrap>”, line 1014, in _gcd_import File “<frozen importlib._bootstrap>”, line 991, in _find_and_load File “<frozen importlib._bootstrap>”, line 961, in _find_and_load_unlocked File “<frozen importlib._bootstrap>”, line 219, in _call_with_frames_removed File “<frozen importlib._bootstrap>”, line 1014, in _gcd_import File “<frozen importlib._bootstrap>”, line 991, in _find_and_load File “<frozen importlib._bootstrap>”, line 973, in _find_and_load_unlocked ModuleNotFoundError: No module named ‘RealTimeData’ ”’

Python 图片去除图片水印的问题

Python 图片去除图片水印的问题

代码


def remove_water_footer(path, newPath):
    """
    去除水印
    """
    # https://zhuanlan.zhihu.com/p/138169619
    img=cv2.imread(path,1)
    hight,width,depth=img.shape[0:3]
    height = hight

    #截取
    # cropped = img[int(hight*0.8):hight, int(width*0.7):width]  # 裁剪坐标为[y0:y1, x0:x1]
    #cropped = cropped_img(img,
    #height=height,
    #width=width)
    cropped_size = cropped_img_size(
    height=height,
    width=width)
    cropped = img[cropped_size[0][0]: cropped_size[0][1], cropped_size[1][0]: cropped_size[1][1]]
    cv2.imwrite(newPath, cropped)
    imgSY = cv2.imread(newPath,1)

    #图片二值化处理,把[200,200,200]-[250,250,250]以外的颜色变成 0
    # thresh = cv2.inRange(imgSY,np.array([200,200,200]),np.array([250,250,250]))
    thresh = cv2.inRange(imgSY,np.array([24, 43, 48]),np.array([160,180,190]))
    #创建形状和尺寸的结构元素
    kernel = np.ones((3,3),np.uint8)
    #扩展待修复区域
    hi_mask = cv2.dilate(thresh,kernel,iterations=10)
    specular = cv2.inpaint(imgSY,hi_mask,5,flags=cv2.INPAINT_TELEA)
    cv2.imwrite(newPath, specular)

    #覆盖图片
    imgSY = Image.open(newPath)
    img = Image.open(path)
    # img.paste(imgSY, (int(width*0.7),int(hight*0.8),width,hight))
    img.paste(imgSY, (cropped_size[1][0],cropped_size[0][0],cropped_size[1][1],cropped_size[0][1]))
    img.save(newPath)
    logger.info("去除水印: `{}` => `{}`".format(path, newPath))

我在网上搜的,但是这个样子会留下背景版,如果吧整个水印都删除呢,

也就是指定区域的部分都删除,这种效果该怎么做呢?

有没有人觉得 Python Flask 写后端很难用?

有没有人觉得 Python Flask 写后端很难用?

  • 自我感觉 python 越用越难用,动态类型提示*其的不友好
  • flask 集成 sqlalchemy 想要返回 JSON 返回内容还需要通过 marshmallow 等把 python 对象转换
  • 如果想要使用登录管理集成 flask_login 等
  • 这样就会出现项目中存在多个模型定义文件
  • 再说 sqlalchemy 本身就挺复杂,然后类型提示大部分都没用,想不起来时候只能查文档,非常繁琐
  • 还有 flaks 本身导入通过一个全局 app 控制,这样进行项目分块时候*其不友好,有蓝图也一样,对比 dotnent 和 java 自识别,flask 需要手动注册就很麻烦了
  • 然后我总结一句就是,动态语言一时爽,过一段时间自己都不知道这个变量会产生什么结果类型

如何把 Java properties 转换为具有层级结构的字典

如何把 Java properties 转换为具有层级结构的字典

java.class.version = 60.0
java.home = /usr/lib/jvm/java-16-openjdk
java.runtime.name = OpenJDK Runtime Environment
java.runtime.version = 16.0.2+7
java.specification.name = Java Platform API Specification
java.specification.vendor = Oracle Corporation
java.specification.version = 16
java.vendor = N/A
java.vendor.url = https://openjdk.java.net/
java.vendor.url = https://openjdk.java.net/
java.vendor.url.bug = https://bugreport.java.com/bugreport/
java.vendor.url.bug = https://bugreport.java.com/bugreport/
java.vendor.url.bug = https://bugreport.java.com/bugreport/
java.version = 16.0.2
java.version.date = 2021-07-20
java.vm.name = OpenJDK 64-Bit Server VM
java.vm.specification.name = Java Virtual Machine Specification
java.vm.specification.vendor = Oracle Corporation
java.vm.specification.version = 16
java.vm.vendor = Oracle Corporation
java.vm.version = 16.0.2+7
sun.arch.data.model = 64

我用 javaproperties 模块把上面内容转换成了字典:

{'java.class.version': '60.0',
 'java.home': '/usr/lib/jvm/java-16-openjdk',
 'java.runtime.name': 'OpenJDK Runtime Environment',
 'java.runtime.version': '16.0.2+7',
 'java.specification.name': 'Java Platform API Specification',
 'java.specification.vendor': 'Oracle Corporation',
 'java.specification.version': '16',
 'java.vendor': 'N/A',
 'java.vendor.url': 'https://openjdk.java.net/',
 'java.vendor.url.bug': 'https://bugreport.java.com/bugreport/',
 'java.version': '16.0.2',
 'java.version.date': '2021-07-20',
 'java.vm.name': 'OpenJDK 64-Bit Server VM',
 'java.vm.specification.name': 'Java Virtual Machine Specification',
 'java.vm.specification.vendor': 'Oracle Corporation',
 'java.vm.specification.version': '16',
 'java.vm.vendor': 'Oracle Corporation',
 'java.vm.version': '16.0.2+7',
 'sun.arch.data.model': '64'}

但是我想根据这些 properties 的键中相同的部分,把它转换成具有层级结构的字典,类似于:

{
    'java': {
        'class': {
            'version': 60.0
        },
        'home': '/usr/lib/jvm/java-16-openjdk',
        'runtime': {
            'name': 'OpenJDK Runtime Environment',
            'version': '16.0.2+7'
        },
        'specification': {
            'name': 'Java Platform API Specification',
            'vendor': 'Oracle Corporation',
            'verison': 16
        },
        ...: ...  # 省略剩余的内容
    },
    'sun': {
        'arch': {
            'data': {
                'model': 64
            }
        }
    }
}

有没有第三方库方便进行转换?或者如果自己造轮子,提供一个思路?

iOS中FMDB的使用

在iOS开发中,如果本地需要做大量的缓存,FMDB用的是比较多的,怎么集成到项目中我这里就不说了,主要说一下FMDB使用的一个思路和自己遇到的坑。
在我自己开发聊天系统的时候,我一般是用FMDB用来缓存*近联系人列表和用户的聊天消息列表。因为聊天系统,涉及到的读和写比较频繁,所以我会封装一个单例类用来处理每个部分的数据存储和读写。
下面是用来管理*近联系人缓存的功能实现,其余的模块也可以按照这样的模式来,封装好方法,操作的时候直接调方法即可,比较方便。
先看一下.h文件的代码,这样大致就可以知道这个类主要用来干什么:

#import <Foundation/Foundation.h>

@class FMDatabase;
@interface simpleChatCache : NSObject

+(instancetype)sharedChatCache;

//设置用户uid
-(BOOL)initialize:(NSString*)myUserID;
-(void)uninitialize;

//获取所有用户信息
-(void)getUserAll:(void(^)(NSMutableArray*))callback;
//插入或者更新新的数据
-(void)upsert:(NSDictionary*)data;
//设置某个用户未读信息条数为0
-(void)setUnreadNum:(NSString*)targetUserID andNum:(int)num;
//设置某个群未读信息条数为0
-(void)setGroupUnreadNum:(NSString *)targetGroupid andNum:(int)num;
//获取未读消息数
-(void)getUnreadNum:(NSString*)targetUserID andCallback:(void(^)(int))callback;
//获取某个群未读条数
-(void)getGroupUnreadNum:(NSString *)targetGroupid andCallback:(void(^)(int))callback;
//删除某人的聊天记录
-(void)remove:(NSString*)targetUserID;
//删除某一条群聊
-(void)removeGroup:(NSString *)groupid;
//清理数据,除了世界聊天
-(void)clearCachesWithoutWorld;
@end

 

.m文件:

#import “simpleChatCache.h”
#import “FMDB.h”
FMDatabaseQueue* __g_DB = nil;

@interface simpleChatCache() {
NSString* _tableName;
NSString* _userID;
}
@end

@implementation simpleChatCache

+(instancetype)sharedChatCache {
static simpleChatCache *sharedRunner;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedRunner = [[simpleChatCache alloc] init];
if(!__g_DB){
__g_DB = [FMDatabaseQueue databaseQueueWithPath:[MyControl simpleChatDBPath]];
}
});
return sharedRunner;
}

-(BOOL)initialize:(NSString*)myUserID {
//NSLog(@”sqlite:%@”, [FMDatabase isSQLiteThreadSafe] ? @”线程安全” : @”线程不安全”);

if(_userID == myUserID)
return YES;

[self uninitialize];

_userID = myUserID;
_tableName = [NSString stringWithFormat:@”message_top%@”, _userID];

if(__g_DB) {
[self checkTableStruct];
}

return (__g_DB != nil);
}

-(void)uninitialize {
_userID = nil;
_tableName = nil;
}

-(void)dealloc {
if(__g_DB) {
[__g_DB close];
__g_DB = nil;
}
NSLog(@”simpleChatCache dealloc”);
}

-(void)createTable {
WS(ws);
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[__g_DB inDatabase:^(FMDatabase *db) {
if(![db tableExists:_tableName]) {
NSString * sql = [NSString stringWithFormat:@”create table if not exists %@(lastchatid,lastchattype,laststrchat,lasttime,nickname,unreadnums,isgroup,groupid,groupname,membercount,gface,srcuid)”,_tableName];
if (![db executeUpdate:sql]) {
NSLog(@”simpleChatCache 创建表%@失败”, _tableName);
}
} else {
[ws upgradeTable:db];
}
}];
});
}

-(void)upgradeTable:(FMDatabase*)db {

}

-(void) checkTableStruct {
[self createTable];
}

//获取所有用户信息
-(void)getUserAll:(void(^)(NSMutableArray*))callback {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[__g_DB inDatabase:^(FMDatabase *db) {

NSMutableArray* array = [NSMutableArray array];

NSString * sql = [NSString stringWithFormat:@”select srcuid,lastchatid,lastchattype,laststrchat,lasttime,nickname,isgroup,groupid,groupname,membercount,gface,unreadnums from %@ where srcuid != 0 order by lasttime desc”,_tableName];
FMResultSet* rs = [db executeQuery:sql];
while (rs.next) {
NSString * srcuid = [rs stringForColumnIndex:0];
NSString * lastchatid = [rs stringForColumnIndex:1];
NSString * lastchattype = [rs stringForColumnIndex:2];
NSString * laststrchat = [rs stringForColumnIndex:3];
NSString * lasttime = [rs stringForColumnIndex:4];
NSString * nickname = [rs stringForColumnIndex:5];
NSString * isgroup = [rs stringForColumnIndex:6];
NSString * groupid = [rs stringForColumnIndex:7];
NSString * gropname = [rs stringForColumnIndex:8];
NSString * membercount = [rs stringForColumnIndex:9];
NSString * gface = [rs stringForColumnIndex:10];
NSString * unreadnums = [rs stringForColumnIndex:11];

NSMutableDictionary * tempDic = [NSMutableDictionary dictionaryWithCapacity:12];
[tempDic setValue:lastchatid forKey:@”lastchatid”];
[tempDic setValue:lastchattype forKey:@”lastchattype”];
[tempDic setValue:laststrchat forKey:@”laststrchat”];
[tempDic setValue:lasttime forKey:@”lasttime”];
[tempDic setValue:nickname forKey:@”nickname”];
[tempDic setValue:unreadnums forKey:@”nums”];
[tempDic setValue:srcuid forKey:@”srcuid”];
[tempDic setValue:isgroup forKey:@”isgroup”];
[tempDic setValue:groupid forKey:@”groupid”];
[tempDic setValue:gropname forKey:@”groupname”];
[tempDic setValue:membercount forKey:@”membercount”];
[tempDic setValue:gface forKey:@”gface”];
[array addObject:tempDic];
}
[rs close];

dispatch_async(dispatch_get_main_queue(), ^{
if(callback) {
callback(array);
}
});
}];
});
}

//插入或者更新新的数据
-(void)upsert:(NSDictionary*)data {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[__g_DB inDatabase:^(FMDatabase *db) {

if ([data[@”isgroup”] intValue]) {
//群聊
NSString * sql;
NSString * gropSql = [NSString stringWithFormat:@”select groupid from %@ where groupid = %@”,_tableName, data[@”groupid”]];
int groupid = [db intForQuery:gropSql];
if (data.count == 3) {
//更新groupname
if (groupid > 0) {
sql = [NSString stringWithFormat:@”update %@ set groupname = ‘%@’ where groupid = %@”,
_tableName,
data[@”groupname”],
data[@”groupid”]];
[db executeUpdate:sql];
}

}else{
if (groupid > 0) {
sql = [NSString stringWithFormat:@”update %@ set groupname = ‘%@’,lastchatid = %@,lastchattype = %@,laststrchat = ‘%@’,lasttime = %@,nickname = ‘%@’,membercount = %@,unreadnums = unreadnums + %@ where groupid = %@”,
_tableName,
data[@”groupname”],
data[@”lastchatid”],
data[@”lastchattype”],
data[@”laststrchat”],
data[@”lasttime”],
data[@”nickname”],
data[@”membercount”],
data[@”nums”],
data[@”groupid”]];
}else{
sql = [NSString stringWithFormat:@”insert into %@(lastchatid,lastchattype,laststrchat,lasttime,nickname,unreadnums,srcuid,isgroup,groupid,groupname,membercount) values (%@,%@,’%@’,%@,’%@’,%@,%@,%@,%@,’%@’,%@)”,
_tableName,
data[@”lastchatid”],
data[@”lastchattype”],
data[@”laststrchat”],
data[@”lasttime”],
data[@”nickname”],
data[@”nums”],
data[@”srcuid”],
data[@”isgroup”],
data[@”groupid”],
data[@”groupname”],
data[@”membercount”]];
}

[db executeUpdate:sql];
}

}else{
NSString * sql = [NSString stringWithFormat:@”select srcuid from %@ where srcuid = %@”,_tableName, data[@”srcuid”]];

int srcuid = [db intForQuery:sql];

if(srcuid > 0) {
if([[data allKeys] containsObject:@”nickname”]) {
//更新私聊
sql = [NSString stringWithFormat:@”update %@ set lastchatid = %@,lastchattype = %@,laststrchat = ‘%@’,lasttime = %@,nickname = ‘%@’,unreadnums = unreadnums + %@ where srcuid = %@”,
_tableName,
data[@”lastchatid”],
data[@”lastchattype”],
data[@”laststrchat”],
data[@”lasttime”],
data[@”nickname”],
data[@”nums”],
data[@”srcuid”]];
} else {
//系统消息
sql = [NSString stringWithFormat:@”update %@ set lastchatid = %@,lastchattype = %@,laststrchat = ‘%@’,lasttime = %@,unreadnums = unreadnums + %@ where srcuid = %@”,
_tableName,
data[@”lastchatid”],
data[@”lastchattype”],
data[@”laststrchat”],
data[@”lasttime”],
data[@”nums”],
data[@”srcuid”]];
}
[db executeUpdate:sql];
} else {
//插入私聊
sql = [NSString stringWithFormat:@”insert into %@(lastchatid,lastchattype,laststrchat,lasttime,nickname,unreadnums,srcuid,isgroup,groupid) values (%@,%@,’%@’,%@,’%@’,%@,%@,%@,%@)”,
_tableName,
data[@”lastchatid”],
data[@”lastchattype”],
data[@”laststrchat”],
data[@”lasttime”],
data[@”nickname”],
data[@”nums”],
data[@”srcuid”],
@”0″,
@”0″];
[db executeUpdate:sql];
}
}
}];
});
}

//设置某个用户未读信息条数为0
-(void)setUnreadNum:(NSString*)targetUserID andNum:(int)num {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[__g_DB inDatabase:^(FMDatabase *db) {
NSString * sql = [NSString stringWithFormat:@”update %@ set unreadnums = %d where srcuid = %@”, _tableName,num, targetUserID];
[db executeUpdate:sql];
}];
});
}

//设置某个群未读信息条数为0
-(void)setGroupUnreadNum:(NSString *)targetGroupid andNum:(int)num
{
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[__g_DB inDatabase:^(FMDatabase *db) {
NSString * sql = [NSString stringWithFormat:@”update %@ set unreadnums = %d where groupid = %@”, _tableName,num, targetGroupid];
[db executeUpdate:sql];
}];
});
}

//获取未读消息数
-(void)getUnreadNum:(NSString*)targetUserID andCallback:(void(^)(int))callback {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[__g_DB inDatabase:^(FMDatabase *db) {
NSString * sql = [NSString stringWithFormat:@”select unreadnums from %@ where srcuid = %@”, _tableName,targetUserID];
int nums = [db intForQuery:sql];
dispatch_async(dispatch_get_main_queue(), ^{
if(callback) {
callback(nums);
}
});
}];
});
}

//获取某个群未读条数
-(void)getGroupUnreadNum:(NSString *)targetGroupid andCallback:(void(^)(int))callback
{
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[__g_DB inDatabase:^(FMDatabase *db) {
NSString * sql = [NSString stringWithFormat:@”select unreadnums from %@ where groupid = %@”, _tableName,targetGroupid];
int nums = [db intForQuery:sql];
dispatch_async(dispatch_get_main_queue(), ^{
if(callback) {
callback(nums);
}
});
}];
});
}

-(void)remove:(NSString*)targetUserID {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[__g_DB inDatabase:^(FMDatabase *db) {
NSString * sql = [NSString stringWithFormat:@”delete from %@ where srcuid = %@”, _tableName,targetUserID];
[db executeUpdate:sql];
}];
});
}

//删除某一条群聊
-(void)removeGroup:(NSString *)groupid
{
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[__g_DB inDatabase:^(FMDatabase *db) {
NSString * sql = [NSString stringWithFormat:@”delete from %@ where groupid = %@”, _tableName,groupid];
[db executeUpdate:sql];
}];
});
}

//清理数据
-(void)clearCachesWithoutWorld {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[__g_DB inDatabase:^(FMDatabase *db) {
NSString * sql = [NSString stringWithFormat:@”delete from %@ where srcuid <> 0″, _tableName];
[db executeUpdate:sql];
}];
});
}

@end

 

我们会看到单例实例化的时候有这样一句代码:

if(!__g_DB){
__g_DB = [FMDatabaseQueue databaseQueueWithPath:[MyControl simpleChatDBPath]];
}

这句代码可以保证整个程序中只存在一个FMDatabaseQueue的实例对象。
因为首先FMDatabase是不具备线程安全的,如果两个线程中同时操作数据库,就会”is currently in use” ;FMDatabaseQueue其实是一个串行的调度队列(G-C-D),数据库的操作必须是顺序执行,不能两个数据库的操作同时执行,如果是两个线程各自创建了FMDatabaseQueue的实例,线程同时执行时,就会出现相同的数据库操作同时触发,导致”database is locked“,所以如果是一个FMDatabaseQueue实例下,多个线程下同时操作,其实是在排在同一个队列中逐一操作的,没有同时操作。这样就可以保证FMDatabase的数据是线程安全的。
所以在使用FMDB的时候一定要使用FMDatabaseQueue,这个是FMDB用来管理数据库的,可以避免很多麻烦。
另外需要注意的是在使用sql语句的时候,如果涉及到字符串,占位符就需要使用单引号,否则也会遇到问题。

iOS中主队列的同/异步执行

主队列是主线中的一个串行队列,通常我们刷新UI都会回到主队列。下面我们来看一下主队列同步执行和异步执行会产生什么样的效果。

主队列,异步执行
//主队列+异步执行
//不会开启新的线程,任务顺序执行
-(void)test5{
NSLog(@”主队列+异步执行”);
//global_queue 主队列
dispatch_queue_t queue = dispatch_get_main_queue();
//dispatch_async 异步执行
dispatch_async(queue, ^{
for (int i = 0; i < 10; i ++) {
NSLog(@”1——%@”,[NSThread currentThread]);
}
});
dispatch_async(queue, ^{
for (int i = 0; i < 10; i ++) {
NSLog(@”2——%@”,[NSThread currentThread]);
}
});
dispatch_async(queue, ^{
for (int i = 0; i < 10; i ++) {
NSLog(@”3——%@”,[NSThread currentThread]);
}
});
NSLog(@”end”);
}

打印结果:

2018-08-30 13:13:04.267741+0800 GCD_demo[2846:151142] 主队列+异步执行
2018-08-30 13:13:04.267853+0800 GCD_demo[2846:151142] end
2018-08-30 13:13:04.270849+0800 GCD_demo[2846:151142] 1——<NSThread: 0x604000077540>{number = 1, name = main}
2018-08-30 13:13:04.270981+0800 GCD_demo[2846:151142] 1——<NSThread: 0x604000077540>{number = 1, name = main}
2018-08-30 13:13:04.271349+0800 GCD_demo[2846:151142] 1——<NSThread: 0x604000077540>{number = 1, name = main}
2018-08-30 13:13:04.271499+0800 GCD_demo[2846:151142] 1——<NSThread: 0x604000077540>{number = 1, name = main}
2018-08-30 13:13:04.271774+0800 GCD_demo[2846:151142] 1——<NSThread: 0x604000077540>{number = 1, name = main}
2018-08-30 13:13:04.271930+0800 GCD_demo[2846:151142] 1——<NSThread: 0x604000077540>{number = 1, name = main}
2018-08-30 13:13:04.272077+0800 GCD_demo[2846:151142] 1——<NSThread: 0x604000077540>{number = 1, name = main}
2018-08-30 13:13:04.272461+0800 GCD_demo[2846:151142] 1——<NSThread: 0x604000077540>{number = 1, name = main}
2018-08-30 13:13:04.272559+0800 GCD_demo[2846:151142] 1——<NSThread: 0x604000077540>{number = 1, name = main}
2018-08-30 13:13:04.272959+0800 GCD_demo[2846:151142] 1——<NSThread: 0x604000077540>{number = 1, name = main}
2018-08-30 13:13:04.273210+0800 GCD_demo[2846:151142] 2——<NSThread: 0x604000077540>{number = 1, name = main}
2018-08-30 13:13:04.273323+0800 GCD_demo[2846:151142] 2——<NSThread: 0x604000077540>{number = 1, name = main}
2018-08-30 13:13:04.273433+0800 GCD_demo[2846:151142] 2——<NSThread: 0x604000077540>{number = 1, name = main}
2018-08-30 13:13:04.273799+0800 GCD_demo[2846:151142] 2——<NSThread: 0x604000077540>{number = 1, name = main}
2018-08-30 13:13:04.274130+0800 GCD_demo[2846:151142] 2——<NSThread: 0x604000077540>{number = 1, name = main}
2018-08-30 13:13:04.274484+0800 GCD_demo[2846:151142] 2——<NSThread: 0x604000077540>{number = 1, name = main}
2018-08-30 13:13:04.274732+0800 GCD_demo[2846:151142] 2——<NSThread: 0x604000077540>{number = 1, name = main}
2018-08-30 13:13:04.274909+0800 GCD_demo[2846:151142] 2——<NSThread: 0x604000077540>{number = 1, name = main}
2018-08-30 13:13:04.275144+0800 GCD_demo[2846:151142] 2——<NSThread: 0x604000077540>{number = 1, name = main}
2018-08-30 13:13:04.275409+0800 GCD_demo[2846:151142] 2——<NSThread: 0x604000077540>{number = 1, name = main}
2018-08-30 13:13:04.275707+0800 GCD_demo[2846:151142] 3——<NSThread: 0x604000077540>{number = 1, name = main}
2018-08-30 13:13:04.276049+0800 GCD_demo[2846:151142] 3——<NSThread: 0x604000077540>{number = 1, name = main}
2018-08-30 13:13:04.276228+0800 GCD_demo[2846:151142] 3——<NSThread: 0x604000077540>{number = 1, name = main}
2018-08-30 13:13:04.277371+0800 GCD_demo[2846:151142] 3——<NSThread: 0x604000077540>{number = 1, name = main}
2018-08-30 13:13:04.277609+0800 GCD_demo[2846:151142] 3——<NSThread: 0x604000077540>{number = 1, name = main}
2018-08-30 13:13:04.277728+0800 GCD_demo[2846:151142] 3——<NSThread: 0x604000077540>{number = 1, name = main}
2018-08-30 13:13:04.277858+0800 GCD_demo[2846:151142] 3——<NSThread: 0x604000077540>{number = 1, name = main}
2018-08-30 13:13:04.277973+0800 GCD_demo[2846:151142] 3——<NSThread: 0x604000077540>{number = 1, name = main}
2018-08-30 13:13:04.278075+0800 GCD_demo[2846:151142] 3——<NSThread: 0x604000077540>{number = 1, name = main}
2018-08-30 13:13:04.278478+0800 GCD_demo[2846:151142] 3——<NSThread: 0x604000077540>{number = 1, name = main}

主队列异步执行时,会先执行完主线程上的代码,然后在主线程上顺序执行任务,不会有新的线程产生,所有任务都是在主线程上完成的。

主队列,同步执行
//主队列+同步执行
//在主线程中执行时会死锁,程序会崩溃
-(void)test6{
//死锁原因:同步对于任务是立刻执行的,当把*个任务放进主队列时,就会立即执行。可是主线程现在正在处理test6方法,任务需要等test6执行完才能执行,然后任务不执行完的话,test6方法也不算执行完,所以出现了test6需要等待任务执行完才能执行完,而任务又需要等待test6执行完才能执行。相互等待,死锁!
NSLog(@”主队列+同步执行”);
//global_queue 主队列
dispatch_queue_t queue = dispatch_get_main_queue();
//dispatch_async 同步执行
//任务
dispatch_sync(queue, ^{
for (int i = 0; i < 10; i ++) {
NSLog(@”1——%@”,[NSThread currentThread]);
}
});
dispatch_sync(queue, ^{
for (int i = 0; i < 10; i ++) {
NSLog(@”2——%@”,[NSThread currentThread]);
}
});
dispatch_sync(queue, ^{
for (int i = 0; i < 10; i ++) {
NSLog(@”3——%@”,[NSThread currentThread]);
}
});
NSLog(@”end”);
}

 

以上方法直接在主线程中调用的话会造成死锁,可以放到子线程中去执行,如使用如下方法调用:

dispatch_async(dispatch_queue_create(“test”, DISPATCH_QUEUE_SERIAL), ^{
[self test6];
});

打印结果:

2018-08-30 13:23:52.827501+0800 GCD_demo[2975:159890] 主队列+同步执行
2018-08-30 13:23:52.830572+0800 GCD_demo[2975:159805] 1——<NSThread: 0x60400007f380>{number = 1, name = main}
2018-08-30 13:23:52.830986+0800 GCD_demo[2975:159805] 1——<NSThread: 0x60400007f380>{number = 1, name = main}
2018-08-30 13:23:52.831360+0800 GCD_demo[2975:159805] 1——<NSThread: 0x60400007f380>{number = 1, name = main}
2018-08-30 13:23:52.831610+0800 GCD_demo[2975:159805] 1——<NSThread: 0x60400007f380>{number = 1, name = main}
2018-08-30 13:23:52.831685+0800 GCD_demo[2975:159805] 1——<NSThread: 0x60400007f380>{number = 1, name = main}
2018-08-30 13:23:52.831847+0800 GCD_demo[2975:159805] 1——<NSThread: 0x60400007f380>{number = 1, name = main}
2018-08-30 13:23:52.831954+0800 GCD_demo[2975:159805] 1——<NSThread: 0x60400007f380>{number = 1, name = main}
2018-08-30 13:23:52.832051+0800 GCD_demo[2975:159805] 1——<NSThread: 0x60400007f380>{number = 1, name = main}
2018-08-30 13:23:52.832248+0800 GCD_demo[2975:159805] 1——<NSThread: 0x60400007f380>{number = 1, name = main}
2018-08-30 13:23:52.832477+0800 GCD_demo[2975:159805] 1——<NSThread: 0x60400007f380>{number = 1, name = main}
2018-08-30 13:23:52.833122+0800 GCD_demo[2975:159805] 2——<NSThread: 0x60400007f380>{number = 1, name = main}
2018-08-30 13:23:52.833498+0800 GCD_demo[2975:159805] 2——<NSThread: 0x60400007f380>{number = 1, name = main}
2018-08-30 13:23:52.833786+0800 GCD_demo[2975:159805] 2——<NSThread: 0x60400007f380>{number = 1, name = main}
2018-08-30 13:23:52.834105+0800 GCD_demo[2975:159805] 2——<NSThread: 0x60400007f380>{number = 1, name = main}
2018-08-30 13:23:52.834322+0800 GCD_demo[2975:159805] 2——<NSThread: 0x60400007f380>{number = 1, name = main}
2018-08-30 13:23:52.834583+0800 GCD_demo[2975:159805] 2——<NSThread: 0x60400007f380>{number = 1, name = main}
2018-08-30 13:23:52.834833+0800 GCD_demo[2975:159805] 2——<NSThread: 0x60400007f380>{number = 1, name = main}
2018-08-30 13:23:52.835076+0800 GCD_demo[2975:159805] 2——<NSThread: 0x60400007f380>{number = 1, name = main}
2018-08-30 13:23:52.835285+0800 GCD_demo[2975:159805] 2——<NSThread: 0x60400007f380>{number = 1, name = main}
2018-08-30 13:23:52.835551+0800 GCD_demo[2975:159805] 2——<NSThread: 0x60400007f380>{number = 1, name = main}
2018-08-30 13:23:52.836106+0800 GCD_demo[2975:159805] 3——<NSThread: 0x60400007f380>{number = 1, name = main}
2018-08-30 13:23:52.836188+0800 GCD_demo[2975:159805] 3——<NSThread: 0x60400007f380>{number = 1, name = main}
2018-08-30 13:23:52.836303+0800 GCD_demo[2975:159805] 3——<NSThread: 0x60400007f380>{number = 1, name = main}
2018-08-30 13:23:52.836637+0800 GCD_demo[2975:159805] 3——<NSThread: 0x60400007f380>{number = 1, name = main}
2018-08-30 13:23:52.836775+0800 GCD_demo[2975:159805] 3——<NSThread: 0x60400007f380>{number = 1, name = main}
2018-08-30 13:23:52.836966+0800 GCD_demo[2975:159805] 3——<NSThread: 0x60400007f380>{number = 1, name = main}
2018-08-30 13:23:52.837446+0800 GCD_demo[2975:159805] 3——<NSThread: 0x60400007f380>{number = 1, name = main}
2018-08-30 13:23:52.838085+0800 GCD_demo[2975:159805] 3——<NSThread: 0x60400007f380>{number = 1, name = main}
2018-08-30 13:23:52.838172+0800 GCD_demo[2975:159805] 3——<NSThread: 0x60400007f380>{number = 1, name = main}
2018-08-30 13:23:52.838279+0800 GCD_demo[2975:159805] 3——<NSThread: 0x60400007f380>{number = 1, name = main}
2018-08-30 13:23:52.838639+0800 GCD_demo[2975:159890] end

放到子线程中执行,test6方法中的任务不用等待test6执行完再执行,所以不会死锁。

iOS 中block的循环引用问题

开发中经常使用weakSelf和strongSelf来解决block的循环引用问题,但是是不是所有的block都会导致循环引用呢?显然不是的,那么怎么判断调用一个带有block方法时是否会造成循环引用呢,我们来分析一下。
首先我们来写一个含有block的类,并调用自己,然后在外部实现这个block,来测试什么情况会出现循环引用。

@interface ALDTestBlockModel ()

@property (nonatomic, copy) dispatch_block_t testBlock;
@property (nonatomic, copy) NSString * testString;

@end

@implementation ALDTestBlockModel
– (void)initWithBlock1:(dispatch_block_t)block{
//对象不持有该block
if (block) {
block();
}
}

– (void)initWithBlock2:(dispatch_block_t)block{
//对象持有该block
self.testBlock = block;
if (self.testBlock) {
self.testBlock();
}
}
@end

 

外部测试代码:

@interface ALDTestBlockController ()

@property (nonatomic, strong) ALDTestBlockModel *model;

@end

@implementation ALDTestBlockController

– (void)viewDidLoad {
[super viewDidLoad];
self.model = [ALDTestBlockModel new];
[self noSelfTest];
//[self haveSelfTest];
}

– (void)noSelfTest{
[self.model initWithBlock1:^{
NSLog(@”1111self=[]”);
}];
[self.model initWithBlock2:^{
NSLog(@”2222self=[]”);
}];
}

– (void)haveSelfTest{
[self.model initWithBlock1:^{
NSLog(@”1111self====== %@”,self.testString);
}];
[self.model initWithBlock2:^{
NSLog(@”2222self====== %@”,self.testString);
}];
}

– (void)dealloc{
NSLog(@””);
}

@end

我们首先来测试在实现block中没有出现self及成员变量之类的,我们断点在block类中,可以看一下block和testBlock的isa,我们会发现block和testBlock的isa都是一个NSGlobalBlock类型,然后在controller点击返回,断点在dealloc,会发现会走断点。

然后来测一下block中出现self或者成员变量时,先测

[self.model initWithBlock1:^{
NSLog(@”1111self====== %@”,self.testString);
}];

 

这个方法里面,未持有block,点击返回,dealloc的断点依然会走。
再来测一下

[self.model initWithBlock2:^{
NSLog(@”2222self====== %@”,self.testString);
}];

 

这个方法里面,model持有了block,点击返回不会走dealloc,说明出现了循环引用。这个时候就可以用weakSelf和strongSelf来解决循环引用的问题了。
总结一下,当调用带有block的方法时,如果该方法内部没有持有该block,那么这个方法在被调用时,就不需要考虑循环引用的问题。如果该方法内部持有了该block,那么这个方法在被调用时,一定要注意block实现时是否会用到self或者self相关的,如果要用到,就需要用weakSelf和strongSelf来避免循环引用。
*后补充一点,block的isa有三种:

NSGlobalBlock:存储在程序的数据区域,在 block 内部没有引用任何外部变量。
NSStackBlock:存储在栈上,在 block 内部引用外部变量。在 MRC 下,栈块在当函数退出的时候,该空间会被回收,因此如果再调用该 block 会导致 crash,通过拷贝该栈块,可以解决该问题。在 ARC 模式下,生成的 block 也是 栈块,只是当赋值给 strong 对象时,系统会主动对其进行 copy。
NSMallocBlock:存储在堆上的 block。
我们都知道栈区的内存是系统自动管理的,所以出现NSStackBlock时我们可以不用考虑内存问题,但是出现NSMallocBlock时一定要注意了,很容易出现内存泄漏。