iOS

UIView 正在进行动画时,默认禁止用户交互

今天遇到一个问题,使用

[UIView animateWithDuration:3 animations:^{
    self.contentView.backgroundColor = [UIColor clearColor];
}];

方法进行动态改变控件背景色,但发现在动画进行时,控件死活不响应用户手势,经过查看API,发现有个参数:

UIViewAnimationOptionAllowUserInteraction      = 1 <<  1, // turn on user interaction while animating

猜想该动画默认是不允许用户进行交互的

通过在该动画中添加该参数:UIViewAnimationOptionAllowUserInteraction

[UIView animateWithDuration:3 delay:0 options:UIViewAnimationOptionAllowUserInteraction animations:^{
    self.contentView.backgroundColor = [UIColor clearColor];
} completion:nil];

使问题得以解决。

为什么 Github 没有记录你的 Contributions

最近在给同学看我的github时,突然发现我的Contributions Graph上一篇空白居然只有两三个小绿块,因为自己平时在公司工作的记录会显示在自己的Contribution Graph上,所以一直没发现个人repo的commit记录都没有被记录,于是外事不决问Google,发现原来是因为git初始化设定时没有设定正确的用户名和邮箱(主要是邮箱),具体可以参考Github官方写的help文档

为什么你的 Contributions 没有在你的 Profile 里显示?

那么问题来了,什么是Contributions呢,Github官网上有这么一句话:

Your profile contributions graph is a record of contributions you've made to GitHub repositories. Contributions are only counted if they meet certain criteria. In some cases, we may need to rebuild your graph in order for contributions to appear.

你的个人简介中的贡献图记录的是你给Github上的仓库的贡献。它仅仅只记录某些符合标准的commits。在某些情况下,我们需要重新建立你的贡献图以便让所有的贡献都显示出来。

什么样的贡献才会被Github统计?

英语好的同学请移步 Why are my contributions not showing up on my profile?

Issues 和 pull requests
  • 这个操作是在一年之内
  • 这个操作是针对一个独立的仓库,不能是fork
Commits

当你的commits满足以下条件时,它才会被展示出来:

  • 一年之内提交的commits
  • commits使用的email地址是与你的Github账号相关联的
  • 这些commits是在一个独立的仓库而不是fork仓库
  • 这些commits是在:

    • 在默认分支上(通常是master)
    • 在gh-pages分支(包含 Project Pages sites 的仓库)

此外,至少满足下面条件中的一个(主要针对你Commit的仓库不是你创建的):

  • 你是这个仓库的协作者,或者是这个版本库的拥有组织中的一员
  • 你fork过这个仓库
  • 你对这个仓库发起过pull request或者issue
  • 你对这个仓库标记了Star
注意:私有库的贡献仅仅对私有库成员显示

Contributions未被Github计入的几个常见原因

  • 进行Commits的用户没有被关联到你的Github帐号中。
  • 不是在这个版本库的默认分支进行的Commit。
  • 这个仓库是一个Fork仓库,而不是独立仓库。

如何排查

你可以在你的本地repo里用git log命令查看commit记录上的个人邮箱是否正确,像我就是因为之前切换到Mac平台开发之后用户名没有配置,所以我之后的commit记录上的邮箱一直是Leo@Leo-MacBook-Pro.local,所以Github就会认为这些commits都不是你提交的!

补救措施

然而这也并不是没有补救办法的,Github官网上就有给出详细的补救过程,英语好的同学请自行移步 Changing author info,下面是我翻译自Github Help的简要步骤:

变更作者信息

为改变已经存在的 commits 的用户名和/或邮箱地址,你必须重写你 Git repo 的整个历史。

警告: 这种行为对你的 repo 的历史具有破坏性。如果你的 repo 是与他人协同工作的,重写已发布的历史是一种不好的习惯。仅限紧急情况执行该操作。

使用脚本改变你 repo 的 Git 历史我们写了一段能把 commit 作者旧的邮箱地址修改为正确用户名和邮箱的脚本。

使用脚本来改变某个repo的Git历史

我们已经创建了一个脚本,使用正确的姓名和电子邮件地址提交后,你以前提交的所有的commits中的作者信息及提交者字段中的旧的用户名和邮箱地址都将被更正

注意: 执行这段脚本会重写 repo 所有协作者的历史。完成以下操作后,任何 fork 或 clone 的人必须获取重写后的历史并把所有本地修改 rebase 入重写后的历史中。

在执行这段脚本前,你需要准备的信息:

  • Mac、Linux下打开Terminal,Windows下打开命令提示符(command prompt)
  • 给你的repo创建一个全新的clone
git clone --bare https://github.com/user/repo.git
cd repo.git
  • 复制粘贴脚本,并根据你的信息修改以下变量:旧的Email地址正确的用户名正确的邮件地址
#!/bin/sh
git filter-branch --env-filter '
OLD_EMAIL="旧的Email地址"
CORRECT_NAME="正确的用户名"
CORRECT_EMAIL="正确的邮件地址"
if [ "$GIT_COMMITTER_EMAIL" = "$OLD_EMAIL" ]
then
    export GIT_COMMITTER_NAME="$CORRECT_NAME"
    export GIT_COMMITTER_EMAIL="$CORRECT_EMAIL"
fi
if [ "$GIT_AUTHOR_EMAIL" = "$OLD_EMAIL" ]
then
    export GIT_AUTHOR_NAME="$CORRECT_NAME"
    export GIT_AUTHOR_EMAIL="$CORRECT_EMAIL"
fi
' --tag-name-filter cat -- --branches --tags
  • Enter键 执行脚本。
  • git log命令看看新 Git 历史有没有错误
  • 把正确历史 push 到 Github
