0%

Welcome to Hexo! This is your very first post. Check documentation for more info. If you get any problems when using Hexo, you can find the answer in troubleshooting or you can ask me on GitHub.

Quick Start

Create a new post

1
//$ hexo new "My New Post"

More info: Writing

Run server

1
//$ hexo server

More info: Server

Generate static files

1
//$ hexo generate

More info: Generating

Deploy to remote sites

1
//$ hexo deploy

More info: Deployment

译自 《THE BIGGEST LIE IN JIU JITSU》

Marcus'Buchecha'Almeida - 现任IBJJF绝对冠军。这家伙很坚强,相信我!图片由BJJ Pix的William Burkhardt提供  。

最近我看到了一个让我捧腹的柔术笑话。

“柔术的技术是无敌的!”

你可能不想听听下面的内容,但是作为一名柔术教练,我的工作是与你分享我认为的真理,而不是虚假的谎言。所以在这里与你们分享我的见解:

目录

  • 技术不是无敌的
  • 我的见解
  • 一个假设
  • 真实的例子
  • 这个神话是从哪里来的?
  • 好消息
  • 如何变得更强
  • 你该怎么做

技术不是无敌的

基础运动能力,特别是力量,对柔术的表现起着巨大的作用,而且往往可以克服技巧。尽管你被告知了卓越的技术并不总是能克服体型和力量优势。但在我看来,力量和技术一样重要。

我的见解

我练习巴西柔术将近二十年,已经是一个黑带了。我认为自己是一个技术顶尖的柔术运动员,我致力于使自己的技术动作更加高效和精准。

但是这里有个小秘密:有时候,我会利用力量强行完成一个柔术动作。我并不常这样做,但我明白力量对于柔术的重要性,并且它可以更好的帮助我完成动作,我已经认识到这样做是正确的。

我的脑袋中仍然有一种“无力游戏”的想法,那就是我技术非常优秀,以至于我不需要出力就可以降服对手。但我知道这只是我的一个天真的想法。

是的,世界顶尖的柔术运动员毫无疑问都拥有顶尖的柔术技术。但是,他们无一例外都是非常强壮的人。由于先天的遗传和后天科学的训练,这些家伙拥有不可思议的力量和体格。

我的经验得出的这个等式适用与大多数情况:

运动员A(中等技术 + 上等身体素质) > 运动员B(上等技术 + 下等身体素质)

一个假设

我知道你还不相信我,所以我会用一个例子来说明我的观点。让我们来看看使用两个战士 Steve 和 JoJo 的假想情景。

Steve:

Steve 5岁开始学习柔术,由马塞洛·加西亚,拉法·门德斯和瑞克森·格雷西执教。它学会了他们所有的技巧,并且吸收了他们所有关于压力,时间和人体力学的智慧。他在接下来的13年里每周训练6天。

18岁,体重200磅的 Steve 击败了所有对手取得了 IBJJF(国际巴西柔术联合会)世界锦标赛的棕色带中量级冠军,并且立即被授予黑带。第二年,在对战拥有绝对黑带实力的 Buchecha(开头照片中的人)的决赛中,用了一个飞身十字固在13秒内降服了他。

JoJo:

JoJo 是一个10岁的银背大猩猩。他体重400磅。他从未接受过柔术或其他武术的训练。

一决胜负:

假设 JoJo 与 Steve 展开一场柔术规则的比赛。

你认为谁会赢得这场比赛?如果你认为 Steve 会用他的“无敌技巧”击败 JoJo,那么你就是妄想。(此外,你可以用 点穴 试试~)

JoJo 的体格力量优势根本无法用技术来克服。

你可以知道世界上所有的柔术运动,但是你不会打败JoJo。

真实的例子

好吧,上面的例子非常不切实际,根本不会发生。但是,我可以举一些我身边的例子:

例子1:

在2013年,我亲眼目睹了世界冠军中,一位黑带女性与体重相同的紫色带男性的比赛,他们在一个开放的垫子上打成一片。这个女人一点机会都没有。她在6分钟内拍垫近十次。

那么现在是因为“女人不擅长柔术”还是因为“男人比女人好”呢?当然不是。这只是一个简单的力量问题。这位男性拥有更高的睾酮水平,因此拥有更强大的结缔组织和更多的肌肉。

