月度归档:2015年05月

如何管理Xcode模板

很久大概xcode4的时候安装了一个cocos2dx的模板,现在xcode6了我一直没管,今天想删除,找了半天,原来要先显示隐藏文件才行。

最终我在/Users/navy/Library/Developer/Xcode/Templates这个位置找到cocos2dx模板并删除。

更老的版本/Users/navy/Library/Application Support/Developer/Shared/Xcode在这个位置。

然后新的xcode如何管理Template了,进入
/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/Library/Xcode/Templates
这个位置,将模板复制到相应的位置,对应File Templates Project Templates位置即可。

最近计划

打算每天学习一点objc.io或者nshipster,都有对应的中文网站:
objcio中国
NSHipster中国

还有React Native ,虽说刚出来的时候给公司那边培训了下,自己也简单入了门,不过facebook历时几年的产品,还是得好好学习下。

阅读计划

构建之法,人件,MacTalk人生元编程,重构和remote,rework,设计师要懂心理学。

Blog计划

写点基础为主:
内存相关:主要涉及retain cycle和CoreFoundation相关,还有dealloc的正确用法,顺带block介绍下。
autolayout相关:主要讲基本用法和三方库Masonry,PureLayout。

项目计划

最近在重构ContactsTouch,主要是业务逻辑上,等重构完,再写一篇文章详细介绍下,个人觉得技术上并没有太多复杂的东西,很基础的一个架构思路。从技术上和产品上说下为何这么做。

技术计划

衣橱项目和instagram下载项目该早点收尾了。
公司这边安监项目还是学习下,vuejs和tornado这两块,之前我没用过。个人觉得既然团队合作就项目应该松耦合,尽量根据功能分多文件,多class来防止git commit的冲突。有些功能模块分的还是挺好的,但是主文件上千行有点难接受,随着另外几个角色的引入,这个py文件怕是会突破两千行。对于这个项目的curd封装觉得有点怪异,不过简化了项目开发。毕竟政府项目,快捷省时才是王道。

如何写线程安全的方法

在没有GCD之前,处理同步可以用@synchronizedNSLock这种方式来加锁。但是效率上就不尽如人意。
如何用GCD写好用的线程安全方法?

_queue=dispatch_queue_create("com.navy.queue", NULL);
- (NSString)someString{
	__block NSString *str;
    dispatch_sync(_queue, ^{
    	str = _someString;
    });
    return str;
}

- (void)setSomeString:(NSString *)someString {
	dispatch_sync(_queue, ^{
		_someString = someString;
    });
};

这种方式使用串行队列同步方式来实现线程安全的属性访问,同一队列,设置和获取都是串行,不会有问题。
不过可以再优化下,设置方法可以修改为异步dispatch_async,不过执行异步会copy lock,如果简单的方法测试效果上可能比同步更慢,复杂的方法的话可以修改为这种方案。

还有更快的方式,多个获取之间可以并行,但是获取和设置方法不能并行,可以使用并行队列。

_queue=dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
- (NSString)someString{
	__block NSString *str;
    dispatch_sync(_queue, ^{
    	str = _someString;
    });
    return str;
}

- (void)setSomeString:(NSString *)someString {
	dispatch_barrier_async(_queue, ^{
		_someString = someString;
    });
};

dispatch_barrier_async表示这个block必须单独执行,不能和其他block并行,只对并发队列有意义。
设置方法必须用同步,因为返回值必须等block里面赋值成功才行。

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,学起来应该还是很容易。

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