git push --force --tags origin 'refs/heads/*'
  • 删掉刚刚临时创建的 clone
cd ..
rm -rf repo.git

如何正确设置你的 git 个人信息


接下来全局设置好你的正确信息,以后就放心的用Github进行版本管理吧 ^_^

git config --global user.email "你的邮件地址"
git config --global user.name "你的Github用户名"

更多配置可以参考Pro Git 8.1自定义 Git - 配置 Git

原文链接 : Github为什么没有记录你的Contributions

Swift 重写父类的 init 方法

重写是相同方法的不同实现,参数不同方法就不同了,楼主是想重载,如下两个例子可以清晰表现用法和区别:

例如UIView重写父类的init(frame: CGRect)方法:

override init(frame: CGRect) {
    super.init(frame: frame)
    //do something what you want
}

重写的话Swift规定不可以缺少这个request init方法:(编译器会自动提示)

required init?(coder aDecoder: NSCoder) {
    super.init(coder: aDecoder)
    fatalError("init(coder:) has not been implemented")
}

重载父类的init(frame: GCRect),增加一个新参数:

init(frame: CGRect, type: String) {
    super.init(frame: frame)
    //do something what you want
    print(type)
}

参考 :Swift Initialization

CocoaPods 版本升级

有时我们使用命令执行pod安装时,会提示:

[!] The version of CocoaPods used to generate the lockfile (1.2.0) is higher than the version of the current executable (1.0.1). Incompatibility issues may arise.

这时,我们确实是需要升级我们的pods了,不然我们无法继续进行后续的工作。

使用命令行来升级我们的CocoaPods
$ sudo gem update --system // 先更新gem,国内需要切换源
$ gem sources --remove https://rubygems.org/
$ gem sources --add https://gems.ruby-china.com/
$ gem sources -l
\*\*\* CURRENT SOURCES \*\*\*
https://gems.ruby-china.com/
$ sudo gem install cocoapods // 安装cocoapods
$ pod setup
我们使用sudo gem install cocoapods命令来进行安装CocoaPods时,在OS X 10.11 以后的系统会出现如下错误:
Updating rubygems-update
ERROR:  While executing gem ... (Errno::EPERM)
    Operation not permitted - /usr/bin/update_rubygems

参考下面方法解决

http://stackoverflow.com/questions/30812777/cannot-install-cocoa-pods-after-uninstalling-results-in-error/30851030#30851030/

sudo gem install -n /usr/local/bin cocoapods

Done !

巧谈GCD

谈到iOS多线程,一般都会谈到四种方式:pthreadNSThreadGCDNSOperation。其中,苹果推荐也是我们最经常使用的无疑是GCD。对于身为开发者的我们来说,并发一直都很棘手,如果对GCD的理解不够透彻,那么iOS开发的历程绝对不会顺利。这里,我会从几个角度浅谈我对GCD的理解。

一、多线程背景

Although threads have been around for many years and continue to have their uses, they do not solve the general problem of executing multiple tasks in a scalable way. With threads, the burden of creating a scalable solution rests squarely on the shoulders of you, the developer. You have to decide how many threads to create and adjust that number dynamically as system conditions change. Another problem is that your application assumes most of the costs associated with creating and maintaining any threads it uses.

上述大致说出了直接操纵线程实现多线程的弊端:

  • 开发人员必须根据系统的变化动态调整线程的数量和状态,即对开发者的负担重。
  • 应用程序会在创建和维护线程上消耗很多成本,即效率低。

相对的,GCD是一套低层级的C API,通过 GCD,开发者只需要向队列中添加一段代码块(block或C函数指针),而不需要直接和线程打交道。GCD在后端管理着一个线程池,它不仅决定着你的代码块将在哪个线程被执行,还根据可用的系统资源对这些线程进行管理。GCD的工作方式,使其拥有很多优点(快、稳、准):

  • 快,更快的内存效率,因为线程栈不暂存于应用内存。
  • 稳,提供了自动的和全面的线程池管理机制,稳定而便捷。
  • 准,提供了直接并且简单的调用接口,使用方便,准确。

二、队列和任务

初学GCD的时候,肯定会纠结一些看似很关键但却毫无意义的问题。比如:GCD和线程到底什么关系;异步任务到底在哪个线程工作;队列到底是个什么东西;mian queue和main thread到底搞什么名堂等等。现在,这些我们直接略过(最后拾遗中会谈一下),苹果既然推荐使用GCD,那么为什么还要纠结于线程呢?需要关注的只有两个概念:队列任务
1. 队列

调度队列是一个对象,它会以first-in、first-out的方式管理您提交的任务。GCD有三种队列类型:

  • 串行队列,串行队列将任务以先进先出(FIFO)的顺序来执行,所以串行队列经常用来做访问某些特定资源的同步处理。你可以也根据需要创建多个队列,而这些队列相对其他队列都是并发执行的。换句话说,如果你创建了4个串行队列,每一个队列在同一时间都只执行一个任务,对这四个任务来说,他们是相互独立且并发执行的。如果需要创建串行队列,一般用dispatch_queue_create这个方法来实现,并指定队列类型DISPATCH_QUEUE_SERIAL。
  • 并行队列,并发队列虽然是能同时执行多个任务,但这些任务仍然是按照先到先执行(FIFO)的顺序来执行的。并发队列会基于系统负载来合适地选择并发执行这些任务。并发队列一般指的就是全局队列(Global queue),进程中存在四个全局队列:高、中(默认)、低、后台四个优先级队列,可以调用dispatch_get_global_queue函数传入优先级来访问队列。当然我们也可以用dispatch_queue_create,并指定队列类型DISPATCH_QUEUE_CONCURRENT,来自己创建一个并发队列。
  • 主队列,与主线程功能相同。实际上,提交至main queue的任务会在主线程中执行。main queue可以调用dispatch_get_main_queue()来获得。因为main queue是与主线程相关的,所以这是一个串行队列。和其它串行队列一样,这个队列中的任务一次只能执行一个。它能保证所有的任务都在主线程执行,而主线程是唯一可用于更新 UI 的线程。