例子2:

我有一个朋友身高 1.95m,重达 300磅(136kg),是一个前NCAA中后卫球员。同时他也是柔术棕色腰带。他可以(而且经常)很容易地只用一只手臂将我从地面上抬起。当我们滚动时他绝对砸我,这时候基本上我是无能为力的。

这是否因为他的技术比我好?当然不是。我的训练的时间比他更长,训练频率和强度要高得多。这是因为他比我更高,更大,更强壮。

例子3:

我的正常体重大约是203磅(92kg)。有时,由于各种原因,包括力量训练计划,肌酸周期或假日过度放纵,可能会高达218甚至220磅。

因为我一直在垫子上呆着,所以我可以敏锐的察觉到体重对于柔术的影响。我可以直接告诉你:你越重,对抗越轻松。我可以更轻松的控制体重较轻的对手,并且能对抗更长的时间。

这个神话是从哪里来的?

传统武术的胡扯

这个误解也是传统武术的骗人的精髓所在。告诉一个弱小的人学习了某种武术,他就轻松可以击败比他高大,更强壮的坏人。

在20世纪,一个巨大的产业就建立在这个基础之上,各种乱七八糟的武术系统被包装并推给了好骗的西方人。尽管MMA中的柔术技术帮助清除了许多武术的骗局,但现在仍然受到影响。

柔术课的结构

还有一部分原因是由于柔术学院商业模式的本质。虽然柔术比赛竞争激烈,但是现在的柔术学院通常还只是围绕着技术动作实战对抗这两个方面进行教学和训练。因此,早期的先驱者重视身体训练,这是有道理的。

乔治·圣皮埃尔的教练Firhas Zahabi曾经对我说过。“随着柔术学院商业化的推广,我们看到了很多必要的体能训练消失了。”他说的对,在绝大多数的柔术学院中,体能训练并不被重视。当然,你也可以做一些跳跃俯卧撑和俯卧撑作为热身的一部分,但这还远远不够。看看拳击手和摔跤手。体能训练往往是他们训练的最重要的组成部分,而对抗往往是花时间最小的一个。

罗伊斯·格雷斯 与 UFC

罗伊斯·格雷西(Royce Gracie)在 UFC 早期的比赛中的惊人表现导致了一些人相信技术确实是无敌的。在我看来,罗伊斯赢了,因为他打的比赛看起来像这样:

斗士A(中等属性+强大的技术)> 斗士B(伟大的属性+没有技术)

由于第二代 MMA 斗士的的属性已经改变,因为家伙们已经开始学习柔术了。比赛开始更像这样:

斗士A(中等属性+强大技术)≥ 斗士B(强大属性+一点点技巧)

在如今的 MMA 比赛中,我们经常看到的情况是这样的:

斗士A(卓越的属性+伟大的技术)> 斗士B(伟大的属性+伟大的技术)

杠杠原理的迷惑

杠杠原理能成倍加强力量,但不是力量的来源。当然,杠杠原理能帮你能更有效的利用力量,但没有力量来源,这个杠杠力也不复存在。这就是‘柔术’中‘杠杠原理’这个概念的迷惑性。

尽管可能会有人告诉你,没有人能为柔术添加杠杠作用。但是一些聪明的运动员及教练确实能够准确的找到杠杠的支点,并且使用的力量来完成动作,效果惊人。

好消息

好消息是就算你只进行柔术对抗训练也能自然而然的提升你的体能,尽管这个提升有局限性并且基因决定了你的体能极限(抱歉,就是这样),而通过科学而且集中体能训练可以大幅度提升你的体能。

同时,体型小的训练者并不是总是处于劣势。相对力量会随着体型的增加而减小。所以假设其他条件相同的情况下,一个体重比你大20%的对手,力量并不会比你大20%,通常这个值会是12%~15%。这就意味着那些拥有惊人身体的小个子训练者通常会扳平体型的劣势,有时候甚至还会反超。

最后一个就是力量的增长也会随着年龄的增长而称下降的趋势,并在年老的时候就维持不变了。“人的力量就是这么真实”。

如何变得更强

检查你的激素水平

如果你是一个男性柔术运动员,我建议你去内分泌专家那检查你的激素水平。如果你的睾丸酮激素水平过低,不管你如何训练,你的身体素质都不会有较大的提升。一个好医生会建议你使用多种补剂和药品来解决这个问题。

