使用 tree 在终端显示树状文件结构
安装 tree
使用 brew 进行安装
$ brew install tree
使用
直接使用
tree
命令,会在当前文件目录下,递归输出所有文件层级$ tree
限制层级
$ tree -L 2
指定当前目录下的某个文件夹
$ tree Desktop
导出文件
用> 文件名.格式
的形式导出
$ tree -L 1 > tree.md
Stay Hungry, Stay Foolish
使用 tree 在终端显示树状文件结构
使用 brew 进行安装
$ brew install tree
直接使用 tree
命令,会在当前文件目录下,递归输出所有文件层级
$ tree
限制层级
$ tree -L 2
指定当前目录下的某个文件夹
$ tree Desktop
用> 文件名.格式
的形式导出
$ tree -L 1 > tree.md
使用
.gitignore
文件忽略指定文件
在Git中,很多时候你只想将代码提交到仓库,而不是将当前文件目录下的文件全部提交到Git仓库中,例如在MacOS系统下面的.DS_Store
文件,或者是Xocde的操作记录,又或者是pod库的中一大串的源代码。这种情况下使用.gitignore
就能够在Git提交时自动忽略掉这些文件。
#
:此为注释 – 将被 Git 忽略*.a
:忽略所有 .a
结尾的文件!lib.a
: 不忽略 lib.a
文件/TODO
:仅仅忽略项目根目录下的 TODO
文件,不包括 subdir/TODO
build/
: 忽略 build/
目录下的所有文件doc/*.txt
: 会忽略 doc/notes.txt
但不包括 doc/server/arch.txt
github上整理了一些常用需要的项目中需要忽略的文件配置,根据需要进行获取
https://github.com/github/gitignore.git
与 Xcode 相关的三个文件
Xcode.gitignore
忽略 Xcode
配置信息,如操作记录,默认打开窗口等
其他两个在 Xcode.gitignore
基础上针对不同的语言进行忽略
将这些文件重写命名为 .gittignore
$ mv Swift.gitignore .gittignore
macOS下默认是\#!/bin/bash
:
$ echo "function gi() { curl -L -s https://www.gitignore.io/api/\$@ ;}" >> ~/.bash_profile && source ~/.bash_profile
如果是 #!/bin/zsh
$ echo "function gi() { curl -L -s https://www.gitignore.io/api/\$@ ;}" >> ~/.zshrc && source ~/.zshrc
在当前终端目录下
$ gi swift > .gitignore
就会针对 Swifit 类型的工程创建 .gitignore
文件。
并不适合阅读的个人文档。
先看图:
sourceTree 中 revert 译为提交回滚
,作用为忽略你指定的版本,然后提交一个新的版本。新的版本中已近删除了你所指定的版本。
reset 为 重置到这次提交,将内容重置到指定的版本。git reset
命令后面是需要加2种参数的:–-hard
和 –-soft
。这条命令默认情况下是 -–soft
。
执行上述命令时,这该条commit号之 后(时间作为参考点)的所有commit的修改都会退回到git缓冲区中。使用git status
命令可以在缓冲区中看到这些修改。而如果加上-–hard
参数,则缓冲区中不会存储这些修改,git会直接丢弃这部分内容。可以使用 git push origin HEAD --force
强制将分区内容推送到远程服务器。
默认参数 -soft
,所有commit的修改都会退回到git缓冲区
参数--hard
,所有commit的修改直接丢弃
$ git reset --hard HEAD^ 回退到上个版本
$ git reset --hard commit_id 退到/进到 指定commit_id
推送到远程
$ git push origin HEAD --force
当你回滚之后,又后悔了,想恢复到新的版本怎么办?
用git reflog
打印你记录你的每一次操作记录
$ git reflog
输出:
c7edbfe HEAD@{0}: reset: moving to c7edbfefab1bdbef6cb60d2a7bb97aa80f022687
470e9c2 HEAD@{1}: reset: moving to 470e9c2
b45959e HEAD@{2}: revert: Revert "add img"
470e9c2 HEAD@{3}: reset: moving to 470e9c2
2c26183 HEAD@{4}: reset: moving to 2c26183
0f67bb7 HEAD@{5}: revert: Revert "add img"
找到你操作的id如:b45959e
,就可以回退到这个版本
$ git reset --hard b45959e
随便整理的一些自用的Git指令
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]
添加标签 在当前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
正所谓前人栽树,后人乘凉。
感谢Huxpro提供的博客模板
从 Jekyll 到 GitHub Pages 中间踩了许多坑,终于把我的个人博客BY Blog搭建出来了。。。
本教程针对的是不懂技术又想搭建个人博客的小白,操作简单暴力且快速。当然懂技术那就更好了。
看看看博客的主页样式:
在手机上的布局:
废话不多说了,开始进入正文。
我采用的搭建博客的方式是使用 GitHub Pages + jekyll 的方式。
要使用 GitHub Pages,首先你要注册一个GitHub账号,GitHub 是全球最大的同性交友网站(吐槽下程序员~),你值得拥有。
注册完成后搜索 qiubaiying.github.io
进入我的仓库
点击右上角的 Fork 将我的仓库拉倒你的账号下
稍等一下,点击刷新,你会看到Fork了成功的页面
点击settings进入设置
你的Github账号名.github.io
,然后 Rename
这时你在在浏览器中输入 你的Github账号名.github.io
例如:baiyingqiu.github.io
你将会看到如下界面
说明已经成功一半了😀。。。当然,还需要修改博客的配置才能变成你的博客。
若是出现
则需要 检查一下你的仓库名是否正确
修改Blog前我们来看看Jekyll 网站的基础结构,当然我们的网站比这个复杂。
1 | ├── _config.yml |
很复杂看不懂是不是,不要紧,你只要记住其中几个OK了
_config.yml
全局配置文件_posts
放置博客文章的文件夹img
存放图片的文件夹其他的想继续深究可以看这里
来到你的仓库,找到_config.yml
文件,这是网站的全局配置文件。
点击修改
然后编辑_config.yml
的内容
接下来我们来详细说说以下配置文件的内容:
1 | # Site settings |
1 | # Sidebar settings |
展示你的其他社交平台
在下面你的社交账号的用户名就可以了,若没有可不用填
1 | # SNS settings |
新加入了简书,jianshu_id
在你打开你的简书主页后的地址如:http://www.jianshu.com/u/e71990ada2fd
中,后面这一串数字:e71990ada2fd
博客中使用的是 Disqus 评论系统,在 官网 注册帐号后,按下面的步骤简单的配置即可:
进入 设置页面 配置个人信息
找到 Username
这个 Username 就是我们 _config.yml
中 disqus_username
1 | # Disqus settings(https://disqus.com/) |
很对人反映 Disqus 评论插件加载不出来,因为 Disqus 在国内加载缓慢,所以我新集成了 Gitalk 评论插件(感谢@FeDemo的推荐),喜欢折腾的朋友可以看这篇:《为博客添加 Gitalk 评论插件》。 我已经在
_config.yml
配置就好了,只需要填写参数可以了。
集成了 Baidu Analytics 和 Google Analytics,到各个网站注册拿到track_id替换下面的就可以了
这是我的 Google Analytics
不要使用我的track_id😂。。。
若不想启用统计,直接删除或注释掉就可以了
1 | # Analytics settings |
1 | friends: [ |
讲网页拉倒底部,点击 Commit changes
提交保存
再次进入你的主页,
恭喜你,你的个人博客搭建完成了😀。
利用 Github网站 ,我们可以不用学习git,就可以轻松管理自己的博客
对于轻车熟路的程序猿来说,使用git管理会更加方便。。。
文章统一放在网站根目录下的 _posts
的文件夹中。
创建一个文件
在下面写文章,和标题,还能实时预览,最后提交保存就能看到自己的新文章了。
每一篇文章文件命名采用的是2017-02-04-Hello-2017.md
时间+标题的形式,空格用-
替换连接。
文件的格式是 .md
的 MarkDown 文件。
我们的博客文章格式采用是 MarkDown+ YAML 的方式。
YAML 就是我们配置 _config
文件用的语言。
MarkDown 是一种轻量级的「标记语言」,很简单。花半个小时看一下就能熟练使用了
大概就是这么一个结构。
1 | --- |
按格式创建文章后,提交保存。进入你的博客主页,新的文章将会出现在你的主页上.
到这里,恭喜你!
你已经成功搭建了自己的个人博客以及学会在博客上撰写文字的技能了(是不是有点小兴奋🙈)。
在首页可以看到这些特色标签,当你的文章出现相同标签(默认相同的标签数量大于1),才会自动生成。
所以当你只放一篇文章的时候是不会出现标签的。
建站的初期,博客比较少,若你想直接在首页生成比较多的标签。你可以在 _congfig.yml
中找到这段:
1 | # Featured Tags |
将其修改为featured-condition-size: 0
, 这样只有一个标签时也会出现在首页了。
相反,当你博客比较多,标签也很多时,这时你就需要改回 1
甚至是 2
了。
搭建好博客之后 你可能不想直接使用 baiyingqiu.github.io 这么长的博客域名吧, 想换成想 qiubaiying.top 这样简短的域名。那我们开始吧!
首先,你必须购买一个自己的域名。
我是在阿里云购买的域名
用阿里云 app也可以注册域名,域名的价格根据后缀的不同和域名的长度而分,比如我这个 qiubaiying.top
的域名第一年才只要4元~
域名尽量选择短一点比较好记住,注意,不能选择中文域名,比如 张三.top
,GitHub Pages 无法处理中文域名,会导致你的域名在你的主页上使用。
注册的步骤就不在介绍了
注册好域名后,需要将域名解析到你的博客上
管理控制台 → 域名与网站(万网) → 域名
选择你注册好的域名,点击解析
添加解析
分别添加两个A
记录类型,
一个主机记录为 www
,代表可以解析 www.qiubaiying.top
的域名
另一个为 @
, 代表 qiubaiying.top
记录值就是我们博客的IP地址,是 GitHub Pagas 在美国的服务器的地址 151.101.100.133
可以通过 这个网站 或者直接在终端输入ping 你的地址
,查看博客的IP
ping qiubaiying.github.io
细心地你会发现所有人的博客都解析到 151.101.100.133
这个IP。
然后 GitHub Pages 再通过 CNAME记录 跳转到你的主页上。
最后一步,只需要修改 我们github仓库下的 CNAME 文件。
选择 CNAME 文件
使用的注册的域名进行替换,然后提交保存
这时,输入你自己的域名,就可以解析到你的主页了。
大功告成!
若你对博客模板进行修改,你就要看看 Jekyll 的开发文档,是中文文档哦,对英语一般的朋友简直是福利啊(比如说我😀)。
还要学习 Git 和 GitHub 的工作机制了及使用。
你可以先看看这个git教程,对git有个初步的了解后,那么相信你就能将自己图片传到GitHub仓库上,或者可以说掌握了 使用git管理自己的GitHub仓库 的技能呢。
对于轻车熟路的程序猿来说,这篇教程就算就结束了,因为下面的内容对于你们来说 so eazy~
但相信很多小白都一脸懵逼,那我们继续👇。
GithHub Desktop 是 GithHub 推出的一款管理GitHub仓库的桌面软件,换句话说就是将你在Github上的文件同步到本地电脑上,并将修改后的文件同步到Github远程仓库。
点击图片进入下载页面,选择对应的平台进行下载
下面以Mac平台为例:
将下载好的文件解压,将这只小猫拖到应用程序文件夹中
就可以在Launchpad找到这只小猫咪~
点开应用,会弹出登录框,
输入你的GitHub账号和密码进行登录
登录后关闭窗口
然后返回引导窗,一直按 Continue 继续
Continue
还是Continue~
进入主界面,先 右键Remve 删除这个用户指导,贼烦~
选择你的仓库克隆到本地
现在文件夹中打开
打开后你会的发现文件结构和你在Github上的一模一样~
你最先关心的可能是你的头像~在img文件夹中把替换我的头像就好了。
不仅是图片,所有在Github上的的操作都可以进行。
当你对仓库文件夹的文件下进行修改、添加或删除时,都可以在 GitHub Desktop 中看到
例如我在 img
中添加了一张图片 avatar-demo.png
添加了一张图片
就可以在看到GitHub Desktop显示了我的修改
保存修改只要按 Commit to master,然后可以写上你的修改说明
将修改同步到 GitHub 远程仓库上只需要一步:点击右上角的同步按钮
打开你的GitHub上的仓库,你就可以看到已经和本地同步了
可以看到你提交的详情: add img
这样,你已经能轻松管理自己的博客了。
想上传头像,背景,或者是删掉你不要的图片(我的头像😏)已经是 so eazy了吧~
你在 GitHub 网站上进行 Commit 操作后,需要在GitHub Desktop上按一下 同步按键 才能同步网站上的修改到你的本地。
修改个人介绍需要修改根目录下的 about.html
文件
看不懂 HTML 标签?没关系,对照着修改就好了~ 还有注意这个有中英介绍
最近有很多人给我提问题,我这边总结一下
刷新几遍浏览器就好了~
不行的话,先清除浏览器缓存再试试。
清除浏览器缓存就OK~
直接在评论中提出来或私信我,我会一一替大家解决的😀
最近有人往我的远程仓库不停的 push,一天连收几十封邮件!例如像这样的
原因大多是直接Clone了我的仓库到本地,没有删除我的远程仓库地址,添加完自己的仓库地址后,一口气推送到所有远程仓库(包括我的😂)~
打扰了我的工作和生活~
所以,请不要往我的仓库上推送分支!
我发现一个问题是,很多人每次修改博客的内容都commit一次到远程仓库,然后再查看修改结果,这样效率非常低!
有心的同学在 jekyll官网 就会发现 jekyll
的 提供的实例代码。
1 | ~ $ gem install jekyll bundler |
这段命令创建了一个默认的 jekll
网站,然后在本机的 4000 窗口展示。聪明的你应该发现怎么做了吧~
安装 jekyll
和 jekyll bundler
1 | $ gem install jekyll |
进入你的 Blog 所在目录,然后创建本地服务器
1 | $ jekyll s |
然后会显示
1 | Auto-regeneration: enabled for '/Users/baiying/Blog' |
你就可以在 http://127.0.0.1:4000/ 看到你的博客,你对本地博客的修改都会在这个地址进行显示,这大大提高了对博客的配置效率。
使用ctrl+c
就可以停止 serve
若本教程顺利帮你搭建了自己的个人博客,请不要 害羞,给我的 github仓库 点个 star 吧!
因为最近发现 Fork 将近破百,加上直接 Clone 仓库的,保守估计已经帮助上百人成功的搭建了自己的博客,可是 Star 却仅仅只有 12!可能还是做的不够好吧!现在已经破百了,感谢大家的Star!
心满意足!
要修改如图所示的网站 icon:
在博客 img
目录下找到并替换 favicon.ico
这个图标即可,图标尺寸为32x32
。
最近有不少小伙伴私信我:如何修改主页的座右铭?
就是这个:
很简单,找到博客目录下的 index.html 文件,修改这句话就可以了。
博客的文章用的是 MarkDown 格式,如果没用过 MarkDown 真的 强烈推荐 花半个小时学习一下。
MarkDown 中添加图片的形式是 :![](图片的URL)
例如:
![MarkDown示例图片](http://upload-images.jianshu.io/upload_images/2178672-eb2effd6b942a500.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
就会显示下面这张图片
https://ws3.sinaimg.cn/large/006tNc79gy1fj9xhjzobbj30yg0my75z.jpg
就是这张图片的URL,我们可以在浏览器输入这个URL找到或下载这张图片。
所以,要在 MacDown 中插入图片,这张图片就需要上传到图床(网上),然后在引
用这张图片的URL。
Mac 上的图床神器:iPic
直接在App Store上下载,谁用谁知道!
使用方法很简单,直接拖动图片到 P 图标上,或者选中图片按快捷键 ⌘+U
,就能请示上传。
上传成功就能直接粘贴图片的URL。
用 iPic 上传图片后,获取URL插入文章中就可以了。
MacDown:可能是Mac上最好的MacDown编辑器了
对于我们的博客来说,图片越大,加载速度越慢。
不信你用手机打开你的博客试试~
所以有必要对我们上传到博客网站中的图片:指的是你的头像,首页背景图片,文章背景图片等。对于博客文章中插入的图片,其实也可以压缩了再上传。
对博客中的所有图片进行压缩:
看看压缩结果,最高的一张压缩了78.7%,这简直是太可怕了!
好了,现在个人博客的加载速度估计要起飞了~
我在博客中的文章,你们可以保留,让更多需要帮助人的看到,当然也可以删除。
但是,我发现居然有人把文章的作者改成了自己,然后当成自己的文章放在自己的博客上,这就令人感到气愤了。
比如说向我请教问题的这位:
我在博客中的每篇文章都是我一字一句敲出来的,转载的文章我也注明了出处,表示对原作者的尊重。同时也希望大家都能尊重我的付出。
谢谢~
有关对 ReactiveCocoa 的看法可以看一下唐巧的这篇ReactiveCocoa 讨论会
ReactiveCocoa(简称为RAC),是由Github开源的一个应用于iOS和OS开发的新框架,Cocoa是苹果整套框架的简称,因此很多苹果框架喜欢以Cocoa结尾。
在我们iOS开发过程中,当某些事件响应的时候,需要处理某些业务逻辑,这些事件都用不同的方式来处理。
比如按钮的点击使用action,ScrollView滚动使用delegate,属性值改变使用KVO等系统提供的方式。其实这些事件,都可以通过RAC处理
ReactiveCocoa为事件提供了很多处理方法,而且利用RAC处理事件很方便,可以把要处理的事情,和监听的事情的代码放在一起,这样非常方便我们管理,就不需要跳到对应的方法里。
非常符合我们开发中高聚合,低耦合的思想。
在开发中我们也不能太依赖于某个框架,否则这个框架不更新了,导致项目后期没办法维护,比如之前Facebook提供的 Three20
框架,在当时也是神器,但是后来不更新了,也就没什么人用了。因此我感觉学习一个框架,还是有必要了解它的编程思想。
先简单介绍下目前咱们已知的编程思想:
响应式编程思想:不需要考虑调用顺序,只需要知道考虑结果,类似于蝴蝶效应,产生一个事件,会影响很多东西,这些事件像流一样的传播出去,然后影响结果,借用面向对象的一句话,万物皆是流。
代表
:KVO
链式编程 是将多个操作(多行代码)通过点号(.)链接在一起成为一句代码,使代码可读性好。如:
1 | make.add(1).add(2).sub(5).muilt(-4).divide(4); |
特点
:方法的返回值是block,block必须有返回值(本身对象),block参数(需要操作的值)
代表
:masonry框架。
实现
:模仿masonry,写一个加法计算器,练习链式编程思想。
NSObject+Caculator.h
1 | # import <Foundation/Foundation.h> |
NSObject+Caculator.m
1 | @implementation NSObject (Caculator) |
CaculatorMaker.h
1 | # import <Foundation/Foundation.h> |
CaculatorMaker.m
1 | # import "CaculatorMaker.h" |
使用:
1 | int result = [NSObject makeCaculators:^(CaculatorMaker *make) { |
函数式编程思想:是把操作尽量写成一系列嵌套的函数或者方法调用。
特点
:每个方法必须有返回值(本身对象),把函数或者Block当做参数,block参数(需要操作的值)block返回值(操作结果)
代表
:ReactiveCocoa
实现
:用函数式编程实现,写一个加法计算器,并且加法计算器自带判断是否等于某个值.
1 | Calculator *caculator = [[Calculator alloc] init]; |
Calculator.h
1 | #import <Foundation/Foundation.h> |
Calculator.m
1 | #import "Calculator.h" |
ReactiveCocoa 结合了这两种种编程风格:
函数式编程(Functional Programming)
响应式编程(Reactive Programming)
所以,你可能听说过 ReactiveCocoa 被描述为函数响应式编程(FRP)框架。
以后使用RAC解决问题,就不需要考虑调用顺序,直接考虑结果,把每一次操作都写成一系列嵌套的方法中,使代码高聚合,方便管理。
ReactiveCocoa的GitHub地址
ReactiveCocoa 2.5版本以后改用了Swift,所以Objective-C项目需要导入2.5版本
CocoaPods
集成:
1 | platform :ios, '8.0' |
PS:新版本的CocoaPods
需要加入
1 | target 'YouProjectName' do |
这句话来限定项目,否则导入失败。
Swift项目导入2.5后的版本
1 | platform :ios, '8.0' |
使用时在全局头文件导入头文件即可
PrefixHeader.pch
1 | #ifndef PrefixHeader_pch |
信号类,一般表示将来有数据传递,只要有数据改变,信号内部接收到数据,就会马上发出数据。
注意:
使用:
1 | // RACSignal使用步骤: |
表示订阅者的意思,用于发送信号,这是一个协议,不是一个类,只要遵守这个协议,并且实现方法才能成为订阅者。通过create创建的信号,都有一个订阅者,帮助他发送数据。
用于取消订阅或者清理资源,当信号发送完成或者发送错误的时候,就会自动触发它。
使用场景:不想监听某个信号时,可以通过它主动取消订阅信号。
RACSubject:信号提供者,自己可以充当信号,又能发送信号。
使用场景:通常用来代替代理,有了它,就不必要定义代理了。
重复提供信号类,RACSubject的子类。
RACReplaySubject
与RACSubject
区别:
RACReplaySubject
可以先发送信号,在订阅信号,RACSubject
就不可以。
使用场景一:如果一个信号每被订阅一次,就需要把之前的值重复发送一遍,使用重复提供信号类。
使用场景二:可以设置capacity数量来限制缓存的value的数量,即只缓充最新的几个值。
ACSubject 和 RACReplaySubject 简单使用:
ACSubject
1 | // RACSubject使用步骤 |
1 | // RACReplaySubject使用步骤: |
RACSubject替换代理(与block类似)
1 | // 需求: |
元组类,类似NSArray,用来包装值.(
@[key, value]
)
RAC中的集合类,用于代替NSArray,NSDictionary,可以使用它来快速遍历数组和字典。
使用场景:字典转模型
1 | // 1.遍历数组 |
RAC中用于处理事件的类,可以把事件如何处理,事件中的数据如何传递,包装到这个类中,他可以很方便的监控事件的执行过程。
一、RACCommand使用步骤:
执行命令 - (RACSignal *)execute:(id)input
二、RACCommand使用注意:
signalBlock必须要返回一个信号,不能传nil.
RACCommand需要被强引用,否则接收不到RACCommand中的信号,因此RACCommand中的信号是延迟发送的。
三、RACCommand设计思想:
内部signalBlock为什么要返回一个信号,这个信号有什么用。
在RAC开发中,通常会把网络请求封装到RACCommand,直接执行某个RACCommand就能发送请求。
当RACCommand内部请求到数据的时候,需要把请求的数据传递给外界,这时候就需要通过signalBlock返回的信号传递了。
四、如何拿到RACCommand中返回信号发出的数据。
RACCommand有个执行信号源executionSignals,这个是signal of signals(信号的信号),意思是信号发出的数据是信号,不是普通的类型。
订阅executionSignals就能拿到RACCommand中返回的信号,然后订阅signalBlock返回的信号,就能获取发出的值。
五、监听当前命令是否正在执行executing
六、使用场景,监听按钮点击,网络请求
使用:
1 | // 1.创建命令 |
用于当一个信号,被多次订阅时,为了保证创建信号时,避免多次调用创建信号中的block,造成副作用,可以使用这个类处理。
注意:RACMulticastConnection通过RACSignal的-publish或者-muticast:方法创建.
RACMulticastConnection使用步骤:
RACMulticastConnection底层原理:
需求:假设在一个信号中发送请求,每次订阅一次都会发送请求,这样就会导致多次请求。
解决:使用RACMulticastConnection就能解决.
问题:每次订阅一次都会发送请求
1 | // 创建请求信号 |
输出:
1 | 2016-12-28 11:37:04.397 ReactiveCacoa[1505:340573] 发送请求 |
可以发现每次订阅都会重新发送请求.
下面我们使用RACMulticastConnection:
1 | RACSignal *signal = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) { |
输出:
1 | 2016-12-28 11:37:04.399 ReactiveCacoa[1505:340573] 发送请求 |
RAC中的队列,用GCD封装的。
表⽰stream不包含有意义的值,也就是看到这个,可以直接理解为nil.
把数据包装成信号事件(signal event)。它主要通过RACSignal的-materialize来使用,然并卵。
rac_signalForSelector:
rac_signalForSelector:
直接监听 Selector
事件的调用
应用场景:监听 RedViewController
中按钮的点击事件 btnTap:
跳转到RedViewController
前,先使用rac_signalForSelector
订阅rvc中的 btnTap:
点击事件
1 | // 使用segue跳转 |
RedViewController.m
中的按钮事件
1 | - (IBAction)btnTap:(id)sender { |
rac_valuesForKeyPath:
1 | // KVO |
rac_addObserverForName
1 | // 原生的订阅通知 |
rac_signalForControlEvents:
1 | // 监听 btn 的 UIControlEventTouchUpInside 点击事件 |
rac_textSignal
1 | [[self.textField rac_textSignal] subscribeNext:^(id x) { |
rac_liftSelector:
1 | - (void)viewDidLoad { |
替换KVO
和 监听文本框文字改变
方法在创建监听方法时就会执行一次。
1 | 2016-12-28 16:53:50.746 ReactiveCacoa[4956:1246592] slider value Change:0.5 |
使用rac_liftSelector
时 @selector(updateWithR1:R2:)
中的方 参数个数 要与 signal个数 相同,否则会被断言Crash!
定时器的使用是软件开发基础技能,用于延时执行或重复执行某些方法。
我相信大部分人接触iOS的定时器都是从这段代码开始的:
1 | [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(action:) userInfo:nil repeats:YES] |
但是你真的会用吗?
首先来介绍iOS中的定时器
iOS中的定时器大致分为这几类:
NSTime定时器是我们比较常使用的定时器,比较常使用的方法有两种:
1 | + (NSTimer *)timerWithTimeInterval:(NSTimeInterval)ti target:(id)aTarget selector:(SEL)aSelector userInfo:(nullable id)userInfo repeats:(BOOL)yesOrNo; |
这两种方法都是创建一个定时器,区别是用timerWithTimeInterval:
方法创建的定时器需要手动加入RunLoop中。
1 | // 创建NSTimer对象 |
需要注意的是: UIScrollView
滑动时执行的是 UITrackingRunLoopMode
,NSDefaultRunLoopMode
被挂起,会导致定时器失效,等恢复为滑动结束时才恢复定时器。其原因可以查看我这篇《Objective-C RunLoop 详解》中的 “RunLoop 的 Mode“章节,有详细的介绍。
举个例子:
1 | - (void)startTimer{ |
将timer
添加到NSDefaultRunLoopMode中,没0.5秒打印一次,然后滑动UIScrollView
.
打印台输出:
可以看出在滑动UIScrollView
时,定时器被暂停了。
所以如果需要定时器在 UIScrollView
拖动时也不影响的话,有两种解决方法
UITrackingRunLoopMode
和 NSDefaultRunLoopMode
中1 | [[NSRunLoop mainRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode]; |
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 | // 停止定时器 |
实际开发中,我们会将NSTimer
对象设置为属性,这样方便释放。
iOS10.0 推出了两个新的API,与上面的方法相比,selector
换成Block回调以、减少传入的参数(那几个参数真是鸡肋)。不过开发中一般需要适配低版本,还是尽量使用上面的方法吧。
1 | + (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)); |
###特点
必须加入Runloop
上面不管使用哪种方法,实际最后都会加入RunLoop中执行,区别就在于是否手动加入而已。
存在延迟
不管是一次性的还是周期性的timer的实际触发事件的时间,都会与所加入的RunLoop和RunLoop Mode有关,如果此RunLoop正在执行一个连续性的运算,timer就会被延时出发。重复性的timer遇到这种情况,如果延迟超过了一个周期,则会在延时结束后立刻执行,并按照之前指定的周期继续执行,这个延迟时间大概为50-100毫秒.
所以NSTimer不是绝对准确的,而且中间耗时或阻塞错过下一个点,那么下一个点就pass过去了.
UIScrollView滑动会暂停计时
添加到NSDefaultRunLoopMode
的 timer
在 UIScrollView
滑动时会暂停,若不想被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 | @property (nonatomic, strong) CADisplayLink *displayLink; |
释放方法:
1 | [self.displayLink invalidate]; |
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 |
|
// 计时时间
@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
在上面代码中,我们设置了一个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 就需要在 后台常驻。
最后总结一下:
NSTimer 使用简单方便,但是应用条件有限。
CADisplayLink 刷新频率与屏幕帧数相同,用于绘制动画。具体使用可看我封装好的一个 水波纹动画。
GCD定时器 精度高,可控性强,使用稍复杂。
对于习惯使用Storyboard的人来说,设置圆角、描边是一件比较蛋疼的事,因为苹果没有在xcode的Interface Builder上直接提供修改控件的圆角,边框设置。
我们来说说如何对某个控件进行圆角、描边处理:
对于一个初学者来说,如果要进行某个控件的圆角、描边设置,就要从Storyboard关联出属性,然后再对属性进行代码处理。
如下代码:
1 | self.myButton.layer.cornerRadius = 20; |
这样不仅需要Storyboard关联出属性,还要写一堆代码对属性进行设置,不得不说实在麻烦~
更聪明的做法是使用Storyboard提供的Runtime Attributes为控件添加圆角描边。
选中控件,然后在Runtime Attributes框中输入对应的Key
与Type
与Value
,这样程序在运行时就会通过KVC为你的控件属性进行赋值。(不仅仅是圆角、描边~)
如下图
设置圆角、描边的Key为:
1 | layer.borderWidth |
我这次在测试时,
这样做不用关联出属性,但是需要输入大串字符串,也是不够方便。
创建UIView的分类,使用IBInspectable
+ IB_DESIGNABLE
关键字:
1 | #import <UIKit/UIKit.h> |
1 | #import "UIView+Inspectable.h" |
附上:GitHub地址
直接将这两个文件拖入项目中即可使用,在右边栏将会显示圆角和描边的属性设置
如图:
直接使用的话只有在运行时才能看到效果,
例如要实时显示一个UIBUtton
圆角、描边效果,需要创建一个类继承UIButton
1 | #import <UIKit/UIKit.h> |
1 | #import "myButton.h" |
只要将button的Class选择该空白类即可
关于IBInspectable
与IB_DESIGNABLE
的使用详情可以参考这篇文章《谈不完美的IBDesignable/IBInspectable可视化效果编程》
JSON转模型是我们做iOS开发的基础技能,本文将通过YYModel这个框架安全快速的完成JSON到模型的转换,其中还会介绍到一款好用的插件ESJsonFormat。
创建模型类我们可以通过ESJsonFormat这款插件快速完成。
使用方法:
将光标移动到代码行中 如下图的13行
然后点击Window
->ESJsonFormat
->Input JSON Window
调出窗口
在窗口中输入你要解析的JSON文本,如下图:
按Enter
继续,然后神奇的一幕发生了
看到在.h中 所有的属性自动为你填上,而且帮你选好了类型
.m 也为你声明了list
中成员的类型,不过这里需要稍作修改,因为我们需要用到YYModel进行解析,所以方法名改成modelContainerPropertyGenericClass
1 | + (NSDictionary *)modelContainerPropertyGenericClass { |
还有问题就是属性中出现关键字id
,我们需要将id改为teacherId
然后在.m的implementation
中声明,将字典的的id
1 | + (NSDictionary *)modelCustomPropertyMapper { |
这样,模型的创建就完成了,剩下的就是用YYModel进行解析了
解析很简单,就只需要一句话
1 | // 将 JSON (NSData,NSString,NSDictionary) 转换为 Model: |
到此,简便快速的完成了JSON到模型的转换。