额外说一句,上面也说过,队列间的执行是并行的,但是也存在一些限制。比如,并行执行的队列数量受到内核数的限制,无法真正做到大量队列并行执行;比如,对于并行队列中的全局队列而言,其存在优先级关系,执行的时候也会遵循其优先顺序,而不是并行。
2. 任务
linux内核中的任务的定义是描述进程的一种结构体,而GCD中的任务只是一个代码块,它可以指一个block或者函数指针。根据这个代码块添加进入队列的方式,将任务分为同步任务和异步任务:
  • 同步任务,使用dispatch_sync将任务加入队列。将同步任务加入串行队列,会顺序执行,一般不这样做并且在一个任务未结束时调起其它同步任务会死锁。将同步任务加入并行队列,会顺序执行,但是也没什么意义。
  • 异步任务,使用dispatch_async将任务加入队列。将异步任务加入串行队列,会顺序执行,并且不会出现死锁问题。将异步任务加入并行队列,会并行执行多个任务,这也是我们最常用的一种方式。
3. 简单应用
// 队列的创建,queue1:中(默认)优先级的全局并行队列、queue2:主队列、queue3:未指定type则为串行队列、queue4:指定串行队列、queue5:指定并行队列
dispatch_queue_t queue1 = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_queue_t queue2 = dispatch_get_main_queue();
dispatch_queue_t queue3 = dispatch_queue_create("queue3", NULL);
dispatch_queue_t queue4 = dispatch_queue_create("queue4", DISPATCH_QUEUE_SERIAL);
dispatch_queue_t queue5 = dispatch_queue_create("queue5", DISPATCH_QUEUE_CONCURRENT);
 
// 队列中添加异步任务
dispatch_async(queue1, ^{
// 任务
...
});
 
// 队列中添加同步任务
dispatch_sync(queue1, ^{
// 任务
...
});

三、GCD常见用法和应用场景

非常喜欢一句话:Talk is cheap, show me the code.接下来对GCD的使用,我会通过代码展示。

1. dispatch_async
一般用法
dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(globalQueue, ^{
    // 一个异步的任务,例如网络请求,耗时的文件操作等等
    ...
    dispatch_async(dispatch_get_main_queue(), ^{
        // UI刷新
        ...
    });
});
应用场景

这种用法非常常见,比如开启一个异步的网络请求,待数据返回后返回主队列刷新UI;又比如请求图片,待图片返回刷新UI等等。

2. dispatch_after
一般用法
dispatch_queue_t queue= dispatch_get_main_queue();
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5.0 * NSEC_PER_SEC)), queue, ^{
    // 在queue里面延迟执行的一段代码
    ...
});
应用场景

这为我们提供了一个简单的延迟执行的方式,比如在view加载结束延迟执行一个动画等等。

3. dispatch_once
一般用法
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
    // 只执行一次的任务
    ...
});
应用场景

可以使用其创建一个单例,也可以做一些其他只执行一次的代码,比如做一个只能点一次的button(好像没啥用)。

4. dispatch_group
一般用法
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_group_t group = dispatch_group_create();
 
dispatch_group_async(group, queue, ^{
    // 异步任务1
});
 
dispatch_group_async(group, queue, ^{
    // 异步任务2
});
 
// 等待group中多个异步任务执行完毕,做一些事情,介绍两种方式
 
// 方式1(不好,会卡住当前线程)
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
...
 
// 方式2(比较好)
dispatch_group_notify(group, mainQueue, ^{
    // 任务完成后,在主队列中做一些操作
    ...
});
应用场景

上述的一种方式,可以适用于自己维护的一些异步任务的同步问题;但是对于已经封装好的一些库,比如AFNetworking等,我们不获取其异步任务的队列,这里可以通过一种计数的方式控制任务间同步,下面为解决单界面多接口的一种方式。

// 两个请求和参数为我项目里面的不用在意。
 
// 计数+1
dispatch_group_enter(group);
[JDApiService getActivityDetailWithActivityId:self.activityId Location:stockAddressId SuccessBlock:^(NSDictionary *userInfo) {
    // 数据返回后一些处理
    ...
 
    // 计数-1
    dispatch_group_leave(group);
} FailureBlock:^(NSError *error) {
    // 数据返回后一些处理
    ...
 
    // 计数-1
    dispatch_group_leave(group);
}];
 
// 计数+1
dispatch_group_enter(group);
[JDApiService getAllCommentWithActivityId:self.activityId PageSize:3 PageNum:self.commentCurrentPage SuccessBlock:^(NSDictionary *userInfo) {
    // 数据返回后一些处理
    ...
 
    // 计数-1
    dispatch_group_leave(group);
} FailureBlock:^(NSError *error) {
    // 数据返回后一些处理
    ...
 
    // 计数-1
    dispatch_group_leave(group);
}];
 
// 其实用计数的说法可能不太对,但是就这么理解吧。会在计数为0的时候执行dispatch_group_notify的任务。
dispatch_group_notify(group, mainQueue, ^{
    // 一般为回主队列刷新UI
    ...
});

5. dispatch_barrier_async

一般用法

// dispatch_barrier_async的作用可以用一个词概括--承上启下,它保证此前的任务都先于自己执行,此后的任务也迟于自己执行。本例中,任务4会在任务1、2、3都执行完之后执行,而任务5、6会等待任务4执行完后执行。
 