体操

总体来说,拥有了功能性力量与身体控制能力,你将很难被击败。如果让我在力量训练之外再挑选一个最为柔术的赋值训练,那就是体操了。

攀岩

另一项能直接对柔术的运动表现及其力量提升极大的运动就是攀岩了,尤其是握力。

举重

举重对运动表现的提升不是通过几组二头弯举或者卧推就可以的,那是健身。你需要在专业教练的指导下练习奥运举和力量举(例如挺举,深蹲)。

你该怎么做

提高柔术水平不仅仅是提升柔术技术。我喜欢柔术的技术,它是那样的直接有效,令人着迷。如果你想在道垫上降服对手,高质量的动作是必不可少的。但这还不够。你可以在这篇文章中找到答案。

真正的武术家是一个在各个方面力精益求精的人。这包括变得更加强壮。如果你想成为顶级的柔术家,将你的体能提升到极限是你的必修课。

比较水的 Personal Notes

查看你的系统有几种shell

cat /etc/shells

显示

/bin/bash
/bin/csh
/bin/ksh
/bin/sh
/bin/tcsh
/bin/zsh

安装 oh my zsh

git clone git://github.com/robbyrussell/oh-my-zsh.git ~/.oh-my-zsh
cp ~/.oh-my-zsh/templates/zshrc.zsh-template ~/.zshrc

重新打开终端,输入

zsh

即可切换终端,并且发现 oh my zsh 已经帮我们配置好 zsh 了

修改主题

open ~/.zshrc 

修改 ZSH_THEME=”robbyrussell”,主题在 ~/.oh-my-zsh/themes 目录下。
修改为

ZSH_THEME="kolo"

可以参照这里进行选择.

设置为默认shell

chsh -s /bin/zsh

添加自定义命令

open ~/.zshrc

添加显示隐藏文件的快捷命令

alias fd='defaults write com.apple.finder AppleShowAllFiles -boolean true ; killall Finder'
alias fh='defaults write com.apple.finder AppleShowAllFiles -boolean false ; killall Finder'

随便整理的一些自用的Git指令

GitHub创建仓库提示代码

echo "# 项目名" >> README.md
git init
git add README.md
git commit -m "first commit"
git remote add origin git@github.com:qiubaiying/项目名.git
git push -u origin master

若仓库存在直接push

git remote add origin git@github.com:qiubaiying/test.git
git push -u origin master

常用操作

创建仓库(初始化)

在当前指定目录下创建
git init

新建一个仓库目录
git init [project-name]

克隆一个远程项目
git clone [url]

添加文件到缓存区

添加所有变化的文件
 git add .

添加名称指定文件
git add text.txt

配置

设置提交代码时的用户信息
git config [--global] user.name "[name]"
git config [--global] user.email "[email address]"

提交

提交暂存区到仓库区
git commit -m "msg"

# 提交暂存区的指定文件到仓库区
$ git commit [file1] [file2] ... -m [message]

# 提交工作区自上次commit之后的变化,直接到仓库区
$ git commit -a

# 提交时显示所有diff信息
$ git commit -v

# 使用一次新的commit,替代上一次提交
# 如果代码没有任何新变化,则用来改写上一次commit的提交信息
$ git commit --amend -m [message]

# 重做上一次commit,并包括指定文件的新变化
$ git commit --amend [file1] [file2] ...

远程同步

# 下载远程仓库的所有变动
$ git fetch [remote]

# 显示所有远程仓库
$ git remote -v

# 显示某个远程仓库的信息
$ git remote show [remote]

# 增加一个新的远程仓库,并命名
$ git remote add [shortname] [url]

# 取回远程仓库的变化,并与本地分支合并
$ git pull [remote] [branch]

# 上传本地指定分支到远程仓库
$ git push [remote] [branch]

# 强行推送当前分支到远程仓库,即使有冲突
$ git push [remote] --force

# 推送所有分支到远程仓库
$ git push [remote] --all

分支

# 列出所有本地分支
$ git branch

# 列出所有远程分支
$ git branch -r

# 列出所有本地分支和远程分支
$ git branch -a

# 新建一个分支,但依然停留在当前分支
$ git branch [branch-name]

