分类目录归档:coding

spring cloud学习笔记

早在16年,部门都尝试过使用Spring boot来搭建项目,实现一些小功能和业务,对于spring cloud也只是表面上的了解,没深入去学习过,这几个月还算比较系统学习,不过迁移成本实在太高,只能放弃,如果说后台团队在8-10人,我想迁移还是值得的,至少能让同事们学习到更多先进的东西,理解它的思路,包括如何使用docker去构建环境,去部署项目。

spring cloud并不是新东西,只是把很多需要的三方框架优化并整合到一起,核心基础是spring-boot,micro service之间是消息通信,如何高效通信,然后就有了注册-发现机制,所有的provider都注册到service discovery component上,这类支持有eureka,consul,zookeeper等,主要功能就是服务注册表,服务注册与发现,服务检查。consumer要调用provider就通过eureka等这类组件。服务要集群部署则可以通过ribbon在consumer实现负载均衡,通过一定规则或者自定义规则调用指定服务名的provider,如果service要提供rest api给consumer,通过feign即可。

不能让用户或者客户端去零散的调用微服务,使用统一网关是必要的,如zuul,不仅能统一服务,还能直接使用过滤器来筛选,提高效率。

如果上大型集群,使用config来统一管理微服务配置,直接配置git连接,总之就是方便。

要监控服务和容错处理则使用hystrix即可,内置rabbitmq兼容,还带有dashboard,实时监控,还有sleuth+zipkin,这种就高级了,实现分布式跟踪。

还有更高级的特性暂时也没细研究,利用spring boot + consul/eureka + ribbon + zuul + hystrix + config 已经可以实现足够强大的微服务后台,而且很简单,复杂的已经被实现,对于我们只需要在于业务方面,如何设计,如何拆分细粒度,松耦合等。不过对于人员的基础要求还是相对高点,现在云南这边大部分培训出来的就整点spring mvc完事,boot都未必接触,分给每个人一个服务,实际上是一个独立的模块,很难控制他的代码质量,增加代码审核成本,像我们现有项目,实际上拆成了4,5个,后台有独立common,service,controller,web,mq等,对于初级点的程序员,我只给controller项目即可,照着设计好的接口文档调用service返回json,供app,node层使用。如果说上cloud,至少来说一个微服务得两人来做,或者一个人一个微服务,然后两三个微服务一个负责人。

我看node也有实现的eureka-js,就是说也可以用node写微服务,供客户端调用,使用sidecar整合。

下个项目想整下这套解决方案,培养一堆相对比较厉害的程序员出来。

另外就是看了两本书,聊聊架构,进化,前者真的是太差了,通篇废话,做架构整成了哲学,什么是架构,什么是产品,我也实在无语,还挺贵一本。另外就是技术相关书籍国内作者和国外差的不知道哪去了。

iOS读书笔记

最近更新iOS版本6米网,顺便看了几本iOS的书,iOS面试之道和iOS开发进阶,个人觉得也就是个入门,相比早年看的objc的书还有cocoa编程,设计模式等书,这两本还是差强人意,还以为有很深入的讲解,其实大部分都是点到为止,还带很多工具介绍,上架介绍等教程,其实没这必要,少贴点代码,多让人去搜索学习更好,比如之前看过的java程序员修炼之道,我就觉得挺好,讲的系统,很多干货需要下来google下,没有那么多琐碎的代码。

Go语言学习笔记

昨天忙完抽空花了点时间在https://golangbot.com/learn-golang-series/ 学习Go语言,环境我是早搭建了,也就写了个HelloWorld。昨天在那个教程上学习了1-17,整个语法感觉还是很像C语言的,唯一让我觉得怪异的就是类型后置,不过这是都是基础,go的核心是在并发处理上,接下来往后学习相关教程,顺便看看https://github.com/ethereum/go-ethereum,早日上手区块链相关。

 

8月17日增补

昨天小程序已经上线,今天打算利用一天时间学完整个golang教程,为下一步区块链做准备。学完基础倒是没觉得有什么特别的,学到concurrency和oop这两章,OOP的三大特性:封装,继承,多态。golang使用自己的方式实现了,struct实现封装,继承。struct里面可以套用匿名struct这算是go的一种特性。对于多态,则使用interface实现掉。