dispatch_queue_t queue = dispatch_queue_create("queue", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
    // 任务1
    ...
});
dispatch_async(queue, ^{
    // 任务2
    ...
});
dispatch_async(queue, ^{
    // 任务3
    ...
});
dispatch_barrier_async(queue, ^{
    // 任务4
    ...
});
dispatch_async(queue, ^{
    // 任务5
    ...
});
dispatch_async(queue, ^{
    // 任务6
    ...
});
应用场景

和dispatch_group类似,dispatch_barrier也是异步任务间的一种同步方式,可以在比如文件的读写操作时使用,保证读操作的准确性。另外,有一点需要注意,dispatch_barrier_sync和dispatch_barrier_async只在自己创建的并发队列上有效,在全局(Global)并发队列、串行队列上,效果跟dispatch_(a)sync效果一样。

6. dispatch_apply
一般用法
// for循环做一些事情,输出0123456789
for (int i = 0; i < 10; i ++) {
    NSLog(@"%d", i);
}
 
// dispatch_apply替换(当且仅当处理顺序对处理结果无影响环境),输出顺序不定,比如1098673452
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
/*! dispatch_apply函数说明
*
*  @brief  dispatch_apply函数是dispatch_sync函数和Dispatch Group的关联API
*         该函数按指定的次数将指定的Block追加到指定的Dispatch Queue中,并等到全部的处理执行结束
*
*  @param 10    指定重复次数  指定10次
*  @param queue 追加对象的Dispatch Queue
*  @param index 带有参数的Block, index的作用是为了按执行的顺序区分各个Block
*
*/
dispatch_apply(10, queue, ^(size_t index) {
    NSLog(@"%zu", index);
});
应用场景

那么,dispatch_apply有什么用呢,因为dispatch_apply并行的运行机制,效率一般快于for循环的类串行机制(在for一次循环中的处理任务很多时差距比较大)。比如这可以用来拉取网络数据后提前算出各个控件的大小,防止绘制时计算,提高表单滑动流畅性,如果用for循环,耗时较多,并且每个表单的数据没有依赖关系,所以用dispatch_apply比较好。

7. dispatch_suspend和dispatch_resume
一般用法
dispatch_queue_t queue = dispatch_get_main_queue();
dispatch_suspend(queue); //暂停队列queue
dispatch_resume(queue);  //恢复队列queue
应用场景

这种用法我还没有尝试过,不过其中有个需要注意的点。这两个函数不会影响到队列中已经执行的任务,队列暂停后,已经添加到队列中但还没有执行的任务不会执行,直到队列被恢复。

8. dispatch_semaphore_signal
一般用法
// dispatch_semaphore_signal有两类用法:a、解决同步问题;b、解决有限资源访问(资源为1,即互斥)问题。
// dispatch_semaphore_wait,若semaphore计数为0则等待,大于0则使其减1。
// dispatch_semaphore_signal使semaphore计数加1。
 
// a、同步问题:输出肯定为1、2、3。
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_semaphore_t semaphore1 = dispatch_semaphore_create(1);
dispatch_semaphore_t semaphore2 = dispatch_semaphore_create(0);
dispatch_semaphore_t semaphore3 = dispatch_semaphore_create(0);
 
dispatch_async(queue, ^{
    // 任务1
    dispatch_semaphore_wait(semaphore1, DISPATCH_TIME_FOREVER);
    NSLog(@"1 ");
    dispatch_semaphore_signal(semaphore2);
    dispatch_semaphore_signal(semaphore1);
});
 
dispatch_async(queue, ^{
    // 任务2
    dispatch_semaphore_wait(semaphore2, DISPATCH_TIME_FOREVER);
    NSLog(@"2 ");
    dispatch_semaphore_signal(semaphore3);
    dispatch_semaphore_signal(semaphore2);
});
 
dispatch_async(queue, ^{
    // 任务3
    dispatch_semaphore_wait(semaphore3, DISPATCH_TIME_FOREVER);
    NSLog(@"3 ");
    dispatch_semaphore_signal(semaphore3);
});
 
// b、有限资源访问问题:for循环看似能创建100个异步任务,实质由于信号限制,最多创建10个异步任务。
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_semaphore_t semaphore = dispatch_semaphore_create(10);
for (int i = 0; i < 100; i ++) {
    dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
    dispatch_async(queue, ^{
    // 任务
    ...
    dispatch_semaphore_signal(semaphore);
    });
}
应用场景

其实关于dispatch_semaphore_t,并没有看到太多应用和资料解释,我只能参照自己对linux信号量的理解写了两个用法,经测试确实相似。这里,就不对一些死锁问题进行讨论了。

9. dispatch_set_context、dispatch_get_context和dispatch_set_finalizer_f
一般用法
// dispatch_set_context、dispatch_get_context是为了向队列中传递上下文context服务的。
// dispatch_set_finalizer_f相当于dispatch_object_t的析构函数。
// 因为context的数据不是foundation对象,所以arc不会自动回收,一般在dispatch_set_finalizer_f中手动回收,所以一般讲上述三个方法绑定使用。
 
- (void)test
{
    // 几种创建context的方式
    // a、用C语言的malloc创建context数据。
    // b、用C++的new创建类对象。
    // c、用Objective-C的对象,但是要用__bridge等关键字转为Core Foundation对象。
 
    dispatch_queue_t queue = dispatch_queue_create("queue", DISPATCH_QUEUE_CONCURRENT);
    if (queue) {
        // "123"即为传入的context
        dispatch_set_context(queue, "123");
        dispatch_set_finalizer_f(queue, &xigou);
    }
    dispatch_async(queue, ^{
        char *string = dispatch_get_context(queue);
        NSLog(@"%s", string);
    });
}
 