# 新建一个分支,并切换到该分支
$ git checkout -b [branch]

# 新建一个分支,指向指定commit
$ git branch [branch] [commit]

# 新建一个分支,与指定的远程分支建立追踪关系
$ git branch --track [branch] [remote-branch]

# 切换到指定分支,并更新工作区
$ git checkout [branch-name]

# 切换到上一个分支
$ git checkout -

# 建立追踪关系,在现有分支与指定的远程分支之间
$ git branch --set-upstream [branch] [remote-branch]

# 合并指定分支到当前分支
$ git merge [branch]

# 选择一个commit,合并进当前分支
$ git cherry-pick [commit]

# 删除分支
$ git branch -d [branch-name]

# 删除远程分支
$ git push origin --delete [branch-name]
$ git branch -dr [remote/branch]

标签Tags

添加标签 在当前commit
git tag -a v1.0 -m 'xxx' 

添加标签 在指定commit
git tag v1.0 [commit]

查看
git tag

删除
git tag -d V1.0

删除远程tag
git push origin :refs/tags/[tagName]

推送
git push origin --tags

拉取
git fetch origin tag V1.0

新建一个分支,指向某个tag
git checkout -b [branch] [tag]

查看信息

# 显示有变更的文件
$ git status

# 显示当前分支的版本历史
$ git log

# 显示commit历史,以及每次commit发生变更的文件
$ git log --stat

# 搜索提交历史,根据关键词
$ git log -S [keyword]

# 显示某个commit之后的所有变动,每个commit占据一行
$ git log [tag] HEAD --pretty=format:%s

# 显示某个commit之后的所有变动,其"提交说明"必须符合搜索条件
$ git log [tag] HEAD --grep feature

# 显示某个文件的版本历史,包括文件改名
$ git log --follow [file]
$ git whatchanged [file]

# 显示指定文件相关的每一次diff
$ git log -p [file]

# 显示过去5次提交
$ git log -5 --pretty --oneline

# 显示所有提交过的用户,按提交次数排序
$ git shortlog -sn

# 显示指定文件是什么人在什么时间修改过
$ git blame [file]

# 显示暂存区和工作区的差异
$ git diff

# 显示暂存区和上一个commit的差异
$ git diff --cached [file]

# 显示工作区与当前分支最新commit之间的差异
$ git diff HEAD

# 显示两次提交之间的差异
$ git diff [first-branch]...[second-branch]

# 显示今天你写了多少行代码
$ git diff --shortstat "@{0 day ago}"

# 显示某次提交的元数据和内容变化
$ git show [commit]

# 显示某次提交发生变化的文件
$ git show --name-only [commit]

# 显示某次提交时,某个文件的内容
$ git show [commit]:[filename]

# 显示当前分支的最近几次提交
$ git reflog

撤销

# 恢复暂存区的指定文件到工作区
$ git checkout [file]

# 恢复某个commit的指定文件到暂存区和工作区
$ git checkout [commit] [file]

# 恢复暂存区的所有文件到工作区
$ git checkout .

# 重置暂存区的指定文件,与上一次commit保持一致,但工作区不变
$ git reset [file]

# 重置暂存区与工作区,与上一次commit保持一致
$ git reset --hard

# 重置当前分支的指针为指定commit,同时重置暂存区,但工作区不变
$ git reset [commit]

# 重置当前分支的HEAD为指定commit,同时重置暂存区和工作区,与指定commit一致
$ git reset --hard [commit]

# 重置当前HEAD为指定commit,但保持暂存区和工作区不变
$ git reset --keep [commit]

# 新建一个commit,用来撤销指定commit
# 后者的所有变化都将被前者抵消,并且应用到当前分支
$ git revert [commit]

# 暂时将未提交的变化移除,稍后再移入
$ git stash
$ git stash pop

其他

# 生成一个可供发布的压缩包
$ git archives

前言

定时器的使用是软件开发基础技能,用于延时执行或重复执行某些方法。

我相信大部分人接触iOS的定时器都是从这段代码开始的:

1
[NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(action:) userInfo:nil repeats:YES]

但是你真的会用吗?

正文

iOS定时器

首先来介绍iOS中的定时器

iOS中的定时器大致分为这几类:

  • NSTimer
  • CADisplayLink
  • GCD定时器