对于并发核心就是goroutine,降低了并发的开发成本。使用go关键字就new了一个goroutine,或者说是一个线程。主函数就是在一个单独的goroutine中运行,即main goroutine。这就有点像iOS的runloop,不过iOS是消息驱动类型的,runloop作用是获取消息和处理事件。题外话,node不就是事件驱动,单线程非阻塞,eventloop获取event,到event handler,处理完成继续event loop。不过node其实是支持多进程的cluster,另外非阻塞实际上IO,异步都是在内部子线程处理。

  • Goroutines are extremely cheap when compared to threads. They are only a few kb in stack size and the stack can grow and shrink according to needs of the application whereas in the case of threads the stack size has to be specified and is fixed.
  • The Goroutines are multiplexed to fewer number of OS threads. There might be only one thread in a program with thousands of Goroutines. If any Goroutine in that thread blocks say waiting for user input, then another OS thread is created and the remaining Goroutines are moved to the new OS thread. All these are taken care by the runtime and we as programmers are abstracted from these intricate details and are given a clean API to work with concurrency.
  • Goroutines communicate using channels. Channels by design prevent race conditions from happening when accessing shared memory using Goroutines. Channels can be thought of as a pipe using which Goroutines communicate. We will discuss channels in detail in the next tutorial.

对于channel,就是读取写入阻塞的队列。select就是多个阻塞队列任意又一个不再阻塞,select就会被执行。mutex就是互斥,和channel搭配使用。

f() //普通阻塞方法
go f() //创建一个新的goroutine然后执行该方法

ch := make(chan int) // 可以写入读写int类型的channel ch <- x //写入 ch = <-ch //读取 <-ch //读取并直接舍弃 func hello(done chan bool) { fmt.Println("Hello world goroutine") done <- true } func main() { done := make(chan bool) go hello(done) <-done fmt.Println("main function") }

defer 表示延迟方法延迟到return之前最后执行,不过如果是defer stack(defer列表),按照last in first out原则。自定义error,使用struct+interface即可。panic主要是用于抛出异常,recover则是捕获这个异常并进行处理,两者必须在同一goroutine中,如果defer a()中加个recover方法,recover方法必须使用defer调用,否则无法捕获,然后 go b()中抛出一个panic,这是无法捕获的,此刻程序就会终止掉。class function则是很像iOS中的block。reflection以为着我们能在运行时获取一个struct的所有变量的type,value。这是高级语言的特性,java,iOS都有这块,不过iOS是要基于runtime获取,java的反射则是spring的基础。

学完了教程,感觉go这块用在后台开发实现高性能是很容易。

静态lib使用注意事项

最近做的项目用了一个三方包,纯C++的,打包成的静态文件,这种包需要注意的地方在于需要引入libstdc++库,然后还需要在build settings中将Language C++中的C++ Standard Library修改为libstdc++(GNU C++ standard)。
另外的就是基本规则,使用的地方将后缀名修改为mm。

几个常用的命令
lipo -info your.a 查看lib支持的架构.
lipo create i386.a arm64.a -output universal.a 将多个lib合并为一个通用lib,方便调试,当然上架肯定得用最小的lib了。

FMDB小技巧

FMDB,这个库我也用了很多年了,早些的版本在多线程使用时会报错,不能在不同的线程使用db操作。我自己用单例独立线程出处理这些问题。现在就方便多了,使用GCD,之前介绍GCD说过,fmdb使用一个独立的串行队列,我只要专注于对模型和DAO的编写就足够了。

_queue = dispatch_queue_create([[NSString stringWithFormat:@"fmdb.%@", self] UTF8String], NULL);
dispatch_queue_set_specific(_queue, kDispatchQueueSpecificKey, (__bridge void *)self, NULL);

在使用的地方(inDatabase)做了断言

FMDatabaseQueue *currentSyncQueue = (__bridge id)dispatch_get_specific(kDispatchQueueSpecificKey);
    assert(currentSyncQueue != self && "inDatabase: was called reentrantly on the same queue, which would lead to a deadlock");

使用方式举例