// 该函数会在dispatch_object_t销毁时调用。
void xigou(void *context)
{
    // 释放context的内存(对应上述abc)
 
    // a、CFRelease(context);
    // b、free(context);
    // c、delete context;
}
应用场景

dispatch_set_context可以为队列添加上下文数据,但是因为GCD是C语言接口形式的,所以其context参数类型是“void *”。需使用上述abc三种方式创建context,并且一般结合dispatch_set_finalizer_f使用,回收context内存。

四、内存和安全

稍微提一下吧,因为部分人纠结于dispatch的内存问题。

内存
  • MRC:用dispatch_retain和dispatch_release管理dispatch_object_t内存。
  • ARC:ARC在编译时刻自动管理dispatch_object_t内存,使用retain和release会报错。
安全

dispatch_queue是线程安全的,你可以随意往里面添加任务。

五、拾遗

这里主要提一下GCD的一些坑和线程的一些问题。

1. 死锁
dispatch_sync
// 假设这段代码执行于主队列
dispatch_queue_t serialQueue = dispatch_queue_create("serialQueue", DISPATCH_QUEUE_SERIAL);
dispatch_queue_t mainQueue = dispatch_get_main_queue();
 
// 在主队列添加同步任务
dispatch_sync(mainQueue, ^{
    // 任务
    ...
});
 
// 在串行队列添加同步任务 
dispatch_sync(serialQueue, ^{
    // 任务
    ...
    dispatch_sync(serialQueue, ^{
        // 任务
        ...
    });
};
dispatch_apply
// 因为dispatch_apply会卡住当前线程,内部的dispatch_apply会等待外部,外部的等待内部,所以死锁。
dispatch_queue_t queue = dispatch_queue_create("queue", DISPATCH_QUEUE_CONCURRENT);
dispatch_apply(10, queue, ^(size_t) {
    // 任务
    ...
    dispatch_apply(10, queue, ^(size_t) {
        // 任务
        ...
    });
});
dispatch_barrier

dispatch_barrier_sync在串行队列和全局并行队列里面和dispatch_sync同样的效果,所以需考虑同dispatch_sync一样的死锁问题。

2. dispatch_time_t
// dispatch_time_t一般在dispatch_after和dispatch_group_wait等方法里作为参数使用。这里最需要注意的是一些宏的含义。
// NSEC_PER_SEC,每秒有多少纳秒。
// USEC_PER_SEC,每秒有多少毫秒。
// NSEC_PER_USEC,每毫秒有多少纳秒。
// DISPATCH_TIME_NOW 从现在开始
// DISPATCH_TIME_FOREVE 永久
 
// time为1s的写法
dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, 1 * NSEC_PER_SEC);
3. GCD和线程的关系

如果你是新手,GCD和线程暂时木有关系。

如果你是高手,我们做朋友吧。

六、参考文献

1、https://developer.apple.com/library/mac/documentation/General/Conceptual/ConcurrencyProgrammingGuide/OperationQueues/OperationQueues.html#//apple_ref/doc/uid/TP40008091-CH102-SW2

2、https://developer.apple.com/library/ios/documentation/Performance/Reference/GCD\_libdispatch_Ref/

3、http://tutuge.me/2015/04/03/something-about-gcd/

4、http://www.jianshu.com/p/85b75c7a6286

5、http://www.jianshu.com/p/d56064507fb8

原文: 巧谈GCD

UITableViewCell 点击不高亮显示

在使用UITableView时,控件默认点击UITableViewCell会高亮显示,这样可能不是我们想要的结果。当然,我们可以使用代理:

- (BOOL)tableView:(UITableView *)tableView shouldHighlightRowAtIndexPath:(NSIndexPath *)indexPath {
    
}

来向系统说明是否需要高亮显示。但,这样会导致UITableViewCell不再响应用户事件。

于是,另一个解决方案出现:

cell.selectionStyle = UITableViewCellSelectionStyleNone;

我们可以在

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    
}

数据源中设置cell的选中样式。

HAVE FUN!

iOS 开发经验总结

  1. 对于网络请求,请求前需要判断网络状态,对于网络请求返回数据,需要判断是否为当前请求的数据
  2. 对于列表型数据,需要注意去重