NSTimer

使用方法

NSTime定时器是我们比较常使用的定时器,比较常使用的方法有两种:

1
2
3
+ (NSTimer *)timerWithTimeInterval:(NSTimeInterval)ti target:(id)aTarget selector:(SEL)aSelector userInfo:(nullable id)userInfo repeats:(BOOL)yesOrNo;

+ (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)ti target:(id)aTarget selector:(SEL)aSelector userInfo:(nullable id)userInfo repeats:(BOOL)yesOrNo;

这两种方法都是创建一个定时器,区别是用timerWithTimeInterval:方法创建的定时器需要手动加入RunLoop中。

1
2
3
4
// 创建NSTimer对象
NSTimer *timer = [NSTimer timerWithTimeInterval:3 target:self selector:@selector(timerAction) userInfo:nil repeats:YES];
// 加入RunLoop中
[[NSRunLoop mainRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];

需要注意的是: UIScrollView 滑动时执行的是 UITrackingRunLoopModeNSDefaultRunLoopMode被挂起,会导致定时器失效,等恢复为滑动结束时才恢复定时器。其原因可以查看我这篇《Objective-C RunLoop 详解》中的 “RunLoop 的 Mode“章节,有详细的介绍。

举个例子:

1
2
3
4
5
6
7
8
9
10
- (void)startTimer{
NSTimer *UIScrollView = [NSTimer timerWithTimeInterval:0.5 target:self selector:@selector(action:) userInfo:nil repeats:YES];
[[NSRunLoop mainRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];
}

- (void)action:(NSTimer *)sender {
static int i = 0;
NSLog(@"NSTimer: %d",i);
i++;
}

timer添加到NSDefaultRunLoopMode中,没0.5秒打印一次,然后滑动UIScrollView.

打印台输出:

可以看出在滑动UIScrollView时,定时器被暂停了。

所以如果需要定时器在 UIScrollView 拖动时也不影响的话,有两种解决方法

  1. timer分别添加到 UITrackingRunLoopModeNSDefaultRunLoopMode
1
2
[[NSRunLoop mainRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];
[[NSRunLoop mainRunLoop] addTimer:timer forMode: UITrackingRunLoopMode];
  1. 直接将timer添加到NSRunLoopCommonModes 中:
1
[[NSRunLoop mainRunLoop] addTimer:timer forMode: NSRunLoopCommonModes]; 

但并不是都timer所有的需要在滑动UIScrollView时继续执行,比如使用NSTimer完成的帧动画,滑动UIScrollView时就可以停止帧动画,保证滑动的流程性。

若没有特殊要求的话,一般使用第二种方法创建完timer,会自动添加到NSDefaultRunLoopMode中去执行,也是平时最常用的方法。

1
NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(action:) userInfo:nil repeats:YES];

参数:

TimeInterval:延时时间

target:目标对象,一般就是self本身

selector:执行方法

userInfo:传入信息

repeats:是否重复执行

以上创建的定时器,若repeats参数设为NO,执行一次后就会被释放掉;

repeats参数设为YES重复执行时,必须手动关闭,否则定时器不会释放(停止)。

释放方法:

1
2
// 停止定时器
[timer invalidate];

实际开发中,我们会将NSTimer对象设置为属性,这样方便释放。

iOS10.0 推出了两个新的API,与上面的方法相比,selector换成Block回调以、减少传入的参数(那几个参数真是鸡肋)。不过开发中一般需要适配低版本,还是尽量使用上面的方法吧。

1
2
3
+ (NSTimer *)timerWithTimeInterval:(NSTimeInterval)interval repeats:(BOOL)repeats block:(void (^)(NSTimer *timer))block API_AVAILABLE(macosx(10.12), ios(10.0), watchos(3.0), tvos(10.0));

+ (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)interval repeats:(BOOL)repeats block:(void (^)(NSTimer *timer))block API_AVAILABLE(macosx(10.12), ios(10.0), watchos(3.0), tvos(10.0));

###特点

  • 必须加入Runloop

    上面不管使用哪种方法,实际最后都会加入RunLoop中执行,区别就在于是否手动加入而已。

  • 存在延迟

    不管是一次性的还是周期性的timer的实际触发事件的时间,都会与所加入的RunLoop和RunLoop Mode有关,如果此RunLoop正在执行一个连续性的运算,timer就会被延时出发。重复性的timer遇到这种情况,如果延迟超过了一个周期,则会在延时结束后立刻执行,并按照之前指定的周期继续执行,这个延迟时间大概为50-100毫秒.

    所以NSTimer不是绝对准确的,而且中间耗时或阻塞错过下一个点,那么下一个点就pass过去了.

  • UIScrollView滑动会暂停计时

    添加到NSDefaultRunLoopModetimerUIScrollView滑动时会暂停,若不想被UIScrollView滑动影响,需要将 timer 添加再到 UITrackingRunLoopMode 或 直接添加到NSRunLoopCommonModes

##CADisplayLink

CADisplayLink官方介绍:

A CADisplayLink object is a timer object that allows your application to synchronize its drawing to the refresh rate of the display

CADisplayLink对象是一个和屏幕刷新率同步的定时器对象。每当屏幕显示内容刷新结束的时候,runloop就会向CADisplayLink指定的target发送一次指定的selector消息, CADisplayLink类对应的 selector 就会被调用一次。

从原理上可以看出,CADisplayLink适合做界面的不停重绘,比如视频播放的时候需要不停地获取下一帧用于界面渲染,或者做动画。
###使用方法

创建:

1
2
3
4
5
6
7
8
@property (nonatomic, strong) CADisplayLink *displayLink;

self.displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(handleDisplayLink:)];

// 每隔1帧调用一次
self.displayLink.frameInterval = 1;

[self.displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];

释放方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
[self.displayLink invalidate];  

self.displayLink = nil;
```
当把**CADisplayLink**对象添加到runloop中后,`selector`就能被周期性调用,类似于重复的NSTimer被启动了;执行`invalidate`操作时,CADisplayLink对象就会从runloop中移除,`selector`调用也随即停止,类似于NSTimer的`invalidate`方法。

**CADisplayLink**中有两个重要的属性:

- **frameInterval**

NSInteger类型的值,用来设置间隔多少帧调用一次`selector`方法,默认值是1,即每帧都调用一次。

- **duration**

`CFTimeInterval`值为`readOnly`,表示两次屏幕刷新之间的时间间隔。需要注意的是,该属性在`targe`t的`selector`被首次调用以后才会被赋值。`selector`的调用间隔时间计算方式是:**调用间隔时间 = duration × frameInterval**。


###特点

- **刷新频率固定**

正常情况iOS设备的屏幕刷新频率是固定**60Hz**,如果CPU过于繁忙,无法保证屏幕60次/秒的刷新率,就会导致跳过若干次调用回调方法的机会,跳过次数取决CPU的忙碌程度。
- **屏幕刷新时调用**

CADisplayLink在正常情况下会在每次刷新结束都被调用,精确度相当高。但如果调用的方法比较耗时,超过了屏幕刷新周期,就会导致跳过若干次回调调用机会

- **适合做界面渲染**

CADisplayLink可以确保系统渲染每一帧的时候我们的方法都被调用,从而保证了动画的流畅性。

##GCD定时器

**GCD定时器**和NSTimer是不一样的,NSTimer受RunLoop影响,但是GCD的定时器不受影响,因为通过源码可知RunLoop也是基于GCD的实现的,所以GCD定时器有非常高的精度。关于GCD的使用可一看看[这篇博客](http://www.cnblogs.com/pure/archive/2013/03/31/2977420.html)。

###使用方法
创建GCD定时器定时器的方法稍微比较复杂,看下面的代码:

####单次的延时调用
NSObject中的`performSelector:withObject:afterDelay:`以及 `performSelector:withObject:afterDelay:inModes:` 这两个方法在调用的时候会设置当前 runloop 中 `timer` ,前者设置的 `timer` 在 `NSDefaultRunLoopMode` 运行,后者则可以指定 **NSRunLoop** 的 `mode` 来执行。我们上面介绍过 runloop 中 `timer` 在 `UITrackingRunLoopMode` 被挂起,就导致了代码就会一直等待 `timer` 的调度,解决办法在上面也有说明。

不过我们可以用另一套方案来解决这个问题,就是使用GCD中的 `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){
[self someMethod];
});

