Jekyll2017-01-01T18:07:30+08:00http://www.dechao.net/http://www.dechao.net//吵了个吵日拱一卒,功不唐捐。可以十年不将军,不可一日不拱卒。idechaoidechao@163.comiOS 面试题2016-12-27T00:00:00+08:002016-12-27T00:00:00+08:00http://www.dechao.net/http://www.dechao.net/blog/interview<section id="table-of-contents" class="toc">
<header>
<h1>Features</h1>
</header>
<div id="drawer">
<ul id="markdown-toc">
<li><a href="#runtime--weak-" id="markdown-toc-runtime--weak-">runtime 如何实现 weak 属性</a> <ul>
<li><a href="#weak" id="markdown-toc-weak">weak特点</a></li>
<li><a href="#runtime-weaknil" id="markdown-toc-runtime-weaknil">runtime 实现weak自动置nil</a></li>
<li><a href="#section" id="markdown-toc-section">对象的内存销毁时间表</a></li>
<li><a href="#section-1" id="markdown-toc-section-1">验证</a></li>
</ul>
</li>
</ul>
</div>
</section>
<!-- /#table-of-contents -->
<h2 id="runtime--weak-">runtime 如何实现 weak 属性</h2>
<h3 id="weak">weak特点</h3>
<p>要实现 weak 属性,首先要搞清楚 weak 属性的特点:</p>
<blockquote>
<p>weak 此特质表明该属性定义了一种“非拥有关系” (nonowning relationship)。为这种属性设置新值时,设置方法既不保留新值,也不释放旧值。此特质同 assign 类似, 然而在属性所指的对象遭到摧毁时,属性值也会清空(nil out)。</p>
</blockquote>
<h3 id="runtime-weaknil">runtime 实现weak自动置nil</h3>
<p>那么 runtime 如何实现 weak 变量的自动置nil?</p>
<blockquote>
<p>runtime 对注册的类, 会进行布局,对于 weak 对象会放入一个 hash 表中。 用 weak 指向的对象内存地址作为 key,当此对象的引用计数为0的时候会 dealloc,假如 weak 指向的对象内存地址是a,那么就会以a为键, 在这个 weak 表中搜索,找到所有以a为键的 weak 对象,从而设置为 nil。</p>
</blockquote>
<h3 id="section">对象的内存销毁时间表</h3>
<p>对象的内存销毁时间表,分四个步骤:</p>
<ol>
<li>调用 -release :引用计数变为零
<ul>
<li>对象正在被销毁,生命周期即将结束.</li>
<li>不能再有新的 __weak 弱引用, 否则将指向 nil.</li>
<li>调用 [self dealloc]</li>
</ul>
</li>
<li>子类 调用 -dealloc
<ul>
<li>继承关系中最底层的子类 在调用 -dealloc</li>
<li>如果是 MRC 代码 则会手动释放实例变量们(iVars)</li>
<li>继承关系中每一层的父类 都在调用 -dealloc</li>
</ul>
</li>
<li>NSObject 调 -dealloc
<ul>
<li>只做一件事:调用 Objective-C runtime 中的 object_dispose() 方法</li>
</ul>
</li>
<li>调用 object_dispose()
<ul>
<li>为 C++ 的实例变量们(iVars)调用 destructors</li>
<li>为 ARC 状态下的 实例变量们(iVars) 调用 -release</li>
<li>解除所有使用 runtime Associate方法关联的对象</li>
<li>解除所有 __weak 引用</li>
<li>调用 free()</li>
</ul>
</li>
</ol>
<h3 id="section-1">验证</h3>
<p>生成weak属性的对象, 并指向一个对象,当对象销毁之后,指向该对象的weak属性的对象也会随之销毁。</p>
<pre class="sunlight-highlight-objective-c">
__weak id weakObj1 = nil;
__weak id weakObj2 = nil;
{
id obj = [[NSObject alloc] init]; // 引用计数会增加,这里不会是weak的
weakObj1 = obj;
weakObj2 = obj;
NSLog(@"obj: %@", obj);
NSLog(@"weakObj1: %@", weakObj1);
NSLog(@"weakObj2: %@", weakObj2);
}
NSLog(@"weakObj1: %@", weakObj1);
NSLog(@"weakObj2: %@", weakObj2);
</pre>
<p>输出为</p>
<pre class="sunlight-highlight-objective-c">
obj: <NSObject: 0x60800000bdc0>
weakObj1: <NSObject: 0x60800000bdc0>
weakObj2: <NSObject: 0x60800000bdc0>
weakObj1: (null)
weakObj2: (null)
</pre>
<p>因为 weak 本身是非拥有的,所有如果是把weakObj1设置为nil的话,是不会对其他的造成影响的。</p>
<p>参考:</p>
<p><a href="https://github.com/ChenYilong/iOSInterviewQuestions/blob/master/01%E3%80%8A%E6%8B%9B%E8%81%98%E4%B8%80%E4%B8%AA%E9%9D%A0%E8%B0%B1%E7%9A%84iOS%E3%80%8B%E9%9D%A2%E8%AF%95%E9%A2%98%E5%8F%82%E8%80%83%E7%AD%94%E6%A1%88/%E3%80%8A%E6%8B%9B%E8%81%98%E4%B8%80%E4%B8%AA%E9%9D%A0%E8%B0%B1%E7%9A%84iOS%E3%80%8B%E9%9D%A2%E8%AF%95%E9%A2%98%E5%8F%82%E8%80%83%E7%AD%94%E6%A1%88%EF%BC%88%E4%B8%8A%EF%BC%89.md" style="color:blue">iOSInterviewQuestions</a></p>idechaoidechao@163.comFeaturesGit的基本使用2016-12-27T00:00:00+08:002016-12-27T00:00:00+08:00http://www.dechao.net/http://www.dechao.net/blog/git<section id="table-of-contents" class="toc">
<header>
<h1 style="color:#0087A6">Features</h1>
</header>
<div class="drawer" id="drawer">
<ul id="markdown-toc">
<li><a href="#font-classh2gitfont" id="markdown-toc-font-classh2gitfont"><font class="h2">Git简介</font></a></li>
<li><a href="#font-classh2gitfont-1" id="markdown-toc-font-classh2gitfont-1"><font class="h2">Git仓库</font></a> <ul>
<li><a href="#font-classh3font" id="markdown-toc-font-classh3font"><font class="h3">创建仓库</font></a></li>
<li><a href="#font-classh3font-1" id="markdown-toc-font-classh3font-1"><font class="h3">克隆仓库</font></a></li>
<li><a href="#font-classh3font-2" id="markdown-toc-font-classh3font-2"><font class="h3">远端仓库</font></a></li>
<li><a href="#font-classh3font-3" id="markdown-toc-font-classh3font-3"><font class="h3">添加远程仓库</font></a></li>
</ul>
</li>
<li><a href="#font-classh2font" id="markdown-toc-font-classh2font"><font class="h2">文件管理</font></a> <ul>
<li><a href="#font-classh3font-4" id="markdown-toc-font-classh3font-4"><font class="h3">添加提交文件</font></a></li>
<li><a href="#font-classh3font-5" id="markdown-toc-font-classh3font-5"><font class="h3">合并更新</font></a></li>
<li><a href="#font-classh3font-6" id="markdown-toc-font-classh3font-6"><font class="h3">查看状态</font></a></li>
<li><a href="#font-classh3font-7" id="markdown-toc-font-classh3font-7"><font class="h3">文件修改</font></a></li>
</ul>
</li>
<li><a href="#font-classh2font-1" id="markdown-toc-font-classh2font-1"><font class="h2">版本回退</font></a></li>
<li><a href="#font-classh2font-2" id="markdown-toc-font-classh2font-2"><font class="h2">分支管理</font></a> <ul>
<li><a href="#font-classh3font-8" id="markdown-toc-font-classh3font-8"><font class="h3">创建分支</font></a></li>
<li><a href="#font-classh3font-9" id="markdown-toc-font-classh3font-9"><font class="h3">合并分支</font></a></li>
<li><a href="#font-classh3font-10" id="markdown-toc-font-classh3font-10"><font class="h3">删除分支</font></a></li>
<li><a href="#font-classh3font-11" id="markdown-toc-font-classh3font-11"><font class="h3">解决冲突</font></a></li>
<li><a href="#font-classh3tagfont" id="markdown-toc-font-classh3tagfont"><font class="h3">标签tag</font></a></li>
</ul>
</li>
<li><a href="#font-classh2font-3" id="markdown-toc-font-classh2font-3"><font class="h2">其他</font></a> <ul>
<li><a href="#font-classh3font-12" id="markdown-toc-font-classh3font-12"><font class="h3">设置忽略文件</font></a></li>
<li><a href="#font-classh3font-13" id="markdown-toc-font-classh3font-13"><font class="h3">设置别名</font></a></li>
</ul>
</li>
</ul>
</div>
</section>
<!-- /#table-of-contents -->
<h2 id="font-classh2gitfont"><font class="h2">Git简介</font></h2>
<p>Git是最优秀的分布式版本控制系统。</p>
<p>每个人都可以从中心版本库中克隆到本地,以多分支的形式进行各种合作开发。</p>
<p>使用<code class="highlighter-rouge">git help</code>查看基本的帮助文档,并在使用前配置好账户</p>
<pre class="sunlight-highlight-objective-c">
git config --global user.name 'Your Name'
git config --global user.email 'Your Email'
</pre>
<h2 id="font-classh2gitfont-1"><font class="h2">Git仓库</font></h2>
<p>有两种方式获取到Git仓库,一是从本地文件中自己创建,再者,从服务器拉取一个Git仓库。</p>
<h3 id="font-classh3font"><font class="h3">创建仓库</font></h3>
<p>Git仓库是存放数据快照的地方,可以根据这些快照对数据进行修改、还原等操作。我们可以很轻松的创建一个仓库。</p>
<pre class="sunlight-highlight-objective-c">
git init
</pre>
<p>在当前目录下会生成一个.git子目录,就是Git仓库。</p>
<h3 id="font-classh3font-1"><font class="h3">克隆仓库</font></h3>
<p>通常我们需要和别人一起开发,或是会查看一些开源的代码,这时候我们就需要克隆一个已有的仓库到本地。</p>
<pre class="sunlight-highlight-objective-c">
git clone [url]
</pre>
<blockquote>
<p>url地址一般有SSH和HTTPS的方式,使用HTTPS的话,每次进行一些操作,比如<code class="highlighter-rouge">pull</code>、<code class="highlighter-rouge">push</code>等都需要输入用户名和密码,但对于别人的项目一般都是使用这种方式,也就是我们只是查看项目的源码;使用SSH的话,就不需要每次输入密码了,前提就是配置好SSH Key。</p>
</blockquote>
<h3 id="font-classh3font-2"><font class="h3">远端仓库</font></h3>
<p>与远端仓库进行交互,首先需要生成一个SSH公钥,公钥默认在主目录下的<code class="highlighter-rouge">~/.ssh</code>目录。</p>
<pre class="sunlight-highlight-objective-c">
$ ls ~/.ssh
id_rsa id_rsa.pub known_hosts
</pre>
<p>用下面的命令生成:</p>
<pre class="sunlight-highlight-objective-c">
ssh-keygen -t rsa -C "your_email@example.com"
</pre>
<p>然后将<code class="highlighter-rouge">id_rsa.pub </code>的内容复制到远端仓库里就大功告成了。</p>
<h3 id="font-classh3font-3"><font class="h3">添加远程仓库</font></h3>
<p>在远端创建一个仓库之后,需要将本地的Git仓库提交上去。基本的创建方式为:</p>
<pre class="sunlight-highlight-objective-c">
cd /path/to/my/repo
git remote add origin [url] # origin url
git push -u origin --all # pushes up the repo and its refs for the first time
git push origin --tags # pushes up any tags
</pre>
<h2 id="font-classh2font"><font class="h2">文件管理</font></h2>
<h3 id="font-classh3font-4"><font class="h3">添加提交文件</font></h3>
<p>在git仓库目录下,执行<code class="highlighter-rouge">echo "love git" > readme.md</code>来创建一个文件,之后需要添加到仓库里:</p>
<pre class="sunlight-highlight-objective-c">
git add readme.md
</pre>
<p>与add相呼应的是commit,即提交修改。</p>
<pre class="sunlight-highlight-objective-c">
git commit -m "add readme.md"
</pre>
<p>通常情况下,实现某些功能的时候,会有很多文件的修改,一个个的add明显是费时费力的事情。使用点的形式来一次性的添加全部文件。</p>
<pre class="sunlight-highlight-objective-c">
git add .
# 或是
git add *
</pre>
<p>对于已经在仓库里的文件若是做了修改,可以将add和commit一起执行。</p>
<pre class="sunlight-highlight-objective-c">
git commit -am "modify readme.md"
</pre>
<p>有时候提交完之后,发现有几个文件没有提交,或是提交信息写错了,这时候可以运行带有<mark>--amend</mark>的命令:</p>
<pre class="sunlight-highlight-objective-c">
git commit --amend
</pre>
<h3 id="font-classh3font-5"><font class="h3">合并更新</font></h3>
<p>从远端拉取最新代码,并合并到分支中:</p>
<pre class="sunlight-highlight-objective-c">
git pull origin next:master # 取回origin主机的next分支,与本地的master分支合并
</pre>
<p>如果远程分支是与当前分支合并,则冒号后面的部分可以省略。</p>
<pre class="sunlight-highlight-objective-c">
git pull origin next
</pre>
<p><code class="highlighter-rouge">pull</code>的含义是先拉取更新,再与分支进行合并,是个自动的过程,也可手动实现:</p>
<pre class="sunlight-highlight-objective-c">
git fetch origin # 拉取更新
git merge origin/next # 合并更新
</pre>
<p>在某些场合,Git会自动在本地分支与远程分支之间,建立一种追踪关系(tracking)。比如,在git clone的时候,所有本地分支默认与远程主机的同名分支,建立追踪关系,也就是说,本地的master分支自动”追踪”origin/master分支。</p>
<pre class="sunlight-highlight-objective-c">
git branch --set-upstream master origin/next
</pre>
<p>如果当前分支与远程分支存在追踪关系,git pull就可以省略远程分支名。</p>
<pre class="sunlight-highlight-objective-c">
git pull origin
</pre>
<h3 id="font-classh3font-6"><font class="h3">查看状态</font></h3>
<p>查看当前分支的状态:</p>
<pre class="sunlight-highlight-objective-c">
git status
</pre>
<p>可以查看简易形式,添加-s参数</p>
<pre class="sunlight-highlight-objective-c">
git status -s
</pre>
<p>想要具体对比不同,需要使用diff命令。</p>
<pre class="sunlight-highlight-objective-c">
git diff
</pre>
<p>diff命令后面可以添加文件路径,查看具体某个文件的改动。</p>
<p>对于提交记录的查看,需要使用log。</p>
<pre class="sunlight-highlight-objective-c">
git log
</pre>
<p>log后面添加参数,会输出不同的log记录。比如:</p>
<ul>
<li>添加–oneline,会以简介的log记录输出;</li>
<li>添加–decorate参数,会显示指向这个提交的所有引用;</li>
<li>添加–graph参数会以图像的形式展示提交历史的分支结构等等。</li>
</ul>
<p>一个比较常用的选项是<mark>-p</mark>,用来显示每次提交的内容差异,比如再加上 -2 来仅显示最近两次提交:</p>
<pre class="sunlight-highlight-objective-c">
git log -p -2
</pre>
<h3 id="font-classh3font-7"><font class="h3">文件修改</font></h3>
<p>对于已经修改的文件,想要放弃当前的修改,还原为之前的状态:</p>
<pre class="sunlight-highlight-objective-c">
git checkout [file name]
</pre>
<p>而已经修改的文件,如果不是想放弃当前的修改,只是想暂存起来,去处理一些比较紧急的事,之后还是需要继续操作,那就需要stash命令:</p>
<pre class="sunlight-highlight-objective-c">
git stash
#处理事情
git stash pop
</pre>
<p>pop就相当于从储藏的堆栈中移除掉最后一次的stash操作。</p>
<h2 id="font-classh2font-1"><font class="h2">版本回退</font></h2>
<p>git reset是版本回退的基本命令,根据后面参数的不同,回退的形式也不同。</p>
<p>1、已经使用<code class="highlighter-rouge">git add</code>添加到仓库的文件,想要使得他退回到add之前:</p>
<pre class="sunlight-highlight-objective-c">
git reset HEAD [file name]
</pre>
<p>其中HEAD表示为当前的版本。</p>
<p>2、对于每次的log记录,想要回退到某一个log记录的时候,并且保留当前所做的修改:</p>
<pre class="sunlight-highlight-objective-c">
git reset [commit id]
</pre>
<p>3、对于每次的log记录,想要回退到某一个log记录的时候,并且放弃之后的修改:</p>
<pre class="sunlight-highlight-objective-c">
git reset --hard [commit id]
</pre>
<p>4、在简单的想要回退当上一个版本,无需查看commit id的形式:</p>
<pre class="sunlight-highlight-objective-c">
git reset --hard HEAD^
</pre>
<p>也可以在后面跟数字,表示之前的第几个:</p>
<pre class="sunlight-highlight-objective-c">
git reset --hard HEAD~5 # 表示之前第5个log
</pre>
<p>注:在zsh中使用<code class="highlighter-rouge">HEAD^</code>的形式可能不识别,需要对字符 ^ 进行转义:</p>
<pre class="sunlight-highlight-objective-c">
git reset --hard HEAD\^
</pre>
<p>5、有时候我们回退了,但是又想放弃这次回退,重新回到回退之前的状态,这时候就需要查看log记录,再根据log来确认想要回到那一条记录。</p>
<pre class="sunlight-highlight-objective-c">
git reflog
</pre>
<h2 id="font-classh2font-2"><font class="h2">分支管理</font></h2>
<p>Git分支是最吸引人的特性。而且也鼓励支持使用分支。</p>
<h3 id="font-classh3font-8"><font class="h3">创建分支</font></h3>
<p>创建一个新的分支并且切换到新分支上去:</p>
<pre class="sunlight-highlight-objective-c">
git checkout -b dev_branch
</pre>
<p>这条命令就相当于</p>
<pre class="sunlight-highlight-objective-c">
git branch dev_branch #创建分支,但没切到该分支
git checkout dev_branch #切到该分支
</pre>
<h3 id="font-classh3font-9"><font class="h3">合并分支</font></h3>
<p>基本的合并命令为merge:</p>
<pre class="sunlight-highlight-objective-c">
git merge dev_branch # 将dev_branch分支合并到当前分支
</pre>
<p>除了merge,还有一个合并的方式,为rebase:</p>
<pre class="sunlight-highlight-objective-c">
git rebase dev_branch
</pre>
<p>二者的区别在于merge会生成一个新的节点,而rebase则不会,在有特殊需求的情况下会使用rebase。</p>
<h3 id="font-classh3font-10"><font class="h3">删除分支</font></h3>
<p>删除一个分支:</p>
<pre class="sunlight-highlight-objective-c">
git branch -d dev_branch
</pre>
<p>如果分支还未合并,可能会删除不成功,可以强制删除:</p>
<pre class="sunlight-highlight-objective-c">
git branch -D dev_branch
</pre>
<h3 id="font-classh3font-11"><font class="h3">解决冲突</font></h3>
<p>在多人同时修改统一文件的时候,会造成某一位置的多次修改,就容易产生冲突 <code class="highlighter-rouge">CONFLICT</code>。</p>
<pre class="sunlight-highlight-objective-c">
<<<<<<< HEAD
# 本地的代码
=======
# 别人的代码
>>>>>>> [分支名]
</pre>
<p>根据实际的需求考虑那段代码是正确的,并删除这些多余的符号。</p>
<p>对于rebase过程中的冲突,解决完冲突之后,应按照下面的步骤来进行:</p>
<pre class="sunlight-highlight-objective-c">
git add . # 更新内容
git rebase --continue # git会继续应用(apply)余下的补丁
# 在任何时候,可以用--abort参数来终止rebase的行动,并且分支会回到rebase开始前的状态
git rebase --abort
</pre>
<h3 id="font-classh3tagfont"><font class="h3">标签tag</font></h3>
<p>对于某些重要的提交,或是到了开发的某一阶段,就需要打个标签做记录。一般情况推荐使用附注标签。</p>
<pre class="sunlight-highlight-objective-c">
git tag -a v1.1.0 -m "add tag" #创建标签
git push origin v1.1.0 #推送到远端仓库
git push origin --tags #推送所有标签
</pre>
<h2 id="font-classh2font-3"><font class="h2">其他</font></h2>
<h3 id="font-classh3font-12"><font class="h3">设置忽略文件</font></h3>
<p>有一些系统文件,隐藏文件,或是不必要的配置文件,没必要每次都跟着提交,就可以添加忽略。在仓库下面有个<code class="highlighter-rouge">.gitignore</code>文件,在里面添加想要忽略的文件,并将该忽略文件也提交到远端。</p>
<p>查看GitHub的忽略文件配置: <a href="https://github.com/github/gitignore" style="color:blue">gitignore</a></p>
<h3 id="font-classh3font-13"><font class="h3">设置别名</font></h3>
<p>经常纠结于过长的名字,还时不时的打错,那应该尝试一下设置别名。下面有一些比较好的例子:</p>
<pre class="sunlight-highlight-objective-c">
git config --global alias.co checkout
git config --global alias.br branch
git config --global alias.ci commit
git config --global alias.st status
</pre>
<p>比较喜欢的是一个日志的设置:</p>
<pre class="sunlight-highlight-objective-c">
git config --global alias.last 'log -1 HEAD' #查看最后一次提交
</pre>
<p>网上有大神还有这么设置的:</p>
<pre class="sunlight-highlight-objective-c">
git config --global alias.lg "log --color --graph --pretty=format:'%Cred%h%Creset -%C(yellow)%d%Creset %s %Cgreen(%cr) %C(bold blue)<%an>%Creset' --abbrev-commit"
</pre>
<p>赶紧去看看效果吧!</p>
<style type="text/css">
.h2 {color:#0087A6;font-size:120%}
.h3 {color:#0087A6;font-size:110%}
mark {font-size:90%;}
</style>idechaoidechao@163.comFeaturesiOS动画基础之Layer2016-12-05T00:00:00+08:002016-12-05T00:00:00+08:00http://www.dechao.net/http://www.dechao.net/blog/layer<section id="table-of-contents" class="toc">
<header>
<h1>Features</h1>
</header>
<div id="drawer">
<ul id="markdown-toc">
<li><a href="#contents" id="markdown-toc-contents">contents属性</a> <ul>
<li><a href="#contentsgravity" id="markdown-toc-contentsgravity">contentsGravity</a></li>
<li><a href="#masktobounds" id="markdown-toc-masktobounds">maskToBounds</a></li>
</ul>
</li>
<li><a href="#cashapelayer" id="markdown-toc-cashapelayer">CAShapeLayer</a> <ul>
<li><a href="#cgpath" id="markdown-toc-cgpath">创建一个CGPath</a></li>
<li><a href="#section" id="markdown-toc-section">圆角</a></li>
</ul>
</li>
<li><a href="#caemitterlayer" id="markdown-toc-caemitterlayer">CAEmitterLayer</a> <ul>
<li><a href="#facebook" id="markdown-toc-facebook">类似于facebook的点赞效果:(这里就不加放大缩小的效果了)</a></li>
<li><a href="#section-1" id="markdown-toc-section-1">下雪的效果</a></li>
</ul>
</li>
<li><a href="#cagradientlayer" id="markdown-toc-cagradientlayer">CAGradientLayer</a> <ul>
<li><a href="#section-2" id="markdown-toc-section-2">基础渐变</a></li>
<li><a href="#section-3" id="markdown-toc-section-3">多重渐变</a></li>
</ul>
</li>
<li><a href="#careplicatorlayer" id="markdown-toc-careplicatorlayer">CAReplicatorLayer</a> <ul>
<li><a href="#section-4" id="markdown-toc-section-4">重复图层之音律跳动</a></li>
<li><a href="#section-5" id="markdown-toc-section-5">重复图层之心形动画</a></li>
<li><a href="#section-6" id="markdown-toc-section-6">反射之倒影效果</a></li>
<li><a href="#section-7" id="markdown-toc-section-7">其他效果</a></li>
</ul>
</li>
<li><a href="#cascrolllayer" id="markdown-toc-cascrolllayer">CAScrollLayer</a></li>
<li><a href="#catransformlayer" id="markdown-toc-catransformlayer">CATransformLayer</a></li>
<li><a href="#catiledlayer" id="markdown-toc-catiledlayer">CATiledLayer</a></li>
<li><a href="#github" id="markdown-toc-github">GitHub地址</a></li>
<li><a href="#section-8" id="markdown-toc-section-8">查考</a></li>
</ul>
</div>
</section>
<!-- /#table-of-contents -->
<h2 id="contents">contents属性</h2>
<p>CALayer有一个属性叫做contents,这个属性的类型被定义为id,意味着它可以是任何类型的对象。在这种情况下,你可以给contents属性赋任何值,你的app仍然能够编译通过。但是,在实践中,如果你给contents赋的不是CGImage,那么你得到的图层将是空白的。</p>
<p>所以基本的使用情况:</p>
<pre class="sunlight-highlight-objective-c">
layer.contents = (__bridge id)image.CGImage;
</pre>
<blockquote>
<p>不过貌似本地测试的时候只是强转了id也能生效</p>
</blockquote>
<h3 id="contentsgravity">contentsGravity</h3>
<p>同UIImageView一样,在大小不合适的情况下,会出现照片的适应性问题。解决方法就是把contentMode属性设置成更合适的值,像这样:</p>
<pre class="sunlight-highlight-objective-c">
view.contentMode = UIViewContentModeScaleAspectFit;
</pre>
<p>这个方法基本和我们遇到的情况的解决方法已经接近了(你可以试一下 :) ),不过UIView大多数视觉相关的属性比如contentMode,对这些属性的操作其实是对对应图层的操作。</p>
<p>CALayer与contentMode对应的属性叫做contentsGravity,但是它是一个NSString类型,而不是像对应的UIKit部分,那里面的值是枚举。</p>
<p>和cotentMode一样,contentsGravity的目的是为了决定内容在图层的边界中怎么对齐,我们将使用kCAGravityResizeAspect,它的效果等同于UIViewContentModeScaleAspectFit, 同时它还能在图层中等比例拉伸以适应图层的边界。</p>
<pre class="sunlight-highlight-objective-c">
self.layerView.layer.contentsGravity = kCAGravityResizeAspect;
</pre>
<h3 id="masktobounds">maskToBounds</h3>
<p>默认情况下,UIView仍然会绘制超过边界的内容或是子视图,在CALayer下也是这样的。
UIView有一个叫做clipsToBounds的属性可以用来决定是否显示超出边界的内容,CALayer对应的属性叫做masksToBounds,把它设置为YES,图片就在边界里啦~</p>
<p>还有一些其他属性,诸如 <code class="highlighter-rouge">contentsScale</code>、<code class="highlighter-rouge">contentsRect</code>、<code class="highlighter-rouge">contentsCenter</code> 、等觉得是并不常用,暂不做介绍了。</p>
<h2 id="cashapelayer">CAShapeLayer</h2>
<p>CAShapeLayer是一个通过矢量图形而不是bitmap来绘制的图层子类。你指定诸如颜色和线宽等属性,用CGPath来定义想要绘制的图形,最后CAShapeLayer就自动渲染出来了。当然,你也可以用Core Graphics直接向原始的CALyer的内容中绘制一个路径,相比直下,使用CAShapeLayer有以下一些优点:</p>
<ul>
<li>渲染快速。CAShapeLayer使用了硬件加速,绘制同一图形会比用Core Graphics快很多。</li>
<li>高效使用内存。一个CAShapeLayer不需要像普通CALayer一样创建一个寄宿图形,所以无论有多大,都不会占用太多的内存。</li>
<li>不会被图层边界剪裁掉。一个CAShapeLayer可以在边界之外绘制。你的图层路径不会像在使用Core Graphics的普通CALayer一样被剪裁掉。</li>
<li>不会出现像素化。当你给CAShapeLayer做3D变换时,它不像一个有寄宿图的普通图层一样变得像素化。</li>
</ul>
<h3 id="cgpath">创建一个CGPath</h3>
<p>CAShapeLayer可以用来绘制所有能够通过CGPath来表示的形状。这个形状不一定要闭合,图层路径也不一定要不可破,事实上你可以在一个图层上绘制好几个不同的形状。你可以控制一些属性比如<code class="highlighter-rouge">lineWith</code>(线宽,用点表示单位),<code class="highlighter-rouge">lineCap</code>(线条结尾的样子),和<code class="highlighter-rouge">lineJoin</code>(线条之间的结合点的样子);但是在图层层面你只有一次机会设置这些属性。如果你想用不同颜色或风格来绘制多个形状,就不得不为每个形状准备一个图层了。</p>
<p>下面的代码用一个CAShapeLayer渲染一个简单的火柴人。CAShapeLayer属性是CGPathRef类型,但是我们用UIBezierPath帮助类创建了图层路径,这样我们就不用考虑人工释放CGPath了。</p>
<pre class="sunlight-highlight-objective-c">
UIView *containerView = [[UIView alloc] initWithFrame:self.view.bounds];
containerView.backgroundColor = [UIColor lightGrayColor];
[self.view addSubview:containerView];
// create path
UIBezierPath *path = [[UIBezierPath alloc] init];
[path moveToPoint:CGPointMake(175, 100)];
[path addArcWithCenter:CGPointMake(150, 100)
radius:25 startAngle:0 endAngle:2*M_PI clockwise:YES];
[path moveToPoint:CGPointMake(150, 125)];
[path addLineToPoint:CGPointMake(150, 175)];
[path addLineToPoint:CGPointMake(125, 225)];
[path moveToPoint:CGPointMake(150, 175)];
[path addLineToPoint:CGPointMake(175, 225)];
[path moveToPoint:CGPointMake(100, 150)];
[path addLineToPoint:CGPointMake(200, 150)];
// create shapr layer
CAShapeLayer *layer = [CAShapeLayer layer];
layer.strokeColor = [UIColor redColor].CGColor;
layer.fillColor = [UIColor clearColor].CGColor;
layer.lineWidth = 5;
layer.lineJoin = kCALineJoinRound;
layer.lineCap = kCALineCapRound;
layer.path = path.CGPath;
// add layer to view
[containerView.layer addSublayer:layer];
</pre>
<figure>
<a href="http://www.dechao.net/images/layer/layer_man.jpg"><img src="http://www.dechao.net/images/layer/layer_man.jpg" /></a>
<figcaption>CAShapeLayer CGPath</figcaption>
</figure>
<h3 id="section">圆角</h3>
<p>CAShapeLayer为创建圆角视图提供了一个方法,就是CALayer的cornerRadius属性。虽然使用CAShapeLayer类需要更多的工作,但是它有一个优势就是可以单独指定每个角。</p>
<p>我们创建圆角矩形其实就是人工绘制单独的直线和弧度,但是事实上UIBezierPath有自动绘制圆角矩形的构造方法,下面这段代码绘制了一个有三个圆角一个直角的矩形:</p>
<pre class="sunlight-highlight-objective-c">
// define path parameters
CGRect rect = CGRectMake(0, 0, 100, 100);
UIRectCorner corners = UIRectCornerTopRight |
UIRectCornerBottomRight | UIRectCornerBottomLeft;
CGSize radii = CGSizeMake(30, 30);
// create path
UIBezierPath *corPath = [UIBezierPath bezierPathWithRoundedRect:rect
byRoundingCorners:corners
cornerRadii:radii];
CAShapeLayer *cornerLayer = [CAShapeLayer layer];
cornerLayer.frame = CGRectMake(100, 260, 100, 100);
cornerLayer.path = corPath.CGPath;
cornerLayer.fillColor = [UIColor blueColor].CGColor;
[containerView.layer addSublayer:cornerLayer];
</pre>
<figure>
<a href="http://www.dechao.net/images/layer/layer_corner.jpg"><img src="http://www.dechao.net/images/layer/layer_corner.jpg" /></a>
<figcaption>CAShapeLayer Corner</figcaption>
</figure>
<p>我们可以通过这个图层路径绘制一个既有直角又有圆角的视图。如果我们想依照此图形来剪裁视图内容,我们可以把CAShapeLayer作为视图的宿主图层,而不是添加一个子视图。</p>
<h2 id="caemitterlayer">CAEmitterLayer</h2>
<p>在iOS 5中,苹果引入了一个新的CALayer子类叫做CAEmitterLayer。CAEmitterLayer是一个高性能的粒子引擎,被用来创建实时例子动画如:烟雾,火,雨等等这些效果。</p>
<p>CAEmitterLayer看上去像是许多CAEmitterCell的容器,这些CAEmitierCell定义了一个例子效果。你将会为不同的例子效果定义一个或多个CAEmitterCell作为模版,同时CAEmitterLayer负责基于这些模版实例化一个粒子流。一个CAEmitterCell类似于一个CALayer:它有一个contents属性可以定义为一个CGImage,另外还有一些可设置属性控制着表现和行为。我们不会对这些属性逐一进行详细的描述,你们可以在CAEmitterCell类的头文件中找到。</p>
<h3 id="facebook">类似于facebook的点赞效果:(这里就不加放大缩小的效果了)</h3>
<figure>
<a href="http://www.dechao.net/images/layer/layer_support.gif"><img src="http://www.dechao.net/images/layer/layer_support.gif" /></a>
<figcaption> CAEmitterLayer </figcaption>
</figure>
<p>代码如下:</p>
<pre class="sunlight-highlight-objective-c">
CAEmitterCell *explosionCell = [CAEmitterCell emitterCell];
// The name of the cell,用于构建key paths。这也是后面手动控制动画开始和结束的关键。
explosionCell.name = @"explosion";
explosionCell.alphaRange = 0.10;
explosionCell.alphaSpeed = -1.0;
// 下面两个属性如果只用了lifetime那么粒子的存活时间就是固定的,比如lifetime=10,那么粒子10s秒后就消失了。
// 如果使用了lifetimeRange,比如lifetimeRange=5,那么粒子的存活时间就是在5s~15s这个范围内消失。
explosionCell.lifetime = 0.7; // 粒子存活的时间,以秒为单位
explosionCell.lifetimeRange = 0.3; // 可以为这个粒子存活的时间再指定一个范围
explosionCell.birthRate = 0; // 每秒生成多少个粒子
// 粒子平均初始速度。正数表示竖直向上,负数竖直向下
explosionCell.velocity = 40.00;
// 可以再指定一个范围
explosionCell.velocityRange = 10.00;
explosionCell.scale = 0.03;
explosionCell.scaleRange = 0.02;
explosionCell.contents = (id)[UIImage imageNamed:@"Sparkle"].CGImage;
self.explosionLayer = [CAEmitterLayer layer];
self.explosionLayer.name = @"emitterLayer";
// 发射源的形状
self.explosionLayer.emitterShape = kCAEmitterLayerCircle;
// 发射源的发射模式
self.explosionLayer.emitterMode = kCAEmitterLayerOutline;
//发射源大小。注意除了宽和高之外,还有纵向深度emitterDepth
self.explosionLayer.emitterSize = CGSizeMake(10, 0);
self.explosionLayer.emitterCells = @[explosionCell];
self.explosionLayer.renderMode = kCAEmitterLayerOldestFirst;
self.explosionLayer.masksToBounds = NO;
self.explosionLayer.position = CGPointMake(10, 10); // 发射源位置
self.explosionLayer.zPosition = -1; // 发射源位置
[self.button.layer addSublayer:self.explosionLayer];
[self.button addTarget:self action:@selector(startC) forControlEvents:UIControlEventTouchUpInside];
</pre>
<pre class="sunlight-highlight-objective-c">
- (void)startC {
//进入下一个动作
// [self performSelector:@selector(explode) withObject:nil afterDelay:0.2];
// //explosionLayer开始时间
self.explosionLayer.beginTime = CACurrentMediaTime();
//explosionLayer每秒喷射的2500个
//CAEmitterLayer 根据自己的 emitterCells 属性找到名叫 explosion 的 cell,
//并设置它的 birthRate 为 500。从而间接地控制了动画的开始。
[self.explosionLayer setValue:@2500 forKeyPath:@"emitterCells.explosion.birthRate"];
//停止喷射
[self performSelector:@selector(stop) withObject:nil afterDelay:0.1];
}
</pre>
<pre class="sunlight-highlight-objective-c">
/**
* 大量喷射
*/
- (void)explode {
//explosionLayer开始时间
self.explosionLayer.beginTime = CACurrentMediaTime();
//explosionLayer每秒喷射的2500个
[self.explosionLayer setValue:@2500 forKeyPath:@"emitterCells.explosion.birthRate"];
//停止喷射
[self performSelector:@selector(stop) withObject:nil afterDelay:0.1];
}
/**
* 停止喷射
*/
- (void)stop {
[self.explosionLayer setValue:@0 forKeyPath:@"emitterCells.explosion.birthRate"];
}
</pre>
<ul>
<li><code class="highlighter-rouge">emitterShape</code>,发射源的形状,平常用的多的比如 emitterShape 的 kCAEmitterLayerLine 和 kCAEmitterLayerPoint。这两个从视觉上还是比较好区分的,这决定了你的粒子是从一个点「喷」出来的,还是从一条线上每个点「喷」下来,前者像焰火,后者像瀑布。显然,下雪的效果更像后者。</li>
<li><code class="highlighter-rouge">emitterMode</code> 的 kCAEmitterLayerOutline 表示向外围扩散,如果你的发射源形状是 circle,那么 kCAEmitterLayerOutline 就会以一个圆的方式向外扩散开。又比如你想表达一股蒸汽向上喷的效果,就可以设置 emitterShape 为 kCAEmitterLayerLine , emitterMode 为 kCAEmitterLayerOutline。</li>
</ul>
<p>CAEmitterLayer的属性它自己控制着整个例子系统的位置和形状。一些属性比如birthRate,lifetime和celocity,这些属性在CAEmitterCell中也有。这些属性会以相乘的方式作用在一起,这样你就可以用一个值来加速或者扩大整个例子系统。其他值得提到的属性有以下这些:</p>
<ul>
<li><code class="highlighter-rouge">preservesDepth</code>,是否将3D例子系统平面化到一个图层(默认值)或者可以在3D空间中混合其他的图层</li>
<li><code class="highlighter-rouge">renderMode</code>,控制着在视觉上粒子图片是如何混合的。</li>
</ul>
<h3 id="section-1">下雪的效果</h3>
<figure>
<a href="http://www.dechao.net/images/layer/layer_snow.gif"><img src="http://www.dechao.net/images/layer/layer_snow.gif" /></a>
<figcaption> CAEmitterLayer </figcaption>
</figure>
<p>代码如下:</p>
<pre class="sunlight-highlight-objective-c">
CAEmitterLayer *snowEmitter = [CAEmitterLayer layer];
//发射点的位置
snowEmitter.emitterPosition = CGPointMake(CGRectGetWidth(self.view.frame) / 2, -30);
snowEmitter.emitterSize = CGSizeMake(CGRectGetWidth(self.view.frame) * 2, 0.0f);
snowEmitter.emitterShape = kCAEmitterLayerLine; // 发射源的形状
snowEmitter.emitterMode = kCAEmitterLayerOutline;
snowEmitter.shadowColor = [UIColor whiteColor].CGColor;
snowEmitter.shadowOffset = CGSizeMake(0.0f, 1.0f);
snowEmitter.shadowRadius = 0.0f;
snowEmitter.shadowOpacity = 1.0f;
CAEmitterCell *snowCell = [CAEmitterCell emitterCell];
snowCell.birthRate = 1.0f; //每秒出现多少个粒子
snowCell.lifetime = 120.0f; // 粒子的存活时间
snowCell.velocity = -10; //速度
snowCell.velocityRange = 10; // 平均速度
snowCell.yAcceleration = 2;//粒子在y方向上的加速度
snowCell.emissionRange = 0.5f * M_PI; //发射的弧度
snowCell.spinRange = 0.75f * M_PI; // 粒子的平均旋转速度
snowCell.contents = (id)[UIImage imageNamed:@"snow"].CGImage;
snowCell.color = [UIColor colorWithRed:0.6 green:0.658 blue:0.743 alpha:1.0].CGColor;
snowEmitter.emitterCells = @[snowCell];
[self.view.layer insertSublayer:snowEmitter atIndex:0];
</pre>
<blockquote>
<p>有时候返回的时候,layer还未移除,所以会看到部分影像,可以在返回的时候手动移除:</p>
</blockquote>
<pre class="sunlight-highlight-objective-c">
for (CALayer *subLayer in self.view.layer.sublayers) {
if ([subLayer isKindOfClass:[CAEmitterLayer class]]) {
CAEmitterLayer *la = (CAEmitterLayer *)subLayer;
[la removeFromSuperlayer];
}
}
</pre>
<h2 id="cagradientlayer">CAGradientLayer</h2>
<p>CAGradientLayer是用来生成两种或更多颜色平滑渐变的。用Core Graphics复制一个CAGradientLayer并将内容绘制到一个普通图层的寄宿图也是有可能的,但是CAGradientLayer的真正好处在于绘制使用了硬件加速。</p>
<h3 id="section-2">基础渐变</h3>
<p>从一个简单的红变蓝的对角线渐变开始。</p>
<p>这些渐变色彩放在一个数组中,并赋给colors属性。这个数组成员接受CGColorRef类型的值(并不是从NSObject派生而来),所以我们要用通过bridge转换以确保编译正常。</p>
<p>CAGradientLayer也有startPoint和endPoint属性,他们决定了渐变的方向。这两个参数是以单位坐标系进行的定义,所以左上角坐标是{0, 0},右下角坐标是{1, 1}。代码如下:</p>
<pre class="sunlight-highlight-objective-c">
/*
* 简单的两种颜色的对角线渐变
*
*/
self.contentView = [[UIView alloc] initWithFrame:CGRectMake(100, 100, 100, 100)];
[self.view addSubview:self.contentView];
CAGradientLayer *gradientLayer = [CAGradientLayer layer];
gradientLayer.frame = self.contentView.bounds;
[self.contentView.layer addSublayer:gradientLayer];
// set gradient colors
gradientLayer.colors = @[(__bridge id)[UIColor redColor].CGColor,
(__bridge id)[UIColor blueColor].CGColor];
// 单位坐标系进行的定义,所以左上角坐标是{0, 0},右下角坐标是{1, 1}
gradientLayer.startPoint = CGPointMake(0, 0);
gradientLayer.endPoint = CGPointMake(1, 1);
</pre>
<p>对应的效果:</p>
<figure>
<a href="http://www.dechao.net/images/layer/layer_gradient_base.png"><img src="http://www.dechao.net/images/layer/layer_gradient_base.png" /></a>
<figcaption> CAGradientLayer 基础渐变 </figcaption>
</figure>
<h3 id="section-3">多重渐变</h3>
<p>如果你愿意,colors属性可以包含很多颜色,所以创建一个彩虹一样的多重渐变也是很简单的。默认情况下,这些颜色在空间上均匀地被渲染,但是我们可以用<code class="highlighter-rouge">locations</code>属性来调整空间。locations属性是一个浮点数值的数组(以NSNumber包装)。这些浮点数定义了colors属性中每个不同颜色的位置,同样的,也是以单位坐标系进行标定。0.0代表着渐变的开始,1.0代表着结束。</p>
<p>locations数组并不是强制要求的,但是如果你给它赋值了就一定要确保locations的数组大小和colors数组大小一定要相同,否则你将会得到一个空白的渐变。</p>
<p>重新构造一个渐变的形式:</p>
<pre class="sunlight-highlight-objective-c">
/*
* 多重渐变
*
*/
self.contentView2 = [[UIView alloc] initWithFrame:CGRectMake(100, 300, 100, 100)];
[self.view addSubview:self.contentView2];
CAGradientLayer *gradientLayer2 = [CAGradientLayer layer];
gradientLayer2.frame = self.contentView2.bounds;
[self.contentView2.layer addSublayer:gradientLayer2];
// set gradient colors
gradientLayer2.colors = @[(__bridge id)[UIColor redColor].CGColor,
(__bridge id)[UIColor yellowColor].CGColor,
(__bridge id)[UIColor greenColor].CGColor];
// set locations
gradientLayer2.locations = @[@0.0, @0.25, @0.5];
//set gradient start and end points
gradientLayer2.startPoint = CGPointMake(0, 0);
gradientLayer2.endPoint = CGPointMake(1, 1);
</pre>
<figure>
<a href="http://www.dechao.net/images/layer/layer_gradient_multi.png"><img src="http://www.dechao.net/images/layer/layer_gradient_multi.png" /></a>
<figcaption> locations 构造偏移至左上角的三色渐变 </figcaption>
</figure>
<p>类似于iPhone上面的解锁提示文案, <code class="highlighter-rouge">> 滑动来解锁</code>,就使用这种方式来实现。</p>
<p>FB有个开源库,就是来做这种事的:
<a href="https://github.com/facebook/Shimmer" style="font-size:130%;color:blue;">facebook/Shimmer</a> ٩(˃̶͈̀௰˂̶͈́)و</p>
<h2 id="careplicatorlayer">CAReplicatorLayer</h2>
<p>CAReplicatorLayer的目的是为了高效生成许多相似的图层。它会<code class="highlighter-rouge">绘制一个或多个图层的子图层</code>,并在每个复制体上应用不同的变换。</p>
<p>先看一下基本的动画效果,包括心形和音律跳动的动画。</p>
<figure>
<a href="http://www.dechao.net/images/layer/layer_replicator.gif"><img src="http://www.dechao.net/images/layer/layer_replicator.gif" /></a>
<figcaption> locations 构造偏移至左上角的三色渐变 </figcaption>
</figure>
<h3 id="section-4">重复图层之音律跳动</h3>
<p>我们先创建一个CAReplicatorLayer,并在上面添加子图层,给该子图层增加一个动画效果。之后设置复制的个数、间隔以及动画的延迟时候等,做出动画效果。</p>
<p>为了有个更好的参考,添加了个参考的图层。</p>
<pre class="sunlight-highlight-objective-c">
#define SYS_DEVICE_WIDTH ([[UIScreen mainScreen] bounds].size.width) // 屏幕宽度
#define SYS_DEVICE_HEIGHT ([[UIScreen mainScreen] bounds].size.height) // 屏幕长度
CAReplicatorLayer *_musicLayer = [CAReplicatorLayer layer];
_musicLayer.frame = CGRectMake(SYS_DEVICE_WIDTH/2.0-50, 410, 100, 100);
_musicLayer.backgroundColor = [UIColor greenColor].CGColor;
_musicLayer.masksToBounds = YES;
[self.view.layer addSublayer:_musicLayer];
// 创建一个子layer,并以此来作为复制的基础
CALayer *tLayer = [CALayer layer];
tLayer.backgroundColor = [UIColor redColor].CGColor;
[_musicLayer addSublayer:tLayer];
// 给个参考的图层
#if 0
CALayer *ttLayer = [CALayer layer];
ttLayer.backgroundColor = [UIColor blueColor].CGColor;
[_musicLayer addSublayer:ttLayer];
ttLayer.frame = CGRectMake(20, 70, 10, 40);
#endif
// 给该子layer加动画
CABasicAnimation *musicAnimation = [CABasicAnimation animationWithKeyPath:@"position.y"];
musicAnimation.duration = 0.35;
musicAnimation.autoreverses = YES;
musicAnimation.repeatCount = MAXFLOAT;
// 通过改变位置关系来展现不同的动画效果
#if 0
tLayer.frame = CGRectMake(10, 70, 10, 30);
musicAnimation.fromValue = @(70);
musicAnimation.toValue = @(45);
#else
tLayer.frame = CGRectMake(10, 100, 10, 30);
musicAnimation.fromValue = @(100);
musicAnimation.toValue = @(85);
#endif
[tLayer addAnimation:musicAnimation forKey:@"musicAnimation"];
// 复制layer,会连layer的动画一同复制
_musicLayer.instanceCount = 3; // 复制的个数
_musicLayer.instanceTransform = CATransform3DMakeTranslation(30, 0, 0); //每个layer的间距。
_musicLayer.instanceDelay = 0.2; // 动画的延迟时间
</pre>
<h3 id="section-5">重复图层之心形动画</h3>
<p>心形的动画,一方面增加了路径的绘制,另一方面也包含了颜色的减弱效果。</p>
<pre class="sunlight-highlight-objective-c">
// love路径
UIBezierPath *tPath = [UIBezierPath bezierPath];
[tPath moveToPoint:CGPointMake(SYS_DEVICE_WIDTH/2.0, 200)];
[tPath addQuadCurveToPoint:CGPointMake(SYS_DEVICE_WIDTH/2.0, 400) controlPoint:CGPointMake(SYS_DEVICE_WIDTH/2.0 + 200, 20)];
[tPath addQuadCurveToPoint:CGPointMake(SYS_DEVICE_WIDTH/2.0, 200) controlPoint:CGPointMake(SYS_DEVICE_WIDTH/2.0 - 200, 20)];
[tPath closePath];
// 具体的layer
UIView *tView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 10, 10)];
tView.center = CGPointMake(SYS_DEVICE_WIDTH/2.0, 200);
tView.layer.cornerRadius = 5;
tView.backgroundColor = [UIColor colorWithRed:1.0 green:1.0 blue:1.0 alpha:1.0];
// 动作效果
CAKeyframeAnimation *loveAnimation = [CAKeyframeAnimation animationWithKeyPath:@"position"];
loveAnimation.path = tPath.CGPath;
loveAnimation.duration = 8;
loveAnimation.repeatCount = MAXFLOAT;
[tView.layer addAnimation:loveAnimation forKey:@"loveAnimation"];
CAReplicatorLayer *_loveLayer = [CAReplicatorLayer layer];
_loveLayer.instanceCount = 40; // 40个layer
_loveLayer.instanceDelay = 0.2; // 每隔0.2出现一个layer
_loveLayer.instanceColor = [UIColor colorWithRed:1.0 green:1.0 blue:1.0 alpha:1.0].CGColor;
_loveLayer.instanceGreenOffset = -0.03; // 颜色值递减。
_loveLayer.instanceRedOffset = -0.02; // 颜色值递减。
_loveLayer.instanceBlueOffset = -0.01; // 颜色值递减。
[_loveLayer addSublayer:tView.layer];
[self.view.layer addSublayer:_loveLayer];
</pre>
<p>在 Demo 中,新增了类实现了一下CAReplicatorLayer的效果,这里就放个图欣赏下,具体代码查看<a href="https://github.com/LiDechao/LayerAnimation" style="font-family:times;color:blue;font-size:25px"> Demo </a>吧。</p>
<figure>
<a href="http://www.dechao.net/images/layer/layer_replicator_2.gif"><img src="http://www.dechao.net/images/layer/layer_replicator_2.gif" /></a>
<figcaption> CAReplicatorLayer </figcaption>
</figure>
<h3 id="section-6">反射之倒影效果</h3>
<p>使用CAReplicatorLayer并应用一个负比例变换于一个复制图层,你就可以创建指定视图(或整个视图层次)内容的镜像图片,这样就创建了一个实时的『反射』效果。</p>
<pre class="sunlight-highlight-objective-c">
UIImage *image = [UIImage imageNamed:@"fireballoon"];
// 定义ReplicatorLayer
CAReplicatorLayer *layer = [CAReplicatorLayer layer];
[layer setBounds:CGRectMake(0, 0, image.size.width, image.size.height * 1.5)];
layer.masksToBounds = YES;
layer.anchorPoint = CGPointMake(0.5, 0.0); // 设置锚点为layer的中间上面部分
layer.position = CGPointMake(self.view.frame.size.width/2, 80.0); // 设置坐标
layer.instanceCount = 2;
[self.view.layer addSublayer:layer];
// 设置图片的翻转并移动到合适的位置
CATransform3D transform = CATransform3DIdentity;
transform = CATransform3DScale(transform, 1.0, -1.0, 1.0);
transform = CATransform3DTranslate(transform, 0, -[image size].height * 2, 1.0);
layer.instanceTransform = transform;
// 设置子layer
_imageLayer = [CALayer layer];
[_imageLayer setContentsScale:[[UIScreen mainScreen] scale]];
[_imageLayer setContents:(__bridge id)image.CGImage];
[_imageLayer setBounds:CGRectMake(0.0, 0.0, [image size].width, [image size].height)];
[_imageLayer setAnchorPoint:CGPointMake(0, 0)];
[layer addSublayer:_imageLayer];
</pre>
<figure>
<a href="http://www.dechao.net/images/layer/layer_replicator_reflex.png"><img src="http://www.dechao.net/images/layer/layer_replicator_reflex.png" /></a>
<figcaption> CAReplicatorLayer 倒影 </figcaption>
</figure>
<p>当然,以view的形式来规定layer,也是一个很简单的方式。详见<a href="https://github.com/LiDechao/LayerAnimation" style="font-family:times;color:blue;font-size:25px"> Demo </a>。</p>
<h3 id="section-7">其他效果</h3>
<p>1、在后面追增一个形式,根据笔迹来运动,直接看动画吧,就不上代码了:</p>
<figure>
<a href="http://www.dechao.net/images/layer/layer_replicator_3.gif"><img src="http://www.dechao.net/images/layer/layer_replicator_3.gif" /></a>
<figcaption> CAReplicatorLayer 倒影 </figcaption>
</figure>
<p>2、奉上苹果官方效果:看到效果的我乐起来~~ <label style="font-family:times;color:orange;font-size:20px">٩(˃̶͈̀௰˂̶͈́)و</label></p>
<p><a href="https://developer.apple.com/library/content/samplecode/ReplicatorDemo/ReplicatorDemo.zip" style="font-family:times;color:blue;font-size:20px"> Apple ReplicatorDemo </a></p>
<h2 id="cascrolllayer">CAScrollLayer</h2>
<p>对于一个未转换的图层,它的bounds和它的frame是一样的,frame属性是由bounds属性自动计算而出的,所以更改任意一个值都会更新其他值。</p>
<p>但是如果你只想显示一个大图层里面的一小部分呢。比如说,你可能有一个很大的图片,你希望用户能够随意滑动,或者是一个数据或文本的长列表。在一个典型的iOS应用中,你可能会用到UITableView或是UIScrollView,但是对于独立的图层来说,什么会等价于刚刚提到的UITableView和UIScrollView呢?</p>
<p>之前,探索了图层的contentsRect属性的用法,它的确是能够解决在图层中小地方显示大图片的解决方法。但是如果你的图层包含子图层那它就不是一个非常好的解决方案,因为,这样做的话每次你想『滑动』可视区域的时候,你就需要手工重新计算并更新所有的子图层位置。</p>
<p>这个时候就需要CAScrollLayer了。CAScrollLayer有一个-scrollToPoint:方法,它自动适应bounds的原点以便图层内容出现在滑动的地方。注意,这就是它做的所有事情。前面提到过,Core Animation并不处理用户输入,所以CAScrollLayer并不负责将触摸事件转换为滑动事件,既不渲染滚动条,也不实现任何iOS指定行为例如滑动反弹(当视图滑动超多了它的边界的将会反弹回正确的地方)。</p>
<p>不同于UIScrollView,我们定制的滑动视图类并没有实现任何形式的边界检查(bounds checking)。图层内容极有可能滑出视图的边界并无限滑下去。CAScrollLayer并没有等同于UIScrollView中contentSize的属性,所以当CAScrollLayer滑动的时候完全没有一个全局的可滑动区域的概念,也无法自适应它的边界原点至你指定的值。它之所以不能自适应边界大小是因为它不需要,内容完全可以超过边界。
那你一定会奇怪用CAScrollLayer的意义到底何在,因为你可以简单地用一个普通的CALayer然后手动适应边界原点啊。真相其实并不复杂,UIScrollView并没有用CAScrollLayer,事实上,就是简单的通过直接操作图层边界来实现滑动。</p>
<p>CAScrollLayer有一个潜在的有用特性。如果你查看CAScrollLayer的头文件,你就会注意到有一个扩展分类实现了一些方法和属性:</p>
<pre class="sunlight-highlight-objective-c">
- (void)scrollPoint:(CGPoint)p;
- (void)scrollRectToVisible:(CGRect)r;
@property(readonly) CGRect visibleRect;
</pre>
<p>看到这些方法和属性名,你也许会以为这些方法给每个CALayer实例增加了滑动功能。但是事实上他们只是放置在CAScrollLayer中的图层的实用方法。scrollPoint:方法从图层树中查找并找到第一个可用的CAScrollLayer,然后滑动它使得指定点成为可视的。scrollRectToVisible:方法实现了同样的事情只不过是作用在一个矩形上的。visibleRect属性决定图层(如果存在的话)的哪部分是当前的可视区域。如果你自己实现这些方法就会相对容易明白一点,但是CAScrollLayer帮你省了这些麻烦,所以当涉及到实现图层滑动的时候就可以用上了。</p>
<p>具体代码查看<a href="https://github.com/LiDechao/LayerAnimation" style="font-family:times;color:blue;font-size:25px"> Demo </a>。</p>
<h2 id="catransformlayer">CATransformLayer</h2>
<p>当我们在构造复杂的3D事物的时候,如果能够组织独立元素就太方便了。比如说,你想创造一个孩子的手臂:你就需要确定哪一部分是孩子的手腕,哪一部分是孩子的前臂,哪一部分是孩子的肘,哪一部分是孩子的上臂,哪一部分是孩子的肩膀等等。</p>
<p>当然是允许独立地移动每个区域的啦。以肘为指点会移动前臂和手,而不是肩膀。Core Animation图层很容易就可以让你在2D环境下做出这样的层级体系下的变换,但是3D情况下就不太可能,因为所有的图层都把他的孩子都平面化到一个场景中。</p>
<p>CATransformLayer解决了这个问题,CATransformLayer不同于普通的CALayer,因为它不能显示它自己的内容。只有当存在了一个能作用域子图层的变换它才真正存在。CATransformLayer并不平面化它的子图层,所以它能够用于构造一个层级的3D结构,比如我的手臂示例。</p>
<h2 id="catiledlayer">CATiledLayer</h2>
<p>CATiledLayer,是载入大图时对性能提升有帮助的layer,将大图分解成小片然后将他们单独按需载入。不详解。</p>
<h2 id="github">GitHub地址</h2>
<p>1、Demo地址:</p>
<p><a href="https://github.com/LiDechao/LayerAnimation" style="font-family:times;color:blue;font-size:25px"> LayerAnimation </a></p>
<p>2、为了更好的理解各种layer的,推荐一下开源软件,里面集合和多种layer的效果:</p>
<p><a href="https://github.com/scotteg/LayerPlayer" style="font-family:times;color:blue;font-size:23px"> LayerPlayer </a></p>
<h2 id="section-8">查考</h2>
<p><a href="https://zsisme.gitbooks.io/ios-/content/">iOS核心动画高级技巧</a></p>
<p><a href="http://www.jianshu.com/p/a927157ac62a">iOS - CAReplicatorLayer的运用</a></p>
<p><a href="http://www.jianshu.com/p/3e3fde03c937">基于CAReplicatorLayer的炫酷动画</a></p>
<p><a href="http://tuohuang.info/14.html#.WFD1o6J97BI">IOS使用CAReplicatorLayer重建动态的倒影</a></p>
<p><a href="http://www.jianshu.com/p/9ed9ce30a2e8">CAReplicatorLayer的使用</a></p>idechaoidechao@163.comFeaturesiOS绘图2016-12-03T00:00:00+08:002016-12-03T00:00:00+08:00http://www.dechao.net/http://www.dechao.net/blog/graphics<section id="table-of-contents" class="toc">
<header>
<h1>Features</h1>
</header>
<div id="drawer">
<ul id="markdown-toc">
<li><a href="#section" id="markdown-toc-section">简介</a> <ul>
<li><a href="#section-1" id="markdown-toc-section-1">上下文</a></li>
<li><a href="#uikit" id="markdown-toc-uikit">UIKit</a></li>
<li><a href="#core-graphics" id="markdown-toc-core-graphics">Core Graphics</a></li>
</ul>
</li>
<li><a href="#section-2" id="markdown-toc-section-2">绘图形式</a></li>
<li><a href="#uiimage" id="markdown-toc-uiimage">UIImage常用的绘图操作</a> <ul>
<li><a href="#section-3" id="markdown-toc-section-3">平移操作</a></li>
<li><a href="#section-4" id="markdown-toc-section-4">缩放操作</a></li>
<li><a href="#section-5" id="markdown-toc-section-5">裁剪操作</a></li>
</ul>
</li>
<li><a href="#cgimage" id="markdown-toc-cgimage">CGImage常用的绘图操作</a> <ul>
<li><a href="#section-6" id="markdown-toc-section-6">为什么会发生倒置问题</a></li>
</ul>
</li>
<li><a href="#section-7" id="markdown-toc-section-7">原理篇 - 绘制像素到屏幕上</a></li>
</ul>
</div>
</section>
<!-- /#table-of-contents -->
<h2 id="section">简介</h2>
<p>Core Graphics Framework是一套基于C的API框架,使用了Quartz作为绘图引擎。它提供了低级别、轻量级、高保真度的2D渲染。该框架可以用于基于路径的绘图、变换、颜色管理、脱屏渲染,模板、渐变、遮蔽、图像数据管理、图像的创建、遮罩以及PDF文档的创建、显示和分析。为了从感官上对这些概念做一个入门的认识,你可以运行一下官方的<a href="https://developer.apple.com/library/ios/samplecode/QuartzDemo/Introduction/Intro.html#//apple_ref/doc/uid/DTS40007531">example code</a>。</p>
<p>iOS支持两套图形API族:Core Graphics/QuartZ 2D 和OpenGL ES。OpenGL ES是跨平台的图形API,属于OpenGL的一个简化版本。QuartZ 2D是苹果公司开发的一套API,它是Core Graphics Framework的一部分。需要注意的是:OpenGL ES是应用程序编程接口,该接口描述了方法、结构、函数应具有的行为以及应该如何被使用的语义。也就是说它只定义了一套规范,具体的实现由设备制造商根据规范去做。而往往很多人对接口和实现存在误解。举一个不恰当的比喻:上发条的时钟和装电池的时钟都有相同的可视行为,但两者的内部实现截然不同。因为制造商可以自由的实现Open GL ES,所以不同系统实现的OpenGL ES也存在着巨大的性能差异。</p>
<p>Core Graphics API所有的操作都在一个上下文中进行。所以在绘图之前需要获取该上下文并传入执行渲染的函数中。如果你正在渲染一副在内存中的图片,此时就需要传入图片所属的上下文。获得一个图形上下文是我们完成绘图任务的第一步,你可以将图形上下文理解为一块画布。如果你没有得到这块画布,那么你就无法完成任何绘图操作。</p>
<h3 id="section-1">上下文</h3>
<p>当然,有许多方式获得一个图形上下文,这里我介绍两种最为常用的获取方法。</p>
<p>第一种方法就是创建一个图片类型的上下文。调UIGraphicsBeginImageContextWithOptions函数就可获得用来处理图片的图形上下文。利用该上下文,你就可以在其上进行绘图,并生成图片。调用UIGraphicsGetImageFromCurrentImageContext函数可从当前上下文中获取一个UIImage对象。记住在你所有的绘图操作后别忘了调用UIGraphicsEndImageContext函数关闭图形上下文。</p>
<p>第二种方法是利用cocoa为你生成的图形上下文。当你子类化了一个UIView并实现了自己的drawRect:方法后,一旦drawRect:方法被调用,Cocoa就会为你创建一个图形上下文,此时你对图形上下文的所有绘图操作都会显示在UIView上。</p>
<p>判断一个上下文是否为当前图形上下文需要注意的几点:</p>
<ol>
<li>UIGraphicsBeginImageContextWithOptions函数不仅仅是创建了一个适用于图形操作的上下文,并且该上下文也属于当前上下文。</li>
<li>当drawRect方法被调用时,UIView的绘图上下文属于当前图形上下文。</li>
<li>回调方法所持有的context:参数并不会让任何上下文成为当前图形上下文。此参数仅仅是对一个图形上下文的引用罢了。</li>
</ol>
<p>作为初学者,很容易被UIKit和Core Graphics两个支持绘图的框架迷惑。</p>
<h3 id="uikit">UIKit</h3>
<p>像UIImage、NSString(绘制文本)、UIBezierPath(绘制形状)、UIColor都知道如何绘制自己。这些类提供了功能有限但使用方便的方法来让我们完成绘图任务。一般情况下,UIKit就是我们所需要的。</p>
<p>使用UiKit,<code class="highlighter-rouge">你只能在当前上下文中绘图</code>,所以如果你当前处于UIGraphicsBeginImageContextWithOptions函数或drawRect:方法中,你就可以直接使用UIKit提供的方法进行绘图。如果你持有一个context:参数,那么使用UIKit提供的方法之前,必须将该上下文参数转化为当前上下文。幸运的是,调用UIGraphicsPushContext 函数可以方便的将context:参数转化为当前上下文,记住最后别忘了调用UIGraphicsPopContext函数恢复上下文环境。</p>
<hr />
<h3 id="core-graphics">Core Graphics</h3>
<p>这是一个绘图专用的API族,它经常被称为QuartZ或QuartZ 2D。Core Graphics是iOS上所有绘图功能的基石,包括UIKit。</p>
<p>使用Core Graphics之前需要指定一个用于绘图的图形上下文(CGContextRef),这个图形上下文会在每个绘图函数中都会被用到。如果你持有一个图形上下文context:参数,那么你等同于有了一个图形上下文,这个上下文也许就是你需要用来绘图的那个。如果你当前处于UIGraphicsBeginImageContextWithOptions函数或drawRect:方法中,并没有引用一个上下文。为了使用Core Graphics,你可以调用UIGraphicsGetCurrentContext函数获得当前的图形上下文。</p>
<p>至此,我们有了两大绘图框架的支持以及三种获得图形上下文的方法(drawRect:、drawRect: inContext:、UIGraphicsBeginImageContextWithOptions)。那么我们就有6种绘图的形式。如果你有些困惑了,不用怕,我接下来将说明这6种情况。无需担心还没有具体的绘图命令,你只需关注上下文如何被创建以及我们是在使用UIKit还是Core Graphics。</p>
<hr />
<h2 id="section-2">绘图形式</h2>
<p><strong>第一种绘图形式</strong>:在UIView的子类方法drawRect:中绘制一个蓝色圆,使用UIKit在Cocoa为我们提供的当前上下文中完成绘图任务。</p>
<figure class="highlight"><pre><code class="language-objc" data-lang="objc"><span class="k">-</span> <span class="p">(</span><span class="kt">void</span><span class="p">)</span> <span class="nf">drawRect</span><span class="p">:</span> <span class="p">(</span><span class="n">CGRect</span><span class="p">)</span> <span class="n">rect</span> <span class="p">{</span>
<span class="n">UIBezierPath</span><span class="o">*</span> <span class="n">path</span> <span class="o">=</span> <span class="p">[</span><span class="nf">UIBezierPathbezierPathWithOvalInRect</span><span class="p">:</span><span class="n">CGRectMake</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">100</span><span class="p">,</span><span class="mi">100</span><span class="p">)];</span>
<span class="p">[[</span><span class="n">UIColor</span> <span class="nf">blueColor</span><span class="p">]</span> <span class="nf">setFill</span><span class="p">];</span>
<span class="p">[</span><span class="n">path</span> <span class="nf">fill</span><span class="p">];</span>
<span class="p">}</span> </code></pre></figure>
<p><strong>第二种绘图形式</strong>:使用Core Graphics实现绘制蓝色圆。</p>
<figure class="highlight"><pre><code class="language-objc" data-lang="objc"><span class="k">-</span> <span class="p">(</span><span class="kt">void</span><span class="p">)</span> <span class="nf">drawRect</span><span class="p">:</span> <span class="p">(</span><span class="n">CGRect</span><span class="p">)</span> <span class="n">rect</span> <span class="p">{</span>
<span class="n">CGContextRef</span> <span class="n">con</span> <span class="o">=</span> <span class="n">UIGraphicsGetCurrentContext</span><span class="p">();</span> <span class="c1">// 获取当前图形上下文
</span>
<span class="n">CGContextAddEllipseInRect</span><span class="p">(</span><span class="n">con</span><span class="p">,</span> <span class="n">CGRectMake</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">100</span><span class="p">,</span> <span class="mi">100</span><span class="p">));</span>
<span class="n">CGContextSetFillColorWithColor</span><span class="p">(</span><span class="n">con</span><span class="p">,</span> <span class="p">[</span><span class="n">UIColor</span> <span class="nf">blueColor</span><span class="p">].</span><span class="n">CGColor</span><span class="p">);</span>
<span class="n">CGContextFillPath</span><span class="p">(</span><span class="n">con</span><span class="p">);</span>
<span class="p">}</span> </code></pre></figure>
<p><strong>第三种绘图形式</strong>:在UIView子类的drawLayer:inContext:方法中实现绘图任务。</p>
<p>drawLayer:inContext:方法是一个绘制图层内容的代理方法。为了能够调用drawLayer:inContext:方法,我们需要设定图层的代理对象。但要注意,不应该将UIView对象设置为显示层的委托对象,这是因为UIView对象已经是隐式层的代理对象,再将它设置为另一个层的委托对象就会出问题。轻量级的做法是:编写负责绘图形的代理类。</p>
<figure class="highlight"><pre><code class="language-objc" data-lang="objc"><span class="k">@interface</span> <span class="nc">MyLayerDelegate</span> <span class="p">:</span> <span class="nc">NSObject</span>
<span class="k">@end</span>
<span class="k">@implementation</span> <span class="nc">MyLayerDelegate</span>
<span class="k">-</span> <span class="p">(</span><span class="kt">void</span><span class="p">)</span><span class="nf">drawLayer</span><span class="p">:(</span><span class="n">CALayer</span><span class="o">*</span><span class="p">)</span><span class="nv">layer</span> <span class="nf">inContext</span><span class="p">:(</span><span class="n">CGContextRef</span><span class="p">)</span><span class="nv">ctx</span> <span class="p">{</span>
<span class="n">UIGraphicsPushContext</span><span class="p">(</span><span class="n">ctx</span><span class="p">);</span>
<span class="n">UIBezierPath</span><span class="o">*</span> <span class="n">path</span> <span class="o">=</span> <span class="p">[</span><span class="n">UIBezierPath</span> <span class="nf">bezierPathWithOvalInRect</span><span class="p">:</span><span class="n">CGRectMake</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">100</span><span class="p">,</span><span class="mi">100</span><span class="p">)];</span>
<span class="p">[[</span><span class="n">UIColor</span> <span class="nf">blueColor</span><span class="p">]</span> <span class="nf">setFill</span><span class="p">];</span>
<span class="p">[</span><span class="n">path</span> <span class="nf">fill</span><span class="p">];</span>
<span class="n">UIGraphicsPopContext</span><span class="p">();</span>
<span class="p">}</span>
<span class="k">@end</span> </code></pre></figure>
<p>需要注意的是,我们所引用的上下文并不是当前上下文,所以为了能够使用UIKit,我们需要将引用的上下文转变成当前上下文。</p>
<p>因为图层的代理是assign内存管理策略,那么这里就不能以局部变量的形式创建MyLayerDelegate实例对象赋值给图层代理。</p>
<figure class="highlight"><pre><code class="language-objc" data-lang="objc"><span class="n">DrawView</span> <span class="o">*</span><span class="n">dview</span> <span class="o">=</span> <span class="p">[[</span><span class="n">DrawView</span> <span class="nf">alloc</span><span class="p">]</span> <span class="nf">initWithFrame</span><span class="p">:</span><span class="n">CGRectMake</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">200</span><span class="p">,</span> <span class="mi">200</span><span class="p">)];</span>
<span class="p">[</span><span class="n">self</span><span class="p">.</span><span class="n">view</span> <span class="nf">addSubview</span><span class="p">:</span><span class="n">dview</span><span class="p">];</span>
<span class="n">_layerDeleagete</span> <span class="o">=</span> <span class="p">[[</span><span class="n">MyLayerDelegate</span> <span class="nf">alloc</span><span class="p">]</span> <span class="nf">init</span><span class="p">];</span>
<span class="n">CALayer</span> <span class="o">*</span><span class="n">myLayer</span> <span class="o">=</span> <span class="p">[</span><span class="n">CALayer</span> <span class="nf">layer</span><span class="p">];</span>
<span class="n">myLayer</span><span class="p">.</span><span class="n">delegate</span> <span class="o">=</span> <span class="p">(</span><span class="n">id</span><span class="p">)</span><span class="n">_layerDeleagete</span><span class="p">;</span> <span class="c1">// 强转一下消除警告
</span>
<span class="p">[</span><span class="n">dview</span><span class="p">.</span><span class="n">layer</span> <span class="nf">addSublayer</span><span class="p">:</span><span class="n">myLayer</span><span class="p">];</span>
<span class="p">[</span><span class="n">dview</span> <span class="nf">setNeedsDisplay</span><span class="p">];</span></code></pre></figure>
<p><strong>第四种绘图形式</strong>: 使用Core Graphics在drawLayer:inContext:方法中实现同样操作,代码如下</p>
<figure class="highlight"><pre><code class="language-objc" data-lang="objc"><span class="k">-</span> <span class="p">(</span><span class="kt">void</span><span class="p">)</span><span class="nf">drawLayer</span><span class="p">:(</span><span class="n">CALayer</span><span class="o">*</span><span class="p">)</span><span class="nv">lay</span> <span class="nf">inContext</span><span class="p">:(</span><span class="n">CGContextRef</span><span class="p">)</span><span class="nv">con</span> <span class="p">{</span>
<span class="n">CGContextAddEllipseInRect</span><span class="p">(</span><span class="n">con</span><span class="p">,</span> <span class="n">CGRectMake</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">100</span><span class="p">,</span><span class="mi">100</span><span class="p">));</span>
<span class="n">CGContextSetFillColorWithColor</span><span class="p">(</span><span class="n">con</span><span class="p">,</span> <span class="p">[</span><span class="n">UIColor</span> <span class="nf">blueColor</span><span class="p">].</span><span class="n">CGColor</span><span class="p">);</span>
<span class="n">CGContextFillPath</span><span class="p">(</span><span class="n">con</span><span class="p">);</span>
<span class="p">}</span> </code></pre></figure>
<p>最后,演示UIGraphicsBeginImageContextWithOptions的用法,并从上下文中生成一个UIImage对象。生成UIImage对象的代码并不需要等待某些方法被调用后或在UIView的子类中才能去做。</p>
<p><strong>第五种绘图形式</strong>: 使用UIKit实现:</p>
<figure class="highlight"><pre><code class="language-objc" data-lang="objc"><span class="n">UIGraphicsBeginImageContextWithOptions</span><span class="p">(</span><span class="n">CGSizeMake</span><span class="p">(</span><span class="mi">100</span><span class="p">,</span><span class="mi">100</span><span class="p">),</span> <span class="nb">NO</span><span class="p">,</span> <span class="mi">0</span><span class="p">);</span>
<span class="n">UIBezierPath</span><span class="o">*</span> <span class="n">p</span> <span class="o">=</span> <span class="p">[</span><span class="n">UIBezierPath</span> <span class="nf">bezierPathWithOvalInRect</span><span class="p">:</span><span class="n">CGRectMake</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">100</span><span class="p">,</span><span class="mi">100</span><span class="p">)];</span>
<span class="p">[[</span><span class="n">UIColor</span> <span class="nf">blueColor</span><span class="p">]</span> <span class="nf">setFill</span><span class="p">];</span>
<span class="p">[</span><span class="n">p</span> <span class="nf">fill</span><span class="p">];</span>
<span class="n">UIImage</span><span class="o">*</span> <span class="n">im</span> <span class="o">=</span> <span class="n">UIGraphicsGetImageFromCurrentImageContext</span><span class="p">();</span>
<span class="n">UIGraphicsEndImageContext</span><span class="p">();</span> </code></pre></figure>
<p>解释一下UIGraphicsBeginImageContextWithOptions函数参数的含义:第一个参数表示所要创建的图片的尺寸;第二个参数用来指定所生成图片的背景是否为不透明,如上我们使用YES而不是NO,则我们得到的图片背景将会是黑色,显然这不是我想要的;第三个参数指定生成图片的缩放因子,这个缩放因子与UIImage的scale属性所指的含义是一致的。传入0则表示让图片的缩放因子根据屏幕的分辨率而变化,所以我们得到的图片不管是在单分辨率还是视网膜屏上看起来都会很好。</p>
<p><strong>第六种绘图形式</strong>: 使用Core Graphics实现:</p>
<figure class="highlight"><pre><code class="language-objc" data-lang="objc"><span class="n">UIGraphicsBeginImageContextWithOptions</span><span class="p">(</span><span class="n">CGSizeMake</span><span class="p">(</span><span class="mi">100</span><span class="p">,</span><span class="mi">100</span><span class="p">),</span> <span class="nb">NO</span><span class="p">,</span> <span class="mi">0</span><span class="p">);</span>
<span class="n">CGContextRef</span> <span class="n">con</span> <span class="o">=</span> <span class="n">UIGraphicsGetCurrentContext</span><span class="p">();</span>
<span class="n">CGContextAddEllipseInRect</span><span class="p">(</span><span class="n">con</span><span class="p">,</span> <span class="n">CGRectMake</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">100</span><span class="p">,</span><span class="mi">100</span><span class="p">));</span>
<span class="n">CGContextSetFillColorWithColor</span><span class="p">(</span><span class="n">con</span><span class="p">,</span> <span class="p">[</span><span class="n">UIColor</span> <span class="nf">blueColor</span><span class="p">].</span><span class="n">CGColor</span><span class="p">);</span>
<span class="n">CGContextFillPath</span><span class="p">(</span><span class="n">con</span><span class="p">);</span>
<span class="n">UIImage</span><span class="o">*</span> <span class="n">im</span> <span class="o">=</span> <span class="n">UIGraphicsGetImageFromCurrentImageContext</span><span class="p">();</span>
<span class="n">UIGraphicsEndImageContext</span><span class="p">();</span></code></pre></figure>
<p>UIKit和Core Graphics可以在相同的图形上下文中混合使用。在iOS 4.0之前,使用UIKit和UIGraphicsGetCurrentContext被认为是线程不安全的。而在iOS4.0以后苹果让绘图操作在第二个线程中执行解决了此问题。</p>
<h2 id="uiimage">UIImage常用的绘图操作</h2>
<p>一个UIImage对象提供了向当前上下文绘制自身的方法。我们现在已经知道如何获取一个图片类型的上下文并将它转变成当前上下文。</p>
<h3 id="section-3">平移操作</h3>
<p>下面的代码展示了如何将UIImage绘制在当前的上下文中。</p>
<figure class="highlight"><pre><code class="language-objc" data-lang="objc"><span class="n">UIImage</span><span class="o">*</span> <span class="n">mars</span> <span class="o">=</span> <span class="p">[</span><span class="n">UIImage</span> <span class="nf">imageNamed</span><span class="p">:</span><span class="s">@"Mars.png"</span><span class="p">];</span>
<span class="n">CGSize</span> <span class="n">sz</span> <span class="o">=</span> <span class="p">[</span><span class="n">mars</span> <span class="nf">size</span><span class="p">];</span>
<span class="n">UIGraphicsBeginImageContextWithOptions</span><span class="p">(</span><span class="n">CGSizeMake</span><span class="p">(</span><span class="n">sz</span><span class="p">.</span><span class="n">width</span><span class="o">*</span><span class="mi">2</span><span class="p">,</span> <span class="n">sz</span><span class="p">.</span><span class="n">height</span><span class="p">),</span> <span class="nb">NO</span><span class="p">,</span> <span class="mi">0</span><span class="p">);</span>
<span class="p">[</span><span class="n">mars</span> <span class="nf">drawAtPoint</span><span class="p">:</span><span class="n">CGPointMake</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">)];</span>
<span class="p">[</span><span class="n">mars</span> <span class="nf">drawAtPoint</span><span class="p">:</span><span class="n">CGPointMake</span><span class="p">(</span><span class="n">sz</span><span class="p">.</span><span class="n">width</span><span class="p">,</span><span class="mi">0</span><span class="p">)];</span>
<span class="n">UIImage</span><span class="o">*</span> <span class="n">im</span> <span class="o">=</span> <span class="n">UIGraphicsGetImageFromCurrentImageContext</span><span class="p">();</span>
<span class="n">UIGraphicsEndImageContext</span><span class="p">();</span>
<span class="n">UIImageView</span><span class="o">*</span> <span class="n">iv</span> <span class="o">=</span> <span class="p">[[</span><span class="n">UIImageView</span> <span class="nf">alloc</span><span class="p">]</span> <span class="nf">initWithImage</span><span class="p">:</span><span class="n">im</span><span class="p">];</span>
<span class="p">[</span><span class="n">self</span><span class="p">.</span><span class="n">window</span><span class="p">.</span><span class="n">rootViewController</span><span class="p">.</span><span class="n">view</span> <span class="nf">addSubview</span><span class="p">:</span> <span class="n">iv</span><span class="p">];</span>
<span class="n">iv</span><span class="p">.</span><span class="n">center</span> <span class="o">=</span> <span class="n">self</span><span class="p">.</span><span class="n">window</span><span class="p">.</span><span class="n">center</span><span class="p">;</span> </code></pre></figure>
<figure>
<a href="http://www.dechao.net/images/graphics/graphics_move.jpg"><img src="http://www.dechao.net/images/graphics/graphics_move.jpg" /></a>
<figcaption>UIImage平移处理</figcaption>
</figure>
<h3 id="section-4">缩放操作</h3>
<p>下面代码展示了如何对UIImage进行缩放操作:</p>
<figure class="highlight"><pre><code class="language-objc" data-lang="objc"><span class="n">UIImage</span><span class="o">*</span> <span class="n">mars</span> <span class="o">=</span> <span class="p">[</span><span class="n">UIImage</span> <span class="nf">imageNamed</span><span class="p">:</span><span class="s">@"Mars.png"</span><span class="p">];</span>
<span class="n">CGSize</span> <span class="n">sz</span> <span class="o">=</span> <span class="p">[</span><span class="n">mars</span> <span class="nf">size</span><span class="p">];</span>
<span class="n">UIGraphicsBeginImageContextWithOptions</span><span class="p">(</span><span class="n">CGSizeMake</span><span class="p">(</span><span class="n">sz</span><span class="p">.</span><span class="n">width</span><span class="o">*</span><span class="mi">2</span><span class="p">,</span> <span class="n">sz</span><span class="p">.</span><span class="n">height</span><span class="o">*</span><span class="mi">2</span><span class="p">),</span> <span class="nb">NO</span><span class="p">,</span> <span class="mi">0</span><span class="p">);</span>
<span class="p">[</span><span class="n">mars</span> <span class="nf">drawInRect</span><span class="p">:</span><span class="n">CGRectMake</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="n">sz</span><span class="p">.</span><span class="n">width</span><span class="o">*</span><span class="mi">2</span><span class="p">,</span><span class="n">sz</span><span class="p">.</span><span class="n">height</span><span class="o">*</span><span class="mi">2</span><span class="p">)];</span>
<span class="p">[</span><span class="n">mars</span> <span class="nf">drawInRect</span><span class="p">:</span><span class="n">CGRectMake</span><span class="p">(</span><span class="n">sz</span><span class="p">.</span><span class="n">width</span><span class="o">/</span><span class="mi">2</span><span class="p">.</span><span class="mi">0</span><span class="p">,</span> <span class="n">sz</span><span class="p">.</span><span class="n">height</span><span class="o">/</span><span class="mi">2</span><span class="p">.</span><span class="mi">0</span><span class="p">,</span> <span class="n">sz</span><span class="p">.</span><span class="n">width</span><span class="p">,</span> <span class="n">sz</span><span class="p">.</span><span class="n">height</span><span class="p">)</span> <span class="nf">blendMode</span><span class="p">:</span><span class="n">kCGBlendModeMultiply</span> <span class="n">alpha</span><span class="o">:</span><span class="mi">1</span><span class="p">.</span><span class="mi">0</span><span class="p">];</span>
<span class="n">UIImage</span><span class="o">*</span> <span class="n">im</span> <span class="o">=</span> <span class="n">UIGraphicsGetImageFromCurrentImageContext</span><span class="p">();</span>
<span class="n">UIGraphicsEndImageContext</span><span class="p">();</span> </code></pre></figure>
<figure>
<a href="http://www.dechao.net/images/graphics/graphics_zoom.jpg"><img src="http://www.dechao.net/images/graphics/graphics_zoom.jpg" /></a>
<figcaption>UIImage缩放处理</figcaption>
</figure>
<p>UIImage没有提供截取图片指定区域的功能。但通过创建一个较小的图形上下文并移动图片到一个适当的图形上下文坐标系内,指定区域内的图片就会被获取。</p>
<h3 id="section-5">裁剪操作</h3>
<p>下面代码展示了如何获取图片的右半边:</p>
<figure class="highlight"><pre><code class="language-objc" data-lang="objc"><span class="n">UIImage</span><span class="o">*</span> <span class="n">mars</span> <span class="o">=</span> <span class="p">[</span><span class="n">UIImage</span> <span class="nf">imageNamed</span><span class="p">:</span><span class="s">@"Mars.png"</span><span class="p">];</span>
<span class="n">CGSize</span> <span class="n">sz</span> <span class="o">=</span> <span class="p">[</span><span class="n">mars</span> <span class="nf">size</span><span class="p">];</span>
<span class="n">UIGraphicsBeginImageContextWithOptions</span><span class="p">(</span><span class="n">CGSizeMake</span><span class="p">(</span><span class="n">sz</span><span class="p">.</span><span class="n">width</span><span class="o">/</span><span class="mi">2</span><span class="p">.</span><span class="mi">0</span><span class="p">,</span> <span class="n">sz</span><span class="p">.</span><span class="n">height</span><span class="p">),</span> <span class="nb">NO</span><span class="p">,</span> <span class="mi">0</span><span class="p">);</span>
<span class="p">[</span><span class="n">mars</span> <span class="nf">drawAtPoint</span><span class="p">:</span><span class="n">CGPointMake</span><span class="p">(</span><span class="o">-</span><span class="n">sz</span><span class="p">.</span><span class="n">width</span><span class="o">/</span><span class="mi">2</span><span class="p">.</span><span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">)];</span>
<span class="n">UIImage</span><span class="o">*</span> <span class="n">im</span> <span class="o">=</span> <span class="n">UIGraphicsGetImageFromCurrentImageContext</span><span class="p">();</span>
<span class="n">UIGraphicsEndImageContext</span><span class="p">();</span> </code></pre></figure>
<p>以上的代码首先创建一个一半图片宽度的图形上下文,然后将图片左上角原点移动到与图形上下文负X坐标对齐,从而让图片只有右半部分与图形上下文相交。</p>
<figure>
<a href="http://www.dechao.net/images/graphics/graphics_cut.jpg"><img src="http://www.dechao.net/images/graphics/graphics_cut.jpg" /></a>
<figcaption>UIImage缩放处理</figcaption>
</figure>
<h2 id="cgimage">CGImage常用的绘图操作</h2>
<p>UIImage的Core Graphics版本是CGImage(具体类型是CGImageRef)。两者可以直接相互转化: 使用UIImage的CGImage属性可以访问Quartz图片数据;将CGImage作为UIImage方法imageWithCGImage:或initWithCGImage:的参数创建UIImage对象。</p>
<p>一个CGImage对象可以让你获取原始图片中指定区域的图片(也可以获取指定区域外的图片,UIImage却办不到)。</p>
<p>下面的代码展示了将图片拆分成两半,并分别绘制在上下文的左右两边:</p>
<figure class="highlight"><pre><code class="language-objc" data-lang="objc"><span class="n">UIImage</span><span class="o">*</span> <span class="n">mars</span> <span class="o">=</span> <span class="p">[</span><span class="n">UIImage</span> <span class="nf">imageNamed</span><span class="p">:</span><span class="s">@"Mars.png"</span><span class="p">];</span>
<span class="c1">// 抽取图片的左右半边
</span>
<span class="n">CGSize</span> <span class="n">sz</span> <span class="o">=</span> <span class="p">[</span><span class="n">mars</span> <span class="nf">size</span><span class="p">];</span>
<span class="n">CGImageRef</span> <span class="n">marsLeft</span> <span class="o">=</span> <span class="n">CGImageCreateWithImageInRect</span><span class="p">([</span><span class="n">mars</span> <span class="nf">CGImage</span><span class="p">],</span><span class="n">CGRectMake</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="n">sz</span><span class="p">.</span><span class="n">width</span><span class="o">/</span><span class="mi">2</span><span class="p">.</span><span class="mi">0</span><span class="p">,</span><span class="n">sz</span><span class="p">.</span><span class="n">height</span><span class="p">));</span>
<span class="n">CGImageRef</span> <span class="n">marsRight</span> <span class="o">=</span> <span class="n">CGImageCreateWithImageInRect</span><span class="p">([</span><span class="n">mars</span> <span class="nf">CGImage</span><span class="p">],</span><span class="n">CGRectMake</span><span class="p">(</span><span class="n">sz</span><span class="p">.</span><span class="n">width</span><span class="o">/</span><span class="mi">2</span><span class="p">.</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="n">sz</span><span class="p">.</span><span class="n">width</span><span class="o">/</span><span class="mi">2</span><span class="p">.</span><span class="mi">0</span><span class="p">,</span><span class="n">sz</span><span class="p">.</span><span class="n">height</span><span class="p">));</span>
<span class="c1">// 将每一个CGImage绘制到图形上下文中
</span>
<span class="n">UIGraphicsBeginImageContextWithOptions</span><span class="p">(</span><span class="n">CGSizeMake</span><span class="p">(</span><span class="n">sz</span><span class="p">.</span><span class="n">width</span><span class="o">*</span><span class="mi">1</span><span class="p">.</span><span class="mi">5</span><span class="p">,</span> <span class="n">sz</span><span class="p">.</span><span class="n">height</span><span class="p">),</span> <span class="nb">NO</span><span class="p">,</span> <span class="mi">0</span><span class="p">);</span>
<span class="n">CGContextRef</span> <span class="n">con</span> <span class="o">=</span> <span class="n">UIGraphicsGetCurrentContext</span><span class="p">();</span>
<span class="n">CGContextDrawImage</span><span class="p">(</span><span class="n">con</span><span class="p">,</span> <span class="n">CGRectMake</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="n">sz</span><span class="p">.</span><span class="n">width</span><span class="o">/</span><span class="mi">2</span><span class="p">.</span><span class="mi">0</span><span class="p">,</span><span class="n">sz</span><span class="p">.</span><span class="n">height</span><span class="p">),</span> <span class="n">marsLeft</span><span class="p">);</span>
<span class="n">CGContextDrawImage</span><span class="p">(</span><span class="n">con</span><span class="p">,</span> <span class="n">CGRectMake</span><span class="p">(</span><span class="n">sz</span><span class="p">.</span><span class="n">width</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="n">sz</span><span class="p">.</span><span class="n">width</span><span class="o">/</span><span class="mi">2</span><span class="p">.</span><span class="mi">0</span><span class="p">,</span><span class="n">sz</span><span class="p">.</span><span class="n">height</span><span class="p">),</span> <span class="n">marsRight</span><span class="p">);</span>
<span class="n">UIImage</span><span class="o">*</span> <span class="n">im</span> <span class="o">=</span> <span class="n">UIGraphicsGetImageFromCurrentImageContext</span><span class="p">();</span>
<span class="n">UIGraphicsEndImageContext</span><span class="p">();</span>
<span class="c1">// 记得释放内存,ARC在这里无效
</span>
<span class="n">CGImageRelease</span><span class="p">(</span><span class="n">marsLeft</span><span class="p">);</span>
<span class="n">CGImageRelease</span><span class="p">(</span><span class="n">marsRight</span><span class="p">);</span> </code></pre></figure>
<p>你也许发现绘出的图是上下颠倒的!图片的颠倒并不是因为被旋转了。当你创建了一个CGImage并使用CGContextDrawImage方法绘图就会引起这种问题。这主要是因为原始的本地坐标系统(坐标原点在左上角)与目标上下文(坐标原点在左下角)不匹配。有很多方法可以修复这个问题,其中一种方法就是使用CGContextDrawImage方法先将CGImage绘制到UIImage上,然后获取UIImage对应的CGImage,此时就得到了一个倒转的CGImage。当再调用CGContextDrawImage方法,我们就将倒转的图片还原回来了。实现代码如下:</p>
<figure class="highlight"><pre><code class="language-objc" data-lang="objc"><span class="n">CGImageRef</span> <span class="nf">flip</span> <span class="p">(</span><span class="n">CGImageRef</span> <span class="n">im</span><span class="p">)</span> <span class="p">{</span>
<span class="n">CGSize</span> <span class="n">sz</span> <span class="o">=</span> <span class="n">CGSizeMake</span><span class="p">(</span><span class="n">CGImageGetWidth</span><span class="p">(</span><span class="n">im</span><span class="p">),</span> <span class="n">CGImageGetHeight</span><span class="p">(</span><span class="n">im</span><span class="p">));</span>
<span class="n">UIGraphicsBeginImageContextWithOptions</span><span class="p">(</span><span class="n">sz</span><span class="p">,</span> <span class="nb">NO</span><span class="p">,</span> <span class="mi">0</span><span class="p">);</span>
<span class="n">CGContextDrawImage</span><span class="p">(</span><span class="n">UIGraphicsGetCurrentContext</span><span class="p">(),</span> <span class="n">CGRectMake</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="n">sz</span><span class="p">.</span><span class="n">width</span><span class="p">,</span> <span class="n">sz</span><span class="p">.</span><span class="n">height</span><span class="p">),</span> <span class="n">im</span><span class="p">);</span>
<span class="n">CGImageRef</span> <span class="n">result</span> <span class="o">=</span> <span class="p">[</span><span class="n">UIGraphicsGetImageFromCurrentImageContext</span><span class="p">()</span> <span class="nf">CGImage</span><span class="p">];</span>
<span class="n">UIGraphicsEndImageContext</span><span class="p">();</span>
<span class="k">return</span> <span class="n">result</span><span class="p">;</span>
<span class="p">}</span> </code></pre></figure>
<p>现在将之前的代码修改如下:</p>
<figure class="highlight"><pre><code class="language-objc" data-lang="objc"><span class="n">CGContextDrawImage</span><span class="p">(</span><span class="n">con</span><span class="p">,</span> <span class="n">CGRectMake</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="n">sz</span><span class="p">.</span><span class="n">width</span><span class="o">/</span><span class="mi">2</span><span class="p">.</span><span class="mi">0</span><span class="p">,</span><span class="n">sz</span><span class="p">.</span><span class="n">height</span><span class="p">),</span> <span class="n">flip</span><span class="p">(</span><span class="n">marsLeft</span><span class="p">));</span>
<span class="n">CGContextDrawImage</span><span class="p">(</span><span class="n">con</span><span class="p">,</span> <span class="n">CGRectMake</span><span class="p">(</span><span class="n">sz</span><span class="p">.</span><span class="n">width</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="n">sz</span><span class="p">.</span><span class="n">width</span><span class="o">/</span><span class="mi">2</span><span class="p">.</span><span class="mi">0</span><span class="p">,</span><span class="n">sz</span><span class="p">.</span><span class="n">height</span><span class="p">),</span> <span class="n">flip</span><span class="p">(</span><span class="n">marsRight</span><span class="p">));</span> </code></pre></figure>
<p>然而,这里又出现了另外一个问题:在双分辨率的设备上,如果我们的图片文件是高分辨率(@2x)版本,上面的绘图就是错误的。原因在于对于UIImage来说,在加载原始图片时使用imageNamed:方法,它会自动根据所在设备的分辨率类型选择图片,并且UIImage通过设置用来适配的scale属性补偿图片的两倍尺寸。但是一个CGImage对象并没有scale属性,它不知道图片文件的尺寸是否为两倍!所以当调用UIImage的CGImage方法,你不能假定所获得的CGImage尺寸与原始UIImage是一样的。在单分辨率和双分辨率下,一个UIImage对象的size属性值都是一样的,但是双分辨率UIImage对应的CGImage是单分辨率UIImage对应的CGImage的两倍大。所以我们需要修改上面的代码,让其在单双分辨率下都可以工作。代码如下:</p>
<figure class="highlight"><pre><code class="language-objc" data-lang="objc"><span class="n">UIImage</span><span class="o">*</span> <span class="n">mars</span> <span class="o">=</span> <span class="p">[</span><span class="n">UIImage</span> <span class="nf">imageNamed</span><span class="p">:</span><span class="s">@"Mars.png"</span><span class="p">];</span>
<span class="n">CGSize</span> <span class="n">sz</span> <span class="o">=</span> <span class="p">[</span><span class="n">mars</span> <span class="nf">size</span><span class="p">];</span>
<span class="c1">// 转换CGImage并使用对应的CGImage尺寸截取图片的左右部分
</span>
<span class="n">CGImageRef</span> <span class="n">marsCG</span> <span class="o">=</span> <span class="p">[</span><span class="n">mars</span> <span class="nf">CGImage</span><span class="p">];</span>
<span class="n">CGSize</span> <span class="n">szCG</span> <span class="o">=</span> <span class="n">CGSizeMake</span><span class="p">(</span><span class="n">CGImageGetWidth</span><span class="p">(</span><span class="n">marsCG</span><span class="p">),</span> <span class="n">CGImageGetHeight</span><span class="p">(</span><span class="n">marsCG</span><span class="p">));</span>
<span class="n">CGImageRef</span> <span class="n">marsLeft</span> <span class="o">=</span> <span class="n">CGImageCreateWithImageInRect</span><span class="p">(</span><span class="n">marsCG</span><span class="p">,</span><span class="n">CGRectMake</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="n">szCG</span><span class="p">.</span><span class="n">width</span><span class="o">/</span><span class="mi">2</span><span class="p">.</span><span class="mi">0</span><span class="p">,</span><span class="n">szCG</span><span class="p">.</span><span class="n">height</span><span class="p">));</span>
<span class="n">CGImageRef</span> <span class="n">marsRight</span> <span class="o">=</span> <span class="n">CGImageCreateWithImageInRect</span><span class="p">(</span><span class="n">marsCG</span><span class="p">,</span> <span class="n">CGRectMake</span><span class="p">(</span><span class="n">szCG</span><span class="p">.</span><span class="n">width</span><span class="o">/</span><span class="mi">2</span><span class="p">.</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="n">szCG</span><span class="p">.</span><span class="n">width</span><span class="o">/</span><span class="mi">2</span><span class="p">.</span><span class="mi">0</span><span class="p">,</span><span class="n">szCG</span><span class="p">.</span><span class="n">height</span><span class="p">));</span>
<span class="n">UIGraphicsBeginImageContextWithOptions</span><span class="p">(</span><span class="n">CGSizeMake</span><span class="p">(</span><span class="n">sz</span><span class="p">.</span><span class="n">width</span><span class="o">*</span><span class="mi">1</span><span class="p">.</span><span class="mi">5</span><span class="p">,</span> <span class="n">sz</span><span class="p">.</span><span class="n">height</span><span class="p">),</span> <span class="nb">NO</span><span class="p">,</span> <span class="mi">0</span><span class="p">);</span>
<span class="c1">//剩下的和之前的代码一样,修复倒置问题
</span>
<span class="n">CGContextRef</span> <span class="n">con</span> <span class="o">=</span> <span class="n">UIGraphicsGetCurrentContext</span><span class="p">();</span>
<span class="n">CGContextDrawImage</span><span class="p">(</span><span class="n">con</span><span class="p">,</span> <span class="n">CGRectMake</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="n">sz</span><span class="p">.</span><span class="n">width</span><span class="o">/</span><span class="mi">2</span><span class="p">.</span><span class="mi">0</span><span class="p">,</span><span class="n">sz</span><span class="p">.</span><span class="n">height</span><span class="p">),</span><span class="n">flip</span><span class="p">(</span><span class="n">marsLeft</span><span class="p">));</span>
<span class="n">CGContextDrawImage</span><span class="p">(</span><span class="n">con</span><span class="p">,</span> <span class="n">CGRectMake</span><span class="p">(</span><span class="n">sz</span><span class="p">.</span><span class="n">width</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="n">sz</span><span class="p">.</span><span class="n">width</span><span class="o">/</span><span class="mi">2</span><span class="p">.</span><span class="mi">0</span><span class="p">,</span><span class="n">sz</span><span class="p">.</span><span class="n">height</span><span class="p">),</span><span class="n">flip</span><span class="p">(</span><span class="n">marsRight</span><span class="p">));</span>
<span class="n">UIImage</span><span class="o">*</span> <span class="n">im</span> <span class="o">=</span> <span class="n">UIGraphicsGetImageFromCurrentImageContext</span><span class="p">();</span>
<span class="n">UIGraphicsEndImageContext</span><span class="p">();</span>
<span class="n">CGImageRelease</span><span class="p">(</span><span class="n">marsLeft</span><span class="p">);</span>
<span class="n">CGImageRelease</span><span class="p">(</span><span class="n">marsRight</span><span class="p">);</span> </code></pre></figure>
<p>上面的代码初看上去很繁杂,不过不用担心,这里还有另一种修复倒置问题的方案。相对于使用flip函数,你可以在绘图之前将CGImage包装进UIImage中,这样做有两大优点:
1.当UIImage绘图时它会自动修复倒置问题
2.当你从CGImage转化为Uimage时,可调用imageWithCGImage:scale:orientation:方法生成CGImage作为对缩放性的补偿。</p>
<p>所以这是一个解决倒置和缩放问题的自包含方法。</p>
<figure class="highlight"><pre><code class="language-objc" data-lang="objc"><span class="n">UIImage</span><span class="o">*</span> <span class="n">mars</span> <span class="o">=</span> <span class="p">[</span><span class="n">UIImage</span> <span class="nf">imageNamed</span><span class="p">:</span><span class="s">@"Mars.png"</span><span class="p">];</span>
<span class="n">CGSize</span> <span class="n">sz</span> <span class="o">=</span> <span class="p">[</span><span class="n">mars</span> <span class="nf">size</span><span class="p">];</span>
<span class="n">CGImageRef</span> <span class="n">marsCG</span> <span class="o">=</span> <span class="p">[</span><span class="n">mars</span> <span class="nf">CGImage</span><span class="p">];</span>
<span class="n">CGSize</span> <span class="n">szCG</span> <span class="o">=</span> <span class="n">CGSizeMake</span><span class="p">(</span><span class="n">CGImageGetWidth</span><span class="p">(</span><span class="n">marsCG</span><span class="p">),</span> <span class="n">CGImageGetHeight</span><span class="p">(</span><span class="n">marsCG</span><span class="p">));</span>
<span class="n">CGImageRef</span> <span class="n">marsLeft</span> <span class="o">=</span> <span class="n">CGImageCreateWithImageInRect</span><span class="p">(</span><span class="n">marsCG</span><span class="p">,</span> <span class="n">CGRectMake</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="n">szCG</span><span class="p">.</span><span class="n">width</span><span class="o">/</span><span class="mi">2</span><span class="p">.</span><span class="mi">0</span><span class="p">,</span><span class="n">szCG</span><span class="p">.</span><span class="n">height</span><span class="p">));</span>
<span class="n">CGImageRef</span> <span class="n">marsRight</span> <span class="o">=</span> <span class="n">CGImageCreateWithImageInRect</span><span class="p">(</span><span class="n">marsCG</span><span class="p">,</span> <span class="n">CGRectMake</span><span class="p">(</span><span class="n">szCG</span><span class="p">.</span><span class="n">width</span><span class="o">/</span><span class="mi">2</span><span class="p">.</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="n">szCG</span><span class="p">.</span><span class="n">width</span><span class="o">/</span><span class="mi">2</span><span class="p">.</span><span class="mi">0</span><span class="p">,</span><span class="n">szCG</span><span class="p">.</span><span class="n">height</span><span class="p">));</span>
<span class="n">UIGraphicsBeginImageContextWithOptions</span><span class="p">(</span><span class="n">CGSizeMake</span><span class="p">(</span><span class="n">sz</span><span class="p">.</span><span class="n">width</span><span class="o">*</span><span class="mi">1</span><span class="p">.</span><span class="mi">5</span><span class="p">,</span> <span class="n">sz</span><span class="p">.</span><span class="n">height</span><span class="p">),</span> <span class="nb">NO</span><span class="p">,</span> <span class="mi">0</span><span class="p">);</span>
<span class="p">[[</span><span class="n">UIImage</span> <span class="nf">imageWithCGImage</span><span class="p">:</span><span class="n">marsLeft</span> <span class="nf">scale</span><span class="p">:[</span><span class="n">mars</span> <span class="nf">scale</span><span class="p">]</span> <span class="nf">orientation</span><span class="p">:</span><span class="n">UIImageOrientationUp</span><span class="p">]</span> <span class="n">drawAtPoint</span><span class="o">:</span><span class="n">CGPointMake</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">)];</span>
<span class="p">[[</span><span class="n">UIImage</span> <span class="nf">imageWithCGImage</span><span class="p">:</span><span class="n">marsRight</span> <span class="nf">scale</span><span class="p">:[</span><span class="n">mars</span> <span class="nf">scale</span><span class="p">]</span> <span class="nf">orientation</span><span class="p">:</span><span class="n">UIImageOrientationUp</span><span class="p">]</span> <span class="n">drawAtPoint</span><span class="o">:</span><span class="n">CGPointMake</span><span class="p">(</span><span class="n">sz</span><span class="p">.</span><span class="n">width</span><span class="p">,</span><span class="mi">0</span><span class="p">)];</span>
<span class="n">UIImage</span><span class="o">*</span> <span class="n">im</span> <span class="o">=</span> <span class="n">UIGraphicsGetImageFromCurrentImageContext</span><span class="p">();</span>
<span class="n">UIGraphicsEndImageContext</span><span class="p">();</span>
<span class="n">CGImageRelease</span><span class="p">(</span><span class="n">marsLeft</span><span class="p">);</span> <span class="n">CGImageRelease</span><span class="p">(</span><span class="n">marsRight</span><span class="p">);</span> </code></pre></figure>
<p>还有另一种解决倒置问题的方案是在绘制CGImage之前,对上下文应用变换操作,有效地倒置上下文的内部坐标系统。这里先不做讨论。</p>
<h3 id="section-6">为什么会发生倒置问题</h3>
<p>究其原因是因为Core Graphics源于Mac OS X系统,在Mac OS X中,坐标原点在左下方并且正y坐标是朝上的,而在iOS中,原点坐标是在左上方并且正y坐标是朝下的。在大多数情况下,这不会出现任何问题,因为图形上下文的坐标系统是会自动调节补偿的。但是创建和绘制一个CGImage对象时就会暴露出倒置问题。</p>
<p>下面还有对Core Image的操作,在这就不做分析了,感兴趣的可以直接查看官方文档。</p>
<p>原文地址:<a href="http://www.cocoachina.com/industry/20140115/7703.html">iOS绘图教程</a></p>
<h2 id="section-7">原理篇 - 绘制像素到屏幕上</h2>
<p>一个像素是如何绘制到屏幕上去的?有很多种方式将一些东西映射到显示屏上,他们需要调用不同的框架、许多功能和方法的结合体。</p>
<p>这里的内容来自 <a href="https://www.objccn.io/">@ObjC 中国</a> 的文章 <a href="https://www.objccn.io/issue-3-1/">绘制像素到屏幕上</a> 。</p>
<p>讲述了屏幕之后发生的事情。</p>idechaoidechao@163.comFeaturesiOS自动化编译打包2016-11-03T00:00:00+08:002016-11-03T00:00:00+08:00http://www.dechao.net/http://www.dechao.net/blog/iOSAutoBuild<p>从xcodebuild到shenzhen,再到Jenkins,完美演绎自动化操作。</p>
<section id="table-of-contents" class="toc">
<header>
<h1>Features</h1>
</header>
<div id="drawer">
<ul id="markdown-toc">
<li><a href="#xcodebuild" id="markdown-toc-xcodebuild">xcodebuild自动构建命令</a> <ul>
<li><a href="#section" id="markdown-toc-section">简介</a></li>
<li><a href="#section-1" id="markdown-toc-section-1">构建</a></li>
<li><a href="#ipa" id="markdown-toc-ipa">生成ipa文件</a></li>
</ul>
</li>
<li><a href="#shenzhen-" id="markdown-toc-shenzhen-">利用 shenzhen 进行打包</a></li>
<li><a href="#jenkins" id="markdown-toc-jenkins">Jenkins自动化</a> <ul>
<li><a href="#section-2" id="markdown-toc-section-2">安装</a></li>
<li><a href="#section-3" id="markdown-toc-section-3">创建项目</a></li>
<li><a href="#section-4" id="markdown-toc-section-4">构建</a></li>
<li><a href="#section-5" id="markdown-toc-section-5">配置远程仓库</a></li>
<li><a href="#section-6" id="markdown-toc-section-6">便捷设置</a></li>
</ul>
</li>
<li><a href="#section-7" id="markdown-toc-section-7">参考:</a></li>
</ul>
</div>
</section>
<!-- /#table-of-contents -->
<h2 id="xcodebuild">xcodebuild自动构建命令</h2>
<p>确保项目证书等配置都没问题,可以完美运行。</p>
<h3 id="section">简介</h3>
<p>首先说明下使用文档:</p>
<pre class="sunlight-highlight-objective-c">
man xcodebuild
</pre>
<p>基本上现在的包管理都是以pod来的,也就是以workspace的形式,所以基本的形式为:</p>
<pre class="sunlight-highlight-objective-c">
xcodebuild [-project projectname] [-target targetname ...]
[-configuration configurationname] [-sdk [sdkfullpath | sdkname]] [buildaction ...]
[setting=value ...] [-userdefault=value ...]
</pre>
<p>解释两个参数:</p>
<ul>
<li>target,可以通过 xcodebuild -list 查看</li>
<li>configrtion,也可以通过 xcodebuild -list 查看</li>
<li>sdk,可用 xcodebuild -showsdks,一般默认就行</li>
</ul>
<p>可以查看项目配置选项:</p>
<pre class="sunlight-highlight-objective-c">
xcodebuild -target Demo -configuration Debug -showBuildSettings
</pre>
<h3 id="section-1">构建</h3>
<p>基本的构建命令:</p>
<pre class="sunlight-highlight-objective-c">
xcodebuild -workspace Demo.xcworkspace -scheme Demo -configuration Debug -sdk iphoneos10.1
</pre>
<p>命令运行成功后会提示 <code class="highlighter-rouge">** BUILD SUCCEEDED **</code>,一般会在项目目录下生成build文件夹,可以在里面看到你的生成的包。</p>
<p>对于<code class="highlighter-rouge">workspace</code>的形式来说,基本上也差不多:</p>
<pre class="sunlight-highlight-objective-c">
xcodebuild -workspace Demo.xcworkspace -scheme Demo -configuration Debug -sdk iphoneos10.1
</pre>
<blockquote>
<p>好像对workspace构建后不会在项目目录下生成build文件夹,可以在你的命令后面添加SYMROOT=buildDir指定一个build文件夹)。</p>
</blockquote>
<h3 id="ipa">生成ipa文件</h3>
<p>生成文件的命令是<code class="highlighter-rouge">xrun</code>:</p>
<pre class="sunlight-highlight-objective-c">
xcrun -sdk iphoneos -v PackageApplication ./build/Release-iphoneos/Demo.app -o ~/Desktop/Demo.ipa
</pre>
<p>打包成功后,会在桌面找到你的ipa。</p>
<h2 id="shenzhen-">利用 shenzhen 进行打包</h2>
<p>利用github上一个开源项目: <a href="https://github.com/nomad/shenzhen" style="font-family:times;color:blue;font-size:22px"> shenzhen </a>可以在命令行为ios项目进行打包并发布。
具体安装步骤如下:</p>
<pre class="sunlight-highlight-objective-c">
gem install shenzhen
</pre>
<p>如果安装过程出现错误有可能是ruby的源找不到,可以到 <a href="https://ruby.taobao.org/" style="font-family:times;color:blue;font-size:18px"> RubyGems 镜像 </a>改变ruby源。</p>
<p>如果还是出现问题可以更新下gem即可(sudo gem update)。</p>
<p>一切准备完毕就能在控制台上运行ipa命令了:</p>
<pre class="sunlight-highlight-objective-c">
$ ipa
Build and distribute iOS apps (.ipa files)
Commands:
build Create a new .ipa file for your app
distribute:crashlytics Distribute an .ipa file over Crashlytics
distribute:deploygate Distribute an .ipa file over deploygate
distribute:fir Distribute an .ipa file over fir.im
distribute:ftp Distribute an .ipa file over FTP
distribute:hockeyapp Distribute an .ipa file over HockeyApp
distribute:itunesconnect Upload an .ipa file to iTunes Connect
distribute:pgyer Distribute an .ipa file over Pgyer
distribute:rivierabuild Distribute an .ipa file over RivieraBuild
distribute:s3 Distribute an .ipa file over Amazon S3
distribute:testfairy Distribute an .ipa file over TestFairy
help Display global or [command] help documentation
info Show mobile provisioning information about an .ipa file
</pre>
<p>可以看出通过bulid参数就能创建ipa文件,比如输入命令:</p>
<pre class="sunlight-highlight-objective-c">
ipa build
</pre>
<p>会直接在当前目录下生成ipa文件以及dSYM文件。</p>
<p>如果你的工程项目有很多targets,则ipa bulid命令会列出现在所有targets,我们可以选择一个进行打包。</p>
<p>如简单的打包蒲公英事例:</p>
<pre class="sunlight-highlight-objective-c">
ipa distribute:pgyer -u USER_KEY -a APP_KEY
</pre>
<p>iTunes Connect Distribution:</p>
<pre class="sunlight-highlight-objective-c">
ipa distribute:itunesconnect -a me@email.com -p myitunesconnectpassword -i appleid --upload
</pre>
<h2 id="jenkins">Jenkins自动化</h2>
<h3 id="section-2">安装</h3>
<p>在 Mac 环境下,我们需要先安装 JDK,在<a href="https://jenkins.io/">Jenkins 的官网</a>下载最新的 war 包。下载完成后,打开终端,进入到 war 包所在目录,执行以下命令:</p>
<pre class="sunlight-highlight-objective-c">
java -jar jenkins.war --httpPort=8080
#或简单的写法
java -jar jenkins.war
</pre>
<p>待Jenkins启动后,在浏览器中输出一下地址:
http://localhost:8080</p>
<p>这样就打开Jenkins管理页面了。</p>
<p>基本界面如下:</p>
<figuer>
<img src="http://www.dechao.net/images/iOSAutoBuild/build_3_1.png" />
<figcaption>Jenkins开始页面</figcaption>
</figuer>
<h3 id="section-3">创建项目</h3>
<p>点击左上角的新建,或是店家开始创建一个新任务,出现下面的页面:</p>
<figuer>
<img src="http://www.dechao.net/images/iOSAutoBuild/build_3_2.png" />
<figcaption>Jenkins新建页面</figcaption>
</figuer>
<p>这里输入的名字为Demo,并选择 <code class="highlighter-rouge">构建一个自由风格的软件项目</code>,点击OK进入到下一页面:</p>
<figuer>
<img src="http://www.dechao.net/images/iOSAutoBuild/build_3_3.png" />
<figcaption>Jenkins新建页面</figcaption>
</figuer>
<p>其中这里在General中,点击高级,先使用本地项目做测试:</p>
<figuer>
<img src="http://www.dechao.net/images/iOSAutoBuild/build_3_4.png" />
<figcaption>Jenkins新建本地页面</figcaption>
</figuer>
<p>源码管理暂选None,构建触发器和构建环境不需要选择:</p>
<figuer>
<img src="http://www.dechao.net/images/iOSAutoBuild/build_3_5.png" />
<figcaption>Jenkins新建本地页面</figcaption>
</figuer>
<p>构建,选择shell形式,使用<code class="highlighter-rouge">shenzhen</code>来构建并直接上传到蒲公英:</p>
<figuer>
<img src="http://www.dechao.net/images/iOSAutoBuild/build_3_6.png" />
<img src="http://www.dechao.net/images/iOSAutoBuild/build_3_7.png" />
<figcaption>Jenkins使用shenzhen构建</figcaption>
</figuer>
<p>其中,<code class="highlighter-rouge">USER_KEY</code> 和 <code class="highlighter-rouge">API_KEY</code> 可以在蒲公英的「账户设置」中找到,之后进行相应替换。</p>
<p>构建后的操作我们也不需要,直接点击保存。</p>
<h3 id="section-4">构建</h3>
<p>保存之后进入到项目工作目录,点击立即构建:</p>
<figuer>
<img src="http://www.dechao.net/images/iOSAutoBuild/build_3_8.png" />
<figcaption>Jenkins构建</figcaption>
</figuer>
<p>会在构建历史中显示构建结构,点击进入查看:</p>
<figuer>
<img src="http://www.dechao.net/images/iOSAutoBuild/build_3_9.png" />
<figcaption>Jenkins</figcaption>
</figuer>
<p>点击 <code class="highlighter-rouge">Console Output</code> 查看日志信息:</p>
<figuer>
<img src="http://www.dechao.net/images/iOSAutoBuild/build_3_10.png" />
<figcaption>Jenkins</figcaption>
</figuer>
<p>会有一堆信息,成功的话会提示去蒲公英查看。</p>
<p>进入到蒲公英后台,会发现我们的应用已经发布上去,可以进行测试了。</p>
<h3 id="section-5">配置远程仓库</h3>
<p>首先配置SSH:</p>
<figuer>
<img src="http://www.dechao.net/images/iOSAutoBuild/build_3_11.png" />
<figcaption>Jenkins</figcaption>
</figuer>
<p>创建global的类型:</p>
<figuer>
<img src="http://www.dechao.net/images/iOSAutoBuild/build_3_12.png" />
<figcaption>Jenkins</figcaption>
</figuer>
<p>进去后点击左侧的 ` Add Credentials`:</p>
<figuer>
<img src="http://www.dechao.net/images/iOSAutoBuild/build_3_13.png" />
<figcaption>Jenkins</figcaption>
</figuer>
<p>选择SSH类型,输入自己的用户名,<code class="highlighter-rouge">Private Key</code> 直接从 <code class="highlighter-rouge">~/.ssh</code> 目录下读取就好。</p>
<p>工程的配置,跟本地的区别就是不需要配置自定义的工作空间,同时选择源码管理中的<code class="highlighter-rouge">Git</code>,填写对应的地址信息:</p>
<figuer>
<img src="http://www.dechao.net/images/iOSAutoBuild/build_3_14.png" />
<figcaption>Jenkins</figcaption>
</figuer>
<p>然后其他的构建、查看过程都一致。</p>
<p>到蒲公英上检查,果然存在。完美!</p>
<h3 id="section-6">便捷设置</h3>
<p>以上面的方式运行的<code class="highlighter-rouge">Jenkins</code>的,命令行是不能关闭的,为了方便的话,需要设置在后台运行:</p>
<pre class="sunlight-highlight-objective-c">
nohup java -jar jenkins.war &
</pre>
<p>将命令写入到sh文件中,比如就叫 start.sh,运行的时候直接跑脚本就好,附上文件内容:</p>
<pre class="sunlight-highlight-objective-c">
#!/bin/sh
nohup java -jar /Users/home/Desktop/jenkinsWorkspace/jenkins/jenkins.war &
</pre>
<p>同样的,关闭命令也可以直接使用,不过在使用关闭之前,需要下载个 <code class="highlighter-rouge">jenkins-cli.jar</code>文件:</p>
<blockquote>
<p>首页 -> 系统管理 -> Jenkins CLI</p>
</blockquote>
<p>里面同样包含好多其他命令,可以根据自己需要来调试。</p>
<p>设置关闭 <code class="highlighter-rouge">Jenkins</code> 的脚本:</p>
<pre class="sunlight-highlight-objective-c">
#!/bin/sh
java -jar /Users/home/Documents/jenkins/jenkins-cli.jar -s http://localhost:8080/ shutdown
</pre>
<p>别忘记修改为自己的路径。</p>
<p>一般的命令可以直接在网址上体现出来,比如重启: <a href="http://localhost:8080/restart">http://localhost:8080/restart</a></p>
<p>上面既然用了iOS的打包,所以脚本感觉也是用swift来写也是比较配套的,在这里就不贴出来了,喜欢研究的童鞋就google一下~,我将之命名为<code class="highlighter-rouge">begin.swift</code>和<code class="highlighter-rouge">end.swift</code>,恩,感觉还是不错的</p>
<h2 id="section-7">参考:</h2>
<p><a href="http://www.jianshu.com/p/3f43370437d2">iOS 自动构建命令——xcodebuild</a></p>
<p><a href="http://www.fx114.net/qa-58-777704.aspx">分享查询网</a></p>
<p><a href="http://www.jianshu.com/p/9934a678c17c">用Jenkins给iOS自动打包</a></p>
<p><a href="https://www.pgyer.com/doc/view/jenkins_ios">蒲公英</a></p>idechaoidechao@163.com从xcodebuild到shenzhen,再到Jenkins,完美演绎自动化操作。白话Runtime之iVar2016-06-28T00:00:00+08:002016-06-28T00:00:00+08:00http://www.dechao.net/http://www.dechao.net/blog/Ivar<p>实例变量增加了私有属性的设置,同时直接使用实例变量比使用属性的方式提高了运行速度。</p>
<section id="table-of-contents" class="toc">
<header>
<h1>Features</h1>
</header>
<div id="drawer">
<ul id="markdown-toc">
<li><a href="#ivar" id="markdown-toc-ivar">iVar变量介绍</a></li>
<li><a href="#ivar-1" id="markdown-toc-ivar-1">输出所有的iVar的值</a></li>
<li><a href="#runtimeivar" id="markdown-toc-runtimeivar">使用Runtime修改iVar的值</a></li>
<li><a href="#ivar-vs-property" id="markdown-toc-ivar-vs-property">iVar vs Property</a></li>
<li><a href="#section" id="markdown-toc-section">参考</a></li>
</ul>
</div>
</section>
<!-- /#table-of-contents -->
<h2 id="ivar">iVar变量介绍</h2>
<p>这里首先认为大家都对Runtime有了一定的了解,不清楚的建议看看 <a href="http://southpeak.github.io/categories/objectivec/" style="font-family:times;color:blue;font-size:18px"> 南峰子的技术博客 </a>。</p>
<p>还是稍微介绍下Ivar的的结构,Ivar其实是一个结构体指针:</p>
<figure class="highlight"><pre><code class="language-c" data-lang="c"><span class="k">typedef</span> <span class="k">struct</span> <span class="n">objc_ivar</span> <span class="o">*</span><span class="n">Ivar</span><span class="p">;</span></code></pre></figure>
<p>再来查看这个结构体的结构:</p>
<figure class="highlight"><pre><code class="language-c" data-lang="c"><span class="k">struct</span> <span class="n">objc_ivar</span> <span class="p">{</span>
<span class="kt">char</span> <span class="o">*</span><span class="n">ivar_name</span> <span class="c1">// 变量名
</span> <span class="kt">char</span> <span class="o">*</span><span class="n">ivar_type</span> <span class="c1">// 变量类型
</span> <span class="kt">int</span> <span class="n">ivar_offset</span> <span class="c1">// 基地址偏移字节
</span> <span class="cp">#ifdef __LP64__
</span> <span class="kt">int</span> <span class="n">space</span>
<span class="cp">#endif
</span><span class="p">}</span> </code></pre></figure>
<h2 id="ivar-1">输出所有的iVar的值</h2>
<p>首先定义一个类,包含几个属性以及变量的值:</p>
<div class="highlighter-rouge"><pre class="highlight"><code><span class="k">@interface</span> <span class="nc">Model</span> <span class="p">:</span> <span class="nc">NSObject</span>
<span class="k">@property</span> <span class="p">(</span><span class="n">nonatomic</span><span class="p">,</span> <span class="n">strong</span><span class="p">)</span> <span class="n">NSString</span> <span class="o">*</span><span class="n">name</span><span class="p">;</span>
<span class="k">@property</span> <span class="p">(</span><span class="n">nonatomic</span><span class="p">,</span> <span class="n">assign</span><span class="p">)</span> <span class="kt">int</span> <span class="n">age</span><span class="p">;</span>
<span class="k">@end</span>
</code></pre>
</div>
<p>同时在extension中生命iVar变量:</p>
<div class="highlighter-rouge"><pre class="highlight"><code><span class="k">@interface</span> <span class="nc">Model</span> <span class="p">()</span> <span class="p">{</span>
<span class="k">@private</span>
<span class="kt">int</span> <span class="n">_value</span><span class="p">;</span>
<span class="n">NSString</span> <span class="o">*</span><span class="n">_title</span><span class="p">;</span>
<span class="p">}</span>
<span class="k">@end</span>
</code></pre>
</div>
<p>输出的方式如下:</p>
<figure class="highlight"><pre><code class="language-c" data-lang="c"><span class="n">Model</span> <span class="o">*</span><span class="n">obj</span> <span class="o">=</span> <span class="p">[[</span><span class="n">Model</span> <span class="n">alloc</span><span class="p">]</span> <span class="n">init</span><span class="p">];</span>
<span class="kt">uint32_t</span> <span class="n">outCount</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
<span class="n">Ivar</span> <span class="o">*</span><span class="n">ivars</span> <span class="o">=</span> <span class="n">class_copyIvarList</span><span class="p">(</span><span class="n">object_getClass</span><span class="p">(</span><span class="n">obj</span><span class="p">),</span> <span class="o">&</span><span class="n">outCount</span><span class="p">);</span>
<span class="k">for</span> <span class="p">(</span><span class="kt">uint32_t</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">i</span> <span class="o"><</span> <span class="n">outCount</span><span class="p">;</span> <span class="n">i</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
<span class="n">Ivar</span> <span class="n">ivar</span> <span class="o">=</span> <span class="n">ivars</span><span class="p">[</span><span class="n">i</span><span class="p">];</span>
<span class="k">const</span> <span class="kt">char</span> <span class="o">*</span><span class="n">name</span> <span class="o">=</span> <span class="n">ivar_getName</span><span class="p">(</span><span class="n">ivar</span><span class="p">);</span>
<span class="k">const</span> <span class="kt">char</span> <span class="o">*</span><span class="n">type</span> <span class="o">=</span> <span class="n">ivar_getTypeEncoding</span><span class="p">(</span><span class="n">ivar</span><span class="p">);</span>
<span class="kt">ptrdiff_t</span> <span class="n">offset</span> <span class="o">=</span> <span class="n">ivar_getOffset</span><span class="p">(</span><span class="n">ivar</span><span class="p">);</span>
<span class="n">NSLog</span><span class="p">(</span><span class="err">@</span><span class="s">"name=%s---type=%s---offset=%td"</span><span class="p">,</span> <span class="n">name</span><span class="p">,</span> <span class="n">type</span><span class="p">,</span> <span class="n">offset</span><span class="p">);</span>
<span class="p">}</span>
<span class="n">free</span><span class="p">(</span><span class="n">ivars</span><span class="p">);</span></code></pre></figure>
<p>输出:</p>
<figure class="highlight"><pre><code class="language-objc" data-lang="objc"><span class="n">name</span><span class="o">=</span><span class="n">_value</span><span class="o">---</span><span class="n">type</span><span class="o">=</span><span class="n">i</span><span class="o">---</span><span class="n">offset</span><span class="o">=</span><span class="mi">8</span>
<span class="n">name</span><span class="o">=</span><span class="n">_title</span><span class="o">---</span><span class="n">type</span><span class="o">=</span><span class="s">@"NSString"</span><span class="o">---</span><span class="n">offset</span><span class="o">=</span><span class="mi">16</span>
<span class="n">name</span><span class="o">=</span><span class="n">_age</span><span class="o">---</span><span class="n">type</span><span class="o">=</span><span class="n">i</span><span class="o">---</span><span class="n">offset</span><span class="o">=</span><span class="mi">24</span>
<span class="n">name</span><span class="o">=</span><span class="n">_name</span><span class="o">---</span><span class="n">type</span><span class="o">=</span><span class="s">@"NSString"</span><span class="o">---</span><span class="n">offset</span><span class="o">=</span><span class="mi">32</span></code></pre></figure>
<p>Type Encodings可参考 <a href="https://developer.apple.com/library/ios/documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Articles/ocrtTypeEncodings.html">Objective-C Runtime Programming Guide</a></p>
<h2 id="runtimeivar">使用Runtime修改iVar的值</h2>
<p>对于iVar的值,相当于私有属性,外部是无法访问的。不过在Runtime面前,一切都是小小case。</p>
<p>在外部可以通过runtime的方法来修改他:</p>
<figure class="highlight"><pre><code class="language-c" data-lang="c"><span class="n">Ivar</span> <span class="n">ivar2</span> <span class="o">=</span> <span class="n">class_getInstanceVariable</span><span class="p">(</span><span class="n">object_getClass</span><span class="p">(</span><span class="n">obj</span><span class="p">),</span> <span class="s">"_title"</span><span class="p">);</span>
<span class="n">object_setIvar</span><span class="p">(</span><span class="n">obj</span><span class="p">,</span> <span class="n">ivar2</span><span class="p">,</span> <span class="err">@</span><span class="s">"newTiele"</span><span class="p">);</span></code></pre></figure>
<p>可以给Model类暴露个打印的方法,在修改完数值之后调用方法观察数值是否发生了改变。</p>
<p>对于上面方法的原型:</p>
<figure class="highlight"><pre><code class="language-c" data-lang="c"><span class="kt">void</span> <span class="n">object_setIvar</span><span class="p">(</span><span class="n">id</span> <span class="n">obj</span><span class="p">,</span> <span class="n">Ivar</span> <span class="n">ivar</span><span class="p">,</span> <span class="n">id</span> <span class="n">value</span><span class="p">)</span> </code></pre></figure>
<p>俩参数为id,所以不能对基本类型做修改,可以通过偏移量的方式来获取,修改的方式如下:</p>
<figure class="highlight"><pre><code class="language-c" data-lang="c"><span class="n">Ivar</span> <span class="n">ivar1</span> <span class="o">=</span> <span class="n">class_getInstanceVariable</span><span class="p">(</span><span class="n">object_getClass</span><span class="p">(</span><span class="n">obj</span><span class="p">),</span> <span class="s">"_value"</span><span class="p">);</span>
<span class="kt">int</span> <span class="o">*</span><span class="n">ivarPtr2</span> <span class="o">=</span> <span class="p">(</span><span class="kt">int</span> <span class="o">*</span><span class="p">)((</span><span class="kt">uint8_t</span> <span class="o">*</span><span class="p">)(</span><span class="kt">long</span><span class="p">)</span><span class="n">obj</span> <span class="o">+</span> <span class="n">ivar_getOffset</span><span class="p">(</span><span class="n">ivar1</span><span class="p">));</span>
<span class="o">*</span><span class="n">ivarPtr2</span> <span class="o">=</span> <span class="mi">222</span><span class="p">;</span></code></pre></figure>
<p>这样我们就能随心所欲的修改各种变量的值了。</p>
<h2 id="ivar-vs-property">iVar vs Property</h2>
<p>现在声明的属性,如果没有声明为<font color="#CF0FA0" size="5px"><i>dynamic</i></font>,默认都是<font color="#CF0FA0" size="5px"><i>synthesize</i></font>的。也就是会自动合成iVar变量。</p>
<p>那什么时候使用属性,什么时候直接使用实例变量呢?</p>
<p>首先要了解通过属性的方式来设置,其实内部还是设置实例变量的方式,无非是多了自己对象的判断以及对于引用计数的管理。所以直接访问实例变量的方式要快一些。</p>
<p>下面给出几种参考:</p>
<ol>
<li>
<p>如果一个对象在它的生命周期中只在 <font color="#CF0FA0" size="5px"><i>alloc & init</i></font> 时对引用计数做了 <em>retain</em> 操作,可直接使用实例变量的方式。</p>
</li>
<li>
<p>如果一个对象经常改变,使用属性的方式,而且使用点语法的方法进行访问(在本身的 <i>getter & setter * dealloc & initializers</i> 等方法里使用实例变量)。</p>
</li>
<li>
<p>私有变量的方式可以通过 <em>extension</em> 的方法,在 <em>.m</em> 文件里声明;暴露出去的方法需要在 <em>.h</em> 文件中以属性的方式来声明。</p>
</li>
<li>
<p>某些通过 <i>setter & getter</i> 方法访问的情形,只能使用属性。比如通过懒加载的方式来进行某些优化,设置观察者等等。</p>
</li>
<li>
<p>通过某些方法访问属性的时候,比如增加 <em>KVC</em> 的 <em>KeyPath</em>,一般通过他的 <em>getter</em> 方法,使用为 <font color="#CF0FA0" size="5px"><i>NSStringFromSelector(@selector(isFinished))</i></font> 来访问, 而非直接设置 <i>isFinished</i> 的方式。</p>
</li>
</ol>
<p>比如生命一个 <em>NSMutableArray</em>, 基本不对他的引用计数做修改,只是使用它做些存储操作,可直接使用实例变量。</p>
<h2 id="section">参考</h2>
<ul>
<li><a href="http://stackoverflow.com/questions/5031230/must-every-ivar-be-a-property">Stack Overflow - Must every ivar be a property?</a></li>
<li><a href="http://stackoverflow.com/questions/9086736/why-would-you-use-an-ivar">Stack Overflow - Why would you use an ivar?</a></li>
<li><a href="https://blog.0xbbc.com/2014/10/objective-c-runtime-ivar/comment-page-1/#comment-578">0xBBC - Objective-C Runtime - Ivar</a></li>
</ul>idechaoidechao@163.com实例变量增加了私有属性的设置,同时直接使用实例变量比使用属性的方式提高了运行速度。高效LLDB调试技巧2016-02-27T00:00:00+08:002016-02-27T00:00:00+08:00http://www.dechao.net/http://www.dechao.net/blog/lldb<p>LLDB是Xcode默认的调试器,它与LLVM编译器一起,带给我们更丰富的流程控制和数据检测的调试功能。平时用Xcode运行程序,实际走的都是LLDB。熟练使用LLDB,可以让你debug事半功倍。</p>
<p>LLDB 命令还是不少的,但有些都集成在 Xcode 的图形化界面中,没必要再去手动操作,这里主要说一下使用起来更加高效的一些命令。</p>
<section id="table-of-contents" class="toc">
<header>
<h1>Features</h1>
</header>
<div id="drawer">
<ul id="markdown-toc">
<li><a href="#help" id="markdown-toc-help">help</a></li>
<li><a href="#p--call--po" id="markdown-toc-p--call--po">p & call & po</a></li>
<li><a href="#expression" id="markdown-toc-expression">expression</a></li>
<li><a href="#thread" id="markdown-toc-thread">thread</a> <ul>
<li><a href="#backtrace--bt" id="markdown-toc-backtrace--bt">backtrace & bt</a></li>
<li><a href="#thread-return" id="markdown-toc-thread-return">thread return</a></li>
</ul>
</li>
<li><a href="#target" id="markdown-toc-target">target</a> <ul>
<li><a href="#image-lookup-address" id="markdown-toc-image-lookup-address">image lookup –address</a></li>
<li><a href="#image-lookup-name" id="markdown-toc-image-lookup-name">image lookup –name</a></li>
<li><a href="#image-lookup-type" id="markdown-toc-image-lookup-type">image lookup –type</a></li>
</ul>
</li>
<li><a href="#section" id="markdown-toc-section">其他</a></li>
<li><a href="#debug" id="markdown-toc-debug">常用的Debug快捷键</a></li>
<li><a href="#end" id="markdown-toc-end">End</a></li>
</ul>
</div>
</section>
<!-- /#table-of-contents -->
<h2 id="help">help</h2>
<p>和大多数命令一样,<em>help</em> 命令会显示出所有的命令列表,对于相关的操作可以直接查看。
若想查看某一条命令的话,直接在 <em>help</em> 后面加上对应的命令名称。如:</p>
<pre>
help print
</pre>
<h2 id="p--call--po">p & call & po</h2>
<p>先说 <em>p</em> 和 <em>call</em>, 二者都是 <em>expression –</em> 的别名, <em>p</em> 为 <em>print</em> 的简写,同时可以写为 <em>pri</em>,打印某个东西,可以i是变量和表达式; <em>call</em> 为调用某个方法,输出变量也是可以的。</p>
<p><em>po</em> 一般用于打印对象,是 <em>expression -O –</em> 的别名。</p>
<p><em>p</em> 和 <em>po</em> 的区别在于使用 <em>po</em> 只会输出对应的值,而 <em>p</em> 则会返回值的类型以及命令结果的引用名。如:</p>
<pre>
(lldb) p self.model.number
(float) $4 = 98
(lldb) p self.model.name
(NSString *) $5 = nil
(lldb) po self.model.number
98
(lldb) po self.model.words
Hello
</pre>
<h2 id="expression">expression</h2>
<p><em>expression</em> 命令的作用是执行一个表达式,并将表达式返回的结果输出。expression的完整语法是这样的:</p>
<pre>
expression (cmd-options) -- (expr)
</pre>
<p>说明下参数:</p>
<ol>
<li>(cmd-options):命令选项,一般情况下使用默认的即可,不需要特别标明。</li>
<li>– –: 命令选项结束符,表示所有的命令选项已经设置完毕,如果没有命令选项,–可以省略。</li>
<li>(expr): 要执行的表达式</li>
</ol>
<p>说 <em>expression</em> 是LLDB里面最重要的命令都不为过。因为他能实现2个功能。</p>
<ul>
<li>执行某个表达式。 我们在代码运行过程中,可以通过执行某个表达式来动态改变程序运行的轨迹。 假如我们在运行过程中,突然想把 <em>self.view</em> 颜色改成红色,看看效果。我们不必写下代码,重新run,只需暂停程序,用expression改变颜色,再刷新一下界面,就能看到效果</li>
</ul>
<pre>
// 改变颜色
(lldb) expression -- self.view.backgroundColor = [UIColor redColor]
// 刷新界面
(lldb) expression -- (void)[CATransaction flush]
</pre>
<ul>
<li>将返回值输出。 也就是说我们可以通过expression来打印东西。 假如我们想打印 <em>self.view</em> :</li>
</ul>
<pre>
(lldb) expression -- self.view
(UIView *) $1 = 0x00007fe322c18a10
</pre>
<h2 id="thread">thread</h2>
<h3 id="backtrace--bt">backtrace & bt</h3>
<p>此命令一般用于将线程的堆栈打印出来,一般在程序出现 crash的时候调用。如;</p>
<pre>
(lldb) thread backtrace
* thread #1: tid = 0xdd42, 0x000000010afb380b libobjc.A.dylib`objc_msgSend + 11, queue = 'com.apple.main-thread', stop reason = EXC_BAD_ACCESS (code=EXC_I386_GPFLT)
frame #0: 0x000000010afb380b libobjc.A.dylib`objc_msgSend + 11
* frame #1: 0x000000010aa9f75e TLLDB`-[ViewController viewDidLoad](self=0x00007fa270e1f440, _cmd="viewDidLoad") + 174 at ViewController.m:23
frame #2: 0x000000010ba67f98 UIKit`-[UIViewController loadViewIfRequired] + 1198
frame #3: 0x000000010ba682e7 UIKit`-[UIViewController view] + 27
frame #4: 0x000000010b93eab0 UIKit`-[UIWindow addRootViewControllerViewIfPossible] + 61
frame #5: 0x000000010b93f199 UIKit`-[UIWindow _setHidden:forced:] + 282
frame #6: 0x000000010b950c2e UIKit`-[UIWindow makeKeyAndVisible] + 42
</pre>
<p><em>bt</em> 为 <em>thread backtrace</em> 的别名,直接使用 <em>bt</em> 和使用上面那一长串是一个效果。</p>
<h3 id="thread-return">thread return</h3>
<p>Debug的时候,也许会因为各种原因,我们不想让代码执行某个方法,或者要直接返回一个想要的值。这时候就该thread return上场了。</p>
<pre>
thread return [expr]
</pre>
<p>thread return可以接受一个表达式,调用命令之后直接从当前的堆栈中返回表达式的值。</p>
<p>e.g: 我们有一个 <em>someMethod</em> 方法,默认情况下是返回YES。我们想要让他返回NO</p>
<figure>
<img src="http://www.dechao.net/images/asset/lldb_1_1.jpg" />
</figure>
<p>我们只需在方法的开始位置加一个断点,当程序中断的时候,输入命令即可:</p>
<pre>
(lldb) thread return NO
</pre>
<p>效果相当于在断点位置直接调用 <strong>return NO;</strong>,不会执行断点后面的代码.</p>
<h2 id="target">target</h2>
<p>对于target这个命令,我们用得最多的可能就是 <em>target modules lookup</em>。由于 LLDB 给 <em>target modules</em> 取了个别名 <em>image</em>,所以这个命令我们又可以写成 <em>image lookup</em>。</p>
<h3 id="image-lookup-address">image lookup –address</h3>
<p>当我们有一个地址,想查找这个地址具体对应的文件位置,可以使用 <em>image lookup –address</em> ,简写为 <em>image lookup -a</em>。 e.g: 当我们发生一个crash</p>
<pre>
*** Terminating app due to uncaught exception 'NSRangeException', reason: '*** -[__NSArray0 objectAtIndex:]: index 1 beyond bounds for empty NSArray'
*** First throw call stack:
(
0 CoreFoundation 0x000000010accde65 __exceptionPreprocess + 165
1 libobjc.A.dylib 0x000000010a746deb objc_exception_throw + 48
2 CoreFoundation 0x000000010ac7c395 -[__NSArray0 objectAtIndex:] + 101
3 TLLDB 0x000000010a1c3e36 -[ViewController viewDidLoad] + 86
4 UIKit 0x000000010b210f98 -[UIViewController loadViewIfRequired] + 1198
5 UIKit 0x000000010b2112e7 -[UIViewController view] + 27
</pre>
<p>我们可以看到是由于-[__NSArray0 objectAtIndex:]:超出边界而导致的crash,但是objectAtIndex:的代码到底在哪儿呢?</p>
<pre>
(lldb) image lookup -a 0x000000010a1c3e36
Address: TLLDB[0x0000000100000e36] (TLLDB.__TEXT.__text + 246)
Summary: TLLDB`-[ViewController viewDidLoad] + 86 at ViewController.m:32
</pre>
<p>根据0x000000010a1c3e36 -[ViewController viewDidLoad]里面的地址,使用image lookup –address查找,我们可以看到代码位置在ViewController.m里面的32行。</p>
<h3 id="image-lookup-name">image lookup –name</h3>
<p>当我们想查找一个方法或者符号的信息,比如所在文件位置等。我们可以使用 <em>image lookup –name</em> ,简写为 <em>image lookup -n</em>。</p>
<p>e.g: 刚刚遇到的真问题,某个第三方SDK用了一个我们项目里原有的第三方库,库里面对 NSDictionary 添加了 category 。也就是有2个 class 对 NSDictionary 添加了名字相同的 category,项目中调用自己的 category 的地方实际走到了第三方SDK里面去了。最大的问题是,这2个同名 category 方法行为并不一致,导致出现 bug</p>
<p>现在问题来了,怎么寻找到底是哪个第三方SDK?方法完全包在.a里面。</p>
<p>其实只需使用image lookup -n即可:</p>
<pre>
(lldb) image lookup -n dictionaryWithXMLString:
2 matches found in /Users/jiangliancheng/Library/Developer/Xcode/DerivedData/VideoIphone-aivsnqmlwjhxapdlvmdmrubbdxpq/Build/Products/Debug-iphoneos/BaiduIphoneVideo.app/BaiduIphoneVideo:
Address: BaiduIphoneVideo[0x00533a7c] (BaiduIphoneVideo.__TEXT.__text + 5414908)
Summary: BaiduIphoneVideo`+[NSDictionary(SAPIXmlDictionary) dictionaryWithXMLString:] at XmlDictionary.m
Module: file = "/Users/jiangliancheng/Library/Developer/Xcode/DerivedData/VideoIphone-aivsnqmlwjhxapdlvmdmrubbdxpq/Build/Products/Debug-iphoneos/BaiduIphoneVideo.app/BaiduIphoneVideo", arch = "armv7"
CompileUnit: id = {0x00000000}, file = "/Users/jiangliancheng/Development/Work/iOS_ShareLib/SharedLib/Srvcs/BDPassport4iOS/BDPassport4iOS/SAPI/Extensive/ThirdParty/XMLDictionary/XmlDictionary.m", language = "Objective-C"
Function: id = {0x23500000756}, name = "+[NSDictionary(SAPIXmlDictionary) dictionaryWithXMLString:]", range = [0x005a6a7c-0x005a6b02)
FuncType: id = {0x23500000756}, decl = XmlDictionary.m:189, clang_type = "NSDictionary *(NSString *)"
Blocks: id = {0x23500000756}, range = [0x005a6a7c-0x005a6b02)
LineEntry: [0x005a6a7c-0x005a6a98): /Users/jiangliancheng/Development/Work/iOS_ShareLib/SharedLib/Srvcs/BDPassport4iOS/BDPassport4iOS/SAPI/Extensive/ThirdParty/XMLDictionary/XmlDictionary.m
Symbol: id = {0x0000f2d5}, range = [0x005a6a7c-0x005a6b04), name="+[NSDictionary(SAPIXmlDictionary) dictionaryWithXMLString:]"
Variable: id = {0x23500000771}, name = "self", type = "Class", location = [sp+32], decl =
Variable: id = {0x2350000077e}, name = "_cmd", type = "SEL", location = [sp+28], decl =
Variable: id = {0x2350000078b}, name = "string", type = "NSString *", location = [sp+24], decl = XmlDictionary.m:189
Variable: id = {0x23500000799}, name = "data", type = "NSData *", location = [sp+20], decl = XmlDictionary.m:192
Address: BaiduIphoneVideo[0x012ee160] (BaiduIphoneVideo.__TEXT.__text + 19810016)
Summary: BaiduIphoneVideo`+[NSDictionary(XMLDictionary) dictionaryWithXMLString:] at XMLDictionary.m
Module: file = "/Users/jiangliancheng/Library/Developer/Xcode/DerivedData/VideoIphone-aivsnqmlwjhxapdlvmdmrubbdxpq/Build/Products/Debug-iphoneos/BaiduIphoneVideo.app/BaiduIphoneVideo", arch = "armv7"
CompileUnit: id = {0x00000000}, file = "/Users/wingle/Workspace/qqlive4iphone/iphone_4.0_fabu_20150601/Common_Proj/mobileTAD/VIDEO/Library/Third Party/XMLDictionary/XMLDictionary.m", language = "Objective-C"
Function: id = {0x79900000b02}, name = "+[NSDictionary(XMLDictionary) dictionaryWithXMLString:]", range = [0x01361160-0x0136119a)
FuncType: id = {0x79900000b02}, decl = XMLDictionary.m:325, clang_type = "NSDictionary *(NSString *)"
Blocks: id = {0x79900000b02}, range = [0x01361160-0x0136119a)
LineEntry: [0x01361160-0x01361164): /Users/wingle/Workspace/qqlive4iphone/iphone_4.0_fabu_20150601/Common_Proj/mobileTAD/VIDEO/Library/Third Party/XMLDictionary/XMLDictionary.m
Symbol: id = {0x0003a1e9}, range = [0x01361160-0x0136119c), name="+[NSDictionary(XMLDictionary) dictionaryWithXMLString:]"
Variable: id = {0x79900000b1e}, name = "self", type = "Class", location = r0, decl =
Variable: id = {0x79900000b2c}, name = "_cmd", type = "SEL", location = r1, decl =
Variable: id = {0x79900000b3a}, name = "string", type = "NSString *", location = r2, decl = XMLDictionary.m:325
Variable: id = {0x79900000b4a}, name = "data", type = "NSData *", location = r2, decl = XMLDictionary.m:327
</pre>
<p>东西有点多,我们只需关注里面的file这一行:</p>
<pre>
CompileUnit: id = {0x00000000}, file = "/Users/jiangliancheng/Development/Work/iOS_ShareLib/SharedLib/Srvcs/BDPassport4iOS/BDPassport4iOS/SAPI/Extensive/ThirdParty/XMLDictionary/XmlDictionary.m", language = "Objective-C"
CompileUnit: id = {0x00000000}, file = "/Users/wingle/Workspace/qqlive4iphone/iphone_4.0_fabu_20150601/Common_Proj/mobileTAD/VIDEO/Library/Third Party/XMLDictionary/XMLDictionary.m", language = "Objective-C"
</pre>
<p>可以清晰的看到,LLDB给我们找出来了这个方法的位置。 当然这个命令也可以找到方法的其他相关信息,比如参数等.</p>
<h3 id="image-lookup-type">image lookup –type</h3>
<p>当我们想查看一个类型的时候,可以使用 <em>image lookup –type</em>,简写为<em>image lookup -t</em>:</p>
<p>e.g: 我们来看看Model的类型:</p>
<pre>
(lldb) image lookup -t Model
Best match found in /Users/jiangliancheng/Library/Developer/Xcode/DerivedData/TLLDB-beqoowskwzbttrejseahdoaivpgq/Build/Products/Debug-iphonesimulator/TLLDB.app/TLLDB:
id = {0x30000002f}, name = "Model", byte-size = 32, decl = Modek.h:11, clang_type = "@interface Model : NSObject{
NSString * _bb;
NSString * _cc;
NSString * _name;
}
@property ( getter = name,setter = setName:,readwrite,nonatomic ) NSString * name;
@end
"
</pre>
<p>可以看到,LLDB把Model这个class的所有属性和成员变量都打印了出来,当我们想了解某个类的时候,直接使用 <em>image lookup -t</em> 即可。</p>
<h2 id="section">其他</h2>
<p>可以直接使用LLDB打开模拟器位置:</p>
<pre>
(lldb) po NSHomeDirectory()
/Users/mfw/Library/Developer/CoreSimulator/Devices/EAFE74A5-4C53-42CE-8B40-141380D73A6D/data/Containers/Data/Application/B4C48D8B-BD8B-4246-B9D7-15FEC3CA8662
(lldb) platform shell open /Users/mfw/Library/Developer/CoreSimulator/Devices/EAFE74A5-4C53-42CE-8B40-141380D73A6D/data/Containers/Data/Application/B4C48D8B-BD8B-4246-B9D7-15FEC3CA8662
</pre>
<h2 id="debug">常用的Debug快捷键</h2>
<p>debug的时候,使用快捷键是一个很好的习惯,我简单列举了几个debug的快捷键</p>
<table border="1" width="100%">
<thead align="center" text-align="center">
<tr>
<th>功能</th>
<th>命令</th>
</tr>
</thead>
<tbody>
<tr>
<td>暂停/继续</td>
<td>cmd + ctrl + Y</td>
</tr>
<tr>
<td>断点失效/生效</td>
<td>cmd + Y</td>
</tr>
<tr>
<td>控制台显示/隐藏</td>
<td>cmd + shift + Y</td>
</tr>
<tr>
<td>光标切换到控制台</td>
<td>cmd + shift + C</td>
</tr>
<tr>
<td>清空控制台</td>
<td>cmd + K</td>
</tr>
<tr>
<td>step over</td>
<td>F6</td>
</tr>
<tr>
<td>step into</td>
<td>F7</td>
</tr>
<tr>
<td>step out</td>
<td>F8</td>
</tr>
<tr>
<td>工程导航器</td>
<td>Command+1</td>
</tr>
<tr>
<td>显示/隐藏导航器面板</td>
<td>Command+0</td>
</tr>
<tr>
<td>显示/隐藏实用工具面板</td>
<td>Command+Option+0</td>
</tr>
<tr>
<td>打开Assistant Editor</td>
<td>项目导航器中选中文件执行Option+左键点击操作</td>
</tr>
<tr>
<td>展示方法列表</td>
<td>Control+6(键入方法/变量名+Enter跳转</td>
</tr>
<tr>
<td>快速打开</td>
<td>Command + Shift + O (字母O)</td>
</tr>
<tr>
<td>文档和参考</td>
<td>Command + Shift + 0 (数字0)</td>
</tr>
<tr>
<td>快速帮助</td>
<td>在类或者方法名上执行Option + Left-click操作</td>
</tr>
<tr>
<td>展示当前你在工程导航器中打开的文件</td>
<td>Command + Shift + J</td>
</tr>
<tr>
<td>迷你窗口,可任意选择位置</td>
<td>Command + Option + Shift + Left-click</td>
</tr>
</tbody>
</table>
<style type="text/css">
thead {color:green;text-align:center;}
tbody {color:blue;}
tfoot {color:red;}
th {text-align:center;}
td {text-align:center;}
</style>
<h2 id="end">End</h2>
<p>这是我比较常用的一些命令,不全但是有效,像那些 <em>breakpoint</em> 的功能,若不是使用 lldb调试可执行文件的话,直接使用 Xcode 的功能效果会更加显著一些。若想使用一些高级命令,可结合 <em>python</em> 脚本使用。</p>
<p>参考:</p>
<p><a href="http://objccn.io/issue-19-2/">与调试器共舞 - LLDB 的华尔兹</a></p>
<p><a href="http://www.phperz.com/article/16/0119/184198.html">熟练使用 LLDB,让你调试事半功倍</a></p>
<p><a href="http://www.dreamingwish.com/article/lldb-usage-a.html">LLDB使用篇(上)</a></p>
<p><a href="http://casatwy.com/shi-yong-lldbdiao-shi-cheng-xu.html">使用LLDB调试程序</a></p>idechaoidechao@163.comLLDB是Xcode默认的调试器,它与LLVM编译器一起,带给我们更丰富的流程控制和数据检测的调试功能。平时用Xcode运行程序,实际走的都是LLDB。熟练使用LLDB,可以让你debug事半功倍。GHUnit单元测试2016-02-18T00:00:00+08:002016-02-18T00:00:00+08:00http://www.dechao.net/http://www.dechao.net/blog/ghunit<p>最近有朋友问我单元测试的问题,网上的资料可能许久没更新了,导致一直没能成功,这里就再记录一下。</p>
<p>GHUnit是一个开源的objective-c的unit test框架,有GUI的界面,直观、方便。
这里就用这个库进行测试。</p>
<section id="table-of-contents" class="toc">
<header>
<h1>Features</h1>
</header>
<div id="drawer">
<ul id="markdown-toc">
<li><a href="#target" id="markdown-toc-target">新建工程以及 Target</a></li>
<li><a href="#framework" id="markdown-toc-framework">添加Framework</a></li>
<li><a href="#section" id="markdown-toc-section">删除多余文件</a></li>
<li><a href="#section-1" id="markdown-toc-section-1">设置主函数</a></li>
<li><a href="#section-2" id="markdown-toc-section-2">配置工程文件</a></li>
<li><a href="#section-3" id="markdown-toc-section-3">运行测试单元</a></li>
</ul>
</div>
</section>
<!-- /#table-of-contents -->
<h2 id="target">新建工程以及 Target</h2>
<p>新建一个 Single View Application,注意不要勾选 Include Unit Tests 和 Include UI Tests,这里我命名为 GHUnitTest。</p>
<p>新增一个 target,可如下图点击 <strong>+</strong> 号,同样的选择 Single View Application,不要勾选 Include Unit Tests 和 Include UI Tests,这里我命名为 Test。</p>
<figure>
<a href="http://www.dechao.net/images/unitTests/unittest_1_1.jpg">
<img src="http://www.dechao.net/images/unitTests/unittest_1_1.jpg" />
</a>
<figcaption> 添加target </figcaption>
</figure>
<h2 id="framework">添加Framework</h2>
<p>在 Test 中添加 GHUnitIOS.framework,<a href="https://github.com/gh-unit/gh-unit/downloads">下载地址</a>,同时在 Link Binary With Libraries 中添加 CoreGraphics.framework、Foundation.framework 和 UIKit.framework。</p>
<h2 id="section">删除多余文件</h2>
<p>删除 Test 中的 AppDelegate 和 ViewController,同时把 Main.storyboard 和 LaunchScreen.storyboard也删除。</p>
<p>删除 Test 下 Info.plist 中的 Main storyboard file base name 和 Launch screen interface file base name 字段。</p>
<p>这时可以添加一张 Default@2x.png 的图片,避免上下黑边的问题。</p>
<h2 id="section-1">设置主函数</h2>
<p>在 Test 中的 main 函数里,将 delegete 的名字改为 @”GHUnitIOSAppDelegate”</p>
<h2 id="section-2">配置工程文件</h2>
<ol>
<li>将 Build Active Architecture Only 设置为 NO</li>
<li>删除 Valid Architectures 中的 arm64。</li>
<li>
<p>在 Test 下的 Build Settings 下的 Linking 下,设置 Other LinkerFlags:</p>
<p><em>-all_load</em>、<em>-ObjC</em>。</p>
</li>
<li>
<p>Framework Search Paths设置为GHUnitIOS.framework目录:</p>
<p><em>$(SRCROOT)/Test/GHUnitIOS.framework</em></p>
</li>
</ol>
<p>注:</p>
<p>-ObjC标志,这个flag告诉链接器把库中定义的Objective-C类和Category都加载进来。这样编译之后的app会变大(因为加载了其他的objc代码进来)。但是如果静态库中有类和category的话只有加入这个flag才行。</p>
<p>-all_load,这个flag是专门处理-ObjC的一个bug的。用了-ObjC以后,如果类库中只有category没有类的时候这些category还是加载不进来。变通方法就是加入-all_load或者-force-load。-all_load会强制链接器把目标文件都加载进来,即使没有objc代码。</p>
<p>-force_load在xcode3.2后可用。但是-force_load后面必须跟一个只想静态库的路径。</p>
<h2 id="section-3">运行测试单元</h2>
<p>运行该 Test ,这时模拟器会展示运行界面,如下图。说明配置成功。</p>
<figure>
<a href="http://www.dechao.net/images/unitTests/unittest_1_2.jpg"><img src="http://www.dechao.net/images/unitTests/unittest_1_2.jpg" /></a>
<figcaption> GHUnit成功界面 </figcaption>
</figure>idechaoidechao@163.com最近有朋友问我单元测试的问题,网上的资料可能许久没更新了,导致一直没能成功,这里就再记录一下。GCD2015-10-18T00:00:00+08:002015-10-18T00:00:00+08:00http://www.dechao.net/http://www.dechao.net/blog/gcd<p>GCD是异步执行任务的技术之一,能通过推迟昂贵计算任务并在后台运行它们来改善你的应用的响应性能;而且,提供一个易于使用的并发模型而不仅仅只是锁和线程,以帮助我们避开并发陷阱。同时具有在常见模式(例如单例)上用更高性能的原语优化你的代码的潜在能力。</p>
<section id="table-of-contents" class="toc">
<header>
<h1>Features</h1>
</header>
<div id="drawer">
<ul id="markdown-toc">
<li><a href="#dispatch-queue" id="markdown-toc-dispatch-queue">Dispatch Queue</a></li>
<li><a href="#dispatchqueuecreate" id="markdown-toc-dispatchqueuecreate">Dispatch_Queue_Create</a></li>
<li><a href="#main-dispatch-queueglobal-dispatch-queue" id="markdown-toc-main-dispatch-queueglobal-dispatch-queue">Main Dispatch Queue/Global Dispatch Queue</a></li>
<li><a href="#dispatchsettargetqueue" id="markdown-toc-dispatchsettargetqueue">dispatch_set_target_queue</a></li>
<li><a href="#dispatchafter--dispatchwalltime" id="markdown-toc-dispatchafter--dispatchwalltime">dispatch_after 与 dispatch_walltime</a></li>
<li><a href="#dispatch-group" id="markdown-toc-dispatch-group">Dispatch Group</a></li>
<li><a href="#dispatchgroupwait" id="markdown-toc-dispatchgroupwait">dispatch_group_wait</a></li>
<li><a href="#dispatchbarrierasync" id="markdown-toc-dispatchbarrierasync">dispatch_barrier_async</a></li>
<li><a href="#dispatchsync" id="markdown-toc-dispatchsync">dispatch_sync</a></li>
<li><a href="#dispatchapply" id="markdown-toc-dispatchapply">dispatch_apply</a></li>
<li><a href="#dispatchsuspenddispatchresume" id="markdown-toc-dispatchsuspenddispatchresume">dispatch_suspend/dispatch_resume</a></li>
<li><a href="#dispatch-semaphore" id="markdown-toc-dispatch-semaphore">Dispatch Semaphore</a></li>
<li><a href="#dispatch-once" id="markdown-toc-dispatch-once">Dispatch Once</a></li>
<li><a href="#dispatch-io" id="markdown-toc-dispatch-io">Dispatch I/O</a></li>
<li><a href="#reference" id="markdown-toc-reference">Reference</a></li>
</ul>
</div>
</section>
<!-- /#table-of-contents -->
<h2 id="dispatch-queue">Dispatch Queue</h2>
<table>
<thead>
<tr>
<th style="text-align: center"> </th>
<th style="text-align: center">Dispatch Queue的种类</th>
<th style="text-align: center">说明</th>
</tr>
</thead>
<tbody>
<tr>
<td style="text-align: center">串行</td>
<td style="text-align: center">Serial Dispatch Queue</td>
<td style="text-align: center">等待现在执行中处理结束</td>
</tr>
<tr>
<td style="text-align: center">并行</td>
<td style="text-align: center">Concurrent Dispatch Queue</td>
<td style="text-align: center">不等待现在执行中处理结束</td>
</tr>
</tbody>
</table>
<h2 id="dispatchqueuecreate">Dispatch_Queue_Create</h2>
<pre class="sunlight-highlight-objective-c">
dispatch_queue_t serial = dispatch_queue_create("com.clgchao.io",
DISPATCH_QUEUE_SERIAL); //串行队列,默认
dispatch_queue_t concurrent = dispatch_queue_create("com.clgchao.io",
DISPATCH_QUEUE_CONCURRENT); //并行队列
</pre>
<p>第一个参数为名字,推荐使用程序ID或逆序全程域名的。</p>
<h2 id="main-dispatch-queueglobal-dispatch-queue">Main Dispatch Queue/Global Dispatch Queue</h2>
<p>Main Dispatch Queue是在主线程执行的,为串行队列。获取方式为:</p>
<pre class="sunlight-highlight-objective-c">
dispatch_queue_t mainQueue = dispatch_get_main_queue();
</pre>
<p>Global Dispatch Queue为全局并发队列,分为高优先级(High Priority)、默认优先级(Default Priority)、低优先级(Low Priority)和后台优先级(Background Priority)。获取方法为:</p>
<pre class="sunlight-highlight-objective-c">
dispatch_queue_t globalHigh =
dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0);
dispatch_queue_t globalDefault =
dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_queue_t globalLow =
dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0);
dispatch_queue_t globalBackground =
dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0);
</pre>
<h2 id="dispatchsettargetqueue">dispatch_set_target_queue</h2>
<p>改变queue的优先级与目标queue相同。</p>
<p>dispatch_set_target_queue(Q1,Q2);
Q1为需要修改优先级的,Q2为Q1要与他同级的。
MainDQ 与GlobalDQ因为是全局,所以无法作为第一个参数。</p>
<p>在必须将不可并行的处理追加到多个串行队列中时,如果使用dispatch_set_target_queue函数将目标指定为某一个为串行队列即可防止处理并行执行。</p>
<h2 id="dispatchafter--dispatchwalltime">dispatch_after 与 dispatch_walltime</h2>
<pre class="sunlight-highlight-objective-c">
dispatch_after 为延迟多久加入队列(相对时间)
dispatch_walltime 为在什么时间加入队列(绝对时间)
</pre>
<p>使用:</p>
<pre class="sunlight-highlight-objective-c">
// 延迟3秒后提交
dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW,
3ull * NSEC_PER_SEC);
dispatch_after(time, dispatch_get_main_queue(), ^{
NSLog(@"dispatch_after");
});
</pre>
<p><strong>强调:</strong></p>
<p><strong>dispatch_after是<code class="highlighter-rouge">延迟提交</code>,不是延迟运行</strong>,指的就是将一个Block在特定的延时以后,加入到指定的队列中,<strong>不是在特定的时间后立即运行!</strong></p>
<p>注意dispatch_time_t:</p>
<pre class="sunlight-highlight-objective-c">
dispatch_time_t dispatch_time ( dispatch_time_t when,
int64_t delta );
</pre>
<p>第一个参数一般是 <strong>DISPATCH_TIME_NOW</strong> ,表示从现在开始。
那么第二个参数就是真正的延时的具体时间。</p>
<p>这里要特别注意的是,delta参数是“纳秒!”,就是说,延时1秒的话,delta应该是“1000000000” (´・ω・`) 太长了,所以理所当然系统提供了常量,如下:</p>
<pre class="sunlight-highlight-objective-c">
#define NSEC_PER_SEC 1000000000ull
#define NSEC_PER_MSEC 1000000ull
#define USEC_PER_SEC 1000000ull
#define NSEC_PER_USEC 1000ull
</pre>
<p>关键词解释:</p>
<ul>
<li>NSEC:纳秒。</li>
<li>USEC:微妙。</li>
<li>SEC:秒</li>
<li>MSEC:兆秒</li>
<li>PER:每</li>
</ul>
<p>所以:</p>
<ol>
<li>NSEC_PER_SEC,每秒有多少纳秒。</li>
<li>NSEC_PER_MSEC,每兆秒有多少纳秒。</li>
<li>USEC_PER_SEC,每秒有多少毫秒。(注意是指在纳秒的基础上)</li>
<li>NSEC_PER_USEC,每毫秒有多少纳秒。</li>
</ol>
<p>所以,延时 <strong>1秒</strong> 可以写成如下形式:</p>
<pre class="sunlight-highlight-objective-c">
dispatch_time(DISPATCH_TIME_NOW, 1ull * NSEC_PER_SEC);
dispatch_time(DISPATCH_TIME_NOW, 1000 * USEC_PER_SEC);
dispatch_time(DISPATCH_TIME_NOW, USEC_PER_SEC * NSEC_PER_USEC);
</pre>
<h2 id="dispatch-group">Dispatch Group</h2>
<p>在追加到Dispatch Queue中的多个处理全部结束后想执行结束处理的情形,除了使用串行队列外, 在使用并行队列或同时使用多个并行队列的时候,应该使用Dispatch Group。</p>
<pre class="sunlight-highlight-objective-c">
dispatch_queue_t queue =
dispatch_get_global_queue(DISPATCH_QUEUE_DEFAULT,0);
dispatch_group_t group = disptach_group_create();
dispatch_group_async(group,queue,^{1...});
dispatch_group_async(group,queue,^{2...});
dispatch_group_async(group,queue,^{3...});
dispatch_group_notify(group,dispatch_get_main_queue(),^{End Action});
</pre>
<p>注意:在dispatch_group_notify函数中不管指定怎么样的Dispatch Queue,属于Dispatch Group的处理在追加到block(上面例子的【End Action】)时都已经结束。</p>
<p>所以,对于添加的block是异步请求时,dispatch_group_async在瞬间就完成了,并不会等待着异步请求结束后再notify。对于这种问题,dispatch_group也有对应的方法:</p>
<p>dispatch_group_enter & dispatch_group_leave这对组合。</p>
<pre class="sunlight-highlight-objective-c">
dispatch_group_t group = dispatch_group_create();
dispatch_queue_t queue =
dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
NSLog(@"11");
dispatch_group_enter(group);
[Manager executeWeatherWithCity:@"北京" resultBlock:^(NSString *weather) { // 自定义异步线程
NSLog(@"22 == %@", weather);
dispatch_group_leave(group);
}];
dispatch_group_enter(group);
[Manager executeWeatherWithCity:@"北京" resultBlock:^(NSString *weather) { // 自定义异步线程
NSLog(@"33 == %@", weather);
dispatch_group_leave(group);
}];
dispatch_group_enter(group);
[Manager executeWeatherWithCity:@"北京" resultBlock:^(NSString *weather) { // 自定义异步线程
NSLog(@"44 == %@", weather);
dispatch_group_leave(group);
}];
dispatch_group_enter(group);
[Manager executeWeatherWithCity:@"北京" resultBlock:^(NSString *weather) { // 自定义异步线程
NSLog(@"55 == %@", weather);
dispatch_group_leave(group);
}];
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
NSLog(@"66");
[Manager executeWeatherWithCity:@"北京" resultBlock:^(NSString *weather) {
NSLog(@"77 == %@", weather);
}];
});
NSLog(@"88");
</pre>
<p>执行顺序就是先 1和8,之后2到5随机,最后才是66以及77。</p>
<p>dispatch_group_enter() 和 dispatch_group_leave()必须成对出现,group_enter是将请求任务放入到group后,便一直被group持有,直到碰到group_leave;才会释放出来,只有group中不在持有任何任务后才会调用notify进行回调通知。</p>
<h2 id="dispatchgroupwait">dispatch_group_wait</h2>
<pre class="sunlight-highlight-objective-c">
dispatch_group_wait(group,DISPATCH_TIME_FOREVER);
</pre>
<p>该函数的第二个参数指定超时等待的时间,为dispatch_time_t类型。该源代码使用 <strong>DISPATCH_TIME_FOREVER</strong>,意味着永久等待。只有属于Dispatch Group的处理尚未结束,就回一直等待,中途不能取消。</p>
<pre class="sunlight-highlight-objective-c">
disptach_time_t time = dispatch_time(DISPATCH_TIME_NOW,1ull*NSEC_PER_SEC);
long result = dispatch_group_wait(group,time);
//返回值为long型
if(result == 0){
// 等待时间结束后,group的全部处理执行结束
}else{
// 等待时间结束后,group的还有未完成的处理
}
</pre>
<p>修改上述源代码result中的时间,不用等待即可判断属于Dispatch Group的处理是否执行完毕。</p>
<pre class="sunlight-highlight-objective-c">
long result = dispatch_group_wait(group,DISPATCH_TIME_NOW);
</pre>
<p>正常情况下不使用dispatch_group_wait,而使用dispatch_group_notify,
可以更好的简化代码。</p>
<h2 id="dispatchbarrierasync">dispatch_barrier_async</h2>
<p>在访问数据库或文件时,使用串行队列可避免数据竞争问题。</p>
<p>但为了高效的进行访问,读取处理追加到并行队列中使用并发读取数据,就容易引起问题。</p>
<pre class="sunlight-highlight-objective-c">
queue 为并发队列
queue 其他处理1
queue 其他处理2
dispatch_barrier_async(queue,blk_for_writing)
queue 其他处理3
queue 其他处理4
</pre>
<p>dispatch_barrier_async函数会等待追加到并发队列上的并行执行的处理全部结束之后,再将指定的处理追加到该队列中。然后再由dispatch_barrier_async函数追加的处理执行完毕后,队列才恢复为一般的动作,追加到该队列的处理又开始并行执行。</p>
<p>上面的执行顺序即为:</p>
<p>处理1 2 执行完 才会执行dispatch_barrier_async,dispatch_barrier_async执行完才会执行 其他处理 3 4。</p>
<p>如果其他处理都是数据库读操作dispatch_barrier_async是数据库写操作,那么这样能保证 3 4 读取的数据是修改(写入)后的,数据保持正确。</p>
<p>值得注意的是;</p>
<ol>
<li>dispatch_barrier(a)sync只在自己创建的并发队列上有效,在全局(Global)并发队列、串行队列上,效果跟dispatch_(a)sync效果一样。</li>
<li>既然在串行队列上跟dispatch_(a)sync效果一样,那就要小心别死锁!</li>
</ol>
<h2 id="dispatchsync">dispatch_sync</h2>
<p>同步执行,与dispatch_async相反。dispatch_sync 函数不会立即返回,会立即阻塞调用时该函数所在的线程,追加任务到添加的线程中,并等待 block同步执行完成。</p>
<p>此方法不能将block加到当前为<em>DISPATCH_QUEUE_SERIAL</em>的线程,会导致死锁。</p>
<p>主线程为<em>DISPATCH_QUEUE_SERIAL</em>的线程,所以也不能直接加。</p>
<p>加入到<em>DISPATCH_QUEUE_CONCURRENT</em>的线程没有任何问题。</p>
<p>注意通过<em>dispatch_get_global_queue</em>获得的线程为<em>DISPATCH_QUEUE_CONCURRENT</em>,所以没问题。</p>
<p>如下两种方式都将死锁。</p>
<pre class="sunlight-highlight-objective-c">
dispatch_queue_t queue = dispatch_get_main_queue();
dispatch_sync(dispatch_get_main_queue(), ^{
NSLog(@"dispatch_sync");
});
// 注:后面类型不写的话默认为DISPATCH_QUEUE_SERIAL
dispatch_queue_t queue =
dispatch_queue_create("serialQueue", DISPATCH_QUEUE_SERIAL);
dispatch_async(queue, ^{
NSLog(@"1");
dispatch_sync(queue, ^{
NSLog(@"2");
});
});
</pre>
<p>像下面这种情况就不会造成死锁:</p>
<pre class="sunlight-highlight-objective-c">
dispatch_queue_t queue =
dispatch_queue_create("serialQueue", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
NSLog(@"1");
dispatch_sync(queue, ^{
NSLog(@"2");
});
});
dispatch_queue_t t = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(t, ^{
NSLog(@"3");
dispatch_sync(t, ^{
NSLog(@"4");
});
});
</pre>
<h2 id="dispatchapply">dispatch_apply</h2>
<p>按照指定的次数将指定的Block追加到指定的队列中,<strong>并等待全部处理执行结束</strong>。</p>
<pre class="sunlight-highlight-objective-c">
NSArray *array = @[@"6",@"5",@"4",@"3",@"2",@"1"];
dispatch_apply(array.count, dispatch_get_global_queue(
DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(size_t index) {
NSLog(@"%zu---%@", index, [array objectAtIndex:index]);
});
NSLog(@"done");
</pre>
<p>done 总是最后输出。</p>
<p>要避免dispatch_apply的嵌套调用,会发生死锁。</p>
<pre class="sunlight-highlight-objective-c">
dispatch_queue_t queue =
dispatch_queue_create("com.dechao.gcd", DISPATCH_QUEUE_SERIAL);
dispatch_apply(3, queue, ^(size_t i) {
NSLog(@"apply loop: %zu", i);
//再来一个dispatch_apply!死锁!
dispatch_apply(3, queue, ^(size_t j) {
NSLog(@"apply loop inside %zu", j);
});
});
</pre>
<h2 id="dispatchsuspenddispatchresume">dispatch_suspend/dispatch_resume</h2>
<p>当追加大量处理到Dispatch Queue时,在追加处理的过程中,有时希望不执行已追加的处理。</p>
<p>dispatch_suspend 挂起已经追加的处理</p>
<p>dispatch_resume 恢复已经追加的处理</p>
<p>dispatch_suspend并不会立即暂停正在运行的block,而是在当前block执行完成后,暂停后续的block执行。</p>
<p>所以下次想暂停正在队列上运行的block时,还是不要用dispatch_suspend了吧~</p>
<h2 id="dispatch-semaphore">Dispatch Semaphore</h2>
<p>信号量是一个整形值并且具有一个初始计数值,并且支持两个操作:信号通知和等待。当一个信号量被信号通知,其计数会被增加。当一个线程在一个信号量上等待时,线程会被阻塞(如果有必要的话),直至计数器大于零,然后线程会减少这个计数。</p>
<p>在GCD中有三个函数是semaphore的操作,分别是:</p>
<p>dispatch_semaphore_create 创建一个semaphore</p>
<p>dispatch_semaphore_signal 发送一个信号</p>
<p>dispatch_semaphore_wait 等待信号</p>
<p>简单的介绍一下这三个函数,第一个函数有一个整形的参数,我们可以理解为信号的总量,dispatch_semaphore_signal是发送一个信号,自然会让信号总量加1,dispatch_semaphore_wait等待信号,当信号总量少于0的时候就会一直等待,否则就可以正常的执行,并让信号总量-1,根据这样的原理,我们便可以快速的创建一个并发控制来同步任务和有限资源访问控制。</p>
<p><strong>注意,正常的使用顺序是先降低然后再提高,这两个函数通常成对使用。</strong></p>
<p>举例如下:</p>
<pre class="sunlight-highlight-objective-c">
dispatch_group_t group = dispatch_group_create();
dispatch_semaphore_t semaphore = dispatch_semaphore_create(10);
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
for (int i = 0; i < 100; i++)
{
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
dispatch_group_async(group, queue, ^{
NSLog(@"%i",i);
sleep(2);
dispatch_semaphore_signal(semaphore);
});
}
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
</pre>
<p>简单的介绍一下这一段代码,创建了一个初使值为10的semaphore,每一次for循环都会创建一个新的线程,线程结束的时候会发送一个信号,线程创建之前会信号等待,所以当同时创建了10个线程之后,for循环就会阻塞,等待有线程结束之后会增加一个信号才继续执行,如此就形成了对并发的控制,如上就是一个并发数为10的一个线程队列。</p>
<h2 id="dispatch-once">Dispatch Once</h2>
<p>说明:<em>Executes a block object once and only once for the lifetime of an application.</em></p>
<p><strong>保证在应用程序执行中只执行一次</strong>指定的API。</p>
<p>常用来声明单例,即使在多线程的环境下,也可以保证安全。</p>
<p>dispatch_once_t必须是全局或static变量,毕竟非全局或非static的dispatch_once_t变量在使用时会导致非常不好排查的bug,正确的如下:</p>
<pre class="sunlight-highlight-objective-c">
//静态变量,保证只有一份实例,才能确保只执行一次
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
//单例代码
});
</pre>
<p>其实就是保证dispatch_once_t只有一份实例。</p>
<h2 id="dispatch-io">Dispatch I/O</h2>
<p>在读取大文件时,如果将文件分为合适的大小并使用Global Dispatch Queue并行读取数据,读取速度会快不少。现在的输入/输出硬件已经可以做到一次性使用多个线程更快的并列读取,实现这一功能的就是Dispatch I/O和Dispatch Data。
通过使用Dispatch I/O可以并发读写数据,提高效率</p>
<h2 id="reference">Reference</h2>
<p><a href="http://tutuge.me/2015/04/03/something-about-gcd/">GCD使用经验与技巧浅谈</a></p>
<p><a href="https://developer.apple.com/library/prerelease/mac/documentation/Performance/Reference/GCD_libdispatch_Ref/index.html">Grand Central Dispatch (GCD) Reference</a></p>
<p><a href="https://github.com/nixzhu/dev-blog/blob/master/2014-04-19-grand-central-dispatch-in-depth-part-1.md">GCD 深入理解:第一部分</a></p>
<p><a href="https://github.com/nixzhu/dev-blog/blob/master/2014-05-14-grand-central-dispatch-in-depth-part-2.md">GCD 深入理解:第二部分</a></p>
<p><a href="http://www.raywenderlich.com/60749/grand-central-dispatch-in-depth-part-1">Grand Central Dispatch In-Depth: Part 1/2</a></p>
<p><a href="http://www.raywenderlich.com/63338/grand-central-dispatch-in-depth-part-2">Grand Central Dispatch In-Depth: Part 2/2</a></p>idechaoidechao@163.comGCD是异步执行任务的技术之一,能通过推迟昂贵计算任务并在后台运行它们来改善你的应用的响应性能;而且,提供一个易于使用的并发模型而不仅仅只是锁和线程,以帮助我们避开并发陷阱。同时具有在常见模式(例如单例)上用更高性能的原语优化你的代码的潜在能力。Objective-C规范指南2015-10-09T00:00:00+08:002015-10-09T00:00:00+08:00http://www.dechao.net/http://www.dechao.net/blog/cocoa<p>合格的工程师不仅会实现各类需求,解决各种BUG,对于Objective-C的代码规范,也有着严格的约定。</p>
<section id="table-of-contents" class="toc">
<header>
<h1>Features</h1>
</header>
<div id="drawer">
<ul id="markdown-toc">
<li><a href="#section" id="markdown-toc-section">点语法</a></li>
<li><a href="#section-1" id="markdown-toc-section-1">间距</a></li>
<li><a href="#section-2" id="markdown-toc-section-2">条件判断</a></li>
<li><a href="#section-3" id="markdown-toc-section-3">常量</a></li>
<li><a href="#section-4" id="markdown-toc-section-4">枚举类型</a></li>
<li><a href="#section-5" id="markdown-toc-section-5">位掩码</a></li>
<li><a href="#section-6" id="markdown-toc-section-6">属性</a></li>
<li><a href="#section-7" id="markdown-toc-section-7">布尔</a></li>
<li><a href="#section-8" id="markdown-toc-section-8">单例</a></li>
<li><a href="#section-9" id="markdown-toc-section-9">导入</a></li>
<li><a href="#xcode-" id="markdown-toc-xcode-">Xcode 工程</a></li>
<li><a href="#section-10" id="markdown-toc-section-10">空行</a></li>
<li><a href="#objective-c-" id="markdown-toc-objective-c-">其他Objective-C 风格指南</a></li>
</ul>
</div>
</section>
<!-- /#table-of-contents -->
<h2 id="section">点语法</h2>
<p>应该 <strong>始终</strong> 使用点语法来访问或者修改属性,访问其他实例时首选括号(对比下面的属性)。</p>
<p>推荐:</p>
<figure class="highlight"><pre><code class="language-objc" data-lang="objc"><span class="n">view</span><span class="p">.</span><span class="n">backgroundColor</span> <span class="o">=</span> <span class="p">[</span><span class="n">UIColor</span> <span class="nf">orangeColor</span><span class="p">];</span>
<span class="p">[</span><span class="n">UIApplication</span> <span class="nf">sharedApplication</span><span class="p">].</span><span class="n">delegate</span><span class="p">;</span></code></pre></figure>
<p>反对:</p>
<figure class="highlight"><pre><code class="language-objc" data-lang="objc"><span class="p">[</span><span class="n">view</span> <span class="nf">setBackgroundColor</span><span class="p">:[</span><span class="n">UIColor</span> <span class="nf">orangeColor</span><span class="p">]];</span>
<span class="n">UIApplication</span><span class="p">.</span><span class="n">sharedApplication</span><span class="p">.</span><span class="n">delegate</span><span class="p">;</span></code></pre></figure>
<h2 id="section-1">间距</h2>
<ul>
<li>一个缩进使用 4 个空格,永远不要使用制表符(tab)缩进。请确保在 Xcode 中设置了此偏好。</li>
<li>方法的大括号和其他的大括号(if/else/switch/while 等等)始终和声明在同一行开始,在新的一行结束。</li>
</ul>
<p>推荐:</p>
<figure class="highlight"><pre><code class="language-objc" data-lang="objc"><span class="k">if</span> <span class="p">(</span><span class="n">user</span><span class="p">.</span><span class="n">isHappy</span><span class="p">)</span> <span class="p">{</span>
<span class="c1">// Do something
</span><span class="p">}</span>
<span class="k">else</span> <span class="k">if</span> <span class="p">(</span><span class="n">user</span><span class="p">.</span><span class="n">isOld</span><span class="p">)</span> <span class="p">{</span>
<span class="c1">// Do something
</span><span class="p">}</span>
<span class="k">else</span> <span class="p">{</span>
<span class="c1">// Do something else
</span><span class="p">}</span></code></pre></figure>
<ul>
<li>方法之间应该正好空一行,这有助于视觉清晰度和代码组织性。在方法中的功能块之间应该使用空白分开,但往往可能应该创建一个新的方法。</li>
<li>@synthesize 和 @dynamic 在实现中每个都应该占一个新行。</li>
</ul>
<h2 id="section-2">条件判断</h2>
<p>条件判断主体部分应该始终使用大括号括住来防止出错,即使它可以不用大括号(例如它只需要一行)。这些错误包括添加第二行(代码)并希望它是 if 语句的一部分时。还有另外一种<a href="http://programmers.stackexchange.com/questions/16528/single-statement-if-block-braces-or-no/16530#16530">更危险的</a>,当 if 语句里面的一行被注释掉,下一行就会在不经意间成为了这个 if 语句的一部分。此外,这种风格也更符合所有其他的条件判断,因此也更容易检查。</p>
<p>推荐:</p>
<figure class="highlight"><pre><code class="language-objc" data-lang="objc"><span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">error</span><span class="p">)</span> <span class="p">{</span>
<span class="k">return</span> <span class="n">success</span><span class="p">;</span>
<span class="p">}</span></code></pre></figure>
<p>反对:</p>
<figure class="highlight"><pre><code class="language-objc" data-lang="objc"><span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">error</span><span class="p">)</span>
<span class="k">return</span> <span class="n">success</span><span class="p">;</span></code></pre></figure>
<p>或:</p>
<figure class="highlight"><pre><code class="language-objc" data-lang="objc"><span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">error</span><span class="p">)</span> <span class="k">return</span> <span class="n">success</span><span class="p">;</span></code></pre></figure>
<p>##三目运算符
三目运算符(? :) ,只有当它可以增加代码清晰度或整洁时才使用。单一的条件都应该优先考虑使用。多条件时通常使用 if 语句会更易懂,或者重构为实例变量。</p>
<p>推荐:</p>
<figure class="highlight"><pre><code class="language-objc" data-lang="objc"><span class="n">result</span> <span class="o">=</span> <span class="n">a</span> <span class="o">></span> <span class="n">b</span> <span class="p">?</span> <span class="n">x</span> <span class="p">:</span> <span class="n">y</span><span class="p">;</span></code></pre></figure>
<p>反对:</p>
<figure class="highlight"><pre><code class="language-objc" data-lang="objc"><span class="n">result</span> <span class="o">=</span> <span class="n">a</span> <span class="o">></span> <span class="n">b</span> <span class="p">?</span> <span class="n">x</span> <span class="o">=</span> <span class="n">c</span> <span class="o">></span> <span class="n">d</span> <span class="p">?</span> <span class="n">c</span> <span class="p">:</span> <span class="n">d</span> <span class="p">:</span> <span class="n">y</span><span class="p">;</span></code></pre></figure>
<p>##错误处理
当引用一个返回错误参数(error parameter)的方法时,应该针对返回值,而非错误变量。</p>
<p>推荐:</p>
<figure class="highlight"><pre><code class="language-objc" data-lang="objc"><span class="n">NSError</span> <span class="o">*</span><span class="n">error</span><span class="p">;</span>
<span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="p">[</span><span class="n">self</span> <span class="nf">trySomethingWithError</span><span class="p">:</span><span class="o">&</span><span class="n">error</span><span class="p">])</span> <span class="p">{</span>
<span class="c1">// 处理错误
</span><span class="p">}</span></code></pre></figure>
<p>反对:</p>
<figure class="highlight"><pre><code class="language-objc" data-lang="objc"><span class="n">NSError</span> <span class="o">*</span><span class="n">error</span><span class="p">;</span>
<span class="p">[</span><span class="n">self</span> <span class="nf">trySomethingWithError</span><span class="p">:</span><span class="o">&</span><span class="n">error</span><span class="p">];</span>
<span class="k">if</span> <span class="p">(</span><span class="n">error</span><span class="p">)</span> <span class="p">{</span>
<span class="c1">// 处理错误
</span><span class="p">}</span></code></pre></figure>
<p>一些苹果的 API 在成功的情况下会写一些垃圾值给错误参数(如果非空),所以针对错误变量可能会造成虚假结果(以及接下来的崩溃)。</p>
<p>##方法
在方法签名中,在 -/+ 符号后应该有一个空格。方法片段之间也应该有一个空格。构造方法使 <em><a href="http://clang.llvm.org/docs/LanguageExtensions.html#related-result-types">instancetype </a></em> 作为返回类型来代替 <em>id</em> 。</p>
<p>推荐:</p>
<figure class="highlight"><pre><code class="language-objc" data-lang="objc"><span class="k">-</span> <span class="p">(</span><span class="kt">void</span><span class="p">)</span><span class="nf">setExampleText</span><span class="p">:(</span><span class="n">NSString</span> <span class="o">*</span><span class="p">)</span><span class="nv">text</span> <span class="nf">image</span><span class="p">:(</span><span class="n">UIImage</span> <span class="o">*</span><span class="p">)</span><span class="nv">image</span><span class="p">;</span></code></pre></figure>
<p>对于私有方法,应该加前缀用以区分。具体使用可以自行决定,建议使用p加下划线的方式:<em>p_</em> , p表示”private”,不建议使用单个下划线的方式,这种方式是预留给苹果使用的。</p>
<p>##变量
变量名应该尽可能命名为描述性的。除了 for() 循环外,其他情况都应该避免使用单字母的变量名。 星号表示指针属性变量,例如:<code class="highlighter-rouge">NSString *text</code>不要写成<code class="highlighter-rouge"> NSString* text </code>或者<code class="highlighter-rouge">NSString * text </code>,常量除外。</p>
<p>尽量定义属性来代替直接使用实例变量,同时声明内存的管理方式。如果一个属性只在 <em>init</em> 方法里设置了一次,声明为 <em>readonly</em> 。</p>
<p>推荐:</p>
<figure class="highlight"><pre><code class="language-objc" data-lang="objc"><span class="k">@interface</span> <span class="nc">WRGSection</span><span class="p">:</span> <span class="nc">NSObject</span>
<span class="k">@property</span> <span class="p">(</span><span class="n">nonatomic</span><span class="p">,</span> <span class="n">strong</span><span class="p">)</span> <span class="n">NSString</span> <span class="o">*</span><span class="n">headline</span><span class="p">;</span>
<span class="k">@end</span></code></pre></figure>
<p>反对:</p>
<figure class="highlight"><pre><code class="language-objc" data-lang="objc"><span class="k">@interface</span> <span class="nc">WRGSection</span> <span class="p">:</span> <span class="nc">NSObject</span> <span class="p">{</span>
<span class="n">NSString</span> <span class="o">*</span><span class="n">headline</span><span class="p">;</span>
<span class="p">}</span></code></pre></figure>
<p>或:</p>
<figure class="highlight"><pre><code class="language-objc" data-lang="objc"><span class="k">@interface</span> <span class="nc">WRGSection</span><span class="p">:</span> <span class="nc">NSObject</span>
<span class="k">@property</span> <span class="p">(</span><span class="n">nonatomic</span><span class="p">)</span> <span class="n">NSString</span> <span class="o">*</span><span class="n">headline</span><span class="p">;</span>
<span class="k">@end</span></code></pre></figure>
<p>###变量限定符
当涉及到<a href="https://developer.apple.com/library/ios/releasenotes/ObjectiveC/RN-TransitioningToARC/Introduction/Introduction.html#//apple_ref/doc/uid/TP40011226-CH1-SW4">在 ARC 中被引入</a>变量限定符时, 限定符 (__strong, __weak, __unsafe_unretained, __autoreleasing) 应该位于最前面,如下。</p>
<figure class="highlight"><pre><code class="language-objc" data-lang="objc"><span class="n">__weak</span> <span class="n">NSString</span> <span class="o">*</span><span class="n">text</span></code></pre></figure>
<p>##命名
尽可能遵守苹果的命名约定,尤其那些涉及到<a href="https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/MemoryMgmt/Articles/MemoryMgmt.html">内存管理规则</a>,(<a href="https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/MemoryMgmt/Articles/MemoryMgmt.html">NARC</a>)的。</p>
<p>长的和描述性的方法名和变量名都不错。</p>
<p>推荐:</p>
<figure class="highlight"><pre><code class="language-objc" data-lang="objc"><span class="n">UIButton</span> <span class="o">*</span><span class="n">settingsButton</span><span class="p">;</span></code></pre></figure>
<p>反对:</p>
<figure class="highlight"><pre><code class="language-objc" data-lang="objc"><span class="n">UIButton</span> <span class="o">*</span><span class="n">setBut</span><span class="p">;</span></code></pre></figure>
<p>类名和常量应该始终使用三个字母的前缀(例如 WRG)(常亮也可使用字母k开头),但 Core Data 实体名称可以省略。为了代码清晰,常量应该使用相关类的名字作为前缀并使用驼峰命名法。</p>
<p>推荐:</p>
<figure class="highlight"><pre><code class="language-objc" data-lang="objc"><span class="k">static</span> <span class="k">const</span> <span class="n">NSTimeInterval</span> <span class="n">WRGArticleViewControllerNavigationFadeAnimationDuration</span> <span class="o">=</span> <span class="mi">0</span><span class="p">.</span><span class="mi">3</span><span class="p">;</span></code></pre></figure>
<p>反对:</p>
<figure class="highlight"><pre><code class="language-objc" data-lang="objc"><span class="k">static</span> <span class="k">const</span> <span class="n">NSTimeInterval</span> <span class="n">fadetime</span> <span class="o">=</span> <span class="mi">1</span><span class="p">.</span><span class="mi">7</span><span class="p">;</span></code></pre></figure>
<p>属性和局部变量应该使用驼峰命名法并且首字母小写。</p>
<p>为了保持一致,实例变量应该使用驼峰命名法命名,并且首字母小写,以下划线为前缀。这与 LLVM 自动合成的实例变量相一致。 如果 <strong>LLVM</strong> 可以自动合成变量,那就让它自动合成。</p>
<p>推荐:</p>
<figure class="highlight"><pre><code class="language-objc" data-lang="objc"><span class="k">@synthesize</span> <span class="n">descriptiveVariableName</span> <span class="o">=</span> <span class="n">_descriptiveVariableName</span><span class="p">;</span></code></pre></figure>
<p>反对:</p>
<figure class="highlight"><pre><code class="language-objc" data-lang="objc"><span class="n">id</span> <span class="n">varnm</span><span class="p">;</span></code></pre></figure>
<p>##注释
当需要的时候,注释应该被用来解释 为什么 特定代码做了某些事情。所使用的任何注释必须保持最新否则就删除掉。</p>
<p>通常应该避免一大块注释,代码就应该尽量作为自身的文档,只需要隔几行写几句说明。这并不适用于那些用来生成文档的注释。</p>
<p>##init 和 dealloc
<em>dealloc</em> 方法应该放在实现文件的最上面,并且刚好在 <em>@synthesize</em> 和 <em>@dynamic</em> 语句的后面。在任何类中,<em>init</em> 都应该直接放在 <em>dealloc</em> 方法的下面。</p>
<p>init 方法的结构应该像这样:</p>
<figure class="highlight"><pre><code class="language-objc" data-lang="objc"><span class="k">-</span> <span class="p">(</span><span class="n">instancetype</span><span class="p">)</span><span class="n">init</span> <span class="p">{</span>
<span class="n">self</span> <span class="o">=</span> <span class="p">[</span><span class="n">super</span> <span class="nf">init</span><span class="p">];</span> <span class="c1">// 或者调用指定的初始化方法
</span> <span class="k">if</span> <span class="p">(</span><span class="n">self</span><span class="p">)</span> <span class="p">{</span>
<span class="c1">// Custom initialization
</span> <span class="p">}</span>
<span class="k">return</span> <span class="n">self</span><span class="p">;</span>
<span class="p">}</span></code></pre></figure>
<p>##字面量
每当创建 <em>NSString</em>, <em>NSDictionary</em>, <em>NSArray</em>,和 <em>NSNumber</em> 类的不可变实例时,都应该使用字面量。要注意 <em>nil</em> 值不能传给 <em>NSArray</em> 和 <em>NSDictionary</em> 字面量,这样做会导致崩溃。</p>
<p>推荐:</p>
<figure class="highlight"><pre><code class="language-objc" data-lang="objc"><span class="n">NSArray</span> <span class="o">*</span><span class="n">names</span> <span class="o">=</span> <span class="p">@[</span><span class="s">@"Brian"</span><span class="p">,</span> <span class="s">@"Matt"</span><span class="p">,</span> <span class="s">@"Chris"</span><span class="p">,</span> <span class="s">@"Alex"</span><span class="p">,</span> <span class="s">@"Steve"</span><span class="p">,</span> <span class="s">@"Paul"</span><span class="p">];</span>
<span class="n">NSDictionary</span> <span class="o">*</span><span class="n">productManagers</span> <span class="o">=</span> <span class="p">@{</span><span class="s">@"iPhone"</span> <span class="o">:</span> <span class="s">@"Kate"</span><span class="p">,</span> <span class="s">@"iPad"</span> <span class="o">:</span> <span class="s">@"Kamal"</span><span class="p">,</span> <span class="s">@"Mobile Web"</span> <span class="o">:</span> <span class="s">@"Bill"</span><span class="p">};</span>
<span class="n">NSNumber</span> <span class="o">*</span><span class="n">shouldUseLiterals</span> <span class="o">=</span> <span class="nb">@YES</span><span class="p">;</span>
<span class="n">NSNumber</span> <span class="o">*</span><span class="n">buildingZIPCode</span> <span class="o">=</span> <span class="mi">@10018</span><span class="p">;</span></code></pre></figure>
<p>反对:</p>
<figure class="highlight"><pre><code class="language-objc" data-lang="objc"><span class="n">NSArray</span> <span class="o">*</span><span class="n">names</span> <span class="o">=</span> <span class="p">[</span><span class="n">NSArray</span> <span class="nf">arrayWithObjects</span><span class="p">:</span><span class="s">@"Brian"</span><span class="p">,</span> <span class="s">@"Matt"</span><span class="p">,</span> <span class="s">@"Chris"</span><span class="p">,</span> <span class="s">@"Alex"</span><span class="p">,</span> <span class="s">@"Steve"</span><span class="p">,</span> <span class="s">@"Paul"</span><span class="p">,</span> <span class="nb">nil</span><span class="p">];</span>
<span class="n">NSDictionary</span> <span class="o">*</span><span class="n">productManagers</span> <span class="o">=</span> <span class="p">[</span><span class="n">NSDictionary</span> <span class="nf">dictionaryWithObjectsAndKeys</span><span class="p">:</span> <span class="s">@"Kate"</span><span class="p">,</span> <span class="s">@"iPhone"</span><span class="p">,</span> <span class="s">@"Kamal"</span><span class="p">,</span> <span class="s">@"iPad"</span><span class="p">,</span> <span class="s">@"Bill"</span><span class="p">,</span> <span class="s">@"Mobile Web"</span><span class="p">,</span> <span class="nb">nil</span><span class="p">];</span>
<span class="n">NSNumber</span> <span class="o">*</span><span class="n">shouldUseLiterals</span> <span class="o">=</span> <span class="p">[</span><span class="n">NSNumber</span> <span class="nf">numberWithBool</span><span class="p">:</span><span class="nb">YES</span><span class="p">];</span>
<span class="n">NSNumber</span> <span class="o">*</span><span class="n">buildingZIPCode</span> <span class="o">=</span> <span class="p">[</span><span class="n">NSNumber</span> <span class="nf">numberWithInteger</span><span class="p">:</span><span class="mi">10018</span><span class="p">];</span></code></pre></figure>
<p>##CGRect 函数
当访问一个 CGRect 的 x, y, width, height 时,应该使用<a href="https://developer.apple.com/library/ios/documentation/GraphicsImaging/Reference/CGGeometry/index.html">CGGeometry 函数</a>代替直接访问结构体成员。苹果的 CGGeometry 参考中说到:</p>
<figure class="highlight"><pre><code class="language-objc" data-lang="objc"><span class="n">All</span> <span class="n">functions</span> <span class="n">described</span> <span class="k">in</span> <span class="n">this</span> <span class="n">reference</span> <span class="n">that</span> <span class="n">take</span> <span class="n">CGRect</span> <span class="n">data</span> <span class="n">structures</span> <span class="n">as</span> <span class="n">inputs</span> <span class="n">implicitly</span> <span class="n">standardize</span> <span class="n">those</span> <span class="n">rectangles</span> <span class="n">before</span> <span class="n">calculating</span> <span class="n">their</span> <span class="n">results</span><span class="p">.</span>
<span class="n">For</span> <span class="n">this</span> <span class="n">reason</span><span class="p">,</span> <span class="n">your</span> <span class="n">applications</span> <span class="n">should</span> <span class="n">avoid</span> <span class="n">directly</span> <span class="n">reading</span> <span class="n">and</span> <span class="n">writing</span> <span class="n">the</span> <span class="n">data</span> <span class="n">stored</span> <span class="k">in</span> <span class="n">the</span> <span class="n">CGRect</span> <span class="n">data</span> <span class="n">structure</span><span class="p">.</span>
<span class="n">Instead</span><span class="p">,</span> <span class="n">use</span> <span class="n">the</span> <span class="n">functions</span> <span class="n">described</span> <span class="n">here</span> <span class="n">to</span> <span class="n">manipulate</span> <span class="n">rectangles</span> <span class="n">and</span> <span class="n">to</span> <span class="n">retrieve</span> <span class="n">their</span> <span class="n">characteristics</span><span class="p">.</span></code></pre></figure>
<p>推荐:</p>
<figure class="highlight"><pre><code class="language-objc" data-lang="objc"><span class="n">CGRect</span> <span class="n">frame</span> <span class="o">=</span> <span class="n">self</span><span class="p">.</span><span class="n">view</span><span class="p">.</span><span class="n">frame</span><span class="p">;</span>
<span class="n">CGFloat</span> <span class="n">x</span> <span class="o">=</span> <span class="n">CGRectGetMinX</span><span class="p">(</span><span class="n">frame</span><span class="p">);</span>
<span class="n">CGFloat</span> <span class="n">y</span> <span class="o">=</span> <span class="n">CGRectGetMinY</span><span class="p">(</span><span class="n">frame</span><span class="p">);</span>
<span class="n">CGFloat</span> <span class="n">width</span> <span class="o">=</span> <span class="n">CGRectGetWidth</span><span class="p">(</span><span class="n">frame</span><span class="p">);</span>
<span class="n">CGFloat</span> <span class="n">height</span> <span class="o">=</span> <span class="n">CGRectGetHeight</span><span class="p">(</span><span class="n">frame</span><span class="p">);</span></code></pre></figure>
<p>反对:</p>
<figure class="highlight"><pre><code class="language-objc" data-lang="objc"><span class="n">CGRect</span> <span class="n">frame</span> <span class="o">=</span> <span class="n">self</span><span class="p">.</span><span class="n">view</span><span class="p">.</span><span class="n">frame</span><span class="p">;</span>
<span class="n">CGFloat</span> <span class="n">x</span> <span class="o">=</span> <span class="n">frame</span><span class="p">.</span><span class="n">origin</span><span class="p">.</span><span class="n">x</span><span class="p">;</span>
<span class="n">CGFloat</span> <span class="n">y</span> <span class="o">=</span> <span class="n">frame</span><span class="p">.</span><span class="n">origin</span><span class="p">.</span><span class="n">y</span><span class="p">;</span>
<span class="n">CGFloat</span> <span class="n">width</span> <span class="o">=</span> <span class="n">frame</span><span class="p">.</span><span class="n">size</span><span class="p">.</span><span class="n">width</span><span class="p">;</span>
<span class="n">CGFloat</span> <span class="n">height</span> <span class="o">=</span> <span class="n">frame</span><span class="p">.</span><span class="n">size</span><span class="p">.</span><span class="n">height</span><span class="p">;</span></code></pre></figure>
<h2 id="section-3">常量</h2>
<p>常量首选内联字符串字面量或数字,因为常量可以轻易重用并且可以快速改变而不需要查找和替换。常量应该声明为 static 常量而不是 #define ,除非非常明确地要当做宏来使用。</p>
<p>推荐:</p>
<figure class="highlight"><pre><code class="language-objc" data-lang="objc"><span class="k">static</span> <span class="n">NSString</span> <span class="o">*</span> <span class="k">const</span> <span class="n">WRGAboutViewControllerAuthorName</span> <span class="o">=</span> <span class="s">@"Warning"</span><span class="p">;</span>
<span class="k">static</span> <span class="k">const</span> <span class="n">CGFloat</span> <span class="n">WRGImageThumbnailHeight</span> <span class="o">=</span> <span class="mi">50</span><span class="p">.</span><span class="mi">0</span><span class="p">;</span></code></pre></figure>
<p>反对:</p>
<figure class="highlight"><pre><code class="language-objc" data-lang="objc"><span class="cp">#define AuthorName @"Warning"
#define thumbnailHeight 2</span></code></pre></figure>
<p>若常量局限于某个实现文件,则以k开头;若在其他类中可见,则以类名为前缀。</p>
<h2 id="section-4">枚举类型</h2>
<p>当使用 enum 时,建议使用新的基础类型规范,因为它具有更强的类型检查和代码补全功能。现在 SDK 包含了一个宏来鼓励使用使用新的基础类型 - NS_ENUM()</p>
<p>推荐:</p>
<figure class="highlight"><pre><code class="language-objc" data-lang="objc"><span class="k">typedef</span> <span class="nf">NS_ENUM</span><span class="p">(</span><span class="n">NSInteger</span><span class="p">,</span> <span class="n">WRGAdRequestState</span><span class="p">)</span> <span class="p">{</span>
<span class="n">WRGAdRequestStateInactive</span><span class="p">,</span>
<span class="n">WRGAdRequestStateLoading</span>
<span class="p">};</span></code></pre></figure>
<p>凡是需要按位或操作来组合的枚举都应该使用NS_OPTIONS定义。</p>
<p>在switch语句中,总是习惯加上default语句,然而,若是用枚举来定义状态机,则最好不要有default分支。这样增加了一种状态,编译器会发出警告提示需要增加新的处理。</p>
<h2 id="section-5">位掩码</h2>
<p>当用到位掩码时,使用 NS_OPTIONS 宏。</p>
<p>举例:</p>
<figure class="highlight"><pre><code class="language-objc" data-lang="objc"><span class="k">typedef</span> <span class="nf">NS_OPTIONS</span><span class="p">(</span><span class="n">NSUInteger</span><span class="p">,</span> <span class="n">WRGAdCategory</span><span class="p">)</span> <span class="p">{</span>
<span class="n">WRGAdCategoryAutos</span> <span class="o">=</span> <span class="mi">1</span> <span class="o"><<</span> <span class="mi">0</span><span class="p">,</span>
<span class="n">WRGAdCategoryJobs</span> <span class="o">=</span> <span class="mi">1</span> <span class="o"><<</span> <span class="mi">1</span><span class="p">,</span>
<span class="n">WRGAdCategoryRealState</span> <span class="o">=</span> <span class="mi">1</span> <span class="o"><<</span> <span class="mi">2</span><span class="p">,</span>
<span class="n">WRGAdCategoryTechnology</span> <span class="o">=</span> <span class="mi">1</span> <span class="o"><<</span> <span class="mi">3</span>
<span class="p">};</span></code></pre></figure>
<h2 id="section-6">属性</h2>
<p>在对象外部访问实例变量的时候,总是应该通过属性来访问。除了几种特殊情况,在对象内部时读取数据时,应该直接访问实例变量,而写入数据时,则应该通过属性来写。这么写的目的是:</p>
<ul>
<li>直接访问实例变量的速度比较快,编译器所生成的代码会直接访问保存对象实例变量的内存。</li>
<li>直接访问实例变量时,不会调用其”设置方法”,这就绕过了为相关属性所定义的”内存管理语意”。</li>
<li>直接访问实例变量,不会触发KVO通知。具体有没有影响需要看具体行为。</li>
<li>通过属性来访问有助于排查错误,可以增加断点来监控对象行为。</li>
</ul>
<p>在初始化方法和dealloc方法以及getter和setter中,应该总是使用实例变量来读写数据。</p>
<p>使用惰性初始化方法时,使用属性来访问。</p>
<p>私有属性应该声明在类实现文件的延展(匿名的类目)中。</p>
<p>支持:</p>
<div class="highlighter-rouge"><pre class="highlight"><code><span class="k">@interface</span> <span class="nc">Counter</span> <span class="p">:</span> <span class="nc">NSObject</span>
<span class="k">@property</span> <span class="p">(</span><span class="n">nonatomic</span><span class="p">,</span> <span class="n">retain</span><span class="p">)</span> <span class="n">NSNumber</span> <span class="o">*</span><span class="n">count</span><span class="p">;</span>
<span class="k">@end</span>
<span class="o">-</span> <span class="p">(</span><span class="n">NSNumber</span> <span class="o">*</span><span class="p">)</span><span class="n">count</span> <span class="p">{</span>
<span class="k">return</span> <span class="n">_count</span><span class="p">;</span>
<span class="p">}</span>
<span class="o">-</span> <span class="p">(</span><span class="kt">void</span><span class="p">)</span><span class="n">setCount</span><span class="o">:</span><span class="p">(</span><span class="n">NSNumber</span> <span class="o">*</span><span class="p">)</span><span class="n">newCount</span> <span class="p">{</span>
<span class="p">[</span><span class="n">newCount</span> <span class="nf">retain</span><span class="p">];</span>
<span class="p">[</span><span class="n">_count</span> <span class="nf">release</span><span class="p">];</span>
<span class="c1">// Make the new assignment.
</span> <span class="n">_count</span> <span class="o">=</span> <span class="n">newCount</span><span class="p">;</span>
<span class="p">}</span>
<span class="o">-</span> <span class="n">init</span> <span class="p">{</span>
<span class="n">self</span> <span class="o">=</span> <span class="p">[</span><span class="n">super</span> <span class="nf">init</span><span class="p">];</span>
<span class="k">if</span> <span class="p">(</span><span class="n">self</span><span class="p">)</span> <span class="p">{</span>
<span class="n">_count</span> <span class="o">=</span> <span class="p">[[</span><span class="n">NSNumber</span> <span class="nf">alloc</span><span class="p">]</span> <span class="nf">initWithInteger</span><span class="p">:</span><span class="mi">0</span><span class="p">];</span>
<span class="p">}</span>
<span class="k">return</span> <span class="n">self</span><span class="p">;</span>
<span class="p">}</span>
<span class="o">-</span> <span class="n">initWithCount</span><span class="o">:</span><span class="p">(</span><span class="n">NSNumber</span> <span class="o">*</span><span class="p">)</span><span class="n">startingCount</span> <span class="p">{</span>
<span class="n">self</span> <span class="o">=</span> <span class="p">[</span><span class="n">super</span> <span class="nf">init</span><span class="p">];</span>
<span class="k">if</span> <span class="p">(</span><span class="n">self</span><span class="p">)</span> <span class="p">{</span>
<span class="n">_count</span> <span class="o">=</span> <span class="p">[</span><span class="n">startingCount</span> <span class="nf">copy</span><span class="p">];</span>
<span class="p">}</span>
<span class="k">return</span> <span class="n">self</span><span class="p">;</span>
<span class="p">}</span>
<span class="o">-</span> <span class="p">(</span><span class="kt">void</span><span class="p">)</span><span class="n">dealloc</span> <span class="p">{</span>
<span class="p">[</span><span class="n">_count</span> <span class="nf">release</span><span class="p">];</span>
<span class="p">[</span><span class="n">super</span> <span class="nf">dealloc</span><span class="p">];</span>
<span class="p">}</span>
</code></pre>
</div>
<p>在继承父类的时候,如果本类中没有相关属性,在 init 方法中使用点语法,则会寻找父类属性,而使用实例变量则不会,可以很好的控制本类中的属性,检查属性。</p>
<p>有关在初始化方法和 dealloc 方法中使用访问器方法的信息,参见<a href="https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/MemoryMgmt/Articles/mmPractical.html#//apple_ref/doc/uid/TP40004447-SW6">这里</a>。</p>
<h2 id="section-7">布尔</h2>
<p>因为 nil 解析为 NO,所以没有必要在条件中与它进行比较。永远不要直接和 YES 进行比较,因为 YES 被定义为 1,而 BOOL 可以多达 8 位。</p>
<p>这使得整个文件有更多的一致性和更大的视觉清晰度。</p>
<p>推荐:</p>
<figure class="highlight"><pre><code class="language-objc" data-lang="objc"><span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">someObject</span><span class="p">)</span> <span class="p">{</span>
<span class="p">}</span></code></pre></figure>
<p>反对:</p>
<figure class="highlight"><pre><code class="language-objc" data-lang="objc"><span class="k">if</span> <span class="p">(</span><span class="n">someObject</span> <span class="o">==</span> <span class="nb">nil</span><span class="p">)</span> <span class="p">{</span>
<span class="p">}</span></code></pre></figure>
<p>对于 BOOL 来说, 这有两种用法:</p>
<figure class="highlight"><pre><code class="language-objc" data-lang="objc"><span class="k">if</span> <span class="p">(</span><span class="n">isAwesome</span><span class="p">)</span>
<span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="p">[</span><span class="n">someObject</span> <span class="nf">boolValue</span><span class="p">])</span></code></pre></figure>
<p>反对:</p>
<figure class="highlight"><pre><code class="language-objc" data-lang="objc"><span class="k">if</span> <span class="p">([</span><span class="n">someObject</span> <span class="nf">boolValue</span><span class="p">]</span> <span class="o">==</span> <span class="nb">NO</span><span class="p">)</span>
<span class="k">if</span> <span class="p">(</span><span class="n">isAwesome</span> <span class="o">==</span> <span class="nb">YES</span><span class="p">)</span> <span class="o">//</span> <span class="err">永远别这么做</span></code></pre></figure>
<p>如果一个 BOOL 属性名称是一个形容词,属性可以省略 “is” 前缀,但为 get 访问器指定一个惯用的名字,例如:</p>
<figure class="highlight"><pre><code class="language-objc" data-lang="objc"><span class="k">@property</span> <span class="p">(</span><span class="n">assign</span><span class="p">,</span> <span class="n">getter</span><span class="o">=</span><span class="n">isEditable</span><span class="p">)</span> <span class="n">BOOL</span> <span class="n">editable</span><span class="p">;</span></code></pre></figure>
<p>内容和例子来自 <a href="https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/CodingGuidelines/Articles/NamingIvarsAndTypes.html#//apple_ref/doc/uid/20001284-BAJGIIJE">Cocoa 命名指南</a> 。</p>
<h2 id="section-8">单例</h2>
<p>单例对象应该使用线程安全的模式创建共享的实例。</p>
<figure class="highlight"><pre><code class="language-objc" data-lang="objc"><span class="k">+</span> <span class="p">(</span><span class="n">instancetype</span><span class="p">)</span><span class="n">sharedInstance</span> <span class="p">{</span>
<span class="k">static</span> <span class="n">id</span> <span class="n">sharedInstance</span> <span class="o">=</span> <span class="nb">nil</span><span class="p">;</span>
<span class="k">static</span> <span class="n">dispatch_once_t</span> <span class="n">onceToken</span><span class="p">;</span>
<span class="n">dispatch_once</span><span class="p">(</span><span class="o">&</span><span class="n">onceToken</span><span class="p">,</span> <span class="o">^</span><span class="p">{</span>
<span class="n">sharedInstance</span> <span class="o">=</span> <span class="p">[[</span><span class="n">self</span> <span class="nf">alloc</span><span class="p">]</span> <span class="nf">init</span><span class="p">];</span>
<span class="p">});</span>
<span class="k">return</span> <span class="n">sharedInstance</span><span class="p">;</span>
<span class="p">}</span></code></pre></figure>
<p>这将会预防<a href="http://cocoasamurai.blogspot.com/2011/04/singletons-your-doing-them-wrong.html">有时可能产生的许多崩溃</a>。</p>
<h2 id="section-9">导入</h2>
<p>将 <em>import</em> 和其他的文件名之间加一个空格。如果有一个以上的 import 语句,就对这些语句进行<a href="http://ashfurrow.com/blog/structuring-modern-objective-c/">分组</a>。每个分组的注释是可选的。
注:对于模块使用 <a href="http://clang.llvm.org/docs/Modules.html#using-modules">@import</a> 语法。</p>
<p>除了子类化或是协议之外,最好使用 <strong>@class</strong> 这种方式,避免过多的头文件引入。在引入协议的时候,如果不是连当前类也引入的情况下,将协议单独声明出来再引入。</p>
<h2 id="xcode-">Xcode 工程</h2>
<p>为了避免文件杂乱,物理文件应该保持和 Xcode 项目文件同步。Xcode 创建的任何组(group)都必须在文件系统有相应的映射。为了更清晰,代码不仅应该按照类型进行分组,也可以根据功能进行分组。</p>
<p>如果可以的话,尽可能一直打开 target Build Settings 中 “Treat Warnings as Errors” 以及一些<a href="http://boredzo.org/blog/archives/2009-11-07/warnings">额外的警告</a>。如果你需要忽略指定的警告,使用 <a href="http://clang.llvm.org/docs/UsersManual.html#controlling-diagnostics-via-pragmas">Clang 的编译特性</a> 。</p>
<h2 id="section-10">空行</h2>
<p>1、在 <em>extension</em> 和 <em>implementation</em> 之间添加一行空格。</p>
<p>推荐:</p>
<figure class="highlight"><pre><code class="language-objc" data-lang="objc"><span class="k">@interface</span> <span class="nc">MyClass</span> <span class="p">()</span>
<span class="c1">// Properties - empty line above and below
</span>
<span class="k">@end</span>
<span class="k">@implementation</span> <span class="nc">MyClass</span>
<span class="c1">// Body - empty line above and below
</span>
<span class="k">@end</span></code></pre></figure>
<p>2、在 <em>@end</em>之后添加一空行</p>
<p>3、使用 <em>pragma mark</em>之后添加一行空格</p>
<figure class="highlight"><pre><code class="language-objc" data-lang="objc"><span class="k">-</span> <span class="p">(</span><span class="n">CGSize</span><span class="p">)</span><span class="n">intrinsicContentSize</span> <span class="p">{</span>
<span class="k">return</span> <span class="n">CGSizeMake</span><span class="p">(</span><span class="mi">12</span><span class="p">,</span> <span class="mi">12</span><span class="p">);</span>
<span class="p">}</span>
<span class="cp">#pragma mark - Private
</span>
<span class="o">-</span> <span class="p">(</span><span class="kt">void</span><span class="p">)</span><span class="n">setup</span> <span class="p">{</span>
<span class="p">}</span></code></pre></figure>
<p>4、操作数学运算符时在运算符俩侧添加空格。一元运算符不用。</p>
<p>推荐:</p>
<figure class="highlight"><pre><code class="language-objc" data-lang="objc"><span class="n">NSInteger</span> <span class="n">index</span> <span class="o">=</span> <span class="n">rand</span><span class="p">()</span> <span class="o">%</span> <span class="mi">50</span> <span class="o">+</span> <span class="mi">25</span><span class="p">;</span>
<span class="n">index</span><span class="o">++</span><span class="p">;</span>
<span class="n">index</span> <span class="o">+=</span> <span class="mi">1</span><span class="p">;</span>
<span class="n">index</span><span class="o">--</span><span class="p">;</span></code></pre></figure>
<p>5、在进行逻辑判断时,在 <em>if</em> 之后添加一个空格,并在 <em>{</em> 之前添加一个空格。</p>
<p>推荐:</p>
<figure class="highlight"><pre><code class="language-objc" data-lang="objc"><span class="k">if</span> <span class="p">(</span><span class="n">alpha</span> <span class="o">+</span> <span class="n">beta</span> <span class="o"><=</span> <span class="mi">0</span><span class="p">)</span> <span class="o">&&</span> <span class="p">(</span><span class="n">kappa</span> <span class="o">+</span> <span class="n">phi</span> <span class="o">></span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
<span class="p">}</span></code></pre></figure>
<p>6、对于多参数的方法,除非方法签名大于或等于3行,否则不要换行。</p>
<p>推荐:</p>
<figure class="highlight"><pre><code class="language-objc" data-lang="objc"> <span class="c1">// blocks are easily readable
</span> <span class="p">[</span><span class="n">UIView</span> <span class="nf">animateWithDuration</span><span class="p">:</span><span class="mi">1</span><span class="p">.</span><span class="mi">0</span> <span class="nf">animations</span><span class="p">:</span><span class="o">^</span><span class="p">{</span>
<span class="c1">// something
</span> <span class="p">}</span> <span class="n">completion</span><span class="o">:^</span><span class="p">(</span><span class="n">BOOL</span> <span class="n">finished</span><span class="p">)</span> <span class="p">{</span>
<span class="c1">// something
</span> <span class="p">}];</span>
</code></pre></figure>
<p>反对:</p>
<figure class="highlight"><pre><code class="language-objc" data-lang="objc"><span class="c1">// colon-aligning makes the block indentation wacky and hard to read
</span><span class="p">[</span><span class="n">UIView</span> <span class="nf">animateWithDuration</span><span class="p">:</span><span class="mi">1</span><span class="p">.</span><span class="mi">0</span>
<span class="nf">animations</span><span class="p">:</span><span class="o">^</span><span class="p">{</span>
<span class="c1">// something
</span> <span class="p">}</span>
<span class="n">completion</span><span class="o">:^</span><span class="p">(</span><span class="n">BOOL</span> <span class="n">finished</span><span class="p">)</span> <span class="p">{</span>
<span class="c1">// something
</span> <span class="p">}];</span></code></pre></figure>
<p>7、不要在对象类型前和 <em>protocol</em>之间添加空格。</p>
<p>推荐:</p>
<div class="highlighter-rouge"><pre class="highlight"><code><span class="k">@property</span> <span class="p">(</span><span class="n">nonatomic</span><span class="p">,</span> <span class="n">weak</span><span class="p">)</span> <span class="n">id</span><span class="o"><</span><span class="n">SGOAnalyticsDelegate</span><span class="o">></span> <span class="n">analyticsDelegate</span><span class="p">;</span>
</code></pre>
</div>
<p>反对:</p>
<div class="highlighter-rouge"><pre class="highlight"><code><span class="k">@property</span> <span class="p">(</span><span class="n">nonatomic</span><span class="p">,</span> <span class="n">weak</span><span class="p">)</span> <span class="n">id</span> <span class="o"><</span><span class="n">SGOAnalyticsDelegate</span><span class="o">></span> <span class="n">analyticsDelegate</span><span class="p">;</span>
</code></pre>
</div>
<h2 id="objective-c-">其他Objective-C 风格指南</h2>
<p>如果感觉不太符合口味,可以看看下面的风格指南:</p>
<ul>
<li><a href="https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/ProgrammingWithObjectiveC/Introduction/Introduction.html">Objective-C 编程语言</a></li>
<li><a href="https://developer.apple.com/legacy/library/documentation/Cocoa/Conceptual/CocoaFundamentals/Introduction/Introduction.html">Cocoa 基本原理指南</a></li>
<li><a href="https://developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/CodingGuidelines/CodingGuidelines.html">Cocoa 编码指南</a></li>
<li><a href="https://developer.apple.com/library/ios/documentation/iPhone/Conceptual/iPhoneOSProgrammingGuide/Introduction/Introduction.html">iOS 应用编程指南</a></li>
<li><a href="https://github.com/NYTimes/objective-c-style-guide">纽约时报 移动团队 Objective-C 规范指南</a></li>
<li><a href="https://github.com/RobotsAndPencils/objective-c-style-guide">Robots & Pencils Objective-C Style Guide</a></li>
</ul>
<p>其他</p>
<ul>
<li><a href="https://github.com/raywenderlich/objective-c-style-guide">raywenderlich.com</a></li>
<li><a href="http://google-styleguide.googlecode.com/svn/trunk/objcguide.xml">Google</a></li>
<li><a href="https://github.com/github/objective-c-style-guide">GitHub</a></li>
<li><a href="https://trac.adium.im/wiki/CodingStyle">Adium</a></li>
<li><a href="https://gist.github.com/soffes/812796">Sam Soffes</a></li>
<li><a href="http://cocoadevcentral.com/articles/000082.php">CocoaDevCentral</a>)</li>
<li><a href="http://www.cimgf.com/zds-code-style-guide/">Marcus Zarra</a></li>
</ul>idechaoidechao@163.com合格的工程师不仅会实现各类需求,解决各种BUG,对于Objective-C的代码规范,也有着严格的约定。