- (WWGroupObject *)selectByGroupId:(NSInteger)groupId
{
__block WWGroupObject *obj = nil;
FMDatabaseQueue *dbQueue = [[WWDatabaseHelper sharedInstance] dbQueue];
[dbQueue inDatabase:^(FMDatabase *db) {
    FMResultSet *rs = [db executeQuery:[self setTable:SELECT_GROUPID_ROSTER], [NSNumber numberWithInteger:groupId]];
    while ([rs next]) {
        obj = [[WWGroupObject alloc] init];
        obj.groupId = [rs intForColumn:@"id"];
        obj.groupName = [rs stringForColumn:@"groupName"];
        obj.groupType = [rs intForColumn:@"groupType"];
        }
    }];
  return obj;
}

因为它是用的dispatch_sync,整个是串行操作,就不会出现不返回数据的情况,之前我还用dispatch_semaphore_t
来判断block是否执行结束然后再返回数据,其实没必要的,串行队列一定是执行完block才return的。

早些版本我是写一个DatabaseHelper单例类保存一个FMDatabase实例,现在是单例类保存一个FMDatabaseQueue实例,它是使用路径初始化的,执行一套sql建表文件executeStatements和更新版本做的一些增删字段的sql语句。

写一堆各种模型,对应数据库表的。

在写一些DAO模块,对应增删改查,DAO使用单例helper的queue执行inDatabase方法,去调用executeQuery执行语句。
这种方式就是看起来比较直观,很容易分模块给别人编写,使用时隐藏了sql,如- (void)insertTitle:(WWTitleObject *)obj,只要初始化这个obj就可以。
安卓下有GreenDao,更方便写模型。

iOS之Widget开发总结

上月底,给crm项目加了个widget,在Today这里展示。

首先打开project,创建target,选择Today Extension,选择关联的project和target。扩展只能依托一个某个target才能存在。

创建好这一切后,我是比较偷懒,直接将需要的代码关联这个target,并没有使用framework去弄。

关联完毕后,有些地方代码需要做target判断,注意一点UIApplication无法在这里面使用需要用self.extensionContext。他只有上下文,比如通过extension打开主应用.

[self.extensionContext openURL:[NSURL URLWithString:@"iOSWidgetApp://123"] completionHandler:^(BOOL success) {
    NSLog(@"open url result:%d",success);
}];

extension是在你手指下拉出这个面板才回刷新数据

- (void)widgetPerformUpdateWithCompletionHandler:(void (^)(NCUpdateResult))completionHandler {
    completionHandler(NCUpdateResultNewData);
    [self loadData];
    }

还有一个问题就是共享数据,选择主target,标签Capabilities,找到App Groups,打开开关,添加一个group,举例叫group.icrm,保存。
在extension的这个位置打开开关,勾选group.icrm
配置完成后再程序中使用路径的位置,对于crm项目是db索索在位的初始化修改一下:

     NSURL *storeURL = [[NSFileManager defaultManager] containerURLForSecurityApplicationGroupIdentifier:@"group.icrm"];
storeURL = [storeURL URLByAppendingPathComponent:@"icrm.db"];

这样在exension中也能访问到db。因为Today Extension是继承自UIViewController,UIKit的用法都差不多。

几个配置地方,我习惯代码开发,所以需要修改info.plist

<dict>
	<key>NSExtensionPointIdentifier</key>
	<string>com.apple.widget-extension</string>
	<key>NSExtensionPrincipalClass</key>
	<string>TodayViewController</string>
</dict>

target general的Main interface也置为空。

UITableView的size问题,因为todo都是列表,动态高度使用下方这种方式,在loadData时设置widget在面板上的size大小。

    self.preferredContentSize = CGSizeMake(self.view.bounds.size.width, _dataArray.count * 50.0f);

URLScheme,在主target中设置好了才能在extension中使用openURL打开主App。

<key>CFBundleURLTypes</key>
<array>
	<dict>
		<key>CFBundleTypeRole</key>
		<string>Todo</string>
		<key>CFBundleURLName</key>
		<string>com.highwe.icrm</string>
		<key>CFBundleURLSchemes</key>
		<array>
			<string>iOSWidgetApp</string>
		</array>
	</dict>
</array>

Grand Central Dispatch(GCD)介绍

基础知识,block是很多高级特性的基础,包括GCD,ReactiveCocoa。