1
2

####循环调用

// 创建GCD定时器
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

dispatch_source_t _timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);

dispatch_source_set_timer(_timer, dispatch_walltime(NULL, 0), 1.0 * NSEC_PER_SEC, 0); //每秒执行

// 事件回调
dispatch_source_set_event_handler(_timer, ^{

dispatch_async(dispatch_get_main_queue(), ^{
    // 在主线程中实现需要的功能
    
}

}

});

// 开启定时器
dispatch_resume(_timer);

// 挂起定时器(dispatch_suspend 之后的 Timer,是不能被释放的!会引起崩溃)
dispatch_suspend(_timer);

// 关闭定时器
dispatch_source_cancel(_timer);

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

上面代码中要注意的是:

1. `dispatch_source_set_event_handler()`中的任务实在子线程中执行的,若需要回到主线程,要调用`dispatch_async(dispatch_get_main_queue(), ^{}`.
- `dispatch_source_set_timer` 中第二个参数,当我们使用 `dispatch_time` 或者 `DISPATCH_TIME_NOW` 时,系统会使用默认时钟来进行计时。然而当系统休眠的时候,默认时钟是不走的,也就会导致计时器停止。使用 `dispatch_walltime ` 可以让计时器按照真实时间间隔进行计时.
- 第三个参数, ` 1.0 * NSEC_PER_SEC` 为每秒执行一次,对应的还有毫秒,分秒,纳秒可以选择.