iOS 开发代码规范

  • 关键代码必须写注释
  • 任意函数长度不得超过50行。(其实很容易就超过50行,这就要考虑代码抽取了。)
  • 任意行代码不能超过80字符。(其实也很容易超过80字符,可以考虑多行显示,比如有多个参数时,可以每个参数放一行。)可以在Xcode中设置超过80个字符的提醒,选中“Page guide at column”.设置完之后就会在代码80个字符处有一条竖线
  • 在每个方法的定义前留白一行,也就是在方法和方法之间留空一行
  • 功能相近的方法要放在一起,并推荐使用#pragma mark - * 来导航代码,切分代码块。这样可以方便函数的查找。并且可以使用快捷键control+6 来快速查找方法的位置
  • 二元运算符和参数之间要有一个空格,如赋值号=左右各留一个空格。
  • 一元运算符和参数之间不放置空格,比如!非运算符,&按位与,|按位或
  • 强制类型转换和参数之间不放置空格。
  • 长的变量值应该拆分为多行。尤其体现在使用数组或者字典。以下也分别是快速声明数组@[]和字典@{}的方法。
  • 尽量使用有意义的名字命名,拒绝使用i,j等无意义字符命名。类的命名首字母大写,其他变量的命名首字符小写,并使用驼峰式分割单词。
  • 尽量减少在代码中直接使用数字常量,而使用宏定义等方式。如:MAX_NUMBER_PHONE替代8等等。这样我们搜索也比较方便。
  • 尽量减少代码中的重复计算,比如代码中多处要使用屏幕宽度,然后计算:[[UIScreenmainScreen] bounds].size.width , 很多次,闲得很繁琐,代码也冗长,不如直接宏定义
  • 合理使用约定俗成的缩略词

    • alloc : 分配;
    • alt : 轮流,交替;
    • app : 应用程序;
    • calc : 计算;
    • dealloc : 销毁、析构;
    • func : 函数、方法;
    • horiz : 水平的;
    • info : 信息;
    • init : 初始化;
    • max : 最大的;
    • min : 最小的;
    • msg : 消息;
    • nib : Interface Builder;
    • rect : 矩形;
    • temp : 暂时的;
    • vert : 垂直的;
  • 宏定义全部字母大写,单词之间需要用下划线分割
  • 函数长度不要超过50行,小函数比大函数可读性更强。函数的参数不宜过多,零元函数最好,一元函数也不错,高于三元的函数需重构。
  • 合理范围内使用链式编程 NSString *myName = [[NSString alloc] init]; 但是嵌套不宜超过3层,超过3层需进行重构
  • 函数调用时所有参数在同一行。如果参数过多,则可以每行一个参数,每个参数以冒号对齐
  • 对传入参数的保护或者说是否为空的判断,尽量不要使用if(!obj),而使用NSAssert断言来处理。NSAssert是系统定义的宏
  • 方法参数名前一般使用”an”,”the”,”new”来进行修饰
  • if-else超过四层的时候,就要考虑重构,多层的if-else结构很难维护
  • 当需要一定条件才执行某项操作时,最左边的应该是最重要的代码,不要将最重要的代码内嵌到if中
  • 所有的逻辑块都使用{}花括号包围,就算只是一行代码
  • 明确指定构造函数,并有适当的注释
  • 不要在init方法中把变量或者说属性初始化为0或者nil,因为没有必要。
  • UIView的子类初始化的时候,不要进行任何的布局操作。布局操作应该在layoutSubviews里面做;需要重新布局的时候调用setNeedsLayout,而不要直接调用layoutSubviews。
  • 保持公共API简单,也就是保持.h文件简单。放在.h中声明的函数都是会被公开的,如果根本就没必要对其他类公开,就不要在.h中声明。OC中的方法都是公有方法,没有私有方法一说。
  • 一个文件只实现一个类。同一个文件中不要有多个类。
  • Protocol单独用一个文件来创建,尽量不要与相关类混在一个文件中。
  • 在类定义中使用到自己定义类的时候,尽量不要在头文件中引入自己定义类的头文件,使用@class替代。而在实现文件中引入头文件。
  • 布局时尽量使用相对布局,比如使用子View在父View中的相对位置。
    代码折叠,这个可能是关于开发效率的,我也写在编码规范中,因为这个很有用。Xcode7默认没有开启代码折叠,如果你的方法体行数很长,看起来会很不方便,此时你就可以把方法“收起来”,一个类中的结构就会很清晰。开启方法如下:Xcode菜单–>Preferences–>Text Editing–>勾选Code folding ribbon
  • 推荐方法的第一个花括号直接跟在方法体后,而不是另起一行,这样可以减少代码行
  • 推荐方法体中的第一行留空,最后一行不留空,这样一个方法就会比较清晰,但是如果该花括号里面又是一个if,for之类的带花括号的语句块,那么上述的第一行可以不留空。同样,如果花括号内第一行是注释的话,第一行也可以不留空。注释也起到了分隔代码的作用,看起来比较清晰。再者,如果花括号内只有一行代码,第一行可以不留空。
  • block中第一行也要留空,同方法体中的第一行留空,使代码清晰
    代表类方法和实例方法的”+”加号,”-“减号后需要一个空格。这是一个非常小的细节,系统默认的方法都是这样的,我们自己声明或者实现一个方法的时候也需要这样
  • 这一条有点像编程经验了,就是为解决某个问题估算时间。比如要开发某个功能、调试某个bug、给自己一个时间限制,如果在这期间不能解决问题,那么就去寻求帮助。这既是给自己一个压力,也为了不浪费时间。虽然,这一条其实很难做到,我往往由于不甘心而无限拖延时间去解决问题
  • 由于提到编程经验,就不得不提到版本控制。务必去学会SVN或者Git,就算你是独立开发,也要学会控制自己的代码,当然,你要经常备份你的代码

未完待续

微信支付集成遇到的坑

调起支付出现“支付验证签名失败”
  1. 服务端访问“ 统一下单”接口,收到返回值。
  2. APP在将上一步返回值传递微信时出现“支付验证签名失败”问题。

需要注意的是:

执行第1步的时候,收到返回值,需要将sign参数再次重新生成签名。返回到APP(相当于需要按相同的方式签名两次再发送微信支付请求)

支付完成无法调起onResp回调
我的开发环境:XCode 8 + iOS 9.3.5

使用微信支付,调起了微信支付,并正常返回后,在我的APP无法调起onResp回调。经过CoderLeon的提示,终于找到了方法,## 在IOS9.2和Xcode7.2之后,需要使用苹果的新方法 ##。看看微信的API也是醉了,下面介绍正确打开姿势:

- (BOOL)application:(UIApplication *)application handleOpenURL:(NSURL *)url {
    return  [WXApi handleOpenURL:url delegate:[WXApiManager shareInstance]];
}

- (BOOL)application:(UIApplication *)application openURL:(NSURL *)url sourceApplication:(NSString *)sourceApplication annotation:(id)annotation {
    return [WXApi handleOpenURL:url delegate:[WXApiManager shareInstance]];
}