GCD操作方式有两种:
同步dispatch_sync(<#dispatch_queue_t queue#>, <#^(void)block#>)
异步dispatch_async(<#dispatch_queue_t queue#>, <#^(void)block#>).

同步则是等block代码执行完毕才回跳转到方法下一步,异步则是直接顺序执行完当前方法,线程处理block。

串行和并行队列介绍:

  1. serial queues(串行队列)又称私有调度队列(private),一般用再对特定资源的同步访问上。我们可以根据需要创建任意数量的串行队列,每一个串行队列之间是并发的。

  2. 并行队列,又称global dispatch queue。并行队列虽然可以并发的执行多个任务,但是任务开始执行的顺序和其加入队列的顺序相同。我们自己不能去创建并行调度队列。只有三个可用的global concurrent queues。

  3. main dispatch queue 是一个全局可用的串行队列,其在行用程序的主线程上执行任务。此队列的任务和应用程序的主循环(run loop)要执行的事件源交替执行。因为其运行在应用程序的主线程,main queue经常用来作为应用程序的一个同步点。

放上一段经典代码:

dispatch_async(dispatch_get_global_queue(0, 0), ^{
    // 处理耗时操作的代码块...
    
    //通知主线程刷新
    dispatch_async(dispatch_get_main_queue(), ^{
        //回调或者说是通知主线程刷新,
    });
    
});

这是GCD最常用的一种手法,先开启一个异步操作,指定queue,block实现模块。

主队列dispatch_queue_t mainDispatchQueue = dispatch_get_main_queue();

全局dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

串行dispatch_queue_t queue = dispatch_queue_create("navy", NULL);

并行dispatch_queue_t queue = dispatch_queue_create("navy", DISPATCH_QUEUE_CONCURRENT);

queue有三类:

  • Main Dispatch Queue: 主线程队列(Serial Queue), 在程序的RunLoop中执行。因为main queue是与主线程相关的,所以这是一个串行队列。

  • Global Dispatch Queue: 系统中所有应用程序共用的全局队列(Concurrent Queue), 全局队列是并发队列,一般不必创建新的Dispatch Queue,使用此Queue就可以了。Global Diapacth Queue有4个优先级:High Priority(高)、Default Priority(默认)、Low Priority(低)、Background Priority(后台)

  • 用户队列:是用函数 dispatch_queue_create创建的队列. 这些队列可以是串行或者并行的,null默认为串行。

处理完费时的工作需要刷新UI就得dispatch_get_main_queue()在主线程里刷新界面。

dispatch_semaphore_t
信号量的作用,就是下一步代码的执行要等待上一步的计算结果。有些队列既希望能异步执行,又希望能获取到block的结果再执行下一步操作。

dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
dispatch_async(dispatch_get_global_queue(0, 0), ^{
    sleep(5);
    WWLog(@"block");
    dispatch_semaphore_signal(semaphore);
});
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
WWLog(@"finish");

这段代码先打印block,再打印finish。信号量要等待block执行结束。

这些基础清楚后,很容易写一个异步模块,抛弃NSThread,NSLock这类东西。

dispatch_once
字面很容易理解,只执行一次,常用于单例模式的初始化,如下:

+ (instancetype)sharedInstance
{
    static RFTagDao *instance;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        instance = [[RFTagDao alloc] init];
    });
    return instance;
}    

dispatch_after()
延迟加载

double delayInSeconds = 2.0;
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC));
dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
    NSLog(@"Waitted at least 2 seconds");
});

dispatch group
可以等待这个组里面的queue执行完再进行下一步。

dispatch_queue_t queue1 = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0);
dispatch_queue_t queue2 = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0);
dispatch_group_dispatchGroup = dispatch_group_create();
for (id obj in arr1) {
	dispatch_group_async(dispatchGroup, queue1, ^{
    	[obj performTask];
    });
}
for (id obj in arr2) {
	dispatch_group_async(dispatchGroup, queue2, ^{
    	[obj performTask];
    });
}

//如果需要阻塞线程可以使用wait
dispatch_group_wait(dispatchGroup, DISPATCH_TIME_FOREVER);
//to do something.

//不阻塞使用notify
dispatch_queue_t notifyQueue = dispatch_get_main_queue();
dispatch_group_notify(dispatchGroup,notifyQueue,^{
	//to do something.
});

dispatch_apply
遍历执行block

group中的for循环遍历执行async block就可以修改为使用apply方式,如下:
dispatch_apply(arr1.count, queue1, ^(size_t){
		id obj = arr[i];
    	[obj performTask];
    });