- `dispatch_source_set_event_handler` 这个函数在执行完之后,block 会立马执行一遍,后面隔一定时间间隔再执行一次。而 `NSTimer` 第一次执行是到计时器触发之后。这也是和 `NSTimer` 之间的一个显著区别。
- 挂起(暂停)定时器, `dispatch_suspend` 之后的 `Timer`,不能被释放的,会引起崩溃.
- 创建的`timer`一定要有`dispatch_suspend(_timer)`或`dispatch_source_cancel(_timer)`这两句话来指定出口,否则定时器将不执行,若我们想无限循环可将 `dispatch_source_cancel(_timer)` 写在一句永不执行的`if`判断语句中。


##使用场景

介绍完iOS中的各种定时器,接下来我们来说说这几种定时器在开发中的几种用法。
###短信重发倒计时

短信倒计时使我们登录注册常用的功能,一般设置为60s,实现方法如下:

// 计时时间
@property (nonatomic, assign) int timeout;

/** 开启倒计时 */

  • (void)startCountdown { if (_timeout > 0) {
      return;
    
    } _timeout = 60; // GCD定时器
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); dispatch_source_t _timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue); dispatch_source_set_timer(_timer, dispatch_walltime(NULL, 0), 1.0 * NSEC_PER_SEC, 0); //每秒执行 dispatch_source_set_event_handler(_timer, ^{
      if(_timeout <= 0 ){// 倒计时结束
          
          // 关闭定时器
          dispatch_source_cancel(_timer);
          
          dispatch_async(dispatch_get_main_queue(), ^{
              
              //设置界面的按钮显示 根据自己需求设置
              [self.sendMsgBtn setTitle:@"发送" forState:UIControlStateNormal];
              
              self.sendMsgBtn.enabled = YES;
              
          });
          
      }else{// 倒计时中
          
          // 显示倒计时结果
          
          NSString *strTime = [NSString stringWithFormat:@"重发(%.2d)", _timeout];
          
          dispatch_async(dispatch_get_main_queue(), ^{
              
              //设置界面的按钮显示 根据自己需求设置
              
              [self.sendMsgBtn setTitle:[NSString stringWithFormat:@"%@",strTime] forState:UIControlStateNormal];
              
              self.sendMsgBtn.enabled = NO;
              
          });
          
          _timeout--;
      }
    
    }); // 开启定时器
    dispatch_resume(_timer);

}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

在上面代码中,我们设置了一个60s循环倒计时,当我们向服务器获取短信验证码成功时 调用该方法开始倒计时。每秒刷新按钮的倒计时数,倒计时结束时再将按钮 `Title` 恢复为“发送”.

有一点需要注意的是,按钮的样式要设置为 **UIButtonTypeCustom**,否则会出现刷新 `Title` 时闪烁.

我们可以把这个方法封装一下,方便调用,否则在控制器中写这么一大段代码确实也不优雅。