- (BOOL)application:(UIApplication *)app openURL:(NSURL *)url options:(NSDictionary*)options {
    return [WXApi handleOpenURL:url delegate:[WXApiManager shareInstance]];
}

CocoaPods 安装使用总结

安装篇

安装ruby环境

因为众所周知的原因,ruby官方源在国内不好用,我们需要添加国内第三方源,以前都是添加淘宝源,但现在淘宝源已经停止更新了,建议使用ruby china 源。

$ gem sources --add https://gems.ruby-china.com/ --remove https://rubygems.org/

https://gems.ruby-china.com/ added to sources
https://rubygems.org/ removed from sources
查看当前的ruby源
$ gem sources -l
*** CURRENT SOURCES ***

https://gems.ruby-china.com
# 请确保只有 gems.ruby-china.com
安装CocoaPods
$ sudo gem install cocoapods
查看cocoapods是否支持某个类库
$ pod search 类库名,支持模糊查询(如:AFNetworking)
使用pod search 查询后,可以使用Q键退出
安装错误解决
  • 错误1:

    Error fetching http://ruby.taobao.org/:
    bad response Not Found 404 (http://ruby.taobao.org/specs.4.8.gz)

    ==解决方案==:把安装流程中

$gem sources -a http://ruby.taobao.org/

改为

$gem sources -a https://gems.ruby-china.com/

  • 错误2:

    ERROR:  While executing gem ... (Errno::EPERM)
    Operation not permitted - /usr/bin/pod

    ==解决方案==:苹果系统升级OS X EL Capitan后会出现的插件错误,
    ==将安装流程:安装CocoaPods 的==

sudo gem install cocoapods

改为

sudo gem install -n /usr/local/bin cocoapods

  • 错误3:
    [!] Unable to satisfy the following requirements: - AVOSCloud (~> 3.1.6.3) required by Podfile
    Specs satisfying the AVOSCloud (~> 3.1.6.3) dependency were found, but they required a higher minimum deployment target.

==解决方案==:安装流程:Podfile文件 中 platform:ios, ‘6.0’ 后边的 6.0 是平台版本号 ,一定要加上

使用篇

添加Podfile文件
  1. 在需要添加pod的项目根目录,添加一个Podfile文件:
1.0版本后为下面的格式,更早的版本不需要target,end
platform :ios, '8.0'   # 制定平台及使用的最低系统版本
target 'DocSite' do    # 设置一个项目target,可以在Xcode的targets下查看
        pod 'AFNetworking'   # 需要添加的第三方库,一行一个库名
end   # 结束标志
Podfile也可以放在任何位置,但是需要在Podfile顶部使用”xcodeproj”关键字指定工程的路径,如下:
xcodeproj "/Users/username/Desktop/CocoaPodsDemo/CocoaPodsDemo.xcworkspace"
但是执行pod install命令后,生成的文件放在了Podfile所在的目录.
安装第三方依赖库

在Terminal下,切入到项目的根目录下,执行如下命令:

pod install

安装完成后,会提示如下信息:

[!] Please close any current Xcode sessions and use `CocoaPodsDemo.xcworkspace` for this project from now on.

经过以上步骤后,我们现在可以打开CocoaPodsDemo.xcworkspace启动我们的新工程了.新工程中已经通过cocoapods引入并配置好了我们刚在Podfile写的需要依赖的第三方库了.

引入第三方库后找不到头文件?

在项目的Targe-Build Settings-Search Paths-User Header Search Paths中添加${SRCROOT} 值为 recursive

如下图:

cocoapods配置头文件

如何编译从github上checkout下来的一个已包含CocoPods类库的项目?

打开终端进入你所下载项目的根目录,执行以下命令,后会得到上面的那句话:

$ pod update

等待命令运行完毕后,同样最后会输出:

[!] From now on use `xxxxx.xcworkspace`.

如何删除cocopods?

  1. 删除工程文件夹下的Podfile、Podfile.lock及Pods文件夹
    删除xcworkspace文件
  2. 使用xcodeproj文件打开工程,删除Frameworks组下的Pods.xcconfig及libPods.a引用
  3. 在工程设置中的Build Phases下删除Check Pods
  4. Manifest.lock及Copy Pods Resources

删除cocoapods

CocoaPods常用命令

  • pod install

根据Podfile文件指定的内容,安装依赖库,如果有Podfile.lock文件而且对应的Podfile文件未被修改,则会根据Podfile.lock文件指定的版本安装。

每次更新了Podfile文件时,都需要重新执行该命令,以便重新安装Pods依赖库。

  • pod update

若果Podfile中指定的依赖库版本不是写死的,当对应的依赖库有了更新,无论有没有Podfile.lock文件都会去获取Podfile文件描述的允许获取到的最新依赖库版本。

  • pod search

命令格式为:

$ pod search 类库名,支持模糊查询(如:AFNetworking)
使用Q键退出搜索

pod search result

红框中的信息为AFNetworking 最新版本,Version中显示了历史版本,根据这些信息来编写我们的Podfile文件如:

pod 'AFNetWorking', '~> 3.1.0'

这句话具体含义是什么呢?
当我们通过cocopods引入依赖库时,需要显示或隐式注明引用的依赖库版本,具体写法和表示含义如下:

pod ‘AFNetworking’      //不显式指定依赖库版本,表示每次都获取最新版本
pod 'AFNetworking', '2.0'     //只使用2.0版本
pod 'AFNetworking', '> 2.0'     //使用高于2.0的版本
pod 'AFNetworking', '>= 2.0'     //使用大于或等于2.0的版本
pod 'AFNetworking', '< 2.0'     //使用小于2.0的版本
pod 'AFNetworking', '<= 2.0'     //使用小于或等于2.0的版本
pod 'AFNetworking', '~> 0.1.2'     //使用大于等于0.1.2但小于0.2的版本
pod 'AFNetworking', '~>0.1'     //使用大于等于0.1但小于1.0的版本
pod 'AFNetworking', '~>0'     //高于0的版本,写这个限制和什么都不写是一个效果,都表示使用最新版本
  • pod setup

用于跟新本地电脑上的保存的Pods依赖库tree。由于每天有很多人会创建或者更新Pods依赖库,这条命令执行的时候会相当慢,还请耐心等待。我们需要经常执行这条命令,否则有新的Pods依赖库的时候执行pod search命令是搜不出来的。

①多个target中使用相同的Pods依赖库

比如,名称为CocoaPodsTest的target和Second的target都需要使用Reachability、SBJson、AFNetworking三个Pods依赖库,可以使用link_with关键字来实现,将Podfile写成如下方式:

link_with ‘CocoaPodsTest’, ‘Second’
platform :ios do
    pod ‘Reachability’,  ‘~> 3.0.0’
    pod ‘SBJson’, ‘~> 4.0.0’
    platform :ios, ‘7.0’
    pod ‘AFNetworking’, ‘~> 2.0’
end

这种写法就实现了CocoaPodsTest和Second两个target共用相同的Pods依赖库。

②不同的target使用完全不同的Pods依赖库

CocoaPodsTest这个target使用的是Reachability、SBJson、AFNetworking三个依赖库,但Second这个target只需要使用OpenUDID这一个依赖库,这时可以使用target关键字,Podfile的描述方式如下:

target :’CocoaPodsTest’ do
platform :ios
    pod ‘Reachability’,  ‘~> 3.0.0’
    pod ‘SBJson’, ‘~> 4.0.0’
platform :ios, ‘7.0’
    pod ‘AFNetworking’, ‘~> 2.0′
end
target :’Second’ do
    pod ‘OpenUDID’, ‘~> 1.0.0’
end

其中,do/end作为开始和结束标识符。

* 注:

有时候,我们在执行pod installpod search命令时,会在终端偶现卡在pod setup界面的情况,
其实,该情况也许并非真的卡住,下面给出两种解决方案。
  • 方案1:

    • 在执行pod install命令时加上参数--verbose即:pod install 'ThirdPartyName' --verbose,可在终端详细显示安装信息,看到pod目前正在做什么(其实是在安装第三方库的索引),确认是否是真的卡住。
    • 进入终端家目录,输入ls -a可看到隐藏的pod文件夹,输入cd .cocoapods进入pod文件夹,然后输入du -sh即可看到repos文件夹的容量,隔几秒执行一下该命令,可看到repos的容量在不断增大,待容量增大至900+M时,说明,repos文件夹索引目录已安装完毕。此时,pod功能即可正常使用。
  • 方案2:

    • 通过方案1,我们知道在pod setup过程中,pod其实是在安装第三方库的索引目录,因此我们可以直接从GitHub上下载索引目录拷进repos文件夹。
    • 前往https://github.com/CocoaPods/Specs,下载该索引,然后拷进repos文件夹。目录结构如下图所示:

  • 完全退出终端,重启终端,pod功能即可正常使用。

Podfile.lock文件

在使用CocoaPods,执行完pod install之后,会生成一个Podfile.lock文件。这个文件看起来跟我们关系不大,实际上绝对不应该忽略它。

该文件用于保存已经安装的Pods依赖库的版本,通过CocoaPods安装了SBJson、AFNetworking、Reachability三个POds依赖库以后对应的Podfile.lock文件内容为

PODS:
– AFNetworking (2.1.0):
– AFNetworking/NSURLConnection
– AFNetworking/NSURLSession
– AFNetworking/Reachability
– AFNetworking/Security
– AFNetworking/Serialization
– AFNetworking/UIKit
– AFNetworking/NSURLConnection (2.1.0):
– AFNetworking/Reachability
– AFNetworking/Security
– AFNetworking/Serialization
– AFNetworking/NSURLSession (2.1.0):
– AFNetworking/NSURLConnection
– AFNetworking/Reachability (2.1.0)
– AFNetworking/Security (2.1.0)
– AFNetworking/Serialization (2.1.0)
– AFNetworking/UIKit (2.1.0):
– AFNetworking/NSURLConnection
– Reachability (3.0.0)
– SBJson (4.0.0)
DEPENDENCIES:
– AFNetworking (~> 2.0)
– Reachability (~> 3.0.0)
– SBJson (~> 4.0.0)
SPEC CHECKSUMS:
AFNetworking: c7d7901a83f631414c7eda1737261f696101a5cd
Reachability: 500bd76bf6cd8ff2c6fb715fc5f44ef6e4c024f2
SBJson: f3c686806e8e36ab89e020189ac582ba26ec4220
COCOAPODS: 0.29.0

Podfile.lock文件最大得用处在于多人开发。当团队中的某个人执行完pod install命令后,生成的Podfile.lock文件就记录下了当时最新Pods依赖库的版本,这时团队中的其它人check下来这份包含Podfile.lock文件的工程以后,再去执行pod install命令时,获取下来的Pods依赖库的版本就和最开始用户获取到的版本一致。如果没有Podfile.lock文件,后续所有用户执行pod install命令都会获取最新版本的SBJson,这就有可能造成同一个团队使用的依赖库版本不一致,这对团队协作来说绝对是个灾难!

在这种情况下,如果团队想使用当前最新版本的SBJson依赖库,有两种方案:

  1. 更改Podfile,使其指向最新版本的SBJson依赖库;
  2. 执行pod update命令;

参考:

cocoapods安装使用及配置私有库

使用CocoaPods卡在了"pod setup"界面的解决办法