不过dispatch_apply会持续阻塞,直到所有任务都执行完毕为止,如果把block指派给了current queue或者高于当前队列的某个串行队列,就会导致死锁。如果想后台执行,还是使用group安心的使用异步遍历吧。

dispatch_set_target_queue
dispatch_set_target_queue(<#dispatch_object_t object#>, <#dispatch_queue_t queue#>)
第一个参数是目标queue1,第二个是需要设置的queue2。
将queue1的的优先级设置为queue2的优先级。
这个方法不管queue1是串行还是并行队列。

dispatch_queue_set_specific
最后介绍dispatch_queue_set_specific这个东西,类似objc_setAssociatedObject objc_getAssociatedObject这套玩意,在fmdb源码中就用了这个,他所有的操作都是在一个队列里进行的。

static const void * const kDispatchQueueSpecificKey = &kDispatchQueueSpecificKey;

_queue = dispatch_queue_create([[NSString stringWithFormat:@"fmdb.%@", self] UTF8String], NULL);

dispatch_queue_set_specific(_queue, kDispatchQueueSpecificKey, (__bridge void *)self, NULL);

FMDatabaseQueue *currentSyncQueue = (__bridge id)dispatch_get_specific(kDispatchQueueSpecificKey);

assert(currentSyncQueue != self && "inDatabase: was called reentrantly on the same queue, which would lead to a deadlock");

这么做的主要目的就是避免死锁,设置源码如下:

dispatch_queue_set_specific(dispatch_queue_t queue, const void *key,
void *context, dispatch_function_t destructor);
objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy)

specific设置key value都是用的void指针,也就意味着能够存储任意数据,甚至当前对象,好处是gcd属于底层api,和corefoundation无缝桥接。

(lldb) thread list 命令将会在控制台打印出所有队列的名字

dispatch_benchmark
用来测试性能,能测试代码运行了多少ns,不过这是私有api,不能用在发布版本。

size_t const objectCount = 1000;
uint64_t n = dispatch_benchmark(10000, ^{
@autoreleasepool {
    id obj = @42;
    NSMutableArray *array = [NSMutableArray array];
    for (size_t i = 0; i < objectCount; ++i) {
        [array addObject:obj];
    }
}
});
NSLog(@"-[NSMutableArray addObject:] : %llu ns", n);

介绍这些应该就足够使用了,还有很多文件相关的GCD知识,只是我并不常用,就不介绍了。这有一个详细的参考链接
iOS相关技术真正发生改变还是在于语言的改进上,在没有block之前,大部分都是delegate调用,或者脏的不行的KVO上。

有了block,代码能够更清晰直观,一个回调就不用写长长的delegate;因为block,然后CGD的出现,让线程队列也变的更容易直观;因为GCD,然后ReactiveCocoa能写更容易的响应式代码,整个的链式语法,核心signal,统一了消息传递。

动态语言早有的特性,iOS其实出的还是慢了点,现在swift也是汲取了动态语言的很多特性,可惜我还没怎么学习,不过毕竟用过python,js,学起来应该还是很容易。

不过最后还是吐槽下,并不是争语言的好坏,而是我觉得太久用动态语言或者各种语法糖结构,其实很容易养成不好的习惯,很容易让代码变得不可读,如果能做到代码即注释,便是极好的。

iOS依赖注入

依赖注入(dependency injection)参考资料,objc.io上一个关于DI的详细介绍依赖注入
关于iOS runtime swizzle的介绍,传说中的移形换位大法swizzle
GitHub上一个开源的iOS DI实现:objection
GitHub上一个开源的swizzle封装:jrswizzle

依赖注入的几种形式:

  1. 构造器注入
  2. 属性注入
  3. 方法注入
  4. 环境上下文
  5. 抽取和重写调用

一句话介绍:给对象传入实例变量,用实例变量储存依赖对象,并用这个变量来进行各种操作。

swizzle简介

Method swizzling指的是改变一个已存在的选择器对应的实现的过程,它依赖于Objectvie-C中方法的调用能够在运行时进改变——通过改变类的调度表(dispatch table)中选择器到最终函数间的映射关系。	    

一句话介绍:selector和imp是一一对应,但是使用这技术可以在运行时修改selector对应的imp,imp类似指针,指向方法具体实现,swizle的作用就是修改指针具体指向。