效果如下:

![](http://upload-images.jianshu.io/upload_images/2178672-3d4d1353bcc36026.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

##### [代码链接](https://github.com/qiubaiying/BYTimer)


###每个几分钟向服务器发送数据

在有定位服务的APP中,我们需要每个一段时间将定位数据发送到服务器,比如每5s定位一次每隔5分钟将再统一将数据发送服务器,这样会处理比较省电。
一般程序进入后台时,定时器会停止,但是在定位APP中,需要持续进行定位,APP在后台时依旧可以运行,所以在后台定时器也是可以运行的。

注:关于iOS后台常驻,可以查看[这篇博客](http://waitingyuan.blog.163.com/blog/static/2155781652014111133150534/)

在使用GCD定时的时候发现GCD定时器也可以在后代运行,创建方法同上面的短信倒计时.

这里我们使用**NSTimer**来创建一个每个5分钟执行一次的定时器.

#import <Foundation/Foundation.h>

typedef void(^TimerBlock)();

@interface BYTimer : NSObject

  • (void)startTimerWithBlock:(TimerBlock)timerBlock;

  • (void)stopTimer;

@end

1

#import “BYTimer.h”

@interface BYTimer ()

@property (nonatomic, strong) NSTimer *timer;
@property (nonatomic, strong) TimerBlock timerBlock;

@end

@implementation BYTimer

  • (void)startTimerWithBlock:(TimerBlock)timerBlock {

    self.timer = [NSTimer timerWithTimeInterval:300 target:self selector:@selector(_timerAction) userInfo:nil repeats:YES];

    [[NSRunLoop mainRunLoop] addTimer:self.timer forMode:NSRunLoopCommonModes];
    _timerBlock = timerBlock;

}

  • (void)_timerAction {
    if (self.timerBlock) {

      self.timerBlock();
    

    }
    }

  • (void)stopTimer {
    [self.timer invalidate];
    }

@end


该接口的实现很简单,就是 **NSTimer** 创建了一个300s执行一次的定时器,但是要注意定时器需要加入`NSRunLoopCommonModes`中。

要使定时器在后台能运行,app 就需要在 [后台常驻](http://waitingyuan.blog.163.com/blog/static/2155781652014111133150534/)。

# 结语

最后总结一下:

NSTimer 使用简单方便,但是应用条件有限。

CADisplayLink 刷新频率与屏幕帧数相同,用于绘制动画。具体使用可看我封装好的一个 [水波纹动画](https://github.com/qiubaiying/WaterRippleView)。

GCD定时器 精度高,可控性强,使用稍复杂。

JSON转模型是我们做iOS开发的基础技能,本文将通过YYModel这个框架安全快速的完成JSON到模型的转换,其中还会介绍到一款好用的插件ESJsonFormat

1、首先创建模型类

创建模型类我们可以通过ESJsonFormat这款插件快速完成。

使用方法:

将光标移动到代码行中 如下图的13行

然后点击Window->ESJsonFormat->Input JSON Window调出窗口

在窗口中输入你要解析的JSON文本,如下图:

Enter继续,然后神奇的一幕发生了

看到在.h中 所有的属性自动为你填上,而且帮你选好了类型

.m 也为你声明了list中成员的类型,不过这里需要稍作修改,因为我们需要用到YYModel进行解析,所以方法名改成modelContainerPropertyGenericClass

1
2
3
4
+ (NSDictionary *)modelContainerPropertyGenericClass {
return @{@"list" : [List class]};
}

还有问题就是属性中出现关键字id,我们需要将id改为teacherId

然后在.m的implementation中声明,将字典的的id

1
2
3
+ (NSDictionary *)modelCustomPropertyMapper {
return @{@"teacherId" : @"id"};
}

这样,模型的创建就完成了,剩下的就是用YYModel进行解析了

2、使用YYModel进行解析

解析很简单,就只需要一句话

1
2
3
4
5
6
7
// 将 JSON (NSData,NSString,NSDictionary) 转换为 Model:
Model *model = [Model yy_modelWithJSON:json];

// 或者
Model *model = [[Model alloc] init];
[model yy_modelSetWithDictionary:json];

到此,简便快速的完成了JSON到模型的转换。

最后,这里附上一篇YYModel的使用