<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet href="/rss.xsl" type="text/xsl"?>
<rss version="2.0">
  <channel>
    <title>IT瘾ios推荐</title>
    <link>https://itindex.net/tags/ios</link>
    <description>IT社区推荐资讯 - ITIndex.net</description>
    <language>zh</language>
    <copyright>https://itindex.net/</copyright>
    <generator>https://itindex.net/</generator>
    <docs>http://backend.userland.com/rss</docs>
    <image>
      <url>https://itindex.net/images/logo.gif</url>
      <title>IT社区推荐资讯 - ITIndex.net</title>
      <link>https://itindex.net/tags/ios</link>
    </image>
    <item>
      <title>基于weex的有赞无线开发框架</title>
      <link>https://itindex.net/detail/58920-weex-%E6%97%A0%E7%BA%BF-%E5%BC%80%E5%8F%91</link>
      <description>&lt;p&gt;出于对开发效率和动态化的要求，无线端的开发框架也一直在更新，从 Hybrid、结构化 Native View、React Native、Weex，再到现在正在大受关注的 Flutter。什么样的框架才是适合自己的团队？不仅要有技术追求，而且要考虑实际业务需要。最近，有赞移动选择了 weex 作为无线开发框架，搭建了从开发、Debug、构建、发布、数据一个闭环的流程。本文将对此进行分享。&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="&amp;#24320;&amp;#21457;&amp;#38381;&amp;#29615;" src="https://segmentfault.com/img/remote/1460000016850044?w=359&amp;h=349" title="&amp;#24320;&amp;#21457;&amp;#38381;&amp;#29615;"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;h2&gt;一、什么是 weex&lt;/h2&gt;
 &lt;p&gt;Weex 是阿里巴巴开源的一套构建高性能、可扩展的原生应用跨平台开发方案。首先总结一下 weex 的特点：&lt;/p&gt;
 &lt;ol&gt;
  &lt;li&gt;
   &lt;strong&gt;页面的开发目前支持    &lt;a href="https://alibaba.github.io/rax/" rel="nofollow noreferrer"&gt;Rax&lt;/a&gt;和    &lt;a href="https://vuejs.org/" rel="nofollow noreferrer"&gt;Vue&lt;/a&gt;&lt;/strong&gt;   &lt;p&gt;Weex 也不是只支持 Vue 和 Rax，你也可以把自己喜欢的前端框架集成到 Weex 中，有一个文档    &lt;a href="https://weex.incubator.apache.org/cn/guide/extend-js-framework.html" rel="nofollow noreferrer"&gt;扩展前端框架&lt;/a&gt;描述了如何实现，但是这个过程仍然非常复杂和棘手，你需要了解关于 js-native 之间通信和原生渲染引擎的许多底层细节。&lt;/p&gt;
&lt;/li&gt;
  &lt;li&gt;
   &lt;strong&gt;一次编写，三端（Android、iOS、前端）运行&lt;/strong&gt;   &lt;p&gt;前提是都集成了 weex sdk，另外视觉表现做不到完全一样，有的会有一些差异，需要做一下适配。所以写 weex 页面的时候，如果支持三端，便需要在三端都进行自测。&lt;/p&gt;
&lt;/li&gt;
  &lt;li&gt;
   &lt;strong&gt;UI 的绘制通过 native 的组件，JavaScript 逻辑在 JS 引擎里运行，两者通过 JavaScriptCore 通信&lt;/strong&gt;   &lt;p&gt;weex 里使用组件都需要在 native 端注册，这样 weex 里才可以使用，运行的时候通过注册时记录的 map 进行查找。weex sdk 内置注册了一些基础的组件，包括 list、text、input 等。WXJSCoreBridge 封装了 JavaScriptCore 实现 native 和 js 之间的通信。&lt;/p&gt;
&lt;/li&gt;
  &lt;li&gt;
   &lt;strong&gt;支持 Native 扩展&lt;/strong&gt;   &lt;p&gt;可以将 native 的 UI 组件封装成 component，将 native 的逻辑代码封装成 module。从而在 weex 里可以进行使用。这里的 natiev UI 组件包括 modal、webview、image 等，这里的 native 逻辑代码包括 storage、network 等。&lt;/p&gt;
&lt;/li&gt;
  &lt;li&gt;
   &lt;strong&gt;每个 weex 页面会被打包成一个 js 文件，weex sdk 将 js 文件渲染成一个 view&lt;/strong&gt;   &lt;br /&gt;weex 的打包通过 webpack，将每个页面打包成独立的一个 js 文件，weex sdk 会将 js 进行解析，将 UI 部分绘制成一个 view, 再绑定 view 的事件与 js 代码绑定。&lt;/li&gt;
&lt;/ol&gt;
 &lt;h2&gt;二、为什么要使用weex进行无线开发&lt;/h2&gt;
 &lt;h3&gt;1. 效率问题&lt;/h3&gt;
 &lt;p&gt;1）开发的人力成本&lt;/p&gt;
 &lt;p&gt;如果不算 web 端，一个页面本来需要 Android 和 iOS   &lt;strong&gt;2&lt;/strong&gt; 个人开发；使用 weex 后只需要   &lt;strong&gt;1&lt;/strong&gt; 个开发页面。&lt;/p&gt;
 &lt;p&gt;2）开发的编译速度&lt;/p&gt;
 &lt;p&gt;随着项目渐渐变得庞大，Android 项目一次编译需要   &lt;strong&gt;2-3 分钟&lt;/strong&gt;，机器不好的还需要   &lt;strong&gt;10 分钟&lt;/strong&gt;，iOS 可能会快一点，也需要   &lt;strong&gt;1-2 分钟&lt;/strong&gt;。使用 weex 后，界面修改，只需要  &lt;strong&gt;十几秒&lt;/strong&gt;。&lt;/p&gt;
 &lt;p&gt;3）测试效率&lt;/p&gt;
 &lt;p&gt;提测之后，发现 bug，修复完成，测试总需要重新下载一个包进行安装；使用 weex 后，跟原生无关的 bug，只要测试重启 App 就可以进行验证。&lt;/p&gt;
 &lt;h3&gt;2. 动态化&lt;/h3&gt;
 &lt;p&gt;weex 页面最后打包完是一个 js 文件，只要能做到动态下发 JavaScript，那便可以实现动态化，可以热修复，甚至可以热部署，完全替换或者新增页面。&lt;/p&gt;
 &lt;h3&gt;3. 成熟度&lt;/h3&gt;
 &lt;p&gt;在 2016 年阿里双十一中，Weex 在阿里双十一会场中的覆盖率接近 99%，页面数量接近 2000，覆盖了包括主会场、分会场、分分会场、人群会场在内几乎所有的阿里双十一会场业务。阿里双十一主会场秒开率97%，全部会场页面达到 93%。  &lt;br /&gt;2016 年 12 月 15 日，阿里巴巴宣布将移动开源项目 Weex 捐赠给 Apache 基金会开始孵化。  &lt;br /&gt;2017 年，weex 在阿里业务里增长如下图，来自 WeexConf 2018。&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="&amp;#38463;&amp;#37324;&amp;#19994;&amp;#21153;&amp;#22686;&amp;#38271;" src="https://segmentfault.com/img/remote/1460000016850045" title="&amp;#38463;&amp;#37324;&amp;#19994;&amp;#21153;&amp;#22686;&amp;#38271;"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;h3&gt;4. 接入成本&lt;/h3&gt;
 &lt;p&gt;经过实践，一个移动端开发，一周时间就可以开始进行使用 weex 进行业务开发。&lt;/p&gt;
 &lt;h2&gt;三、如何使用 weex 进行无线开发&lt;/h2&gt;
 &lt;p&gt;weex 其实是一套方案，各个流程很多东西需要自己建设，把它建设得让小伙伴可以以较小成本开始使用 weex，把它建设得融入已有的系统。这方面，我们目前做了下面这几个方面，还任重道远。&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="zanweex &amp;#24314;&amp;#35774;" src="https://segmentfault.com/img/remote/1460000016850046" title="zanweex &amp;#24314;&amp;#35774;"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;h3&gt;1. 开发工具 zweex-toolkit&lt;/h3&gt;
 &lt;p&gt;这是一个脚手架工具，基于 weex 官方的 weex-toolkit，用于新建 weex 工程，目前只支持 vue。&lt;/p&gt;
 &lt;p&gt;随着页面的增多，业务的复杂，工程会慢慢变得庞大，每次运行的时候如果全部页面都运行起来比较慢。为了解决这个问题，使用 zweex-toolkit 创建建的工程模板支持运行的时候，支持只运行指定目录下的页面，只要在 npm start 后加上参数即可，如：&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;npm run start hi,helloworld&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;这样就表示只运行 hi 目录下和 helloworld 下的页面。  &lt;br /&gt;另外，我们支持：&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;新增页面   &lt;code&gt;zweex page&lt;/code&gt;
&lt;/li&gt;
  &lt;li&gt;开启调试   &lt;code&gt;zweex debug&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;
 &lt;h3&gt;2. ZanWeex SDK 的实现&lt;/h3&gt;
 &lt;p&gt;官方 weex sdk 做的事情，就是输入一个 js 文件，然后返回一个view。考虑到每个应用的路由和个性化的需要，这一点，ZanWeex SDK 没有做其他工作，也还是返回了一个view，业务方可以根据自己的需要将view添加到自己想要展示的地方。ZanWeex SDK 做的事情主要有如下几方面：&lt;/p&gt;
 &lt;p&gt;1）  &lt;strong&gt;支持下发配置，支持动态化，可以完成整个页面的替换&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;weex 页面打包后的结果是一个 js 文件，所以可以进行下发进行动态更新，那么就需要有一份配置，来关联页面路由和 js 文件的关系，于是我们设计了这样的数据结构：&lt;/p&gt;
 &lt;p&gt;h5：页面路由地址，可以直接使用发布平台生成的 h5 地址&lt;/p&gt;
 &lt;p&gt;js：打包后的 js 文件地址&lt;/p&gt;
 &lt;p&gt;version：支持的最低 App 版本，因为新页面如果需要 native 扩展，那就需要发布新版本进行支持&lt;/p&gt;
 &lt;p&gt;md5：为了校验完整性，我们在配置里添加每个 js 文件的 md5。&lt;/p&gt;
 &lt;p&gt;2）  &lt;strong&gt;支持多模块独立配置，互不影响&lt;/strong&gt;  &lt;br /&gt; 一个App里会有多个模块，每个模块可能由独立的团队进行负责，所以为了减少耦合，我们将配置独立，每个模块可以独立管理自己的配置，独立接入weex，不依赖于宿主App。&lt;/p&gt;
 &lt;p&gt;3）  &lt;strong&gt;预加载页面模板，支持页面模板缓存和配置缓存&lt;/strong&gt;&lt;/p&gt;
 &lt;ul&gt;  &lt;li&gt;如果没有缓存，每次都从服务端拉取页面模板，那么是不可能达到秒开的，跟没有做缓存的H5页面就区别不大了。我们SDK会预加载页面模板到本地，打开过的页面会缓存到内存。这样渲染的时间就更接近原生的渲染时间了。&lt;/li&gt;&lt;/ul&gt;
 &lt;p&gt;4）  &lt;strong&gt;支持开发时的hot  reloading，前端开发般的体验&lt;/strong&gt;&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;如果没有hot  reloading，那么每次修改完页面，都得退出页面重新进入。为了省去这个操作，hot reloading是必须的。&lt;/li&gt;
  &lt;li&gt;weex 工程里本地开发时候，通过webpack-dev-server来启动一个websocket，zan weex sdk 打开一个weex页面后，去与它建立连接。webpack-dev-server将工程的编译状态发送给ZanWeex SDK，当接收到渲染完成的指令时，就重新渲染页面，从而达到 hot reloading的目的。&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;5）  &lt;strong&gt;支持页面的适配，提供环境变量&lt;/strong&gt;  &lt;br /&gt;ZanWeex SDK 会提供以下四个变量共 weex 页面使用，方便完成页面配置。&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;容器的高度：weex.config.yzenv.viewHeight&lt;/li&gt;
  &lt;li&gt;容器的宽度：weex.config.yzenv.viewWidth&lt;/li&gt;
  &lt;li&gt;状态栏高度：weex.config.yzenv.statusBarHeight&lt;/li&gt;
  &lt;li&gt;底部栏高度（针对iPhone X，其他为0)：weex.config.yzenv.bottomHeight&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;6）  &lt;strong&gt;开发阶段日志的查看&lt;/strong&gt;  &lt;br /&gt;在开发阶段，weex sdk 源码里输出的日志以及 js 里通过 console.log 输出的日志，还有 js 运行的报错，都只能通过 XCode 和 Android Studio 进行查看。这对于一个只了解一端的开发人员是非常不方便的。于是我们做了一个入口，在打开 weex 页面的时候，会显示该入口，点击即可查看所输出的日志。&lt;/p&gt;
 &lt;p&gt;7）  &lt;strong&gt;参数传递&lt;/strong&gt;  &lt;br /&gt;正向传参：从 A 页面跳转到 B 页面，参数传递是开发过程肯定会遇见的一个场景。SDK  对外提供的渲染接口 renderByH5 的参数包括 url，params，data。业务方进行渲染的时候，可以将参数直接跟在 url 后面，或者通过 params、data 传入，不同方式，取的方式也不一样：&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;url 后面的参数，会传入 data，weex 页面里直接在 data 里定义参数就会自动赋值；&lt;/li&gt;
  &lt;li&gt;params的参数，在 weex 页面里可以通过 weex.config.name 来获取；&lt;/li&gt;
  &lt;li&gt;data 传入的参数，获取方式同第一种。&lt;/li&gt;
  &lt;li&gt;反向传参：从 B 页面返回到 A 页面的时候，携带参数返回也是很常见的一个场景。SDK  提供了统一的存储类 ZParamStorage 来临时存储参数。页面 B 要返回的时候先把数据存入存储区，A 页面显示的时候再从存储区获取，然后清空存储区。&lt;/li&gt;
  &lt;li&gt;非跳转的参数传递：weex 页面之间，可以采用 BroadcastChannel 进行传参，weex 与 native 之间的传递可以通过自己封装 Module 进行实现。&lt;/li&gt;
&lt;/ul&gt;
 &lt;h3&gt;3. 页面的开发&lt;/h3&gt;
 &lt;p&gt;前面有提到，weex 的页面目前可以采用 vue 或者 Rax 编写。对于 Vue 和 Rax 的语法这里不做陈述。这里主要总结了容易在实际开发中卡住小伙伴的几个问题。&lt;/p&gt;
 &lt;p&gt;1）  &lt;strong&gt;如何判断一个页面是否用 weex 来实现？&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;可以认为所有的新页面都可以采取 weex 来开发，区别在于这个页面使用的 native 能力有多少。可以通过自定义 Module 来调用 native 的能力，通过自定义 component 来使用 native 的组件；&lt;/p&gt;
 &lt;p&gt;2）  &lt;strong&gt;什么时候需要自定义 Module？&lt;/strong&gt;&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;
   &lt;p&gt;需要原生的能力的时候，比如：&lt;/p&gt;
   &lt;ul&gt;
    &lt;li&gt;要调用系统选择图片的接口&lt;/li&gt;
    &lt;li&gt;调用打电话、发短信的功能&lt;/li&gt;
    &lt;li&gt;打开其他应用&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
  &lt;li&gt;
   &lt;p&gt;调用已有的业务逻辑，比如：&lt;/p&gt;
   &lt;ul&gt;
    &lt;li&gt;加密、解密逻辑&lt;/li&gt;
    &lt;li&gt;登录逻辑&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;3）  &lt;strong&gt;什么时候需要自定义 component？&lt;/strong&gt;&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;如果一个组件已经使用 native 实现，为了保持统一一致，那么可以将原有的组件封装成 component&lt;/li&gt;
  &lt;li&gt;如果一个组件不能使用 weex 实现，比如地图组件、超长图显示等&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;4）  &lt;strong&gt;多个弹层的布局如何实现？&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;weex 页面渲染的层级，是从上而下的，越在下面的布局，显示越上层。所以要作为弹层的布局，就把它放到最下面。&lt;/p&gt;
 &lt;p&gt;5）  &lt;strong&gt;页面的动画如何实现？&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;官方 weex sdk 已经封装了 animation 的 module 可以直接使用，复杂的动画可以使用 BindingX 实现。&lt;/p&gt;
 &lt;p&gt;6）  &lt;strong&gt;weex 的代码如何复用？&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;代码都可以抽离出组件。&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;作为一个 UI 组件，抽离成一个组件，向外暴露属性参数和事件接口；&lt;/li&gt;
  &lt;li&gt;作为独立的 js 函数，抽离成一个 js 供其他页面引入；&lt;/li&gt;
  &lt;li&gt;css 样式也可以抽离成一个 css 文件，供其他页面引入；&lt;/li&gt;
  &lt;li&gt;如果包含多个组件形式，可以通过 mixins 来引入。&lt;/li&gt;
&lt;/ul&gt;
 &lt;h3&gt;4. 构建和打包平台&lt;/h3&gt;
 &lt;p&gt;我们开发了以项目为单位的构建平台：&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;每个项目可以添加多个分支，可以是不同仓库的分支。因为一个项目有可能是跨团队跨模块的，但是需要一起发布。&lt;/li&gt;
  &lt;li&gt;构建通过 webpack 构建，构建之后，支持发布线下存储和线上 cdn&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;我们还开发了以应用为单位的 weex 发布平台：&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;这里的应用是一个抽象概念，不是传统的“应用”，可以理解成模块&lt;/li&gt;
  &lt;li&gt;业务方可以在构建平台构建完成后，一键跳转到发布平台进行发布，除了需要第一次填写最低支持的版本号，其他均无需操作。&lt;/li&gt;
  &lt;li&gt;发布平台支持灰度发布、全量发布和回滚。&lt;/li&gt;
  &lt;li&gt;发布平台会展示 weex 在端上的使用情况，渲染时间、渲染错误、下载时间等&lt;/li&gt;
&lt;/ul&gt;
 &lt;h2&gt;四、遇到的问题以及解决方案&lt;/h2&gt;
 &lt;p&gt;在开发过程中，很多问题，可以通过阅读源码来解决，比如：&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;使用 iconfont 的时候，是否已支持缓存?   &lt;p&gt;答：已支持，包括内存缓存和文件缓存，内存缓存使用 familyname 来做 key，文件缓存使用 md5(url) 来做本地文件名&lt;/p&gt;
&lt;/li&gt;
  &lt;li&gt;module实现的函数能不能返回参数？   &lt;p&gt;答：module 的函数氛围 UIThread 和 JSThread，JSThread 对于 js 线程来说是同步的，支持直接返回参数；UIThread 对于 JS 线程来说是异步的，不支持直接返回参数，只能使用 callback&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;另外，很多常见的问题，我们已经在 ZanWeexSDK 进行了解决，包括实现动态化、多模块的支持、缓存管理、Hot Reloading、日志查看、页面适配、参数传递等。&lt;/p&gt;
 &lt;p&gt;此外，还会有一些常见的问题，在此罗列一下：&lt;/p&gt;
 &lt;ol&gt;
  &lt;li&gt;配置的更新机制是怎样的？更新失败，如何打开 weex 页面？   &lt;p&gt;答： 配置的更新接口开放给业务方调用，由业务方决定什么时候调用更新接口；SDK 里做了三种处理，来尽量保证配置可以更新成功：&lt;/p&gt;
   &lt;p&gt;1）配置接口拉取失败后，会有三次重试；&lt;/p&gt;
   &lt;p&gt;2）网络从无网变成有网时，sdk 会检查配置是否已拉取，如果未拉取就主动拉取&lt;/p&gt;
   &lt;p&gt;3）允许业务方内置配置和 js 文件，当拉取失败后，SDK里会从内置配置里读取&lt;/p&gt;
&lt;/li&gt;
  &lt;li&gt;配置的版本管理是怎样的？   &lt;p&gt;答：配置每次发布的时候，都会指定该发布支持的 App 最低版本号。每次请求，会携带 App 版本号，服务端只会返回符合该版本号的最新配置。&lt;/p&gt;
&lt;/li&gt;
  &lt;li&gt;支持不支持屏幕旋转？   &lt;p&gt;答：答案是支持的。旋转之后，屏幕变成了横屏，weex 就按照横屏的尺寸来渲染，问题是只要你写的页面符合这种变化就可以了，跟 native 来实现页面没有什么区别。&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
 &lt;h2&gt;五、未来还要继续做的事情&lt;/h2&gt;
 &lt;ol&gt;
  &lt;li&gt;组件库的建设&lt;/li&gt;
  &lt;li&gt;性能统计，比如帧率、内存、CPU&lt;/li&gt;
  &lt;li&gt;配置和js文件的增量更新、推送更新&lt;/li&gt;
  &lt;li&gt;降级处理&lt;/li&gt;
&lt;/ol&gt;
 &lt;p&gt;  &lt;img alt="&amp;#22270;&amp;#29255;&amp;#25551;&amp;#36848;" src="https://segmentfault.com/img/bV50Mk?w=640&amp;h=400" title="&amp;#22270;&amp;#29255;&amp;#25551;&amp;#36848;"&gt;&lt;/img&gt;&lt;/p&gt;
&lt;div&gt; &lt;a href="https://itindex.net/"  title="IT 资讯"&gt;&lt;img src="https://itindex.net/images/iconWarning.gif" title="IT 资讯" border="0"/&gt; &lt;/a&gt;</description>
      <category>前端 ios android</category>
      <guid isPermaLink="true">https://itindex.net/detail/58920-weex-%E6%97%A0%E7%BA%BF-%E5%BC%80%E5%8F%91</guid>
      <pubDate>Tue, 30 Oct 2018 14:45:58 CST</pubDate>
    </item>
    <item>
      <title>详解音视频直播中的低延时</title>
      <link>https://itindex.net/detail/58297-%E8%A7%86%E9%A2%91-%E7%9B%B4%E6%92%AD</link>
      <description>&lt;blockquote&gt;  &lt;em&gt;高泽华，声网 Agora 音频工匠，先后在中磊电子、士兰微电子、虹软科技主导音频项目。任职 YY 期间负责语音音频技术工作。在音乐、语音编解码方面有超过十年的研发经验。&lt;/em&gt;&lt;/blockquote&gt;
 &lt;p&gt;音视频实时通讯的应用场景已经随处可见，从“吃鸡”的语音对讲、直播连麦、直播答题组队开黑，再到银行视频开户等。对于开发者来讲，除了关注如何能快速实现不同应用场景重点额音视频通讯，另一个更需要关注的可能就是“低延时”。但是，到底实时音视频传输延时应该如何“低”，才能满足你的应用场景呢？&lt;/p&gt;
 &lt;h2&gt;延时的产生与优化&lt;/h2&gt;
 &lt;p&gt;在聊低延时之前，我们先要讲清延时是如何产生的。由于音视频的传输路径一样，我们可以通过一张图来说明延时的产生:&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" src="https://segmentfault.com/img/remote/1460000014558046?w=1494&amp;h=538" title=""&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;在音视频传输过程中，在不同阶段都会产生延时。总体可以分为三类：&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" src="https://segmentfault.com/img/remote/1460000014558047?w=1290&amp;h=452" title=""&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;h3&gt;T1：设备端上的延时&lt;/h3&gt;
 &lt;p&gt;音视频数据在设备端上产生延时还可以细分。设备端上的延时主要与硬件性能、采用的编解码算法、音视频数据量相关，设备端上的延时可达到 30~200ms，甚至更高。如上表所示，音频与视频分别在采集端或播放端产生延时的过程基本相同，但产生延时的原因不同。&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;音频在设备端上的延时：&lt;/strong&gt;&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;音频采集延时：采集后的音频首先会经过声卡进行信号转换，声卡本身会产生延时，比如 M-Audio 声卡设备延迟 1ms，艾肯声卡设备延迟约为 37ms；&lt;/li&gt;
  &lt;li&gt;编解码延时：随后音频进入前处理、编码的阶段，如果采用 OPUS 标准编码，最低算法延时大约需要 2.5~60ms；&lt;/li&gt;
  &lt;li&gt;音频播放延时：这部分延时与播放端硬件性能相关。&lt;/li&gt;
  &lt;li&gt;音频处理延时：前后处理，包括 AEC，ANS，AGC 等前后处理算法都会带来算法延时，通常这里的延时就是滤波器阶数。在 10ms 以内。&lt;/li&gt;
  &lt;li&gt;端网络延时：这部分延时主要出现在解码之前的 jitter buffer 内，如果在抗丢包处理中，增加了重传算法和前向纠错算法，这里的延时一般在 20ms 到 200ms 左右。但是受到 jitter buffer 影响，可能会更高。&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;  &lt;strong&gt;视频在设备端上的延时：&lt;/strong&gt;&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;采集延时：采集时会遇到成像延迟，主要由 CCD 相关硬件产生，市面上较好的 CCD 一秒可达 50 帧，成像延时约为 20ms，如果是一秒 20~25 帧的 CCD，会产生 40~50ms 的延时；&lt;/li&gt;
  &lt;li&gt;编解码延时：以 H.264 为例，它包含 I、P、B 三种帧（下文会详细分析），如果是每秒 30 帧相连帧，且不包括 B 帧（由于 B 帧的解码依赖前后视频帧会增加延迟），采集的一帧数据可能直接进入编码器，没有 B 帧时，编码的帧延时可以忽略不计，但如果有 B 帧，会带来算法延时。&lt;/li&gt;
  &lt;li&gt;视频渲染延时：一般情况下渲染延时非常小，但是它也会受到系统性能、音画同步的影响而增大。&lt;/li&gt;
  &lt;li&gt;端网络延时：与音频一样，视频也会遇到端网络延时。&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;另外，在设备端，CPU、缓存通常会同时处理来自多个应用、外接设备的请求，如果某个问题设备的请求占用了 CPU，会导致音视频的处理请求出现延时。以音频为例，当出现该状况时，CPU 可能无法及时填充音频缓冲区，音频会出现卡顿。所以设备整体的性能，也会影响音视频采集、编解码与播放的延时。&lt;/p&gt;
 &lt;h3&gt;T2：端与服务器间的延时&lt;/h3&gt;
 &lt;p&gt;影响采集端与服务器、服务器与播放端的延时的有以下主几个因素：客户端同服务间的物理距离、客户端和服务器的网络运营商、终端网络的网速、负载和网络类型等。如果服务器就近部署在服务区域、服务器与客户端的网络运营商一致时，影响上下行网络延时的主要因素就是终端网络的负载和网络类型。一般来说，无线网络环境下的传输延时波动较大，传输延时通常在 10~100ms 不定。而有线宽带网络下，同城的传输延时能较稳定的低至 5ms~10ms。但是在国内有很多中小运营商，以及一些交叉的网络环境、跨国传输，那么延时会更高。&lt;/p&gt;
 &lt;h3&gt;T3：服务器间的延时&lt;/h3&gt;
 &lt;p&gt;在此我们要要考虑两种情况，第一种，两端都连接着同一个边缘节点，那么作为最优路径，数据直接通过边缘节点进行转发至播放端；第二种，采集端与播放端并不在同一个边缘节点覆盖范围内，那么数据会经由“靠近”采集端的边缘节点传输至主干网络，然后再发送至“靠近”播放端的边缘节点，但这时服务器之间的传输、排队还会产生延时。仅以骨干网络来讲，数据传输从黑龙江到广州大约需要 30ms，从上海到洛杉矶大约需要 110ms~130ms。&lt;/p&gt;
 &lt;p&gt;在实际情况下，我们为了解决网络不佳、网络抖动，会在采集设备端、服务器、播放端增设缓冲策略。一旦触发缓冲策略就会产生延时。如果卡顿情况多，延时会慢慢积累。要解决卡顿、积累延时，就需要优化整个网络状况。&lt;/p&gt;
 &lt;p&gt;综上所述，由于音视频在采集与播放端上的延时取决于硬件性能、编解码内核的优化，不同设备，表现不同。所以通常市面上常见的“端到端延时”指的是 T2+T3。&lt;/p&gt;
 &lt;h2&gt;延时低≠通话质量可靠&lt;/h2&gt;
 &lt;p&gt;不论是教育、社交、金融，还是其它场景下，大家在开发产品时可能会认为“低延时”一定就是最好的选择。但有时，这种“追求极致”也是陷入误区的表现，低延时不一定意味着通讯质量可靠。由于音频与视频本质上的差异，我们需要分别来讲实时音频、视频的通讯质量与延时之间的关系。&lt;/p&gt;
 &lt;h3&gt;音频质量与延时&lt;/h3&gt;
 &lt;p&gt;  &lt;img alt="&amp;#38899;&amp;#39057;&amp;#37319;&amp;#26679;&amp;#31034;&amp;#24847;&amp;#22270;" src="https://segmentfault.com/img/remote/1460000014558048?w=3116&amp;h=1449" title="&amp;#38899;&amp;#39057;&amp;#37319;&amp;#26679;&amp;#31034;&amp;#24847;&amp;#22270;"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;影响实时音频通讯质量的因素包括：音频采样率、码率、延时。音频信息其实就是一段以时间为横轴的正弦波，它是一段连续的信号（如上图）。&lt;/p&gt;
 &lt;p&gt;采样率：是每秒从连续信号中提取并组成离散信号的采样个数。采样率越高，音频听起来越接近真实声音。&lt;/p&gt;
 &lt;p&gt;码率：它描述了单位时间长度的媒体内容需要空间。码率越高，意味着每个采样的信息量就越大，对这个采样的描述就越精确，音质越好。&lt;/p&gt;
 &lt;p&gt;假设网络状态稳定不变，那么采样率越高、码率越高，音质就越好，但是相应单个采样信息量就越大，那么传输时间可能会相对更长。&lt;/p&gt;
 &lt;p&gt;对照我们之前的公式，如果想要达到低延时，那么可以提高网络传输效率，比如提高带宽、网络速度，这在实验室环境下可以轻易实现。但放到生活环境中，弱网、中小运营商等不可控的问题必定会影响网络传输效率，最后结果就是通讯质量没有保障。还有一种方法，就是降低码率，那么会损失音质。&lt;/p&gt;
 &lt;h3&gt;视频质量与延时&lt;/h3&gt;
 &lt;p&gt;影响实时视频质量的因素包括：码率、帧率、分辨率、延时。其中视频的码率与音频码率相似，是指单位时间传输的数据位数。码率越大，画面细节信息越丰富，视频文件体积越大。&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" src="https://segmentfault.com/img/remote/1460000014558049?w=640&amp;h=479" title=""&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;帧：&lt;/strong&gt;正如大家所知，视频由一帧帧图像组成，如上图所示为 H.264 标准下的视频帧。它以 I 帧、P 帧、B 帧组成的 GOP 分组来表示图像画面（如下图）：I 帧是关键帧，带有图像全部信息；P 帧是预测编码帧，表示与当前与前一帧（I 或 P 帧）之间的差别；B 帧是双向预测编码帧，记录本帧与前后帧的差别。&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;帧率：&lt;/strong&gt;它是指每秒钟刷新的图像帧数。它直接影响视频的流畅度，帧率越大，视频越流畅。由于人类眼睛与大脑处理图像信息非常快，当帧率高于 24fps 时，画面看起来是连贯的，但这只是一个起步值。在游戏场景下，帧率小于 30fps 就会让人感到画面不流畅，当提升到 60fps 时会带来更实时的交互感，但超过 75fps 后一般很难让人感到有什么区别了。&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;分辨率：&lt;/strong&gt;是指单位英寸中所包含的像素点数，直接影响图像的清晰度。如果将一张 640 x 480 与 1024 x 768 的视频在同一设备上全屏播放，你会感到清晰度明显不同。&lt;/p&gt;
 &lt;p&gt;在分辨率一定的情况下，码率与清晰度成正比关系，码率越高，图像越清晰；码率越低，图像越不清晰。&lt;/p&gt;
 &lt;p&gt;在实时视频通话情况下，会出现多种质量问题，比如：与编解码相关的画面糊、不清晰、画面跳跃等现象，因网络传输问题带来的延时、卡顿等。所以解决了低延时，只是解决了实时音频通讯的一小部分问题而已。&lt;/p&gt;
 &lt;p&gt;综上来看，如果在网络传输稳定的情况下，想获得越低的延时，就需要在流畅度、视频清晰度、音频质量等方面进行权衡。&lt;/p&gt;
 &lt;h2&gt;不同场景下的延时&lt;/h2&gt;
 &lt;p&gt;我们通过下表看到每个行业对实时音视频部分特性的大致需求。但是每个行业，不仅对低延时的要求不同，对延时、音质、画质，甚至功耗之间的平衡也有要求。在有些行业中，低延时并非永远排在首位。&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" src="https://segmentfault.com/img/remote/1460000014558050?w=1710&amp;h=564" title=""&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;游戏场景&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;在手游场景下，不同游戏类型对实时音视频的要求不同，比如狼人杀这样的桌游，语音沟通是否顺畅，对游戏体验影响很大，所以对延时要求较高。其它类型游戏具体如下方表格所示。&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" src="https://segmentfault.com/img/remote/1460000014558051?w=1330&amp;h=132" title=""&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;但满足低延时，并不意味着能满足手游开发的要求。因为手游开发本身存在很多痛点，比如功耗、安装包体积、安全性等。从技术层面讲，将实时音视频与手游结合时，手游开发关注的问题有两类：性能类与体验类。&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" src="https://segmentfault.com/img/remote/1460000014558052?w=1280&amp;h=158" title=""&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;在将实时音视频与手游结合时，除了延时，更注重包的大小、功耗等。安装包的大小直接影响用户是否安装，而功耗则直接影响游戏体验。&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;社交直播场景&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;目前的社交直播产品按照功能类型分有仅支持纯音频社交的，比如荔枝 FM；还有音视频社交的，比如陌陌。这两类场景对实时音视频的要求包括：&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" src="https://segmentfault.com/img/remote/1460000014558053?w=1298&amp;h=418" title=""&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;直播答题场景&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;在直播答题场景中，对实时音视频的要求主要有如下两点：&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" src="https://segmentfault.com/img/remote/1460000014558054?w=1294&amp;h=176" title=""&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;我们以前经常能看到主持人说完一道题，题目却还没发到手机上，最后只剩 3 秒的答题时间，甚至没看到题就已出局。该场景的痛点不是低延时，而是直播音视频与题目的同步，保证所有人公平，有钱分。&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;K 歌合唱场景&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;天天 K 歌、唱吧等 K 歌类应用中，都有合唱功能，主流形式是 A 用户上传完整录音，B 用户再进行合唱。实现实时合唱的主要需求有如下几点：&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" src="https://segmentfault.com/img/remote/1460000014558055?w=1270&amp;h=292" title=""&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;在这个场景中，两人的歌声与音乐三者之间的同步给低延时提出了很高的要求。同时，音质也是关键，如果为了延时而大幅降低音质，就偏离了 K 歌应用的初衷。&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;金融场景&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;对于核保、银行开户来讲，需要一对一音视频通话。由于金融业特殊性，该类应用对实时音视频的需求，按照重要性来排序如下： &lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" src="https://segmentfault.com/img/remote/1460000014558056?w=1270&amp;h=242" title=""&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;在这个场景中，低延时不是关键。重要的是，要保证安全性、双录功能和系统平台的兼容。&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;在线教育&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;在线教育主要分为两类：非 K12 在线教育，比如技术开发类教学，该场景对实时音视频的要求主要有：&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" src="https://segmentfault.com/img/remote/1460000014558057?w=1266&amp;h=170" title=""&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;很多非 K12 教学发生在单向直播场景下，所以延时要求并不高。&lt;/p&gt;
 &lt;p&gt;另一类是 K12 在线教育，比如英语外教、部分兴趣教学，通常会有一对一或一对多的师生连麦功能，它对直播场景的要求包括：&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" src="https://segmentfault.com/img/remote/1460000014558058?w=1272&amp;h=248" title=""&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;在 K12 的在线教育中，师生的连麦在低延时方面有较高的要求。如果会涉及跨国的英语教学，或需要面向偏远地区学生，那还要考虑海外节点部署、中小运营商网络的支持等。&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;在线抓娃娃&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;在线抓娃娃是近期新兴热点，主要依靠实时音视频与线下娃娃机来实现。它对实时音视频的要求包括：&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" src="https://segmentfault.com/img/remote/1460000014558059?w=1264&amp;h=242" title=""&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;h2&gt;瓶颈与权衡&lt;/h2&gt;
 &lt;p&gt;  &lt;img alt="" src="https://segmentfault.com/img/remote/1460000014558060?w=900&amp;h=500" title=""&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;产品的开发追求极致，需要让延时低到极限。但理想丰满，现实骨感。我们曾在上文提到，延时是因多个阶段的数据处理、传输而产生的。那么就肯定有它触及天花板的时候。&lt;/p&gt;
 &lt;p&gt;我们大胆假设，要从北京机场传输一路音视频留到上海虹桥机场。我们突破一切物理环境、财力、人力限制，在两地之间搭设了一条笔直的光纤，且保证真空传输（实际上根本不可能）。两地之间距离约为 1061 km。通过计算可知，传输需要约 35ms。数据在采集设备与播放设备端需要的采集、编解码处理与播放缓冲延时计为较高的值，30ms。那么端到端的延时大概需要 65ms。请注意，我们在这里还忽略了音视频文件本身、系统、光的衰减等因素带来的影响。&lt;/p&gt;
 &lt;p&gt;所以，所谓“超低延时”也会遇到瓶颈。在任何实验环境下都可以达到很低的延时，但是到实际环境中，要考虑边缘节点的部署、主干网络拥塞、弱网环境、设备性能、系统性能等问题，实际延时会更大。在一定的网络条件限制下，针对不同场景选择低延时方案或技术选型时，就需要围绕延时、卡顿、音频质量、视频清晰度等指标进行权衡与判断。&lt;/p&gt;
 &lt;hr&gt;&lt;/hr&gt;
 &lt;p&gt;声网Agora有奖征文活动 正在进行中，只要在5月25日前分享你与声网SDK相关的开发经验，即有机会获得机械键盘、T恤等声网定制奖品。  &lt;a href="https://mp.weixin.qq.com/s/HKEmuMzxRokFXXYHXSVjtw" rel="nofollow noreferrer"&gt;详情请戳这里&lt;/a&gt;或邮件咨询tougao#agora.io&lt;/p&gt;
&lt;div&gt; &lt;a href="https://itindex.net/"  title="IT 资讯"&gt;&lt;img src="https://itindex.net/images/iconWarning.gif" title="IT 资讯" border="0"/&gt; &lt;/a&gt;</description>
      <category>ios android</category>
      <guid isPermaLink="true">https://itindex.net/detail/58297-%E8%A7%86%E9%A2%91-%E7%9B%B4%E6%92%AD</guid>
      <pubDate>Tue, 24 Apr 2018 11:06:12 CST</pubDate>
    </item>
    <item>
      <title>app端用户信息自动获取--微博</title>
      <link>https://itindex.net/detail/58027-app-%E7%94%A8%E6%88%B7-%E4%BF%A1%E6%81%AF</link>
      <description>&lt;p&gt;  &lt;a href="https://github.com/zgbgx/appWeiboInfoCrawl"&gt;github地址&lt;/a&gt;&lt;/p&gt;
 &lt;h1&gt;项目目的&lt;/h1&gt;
 &lt;p&gt;在app(ios和android)端使用webview组件与js进行交互，串改页面，让用户授权登录后，获取用户关键信息，并完成自动关注一个账号。&lt;/p&gt;
 &lt;h1&gt;传统爬虫模式的局限&lt;/h1&gt;
 &lt;p&gt;传统爬虫模式，让用户在客户端在输入账号密码，然后传送到后端进行登录，爬取信息，这种方式将要面对各种人机验证措施，加密方法复杂的情况下，还得选择selenium，性能更无法保证。同时，对于个人账户，安全措施越来越严，使用代理ip进行操作，很容易造成异地登录等问题，代理ip也很可能在全网被重复使用的情况下，被封杀，频繁的代理ip切换也会带来需要二次登录等问题。  &lt;br /&gt;所以这两年年来，发现市面上越来越多的提供sdk方式的数据提供商，经过抓包及反编译sdk，发现其大多数使用webview载入第三方页面的方式完成登录，有的在登录完成之后，获取cookie传送到后端完成爬取，有的直接在app内完成所需信息的收集。&lt;/p&gt;
 &lt;h1&gt;登录&lt;/h1&gt;
 &lt;p&gt;这是微博移动端登录页  &lt;br /&gt;  &lt;img alt="weibo&amp;#21407;&amp;#31227;&amp;#21160;&amp;#31471;&amp;#30331;&amp;#24405;&amp;#39029;.png" src="http://upload-images.jianshu.io/upload_images/10280397-c258622a77703836.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240" title="weibo&amp;#21407;&amp;#31227;&amp;#21160;&amp;#31471;&amp;#30331;&amp;#24405;&amp;#39029;.png"&gt;&lt;/img&gt;  &lt;br /&gt;首先使用JavaScript串改当前页面元素，让用户没法意识到这是微博官方的登录页。&lt;/p&gt;
 &lt;h2&gt;载入页面&lt;/h2&gt;
 &lt;p&gt;android&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;webView.loadUrl(LOGINPAGEURL);&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;iOS&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;[self requestUrl:self.loginPageUrl];
//请求url方法
-(void) requestUrl:(NSString*) urlString{
    NSURL* url=[NSURL URLWithString:urlString];
    NSURLRequest* request=[NSURLRequest requestWithURL:url];
    [self.webView loadRequest:request];
}&lt;/code&gt;&lt;/pre&gt;
 &lt;h2&gt;js代码注入&lt;/h2&gt;
 &lt;p&gt;首先我们注入js代码到app的webview中  &lt;br /&gt;android&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;private void injectScriptFile(String filePath) {
        InputStream input;
        try {
            input = webView.getContext().getAssets().open(filePath);
            byte[] buffer = new byte[input.available()];
            input.read(buffer);
            input.close();
            // String-ify the script byte-array using BASE64 encoding
            String encoded = Base64.encodeToString(buffer, Base64.NO_WRAP);
            String funstr = &amp;quot;javascript:(function() {&amp;quot; +
                    &amp;quot;var parent = document.getElementsByTagName(&amp;apos;head&amp;apos;).item(0);&amp;quot; +
                    &amp;quot;var script = document.createElement(&amp;apos;script&amp;apos;);&amp;quot; +
                    &amp;quot;script.type = &amp;apos;text/javascript&amp;apos;;&amp;quot; +
                    &amp;quot;script.innerHTML = decodeURIComponent(escape(window.atob(&amp;apos;&amp;quot; + encoded + &amp;quot;&amp;apos;)));&amp;quot; +
                    &amp;quot;parent.appendChild(script)&amp;quot; +
                    &amp;quot;})()&amp;quot;;
            execJsNoReturn(funstr);
        } catch (IOException e) {
            Log.e(TAG, &amp;quot;injectScriptFile: &amp;quot; + e);
        }
    }&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;iOS&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;//注入js文件
- (void) injectJsFile:(NSString *)filePath{
    NSString *jsPath = [[NSBundle mainBundle] pathForResource:filePath ofType:@&amp;quot;js&amp;quot; inDirectory:@&amp;quot;assets&amp;quot;];
    NSData *data=[NSData dataWithContentsOfFile:jsPath];
    NSString *responData =  [data base64EncodedStringWithOptions:0];
    NSString *jsStr=[NSString stringWithFormat:@&amp;quot;javascript:(function() {\
                     var parent = document.getElementsByTagName(&amp;apos;head&amp;apos;).item(0);\
                     var script = document.createElement(&amp;apos;script&amp;apos;);\
                     script.type = &amp;apos;text/javascript&amp;apos;;\
                     script.innerHTML = decodeURIComponent(escape(window.atob(&amp;apos;%@&amp;apos;)));\
                     parent.appendChild(script)})()&amp;quot;,responData];
    [self.webView evaluateJavaScript:jsStr completionHandler:^(id _Nullable htmlStr,NSError * _Nullable error){
        
    }];
}&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;我们都采用读取js文件，然后base64编码后，使用window.atob把其做为一个脚本注入到当前页面(注意：window.atob处理中文编码后会得到的编码不正确，需要使用ecodeURIComponent escape来进行正确的校正。)  &lt;br /&gt;在这里已经使用了app端，调用js的方法来创建元素。&lt;/p&gt;
 &lt;h2&gt;app端调用js方法&lt;/h2&gt;
 &lt;p&gt;android端：&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;webView.evaluateJavascript(funcStr, new ValueCallback&amp;lt;String&amp;gt;() {
            @Override
            public void onReceiveValue(String s) {

            }

        });&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;ios端：&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;[self.webView evaluateJavaScript:funcStr completionHandler:^(id _Nullable htmlStr,NSError * _Nullable error){
        
    }];&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;这两个方法可以获取返回值，正因为如此，可以使用js提取页面信息后，返回给webview，然后收集信息完成之后，汇总进行通信。&lt;/p&gt;
 &lt;h1&gt;js串改页面&lt;/h1&gt;
 &lt;pre&gt;  &lt;code&gt;//串改页面元素，让用户以为是授权登录
function getLogin(){
 var topEle=selectNode(&amp;apos;//*[@id=&amp;quot;avatarWrapper&amp;quot;]&amp;apos;);
 var imgEle=selectNode(&amp;apos;//*[@id=&amp;quot;avatarWrapper&amp;quot;]/img&amp;apos;);
 topEle.remove(imgEle);
 var returnEle=selectNode(&amp;apos;//*[@id=&amp;quot;loginWrapper&amp;quot;]/a&amp;apos;);
 returnEle.className=&amp;apos;&amp;apos;;
 returnEle.innerText=&amp;apos;&amp;apos;;
 pEle=selectNode(&amp;apos;//*[@id=&amp;quot;loginWrapper&amp;quot;]/p&amp;apos;);
 pEle.className=&amp;quot;&amp;quot;;
 pEle.innerHTML=&amp;quot;&amp;quot;;
 footerEle=selectNode(&amp;apos;//*[@id=&amp;quot;loginWrapper&amp;quot;]/footer&amp;apos;);
 footerEle.innerHTML=&amp;quot;&amp;quot;;
 var loginNameEle=selectNode(&amp;apos;//*[@id=&amp;quot;loginName&amp;quot;]&amp;apos;);
 loginNameEle.placeholder=&amp;quot;请输入用户名&amp;quot;;
 var buttonEle=selectNode(&amp;apos;//*[@id=&amp;quot;loginAction&amp;quot;]&amp;apos;);
 buttonEle.innerText=&amp;quot;请进行用户授权&amp;quot;;
 selectNode(&amp;apos;//*[@id=&amp;quot;loginWrapper&amp;quot;]/form/section/div[1]/i&amp;apos;).className=&amp;quot;&amp;quot;;
 selectNode(&amp;apos;//*[@id=&amp;quot;loginWrapper&amp;quot;]/form/section/div[2]/i&amp;apos;).className=&amp;quot;&amp;quot;;
 selectNode(&amp;apos;//*[@id=&amp;quot;loginAction&amp;quot;]&amp;apos;).className=&amp;quot;btn&amp;quot;;
 selectNode(&amp;apos;//a[@id=&amp;quot;loginAction&amp;quot;]&amp;apos;).addEventListener(&amp;apos;click&amp;apos;,transPortUnAndPw,false);
 return window.webkit;
}
function transPortUnAndPw(){
 username=selectNode(&amp;apos;//*[@id=&amp;quot;loginName&amp;quot;]&amp;apos;).value;
 pwd=selectNode(&amp;apos;//*[@id=&amp;quot;loginPassword&amp;quot;]&amp;apos;).value;
 window.webkit.messageHandlers.getInfo({body:JSON.stringify({&amp;quot;username&amp;quot;:username,&amp;quot;pwd&amp;quot;:pwd})});
}&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;使用js修改页面元素，使之看起来不会让人发觉这是weibo官方的页面。  &lt;br /&gt;修改后的页面如图：  &lt;br /&gt;  &lt;img alt="&amp;#20462;&amp;#25913;&amp;#21518;&amp;#30331;&amp;#24405;&amp;#39029;&amp;#38754;.png" src="http://upload-images.jianshu.io/upload_images/10280397-c2b2aee8b46a2417.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240" title="&amp;#20462;&amp;#25913;&amp;#21518;&amp;#30331;&amp;#24405;&amp;#39029;&amp;#38754;.png"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;h1&gt;串改登录点击事件，获取用户名密码&lt;/h1&gt;
 &lt;pre&gt;  &lt;code&gt;selectNode(&amp;apos;//a[@id=&amp;quot;loginAction&amp;quot;]&amp;apos;).addEventListener(&amp;apos;click&amp;apos;,transPortUnAndPw,false);
function transPortUnAndPw(){
  username=selectNode(&amp;apos;//*[@id=&amp;quot;loginName&amp;quot;]&amp;apos;).value;
  pwd=selectNode(&amp;apos;//*[@id=&amp;quot;loginPassword&amp;quot;]&amp;apos;).value;
  window.webkit.messageHandlers.getInfo({body:JSON.stringify({&amp;quot;username&amp;quot;:username,&amp;quot;pwd&amp;quot;:pwd})});
}&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;同时串改登录点击按钮，通过js调用app webview的方法，把用户名和密码传递给app webview 完成信息收集。&lt;/p&gt;
 &lt;h2&gt;js调用webview的方法&lt;/h2&gt;
 &lt;p&gt;android端：&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;// js代码
window.weibo.getPwd(JSON.stringify({&amp;quot;username&amp;quot;:username,&amp;quot;pwd&amp;quot;:pwd}));
//Java代码
webView.addJavascriptInterface(new WeiboJsInterface(), &amp;quot;weibo&amp;quot;);
public class WeiboJsInterface {
        @JavascriptInterface
        public void getPwd(String returnValue) {
            try {
                unpwDict = new JSONObject(returnValue);
            } catch (JSONException e) {
                e.printStackTrace();
            }
        }
    }&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;android通过实现一个@JavaScriptInterface接口，把这个方法添加类添加到webview的浏览器内核之上，当调用这个方法时，会触发android端的调用。  &lt;br /&gt;ios端：&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;//js代码
window.webkit.messageHandlers.getInfo({body:JSON.stringify({&amp;quot;username&amp;quot;:username,&amp;quot;pwd&amp;quot;:pwd})});
//oc代码
WKUserContentController *userContentController = [[WKUserContentController alloc] init];
 [userContentController addScriptMessageHandler:self name:@&amp;quot;getInfo&amp;quot;];

- (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message
{
    
    self.unpwDict=[self getReturnDict:message.body];
}&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;ios方式，实现方式与此类似，不过由于我对oc以及ios开发不熟悉，代码运行不符合期望,希望专业的能指正。&lt;/p&gt;
 &lt;h1&gt;个人信息获取&lt;/h1&gt;
 &lt;h2&gt;直接提取页面的难点&lt;/h2&gt;
 &lt;p&gt;webview这个组件，无论是在android端 onPageFinished方法还是ios端的didFinishNavigation方法，都无法正确判定页面是否加载完全。所以对于很多页面，还是选择走接口&lt;/p&gt;
 &lt;h1&gt;请求接口&lt;/h1&gt;
 &lt;p&gt;本项目中，获取用户自己的微博，关注，和分析，都是使用接口，拿到预览页，直接解析数，对于关键的参数，需要仔细抓包获取  &lt;br /&gt;  &lt;img alt="&amp;#25235;&amp;#21253;1.png" src="http://upload-images.jianshu.io/upload_images/10280397-b14b37bd91c4ab4c.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240" title="&amp;#25235;&amp;#21253;1.png"&gt;&lt;/img&gt;  &lt;br /&gt;仔细分析 “我”这个标签下的请求情况，发现  &lt;a href="https://m.weibo.cn/home/me?format=cards%E8%BF%99%E4%B8%AA%E9%93%BE%E6%8E%A5%E5%8C%85%E5%90%AB%E7%94%A8%E6%88%B7%E6%A0%B8%E5%BF%83%E6%95%B0%E6%8D%AE"&gt;https://m.weibo.cn/home/me?fo...&lt;/a&gt;，通过这个请求，获取核心参数，然后，获取用户的微博 关注 粉丝的预览页面。  &lt;br /&gt;然后通过&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;JSON.stringify(JSON.parse(document.getElementsByTagName(&amp;apos;pre&amp;apos;)[0].innerText))&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;获取json字符串，并传到app端进行解析。  &lt;br /&gt;解析及多次请求的逻辑&lt;/p&gt;
 &lt;h1&gt;请求页面&lt;/h1&gt;
 &lt;p&gt;也有页面，如个人资料，页面较简单，可以使用js提取&lt;/p&gt;
 &lt;h3&gt;js代码&lt;/h3&gt;
 &lt;pre&gt;  &lt;code&gt;function getPersonInfo(){
  var name=selectNodeText(&amp;apos;//*[@id=&amp;quot;J_name&amp;quot;]&amp;apos;);
  var sex=selectNodeText(&amp;apos;/*[@id=&amp;quot;sex&amp;quot;]/option[@selected]&amp;apos;);
  var location=selectNodeText(&amp;apos;//*[@id=&amp;quot;J_location&amp;quot;]&amp;apos;);
  var year=selectNodeText(&amp;apos;//*[@id=&amp;quot;year&amp;quot;]/option[@selected]&amp;apos;);
  var month=selectNodeText(&amp;apos;//*[@id=&amp;quot;month&amp;quot;]/option[@selected]&amp;apos;);
  var day=selectNodeText(&amp;apos;//*[@id=&amp;quot;day&amp;quot;]/option[@selected]&amp;apos;);
  var email=selectNodeText(&amp;apos;//*[@id=&amp;quot;J_email&amp;quot;]&amp;apos;);
  var blog=selectNodeText(&amp;apos;//*[@id=&amp;quot;J_blog&amp;quot;]&amp;apos;);
  if(blog==&amp;apos;输入博客地址&amp;apos;){
    blog=&amp;apos;未填写&amp;apos;;
  }
  var qq=selectNodeText(&amp;apos;//*[@id=&amp;quot;J_QQ&amp;quot;]&amp;apos;);
  if(qq==&amp;apos;QQ帐号&amp;apos;){
    qq=&amp;quot;未填写&amp;quot;;
  }
  birthday=year+&amp;apos;-&amp;apos;+month+&amp;apos;-&amp;apos;+day;
  theDict={&amp;apos;name&amp;apos;:name,&amp;apos;sex&amp;apos;:sex,&amp;apos;localtion&amp;apos;:location,&amp;apos;birthday&amp;apos;:birthday,&amp;apos;email&amp;apos;:email,&amp;apos;blog&amp;apos;:blog,&amp;apos;qq&amp;apos;:qq};
  return JSON.stringify({&amp;apos;personInfomation&amp;apos;:theDict});
}&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;由于webview不支持 $x 的xpath写法，为了方便，使用原生的XPathEvaluator, 实现了特定的提取。&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;function selectNodes(sXPath) {
  var evaluator = new XPathEvaluator();
  var result = evaluator.evaluate(sXPath, document, null, XPathResult.ANY_TYPE, null);
  if (result != null) {
    var nodeArray = [];
    var nodes = result.iterateNext();
    while (nodes) {
      nodeArray.push(nodes);
      nodes = result.iterateNext();
    }
    return nodeArray;
  }
  return null;
};
//选取子节点
function selectChildNode(sXPath, element) {
  var evaluator = new XPathEvaluator();
  var newResult = evaluator.evaluate(sXPath, element, null, XPathResult.ANY_TYPE, null);
  if (newResult != null) {
    var newNode = newResult.iterateNext();
    return newNode;
  }
}

function selectChildNodeText(sXPath, element) {
  var evaluator = new XPathEvaluator();
  var newResult = evaluator.evaluate(sXPath, element, null, XPathResult.ANY_TYPE, null);
  if (newResult != null) {
    var newNode = newResult.iterateNext();
    if (newNode != null) {
      return newNode.textContent.replace(/(^\s*)|(\s*$)/g, &amp;quot;&amp;quot;); ;
    } else {
      return &amp;quot;&amp;quot;;
    }
  }
}

function selectChildNodes(sXPath, element) {
  var evaluator = new XPathEvaluator();
  var newResult = evaluator.evaluate(sXPath, element, null, XPathResult.ANY_TYPE, null);
  if (newResult != null) {
    var nodeArray = [];
    var newNode = newResult.iterateNext();
    while (newNode) {
      nodeArray.push(newNode);
      newNode = newResult.iterateNext();
    }
    return nodeArray;
  }
}

function selectNodeText(sXPath) {
  var evaluator = new XPathEvaluator();
  var newResult = evaluator.evaluate(sXPath, document, null, XPathResult.ANY_TYPE, null);
  if (newResult != null) {
    var newNode = newResult.iterateNext();
    if (newNode) {
      return newNode.textContent.replace(/(^\s*)|(\s*$)/g, &amp;quot;&amp;quot;); ;
    }
    return &amp;quot;&amp;quot;;
  }
}
function selectNode(sXPath) {
  var evaluator = new XPathEvaluator();
  var newResult = evaluator.evaluate(sXPath, document, null, XPathResult.ANY_TYPE, null);
  if (newResult != null) {
    var newNode = newResult.iterateNext();
    if (newNode) {
      return newNode;
    }
    return null;
  }
}&lt;/code&gt;&lt;/pre&gt;
 &lt;h1&gt;自动关注用户&lt;/h1&gt;
 &lt;p&gt;由于个人微博页面 onPageFinished与didFinishNavigation这两个方法无法判定页面是否加载完全，  &lt;br /&gt;为了解决这个问题，在android端，使用拦截url，判定页面加载图片的数量来确定，是否，加载完全&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;//由于页面的正确加载onPageFinieshed和onProgressChanged都不能正确判定，所以选择在加载多张图片后，判定页面加载完成。
            //在这样的情况下，自动点击元素，完成自动关注用户。
            @Override
            public void onLoadResource(WebView view, String url) {
                if (webView.getUrl().contains(AUTOFOCUSURL) &amp;amp;&amp;amp; url.contains(&amp;quot;jpg&amp;quot;)) {
                    newIndex++;
                    if (newIndex == 5) {
                        webView.post(new Runnable() {
                            @Override
                            public void run() {
                                injectJsUseXpath(&amp;quot;autoFocus.js&amp;quot;);
                                execJsNoReturn(&amp;quot;autoFocus();&amp;quot;);
                            }
                        });
                    }
                }
                super.onLoadResource(view, url);
            }&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;js 自动点击&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;function autoFocus(){
  selectNode(&amp;apos;//span[@class=&amp;quot;m-add-box&amp;quot;]&amp;apos;).click();
}&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;在ios端，使用访问接口的方式  &lt;br /&gt;  &lt;img alt="&amp;#25235;&amp;#21253;2.png" src="http://upload-images.jianshu.io/upload_images/10280397-d3ed5bcb8c443191.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240" title="&amp;#25235;&amp;#21253;2.png"&gt;&lt;/img&gt;  &lt;br /&gt;除了目标用户的id外，还有一个st字符串，通过chrome的search，定位，然后通过js提取&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;function getSt(){
  return config[&amp;apos;st&amp;apos;];
}&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;然后构造post，请求，完成关注&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;- (void) autoFocus:(NSString*) st{
    //Wkwebview采用js模拟完成表单提交
    NSString *jsStr=[NSString stringWithFormat:@&amp;quot;function post(path, params) {var method = \&amp;quot;post\&amp;quot;; \
                     var form = document.createElement(\&amp;quot;form\&amp;quot;); \
                     form.setAttribute(\&amp;quot;method\&amp;quot;, method); \
                     form.setAttribute(\&amp;quot;action\&amp;quot;, path); \
                     for(var key in params) { \
                     if(params.hasOwnProperty(key)) { \
                     var hiddenField = document.createElement(\&amp;quot;input\&amp;quot;);\
                     hiddenField.setAttribute(\&amp;quot;type\&amp;quot;, \&amp;quot;hidden\&amp;quot;);\
                     hiddenField.setAttribute(\&amp;quot;name\&amp;quot;, key);\
                     hiddenField.setAttribute(\&amp;quot;value\&amp;quot;, params[key]);\
                     form.appendChild(hiddenField);\
                     }\
                     }\
                     document.body.appendChild(form);\
                     form.submit();\
                     }\
                     post(&amp;apos;https://m.weibo.cn/api/friendships/create&amp;apos;,{&amp;apos;uid&amp;apos;:&amp;apos;1195242865&amp;apos;,&amp;apos;st&amp;apos;:&amp;apos;%@&amp;apos;});&amp;quot;,st];
    [self execJsNoReturn:jsStr];
}&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;ios WkWebview没有post请求，接口，所以构造一个表单提交，完成post请求。  &lt;br /&gt;完成，一个自动关注，当然，构造一个用户id的列表，很简单就可以实现自动关注多个用户。&lt;/p&gt;
 &lt;h1&gt;关于cookie&lt;/h1&gt;
 &lt;p&gt;如果需要爬取的数据量大，可以选择爬取少量关键信息后，把cookie传到后端处理  &lt;br /&gt;android 端 cookie处理&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;CookieSyncManager.createInstance(context);  
CookieManager cookieManager = CookieManager.getInstance(); &lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;通过cookieManage对象可以获取cookie字符串，传送到后端，继续爬取&lt;/p&gt;
 &lt;p&gt;ios端cookie处理&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;NSDictionary *cookie = [AppInfo shareAppInfo].userModel.cookies;&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;处理方式与android端类似。&lt;/p&gt;
 &lt;h1&gt;总结&lt;/h1&gt;
 &lt;p&gt;对于数据工程师来说，webview有点类似于selenium，但是运行在服务端的selenium，有太多的局限性。webview的在客户端运行，就像一个用户就是一台肉机。  &lt;br /&gt;以webview为基础，使用app收集信息加以利用，现阶段大多数人都还没意识到，但是，市场上的产品已经越来越多，特别是那些对数据有特殊需要的各种金融机构。  &lt;br /&gt;对于普通用户来说，不要轻易在一个app上登录第三方账户，信息泄露，财产损失，在按下登录或者本例中的假装授权后，都是不可避免的。&lt;/p&gt;
&lt;div&gt; &lt;a href="https://itindex.net/"  title="IT 资讯"&gt;&lt;img src="https://itindex.net/images/iconWarning.gif" title="IT 资讯" border="0"/&gt; &lt;/a&gt;</description>
      <category>安全 大数据 网页爬虫 ios android</category>
      <guid isPermaLink="true">https://itindex.net/detail/58027-app-%E7%94%A8%E6%88%B7-%E4%BF%A1%E6%81%AF</guid>
      <pubDate>Wed, 07 Feb 2018 14:24:39 CST</pubDate>
    </item>
    <item>
      <title>IOS分析崩溃日志</title>
      <link>https://itindex.net/detail/56404-ios-%E5%88%86%E6%9E%90-%E6%97%A5%E5%BF%97</link>
      <description>&lt;p&gt;  &lt;a href="http://www.jianshu.com/p/727eff8b5404"&gt;我的简书文章地址&lt;/a&gt;&lt;/p&gt;
 &lt;h2&gt;  &lt;strong&gt;前言&lt;/strong&gt;&lt;/h2&gt;
 &lt;p&gt;  IOS分析定位崩溃问题有很多种方式，但是发布到AppStore的应用如果崩溃了，我们该怎么办呢？通常我们都会在系统中接入统计系统，在系统崩溃的时候记录下崩溃日志，下次启动时将日志发送到服务端，比较好的第三方有umeng之类的。今天我们来讲一下通过崩溃日志来分析定位我们的bug。&lt;/p&gt;
 &lt;h2&gt;  &lt;strong&gt;dYSM文件&lt;/strong&gt;&lt;/h2&gt;
 &lt;p&gt;  分析崩溃日志的前提是我们需要有  &lt;strong&gt;dYSM文件&lt;/strong&gt;，这个文件是我们用archive打包时生成的  &lt;strong&gt;.xcarchive&lt;/strong&gt;后缀的文件包。一个良好的习惯是，在你打包提交审核的时候，将生成的.xcarchive与ipa文件一同拷贝一份，按照版本号保存下来，这样如果线上出现问题可以快速的找到你想要的文件来定位你的问题。&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" src="https://segmentfault.com/img/remote/1460000007876908" title=""&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;h2&gt;  &lt;strong&gt;崩溃日志&lt;/strong&gt;&lt;/h2&gt;
 &lt;p&gt;一般崩溃日志都会像下面这样:&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;NSConcreteMutableAttributedString addAttribute:value:range:: nil value
(null)
((
    CoreFoundation                      0x0000000185c642f4 &amp;lt;redacted&amp;gt; + 160
    libobjc.A.dylib                     0x00000001974880e4 objc_exception_throw + 60
    CoreFoundation                      0x0000000185c64218 &amp;lt;redacted&amp;gt; + 0
    Foundation                          0x0000000186a9dfb4 &amp;lt;redacted&amp;gt; + 152
    Xmen                                0x10073fb30 Xmen + 7600944
    Xmen                                0x1006bbbf4 Xmen + 7060468
    UIKit                               0x000000018a9a47fc &amp;lt;redacted&amp;gt; + 60
    UIKit                               0x000000018a9a512c &amp;lt;redacted&amp;gt; + 104
    UIKit                               0x000000018a6b2b6c &amp;lt;redacted&amp;gt; + 88
    UIKit                               0x000000018a9a4fd4 &amp;lt;redacted&amp;gt; + 444
    UIKit                               0x000000018a78e274 &amp;lt;redacted&amp;gt; + 1012
    UIKit                               0x000000018a999aac &amp;lt;redacted&amp;gt; + 2904
    UIKit                               0x000000018a785268 &amp;lt;redacted&amp;gt; + 172
    UIKit                               0x000000018a6a1760 &amp;lt;redacted&amp;gt; + 580
    QuartzCore                          0x0000000189fe9e1c &amp;lt;redacted&amp;gt; + 152
    QuartzCore                          0x0000000189fe4884 &amp;lt;redacted&amp;gt; + 320
    QuartzCore                          0x0000000189fe4728 &amp;lt;redacted&amp;gt; + 32
    QuartzCore                          0x0000000189fe3ebc &amp;lt;redacted&amp;gt; + 276
    QuartzCore                          0x0000000189fe3c3c &amp;lt;redacted&amp;gt; + 528
    QuartzCore                          0x0000000189fdd364 &amp;lt;redacted&amp;gt; + 80
    CoreFoundation                      0x0000000185c1c2a4 &amp;lt;redacted&amp;gt; + 32
    CoreFoundation                      0x0000000185c19230 &amp;lt;redacted&amp;gt; + 360
    CoreFoundation                      0x0000000185c19610 &amp;lt;redacted&amp;gt; + 836
    CoreFoundation                      0x0000000185b452d4 CFRunLoopRunSpecific + 396
    GraphicsServices                    0x000000018f35b6fc GSEventRunModal + 168
    UIKit                               0x000000018a70afac UIApplicationMain + 1488
    Xmen                                0x1008cf9c0 Xmen + 9238976
    libdyld.dylib                       0x0000000197b06a08 &amp;lt;redacted&amp;gt; + 4
)
dSYM UUID: 30833A40-0F40-3980-B76B-D6E86E4DBA85
CPU Type: arm64
Slide Address: 0x0000000100000000
Binary Image: Xmen
Base Address: 0x000000010007c000&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;是不是看的一脸懵逼，下面我来教大家如何结合crash log 与 dYSM文件 来分析定位出代码崩溃在哪一个文件的哪一行代码&lt;/p&gt;
 &lt;h2&gt;  &lt;strong&gt;提取崩溃日志中有用的信息&lt;/strong&gt;&lt;/h2&gt;
 &lt;ul&gt;
  &lt;li&gt;   &lt;p&gt;NSConcreteMutableAttributedString addAttribute:value:range:: nil value 崩溃的原因是value为nil&lt;/p&gt;&lt;/li&gt;
  &lt;li&gt;   &lt;p&gt;&amp;quot;    4  Xmen                                0x10073fb30 Xmen + 7600944&amp;quot; 它指出了应用名称，崩溃时的调用方法的地址，文件的地址以及方法所在的行的位置，我们需要的是这一个:&amp;quot;0x10073fb30&amp;quot;。&lt;/p&gt;&lt;/li&gt;
  &lt;li&gt;   &lt;p&gt;&amp;quot;dSYM UUID: 30833A40-0F40-3980-B76B-D6E86E4DBA85&amp;quot;。&lt;/p&gt;&lt;/li&gt;
  &lt;li&gt;   &lt;p&gt;&amp;quot;CPU Type: arm64&amp;quot;。&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
 &lt;h2&gt;  &lt;strong&gt;开始分析&lt;/strong&gt;&lt;/h2&gt;
 &lt;ul&gt;
  &lt;li&gt;   &lt;p&gt;打开终端进入到你的dYSM文件的目录下面:    &lt;br /&gt;    &lt;code&gt;cd /Dandy/XMEN/上线版本/2.0.17_105/aaaa.xcarchive/dSYMs&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
  &lt;li&gt;   &lt;p&gt;验证下崩溃日志中的UUID与本地的dYSM文件是否是相匹配的：    &lt;br /&gt;&amp;quot;Xmen&amp;quot;为你的app名称&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;  &lt;code&gt;dwarfdump --uuid Xmen.app.dSYM&lt;/code&gt;  &lt;br /&gt;结果是:&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;UUID: BFF6AE00-8B5F-39BD-AFD0-27707C489B25 (armv7) Xmen.app.dSYM/Contents/Resources/DWARF/Xmen
UUID: 30833A40-0F40-3980-B76B-D6E86E4DBA85 (arm64) Xmen.app.dSYM/Contents/Resources/DWARF/Xmen&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;发现与我们日志中的:UUID和CPU Type是相匹配的&lt;/p&gt;
 &lt;ul&gt;  &lt;li&gt;   &lt;p&gt;查找错误信息    &lt;br /&gt;    &lt;code&gt;dwarfdump --arch=arm64 --lookup 0x10073fb30  /Dandy/XMEN/上线版本/2.0.17_105/aaaa.xcarchive/dSYMs/Xmen.app.dSYM/Contents/Resources/DWARF/Xmen&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;
 &lt;p&gt;&amp;quot;arm64&amp;quot;与&amp;quot;0x1008cf9c0&amp;quot;分别对应于上面我们从日志中提取出来的有用信息  &lt;br /&gt;&amp;quot;/Dandy/XMEN/上线版本/2.0.17_105/aaaa.xcarchive/dSYMs/Xmen.app.dSYM/Contents/Resources/DWARF/Xmen&amp;quot;对应于你本地dYSM文件目录  &lt;br /&gt;&amp;quot;Xmen&amp;quot;对应于你的app名称  &lt;br /&gt;结果像下面这样:&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;File: /Dandy/XMEN/上线版本/2.0.17_105/aaaa.xcarchive/dSYMs/Xmen.app.dSYM/Contents/Resources/DWARF/Xmen (arm64)
Looking up address: 0x000000010073fb30 in .debug_info... found!
0x00219b05: Compile Unit: length = 0x00003dd0  version = 0x0002  abbr_offset = 0x00000000  addr_size = 0x08  (next CU at 0x0021d8d9)
0x00219b10: TAG_compile_unit [107] *
AT_producer( &amp;quot;Apple LLVM version 8.0.0 (clang-800.0.42.1)&amp;quot; )
AT_language( DW_LANG_ObjC )
AT_name( &amp;quot;/Dandy/checkSvn/IOS/xmen/Xmen/Modules/StoreManage/SellerOrder/View/DSSellerOrderSectionHeaderView.m&amp;quot; )
AT_stmt_list( 0x001272a9 )
AT_comp_dir( &amp;quot;/Dandy/checkSvn/IOS/xmen&amp;quot; )
AT_APPLE_major_runtime_vers( 0x02 )
AT_low_pc( 0x000000010072b8ac )
AT_high_pc( 0x000000010074e350 )
0x0021aec5:    TAG_subprogram [119] *
AT_low_pc( 0x0000000100739810 )
AT_high_pc( 0x000000010074006c )
AT_frame_base( reg29 )
AT_object_pointer( {0x0021aee3} )
AT_name( &amp;quot;-[DSSellerOrderSectionHeaderView updateContentWithOrderData:isEdit:]&amp;quot; )
AT_decl_file( &amp;quot;/Dandy/checkSvn/IOS/xmen/Xmen/Modules/StoreManage/SellerOrder/View/DSSellerOrderSectionHeaderView.m&amp;quot; )
AT_decl_line( 248 )
AT_prototyped( 0x01 )
0x0021af36:        TAG_lexical_block [138] *
AT_ranges( 0x00008640
[0x000000010073cf90 - 0x000000010073fb88)
[0x000000010073fbc0 - 0x000000010073fbc4)
End )
Line table dir : &amp;apos;/Dandy/checkSvn/IOS/xmen/Xmen/Modules/StoreManage/SellerOrder/View&amp;apos;
Line table file: &amp;apos;DSSellerOrderSectionHeaderView.m&amp;apos; line 680, column 9 with start address 0x000000010073faf8
Looking up address: 0x000000010073fb30 in .debug_frame... not found.&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;我们来从终端结果来分析出我们想要的结果:  &lt;br /&gt;这一行告诉我们崩溃的代码所在的文件的目录&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;Line table dir : &amp;apos;/Dandy/checkSvn/IOS/xmen/Xmen/Modules/StoreManage/SellerOrder/View&amp;apos;&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;这一行告诉我们崩溃代码所在的具体文件&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;AT_decl_file( &amp;quot;/Dandy/checkSvn/IOS/xmen/Xmen/Modules/StoreManage/SellerOrder/View/DSSellerOrderSectionHeaderView.m&amp;quot; )&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;这一行告诉我们崩溃代码是在哪一个方法里面&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;AT_name( &amp;quot;-[DSSellerOrderSectionHeaderView updateContentWithOrderData:isEdit:]&amp;quot; )&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;这一行告诉我们崩溃代码具体在哪一行了&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;Line table file: &amp;apos;DSSellerOrderSectionHeaderView.m&amp;apos; line 680, column 9 with start address 0x000000010073faf8&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;好的，现在我们分析到了崩溃代码在哪一行了，下面我们来找一找bug&lt;/p&gt;
 &lt;h2&gt;  &lt;strong&gt;查找bug&lt;/strong&gt;&lt;/h2&gt;
 &lt;p&gt;  我们的代码都应该有托管平台，每个版本上线都需要打一个  &lt;strong&gt;tag&lt;/strong&gt;，这是一个好习惯。下面我拉下我崩溃的对应版本的tag，找到崩溃代码那一行：&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" src="https://segmentfault.com/img/remote/1460000007876909" title=""&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;  结合崩溃日志中告诉我:崩溃的原因是value为nil,发现是因为receiverTelephone字段中有中文导致转url时为nil导致的，下面的解bug就看各自本领啦。&lt;/p&gt;
 &lt;h2&gt;  &lt;strong&gt;结语&lt;/strong&gt;&lt;/h2&gt;
 &lt;p&gt;  希望对您有帮助，谢谢支持~&lt;/p&gt;
&lt;div&gt; &lt;a href="https://itindex.net/"  title="IT 资讯"&gt;&lt;img src="https://itindex.net/images/iconWarning.gif" title="IT 资讯" border="0"/&gt; &lt;/a&gt;</description>
      <category>iphone ios xcode objective-c</category>
      <guid isPermaLink="true">https://itindex.net/detail/56404-ios-%E5%88%86%E6%9E%90-%E6%97%A5%E5%BF%97</guid>
      <pubDate>Wed, 21 Dec 2016 15:16:29 CST</pubDate>
    </item>
    <item>
      <title>提高iOS开发效率的方法和工具（升级篇）</title>
      <link>https://itindex.net/detail/54302-ios-%E5%BC%80%E5%8F%91-%E6%96%B9%E6%B3%95</link>
      <description>&lt;h2&gt;介绍&lt;/h2&gt;
 &lt;p&gt;这篇文章主要是介绍一下我在iOS开发中使用到的一些可以提升开发效率的方法和工具。&lt;/p&gt;
 &lt;h2&gt;IDE&lt;/h2&gt;
 &lt;p&gt;首先要说的肯定是IDE了，说到IDE，Xcode不能跑，当然你也可能同时在使用AppCode等其他的IDE，在这里我主要介绍Xcode中提升开发效率的方法。&lt;/p&gt;
 &lt;h4&gt;1.善用快捷键&lt;/h4&gt;
 &lt;p&gt;快捷键是开发中必不可少的，当你善于使用快捷键的时候，十指在键盘上飞舞，那画面太美，我不敢想象  &lt;img alt="" src="https://raw.githubusercontent.com/yyny1789/yyny1789.github.io/master/images/wulian.png" title="&amp;#25552;&amp;#39640;iOS&amp;#24320;&amp;#21457;&amp;#25928;&amp;#29575;&amp;#30340;&amp;#26041;&amp;#27861;&amp;#21644;&amp;#24037;&amp;#20855;&amp;#65288;&amp;#21319;&amp;#32423;&amp;#31687;&amp;#65289; - &amp;#31532;1&amp;#24352;  | IT&amp;#27743;&amp;#28246;" width="25"&gt;&lt;/img&gt;。&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;   &lt;a href="http://www.cocoachina.com/ios/20141224/10752.html"&gt;    &lt;strong&gt;     &lt;em&gt;常用快捷键操作&lt;/em&gt;&lt;/strong&gt;&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
 &lt;h4&gt;2.常用代码片段&lt;/h4&gt;
 &lt;p&gt;开发中有一些常用的代码，可以放到代码片段中，然后下次你就可以使用快捷方法来使用这些代码了，给大家看下我的Xcode中部分代码片段:&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" src="https://raw.githubusercontent.com/yyny1789/yyny1789.github.io/master/images/daimapianduan.png" title="&amp;#25552;&amp;#39640;iOS&amp;#24320;&amp;#21457;&amp;#25928;&amp;#29575;&amp;#30340;&amp;#26041;&amp;#27861;&amp;#21644;&amp;#24037;&amp;#20855;&amp;#65288;&amp;#21319;&amp;#32423;&amp;#31687;&amp;#65289; - &amp;#31532;2&amp;#24352;  | IT&amp;#27743;&amp;#28246;" width="320"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;   &lt;a href="http://www.2cto.com/kf/201409/336245.html"&gt;    &lt;strong&gt;     &lt;em&gt;偷懒小技巧&lt;/em&gt;&lt;/strong&gt;&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
 &lt;h4&gt;3.Xcode插件&lt;/h4&gt;
 &lt;p&gt;我想插件是Xcode必不可少的把&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;   &lt;a href="http://www.cocoachina.com/industry/20130918/7022.html"&gt;    &lt;strong&gt;     &lt;em&gt;那些不能错过的Xcode插件&lt;/em&gt;&lt;/strong&gt;&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;除此之外，我自己还经常用到的插件有：&lt;/p&gt;
 &lt;p&gt;1.  &lt;a href="https://github.com/markohlebar/Peckham"&gt;   &lt;strong&gt;    &lt;em&gt;快速Add #import&lt;/em&gt;&lt;/strong&gt;&lt;/a&gt;&lt;/p&gt;
 &lt;p&gt;2.  &lt;a href="https://github.com/trawor/XToDo"&gt;   &lt;strong&gt;    &lt;em&gt;查看项目的’TODO’,’FIXME’等&lt;/em&gt;&lt;/strong&gt;&lt;/a&gt;&lt;/p&gt;
 &lt;p&gt;在此强烈推荐给大家。&lt;/p&gt;
 &lt;p&gt;你可能想，如果没有我要用的插件怎么办？少年，这个时候就要自己动手丰衣足食了,我想你可以看看这个  &lt;a href="http://zixun.github.io/blog/2015/05/04/xcode6cha-jian-kai-fa-ru-men/"&gt;   &lt;strong&gt;    &lt;em&gt;Xcode6插件开发入门&lt;/em&gt;&lt;/strong&gt;&lt;/a&gt;。&lt;/p&gt;
 &lt;h4&gt;4.注释&lt;/h4&gt;
 &lt;p&gt;注释的作用就不多说了，而且现在公司都要求代码必须有注释。&lt;/p&gt;
 &lt;p&gt;之前一直在用 喵神  &lt;a href="http://weibo.com/onevcat?from=myfollow_all"&gt;   &lt;strong&gt;    &lt;em&gt;onevcat&lt;/em&gt;&lt;/strong&gt;&lt;/a&gt; 开源的   &lt;a href="https://github.com/onevcat/VVDocumenter-Xcode"&gt;   &lt;strong&gt;    &lt;em&gt;VVDocumenter-Xcode&lt;/em&gt;&lt;/strong&gt;&lt;/a&gt;.&lt;/p&gt;
 &lt;p&gt;但是后来觉得这种注释会有这样一个问题：一个注释多三行&lt;/p&gt;
 &lt;div&gt;
  &lt;pre&gt;   &lt;code&gt;/**
 *  顶部公告btn
 */
@property (nonatomic, strong) UIButton *topAnnouncementBtn;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
 &lt;p&gt;接口用这种方法会简单明了，但是属性的话，总感觉.h文件好多东西（其实没几个属性啊）&lt;/p&gt;
 &lt;p&gt;后来换成这样：&lt;/p&gt;
 &lt;div&gt;
  &lt;pre&gt;   &lt;code&gt;/**顶部公告btn */
@property (nonatomic, strong) UIButton *topAnnouncementBtn;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
 &lt;p&gt;还是多一行，再后来换成这样：&lt;/p&gt;
 &lt;div&gt;
  &lt;pre&gt;   &lt;code&gt;@property (nonatomic, strong) UIButton *topAnnouncementBtn; // 顶部公告btn&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
 &lt;p&gt;但是这种方式，在你使用这个属性的时候，是不会有注释提示的。没有就没有把，遇见不明大意的属性，到时候再跳到.h 文件 看一眼。(“呸，你怎么这么容易就妥协了！！！”，我当时应该在心里暗暗骂自己的）&lt;/p&gt;
 &lt;p&gt;之后某天在微博上看到    &lt;a href="http://weibo.com/JoanfenZhang"&gt;   &lt;strong&gt;    &lt;em&gt;芳仔小脚印&lt;/em&gt;&lt;/strong&gt;&lt;/a&gt; 的博客   &lt;a href="http://my.oschina.net/joanfen/blog/415058"&gt;   &lt;strong&gt;    &lt;em&gt;我是如何收拾代码的&lt;/em&gt;&lt;/strong&gt;&lt;/a&gt; 中介绍她是这样注释属性的：&lt;/p&gt;
 &lt;div&gt;
  &lt;pre&gt;   &lt;code&gt;UIButton *btnSend;/**&amp;lt; 发送按钮 */&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
 &lt;p&gt;  &lt;img alt="" src="https://raw.githubusercontent.com/yyny1789/yyny1789.github.io/master/images/zhushi2.png" title="&amp;#25552;&amp;#39640;iOS&amp;#24320;&amp;#21457;&amp;#25928;&amp;#29575;&amp;#30340;&amp;#26041;&amp;#27861;&amp;#21644;&amp;#24037;&amp;#20855;&amp;#65288;&amp;#21319;&amp;#32423;&amp;#31687;&amp;#65289; - &amp;#31532;3&amp;#24352;  | IT&amp;#27743;&amp;#28246;" width="400"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;试用了一下，很方便。之后一直用这种方法做属性注释，在这里分享给大家。&lt;/p&gt;
 &lt;p&gt;感谢   &lt;a href="http://weibo.com/JoanfenZhang"&gt;   &lt;strong&gt;    &lt;em&gt;芳仔小脚印&lt;/em&gt;&lt;/strong&gt;&lt;/a&gt; 的分享&lt;/p&gt;
 &lt;p&gt;后面网友   &lt;a href="http://www.cocoachina.com/bbs/u.php?uid=221894"&gt;   &lt;strong&gt;    &lt;em&gt;q582975598&lt;/em&gt;&lt;/strong&gt;&lt;/a&gt; 提醒&lt;/p&gt;
 &lt;div&gt;
  &lt;pre&gt;   &lt;code&gt;UIButton *btnSend;/**&amp;lt; 发送按钮 */&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
 &lt;p&gt;等于&lt;/p&gt;
 &lt;div&gt;
  &lt;pre&gt;   &lt;code&gt;UIButton *btnSend;///&amp;lt;发送按钮&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
 &lt;p&gt;使用了下，真的是这样哦&lt;/p&gt;
 &lt;h2&gt;网络数据相关&lt;/h2&gt;
 &lt;h4&gt;1.调试接口&lt;/h4&gt;
 &lt;p&gt;少年，你还在写方法调试接口吗？如果是，那你一定需要下面这2个了哈:&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" src="https://raw.githubusercontent.com/yyny1789/yyny1789.github.io/master/images/tiaoshijiekou2.png" title="&amp;#25552;&amp;#39640;iOS&amp;#24320;&amp;#21457;&amp;#25928;&amp;#29575;&amp;#30340;&amp;#26041;&amp;#27861;&amp;#21644;&amp;#24037;&amp;#20855;&amp;#65288;&amp;#21319;&amp;#32423;&amp;#31687;&amp;#65289; - &amp;#31532;4&amp;#24352;  | IT&amp;#27743;&amp;#28246;" width="320"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;a href="chrome-extension://aejoelaoggembcahagimdiliamlcdmfm/dhc.html"&gt;   &lt;strong&gt;    &lt;em&gt;DHC&lt;/em&gt;&lt;/strong&gt;&lt;/a&gt; 在线调试接口，支持HTTP和HTTPS呦。&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" src="https://raw.githubusercontent.com/yyny1789/yyny1789.github.io/master/images/dhc.png" title="&amp;#25552;&amp;#39640;iOS&amp;#24320;&amp;#21457;&amp;#25928;&amp;#29575;&amp;#30340;&amp;#26041;&amp;#27861;&amp;#21644;&amp;#24037;&amp;#20855;&amp;#65288;&amp;#21319;&amp;#32423;&amp;#31687;&amp;#65289; - &amp;#31532;5&amp;#24352;  | IT&amp;#27743;&amp;#28246;" width="600"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;a href="http://chromecj.com/web-development/2014-09/60.html"&gt;   &lt;strong&gt;    &lt;em&gt;Postman&lt;/em&gt;&lt;/strong&gt;&lt;/a&gt; 一款功能强大的网页调试与发送网页HTTP请求的Chrome插件。(感谢  &lt;a href="http://weibo.com/u/1438670852?from=feed&amp;loc=nickname"&gt;   &lt;strong&gt;    &lt;em&gt;叶孤城___&lt;/em&gt;&lt;/strong&gt;&lt;/a&gt;提醒)&lt;/p&gt;
 &lt;h4&gt;2.JSON数据编辑&lt;/h4&gt;
 &lt;p&gt;废话不多说，直接上图：&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" src="https://raw.githubusercontent.com/yyny1789/yyny1789.github.io/master/images/jsonedit.png" title="&amp;#25552;&amp;#39640;iOS&amp;#24320;&amp;#21457;&amp;#25928;&amp;#29575;&amp;#30340;&amp;#26041;&amp;#27861;&amp;#21644;&amp;#24037;&amp;#20855;&amp;#65288;&amp;#21319;&amp;#32423;&amp;#31687;&amp;#65289; - &amp;#31532;6&amp;#24352;  | IT&amp;#27743;&amp;#28246;" width="600"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;   &lt;a href="chrome-extension://lhkmoheomjbkfloacpgllgjcamhihfaj/index.html"&gt;    &lt;strong&gt;     &lt;em&gt;JSON Editor Online&lt;/em&gt;&lt;/strong&gt;&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;  &lt;img alt="" src="https://raw.githubusercontent.com/yyny1789/yyny1789.github.io/master/images/jsonedit2.png" title="&amp;#25552;&amp;#39640;iOS&amp;#24320;&amp;#21457;&amp;#25928;&amp;#29575;&amp;#30340;&amp;#26041;&amp;#27861;&amp;#21644;&amp;#24037;&amp;#20855;&amp;#65288;&amp;#21319;&amp;#32423;&amp;#31687;&amp;#65289; - &amp;#31532;7&amp;#24352;  | IT&amp;#27743;&amp;#28246;" width="600"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;   &lt;a href="http://www.runoob.com/tool/json/index.html"&gt;JSON格式化工具&lt;/a&gt; (感谢   &lt;a href="http://weibo.com/luohanchenyilong?from=feed&amp;loc=nickname"&gt;    &lt;strong&gt;     &lt;em&gt;iOS程序犭袁&lt;/em&gt;&lt;/strong&gt;&lt;/a&gt; 提供)&lt;/li&gt;
&lt;/ul&gt;
 &lt;h2&gt;UI相关&lt;/h2&gt;
 &lt;h4&gt;1.距离&lt;/h4&gt;
 &lt;p&gt;不行！说的是20px！差1px，2px，5px，10px，都不算20px！&lt;/p&gt;
 &lt;p&gt;遇到有像素眼的设计师，想哭的心情总是有。但是他们可能有时候会忘记标X、Y，或者就是宽高，  &lt;s&gt;下面是我司UI给的一张图&lt;/s&gt;, 用下图来举例不合适，但是大体意思是说可能会忘记标注&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" src="https://raw.githubusercontent.com/yyny1789/yyny1789.github.io/master/images/xiangqing.png" title="&amp;#25552;&amp;#39640;iOS&amp;#24320;&amp;#21457;&amp;#25928;&amp;#29575;&amp;#30340;&amp;#26041;&amp;#27861;&amp;#21644;&amp;#24037;&amp;#20855;&amp;#65288;&amp;#21319;&amp;#32423;&amp;#31687;&amp;#65289; - &amp;#31532;8&amp;#24352;  | IT&amp;#27743;&amp;#28246;" width="320"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;魂淡，说好的X，Y呢？&lt;/p&gt;
 &lt;p&gt;然后我最开始是这样做的&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" src="https://raw.githubusercontent.com/yyny1789/yyny1789.github.io/master/images/ui.png" title="&amp;#25552;&amp;#39640;iOS&amp;#24320;&amp;#21457;&amp;#25928;&amp;#29575;&amp;#30340;&amp;#26041;&amp;#27861;&amp;#21644;&amp;#24037;&amp;#20855;&amp;#65288;&amp;#21319;&amp;#32423;&amp;#31687;&amp;#65289; - &amp;#31532;9&amp;#24352;  | IT&amp;#27743;&amp;#28246;" width="600"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;可是总会有辣么一点误差，而且费眼。。。后来我偶然听一个产品朋友说他们在用  &lt;a href="http://www.getmarkman.com/"&gt;   &lt;strong&gt;    &lt;em&gt;马克鳗&lt;/em&gt;&lt;/strong&gt;&lt;/a&gt;标图，它有免费和收费2个版本，免费版本可以使用基本功能，感觉还不错。&lt;/p&gt;
 &lt;p&gt;今天喵神  &lt;a href="http://weibo.com/onevcat?from=myfollow_all"&gt;   &lt;strong&gt;    &lt;em&gt;onevcat&lt;/em&gt;&lt;/strong&gt;&lt;/a&gt;在微博发了一个测量的工具：  &lt;a href="http://www.ricciadams.com/projects/pixel-winch"&gt;   &lt;strong&gt;    &lt;em&gt;Pixel Winch&lt;/em&gt;&lt;/strong&gt;&lt;/a&gt; ,试了一下，比  &lt;strong&gt;   &lt;em&gt;马克鳗&lt;/em&gt;&lt;/strong&gt;好使。另外就是可以设置一下   &lt;strong&gt;   &lt;em&gt;Show screenshots&lt;/em&gt;&lt;/strong&gt; 的快捷键，那感觉飞起来一般&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" src="https://raw.githubusercontent.com/yyny1789/yyny1789.github.io/master/images/shezhikuaijiejian.jpg" title="&amp;#25552;&amp;#39640;iOS&amp;#24320;&amp;#21457;&amp;#25928;&amp;#29575;&amp;#30340;&amp;#26041;&amp;#27861;&amp;#21644;&amp;#24037;&amp;#20855;&amp;#65288;&amp;#21319;&amp;#32423;&amp;#31687;&amp;#65289; - &amp;#31532;10&amp;#24352;  | IT&amp;#27743;&amp;#28246;" width="300"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;h4&gt;2.图片压缩&lt;/h4&gt;
 &lt;p&gt;我们UI就不太注重图片的大小，尼玛，有一次给的图片有4M多，害我自己还得压缩一遍&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;   &lt;a href="https://tinypng.com/"&gt;    &lt;strong&gt;     &lt;em&gt;tinypng&lt;/em&gt;&lt;/strong&gt;&lt;/a&gt;，保质压缩，我感觉还不错，推荐给我们UI和后台，他们用过之后都说好&lt;/li&gt;
  &lt;li&gt;   &lt;a href="https://github.com/ylovern/GGTinypng"&gt;    &lt;strong&gt;     &lt;em&gt;tinypng批量压缩图片脚本&lt;/em&gt;&lt;/strong&gt;&lt;/a&gt; 配套使用更佳。(感谢   &lt;a href="http://weibo.com/u/2972590612"&gt;    &lt;strong&gt;     &lt;em&gt;newbee_nAn&lt;/em&gt;&lt;/strong&gt;&lt;/a&gt; 提供)&lt;/li&gt;
&lt;/ul&gt;
 &lt;h4&gt;3.AppIcon&lt;/h4&gt;
 &lt;p&gt;AppIcon只需要UI提供一张1024*1024的图就可以了，具体的icon可以用  &lt;a href="https://itunes.apple.com/tw/app/prepo/id476533227?mt=12"&gt;   &lt;strong&gt;    &lt;em&gt;Prepo&lt;/em&gt;&lt;/strong&gt;&lt;/a&gt;生成&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" src="https://raw.githubusercontent.com/yyny1789/yyny1789.github.io/master/images/Prepo.jpeg" title="&amp;#25552;&amp;#39640;iOS&amp;#24320;&amp;#21457;&amp;#25928;&amp;#29575;&amp;#30340;&amp;#26041;&amp;#27861;&amp;#21644;&amp;#24037;&amp;#20855;&amp;#65288;&amp;#21319;&amp;#32423;&amp;#31687;&amp;#65289; - &amp;#31532;11&amp;#24352;  | IT&amp;#27743;&amp;#28246;" width="600"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;h2&gt;两地办公&lt;/h2&gt;
 &lt;p&gt;假设这么一种情况：公司用的是SVN，公司一台公司电脑，家里一台自己电脑，有时候可能想回来后接着敲代码，怎么办？&lt;/p&gt;
 &lt;p&gt;再假设这么一种情况：公司用的是SVN，产品想实现一种效果，但是你又不确定能不能写出来，所以你可能会纠结要不要在公司项目上改动，怎么办？&lt;/p&gt;
 &lt;p&gt;如果有上述两种烦恼，那么Github 和 Bitbucket 是您的首选，具体选哪个，这里有一篇对比文章:  &lt;a href="http://www.oschina.net/translate/bitbucket-vs-github-its-more-than-just-features"&gt;   &lt;strong&gt;    &lt;em&gt;GitHub vs. Bitbucket 不只是功能不同&lt;/em&gt;&lt;/strong&gt;&lt;/a&gt;.&lt;/p&gt;
 &lt;h2&gt;Github&lt;/h2&gt;
 &lt;p&gt;Github上好的开源项目太多，一个一个的star，太慢了，怎么破？&lt;/p&gt;
 &lt;blockquote&gt;  &lt;p&gt;language:Objective-C stars:&amp;gt;900&lt;/p&gt;&lt;/blockquote&gt;
 &lt;p&gt;  &lt;img alt="" src="https://raw.githubusercontent.com/yyny1789/yyny1789.github.io/master/images/githubsearch.png" title="&amp;#25552;&amp;#39640;iOS&amp;#24320;&amp;#21457;&amp;#25928;&amp;#29575;&amp;#30340;&amp;#26041;&amp;#27861;&amp;#21644;&amp;#24037;&amp;#20855;&amp;#65288;&amp;#21319;&amp;#32423;&amp;#31687;&amp;#65289; - &amp;#31532;12&amp;#24352;  | IT&amp;#27743;&amp;#28246;" width="800"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;这个其实就是Github的  &lt;strong&gt;   &lt;em&gt;Advanced search&lt;/em&gt;&lt;/strong&gt;功能：&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" src="https://raw.githubusercontent.com/yyny1789/yyny1789.github.io/master/images/githubAdvancedSearch.png" title="&amp;#25552;&amp;#39640;iOS&amp;#24320;&amp;#21457;&amp;#25928;&amp;#29575;&amp;#30340;&amp;#26041;&amp;#27861;&amp;#21644;&amp;#24037;&amp;#20855;&amp;#65288;&amp;#21319;&amp;#32423;&amp;#31687;&amp;#65289; - &amp;#31532;13&amp;#24352;  | IT&amp;#27743;&amp;#28246;" width="300"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" src="https://raw.githubusercontent.com/yyny1789/yyny1789.github.io/master/images/githubAdvancedSearch2.png" title="&amp;#25552;&amp;#39640;iOS&amp;#24320;&amp;#21457;&amp;#25928;&amp;#29575;&amp;#30340;&amp;#26041;&amp;#27861;&amp;#21644;&amp;#24037;&amp;#20855;&amp;#65288;&amp;#21319;&amp;#32423;&amp;#31687;&amp;#65289; - &amp;#31532;14&amp;#24352;  | IT&amp;#27743;&amp;#28246;" width="800"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;小伙伴们切记啊，star后并不代表你就掌握了，只有真正深入了解后才是自己的。&lt;/p&gt;
 &lt;p&gt;另外  &lt;strong&gt;Github Advanced Search&lt;/strong&gt; 可以用来寻找小伙伴哦——   &lt;a href="http://wangchao.de/github-advanced-search%E7%8C%8E%E5%A4%B4%E5%A4%A7%E6%B3%95/"&gt;   &lt;strong&gt;    &lt;em&gt;Github Advanced Search猎头大法&lt;/em&gt;&lt;/strong&gt;&lt;/a&gt;.&lt;/p&gt;
 &lt;h2&gt;未完待续…&lt;/h2&gt;
 &lt;p&gt;来自  &lt;a href="http://yyny.me/ios/&amp;#25552;&amp;#39640;iOS&amp;#24320;&amp;#21457;&amp;#25928;&amp;#29575;&amp;#30340;&amp;#26041;&amp;#27861;&amp;#21644;&amp;#24037;&amp;#20855;/" target="_blank"&gt;yyny.me &lt;/a&gt;&lt;/p&gt;
&lt;div&gt; &lt;a href="https://itindex.net/"  title="IT 资讯"&gt;&lt;img src="https://itindex.net/images/iconWarning.gif" title="IT 资讯" border="0"/&gt; &lt;/a&gt;</description>
      <category>ios</category>
      <guid isPermaLink="true">https://itindex.net/detail/54302-ios-%E5%BC%80%E5%8F%91-%E6%96%B9%E6%B3%95</guid>
      <pubDate>Fri, 04 Sep 2015 19:44:13 CST</pubDate>
    </item>
    <item>
      <title>APP调用Custom URL Scheme</title>
      <link>https://itindex.net/detail/55223-app-custom-url</link>
      <description>&lt;p&gt;  &lt;strong&gt;标签：&lt;/strong&gt;    &lt;a href="http://blogread.cn/it/tags.php?tag=Scheme" target="_blank"&gt;Scheme&lt;/a&gt;    &lt;a href="http://blogread.cn/it/tags.php?tag=%E8%B7%B3%E8%BD%AC" target="_blank"&gt;跳转&lt;/a&gt;&lt;/p&gt; &lt;p&gt;Custom URL scheme 的好处就是，你可以在其它程序中通过这个url打开应用程序。如Ａ应用程序注册了一个url scheme:myApp,  那么就在mobile浏览器中就可以通过&amp;lt;href=’myApp://’&amp;gt;打开你的应用程序Ａ。&lt;/p&gt; &lt;h2&gt;Android&lt;/h2&gt; &lt;p&gt;首先在AndroidManifast.xml要被指定Scheme的Activity下设置如下参数&lt;/p&gt; &lt;pre&gt;&amp;lt;intent-filter&amp;gt;  
  &amp;lt;category android:name=&amp;quot;android.intent.category.DEFAULT&amp;quot;&amp;gt;&amp;lt;/category&amp;gt;  
  &amp;lt;action android:name=&amp;quot;android.intent.action.VIEW&amp;quot;&amp;gt;&amp;lt;/action&amp;gt;  
  &amp;lt;data android:scheme=&amp;quot;mgtv&amp;quot;&amp;gt;&amp;lt;/data&amp;gt;  
&amp;lt;/intent-filter&amp;gt;  

&lt;/pre&gt; &lt;p&gt;这样即指定了接收Uri的Scheme为 mgtv 且 Action为View的Intent。&lt;/p&gt; &lt;p&gt;利用如下Intent调用Activity&lt;/p&gt; &lt;pre&gt;startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(&amp;quot;mgtv://?action=play&amp;amp;data=12345&amp;quot;)));

&lt;/pre&gt; &lt;p&gt;或在浏览器中调用A链接打开&lt;/p&gt; &lt;pre&gt;&amp;lt;a href=&amp;quot;mgtv://?action=play&amp;amp;data=12345&amp;quot;&amp;gt;打开你的应用程序&amp;lt;/a&amp;gt;

&lt;/pre&gt; &lt;p&gt;在接收的Activity中使用如下代码获得数据&lt;/p&gt; &lt;pre&gt;//获得Scheme名称  
this.getIntent().getScheme();
//获得Uri全部路径
this.getIntent().getDataString();

&lt;/pre&gt; &lt;h2&gt;iOS&lt;/h2&gt; &lt;p&gt;  &lt;img border="0" height="334" hspace="0" src="http://www.linchangyu.com/images/xcodeurlscheme.jpg" vspace="0" width="486"&gt;&lt;/img&gt;&lt;/p&gt; &lt;ul&gt;  &lt;li&gt;   &lt;p&gt;如果没有URL types，随意点一个key行后面＋号，输入大写URL选择URL types&lt;/p&gt;&lt;/li&gt;  &lt;li&gt;   &lt;p&gt;右键URL types，选择add row&lt;/p&gt;&lt;/li&gt;  &lt;li&gt;   &lt;p&gt;item0下改成url identifier，后面的value值随意写，com.xx,xx&lt;/p&gt;&lt;/li&gt;  &lt;li&gt;   &lt;p&gt;加一行，选择url schemes，item后面值写成你需要的，譬如上面的todolist&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt; &lt;p&gt;在其他应用里就可以用以下语句启动你的app&lt;/p&gt; &lt;pre&gt;[[UIApplication sharedApplication] openURL:[NSURL URLWithString:@&amp;quot;todolist://&amp;quot;]];&lt;/pre&gt; &lt;p&gt;在自定义了 URL scheme 的应用中，app delegate 必须实现以下方法：&lt;/p&gt; &lt;pre&gt;- (BOOL)application:(UIApplication *)application openURL:(NSURL *)url
                                       sourceApplication:(NSString *)sourceApplication
                                              annotation:(id)annotation&lt;/pre&gt; &lt;p&gt;例如，假设我们使用以下的 URL scheme，我们可以像这样创建一个 URL：&lt;/p&gt; &lt;pre&gt;NSString *customURL = @&amp;quot;mgtv://?action=play&amp;amp;data=12345&amp;quot;;&lt;/pre&gt; &lt;p&gt;在 web 开发中，字符串 ?action=play&amp;amp;data=12345 被称作查询询串(query string)。&lt;/p&gt; &lt;p&gt;在被调用(设置了自定义 URL)的应用的 app delegate 中，获取参数的代码如下:&lt;/p&gt; &lt;pre&gt;- (BOOL)application:(UIApplication *)application openURL:(NSURL *)url
                                       sourceApplication:(NSString *)sourceApplication
                                              annotation:(id)annotation
{
  NSLog(@&amp;quot;Calling Application Bundle ID: %@&amp;quot;, sourceApplication);
  NSLog(@&amp;quot;URL scheme:%@&amp;quot;, [url scheme]);
  NSLog(@&amp;quot;URL query: %@&amp;quot;, [url query]);

  return YES;
}&lt;/pre&gt; &lt;p&gt;以上代码在应用被调用时的输出为：&lt;/p&gt; &lt;pre&gt;Calling Application Bundle ID: com.hunantv.app
URL scheme:mgtv
URL query: action=play&amp;amp;data=12345&lt;/pre&gt; &lt;h3&gt;额外功能&lt;/h3&gt; &lt;p&gt;果处理成功的Scheme如包含了TestBAPP://callsuccess，那么说明你调用其他的APP成功了。如果不是，那么说明是别的APP如TestAAPP调用了你的APP，此时在你的APPDelegate里面添加如下函数以及实现处理，这里是直接返回告诉TestAAPP调用成功的标识TestAAPP://callsuccess：&lt;/p&gt; &lt;pre&gt;- (BOOL)application:(UIApplication *)application handleOpenURL:(NSURL *)url  
{  
    // Do something with the url here  
    if (!url)  
    {  
        return NO;  
    }  
    NSString *handleUrl = [url absoluteString];  
    if ([handleUrl isEqualToString:@&amp;quot;TestBApp://callsuccess&amp;quot;]) {  
        return YES;  
    }else{  
        NSString *urlstr = @&amp;quot;TestAAPP:/com.baidu.sidepath.TestA&amp;amp;_callback=TestAApp://callsuccess&amp;quot;;  
        NSURL *handlbackeUrl = [NSURL URLWithString:urlstr];  
        [[UIApplication sharedApplication] openURL:handlbackeUrl];  

    }
}  &lt;/pre&gt; &lt;p&gt;如果你不想直接返回callback，而是想启动一个页面那么，此时要考虑你的应用是否已经启动，可以如下判断使用：&lt;/p&gt; &lt;pre&gt;- (BOOL)application:(UIApplication *)application openURL:(NSURL *)url  
  sourceApplication:(NSString *)sourceApplication annotation:(id)annotation{  
    NSString *handleUrl = [url absoluteString];  
    if ([handleUrl isEqualToString:@&amp;quot;TestBApp://callsuccess&amp;quot;]) {  
        return YES;  
    }else{  
        UINavigationController *vc = (UINavigationController *)_window.rootViewController;  
        if (vc == nil) {  
            PathViewController *controller = [[PathViewController alloc] initWithNibName:@&amp;quot;PathViewController&amp;quot; bundle:nil];  

            self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];  
            self.mUINavigationController = [[UINavigationController alloc] init];  


            [self.mUINavigationController pushViewController:controller animated:YES];  
            [self.window addSubview:self.mUINavigationController.view];  


            // Override point for customization after application launch.  
            self.window.backgroundColor = [UIColor whiteColor];  
            [self.window makeKeyAndVisible];  
        }  
        return YES;  
    }
  }&lt;/pre&gt; &lt;p&gt;也就是把appdelegate里面的didFinishLaunchingWithOptions初始化app的代码拷贝进去。此时会启动PathViewController这个页面。然后在这个页面里面可以添加一个返回按钮来返回到调用APP。&lt;/p&gt; &lt;p&gt;再次 在TestAAPp里面使用URl Scheme调起你的APP&lt;/p&gt; &lt;pre&gt;- (void)buttonPressed:(UIButton *)button
{
  NSString *customURL = @&amp;quot;mgtv://play/12345&amp;quot;;

  if ([[UIApplication sharedApplication]
    canOpenURL:[NSURL URLWithString:customURL]])
  {
    [[UIApplication sharedApplication] openURL:[NSURL URLWithString:customURL]];
  }
  else
  {
    UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@&amp;quot;URL error&amp;quot;
                          message:[NSString stringWithFormat:
                            @&amp;quot;No custom URL defined for %@&amp;quot;, customURL]
                          delegate:self cancelButtonTitle:@&amp;quot;Ok&amp;quot;
                          otherButtonTitles:nil];
    [alert show];
  }
}&lt;/pre&gt; &lt;p&gt;查看更多苹果官方资料：&lt;/p&gt; &lt;p&gt;  &lt;a href="https://developer.apple.com/library/ios/documentation/UIKit/Reference/UIApplicationDelegate_Protocol/Reference/Reference.html"&gt;https://developer.apple.com/library/ios/documentation/UIKit/Reference/UIApplicationDelegate_Protocol/Reference/Reference.html&lt;/a&gt;&lt;/p&gt; &lt;h2&gt;其他&lt;/h2&gt; &lt;p&gt;iOS与Android在这儿有点小区别，在iOS中如果系统注册了url scheme且安装了那个应用程序，通过上面那种网页的方式就可以启动应用程序，如果没有注册或没有安装那个应用程序，就没有任何效果(你注册的url scheme不能是http://xxx 这样的)。&lt;/p&gt; &lt;p&gt;在Android系统中注册了url scheme且安装了那个应用程序，通过上面那种网页的方式就可以启动应用程序，如果没有注册或没有安装那个应用程序，就没有任何效果；如果注册了是http://xxx这样的，就会弹了一个对话框让你选，是打开网页还是程序。&lt;/p&gt; &lt;p&gt;iOS中不能注册http://xxx这样的url scheme,而Android是可以的。&lt;/p&gt;
				 &lt;p&gt;  &lt;strong&gt;您可能还对下面的文章感兴趣：&lt;/strong&gt;&lt;/p&gt;
				 &lt;p&gt;  &lt;ol&gt;   &lt;li&gt;    &lt;a href="http://blogread.cn/it/article.php?id=7751" target="_blank"&gt;安卓第三方应用调起常见问题&lt;/a&gt; [2016-02-16 20:53:22]&lt;/li&gt;   &lt;li&gt;    &lt;a href="http://blogread.cn/it/article.php?id=7741" target="_blank"&gt;苹果iOS系统下检查第三方APP是否安装及跳转启动&lt;/a&gt; [2016-02-16 20:32:38]&lt;/li&gt;   &lt;li&gt;    &lt;a href="http://blogread.cn/it/article.php?id=790" target="_blank"&gt;.htaccess的301跳转&lt;/a&gt; [2009-12-09 16:45:58]&lt;/li&gt;&lt;/ol&gt;&lt;/p&gt; &lt;img alt="" height="1" src="http://feeds.feedburner.com/~r/blogreadIT/~4/nPLVO7xdL68" width="1"&gt;&lt;/img&gt;&lt;div&gt; &lt;a href="https://itindex.net/"  title="IT 资讯"&gt;&lt;img src="https://itindex.net/images/iconWarning.gif" title="IT 资讯" border="0"/&gt; &lt;/a&gt;</description>
      <category>iOS开发</category>
      <guid isPermaLink="true">https://itindex.net/detail/55223-app-custom-url</guid>
      <pubDate>Fri, 19 Feb 2016 07:45:10 CST</pubDate>
    </item>
    <item>
      <title>苹果iOS系统下的推送机制及实现</title>
      <link>https://itindex.net/detail/55201-%E8%8B%B9%E6%9E%9C-ios-%E7%B3%BB%E7%BB%9F</link>
      <description>&lt;p&gt;  &lt;strong&gt;标签：&lt;/strong&gt;    &lt;a href="http://blogread.cn/it/tags.php?tag=push" target="_blank"&gt;push&lt;/a&gt;    &lt;a href="http://blogread.cn/it/tags.php?tag=%E6%8E%A8%E9%80%81" target="_blank"&gt;推送&lt;/a&gt;&lt;/p&gt; &lt;div&gt;  &lt;p&gt;   &lt;em&gt;本文译自    &lt;a href="http://www.raywenderlich.com/"&gt;http://www.raywenderlich.com&lt;/a&gt;。原文由iOS教程团队 Matthijs Hollemans 撰写，经原网站管理员授权本博翻译。&lt;/em&gt;&lt;/p&gt;  &lt;p&gt;在iOS系统，考虑到手机电池电量，应用不允许在后台进行过多的操作，当用户未开启应用时，要怎么样才能通知用户呢？&lt;/p&gt;  &lt;p&gt;好比用户收到一个新的微博、喜欢的球队取得一场胜利或者是晚餐准备好了，如果应用都不在运行当中，当然也就无法去获得这些事件。&lt;/p&gt;  &lt;p&gt;幸运的是苹果提供一个解决方案，通过你自己的服务组件来替代应用持续地检查事件或在后台进行类似的操作，当事件实时发生时，服务组件能发送通知给应用，通过下列三种方式提醒用户：&lt;/p&gt;  &lt;p&gt;显示简短的文本作息，播放提示音，在应用图标上显示数字提示（就是红色背景的那个）&lt;/p&gt;  &lt;p&gt;你可以把这三种方式随便地组合使用，比如播放声音并显示一个提示数字而已。&lt;/p&gt;  &lt;p&gt;在本教程中，你可以用APNS（Apple Push Notification Services）来开发一个简单的应用。&lt;/p&gt;  &lt;p&gt;我们先来学习一下在应用开发中如何设置接收推送通知，如何接收一条测试通知。&lt;/p&gt;  &lt;p&gt;本教程针对的是有一些经验的iOS开发者，初学者请在先选择一些初级教程：&lt;/p&gt;  &lt;p&gt;目录：   &lt;a href="http://www.raywenderlich.com/tutorials"&gt;raywenderlich.com/tutorials&lt;/a&gt;&lt;/p&gt;  &lt;p&gt;特别是这两篇：&lt;/p&gt;  &lt;ul&gt;   &lt;li&gt;    &lt;p&gt;     &lt;a href="http://www.raywenderlich.com/2941/how-to-write-a-simple-phpmysql-web-service-for-an-ios-app"&gt;How To Write A Simple PHP/MySQL Web Service for an iOS App&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;   &lt;li&gt;    &lt;p&gt;     &lt;a href="http://www.raywenderlich.com/2965/how-to-write-an-ios-app-that-uses-a-web-service"&gt;How To Write an iOS App That Uses A Web Service&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;  &lt;p&gt;准备好了吗，让我们开始吧：&lt;/p&gt;  &lt;h2&gt;基本框架&lt;/h2&gt;  &lt;p&gt;要让苹果推送通知服务正常工作，涉及很多方面，下图是一个基本框架：&lt;/p&gt;  &lt;p&gt;   &lt;img alt="Apple Push Notification Services (APNS) Overview" height="500" src="http://www.linchangyu.com/images/2014/Push-Overview-467x500.jpg" title="Apple Push Notification Services (APNS) Overview" width="467"&gt;&lt;/img&gt;&lt;/p&gt;  &lt;ol&gt;   &lt;li&gt;    &lt;p&gt;应用启用推送通知功能，需要用户确认；&lt;/p&gt;&lt;/li&gt;   &lt;li&gt;    &lt;p&gt;应用收到设备识别ID（device token），相当于接收推送通知的地址；&lt;/p&gt;&lt;/li&gt;   &lt;li&gt;    &lt;p&gt;应用将设备识别ID发送到你开发的服务器；&lt;/p&gt;&lt;/li&gt;   &lt;li&gt;    &lt;p&gt;当有推送通知的需要时，你就可以通过你开发的服务组件发送信息到苹果的服务器上；&lt;/p&gt;&lt;/li&gt;   &lt;li&gt;    &lt;p&gt;苹果推送通知服务将信息推送到用户的设备上。&lt;/p&gt;&lt;/li&gt;&lt;/ol&gt;  &lt;p&gt;用户设备接收到推送信息时，显示提示信息或播放提示音或更新主屏图标的提示数字，用户可以从提示信息窗口打开应用程序，应用可以根据提示信息的内容作进一步的处理。&lt;/p&gt;  &lt;p&gt;iOS4支持本地通知和后台多任务，是否我们就不需要推送通知了呢？&lt;/p&gt;  &lt;p&gt;答案是否定的，本地通知仅限于周期性定时事件处理，后台多任务也仅限于一些必须保持运行的应用，比如IP语音、后台音乐播放、导航等，如果你需要在你的应用关闭时提醒你的用户，你就必须使用推送通知服务 接下来，我们来探讨一些实现苹果推送通知服务的技术细节，内容比较多，泡好一杯咖啡，安静认真地阅读本教程吧。&lt;/p&gt;  &lt;h2&gt;苹果推送通知服务的目的&lt;/h2&gt;  &lt;p&gt;在你的应用中增加苹果推送通知服务有以下几项准备工作：&lt;/p&gt;  &lt;p&gt;一台iPhone或iPad，苹果推送通知服务不能在模拟器上工作，你必须在真机上测试；&lt;/p&gt;  &lt;p&gt;你得有付费开通的iOS开发者会员资格，你必须在苹果开发者门户（iOS Provisioning Portal）新增一个新的应用ID、对应的配置文件（provisioning profile）、专属于你的SLL认证证书；在整个过程中，你将创建自己的配置文件和证书，获取认证证书是很慎重的过程，必须按照规定执行，后文中有详细的操作步骤；&lt;/p&gt;  &lt;p&gt;一台联入互联网的服务器，苹果推送通知服务是在互联网上工作，开发时你可以在你的工作站上测试，但是实际使用时，你至少需要一台虚拟个人服务器，但是要保证可以安装证书，并开放相应的端口与苹果的服务器建立安全套接字（TLS）网络连接，一般IDC虚拟空间提供商不会提供此类额外服务，请先与你的供应商确认这些细节； 当然，还有专门提供苹果推送通知服务的网络服务商，你可以自行谷歌之，本文不讨论。&lt;/p&gt;  &lt;h2&gt;推送通知格式&lt;/h2&gt;  &lt;p&gt;你的服务器组件将根据事件或需要创建推送通知，你得先了解推送通知的具体格式；&lt;/p&gt;  &lt;p&gt;一个推送通知包括设备识别ID，通知主体和一些标识字节，通知主体是我们要发送的内容。&lt;/p&gt;  &lt;p&gt;首先我们得按JSON格式组织好通知主体，下面是一个最简单的示例：&lt;/p&gt;  &lt;pre&gt;   &lt;code&gt;{  &amp;quot;aps&amp;quot;:  {    &amp;quot;alert&amp;quot;:&amp;quot;测试信息&amp;quot;,    &amp;quot;sound&amp;quot;:&amp;quot;default&amp;quot;  }}&lt;/code&gt;&lt;/pre&gt;  &lt;p&gt;用大括号｛｝将键值对（字典对象）封装起来，有点像NSDictionary。&lt;/p&gt;  &lt;p&gt;通知主体至少得包括一个项目：”aps”，这个项的内容还是一个字典对象，在上面这个示例中，”aps”包括两个字段：alert和sound，让设备收到这个推送通知时，设备会弹出一个提示窗口，内容是测试信息，同时播放标准的提示声音。&lt;/p&gt;  &lt;p&gt;在aps这个字段下我们还可以自定义一些内容：&lt;/p&gt;  &lt;pre&gt;   &lt;code&gt;{  &amp;quot;aps&amp;quot;:  {    &amp;quot;alert&amp;quot;:    {      &amp;quot;action-loc-key&amp;quot;:&amp;quot;Open&amp;quot;,      &amp;quot;body&amp;quot;:&amp;quot;Hello, world!&amp;quot;      },    &amp;quot;badge&amp;quot;:2  }}&lt;/code&gt;&lt;/pre&gt;  &lt;p&gt;在这个示例中，字段”alert”也变成一个字典对象，字段”action-loc-key”重新定义弹出提示窗口中确认按钮上的文字，”badge”字段是需要在主屏图标上显示的提示数字，这个示例没有播放声音。&lt;/p&gt;  &lt;p&gt;还有很多通知主体内容的设置方式，你可以改变播放的声音，可以根据本地语言化的调置提供本地化的提示文字，甚至于加上你自定义的字段内容。更多资料请移至官方   &lt;a href="http://developer.apple.com/library/ios/#documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/Introduction/Introduction.html"&gt;《本地和推送通知开发指引（英）》&lt;/a&gt;。&lt;/p&gt;  &lt;p&gt;出于效率的考虑，推送通知的字节长度不能超过256个字节，类似于短信或推特（微博），所以在组织JSON通知主体内容时，一般我们都不保留换行和空格：&lt;/p&gt;  &lt;pre&gt;   &lt;code&gt;{  &amp;quot;aps&amp;quot;:  {    &amp;quot;alert&amp;quot;:&amp;quot;Hello, world!&amp;quot;,    &amp;quot;sound&amp;quot;:&amp;quot;default&amp;quot;  }}&lt;/code&gt;&lt;/pre&gt;  &lt;p&gt;这样已经很清楚了，不是吗，超过256个字节的推送通知，苹果的服务器可是会自动过滤掉的。&lt;/p&gt;  &lt;h2&gt;正式开始&lt;/h2&gt;  &lt;p&gt;   &lt;img alt="Push Notifications Are Unreliable!" height="187" src="http://www.linchangyu.com/images/2014/PushNotifWhy-250x187.jpg" title="Push Notifications Are Unreliable!" width="250"&gt;&lt;/img&gt;&lt;/p&gt;  &lt;p&gt;推送通知不保证发送接收的可靠性！！？？&lt;/p&gt;  &lt;p&gt;是的，就算APNS接收发送的请求，推送通知的接收也是没有保证的。&lt;/p&gt;  &lt;p&gt;认真考虑你的应用是否适用推送通知，现在没有办法确认推送通知发送的状态和接收与否，发送时间也无法得到保证，可能几秒也可以半小时。&lt;/p&gt;  &lt;p&gt;而且，如果用户设备通过受限的局域网在线或处于关机状态，也是收不到推送通知的。&lt;/p&gt;  &lt;p&gt;APNS会尝试在设备重新上线时发送最后一条推送通知，但是这种尝试不会持续太长时间，之后推送通知就永远失效了。&lt;/p&gt;  &lt;p&gt;   &lt;img alt="After looking at the APNS Server Bill" height="197" src="http://www.linchangyu.com/images/2014/RageFace-250x197.jpg" title="After looking at the APNS Server Bill" width="250"&gt;&lt;/img&gt;&lt;/p&gt;  &lt;p&gt;别指望在APNS里查找历史记录 发送推送通知的成本可能比你估计的要高！增加这个功能很简单，但是要维护一个比较大的用户群或一些特殊应用场合时，你可能需要承担较高的成本。&lt;/p&gt;  &lt;p&gt;只要监控你网站上的更新，并发送通知到你的用户是比较简单的，但是如果你提供的功能包括自定义监控其他网站的更新时，你的服务器要能够运行得了可能远超过你估计的监控任务。&lt;/p&gt;  &lt;p&gt;所以你还必须从维护成本上考虑你的应用是否需要这功能。&lt;/p&gt;  &lt;p&gt;好了，理论性的东西就这么多了，现在是时候来实践一下了。在编写代码之前，你还需要在苹果的开发者门户网站上处理一些烦人的步骤。&lt;/p&gt;  &lt;h2&gt;配置文件和证书&lt;/h2&gt;  &lt;p&gt;我勒个去！&lt;/p&gt;  &lt;p&gt;   &lt;img alt="APNS needs a certificate!" height="87" src="http://www.linchangyu.com/images/2014/Screen-shot-2011-05-09-at-5.11.18-PM-250x87.jpg" title="APNS needs a certificate!" width="250"&gt;&lt;/img&gt;&lt;/p&gt;  &lt;p&gt;APNS需要认证证书！&lt;/p&gt;  &lt;p&gt;要在你的应用中启用推送功能，一个对应的配置文件是必不可少的，你需要用它来跟APNS确认APP，不是吗？&lt;/p&gt;  &lt;p&gt;配置文件和证书只能通过对应有效的苹果开发者计划成员来取得，这样才能保证只有你的服务组件才能发推送通知到你的应用。&lt;/p&gt;  &lt;p&gt;如果你有过开发经验，你知道应用配置文件分为开发和发布两种类型，推送配置文件也有两种：&lt;/p&gt;  &lt;ul&gt;   &lt;li&gt;    &lt;p&gt;开发（Development）.对应着你开发测试时用的应用配置文件&lt;/p&gt;&lt;/li&gt;   &lt;li&gt;    &lt;p&gt;发布（Production）.对应着你正式发布时用的应用配置文件&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;  &lt;p&gt;在本教程中，我们只要关注开发配置文件即可。&lt;/p&gt;  &lt;h2&gt;准备证书申请文件（Certificate Signing Request）&lt;/h2&gt;  &lt;p&gt;还记得初次通过苹果开发者门户获取开发证书的过程吗？下面的过程你应该会觉得比较熟悉。但是还是要认真地浏览其中的细节，大多数故障问题都跟证书有关。&lt;/p&gt;  &lt;p&gt;数字证书基于公私钥加密方式，这里我们并不需要了解加密的过程，你只要了解数字证书总是和个人私钥文件合并使用就行了。证书是公钥的部分，用于建立基于SSL的加密网络连接，并不需要高强度的保密，而私钥不同，这是”私的”，你需要好好地保存。&lt;/p&gt;  &lt;p&gt;要申请数字认证，你需要先准备一个证书申请文件（CSR），准备好后会在”钥匙串”程序（MAC OS）中生成一个新的私钥项目，把证书申请文件发给证书发放方（这里是苹果开发者门户），你将可以获取一个对应的SSL证书。&lt;/p&gt;  &lt;p&gt;在你的MAC电脑上的【应用程序/实用工具】下打开”钥匙串访问”程序（Applications/Utilities/Keychain Access），在【Keychain Access/Certificate Assistant】菜单中选择【Request a Certificate from a Certificate Authority…】&lt;/p&gt;  &lt;p&gt;   &lt;img alt="Requesting a certificate with Keychain Access" height="200" src="http://www.linchangyu.com/images/2014/Keychain-Access-1-Request-Certificate-500x200.jpg" title="Requesting a certificate with Keychain Access" width="500"&gt;&lt;/img&gt;&lt;/p&gt;  &lt;p&gt;如果你没有找到这个菜单项或是提示”Request a Certificate from a Certificate Authority with key”，你还需要额外在开发者门户里下载并安装   &lt;a href="https://developer.apple.com/certificationauthority/AppleWWDRCA.cer"&gt;WWDR Intermediate Certificate&lt;/a&gt;。&lt;/p&gt;  &lt;p&gt;确认没有选中窗口列表中的任何私钥，你应该可以看到以下窗口：&lt;/p&gt;  &lt;p&gt;   &lt;img alt="Generating a certificate sign request with Keychain Access" height="370" src="http://www.linchangyu.com/images/2014/Keychain-Access-2-Generate-CSR-500x370.jpg" title="Generating a certificate sign request with Keychain Access" width="500"&gt;&lt;/img&gt;&lt;/p&gt;  &lt;p&gt;输入你的邮件地址，虽然有人建议说最好是跟开发资格的用户一致，但是看起来不一样也没有关系。&lt;/p&gt;  &lt;p&gt;输入PushChat（示例应用名称）在common name 项，实际上随便你填什么，只要你到时能够通过命名找到你的私钥就行。&lt;/p&gt;  &lt;p&gt;选择【保存在本地磁盘（Saved to disk）】，将CSR保存为”PushChat.certSigningRequest”。&lt;/p&gt;  &lt;p&gt;现在你在”钥匙串访问”程序中Keys分类下应该可以找到一个新的私钥项目，右击并选择Export。&lt;/p&gt;  &lt;p&gt;   &lt;img alt="Exporting your private key with keychain access" height="279" src="http://www.linchangyu.com/images/2014/Keychain-Access-3-Export-Private-Key-500x279.jpg" title="Exporting your private key with keychain access" width="500"&gt;&lt;/img&gt;&lt;/p&gt;  &lt;p&gt;将私钥保存为文件，保存时会提示你输入口令，命名为”PushChatKey.p12”。&lt;/p&gt;  &lt;p&gt;为了方便在本教程里引用，我使用”pushchat” 作为口令，实际应用时应该使用高强度的密码口令。可不能忘记口令，否则后面你可能无法访问私钥文件。&lt;/p&gt;  &lt;h2&gt;准备应用ID（App ID）和SSL证书&lt;/h2&gt;  &lt;p&gt;登入   &lt;a href="https://developer.apple.com/ios/manage/overview/index.action"&gt;苹果开发者门户&lt;/a&gt;。&lt;/p&gt;  &lt;p&gt;首先，我们先填写一个新的应用ID，每个推送通知服务都对应着唯一的应用，在这里，你不能使用通配符。&lt;/p&gt;  &lt;p&gt;在左项菜单中选择APP IDs，点击按钮【New App ID】。&lt;/p&gt;  &lt;p&gt;   &lt;img alt="Creating a new App ID" height="389" src="http://www.linchangyu.com/images/2014/Provisioning-1-New-AppID-500x389.jpg" title="Creating a new App ID" width="500"&gt;&lt;/img&gt;&lt;/p&gt;  &lt;p&gt;填写如下：&lt;/p&gt;  &lt;ul&gt;   &lt;li&gt;    &lt;p&gt;Description: PushChat&lt;/p&gt;&lt;/li&gt;   &lt;li&gt;    &lt;p&gt;Bundle Seed ID: Generate New (this is the default option)&lt;/p&gt;&lt;/li&gt;   &lt;li&gt;    &lt;p&gt;Bundle Identifier: com.hollance.PushChat&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;  &lt;p&gt;这里你得填上自己的标识：com.yoursite.PushChat，因为你在XCODE得用同样的标识开发应用。&lt;/p&gt;  &lt;p&gt;稍等一会，你就可以生成对应这个APP的SSL证书，你的服务组件通过这个证书也只能发送通知到这个APP。&lt;/p&gt;  &lt;p&gt;填写完毕，你应该可以看到下列信息：&lt;/p&gt;  &lt;p&gt;   &lt;img alt="List of App IDs in the iOS Provisioning Portal" height="50" src="http://www.linchangyu.com/images/2014/Provisioning-2-List-of-AppIDs-500x50.jpg" title="List of App IDs in the iOS Provisioning Portal" width="500"&gt;&lt;/img&gt;&lt;/p&gt;  &lt;p&gt;在【Apple Push Notification service】列，有两行有橙色小圆点起始的信息：【Configurable for Development】 和【Configurable for Production】。 这就意味着这个应用ID已经准备好了，接下来设置相关的选项。点击【Configure】链接。&lt;/p&gt;  &lt;p&gt;   &lt;img alt="Configuring your App ID in the iOS Provisioning Portal" height="350" src="http://www.linchangyu.com/images/2014/Provisioning-3-Configure-AppID-500x350.jpg" title="Configuring your App ID in the iOS Provisioning Portal" width="500"&gt;&lt;/img&gt;&lt;/p&gt;  &lt;p&gt;点选【Enable for Apple Push Notification service】框，点击对应【Development Push SSL Certificate】的设置按钮 【Configure】，弹出 苹果推送服务SSL证书助理（Apple Push Notification service SSL Certificate Assistant）窗口 :&lt;/p&gt;  &lt;p&gt;   &lt;img alt="Uploading your CSR with the SSL Assistant" height="453" src="http://www.linchangyu.com/images/2014/SSL-Assistant-1-Upload-CSR-500x453.jpg" title="Uploading your CSR with the SSL Assistant" width="500"&gt;&lt;/img&gt;&lt;/p&gt;  &lt;p&gt;首先提示你准备好证书签名申请文件，我们上面已经准备好了，点击继续，在下一步中进行上传CSR文件的操作，选中之前生成的CSR文件然后点击生成（Generate）。&lt;/p&gt;  &lt;p&gt;   &lt;img alt="Generating a Certificate with the SSL Assistant" height="456" src="http://www.linchangyu.com/images/2014/SSL-Assistant-2-Generating-Certificate-500x456.jpg" title="Generating a Certificate with the SSL Assistant" width="500"&gt;&lt;/img&gt;&lt;/p&gt;  &lt;p&gt;生成证书需要几秒钟，接着点继续按钮（Continue）。&lt;/p&gt;  &lt;p&gt;   &lt;img alt="Downloading a certificate with the SSL assistant" height="455" src="http://www.linchangyu.com/images/2014/SSL-Assistant-3-Download-Certificate-500x455.jpg" title="Downloading a certificate with the SSL assistant" width="500"&gt;&lt;/img&gt;&lt;/p&gt;  &lt;p&gt;下载生成的证书并保存为”aps_developer_identity.cer”。点击完成按钮（Done）关闭助理窗口返回APP ID界面。&lt;/p&gt;  &lt;p&gt;   &lt;img alt="Screenshot after the SSL Assistant is Complete" height="111" src="http://www.linchangyu.com/images/2014/Provisioning-4-After-SSL-Assistant-500x111.jpg" title="Screenshot after the SSL Assistant is Complete" width="500"&gt;&lt;/img&gt;&lt;/p&gt;  &lt;p&gt;你可以看到，我们已经激活开发用的证书认证，如果需要你可以在这里重新下载证书，开发证书的有效期为3个月。&lt;/p&gt;  &lt;p&gt;等你要正式发布你的应用的时候，你必须在开发认证那一项目下把这个过程重新来一遍。&lt;/p&gt;  &lt;p&gt;备注：开发认证的证书有效期为一年，你必须在到期前重新生成一次来保证你的系统正常运行。&lt;/p&gt;  &lt;p&gt;在本例中你不需要安装这个证书，如果你双击文件进行了安装，你可以在钥匙串访问程序中查找到，这个证书已经和私钥绑定在一起了。&lt;/p&gt;  &lt;h2&gt;制作PEM文件&lt;/h2&gt;  &lt;p&gt;现在我们有三个文件：&lt;/p&gt;  &lt;p&gt;*认证签名申请文件（CSR） *私钥文件（PushChatKey.p12） *SSL证书文件（aps_developer_identity.cer）&lt;/p&gt;  &lt;p&gt;妥善地保存好这三个文件，特别是CSR文件，在你的证书失效之后，你可能会再次用到它来申请证书。原来的私钥还可以用，只有SSL证书文件是新的。&lt;/p&gt;  &lt;p&gt;我们需要转换证书和私钥文件为一种常用格式，示例中我们使用PHP开发服务组件，我们需要把证书和私钥文件合并为PEM格式文件。&lt;/p&gt;  &lt;p&gt;我们不用关心是哪一种具体的PEM编码格式（实际上我也不是很清楚），关键是PHP可以用来与服务器建立有效的网络连接，其他的编程语言可能会用到其它格式的文件。&lt;/p&gt;  &lt;p&gt;在这里，我们用MAC电脑的命令行工具OpenSSL来操作，打开一个”终端”（Terminal）程序：&lt;/p&gt;  &lt;p&gt;通过cd命令转到存放三个文件的文件夹，我这里的操作是：&lt;/p&gt;  &lt;pre&gt;   &lt;code&gt;$ cd /Users/matthijs/Desktop&lt;/code&gt;&lt;/pre&gt;  &lt;p&gt;转换证书 .cer 文件到 .pem 文件格式：&lt;/p&gt;  &lt;pre&gt;   &lt;code&gt;$ openssl x509 -in aps_developer_identity.cer -inform der -outPushChatCert.pem&lt;/code&gt;&lt;/pre&gt;  &lt;p&gt;转换私钥 .p12 文件 到 .pem 文件格式:&lt;/p&gt;  &lt;pre&gt;   &lt;code&gt;$ openssl pkcs12 -nocerts -outPushChatKey.pem -inPushChatKey.p12EnterImportPassword:MAC verified OKEnter PEM pass phrase:Verifying-Enter PEM pass phrase:&lt;/code&gt;&lt;/pre&gt;  &lt;p&gt;在这里，你首先要输入私钥文件的口令以便访问.p12文件，接着要求输入一次新的口令来加密新的PEM文件，这里我们都用”pushchat” ，你也可以使用更复杂的口令来保护你的私钥。&lt;/p&gt;  &lt;p&gt;备注：如果你不输入一个6位以上的加密口令，openssl工具将给出错误信息并取消操作。&lt;/p&gt;  &lt;p&gt;最后，我们把这两个文件合并成一个 .pem 文件：&lt;/p&gt;  &lt;pre&gt;   &lt;code&gt;$ cat PushChatCert.pem PushChatKey.pem &amp;gt; ck.pem&lt;/code&gt;&lt;/pre&gt;  &lt;p&gt;想测试一下证书是否正常，来试一下：&lt;/p&gt;  &lt;pre&gt;   &lt;code&gt;$ telnet gateway.sandbox.push.apple.com 2195Trying 17.172..232.226...Connected to gateway.sandbox.push-apple.com.akadns.net.Escape character is&amp;apos;^]&amp;apos;.&lt;/code&gt;&lt;/pre&gt;  &lt;p&gt;这里是生成一个普通的网络连接，如果有上面的信息，说明你的电脑可以联上APNS服务器，按Ctrl+C关闭连接。&lt;/p&gt;  &lt;p&gt;如果有问题，你需要检查一下网络和防火墙的端口2195的设置。&lt;/p&gt;  &lt;p&gt;这次我们试一试用私钥和证书进行SSL加密连接：&lt;/p&gt;  &lt;pre&gt;   &lt;code&gt;$ openssl s_client -connect gateway.sandbox.push.apple.com:2195 -cert PushChatCert.pem -key PushChatKey.pemEnterpass phrase forPushChatKey.pem:&lt;/code&gt;&lt;/pre&gt;  &lt;p&gt;你应该可以看到接下来的整个输出，我们已经站在正确的起跑线上了。&lt;/p&gt;  &lt;p&gt;如果连接成功，你可以输入几个字符，当你按下回车，连接就断开了，连接失败的话也会有提示信息。&lt;/p&gt;  &lt;p&gt;特别提示你有两个不用的APNS服务器，一个用于测试的沙盒服务器，一个用于正式使用的服务器，我们制作的证书是用于测试的，所以上面的示例中我们使用的是沙盒服务器的地址。&lt;/p&gt;  &lt;h2&gt;准备配置文件&lt;/h2&gt;  &lt;p&gt;开发者门户上的操作还没有完，点击左项菜单上的【Provisioning】，点击【New Profile】新建一个配置文件。&lt;/p&gt;  &lt;p&gt;   &lt;img alt="Creating a Provisioning Profile in the iOS Provisioning Portal" height="293" src="http://www.linchangyu.com/images/2014/Provisioning-5-Create-Profile-500x293.jpg" title="Creating a Provisioning Profile in the iOS Provisioning Portal" width="500"&gt;&lt;/img&gt;&lt;/p&gt;  &lt;p&gt;填入如下内容：&lt;/p&gt;  &lt;ul&gt;   &lt;li&gt;    &lt;p&gt;配置名称Profile Name: PushChat Development&lt;/p&gt;&lt;/li&gt;   &lt;li&gt;    &lt;p&gt;证书（Certificates）:选中你的开发者证书&lt;/p&gt;&lt;/li&gt;   &lt;li&gt;    &lt;p&gt;应用ID（App ID）: PushChat&lt;/p&gt;&lt;/li&gt;   &lt;li&gt;    &lt;p&gt;设备（Devices）: 选中你的开发设备&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;  &lt;p&gt;这里的操作跟你之前的没有什么不用，只是具备推送功能的应用需要一个新配置文件来跟设置好的APP对应起来。&lt;/p&gt;  &lt;p&gt;点击发送按钮（Submit），新的配置文件就会重新生成，稍等片刻，再刷新页面即可下载新的配置文件（PushChat_Development.mobileprovision）。&lt;/p&gt;  &lt;p&gt;将配置文件下载并加载到XCode（双击或拖到XCode图标上）。&lt;/p&gt;  &lt;p&gt;准备正式发布前，你也要这样操作来准备用发布用的配置文件。&lt;/p&gt;  &lt;h2&gt;简单的示例应用&lt;/h2&gt;  &lt;p&gt;你还在吗？经历了这么多终于可以来点兴奋点的事情了，不过上面的这些过程是必不可少的，至少这些也不需要天天搞的，否则是会死人的，不是吗？ 我们已经实现与沙盒服务器的加密网络连接，现在就让我们来实现推送通知的发送吧。&lt;/p&gt;  &lt;p&gt;打开你的XCode，选择【File】【New Project】，选择【View-based Application】点击继续：&lt;/p&gt;  &lt;p&gt;   &lt;img alt="Creating a View-Based Application with Xcode 4" height="340" src="http://www.linchangyu.com/images/2014/Basic-App-1-Xcode-Assistant-500x340.jpg" title="Creating a View-Based Application with Xcode 4" width="500"&gt;&lt;/img&gt;&lt;/p&gt;  &lt;p&gt;填入以下内容：&lt;/p&gt;  &lt;ul&gt;   &lt;li&gt;    &lt;p&gt;Product Name: PushChat&lt;/p&gt;&lt;/li&gt;   &lt;li&gt;    &lt;p&gt;Company Identifier: com.hollance&lt;/p&gt;&lt;/li&gt;   &lt;li&gt;    &lt;p&gt;Device Family: iPhone&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;  &lt;p&gt;根据你的实际情况填入应用名称和开发者标识，这里我们就填入”com.hollance.PushChat”。你应该填入跟你在开发者门户里填入的一致信息(com.yourname.PushChat)。&lt;/p&gt;  &lt;p&gt;完成新建项目的操作，打开PushChatAppDelegate.m，修改 didFinishLaunchingWithOptions 过程：&lt;/p&gt;  &lt;pre&gt;   &lt;code&gt;-(BOOL)application:(UIApplication*)application didFinishLaunchingWithOptions:(NSDictionary*)launchOptions{  self.window.rootViewController =self.viewController;  [self.window makeKeyAndVisible];  // 通知设备需要接收推送通知 Let the device know we want to receive push notifications  [[UIApplication sharedApplication] registerForRemoteNotificationTypes:(UIRemoteNotificationTypeBadge|UIRemoteNotificationTypeSound|UIRemoteNotificationTypeAlert)];  return YES;}&lt;/code&gt;&lt;/pre&gt;  &lt;p&gt;调用registerForRemoteNotificationTypes 通知系统应用是需要接收推送信息的。&lt;/p&gt;  &lt;p&gt;在你的设备上编译运行应用，模拟器是不支持推送信息的。XCode应该会自动选择配置文件，如果出现签名错误，你需要在 Code Signing build settings手动选择之前下载的配置文件。应用启动时会注册推送通知服务，弹出下面的确认窗口提示用户允许此应用接收推送通知服务。&lt;/p&gt;  &lt;p&gt;   &lt;img alt="A Simple iPhone App Requesting Permission to Deliver Notifications" height="500" src="http://www.linchangyu.com/images/2014/Basic-App-2-Allow-Notifications-333x500.jpg" title="A Simple iPhone App Requesting Permission to Deliver Notifications" width="333"&gt;&lt;/img&gt;&lt;/p&gt;  &lt;p&gt;应用只会提示询问一次，如果用户选择接受，设备就一切就绪了。如果用户选择了拒绝，应用将永远无法接收到信息，用户可以在设备的设置项目中修改此项设定。&lt;/p&gt;  &lt;p&gt;   &lt;img alt="Viewing Push Notification Permissions in iPhone Settings" height="500" src="http://www.linchangyu.com/images/2014/Basic-App-3-Settings-1-333x500.jpg" title="Viewing Push Notification Permissions in iPhone Settings" width="333"&gt;&lt;/img&gt;&lt;/p&gt;  &lt;p&gt;应用的名称将会添加到设置程序中的通知项目下，用户可以方便地在这里开启或关闭或自定义接收信息的种类和方式。&lt;/p&gt;  &lt;p&gt;   &lt;img alt="A single app's Push Notification Settings" height="500" src="http://www.linchangyu.com/images/2014/Basic-App-4-Settings-2-333x500.jpg" title="A single app's Push Notification Settings" width="333"&gt;&lt;/img&gt;&lt;/p&gt;  &lt;p&gt;应用也可以通过程序来激活具体的提示方式：&lt;/p&gt;  &lt;pre&gt;   &lt;code&gt;UIRemoteNotificationType enabledTypes =[[UIApplication sharedApplication] enabledRemoteNotificationTypes];&lt;/code&gt;&lt;/pre&gt;  &lt;p&gt;还有额外的一件事，为了发送信息到指定的手机，我们还需要一些操作：&lt;/p&gt;  &lt;pre&gt;   &lt;code&gt;-(void)application:(UIApplication*)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData*)deviceToken{    NSLog(@&amp;quot;我的设备ID: %@&amp;quot;, deviceToken);}-(void)application:(UIApplication*)application didFailToRegisterForRemoteNotificationsWithError:(NSError*)error{    NSLog(@&amp;quot;注册失败，无法获取设备ID, 具体错误: %@&amp;quot;, error);}&lt;/code&gt;&lt;/pre&gt;  &lt;p&gt;当应用注册推送服务成功时，就可以获取用户设备识别ID（Token ID），这是对应你的设备一个32位的唯一编码，你可以理解为推送信息的地址。&lt;/p&gt;  &lt;p&gt;运行应用，在XCode的终端窗口你可以看到以下信息：&lt;/p&gt;  &lt;pre&gt;   &lt;code&gt;我的设备ID:&amp;lt;740f4707 bebcf74f 9b7c25d4 8e335894 5f6aa01d a5ddb387 462c7eaf 61bb78ad&amp;gt;&lt;/code&gt;&lt;/pre&gt;  &lt;p&gt;设备识别ID（Token ID）是加密的数据结构，储备在NSData对象中。这里你知道它是32位长度就够了，上面你看到的是64个16进制的字符，我们将使用这个格式，当然&amp;lt;&amp;gt;和空格要过滤掉。&lt;/p&gt;  &lt;p&gt;在模拟器中运行，didFailToRegisterForRemoteNotificationsWithError会返回错误：method will be called as push notifications are not supported in the simulator.&lt;/p&gt;  &lt;p&gt;应用准备好了，就差最后一件事了。&lt;/p&gt;  &lt;h2&gt;发送我们的推送通知&lt;/h2&gt;  &lt;p&gt;之前我们都谈到要有服务器或服务组件来实现推送通知的发送和管理，在这里，我们先不急着搭建服务器，这里有一个简单的PHP脚本用来建立到APNS的连接和发送测试信息到之前的设备上。&lt;/p&gt;  &lt;p&gt;你可以在MAC上直接使用：&lt;/p&gt;  &lt;p&gt;   &lt;a href="http://d1xzuxjlafny7l.cloudfront.net/downloads/SimplePush.zip"&gt;下载SimplePush源代码&lt;/a&gt;，解开后，修改simplepush.php中的以下几个地方：&lt;/p&gt;  &lt;pre&gt;   &lt;code&gt;// Put your device token here (without spaces):$deviceToken =&amp;apos;0f744707bebcf74f9b7c25d48e3358945f6aa01da5ddb387462c7eaf61bbad78&amp;apos;;// Put your private key&amp;apos;s passphrase here:$passphrase =&amp;apos;pushchat&amp;apos;;// Put your alert message here:$message =&amp;apos;My first push notification!&amp;apos;;&lt;/code&gt;&lt;/pre&gt;  &lt;p&gt;复制设备识别ID到变量$deviceToken，别留下任何一个空格，完完全全就是64个16进制字符；指定私钥的口令和要发送的信息内容；复制ck.pem到脚本所在文件夹，ck.pem包括了证书和私钥。&lt;/p&gt;  &lt;p&gt;开启终端程序（ Terminal）：&lt;/p&gt;  &lt;pre&gt;   &lt;code&gt;$ php simplepush.php&lt;/code&gt;&lt;/pre&gt;  &lt;p&gt;如果一切OK的话，脚本将返回：&lt;/p&gt;  &lt;pre&gt;   &lt;code&gt;Connected to APNSMessage successfully delivered&lt;/code&gt;&lt;/pre&gt;  &lt;p&gt;几秒钟内，你应该可以在设备上收到推送的信息了。&lt;/p&gt;  &lt;p&gt;   &lt;img alt="Simple app receiving a Push Notification" height="500" src="http://www.linchangyu.com/images/2014/Basic-App-5-Success-333x500.jpg" title="Simple app receiving a Push Notification" width="333"&gt;&lt;/img&gt;&lt;/p&gt;  &lt;p&gt;注意如果应用在开启运行状态的话，你看不到任何信息，信息被直接发送给应用本身，但是我们还没有通过编程来处理收到的信息，不信你可以再试一下。&lt;/p&gt;  &lt;p&gt;如果PHP脚本退出并返回错误信息，请检查PEM文件是否正确、连接沙盒服务器是否正常。&lt;/p&gt;  &lt;p&gt;PHP脚本具体的实现过程就不讨论了，有兴趣的或需要自行搭建服务器来管理发送推送通知的可以看此教程的下篇（请找原出处）。&lt;/p&gt;  &lt;h2&gt;接下来呢&lt;/h2&gt;  &lt;p&gt;现在你已经成功地实现了应用的推送通知服务，在此教程的下篇中，我们来开发一个简单的短消息应用（PushChat）来实现用户之间的推送通知功能。还有完整用于在后台不间断提供推送通知服务的服务组件API。&lt;/p&gt;  &lt;p&gt;欢迎向我们提问交流，谢谢。&lt;/p&gt;  &lt;p&gt;   &lt;img alt="Matthijs Hollemans" height="100" src="http://www.linchangyu.com/images/2014/hollemans_100x100.jpg" title="Matthijs Hollemans" width="100"&gt;&lt;/img&gt; 作者：    &lt;a href="http://twitter.com/#!/mhollemans"&gt;Matthijs Hollemans&lt;/a&gt;，iOS 教程小组成员，高级iOS开发人员和设计人员。&lt;/p&gt;  &lt;p&gt;   &lt;img alt="Cheney Hollemans" height="100" src="http://www.linchangyu.com/images/lcy.png" title="Cheney Lin" width="100"&gt;&lt;/img&gt; 译者：   &lt;a href="http://twitter.com/#!/linchangyu"&gt;Cheney Lin&lt;/a&gt;,    &lt;a href="http://www.linchangyu.com/"&gt;linchangyu.com&lt;/a&gt;, 全栈开发者。&lt;/p&gt;&lt;/div&gt; &lt;ul&gt;  &lt;li&gt;&lt;/li&gt;&lt;/ul&gt; &lt;p&gt;  &lt;br /&gt;&lt;/p&gt;
				 &lt;p&gt;  &lt;strong&gt;您可能还对下面的文章感兴趣：&lt;/strong&gt;&lt;/p&gt;
				 &lt;p&gt;  &lt;ol&gt;   &lt;li&gt;    &lt;a href="http://blogread.cn/it/article.php?id=7594" target="_blank"&gt;APP的推送是咋回事&lt;/a&gt; [2015-12-13 21:43:13]&lt;/li&gt;   &lt;li&gt;    &lt;a href="http://blogread.cn/it/article.php?id=3063" target="_blank"&gt;2011年手机产品设计趋势（2）：推送&lt;/a&gt; [2011-01-20 22:48:36]&lt;/li&gt;&lt;/ol&gt;&lt;/p&gt; &lt;img alt="" height="1" src="http://feeds.feedburner.com/~r/blogreadIT/~4/1VpLu7Yf75E" width="1"&gt;&lt;/img&gt;&lt;div&gt; &lt;a href="https://itindex.net/"  title="IT 资讯"&gt;&lt;img src="https://itindex.net/images/iconWarning.gif" title="IT 资讯" border="0"/&gt; &lt;/a&gt;</description>
      <category>iOS开发</category>
      <guid isPermaLink="true">https://itindex.net/detail/55201-%E8%8B%B9%E6%9E%9C-ios-%E7%B3%BB%E7%BB%9F</guid>
      <pubDate>Tue, 16 Feb 2016 01:08:09 CST</pubDate>
    </item>
    <item>
      <title>如何通过 OAuth 2.0 使 iOS Apps 集成 LinkedIn 登录功能？</title>
      <link>https://itindex.net/detail/55343-oauth-ios-apps</link>
      <description>&lt;p&gt;  &lt;strong&gt;社交网络早已成为人们日常生活的一部分。其实，社交网络也是编程生活的一部分，大多数 App 必须通过某种方式与社交网络交互，传送或接收与用户相关的数据。大多数情况下，用户需要登录某种社交网络，授权 App 代表自己进行请求。&lt;/strong&gt;&lt;/p&gt;

 &lt;p&gt;目前，此类社交网络的种类非常丰富，以 Facebook 与 Twitter 最为常用。而且，iOS 系统内置了对这两款社交网络的支持。然而，对于其他类型的社交网络，开发者必须投入更多的努力，以使 App 获得授权访问这些社交网络，继而运行经过授权的合法请求。LinkedIn 就是这样一种社交网络，在本教程中，我们会了解如何为 App 授权，使之与 LinkedIn 的服务器交换受保护的数据。&lt;/p&gt;

 &lt;p&gt;为 iOS App 授权以访问 LinkedIn，并根据后者提供的 API 运行特定的操作，可以通过两种方法实现。方法一：使用 LinkedIn 支持的 OAuth 2.0 协议。方法二：使用 LinkedIn 提供的 iOS SDK。与所有第三方 SDK 一样，LinkedIn 提供的 SDK 必须集成到项目中，经过合理的配置才能使用。&lt;/p&gt;

 &lt;p&gt;  &lt;img alt="linked-in-sign-in" src="http://www.appcoda.com/wp-content/uploads/2015/12/linked-in-sign-in-1024x722.jpg"&gt;&lt;/img&gt;&lt;/p&gt;

 &lt;p&gt;在本文中，我们将仅专注于第一种方法。因此，我们将学习 LinkedIn 与 OAuth 2.0 指南中与此相关的指定内容，包括让用户通过 App（是任何 App，而不仅仅是 iOS 系统）登录 LinkedIn 并为 App 授权执行进一步请求的必要流程。尽管 LinkedIn iOS SDK 也是很好的选择，但笔者更喜欢 OAuth 方法，原因有三：&lt;/p&gt;

 &lt;ol&gt;
  &lt;li&gt;笔者个人更偏爱此类任务：使用 REST API 调用与服务器进行直接的交流，以顺利完成授权过程。  &lt;/li&gt;
  &lt;li&gt;LinkedIn 网站中有关 LinkedIn iOS SDK 的介绍相当明确详尽，因此笔者认为基于相同主题写的教程恐怕益处不大。  &lt;/li&gt;
  &lt;li&gt;笔者认为，使用 LinkedIn iOS SDK 时存在一个缺陷：官方的 LinkedIn App 必须已经安装在设备中，否则登录与授权过程就无法完成。如果某个 App 需要获得用户的 LinkedIn 主页信息，但用户并不想安装 LinkedIn 官方应用，就会造成不便。&lt;/li&gt;
&lt;/ol&gt;

 &lt;p&gt;关于 OAuth 2.0 协议，能说的实在太多了。读者最好还是登录  &lt;a href="http://oauth.net/2/"&gt;官网&lt;/a&gt;仔细研读一下。简而言之，为了成功完成登录与授权过程，本教程将会遵循以下步骤：&lt;/p&gt;

 &lt;ul&gt;
  &lt;li&gt;必要地，我们将在    &lt;a href="https://developer.linkedin.com/"&gt;LinkedIn 开发者网站&lt;/a&gt;创建一个新的 App。从而得到完成后续过程必备的两个重要密匙（Client ID 与 Client Secret）。&lt;/li&gt;
  &lt;li&gt;通过一个 Web 视图，让用户登录其 LinkedIn 账户。&lt;/li&gt;
  &lt;li&gt;根据以上所得，再加上一些必要数据，向 LinkedIn 服务器索取授权码。&lt;/li&gt;
  &lt;li&gt;与一个访问令牌交换授权码。&lt;/li&gt;
&lt;/ul&gt;

 &lt;p&gt;访问令牌是与 OAuth 交互的必要条件。通过一个有效的令牌，我们便能向 LinkedIn 服务器发送经过授权的请求。并根据 App 的性质，“get”或“post” 数据到用户的主页。&lt;/p&gt;

 &lt;p&gt;在继续阅读之前，请确保你理解 OAuth 2.0 的工作原理，以及它的流（flow）。如果必要，阅读其他资源以获取更多信息（比如  &lt;a href="https://en.wikipedia.org/wiki/OAuth"&gt;这儿&lt;/a&gt;，  &lt;a href="https://www.digitalocean.com/community/tutorials/an-introduction-to-oauth-2"&gt;这儿&lt;/a&gt;和  &lt;a href="http://tools.ietf.org/html/rfc6749"&gt;这儿&lt;/a&gt;）。&lt;/p&gt;

 &lt;p&gt;说了这么多，让我们进入正题，介绍本教程的演示应用，然后进入具体实现。笔者相信，我们将要学习的内容是趣味无穷的。&lt;/p&gt;

 &lt;p&gt;作为参考，以下是 LinkedIn 官方文档的链接：&lt;/p&gt;

 &lt;ul&gt;
  &lt;li&gt;   &lt;a href="https://developer.linkedin.com/docs/oauth2"&gt;关于 OAuth 2.0&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;   &lt;a href="https://developer.linkedin.com/docs/signin-with-linkedin"&gt;使用 LinkedIn 账号登录&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

 &lt;h2&gt;演示 App 概览&lt;/h2&gt;

 &lt;p&gt;我们在本教程中将要实现的演示 App 由两部分视图控制器组成：第一个（默认的 ViewController 类）只包含三个按钮：&lt;/p&gt;

 &lt;ol&gt;
  &lt;li&gt;一个名为 LinkedIn Sign In（LinkedIn 登录）的按钮，用于启动登录与授权流程。  &lt;/li&gt;
  &lt;li&gt;一个名为 Get my profile URL（获得我的主页 URL）的按钮，用于执行一个经过授权的请求，使用访问令牌获得用户主页的 URL。  &lt;/li&gt;
  &lt;li&gt;一个展示主页 URL 的按钮，点击之后会在 Safari 中打开用户的 LinkedIn 主页。&lt;/li&gt;
&lt;/ol&gt;

 &lt;p&gt;默认情况下，只会启用第一个按钮。实际上，只要还未获得访问令牌，该按钮就会一直可用。在其他情况下，第一个按钮会被禁用，同时启用第二个按钮。第三个按钮是隐藏的，只有当得到（通过第二个按钮）用户主页的 URL 时，才会可见。&lt;/p&gt;

 &lt;p&gt;  &lt;img alt="view-controller-signin" src="http://www.appcoda.com/wp-content/uploads/2015/12/view-controller-signin.png"&gt;&lt;/img&gt;&lt;/p&gt;

 &lt;p&gt;第二个视图控制器会包含一个 Web 视图。通过该试图，你可以登录 LinkedIn 账户，这样认证与授权过程才能成功进行。当获得用于向 LinkedIn 发送合法请求的访问令牌后，该视图控制器就会被移除。&lt;/p&gt;

 &lt;p&gt;  &lt;img alt="t47_2_user_sign_in" src="http://www.appcoda.com/wp-content/uploads/2015/12/t47_2_user_sign_in.png"&gt;&lt;/img&gt;&lt;/p&gt;

 &lt;p&gt;与往常一样，我们不需要从头开始创建项目。你可以  &lt;a href="https://www.dropbox.com/s/q7hjsbf51y1l6ok/LinkedInSignInStarter.zip?dl=0"&gt;下载一个启动项目&lt;/a&gt;，在此基础上继续搭建。&lt;/p&gt;

 &lt;p&gt;基本上，我们的主要努力将专注于获取访问令牌。我们会遵循 OAuth 2.0 协议以及 LinkedIn 指南的指定，一步一步地完成所有必备流程。获得访问令牌之后，我们会继续解释如何向 LinkedIn 发送合法请求，以获得授权用户公共主页的 URL。成功得到 URL 之后，我们会请用前面提到的第三个按钮，将主页内容显示在 Safari 浏览器中。&lt;/p&gt;

 &lt;p&gt;在你继续阅读之前，请确保已经下载启动项目，打开它并熟悉它的。准备就绪之后，请继续往下看。&lt;/p&gt;

 &lt;h2&gt;LinkedIn 开发者网站—— 创建新的 App&lt;/h2&gt;

 &lt;p&gt;实现 OAuth 2.0 登录流程的第一步，是在 LinkedIn 开发者网站创建一个新的 App 记录。为此，你仅需访问  &lt;a href="https://www.linkedin.com/developer/apps"&gt;此链接&lt;/a&gt;。如果你还没有登录 LinkedIn 主页，你将收到提示以完成登录操作。&lt;/p&gt;

 &lt;p&gt;  &lt;em&gt;注意：如果你在下面的步骤中使用 Safari 出现问题，请选择其他浏览器。我使用的是 Chrome 浏览器。&lt;/em&gt;&lt;/p&gt;

 &lt;p&gt;登录之后，找到网站“我的应用（My Applications）”部分，你会发现一个名为“创建应用（Create Application）”的黄色按钮。点击它开始创建新的应用，之后我们会将它与 iOS App 进行联结。&lt;/p&gt;

 &lt;p&gt;  &lt;img alt="t47_3_create_app_button" src="http://www.appcoda.com/wp-content/uploads/2015/12/t47_3_create_app_button.png"&gt;&lt;/img&gt;&lt;/p&gt;

 &lt;p&gt;在接下来出现的表格中，填写所有栏目。如果需要填写公司名称或上传应用 logo，不用担心，输入一些虚假信息也可。之后，接受使用条款，点击提交按钮。请一定要在带红色星号的栏目中输入内容，否则你将无法继续。以下为示例：&lt;/p&gt;

 &lt;p&gt;  &lt;img alt="t47_4_create_new_app" src="http://www.appcoda.com/wp-content/uploads/2015/12/t47_4_create_new_app.png"&gt;&lt;/img&gt;&lt;/p&gt;

 &lt;p&gt;我们的目标是抵达下一个页面：&lt;/p&gt;

 &lt;p&gt;  &lt;img alt="t47_5_app_settings" src="http://www.appcoda.com/wp-content/uploads/2015/12/t47_5_app_settings.png"&gt;&lt;/img&gt;&lt;/p&gt;

 &lt;p&gt;如你在上面的截图中所见，在此页面可以看到 Client ID 与 Client Secret 的值。请不要关闭该窗口，因为接下来的步骤中会用到它们。你可以使用窗口左侧的菜单选项，随意探索应用的设置。&lt;/p&gt;

 &lt;p&gt;此处，除了得到 Client 密匙（Client ID 与 Client Secret 的值），我们还要完成的另一项重要任务，是在“合法重定向 URLs(Authorized Redirect URLs)”一栏填入合适的值。当客户端 App 试图刷新现有的访问令牌，用户无需通过 Web 浏览器重新登录，使用合法重定向 URL 即可。OAuth 流会自动使用该 URL 将 App 重定向。在正常的登录过程中，客户端 App 与 LinkedIn 服务器会交换该 URL，同时取得授权码与访问令牌。总之，该值不能为空，稍后会用来与服务器进行交换，因此必须定义它。&lt;/p&gt;

 &lt;p&gt;重定向 URL 不需要是真实存在的 URL，可以是任何以 “  &lt;a href="https://&amp;#8221;"&gt;https://”&lt;/a&gt; 开头的值。在此，笔者将其赋值如下。你可以将其改为任何你希望的值。&lt;/p&gt;

 &lt;pre&gt;  &lt;code&gt;https://com.appcoda.linkedin.oauth/oauth  
&lt;/code&gt;&lt;/pre&gt;

 &lt;p&gt;  &lt;em&gt;如果你使用了一个不同的 URL, 千万记得对后面出现的代码进行相应的修改。&lt;/em&gt;&lt;/p&gt;

 &lt;p&gt;在“OAuth 2.0”一节写入合法重定向 URL 后，必须点击添加按钮，保证将其加入 App 中。&lt;/p&gt;

 &lt;p&gt;  &lt;img alt="t47_6_authorized_redirect_url" src="http://www.appcoda.com/wp-content/uploads/2015/12/t47_6_authorized_redirect_url.png"&gt;&lt;/img&gt;&lt;/p&gt;

 &lt;p&gt;此外，记得点击屏幕底部的更新按钮。&lt;/p&gt;

 &lt;p&gt;至于有关访问权限的选项，保留基本选项即可，因为其完全满足本教程的需求。当然，你也可以选择更多权限，或在演示 App 准备就绪之后再做修改。请注意，如果 App 请求的最初权限遭到改动，用户必须重新登录以认可这些改动。&lt;/p&gt;

 &lt;h2&gt;开始授权过程&lt;/h2&gt;

 &lt;p&gt;现在，打开 Xcode 中的启动项目，我们即将开始实现，并最终完成 OAuth 2.0 流。不过，在开始之前，请选择项目导航栏（Project Navigator）中的 WebViewController.swift 文件，打开它。在该类的头部，你会看到两个名为 linkedInKey 与 linkedInSecret 的变量。你需要将之前从 LinkedIn 开发者网站得到的 Client ID 与 Client Secret 值分别赋值给这两个变量（简单的复制、黏贴即可）。&lt;/p&gt;

 &lt;p&gt;  &lt;img alt="t47_7_assigned_keys" src="http://www.appcoda.com/wp-content/uploads/2015/12/t47_7_assigned_keys.png"&gt;&lt;/img&gt;&lt;/p&gt;

 &lt;p&gt;本步的主要目的，是准备好用来获取授权码的请求，并通过一个 Web 视图加载它。界面生成器（Interface Builder）中的 WebViewController 已经包含了一个 Web 视图，因此我们将以 WebViewController 类为基础构建视图。用于获取授权码的请求必须包含以下参数：&lt;/p&gt;

 &lt;ul&gt;
  &lt;li&gt;response_type：取值为恒定的标准值：code。&lt;/li&gt;
  &lt;li&gt;client_id：取值为来自 LinkedIn 开发者网站的 Client ID，之后会赋值给项目中的 linkedInKey 属性。&lt;/li&gt;
  &lt;li&gt;redirect_uri：取值为在前一节指定的合法重定向 URL。请确保在后面的代码段中填入相应的值。&lt;/li&gt;
  &lt;li&gt;state：取值为唯一的字符串，用于预防跨站请求伪造(CSRF)。&lt;/li&gt;
  &lt;li&gt;scope：取值为 App 请求的访问权限列表，以 URL 形式编码。&lt;/li&gt;
&lt;/ul&gt;

 &lt;p&gt;下面，介绍代码的具体实现。首先，在 WebViewController 类下创建一个名为 startAuthorization() 的新函数。该函数的第一个任务是根据上文的描述为请求参数赋值。&lt;/p&gt;

 &lt;pre&gt;  &lt;code&gt;func startAuthorization() {  
    // Specify the response type which should always be &amp;quot;code&amp;quot;.
    let responseType = &amp;quot;code&amp;quot;

    // Set the redirect URL. Adding the percent escape characthers is necessary.
    let redirectURL = &amp;quot;https://com.appcoda.linkedin.oauth/oauth&amp;quot;.stringByAddingPercentEncodingWithAllowedCharacters(NSCharacterSet.alphanumericCharacterSet())!

    // Create a random string based on the time interval (it will be in the form linkedin12345679).
    let state = &amp;quot;linkedin\(Int(NSDate().timeIntervalSince1970))&amp;quot;

    // Set preferred scope.
    let scope = &amp;quot;r_basicprofile&amp;quot;


}
&lt;/code&gt;&lt;/pre&gt;

 &lt;p&gt;请注意：不是简单地将合法重定向 URL 赋值给 redirectURL 变量。我们需要将 URL 中的特殊符号通过 URL 编码替换为百分比编码字符。因此，下面的链接：&lt;/p&gt;

 &lt;pre&gt;  &lt;code&gt;https://com.appcoda.linkedin.oauth/oauth  
&lt;/code&gt;&lt;/pre&gt;

 &lt;p&gt;将会转换为：&lt;/p&gt;

 &lt;pre&gt;  &lt;code&gt;https%3A%2F%2Fcom.appcoda.linkedin.oauth%2oauth  
&lt;/code&gt;&lt;/pre&gt;

 &lt;p&gt;(See more about URL-encoding here).&lt;/p&gt;

 &lt;p&gt;（  &lt;a href="http://www.w3schools.com/tags/ref_urlencode.asp"&gt;点此&lt;/a&gt;了解 URL 编码的更多信息。）&lt;/p&gt;

 &lt;p&gt;其次，state 变量必须包含一个唯一且莫测的字符串。在上面的代码中，我们将“linkedin”字符串与当前时间戳（自1970年以来的时间间隔）的整数部分进行联结，以此确保字符串的唯一性。你也可以生成随机字符，再将其附加到 state 字符串上。&lt;/p&gt;

 &lt;p&gt;最后，将 scope 赋值为“r_basicprofile”，后者与之前在 LinkedIn 开发者网站设定的 App 访问权限相匹配。当你设置访问权限时，请确保与官方文档中的规定一致。&lt;/p&gt;

 &lt;p&gt;Our next step is to compose the authorization URL. Note that the   &lt;em&gt;   &lt;a href="https://www.linkedin.com/uas/oauth2/authorization"&gt;https://www.linkedin.com/uas/oauth2/authorization&lt;/a&gt;&lt;/em&gt; URL must be used for the request, which is already assigned to the authorizationEndPoint property.&lt;/p&gt;

 &lt;p&gt;下一步，创建授权 URL。请注意，URL   &lt;em&gt;   &lt;a href="https://www.linkedin.com/uas/oauth2/authorization"&gt;https://www.linkedin.com/uas/oauth2/authorization&lt;/a&gt;&lt;/em&gt; 必须用于该请求，而该 URL 已经赋做 authorizationEndPoint 属性的值。&lt;/p&gt;

 &lt;p&gt;回到代码：&lt;/p&gt;

 &lt;pre&gt;  &lt;code&gt;func startAuthorization() {  
    ...

    // Create the authorization URL string.
    var authorizationURL = &amp;quot;\(authorizationEndPoint)?&amp;quot;
    authorizationURL += &amp;quot;response_type=\(responseType)&amp;amp;&amp;quot;
    authorizationURL += &amp;quot;client_id=\(linkedInKey)&amp;amp;&amp;quot;
    authorizationURL += &amp;quot;redirect_uri=\(redirectURL)&amp;amp;&amp;quot;
    authorizationURL += &amp;quot;state=\(state)&amp;amp;&amp;quot;
    authorizationURL += &amp;quot;scope=\(scope)&amp;quot;

    print(authorizationURL)
}
&lt;/code&gt;&lt;/pre&gt;

 &lt;p&gt;此处，笔者添加了打印命令，是为了让读者亲眼看到该请求最终是如何形成的。&lt;/p&gt;

 &lt;p&gt;最终，我们需要在 Web 视图中加载该请求。请记住，只有前文所述的请求配置得当，用户才能通过 Web 视图成功登录。否则，LinkedIn 将返回错误消息，导致无法进行下一步操作。&lt;/p&gt;

 &lt;p&gt;因此，请确保正确拷贝了 Client Key、Client Secret，以及统一的合法重定向 URL。&lt;/p&gt;

 &lt;p&gt;在 Web 视图中加载该请求只需短短几行代码：&lt;/p&gt;

 &lt;pre&gt;  &lt;code&gt;func startAuthorization() {  
    ...

    // Create a URL request and load it in the web view.
    let request = NSURLRequest(URL: NSURL(string: authorizationURL)!)
    webView.loadRequest(request)
}
&lt;/code&gt;&lt;/pre&gt;

 &lt;p&gt;在结束本节之前，我们必须调用上面的函数。可以通过 viewDidLoad(_: ) 函数进行调用：&lt;/p&gt;

 &lt;pre&gt;  &lt;code&gt;override func viewDidLoad() {  
    ...

    startAuthorization()
}
&lt;/code&gt;&lt;/pre&gt;

 &lt;p&gt;此时，你终于可以运行 App，测试其是否成功了。如果你根据笔者的指导配置正确，应该可以看到以下页面：&lt;/p&gt;

 &lt;p&gt;  &lt;img alt="t47_2_user_sign_in" src="http://www.appcoda.com/wp-content/uploads/2015/12/t47_2_user_sign_in.png"&gt;&lt;/img&gt;&lt;/p&gt;

 &lt;p&gt;不过，先别急着登录 LinkedIn 账号，本节还有一部分工作未完成。然而，如果你看到了登录表格，说明你已经成功发送了获取授权码的请求。登录之后，LinkedIn 会向浏览器（在本例中，也即我们的 Web 视图）返回一个授权码。&lt;/p&gt;

 &lt;p&gt;除此之外，还会在控制台打印出 authorizationURL（授权 URL）字符串：&lt;/p&gt;

 &lt;p&gt;  &lt;img alt="t47_8_authorization_request" src="http://www.appcoda.com/wp-content/uploads/2015/12/t47_8_authorization_request.png"&gt;&lt;/img&gt;&lt;/p&gt;

 &lt;h2&gt;Getting an Authorization Code&lt;/h2&gt;

 &lt;h2&gt;获取授权码&lt;/h2&gt;

 &lt;p&gt;授权码请求函数准备就绪，且在 Web 视图中成功加载之后，我们可以继续执行 webView(:shouldStartLoadWithRequest:navigationType) 委托函数。在此函数中，我们会捕获来自 LinkedIn 的响应，并从中抽取出渴望已久的授权码。&lt;/p&gt;

 &lt;p&gt;包含授权码的响应如下所示：&lt;/p&gt;

 &lt;pre&gt;  &lt;code&gt;http://com.appcoda.linkedin.oauth/oauth?&amp;lt;strong&amp;gt;code=AQSetQ252oOM237XeXvUreC1tgnjR-VC1djehRxEUbyZ-sS11vYe0r0JyRbe9PGois7Xf42g91cnUOE5mAEKU1jpjogEUNynRswyjg2I3JG_pffOClk&amp;lt;/strong&amp;gt;&amp;amp;state=linkedin1450703646  
&lt;/code&gt;&lt;/pre&gt;

 &lt;p&gt;因此，我们需要将该字符串分为多个部分，隔离出“code”的值。不过，有两点注意：其一，我们必须确保委托函数中的 URL 是我们感兴趣的。其二，必须确保授权码的确存在于该 LinkedIn 响应中。代码的实现如下：&lt;/p&gt;

 &lt;pre&gt;  &lt;code&gt;func webView(webView: UIWebView, shouldStartLoadWithRequest request: NSURLRequest, navigationType: UIWebViewNavigationType) -&amp;gt; Bool {  
    let url = request.URL!
    print(url)

    if url.host == &amp;quot;com.appcoda.linkedin.oauth&amp;quot; {
        if url.absoluteString.rangeOfString(&amp;quot;code&amp;quot;) != nil {

        }
    }

    return true
}
&lt;/code&gt;&lt;/pre&gt;

 &lt;p&gt;首先，通过请求参数获得该 URL。接着，检查 URL 的主机属性值以确保这是我们需要的 URL（也即在 LinkedIn 开发者网站设定的重定向 URL）。如果是，请求字符串中 “code” 所在的范围，以验证该 URL 是否真的包含授权码。如果返回不为空，则证明授权码的确存在。&lt;/p&gt;

 &lt;p&gt;将 URL 字符串分为多个部分并不难。为了简化步骤，笔者将该任务分为两步：&lt;/p&gt;

 &lt;pre&gt;  &lt;code&gt;func webView(webView: UIWebView, shouldStartLoadWithRequest request: NSURLRequest, navigationType: UIWebViewNavigationType) -&amp;gt; Bool {  
    let url = request.URL!
    print(url)

    if url.host == &amp;quot;com.appcoda.linkedin.oauth&amp;quot; {
        if url.absoluteString.rangeOfString(&amp;quot;code&amp;quot;) != nil {
            // Extract the authorization code.
            let urlParts = url.absoluteString.componentsSeparatedByString(&amp;quot;?&amp;quot;)
            let code = urlParts[1].componentsSeparatedByString(&amp;quot;=&amp;quot;)[1]

            requestForAccessToken(code)
        }
    }

    return true
}
&lt;/code&gt;&lt;/pre&gt;

 &lt;p&gt;除了上面新出现的两行代码，你也肯定注意到了对 requestForAccessToken(_: ) 函数的调用。这是我们将在下一部分实现的自定义函数。在此函数中，我们会用此处获得的授权码读取访问令牌。&lt;/p&gt;

 &lt;p&gt;如你所见，只差一步，我们就能使用 OAuth 2.0 流获取访问令牌了。此处扼要重述一下之前的步骤：首先，我们成功创建了获取授权码的请求。接着，作为授权过程的一部分，用户通过该请求连接他们的 LinkedIn 账号。最后，得到并抽取出授权码。&lt;/p&gt;

 &lt;p&gt;如果你想对目前的 App 进行测试，只需注释掉 requestForAccessToken(_: ) 函数的调用部分即可。你大可以在任意位置添加打印命令，从而深刻理解每个步骤的作用。&lt;/p&gt;

 &lt;h2&gt;Requesting for the Access Token&lt;/h2&gt;

 &lt;p&gt;信息结构，创作我们的信息就是如此，我已经很是在此基础上我们创作就是token整个结构就是做这些事情的&lt;/p&gt;

 &lt;h2&gt;请求访问令牌&lt;/h2&gt;

 &lt;p&gt;此前，我们与 LinkedIn 服务器的所有交流都是通过 Web 视图进行的。从现在起，我们将仅通过简便的 RESTful 请求（也即 POST 与 GET 请求）与服务器交流。更具体地说，我们会发起一个 POST 请求来获取访问令牌，之后再用 GET 请求获得用户主页的 URL。&lt;/p&gt;

 &lt;p&gt;话虽如此，现在要先创建在上一部分末尾提过的新的自定义函数：requestForAccessToken()。在此函数内部，我们将执行三个任务：&lt;/p&gt;

 &lt;ol&gt;
  &lt;li&gt;准备好 POST 请求的参数。  &lt;/li&gt;
  &lt;li&gt;初始化并配置一个可变的 URL 请求对象（NSMutableURLRequest）。  &lt;/li&gt;
  &lt;li&gt;实例化一个 NSURLSession 对象，继而执行一个数据任务请求。在得到恰当的响应之后，我们将访问令牌存储在用户默认的字典中。&lt;/li&gt;
&lt;/ol&gt;

 &lt;h2&gt;准备 POST 请求参数&lt;/h2&gt;

 &lt;p&gt;与获取授权码的请求准备相似，为了获得访问令牌，我们需要在请求中 POST 特定的参数与其对应的值。这些参数包括：&lt;/p&gt;

 &lt;ul&gt;
  &lt;li&gt;grant   &lt;em&gt;type: It’s a standard value that should always be: authorization&lt;/em&gt;code.&lt;/li&gt;
  &lt;li&gt;code: The authorization code acquired in the previous part.&lt;/li&gt;
  &lt;li&gt;redirect_uri: It’s the authorized redirection URL we’ve talked about many times earlier.&lt;/li&gt;
  &lt;li&gt;client_id: The Client Key value.&lt;/li&gt;
  &lt;li&gt;client_secret: The Client Secret Value.&lt;/li&gt;
  &lt;li&gt;grant   &lt;em&gt;type：取值为恒定的标准值：authorization&lt;/em&gt;code。&lt;/li&gt;
  &lt;li&gt;code：取值为在上一部分获得的授权码。&lt;/li&gt;
  &lt;li&gt;redirect_uri：取值为前面多次提到的合法重定向 URL。&lt;/li&gt;
  &lt;li&gt;client_id：取值为 Client Key 的值。&lt;/li&gt;
  &lt;li&gt;client_secret：取值为 Client Secret 的值。&lt;/li&gt;
&lt;/ul&gt;

 &lt;p&gt;在上一部分得到的授权码将在新函数中用作参数。首先，让我们为参数“grant  &lt;em&gt;type”与“redirect&lt;/em&gt;uri”赋值：&lt;/p&gt;

 &lt;pre&gt;  &lt;code&gt;   func requestForAccessToken(authorizationCode:     String) {
    let grantType = &amp;quot;authorization_code&amp;quot;

    let redirectURL = &amp;quot;https://com.appcoda.linkedin.oauth/oauth&amp;quot;.stringByAddingPercentEncodingWithAllowedCharacters(NSCharacterSet.alphanumericCharacterSet())!
}
&lt;/code&gt;&lt;/pre&gt;

 &lt;p&gt;其他所有参数的值 App 都已经知道了，因此，我们可以将其整合为一个字符串：&lt;/p&gt;

 &lt;pre&gt;  &lt;code&gt;func requestForAccessToken(authorizationCode: String) {  
    ...

    // Set the POST parameters.
    var postParams = &amp;quot;grant_type=\(grantType)&amp;amp;&amp;quot;
    postParams += &amp;quot;code=\(authorizationCode)&amp;amp;&amp;quot;
    postParams += &amp;quot;redirect_uri=\(redirectURL)&amp;amp;&amp;quot;
    postParams += &amp;quot;client_id=\(linkedInKey)&amp;amp;&amp;quot;
    postParams += &amp;quot;client_secret=\(linkedInSecret)&amp;quot;
}
&lt;/code&gt;&lt;/pre&gt;

 &lt;p&gt;如果你曾用 NSMutableURLRequest 类创建过 POST 请求，那你一定知道 POST 参数无法以字符串的形式传送。它们必须转化为 NSData 对象，再赋值给请求的 HTTPBody 部分（后文会有解释）。因此，让我们按照要求转化 postParams：&lt;/p&gt;

 &lt;pre&gt;  &lt;code&gt;func requestForAccessToken(authorizationCode: String) {  
    ...

    // Convert the POST parameters into a NSData object.
    let postData = postParams.dataUsingEncoding(NSUTF8StringEncoding)
}
&lt;/code&gt;&lt;/pre&gt;

 &lt;h2&gt;准备请求对象&lt;/h2&gt;

 &lt;p&gt;准备好 POST 参数之后，我们可以继续初始化并配置 NSMutableURLRequest 对象。初始化时会用到获取访问令牌所需的 URL(  &lt;a href="https://www.linkedin.com/uas/oauth2/accessToken"&gt;https://www.linkedin.com/uas/oauth2/accessToken&lt;/a&gt;) ，而后者已经赋值给 accessTokenEndPoint 属性。&lt;/p&gt;

 &lt;pre&gt;  &lt;code&gt;func requestForAccessToken(authorizationCode: String) {  
    ...    

    // Initialize a mutable URL request object using the access token endpoint URL string.
    let request = NSMutableURLRequest(URL: NSURL(string: accessTokenEndPoint)!)
}
&lt;/code&gt;&lt;/pre&gt;

 &lt;p&gt;Next, it’s time to “say” to the request object what kind of request we want to make, as well as to pass it the POST parameters:&lt;/p&gt;

 &lt;p&gt;接下来，告诉请求对象我们想要创建的请求类型，并传入 POST 参数：&lt;/p&gt;

 &lt;pre&gt;  &lt;code&gt;func requestForAccessToken(authorizationCode: String) {  
    ...

    // Indicate that we&amp;apos;re about to make a POST request.
    request.HTTPMethod = &amp;quot;POST&amp;quot;

    // Set the HTTP body using the postData object created above.
    request.HTTPBody = postData
}
&lt;/code&gt;&lt;/pre&gt;

 &lt;p&gt;根据 LinkedIn 文档，请求的 Content-Type 部分需要设置为 application/x-www-form-urlencoded：&lt;/p&gt;

 &lt;pre&gt;  &lt;code&gt;func requestForAccessToken(authorizationCode: String) {  
    ...

    // Add the required HTTP header field.
    request.addValue(&amp;quot;application/x-www-form-urlencoded;&amp;quot;, forHTTPHeaderField: &amp;quot;Content-Type&amp;quot;)
}
&lt;/code&gt;&lt;/pre&gt;

 &lt;p&gt;终于，请求对象的必要配置完成了。现在可以使用它了。&lt;/p&gt;

 &lt;h2&gt;Performing the request&lt;/h2&gt;

 &lt;h2&gt;执行请求&lt;/h2&gt;

 &lt;p&gt;我们将把用于获取访问令牌的请求实现为 NSURLSession 类的对象。通过该对象，创建一个数据任务请求，并在完成处理程序（completion handler）内部处理 LinkedIn 服务器的响应：&lt;/p&gt;

 &lt;pre&gt;  &lt;code&gt;func requestForAccessToken(authorizationCode: String) {  
    ...

    // Initialize a NSURLSession object.
    let session = NSURLSession(configuration: NSURLSessionConfiguration.defaultSessionConfiguration())

    // Make the request.
    let task: NSURLSessionDataTask = session.dataTaskWithRequest(request) { (data, response, error) -&amp;gt; Void in

    }

    task.resume()
}
&lt;/code&gt;&lt;/pre&gt;

 &lt;p&gt;如果请求成功，LinkedIn 服务器将会返回包含访问令牌的 JSON 数据。因此，我们的任务是得到该 JSON 数据，将之转化为字典对象，然后抽取出访问令牌。当然，这一切只有在返回的 HTTP 状态码是 200，也即请求成功时，才能进行。&lt;/p&gt;

 &lt;pre&gt;  &lt;code&gt;func requestForAccessToken(authorizationCode: String) {  
    ...

    // Make the request.
    let task: NSURLSessionDataTask = session.dataTaskWithRequest(request) { (data, response, error) -&amp;gt; Void in
        // Get the HTTP status code of the request.
        let statusCode = (response as! NSHTTPURLResponse).statusCode

        if statusCode == 200 {
            // Convert the received JSON data into a dictionary.
            do {
                let dataDictionary = try NSJSONSerialization.JSONObjectWithData(data!, options: NSJSONReadingOptions.MutableContainers)

                let accessToken = dataDictionary[&amp;quot;access_token&amp;quot;] as! String
            }
            catch {
                print(&amp;quot;Could not convert JSON data into a dictionary.&amp;quot;)
            }
        }
    }

    task.resume()
}
&lt;/code&gt;&lt;/pre&gt;

 &lt;h6&gt;我很好奇&lt;/h6&gt;

 &lt;p&gt;此类操作可能抛出异常，将json 数据的倒换就是加载我们呢自身的SDK，接入信息就是从来不会有很多的考很多事情就是自己做起来用户就是会考
请注意，转化发生在一个 do-catch 语句内部，因为从 Swift 2.0 开始，此类操作可能抛出异常（并不存在错误参数）。在我们的演示 App 中，无需特别考虑出现异常的情况，因此可以向控制器发送一条信息，表示转化失败。如果一切运行顺利，我们就将 JSON 数据（闭包中的数据参数）转化为字典（dataDictionary 对象），之后就可以直接读取访问令牌。&lt;/p&gt;

 &lt;p&gt;接下来做什么呢？将字典保存在用户默认的字典中，然后移除视图控制器：&lt;/p&gt;

 &lt;pre&gt;  &lt;code&gt;func requestForAccessToken(authorizationCode: String) {  
    ...

    // Make the request.
    let task: NSURLSessionDataTask = session.dataTaskWithRequest(request) { (data, response, error) -&amp;gt; Void in
        // Get the HTTP status code of the request.
        let statusCode = (response as! NSHTTPURLResponse).statusCode

        if statusCode == 200 {
            // Convert the received JSON data into a dictionary.
            do {
                ...

                NSUserDefaults.standardUserDefaults().setObject(accessToken, forKey: &amp;quot;LIAccessToken&amp;quot;)
                NSUserDefaults.standardUserDefaults().synchronize()

                dispatch_async(dispatch_get_main_queue(), { () -&amp;gt; Void in
                    self.dismissViewControllerAnimated(true, completion: nil)
                })                
            }
            catch {
                print(&amp;quot;Could not convert JSON data into a dictionary.&amp;quot;)
            }
        }
    }

    task.resume()
}
&lt;/code&gt;&lt;/pre&gt;

 &lt;p&gt;注意，视图控制器会在主线程中移除。请永远牢记，与 UI 相关的改动必须发生在 App 的主线程中，而不是背景线程中。而上面显示的完成处理程序（闭包）则永远在背景线程中执行。&lt;/p&gt;

 &lt;p&gt;Our ultimate goal has been finally achieved! We managed to acquire the access token that will “unlock” several API features.&lt;/p&gt;

 &lt;p&gt;我们的终极目标终于完成啦！得到访问令牌之后，可以“解锁”许多 API 功能。&lt;/p&gt;

 &lt;h2&gt;获得用户主页的 URL&lt;/h2&gt;

 &lt;p&gt;接下来，我们将演示如何用访问令牌获得用户主页的 URL，并在 Safari 浏览器中打开它。然而，在此之前，让我们先讨论一点别的问题。当你启动 App 时，你有两个选择，如下图所示：&lt;/p&gt;

 &lt;p&gt;  &lt;img alt="view-controller-signin" src="http://www.appcoda.com/wp-content/uploads/2015/12/view-controller-signin.png"&gt;&lt;/img&gt;&lt;/p&gt;

 &lt;p&gt;默认情况下，LinkedIn Sign In (LinkedIn 登录)按钮是启用的，而 Get my profile URL（获得我的主页 URL）按钮是禁用的。既然现在已经得到了访问令牌，我们需要启用第二个按钮，同时禁用第一个按钮。这要如何完成呢？&lt;/p&gt;

 &lt;p&gt;一种实现方式是使用委托模式，通过一个委托函数通知 ViewController 类：访问令牌已经得到，请启用第二个按钮。另一种方式是从 WebViewController 类中 Post 一个自定义通知（NSNotification 对象），在 ViewController 类中监听该通知。其实，两种方法都可以实现。但是，还有一种更为简单的方法三：在 ViewController 出现时，检查访问令牌是否存在于用户默认的字典中。如果存在，就禁用登录按钮，启用第二个按钮。否则，就保持不变。&lt;/p&gt;

 &lt;p&gt;此处，我们会在 ViewController 类中实现一个新的小函数来进行检查。请注意，我们还设置了第三个默认隐藏的按钮（也即   &lt;em&gt;btnOpenProfile&lt;/em&gt; IBOutlet 属性）。当得到用户主页的 URL 时，该按钮就会变为可见，并以此 URL 字符串作为其标题（后文会有示例）。&lt;/p&gt;

 &lt;p&gt;Now, let’s define this new function:&lt;/p&gt;

 &lt;p&gt;现在，先来定义这个新函数：&lt;/p&gt;

 &lt;pre&gt;  &lt;code&gt;func checkForExistingAccessToken() {  
    if NSUserDefaults.standardUserDefaults().objectForKey(&amp;quot;LIAccessToken&amp;quot;) != nil {
        btnSignIn.enabled = false
        btnGetProfileInfo.enabled = true
    }
}
&lt;/code&gt;&lt;/pre&gt;

 &lt;p&gt;我们会在 viewWillAppear(_: ) 方法中调用该函数：&lt;/p&gt;

 &lt;pre&gt;  &lt;code&gt;override func viewWillAppear(animated: Bool) {  
    super.viewWillAppear(animated)

    checkForExistingAccessToken()
}
&lt;/code&gt;&lt;/pre&gt;

 &lt;p&gt;之后，App 就能合理地启用或禁用 ViewController 中的两个按钮了。&lt;/p&gt;

 &lt;p&gt;接下来，让我们聚焦于 getProfileInfo(_: ) IBAction 方法。此方法会在 Get my profile URL（获得我的主页 URL）按钮被点击时执行。届时，我们可以向 LinkedIn 服务器发送 GET 请求，使用访问令牌获得用户主页的 URL。此处采用的方法与在上一部分创建获取访问令牌的请求时所用的方法非常相似。&lt;/p&gt;

 &lt;p&gt;现在，让我们从指定请求的 URL 字符串开始吧。请注意，当你不是很确定自己需要什么 URL，或者指定哪些参数时，大可以寻求官方文档的帮助。&lt;/p&gt;

 &lt;pre&gt;  &lt;code&gt;@IBAction func getProfileInfo(sender: AnyObject) {
    if let accessToken = NSUserDefaults.standardUserDefaults().objectForKey(&amp;quot;LIAccessToken&amp;quot;) {
        // Specify the URL string that we&amp;apos;ll get the profile info from.
        let targetURLString = &amp;quot;https://api.linkedin.com/v1/people/~:(public-profile-url)?format=json&amp;quot;
    }
}
&lt;/code&gt;&lt;/pre&gt;

 &lt;p&gt;此处，作为额外措施，我们再一次检查了访问令牌是否存在。通过 if-let 语句，如果访问令牌存在，我们便将其赋值给 accessToken 常量。而且，上面的 URL 会返给我们用户主页的 URL。不要忘记，在执行这类请求之前，必须获得适当的权限。在本演示案例中，我们已经获得了访问用户基本介绍信息的权限。&lt;/p&gt;

 &lt;p&gt;接下来，创建一个新的 NSMutableURLRequest 对象，并以“GET”方法作为理想的 HTTP 方法。此外，还需指定一个 HTTP 头字段，此处将用访问令牌为其赋值。&lt;/p&gt;

 &lt;pre&gt;  &lt;code&gt;@IBAction func getProfileInfo(sender: AnyObject) {
    if let accessToken = NSUserDefaults.standardUserDefaults().objectForKey(&amp;quot;LIAccessToken&amp;quot;) {
        ...

        // Initialize a mutable URL request object.
        let request = NSMutableURLRequest(URL: NSURL(string: targetURLString)!)

        // Indicate that this is a GET request.
        request.HTTPMethod = &amp;quot;GET&amp;quot;

        // Add the access token as an HTTP header field.
        request.addValue(&amp;quot;Bearer \(accessToken)&amp;quot;, forHTTPHeaderField: &amp;quot;Authorization&amp;quot;)        
    }
}
&lt;/code&gt;&lt;/pre&gt;

 &lt;p&gt;最后，再一次地，使用 NSURLSession 与 NSURLSessionDataTask 类创建该请求：&lt;/p&gt;

 &lt;pre&gt;  &lt;code&gt;@IBAction func getProfileInfo(sender: AnyObject) {
    if let accessToken = NSUserDefaults.standardUserDefaults().objectForKey(&amp;quot;LIAccessToken&amp;quot;) {
        ...

        // Initialize a NSURLSession object.
        let session = NSURLSession(configuration: NSURLSessionConfiguration.defaultSessionConfiguration())

        // Make the request.
        let task: NSURLSessionDataTask = session.dataTaskWithRequest(request) { (data, response, error) -&amp;gt; Void in

        }

        task.resume()
    }
}
&lt;/code&gt;&lt;/pre&gt;

 &lt;p&gt;如果请求成功（也即 HTTP 状态码为 200），闭包中的数据参数将会包含服务器返回的 JSON 数据。与之前一样，我们必须将此 JSON 数据转化为字典，才能最终抽取出用户主页的 URL 字符串。&lt;/p&gt;

 &lt;pre&gt;  &lt;code&gt;@IBAction func getProfileInfo(sender: AnyObject) {
    if let accessToken = NSUserDefaults.standardUserDefaults().objectForKey(&amp;quot;LIAccessToken&amp;quot;) {
        ...

        // Make the request.
        let task: NSURLSessionDataTask = session.dataTaskWithRequest(request) { (data, response, error) -&amp;gt; Void in
            // Get the HTTP status code of the request.
            let statusCode = (response as! NSHTTPURLResponse).statusCode

            if statusCode == 200 {
                // Convert the received JSON data into a dictionary.
                do {
                    let dataDictionary = try NSJSONSerialization.JSONObjectWithData(data!, options: NSJSONReadingOptions.MutableContainers)

                    let profileURLString = dataDictionary[&amp;quot;publicProfileUrl&amp;quot;] as! String
                }
                catch {
                    print(&amp;quot;Could not convert JSON data into a dictionary.&amp;quot;)
                }
            }
        }

        task.resume()
    }
}
&lt;/code&gt;&lt;/pre&gt;

 &lt;p&gt;现在，回到之前提到过的一点内容：profileURLString 的值将会赋给 btnOpenProfile 按钮的标题，该按钮也会变成可见。还记得不？我们现在的工作都是在背景线程中进行的，因此，我们还需将其加入主线程中：&lt;/p&gt;

 &lt;pre&gt;  &lt;code&gt;@IBAction func getProfileInfo(sender: AnyObject) {
    if let accessToken = NSUserDefaults.standardUserDefaults().objectForKey(&amp;quot;LIAccessToken&amp;quot;) {
        ...

        // Make the request.
        let task: NSURLSessionDataTask = session.dataTaskWithRequest(request) { (data, response, error) -&amp;gt; Void in
            // Get the HTTP status code of the request.
            let statusCode = (response as! NSHTTPURLResponse).statusCode

            if statusCode == 200 {
                // Convert the received JSON data into a dictionary.
                do {
                    ...

                    dispatch_async(dispatch_get_main_queue(), { () -&amp;gt; Void in
                        self.btnOpenProfile.setTitle(profileURLString, forState: UIControlState.Normal)
                        self.btnOpenProfile.hidden = false

                    })
                }
                catch {
                    print(&amp;quot;Could not convert JSON data into a dictionary.&amp;quot;)
                }
            }
        }

        task.resume()
    }
}
&lt;/code&gt;&lt;/pre&gt;

 &lt;p&gt;现在，运行 App，如果你成功得到了访问令牌，在点击 Get my profile URL（获得我的主页 URL）按钮后不久，你就能看到自己主页的 URL 显示在第三个按钮的位置。&lt;/p&gt;

 &lt;p&gt;  &lt;img alt="t47_9_get_profile_url" src="http://www.appcoda.com/wp-content/uploads/2015/12/t47_9_get_profile_url.png"&gt;&lt;/img&gt;&lt;/p&gt;

 &lt;h2&gt;在 Safari 浏览器查看主页&lt;/h2&gt;

 &lt;p&gt;现在，通过使用访问令牌与 LinkedIn API，我们得到了用户主页的 URL。接下来就该验证其是否正确了。既然已将此 URL 设置为一个按钮的标题，最快的验证方法莫过于打开它了。具体的实现方法箱单简单，因此笔者也不必要多言：&lt;/p&gt;

 &lt;pre&gt;  &lt;code&gt;@IBAction func openProfileInSafari(sender: AnyObject) {
    let profileURL = NSURL(string: btnOpenProfile.titleForState(UIControlState.Normal)!)
    UIApplication.sharedApplication().openURL(profileURL!)
}
&lt;/code&gt;&lt;/pre&gt;

 &lt;p&gt;The last line above will trigger the appearance of Safari, which will load and display the profile webpage.&lt;/p&gt;

 &lt;p&gt;上面最后一行代码会触发 Safari 浏览器，后者会加载并展示用户的主页。&lt;/p&gt;

 &lt;p&gt;  &lt;img alt="t47_10_open_profile" src="http://www.appcoda.com/wp-content/uploads/2015/12/t47_10_open_profile.png"&gt;&lt;/img&gt;&lt;/p&gt;

 &lt;p&gt;你可能已经发现，教程已经步入尾声，却仍然没有提及废除或刷新访问令牌的内容。其实原因如下：关于废除访问令牌，LinkedIn 并未提供任何相关的 API。因此，如果你需要停止 App 发送合法请求，最好的做法应该是从存储机制（数据库，用户默认设置等）中删除之。除此之外，一个访问令牌的有效期大约为60天（在笔者撰写本文之时，官网文档是如此规定的）。LinkedIn 建议，在此时间范围到期之前，刷新访问令牌。刷新的操作非常简单，你只需要从头进行验证与授权过程即可。刷新时，如果访问令牌有效，用户便无需再次输入登录信息，一切都会在后台进行，访问令牌会自动刷新，延迟有效期60天。然而，对于大多数 Web 应用，存在一个常见情况：后台刷新的一个基本前提，是用户已经登录了他们的 LinkedIn 账号，而对于 App 中的内部 Web 视图，这一条件无法满足。因此，在访问令牌快要到期之前，你很可能要让用户再走一遍登录流程。想要了解更多信息，可以点击  &lt;a href="https://developer.linkedin.com/docs/oauth2"&gt;此处&lt;/a&gt;，查看“刷新访问令牌”一节。好了，说再见的时候到了。笔者希望本教程对你有所帮助，并成功向 LinkedIn 发送经过授权的请求。&lt;/p&gt;

 &lt;p&gt;作为参考，你可以从 GitHub 下载本案例  &lt;a href="https://github.com/appcoda/LinkedInSignInDemo"&gt;完整的 Xcode 项目文件&lt;/a&gt;。&lt;/p&gt;

 &lt;p&gt;  &lt;strong&gt;OneAPM    &lt;a href="http://www.oneapm.com/mi/feature.html?utm_source=Community&amp;utm_medium=Article&amp;utm_term=%E5%A6%82%E4%BD%95%E9%80%9A%E8%BF%87%20OAuth%202.0%20%E4%BD%BF%20iOS%20Apps%20%E9%9B%86%E6%88%90%20LinkedIn%20%E7%99%BB%E5%BD%95%E5%8A%9F%E8%83%BD%EF%BC%9F&amp;utm_content=wk321-327&amp;utm_campaign=MiiOSArti&amp;from=jseagsynty"&gt;Mobile Insight &lt;/a&gt;以真实用户体验为度量标准进行    &lt;a href="http://news.oneapm.com/tag/crash/?utm_source=Community&amp;utm_medium=Article&amp;utm_term=%E5%A6%82%E4%BD%95%E9%80%9A%E8%BF%87%20OAuth%202.0%20%E4%BD%BF%20iOS%20Apps%20%E9%9B%86%E6%88%90%20LinkedIn%20%E7%99%BB%E5%BD%95%E5%8A%9F%E8%83%BD%EF%BC%9F&amp;utm_content=wk321-327&amp;utm_campaign=MiiOSArti&amp;from=jseagsynty"&gt;Crash 分析&lt;/a&gt;，监控网络请求及网络错误，提升用户留存。访问    &lt;a href="http://www.oneapm.com/index.html"&gt;OneAPM 官方网站&lt;/a&gt;感受更多应用性能优化体验，想阅读更多技术文章，请访问 OneAPM    &lt;a href="http://news.oneapm.com/?utm_source=Community&amp;utm_medium=Article&amp;utm_term=%E5%A6%82%E4%BD%95%E9%80%9A%E8%BF%87%20OAuth%202.0%20%E4%BD%BF%20iOS%20Apps%20%E9%9B%86%E6%88%90%20LinkedIn%20%E7%99%BB%E5%BD%95%E5%8A%9F%E8%83%BD%EF%BC%9F&amp;utm_content=wk321-327&amp;utm_campaign=MiiOSArti&amp;from=jseagsynty"&gt;官方技术博客&lt;/a&gt;。&lt;/strong&gt;&lt;/p&gt;&lt;div&gt; &lt;a href="https://itindex.net/"  title="IT 资讯"&gt;&lt;img src="https://itindex.net/images/iconWarning.gif" title="IT 资讯" border="0"/&gt; &lt;/a&gt;</description>
      <category>Mobile Insight iOS</category>
      <guid isPermaLink="true">https://itindex.net/detail/55343-oauth-ios-apps</guid>
      <pubDate>Mon, 21 Mar 2016 14:44:02 CST</pubDate>
    </item>
    <item>
      <title>iOS编写高质量代码</title>
      <link>https://itindex.net/detail/54290-ios-%E8%B4%A8%E9%87%8F-%E4%BB%A3%E7%A0%81</link>
      <description>&lt;h1&gt;前言&lt;/h1&gt;
 &lt;p&gt;github：  &lt;a href="https://github.com/koknine"&gt;https://github.com/koknine&lt;/a&gt;&lt;/p&gt;
 &lt;p&gt;这是一篇读书笔记，快速记录各种高效率编程的技巧和方法。这些方法是为了提升编码质量和效率，高质量代码利于后期的维护和更新，毕竟不能一份代码到永远。&lt;/p&gt;
 &lt;p&gt;由于是记录形式，当然不能把整篇内容都写下来，只记录关键性的内容，长期更新。&lt;/p&gt;
 &lt;h1&gt;正文&lt;/h1&gt;
 &lt;p&gt;  &lt;code&gt;Objective-C&lt;/code&gt;使用了消息机制代替调用方法。&lt;/p&gt;
 &lt;p&gt;区别：使用消息结构的语言，其运行时缩影执行的代码由运行环境来决定。而使用函数调用的语言，则又编译器决定。&lt;/p&gt;
 &lt;h3&gt;头文件中少引用其他文件&lt;/h3&gt;
 &lt;p&gt;在头文件中使用  &lt;code&gt;@Class&lt;/code&gt;代替直接引用其他头文件&lt;/p&gt;
 &lt;h3&gt;多使用字面量语法&lt;/h3&gt;
 &lt;pre&gt;  &lt;code&gt;    NSNumber *intNumber = @1;
    NSNumber *floatNumber = @2.5f;
    NSNumber *doubleNumber = @3.1415926;
    NSNumber *boolNumber = @YES;
    NSNumber *charNumber = @&amp;apos;a&amp;apos;;
    
    int a = 3;
    float b = 2.1;
    NSNumber *c = @(a*b);
    
    NSArray *animals = @[@&amp;quot;cat&amp;quot;,@&amp;quot;dog&amp;quot;,@&amp;quot;monkey&amp;quot;];
    
    NSString *dog = animals[1];

    NSDictionary *dataDict = @{ @&amp;quot;firstName&amp;quot; : @&amp;quot;aa&amp;quot;,
                                @&amp;quot;lastName&amp;quot; : @&amp;quot;bb&amp;quot;,
                                @&amp;quot;age&amp;quot; : @20 };
    
    NSString *lastName = dataDict[@&amp;quot;lastName&amp;quot;];
    
    NSMutableArray *mutableArray = animals.mutableCopy;&lt;/code&gt;&lt;/pre&gt;
 &lt;h3&gt;多用类型常量，少用  &lt;code&gt;#define&lt;/code&gt;预处理&lt;/h3&gt;
 &lt;p&gt;如果只在本类使用的常量，使用  &lt;code&gt;static const&lt;/code&gt;关键字来定义常量。&lt;/p&gt;
 &lt;p&gt;如果多个类都需使用到某一常量，则需将常量定义成公开的，具体方式是在类的声明文件中使用  &lt;code&gt;extern const&lt;/code&gt;关键字声明常量，在类的实现文件中使用  &lt;code&gt;const&lt;/code&gt;关键字定义常量，这样任何类只要导入了声明常量的头文件就可以直接使用定义好的常量了。&lt;/p&gt;
 &lt;p&gt;在  &lt;code&gt;.h&lt;/code&gt;文件中声明&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;extern NSString *const XFExternalConst;&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;在  &lt;code&gt;.m文件中&lt;/code&gt;描述&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;NSString *const XFExternalConst = @&amp;quot;ko&amp;quot;;&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;为避免冲突，一般都用类名做前缀。&lt;/p&gt;
 &lt;h3&gt;用枚举表示状态、选项、状态码&lt;/h3&gt;
 &lt;p&gt;枚举只是一种常量命名方式，某个对象所经历的各种状态可以定义为一个枚举集。&lt;/p&gt;
 &lt;p&gt;编译器会为枚举分配一个独有的编号，从0开始每个递增加1.实现枚举所用的数据类型取决于编译器，不过其二进制位的个数必须能完全表示枚举编号才行。&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;enum ConnectionState {
    ConnectionStateDisconnected,
    ConnectionStateConnecting,
    ConnectionStateConnected
};

typedef enum ConnectionState ConnectingState;
&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;还可以不使用编译器所分配的编号，手工指定某个枚举成员所对应的值。&lt;/p&gt;
 &lt;p&gt;还有一种情况应该使用枚举类型，那就是定义选项的时候。若这些选项可以彼此组合，则更应该如此。只要枚举定义的对，各选项之间就可以通过“按位或操作符”来组合。&lt;/p&gt;
 &lt;p&gt;凡是需要以按位或操作来组合的枚举都应该用  &lt;code&gt;NS_OPTIONS&lt;/code&gt;宏，如果没有组合需求，就用  &lt;code&gt;NS_ENUM&lt;/code&gt;宏。&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;typedef NS_ENUM(NSInteger, UIViewAnimationTransition) {
    UIViewAnimationTransitionNone,
    UIViewAnimationTransitionFlipFromLeft,
    UIViewAnimationTransitionFlipFromRight,
    UIViewAnimationTransitionCurlUp,
    UIViewAnimationTransitionCurlDown,
};

typedef NS_OPTIONS(NSUInteger, UIViewAutoresizing) {
    UIViewAutoresizingNone                 = 0,
    UIViewAutoresizingFlexibleLeftMargin   = 1 &amp;lt;&amp;lt; 0,
    UIViewAutoresizingFlexibleWidth        = 1 &amp;lt;&amp;lt; 1,
    UIViewAutoresizingFlexibleRightMargin  = 1 &amp;lt;&amp;lt; 2,
    UIViewAutoresizingFlexibleTopMargin    = 1 &amp;lt;&amp;lt; 3,
    UIViewAutoresizingFlexibleHeight       = 1 &amp;lt;&amp;lt; 4,
    UIViewAutoresizingFlexibleBottomMargin = 1 &amp;lt;&amp;lt; 5
};&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;枚举在  &lt;code&gt;switch&lt;/code&gt;语句里面的时候，不需要加  &lt;code&gt;default&lt;/code&gt;分支。&lt;/p&gt;
 &lt;h3&gt;属性的概念&lt;/h3&gt;
 &lt;p&gt;基本方法就不描述了。&lt;/p&gt;
 &lt;p&gt;  &lt;code&gt;@dynamic&lt;/code&gt;关键字，表示不要自动创建实现属性所有的实例变量，也不要为其创建存取方法。即使编译器没有发现定义存取方法，也不会报错，它相信这些方法能在运行期找到。&lt;/p&gt;
 &lt;p&gt;属性的四种特质&lt;/p&gt;
 &lt;ul&gt;  &lt;li&gt;   &lt;p&gt;原子性&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;
 &lt;p&gt;默认情况下，编译器合成的方法会锁定机制保持  &lt;code&gt;atomic&lt;/code&gt;。如果使用  &lt;code&gt;nonatomic&lt;/code&gt;，则不使用同步锁。&lt;/p&gt;
 &lt;ul&gt;  &lt;li&gt;   &lt;p&gt;读写权限&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;
 &lt;p&gt;  &lt;code&gt;readwrite&lt;/code&gt;的属性具有  &lt;code&gt;getter&lt;/code&gt;和  &lt;code&gt;setter&lt;/code&gt;方法&lt;/p&gt;
 &lt;p&gt;  &lt;code&gt;readonly&lt;/code&gt;的属性仅具有  &lt;code&gt;getter&lt;/code&gt;方法&lt;/p&gt;
 &lt;ul&gt;  &lt;li&gt;   &lt;p&gt;内存管理语义&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;
 &lt;p&gt;  &lt;code&gt;assign&lt;/code&gt;只针对“纯量类型”，比如  &lt;code&gt;CGFloat&lt;/code&gt;或者  &lt;code&gt;NSInteger&lt;/code&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;code&gt;strong&lt;/code&gt;表示该属性定义了一种  &lt;code&gt;拥有关系&lt;/code&gt;。为这种属性设置新值时，设置方法会先保留新值，并释放旧值，然后将新值设置上去&lt;/p&gt;
 &lt;p&gt;  &lt;code&gt;weak&lt;/code&gt;表示该属性定义另一种  &lt;code&gt;非拥有关系&lt;/code&gt;。为这种属性设置新值时，设置方法既不保留新值，也不释放旧值。和  &lt;code&gt;assign&lt;/code&gt;类似，然而在属性所指的对象遭到摧毁时，属性值也会清空&lt;/p&gt;
 &lt;p&gt;  &lt;code&gt;unsafe_unretained&lt;/code&gt;这个和  &lt;code&gt;assign&lt;/code&gt;相同，但是它适用于  &lt;code&gt;对象类型&lt;/code&gt;，该特质表达一种  &lt;code&gt;非拥有关系&lt;/code&gt;，当目标对象遭到摧毁时，属性值不会自动清空，是不安全的&lt;/p&gt;
 &lt;p&gt;  &lt;code&gt;copy&lt;/code&gt;表达的所属关系和  &lt;code&gt;strong&lt;/code&gt;类型。然后设置方法并不保留新值，而是将其拷贝。当属性类型为  &lt;code&gt;NSString *&lt;/code&gt;时，经常用此特质来保护其封装性，因为传递给设置方法的新值可能指向一个  &lt;code&gt;NSMutableString&lt;/code&gt;类的实例。如果不是拷贝的花，那么设置完属性以后，字符串的值可能会在对象不知情的情况下遭人更改。所以这个时候需要拷贝一份不可变的字符串。&lt;/p&gt;
 &lt;ul&gt;  &lt;li&gt;   &lt;p&gt;方法名&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;
 &lt;p&gt;  &lt;code&gt;getter=&amp;lt;name&amp;gt;&lt;/code&gt; 指定  &lt;code&gt;getter&lt;/code&gt;的方法名。如果属性是  &lt;code&gt;Boolean&lt;/code&gt;型，在方法名加上  &lt;code&gt;is&lt;/code&gt;前缀，就可以用这个方法来指定。&lt;/p&gt;
 &lt;p&gt;  &lt;code&gt;setter=&amp;lt;name&amp;gt;&lt;/code&gt; 指定  &lt;code&gt;setter&lt;/code&gt;的方法名。这个不常见。&lt;/p&gt;
 &lt;h3&gt;在对象内部尽量直接访问实例变量&lt;/h3&gt;
 &lt;p&gt;懒加载是重写  &lt;code&gt;getter&lt;/code&gt;方法&lt;/p&gt;
 &lt;h3&gt;理解  &lt;code&gt;对象等同性&lt;/code&gt;的概念&lt;/h3&gt;
 &lt;p&gt;按照  &lt;code&gt;==&lt;/code&gt;操作符比较出来的结果未必是我们想要的，因为该操作符比较出来的是两个指针本身，而不是指针所指的对象。应该是用  &lt;code&gt;NSObject&lt;/code&gt;协议中声明的  &lt;code&gt;isEqual&lt;/code&gt;方法来判断两个对象的等同性。来办来说两个类型不同的对象总是不相等的。&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;NSString *oneStr = @&amp;quot;aaa 21&amp;quot;;
NSString *twoStr = [NSString stringWithFormat:@&amp;quot;aaa %d&amp;quot;,21];
BOOL equalA = (oneStr == twoStr);//NO
BOOL equalB = [oneStr isEqual:twoStr];//YES
BOOL equalC = [oneStr isEqualToString:twoStr];//YES
&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;两个用于判断等同性的关键方法&lt;/p&gt;
 &lt;ul&gt;  &lt;li&gt;   &lt;p&gt;(BOOL)isEqual:(id)object;    &lt;br /&gt;@property (readonly) NSUInteger hash;&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;
 &lt;p&gt;默认实现是：当且仅当其指针值完全相等时，这两个对象才相等。&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;几个要点&lt;/strong&gt;&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;   &lt;p&gt;若想监测对象的等同性，提供    &lt;code&gt;isEqual:&lt;/code&gt;与 hash 方法&lt;/p&gt;&lt;/li&gt;
  &lt;li&gt;   &lt;p&gt;相同的对象必须具有相同的哈希码，但是两个哈希码相同的对象却未必相同&lt;/p&gt;&lt;/li&gt;
  &lt;li&gt;   &lt;p&gt;不要盲目逐个检测每条属性，而是应该依照具体需求来制定监测方案&lt;/p&gt;&lt;/li&gt;
  &lt;li&gt;   &lt;p&gt;编写 hash 方法时，应该是用计算速度快而且碰撞低的算法&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
 &lt;h3&gt;以“类族模式”隐藏实现细节&lt;/h3&gt;
 &lt;p&gt;核心套路就是类似  &lt;code&gt;UIButton&lt;/code&gt;，创建的时候传入一个枚举值，根据枚举值来创建子类。（这里的笔记是我看懂以后写的，不知道的朋友先搜索一下  &lt;code&gt;工厂模式&lt;/code&gt;，其实就是那个意思）&lt;/p&gt;
 &lt;h3&gt;在既有类中使用关联对象存放自定义数据&lt;/h3&gt;
 &lt;p&gt;有时候需要在对象中存放相关信息，这时候我们通常会从对象所属的类中继承一个子类，然后改用这个子类对象。然而并非所有情况下都能这么做，有时候类的实例可能由某种机制创建。  &lt;code&gt;Objective-C&lt;/code&gt;有一种强大机制叫  &lt;code&gt;关联对象&lt;/code&gt;。&lt;/p&gt;
 &lt;p&gt;这种机制要小心使用，因为会使代码失控。&lt;/p&gt;
 &lt;h3&gt;理解  &lt;code&gt;objc_msgSend&lt;/code&gt;的作用&lt;/h3&gt;
 &lt;p&gt;原型&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;void objc_msgSend(id self, SEL cmd, ...)&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;一个例子&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;id returnValue = [someObject messageName:parameter];&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;编译器会把它转换为以下函数&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;id returnValue = objc_msgSend(someObject,@selector(messageName:),parameter);&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;为了完成调用方法，该方法需要在接受者所属的类中搜寻其  &lt;code&gt;方法列表&lt;/code&gt;，如果能找到，就跳转过去。如果找不到，就沿着继承体系向上继续查找，等找到合适的再跳转。如果最终还是找不到，就执行  &lt;code&gt;消息转发&lt;/code&gt;的操作。&lt;/p&gt;
 &lt;p&gt;一些  &lt;strong&gt;边界情况&lt;/strong&gt;，则交由另一些函数处理&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;   &lt;p&gt;    &lt;code&gt;objc_msgSend_stret&lt;/code&gt; 如果待发送的消息要返回结构体，可交此函数处理。&lt;/p&gt;&lt;/li&gt;
  &lt;li&gt;   &lt;p&gt;    &lt;code&gt;objc_msgSend_fpret&lt;/code&gt; 如果消息返回的是浮点数，可交由此函数处理。&lt;/p&gt;&lt;/li&gt;
  &lt;li&gt;   &lt;p&gt;    &lt;code&gt;ojbc_msgSendSuper&lt;/code&gt; 如果要给超类发送消息，例如    &lt;code&gt;[super message:parameter]&lt;/code&gt;，那么就就交由此函数处理。&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
 &lt;h3&gt;理解消息转发机制&lt;/h3&gt;
 &lt;p&gt;消息转发分为两大阶段。&lt;/p&gt;
 &lt;p&gt;第一阶段先问接受者，所属的类，看其是否能动态添加方法，以处理当前这个  &lt;code&gt;unknown selector&lt;/code&gt;，这称为  &lt;code&gt;dynamic method resolution&lt;/code&gt;。&lt;/p&gt;
 &lt;p&gt;第二阶段涉及  &lt;code&gt;full forwarding mechanism&lt;/code&gt;。如果运行期系统已经把第一阶段执行完了，那么接受者自己就无法再以动态新增方法的手段来响应包含该  &lt;code&gt;selector&lt;/code&gt;的消息了。此时，运行期系统会请求接受者以其他手段来处理与消息相关的方法调用。然后又分两部。&lt;/p&gt;
 &lt;p&gt;首先，让接受者看看有没有其他对象能处理这条消息。如果有，就转发给那个对象。&lt;/p&gt;
 &lt;p&gt;如果没有，就会启动完整的消息转发机制，运行期系统会把消息有关的全部细节封装到  &lt;code&gt;NSInvocation&lt;/code&gt;对象中，再给接受者最后一次机会，让它设法解决当前还未处理的这条消息。&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;动态方法解析&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;对象收到无法解读的消息后，先调用&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;+ (BOOL)resolveInstanceMethod:(SEL)sel &lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;该方法的参数就是那个未知的  &lt;code&gt;selector&lt;/code&gt;，返回  &lt;code&gt;Boolean&lt;/code&gt;类型，表示这个类是否能新增一个实例方法用来处理这个  &lt;code&gt;selector&lt;/code&gt;。在继续走下去之前，这有个机会新增一个处理的方法。&lt;/p&gt;
 &lt;p&gt;如果尚未实现的不是实例方法而是类方法，则调用&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;+ (BOOL)resolveClassMethod:(SEL)sel&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;使用他们的前提是，相关方法的实现代码已经写好，只等着运行的时候动态插入在类里面。&lt;/p&gt;
 &lt;p&gt;这个常常用来实现  &lt;code&gt;@dynamic&lt;/code&gt;属性。&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;后备接收者&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;当前接收者还有第二次机会处理，能不能把消息转发给其他接收者&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;- (id)forwardingTargetForSelector:(SEL)aSelector&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;找得到就返回对象，找不到就返回  &lt;code&gt;nil&lt;/code&gt;。&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;完整的消息转发&lt;/strong&gt;&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;- (void)forwardInvocation:(NSInvocation *)anInvocation&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;先创建  &lt;code&gt;NSInvocation&lt;/code&gt;对象，把尚未处理的那条消息有关的全部细节都封在其中。此对象包含  &lt;code&gt;selector&lt;/code&gt;，  &lt;code&gt;target&lt;/code&gt;以及参数。&lt;/p&gt;
 &lt;p&gt;继承体系中的每个类都有机会处理此调用请求，直到  &lt;code&gt;NSObject&lt;/code&gt;。如果还没有找到，那么该方法还会继续调用  &lt;code&gt;doesNotRecognizeSelector:&lt;/code&gt;抛出异常，此异常表示最终未能处理。&lt;/p&gt;
 &lt;p&gt;这个机制属于底层机制，可以动态注入方法，甚至之前的可以动态注入属性，云后端服务商可以说基本就靠这个套路，通过KVC的样子往类里面添加属性。&lt;/p&gt;
 &lt;h3&gt;用方法调配技术调试黑盒方法&lt;/h3&gt;
 &lt;p&gt;黑科技。&lt;/p&gt;
 &lt;p&gt;IMP指针，改方法实现，替换系统方法，可以多添加日志打印。&lt;/p&gt;
 &lt;h3&gt;类对象&lt;/h3&gt;
 &lt;p&gt;OC 是一门极其动态的语言。&lt;/p&gt;
 &lt;p&gt;每个 OC 对象实例都是指向某块内存数据的指针。&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;typedef struct objc_object {
    Class isa;
} *id;&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;每个对象结构体的首个成员是  &lt;code&gt;Class&lt;/code&gt;类的变量。该变量定义了对象所属的类，通常称为  &lt;code&gt;is a&lt;/code&gt;指针。&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;typedef struct objc_class *Class;
struct objc_class {
    Class isa;
    Class super_class;
    const char *name;
    long version;
    long info;
    long instance_size;
    struct objc_ivar_list *ivars;
    struct objc_method_list **methodLists;
    struct objc_cache *cache;
    struct objc_protocol_list *protocols;
};&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;此结构体存放类的  &lt;code&gt;元数据&lt;/code&gt;，例如类的实例实现了几个方法，具备多少个实例变量等信息。  &lt;br /&gt;首个变量是  &lt;code&gt;isa&lt;/code&gt;指针，说明  &lt;code&gt;Class&lt;/code&gt;本身也是 OC 对象。&lt;/p&gt;
 &lt;p&gt;  &lt;code&gt;super_class&lt;/code&gt;定义了本类的超类。类对象所属的类型是另一个类，叫做  &lt;code&gt;超类&lt;/code&gt;。&lt;/p&gt;
 &lt;p&gt;每个类仅有一个  &lt;code&gt;类对象&lt;/code&gt;，而每个  &lt;code&gt;类对象&lt;/code&gt;仅有一个与之相关的  &lt;code&gt;元类&lt;/code&gt;。&lt;/p&gt;
 &lt;p&gt;  &lt;code&gt;class&lt;/code&gt;方法所返回的类表示发起代理的对象，而非接受代理的对象。&lt;/p&gt;
 &lt;h3&gt;用前缀避免命名空间冲突&lt;/h3&gt;
 &lt;p&gt;开发者可能会忽视另外一个容易引发命名冲突的地方，那就是类的实现文件中所用的纯 C 函数及全局变量。&lt;/p&gt;
 &lt;h3&gt;提供全能初始化方法&lt;/h3&gt;
 &lt;p&gt;所有对象均要初始化。&lt;/p&gt;
 &lt;p&gt;提供一个全能初始化方法，其他的几种初始化方法调用它。&lt;/p&gt;
 &lt;p&gt;如果全能初始化方法与超类不同，则需覆写超类中的对应方法。&lt;/p&gt;
 &lt;h3&gt;实现  &lt;code&gt;description&lt;/code&gt;方法&lt;/h3&gt;
 &lt;p&gt;重写  &lt;code&gt;- (NSString *)description&lt;/code&gt;&lt;/p&gt;
 &lt;p&gt;控制台  &lt;code&gt;- (NSString *)debugDescription&lt;/code&gt;&lt;/p&gt;
 &lt;h3&gt;尽量使用不可变对象&lt;/h3&gt;
 &lt;p&gt;尽量把对外公布出来的属性设为只读，只在必要时候对外公布。&lt;/p&gt;
 &lt;p&gt;有时候想修改封装在对象内部的数据，但是却不想让外人所改动。这种情况需要将  &lt;code&gt;readonly&lt;/code&gt;在  &lt;code&gt;.m&lt;/code&gt;文件中重新生成  &lt;code&gt;readwrite&lt;/code&gt;。但是为了避免产生意外，需要在必要时通过  &lt;code&gt;dispatch queue&lt;/code&gt;来实现。&lt;/p&gt;
 &lt;p&gt;不要把可变的内容作为属性公开，而是提供相关方法，以此修改对象中的可变内容。&lt;/p&gt;
 &lt;h3&gt;使用清洗而协调的命名方式&lt;/h3&gt;
 &lt;p&gt;驼峰命名法&lt;/p&gt;
 &lt;p&gt;方法与变量以  &lt;code&gt;小写字母&lt;/code&gt;开头&lt;/p&gt;
 &lt;p&gt;类名以  &lt;code&gt;大写字母&lt;/code&gt;开头&lt;/p&gt;
 &lt;p&gt;不要使用  &lt;code&gt;str&lt;/code&gt;这种简称，而用  &lt;code&gt;string&lt;/code&gt;这样的全称&lt;/p&gt;
 &lt;p&gt;  &lt;code&gt;Boolean&lt;/code&gt;属性应该加  &lt;code&gt;is&lt;/code&gt;前缀，如果某方法返回非属性的  &lt;code&gt;Boolean&lt;/code&gt;值，应该根据功能选用  &lt;code&gt;has&lt;/code&gt;或者  &lt;code&gt;is&lt;/code&gt;当前缀&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;类与协议的命名&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;为类与协议的名称加上前缀，以避免命名空间的冲突&lt;/p&gt;
 &lt;p&gt;委托一般使用委托的发起方名称后面跟一个  &lt;code&gt;Delegate&lt;/code&gt;&lt;/p&gt;
 &lt;h3&gt;为私有方法名加前缀&lt;/h3&gt;
 &lt;p&gt;一般可以使用  &lt;code&gt;p_&lt;/code&gt;作为前缀，表示私有方法&lt;/p&gt;
 &lt;p&gt;不要用一个单独的下划线作为私有方法的前缀&lt;/p&gt;
 &lt;h3&gt;理解  &lt;code&gt;Objective-C&lt;/code&gt;错误模型&lt;/h3&gt;
 &lt;p&gt;异常  &lt;code&gt;NSException&lt;/code&gt;应该用于极其严重的错误，比如编写了某个抽象基类，它的正确用法是先从重继承一个子类，然后再使用这个子类。在这种情况下，如果有人直接使用了这个抽象基类，那么可以考虑抛出异常。&lt;/p&gt;
 &lt;p&gt;  &lt;code&gt;NSError&lt;/code&gt;的用法很灵活，封装了三条信息&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;   &lt;p&gt;    &lt;code&gt;Error domain&lt;/code&gt; 错误范围，类型为字符串    &lt;br /&gt;错误发生的范围，通常用一个特有的全局变量来定义。&lt;/p&gt;&lt;/li&gt;
  &lt;li&gt;   &lt;p&gt;    &lt;code&gt;Error code&lt;/code&gt; 错误码，类型为证书    &lt;br /&gt;独有的错误代码。这种错误通常采用    &lt;code&gt;enum&lt;/code&gt;来定义，比如 HTTP 请求返回的状态码。&lt;/p&gt;&lt;/li&gt;
  &lt;li&gt;   &lt;p&gt;    &lt;code&gt;User info&lt;/code&gt; 用户信息，类型为字典    &lt;br /&gt;有关此错误的额外信息，其中或许包含一段    &lt;em&gt;本地化描述&lt;/em&gt;，或许还包含导致该错误发生的另外一个错误，经由此种信息，可将相关错误传承一条    &lt;code&gt;chain of errors&lt;/code&gt;。&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
 &lt;h3&gt;理解  &lt;code&gt;NSCopying&lt;/code&gt;协议&lt;/h3&gt;
 &lt;p&gt;使用对象时经常需要拷贝它。如果想令自己的类支持拷贝操作&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;- (id)copyWithZone:(NSZone *)zone;&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;为什么会出现  &lt;code&gt;NSZone&lt;/code&gt;，以前开发的时候，会把内存分成不同的  &lt;code&gt;zone&lt;/code&gt;，而对象会创建在某个区里面。现在不用了，每个程序只有一个  &lt;code&gt;default zone&lt;/code&gt;&lt;/p&gt;
 &lt;p&gt;另外一个  &lt;code&gt;NSMutableCopying&lt;/code&gt;协议，返回可变的副本&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;- (id)mutableCopyWithZone:(NSZone *)zone;&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;  &lt;strong&gt;深拷贝&lt;/strong&gt;  &lt;br /&gt;在拷贝对象自身时，将底层数据也一并复制过去&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;浅拷贝&lt;/strong&gt;  &lt;br /&gt;  &lt;code&gt;Foundation&lt;/code&gt;框架中所有的容器类默认情况下执行浅拷贝，只拷贝对象本身，不复制数据  &lt;br /&gt;因为不是所有对象都能拷贝，而且调用者也未必需要都一一拷贝。&lt;/p&gt;
 &lt;h3&gt;通过委托与数据源协议进行对象间通信&lt;/h3&gt;
 &lt;p&gt;委托属性要定义成  &lt;code&gt;weak&lt;/code&gt;，因为两者之间必须为  &lt;code&gt;非拥有关系&lt;/code&gt;&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;- (BOOL)respondsToSelector:(SEL)aSelector;&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;也可以用协议定义一套接口，令某类从该接口获取所需的数据。委托模式的这种用法是向类提供数据，所以成为  &lt;code&gt;dataSource&lt;/code&gt;。在这种模式中，信息从数据源流向类。而在常规的代理模式中，信息则从类流向受委托者。&lt;/p&gt;
 &lt;h3&gt;将类的实现代码分散到便于管理的数个分类之中&lt;/h3&gt;
 &lt;p&gt;把一个类中的几个不同模块方法写到别的文件中，合理使用  &lt;code&gt;category&lt;/code&gt;。&lt;/p&gt;
 &lt;h3&gt;不要在分类中声明属性&lt;/h3&gt;
 &lt;p&gt;除了  &lt;code&gt;extension&lt;/code&gt;外，其他的分类都无法向类中新增实例变量&lt;/p&gt;
 &lt;p&gt;声明为  &lt;code&gt;@dynamic&lt;/code&gt;，然后动态添加&lt;/p&gt;
 &lt;h3&gt;使用  &lt;code&gt;extension&lt;/code&gt;隐藏实现细节&lt;/h3&gt;
 &lt;h3&gt;通过协议提供匿名对象&lt;/h3&gt;
 &lt;p&gt;使用匿名对象来隐藏类型名称&lt;/p&gt;
 &lt;h3&gt;理解引用计数&lt;/h3&gt;
 &lt;p&gt;  &lt;code&gt;retain&lt;/code&gt; 增计数  &lt;br /&gt;  &lt;code&gt;release&lt;/code&gt; 减计数  &lt;br /&gt;  &lt;code&gt;autorelease&lt;/code&gt; 待稍后清理  &lt;code&gt;autorelease pool&lt;/code&gt;时，再减少计数&lt;/p&gt;
 &lt;p&gt;对象创建出来时，其保留计数至少为1&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;自动释放池&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;循环引用&lt;/strong&gt;&lt;/p&gt;
 &lt;h3&gt;以ARC简化引用计数&lt;/h3&gt;
 &lt;p&gt;若方法名以下列词语开头，则返回的对象归调用者所有&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;   &lt;p&gt;alloc&lt;/p&gt;&lt;/li&gt;
  &lt;li&gt;   &lt;p&gt;new&lt;/p&gt;&lt;/li&gt;
  &lt;li&gt;   &lt;p&gt;copy&lt;/p&gt;&lt;/li&gt;
  &lt;li&gt;   &lt;p&gt;mutableCopy&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;在应用程序中，可用下列修饰符来改变局部变量与实例变量的语义&lt;/p&gt;
 &lt;p&gt;  &lt;code&gt;__strong&lt;/code&gt; 默认语义，保留这个值&lt;/p&gt;
 &lt;p&gt;  &lt;code&gt;__unsafe_unretained&lt;/code&gt; 不保留这个值，这么做可能不安全，因为等到再次使用变量时，其对象可能已经回收了&lt;/p&gt;
 &lt;p&gt;  &lt;code&gt;__weak&lt;/code&gt; 不保留这个值，但是变量可以安全使用，因为如果系统把这个对象回收了，那么变量也会自动清空&lt;/p&gt;
 &lt;p&gt;  &lt;code&gt;__autoreleasing&lt;/code&gt; 把对象  &lt;em&gt;按引用传递&lt;/em&gt;给方法时，使用这个特殊的修饰符，此值在方法返回时自动释放&lt;/p&gt;
 &lt;p&gt;比如，想令实例变量的语义与不使用 ARC 时相同，可以使用  &lt;code&gt;__weak&lt;/code&gt;或  &lt;code&gt;__unsafe_unretained&lt;/code&gt;修饰符&lt;/p&gt;
 &lt;p&gt;block 块会自动保留其所捕获的全部对象，而如果这其中有某个对象又保留了块本身，那么就可能导致循环引用，可以用  &lt;code&gt;__weak&lt;/code&gt;局部变量来打破这种循环引用&lt;/p&gt;
 &lt;p&gt;注意：  &lt;code&gt;CoreFoundation&lt;/code&gt;对象不归 ARC 管理，开发者必须适时调用  &lt;code&gt;CFRetain/CFRelease&lt;/code&gt;&lt;/p&gt;
 &lt;h3&gt;在  &lt;code&gt;dealloc&lt;/code&gt;方法中只释放引用并解除监听&lt;/h3&gt;
 &lt;p&gt;把原来配置过的观测行为都清除掉，如果使用  &lt;code&gt;NSNotificationCenter&lt;/code&gt;给此对象注册过某种通知，那么一般应该在这里注销&lt;/p&gt;
 &lt;p&gt;（未完待续）&lt;/p&gt;
 &lt;h1&gt;总结&lt;/h1&gt;
 &lt;p&gt;纯属个人笔记，特别是底层机制很有作用，如今  &lt;code&gt;iOS&lt;/code&gt;开发不再仅仅是把一个内容展现出来，里面还有涉及到各种安全性能，了解根本才是持续发展之道。&lt;/p&gt;
&lt;div&gt; &lt;a href="https://itindex.net/"  title="IT 资讯"&gt;&lt;img src="https://itindex.net/images/iconWarning.gif" title="IT 资讯" border="0"/&gt; &lt;/a&gt;</description>
      <category>ios objective-c</category>
      <guid isPermaLink="true">https://itindex.net/detail/54290-ios-%E8%B4%A8%E9%87%8F-%E4%BB%A3%E7%A0%81</guid>
      <pubDate>Tue, 01 Sep 2015 14:24:25 CST</pubDate>
    </item>
    <item>
      <title>浅谈 iOS 线程</title>
      <link>https://itindex.net/detail/54113-ios-%E7%BA%BF%E7%A8%8B</link>
      <description>&lt;ul&gt;
  &lt;li&gt;
   &lt;p&gt;通常主线程和其他线程的使用场景    &lt;br /&gt;
主线程一般用于    &lt;br /&gt;
 绘制UI    &lt;br /&gt;
 响应用户操作等    &lt;br /&gt;
其他线程用于    &lt;br /&gt;
 网络请求    &lt;br /&gt;
 解析网络返回等&lt;/p&gt;

   &lt;p&gt;Tips: 解压、打开 Zip 包，读写较大文件的操作也不宜放在主线程里。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;一般异步网络请求中会有一个 completionBlock ,这个 completionBlock 是在主线程中被调用的。  &lt;br /&gt;
所以，可能消耗大量时间的代码（例如上面提到的处理 Zip 包的方法）也不宜放在这些 block 中。如下面的代码所示：&lt;/p&gt;

 &lt;pre&gt;  &lt;code&gt;[request setCompletionBlock:^{
    NSLog(@&amp;quot;Zip file downloaded.&amp;quot;);
    NSData *data = [request responseData];
    [self processZip:data sourceURL:sourceURL]; // Ack - heavy work on main thread!
}];
&lt;/code&gt;&lt;/pre&gt;

 &lt;p&gt;那么，如果在blcok 中有数据需要花费大量时间处理，我们可以使用 Grand Central Dispatch（GCD）系统，让数据在后台被处理，或者使用GCD在后台执行这些代码。简单来说，如果希望有代码在后台执行，只需要调用 dispatch_async,然后把代码扔进去。  &lt;br /&gt;
接来下的事就交给 GCD 啦，如果需要，GCD 会创建一个新的线程；或者 GCD 会重用一个已经存在的可用线程。&lt;/p&gt;

 &lt;p&gt;当你调用 dispatch_async 的时候，就是将代码传入了一个 dispatch 队列，这个队列里存储了所有你传入的 block。&lt;/p&gt;

 &lt;p&gt;我们可以创建自己的 dispatch 队列（通过 dispatch _create 方法），也可以为了主线程的到一个特殊的队列（通过 dispatch_get_main_queue）。&lt;/p&gt;

 &lt;p&gt;一个 dispatch 队列（queue）是按次序排设好的，这就意味着队列中每次只有一个 block 的代码被执行。这个特性非常方便，我们可以用它保护共享数据（shared data）。  &lt;br /&gt;
关于保护数据的核心思想是：你需要设置好你的代码，使得一个特定的数据结构只能被一个特定的正在运行的 dispatch 队列所访问。因为 dispatch 队列按次序执行 block，那么每次就只有一个block 能够访问该数据结构。&lt;/p&gt;

 &lt;ul&gt;
  &lt;li&gt;GCD 实践&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;预先声明一个dispatch_queue，添加一个 dispatch queue 实例&lt;/p&gt;

 &lt;pre&gt;  &lt;code&gt;// Add new instance variable
dispatch_queue_t backgroundQueue;
&lt;/code&gt;&lt;/pre&gt;

 &lt;p&gt;  &lt;em&gt;关于 dispatch_queue_t 苹果官方文档说明：一个 dispatch queue 是一个用来注册将要被按顺序执行的代码块的轻量级对象。&lt;/em&gt;&lt;/p&gt;

 &lt;p&gt;创建dispatch queue  &lt;br /&gt;
backgroundQueue = dispatch_queue_create(&amp;quot;com.razeware.imagegrabber.bgqueue&amp;quot;, NULL);  &lt;br /&gt;
初始化前面说声明的 dispatch queue，并给这个dispatch queue 命名。上面填 null 参数的地方还可以填 DISPATCH_QUEUE_SERIAL（等同于null）或者 DISPATCH_QUEUE_CONCURRENT。  &lt;br /&gt;
DISPATCH_QUEUE_SERIAL  ：按先进先出原则执行 block 的 dispatch 队列。  &lt;br /&gt;
DISPATCH_QUEUE_CONCURRENT：执行当前 block 的 dispatch 队列。虽然该队列执行的是当前的代码，我们也可以使用 barrier block 在队列中创建同步点。&lt;/p&gt;

 &lt;p&gt;使用刚才创建的 dispatch queue&lt;/p&gt;

 &lt;pre&gt;  &lt;code&gt;- (void)process {   
    dispatch_async(backgroundQueue, ^(void) {
        [self processHtml];
    });   
}
&lt;/code&gt;&lt;/pre&gt;

 &lt;p&gt;原本 - (void)process 中的代码是直接执行 [self processHtml]; processHTMl 方法阻塞了主线程，而现在通过调用 dispatch_async 我们可以使得 processHtml 在我们创建的 backgroundQueue 中在后台运行。&lt;/p&gt;

 &lt;ul&gt;
  &lt;li&gt;关于NSOperations 和 operation 序列&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;NSOperations 其实就是基于 GCD 实现的，使用NSOperation 的时候其实也就是在使用 GCD。但是NSOperation 给我们提供了更多更方便的功能，我们可以操作一些 operation，这些operation 依赖于其他 operation。还可以在 submit block 之后重新对 operation queue 进行排序。&lt;/p&gt;

 &lt;p&gt;Bingo！  &lt;br /&gt;
之后应该会总结一些关于 GCD 使用的更详细的文章。&lt;/p&gt;
&lt;div&gt; &lt;a href="https://itindex.net/"  title="IT 资讯"&gt;&lt;img src="https://itindex.net/images/iconWarning.gif" title="IT 资讯" border="0"/&gt; &lt;/a&gt;</description>
      <category>ios objective-c thread gcd</category>
      <guid isPermaLink="true">https://itindex.net/detail/54113-ios-%E7%BA%BF%E7%A8%8B</guid>
      <pubDate>Sat, 08 Aug 2015 00:23:30 CST</pubDate>
    </item>
    <item>
      <title>YYCache 设计思路</title>
      <link>https://itindex.net/detail/54588-yycache-%E8%AE%BE%E8%AE%A1</link>
      <description>&lt;p&gt;  &lt;a href="http://blog.ibireme.com/wp-content/uploads/2015/10/cache_all_the_things.jpg"&gt;   &lt;img alt="cache_all_the_things" height="300" src="http://blog.ibireme.com/wp-content/uploads/2015/10/cache_all_the_things.jpg" width="400"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;
 &lt;p&gt;iOS 开发中总会用到各种缓存，最初我是用的一些开源的缓存库，但到总觉得缺少某些功能，或某些 API 设计的不够好用。YYCache (  &lt;a href="https://github.com/ibireme/YYCache" target="_blank"&gt;https://github.com/ibireme/YYCache&lt;/a&gt;) 是我新造的一个轮子，下面说一下这个轮子的设计思路。&lt;/p&gt;
 &lt;h2&gt;内存缓存&lt;/h2&gt;
 &lt;p&gt;通常一个缓存是由内存缓存和磁盘缓存组成，内存缓存提供容量小但高速的存取功能，磁盘缓存提供大容量但低速的持久化存储。相对于磁盘缓存来说，内存缓存的设计要更简单些，下面是我调查的一些常见的内存缓存。&lt;/p&gt;
 &lt;p&gt;NSCache 是苹果提供的一个简单的内存缓存，它有着和 NSDictionary 类似的 API，不同点是它是线程安全的，并且不会 retain key。我在测试时发现了它的几个特点：NSCache 底层并没有用 NSDictionary 等已有的类，而是直接调用了 libcache.dylib，其中线程安全是由 pthread_mutex 完成的。另外，它的性能和 key 的相似度有关，如果有大量相似的 key (比如 &amp;quot;1&amp;quot;, &amp;quot;2&amp;quot;, &amp;quot;3&amp;quot;, ...)，NSCache 的存取性能会下降得非常厉害，大量的时间被消耗在 CFStringEqual() 上，不知这是不是 NSCache 本身设计的缺陷。&lt;/p&gt;
 &lt;p&gt;  &lt;a href="https://github.com/tumblr/TMCache" target="_blank"&gt;TMMemoryCache&lt;/a&gt; 是   &lt;a href="https://github.com/tumblr/TMCache" target="_blank"&gt;TMCache&lt;/a&gt; 的内存缓存实现，最初由 Tumblr 开发，但现在已经不再维护了。TMMemoryCache 实现有很多 NSCache 并没有提供的功能，比如数量限制、总容量限制、存活时间限制、内存警告或应用退到后台时清空缓存等。TMMemoryCache 在设计时，主要目标是线程安全，它把所有读写操作都放到了同一个 serial queue 中，然后用 dispatch_semaphore 来保证最多只有一个线程访问 queue。它错误的用了大量异步 block 回调来实现存取功能，以至于产生了很大的性能和死锁问题。&lt;/p&gt;
 &lt;p&gt;  &lt;a href="https://github.com/pinterest/PINCache" target="_blank"&gt;PINMemoryCache&lt;/a&gt; 是 Tumblr 宣布不在维护 TMCache 后，由 Pinterest 维护和改进的一个内存缓存。它的功能和接口基本和 TMMemoryCache 一样，但修复了性能和死锁的问题。它同样也用 dispatch_semaphore 来保证线程安全，但去掉了 serial queue，避免了线程切换带来的巨大开销，也避免了可能的死锁。&lt;/p&gt;
 &lt;p&gt;  &lt;a href="https://github.com/ibireme/YYCache" target="_blank"&gt;YYMemoryCache&lt;/a&gt; 是我开发的一个内存缓存，相对于 PINMemoryCache 来说，我去掉了异步访问的接口，尽量优化了同步访问的性能，用 OSSpinLock 来保证性能安全。另外，缓存内部用双向链表和 NSDictionary 实现了 LRU 淘汰算法，相对于上面几个算是一点进步吧。&lt;/p&gt;
 &lt;p&gt;下面的单线程的 Memory Cache 性能基准测试：&lt;/p&gt;
 &lt;p&gt;  &lt;a href="http://blog.ibireme.com/wp-content/uploads/2015/10/memory_cache_bench_result.png"&gt;   &lt;img alt="memory_cache_bench_result" height="500" src="http://blog.ibireme.com/wp-content/uploads/2015/10/memory_cache_bench_result.png" width="728"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;
 &lt;p&gt;可以看到 YYMemoryCache 的性能不错，仅次于 NSDictionary + OSSpinLock；  &lt;br /&gt;
NSCache 的写入性能稍差，读取性能不错；  &lt;br /&gt;
PINMemoryCache 的读写性能也还可以，但读取速度差于 NSCache；  &lt;br /&gt;
TMMemoryCache 性能太差以至于图上都看不出来了。&lt;/p&gt;
 &lt;h2&gt;磁盘缓存&lt;/h2&gt;
 &lt;p&gt;为了设计一个比较好的磁盘缓存，我调查了大量的开源库，包括 TMDiskCache、PINDiskCache、SDWebImage、FastImageCache 等，也调查了一些闭源的实现，包括 NSURLCache、Facebook 的 FBDiskCache 等。他们的实现技术大致分为三类：基于文件读写、基于 mmap 文件内存映射、基于数据库。&lt;/p&gt;
 &lt;p&gt;TMDiskCache, PINDiskCache, SDWebImage 等缓存，都是基于文件系统的，即一个 Value 对应一个文件，通过文件读写来缓存数据。他们的实现都比较简单，性能也都相近，缺点也是同样的：不方便扩展、没有元数据、难以实现较好的淘汰算法、数据统计缓慢。&lt;/p&gt;
 &lt;p&gt;FastImageCache 采用的是 mmap 将文件映射到内存。用过 MongoDB 的人应该很熟悉 mmap 的缺陷：热数据的文件不要超过物理内存大小，不然 mmap 会导致内存交换严重降低性能；另外内存中的数据是定时 flush 到文件的，如果数据还未同步时程序挂掉，就会导致数据错误。抛开这些缺陷来说，mmap 性能非常高。&lt;/p&gt;
 &lt;p&gt;NSURLCache、FBDiskCache 都是基于 SQLite 数据库的。基于数据库的缓存可以很好的支持元数据、扩展方便、数据统计速度快，也很容易实现 LRU 或其他淘汰算法，唯一不确定的就是数据库读写的性能，为此我评测了一下 SQLite 在真机上的表现。iPhone 6 64G 下，SQLite 写入性能比直接写文件要高，但读取性能取决于数据大小：当单条数据小于 20K 时，数据越小 SQLite 读取性能越高；单条数据大于 20K 时，直接写为文件速度会更快一些。这和   &lt;a href="http://www.sqlite.org/intern-v-extern-blob.html" target="_blank"&gt;SQLite 官网的描述&lt;/a&gt;基本一致。另外，直接从官网下载最新的 SQLite 源码编译，会比 iOS 系统自带的 sqlite3.dylib 性能要高很多。基于 SQLite 的这种表现，磁盘缓存最好是把 SQLite 和文件存储结合起来：key-value 元数据保存在 SQLite 中，而 value 数据则根据大小不同选择 SQLite 或文件存储。NSURLCache 选定的数据大小的阈值是 16K；FBDiskCache 则把所有 value 数据都保存成了文件。&lt;/p&gt;
 &lt;p&gt;我的 YYDiskCache 也是采用的 SQLite 配合文件的存储方式，在 iPhone 6 64G 上的性能基准测试结果见下图。在存取小数据 (NSNumber) 时，YYDiskCache 的性能远远高出基于文件存储的库；而较大数据的存取性能则比较接近了。但得益于 SQLite 存储的元数据，YYDiskCache 实现了 LRU 淘汰算法、更快的数据统计，更多的容量控制选项。&lt;/p&gt;
 &lt;p&gt;  &lt;a href="http://blog.ibireme.com/wp-content/uploads/2015/10/disk_cache_bench_result.png"&gt;   &lt;img alt="disk_cache_bench_result" height="397" src="http://blog.ibireme.com/wp-content/uploads/2015/10/disk_cache_bench_result.png" width="728"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;
 &lt;h2&gt;备注：&lt;/h2&gt;
 &lt;p&gt;  &lt;strong&gt;关于锁：&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;OSSpinLock 自旋锁，性能最高的锁。原理很简单，就是一直 do while 忙等。它的缺点是当等待时会消耗大量 CPU 资源，所以它不适用于较长时间的任务。对于内存缓存的存取来说，它非常合适。&lt;/p&gt;
 &lt;p&gt;dispatch_semaphore 是信号量，但当信号总量设为 1 时也可以当作锁来。在没有等待情况出现时，它的性能比 pthread_mutex 还要高，但一旦有等待情况出现时，性能就会下降许多。相对于 OSSpinLock 来说，它的优势在于等待时不会消耗 CPU 资源。对磁盘缓存来说，它比较合适。&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;关于 Realm：&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;Realm 是一个比较新的数据库，号称是针对移动应用所设计。我在测试 SQLite 性能时，也尝试对它做了些简单的评测。我从 Realm 官网下载了它提供的 benchmark 项目，更新 SQLite 到官网最新的版本，并启用了 SQLite 的 sqlite3_stmt 缓存。评测结果显示 Realm 在写入性能上差 SQLite 很多，读取小数据时也差 SQLite 不少，只有读取较大数据时 Realm 才有很大的优势。我想看看它的实现原理，但发现 Realm 的核心 realm-core 是闭源的（还有迹象未来要收费），能知道的是 Realm 应该用 了 mmap 把文件映射到内存，所以才在较大数据读取时获得很高的性能。另外我注意到添加了 Realm 的 App 会在启动时向某几个 IP 发送数据，所以我强烈建议大家不要用 Realm。&lt;/p&gt;
&lt;div&gt; &lt;a href="https://itindex.net/"  title="IT 资讯"&gt;&lt;img src="https://itindex.net/images/iconWarning.gif" title="IT 资讯" border="0"/&gt; &lt;/a&gt;</description>
      <category>iOS 技术</category>
      <guid isPermaLink="true">https://itindex.net/detail/54588-yycache-%E8%AE%BE%E8%AE%A1</guid>
      <pubDate>Mon, 26 Oct 2015 00:06:04 CST</pubDate>
    </item>
    <item>
      <title>「原创译文」iOS 性能优化：Instruments 工具的救命三招</title>
      <link>https://itindex.net/detail/52825-%E5%8E%9F%E5%88%9B-%E8%AF%91%E6%96%87-ios</link>
      <description>&lt;blockquote&gt;
    &lt;p&gt;你的 iOS 应用，运行速度靠谱吗？中枪的同学莫要愁，性能优化咱有妙招。用 Xcode 自家的调试工具 Instruments，揪出那些堵线程、占内存、耗资源的问题代码，彻底破掉迷局，让应用扬眉吐气！&lt;/p&gt;
&lt;/blockquote&gt;

 &lt;hr&gt;&lt;/hr&gt;
 &lt;p&gt;对于每位 iOS 开发者来说，代码性能是个避不开的话题。随着项目的扩大和功能的增多，没经过认真调试和优化的代码，要么任性地卡顿运行，要么低调地崩溃了之……结果呢，大家用着不高兴，开发者也不开心。&lt;/p&gt;

 &lt;p&gt;其实要破这个局面并不难，只要在 Xcode 自带的监控调试工具 Instruments 上花点功夫，让大代码流畅运行也不是神话。Instruments 提供了很多功能，我会重点介绍一下我最常用的三大类：&lt;/p&gt;

 &lt;ul&gt;
  &lt;li&gt;Time Profiler：分析代码的执行时间，找出导致程序变慢的原因。&lt;/li&gt;
  &lt;li&gt;Allocations：监测内存使用/分配情况   &lt;br /&gt;
迅速膨胀的内存可以很快让程序毙命，所以要多加防范。&lt;/li&gt;
  &lt;li&gt;Leaks：找到引发内存泄漏的起点&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;即使有 ARC（自动引用计数）内存管理机制，但在现实中对象之间引用复杂，循环引用导致的内存泄漏仍然难以避免，所以关键时刻还要自力更生。&lt;/p&gt;

 &lt;p&gt;针对这三方面的测试，我写了个演示应用，放在   &lt;a href="https://github.com/mcgraw/dojo-instruments" rel="nofollow"&gt;GitHub&lt;/a&gt; 上，来帮助大家更直观地了解这些工具的使用方法。好，进入正题。&lt;/p&gt;

 &lt;p&gt;  &lt;a href="https://blog.leancloud.cn/wp-content/uploads/2015/02/001.png" rel="attachment wp-att-2842"&gt;   &lt;img alt="001" height="625" src="https://blog.leancloud.cn/wp-content/uploads/2015/02/001-397x625.png" width="397"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;

 &lt;h2&gt;Time Profiler&lt;/h2&gt;

 &lt;p&gt;时间都去哪儿啦？ Time Profiler 可以回答。它会按照设定的时间间隔（默认 1 毫秒）来跟踪每一线程的堆栈信息（stack trace），并通过比较时间间隔之间的堆栈状态，来推算出某个方法执行了多久，给出一个近似值。  &lt;br /&gt;
在演示应用头一项「Time Profiler: System Methods」中，我用插入排序（Insertion Sort）和冒泡排序（Bubble Sort）两种算法来做性能比较，下面是 Swift 代码：&lt;/p&gt;

 &lt;pre&gt;  &lt;code&gt;/* 引用自：http://waynewbishop.com/swift/sorting-algorithms/ */

func insertionSort() {
    var x, y, key: Int

    for (x = 0; x &amp;lt; numberList.count; x++) {
        key = numberList[x]

        for (y = x; y &amp;gt; -1; y--) {
            if key &amp;lt; numberList[y] {
                numberList.removeAtIndex(y + 1)
                numberList.insert(key, atIndex: y)
            }
        }
    }
}

func bubbleSort() {
    var x, y, z, passes, key : Int

    for (x = 0; x &amp;lt; numberList.count; ++x) {
        passes = (numberList.count - 1) - x;

        for (y = 0; y &amp;lt; passes; y++) {
            key = numberList[y]

            if (key &amp;gt; numberList[y + 1]) {
                z = numberList[y + 1]
             numberList[y + 1] = key
                numberList[y] = z
            }
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;

 &lt;p&gt;这段代码主要是对数组的添加和删除，两种方法执行起来耗时不多，但后台发生的系统动作却多得让人眼晕。&lt;/p&gt;

 &lt;p&gt;  &lt;a href="https://blog.leancloud.cn/wp-content/uploads/2015/02/004.jpg" rel="attachment wp-att-2845"&gt;   &lt;img alt="004" height="352" src="https://blog.leancloud.cn/wp-content/uploads/2015/02/004-625x352.jpg" width="625"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;

 &lt;p&gt;可以发现，代码用到了很多间接依赖，这些都是支撑代码运行的系统库文件。因为处理大数据集比较消耗系统资源，所以要尽可能地把繁重的操作放到后台去做，上面的代码就走的后台线程。在上图的 Call Tree 中可以看到，被调用的堆栈名是 dispatch_worker_thread3。如果把它放到主线程去执行，程序肯定会挂起。不信你注释掉 dispatch_async 调用看一下。&lt;/p&gt;

 &lt;p&gt;再来个图片加载的例子。&lt;/p&gt;

 &lt;p&gt;  &lt;a href="https://blog.leancloud.cn/wp-content/uploads/2015/02/003.jpg" rel="attachment wp-att-2844"&gt;   &lt;img alt="003" height="352" src="https://blog.leancloud.cn/wp-content/uploads/2015/02/003-625x352.jpg" width="625"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;

 &lt;p&gt;这儿有三种图片加载方法：&lt;/p&gt;

 &lt;ul&gt;
  &lt;li&gt;loadSlowImage1：从指定 URL 下载一张图片（加载速度慢）&lt;/li&gt;
  &lt;li&gt;loadImage2：从本地资源库加载一张图片（注意：没用系统缓存）&lt;/li&gt;
  &lt;li&gt;loadFastImage3：从系统缓存中加载一张图片（加载速度快）&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;我们来看看 Time Profiler 算出的结果是不是跟预想的一样。&lt;/p&gt;

 &lt;p&gt;进入演示应用第二项「Time Profiler: Our Methods」，点击「Reload」十次来重复加载图片，这样能产生足够的数据来分析。然后在 Time Profiler 图表中通过拖拉鼠标选中要放大查看的区域，从 Call Tree 中双击调用了 .reload 方法那一行（上图中加亮选中那一行），就会跳转到对应的代码行，所用时间也标注出来了。&lt;/p&gt;

 &lt;p&gt;  &lt;a href="https://blog.leancloud.cn/wp-content/uploads/2015/02/004.jpg" rel="attachment wp-att-2845"&gt;   &lt;img alt="004" height="352" src="https://blog.leancloud.cn/wp-content/uploads/2015/02/004-625x352.jpg" width="625"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;

 &lt;p&gt;看到谁最花时间了吧。虽然代码没什么可优化的地方，但大家应该认识到缓存能发挥的作用。所以即使有时还得调用 loadSlowImage，多数情况下把图片缓存下来，还是能省些资源占用。&lt;/p&gt;

 &lt;p&gt;此外，我想再说说 Call Tree 的选项设置。&lt;/p&gt;

 &lt;p&gt;  &lt;a href="https://blog.leancloud.cn/wp-content/uploads/2015/02/005.jpg" rel="attachment wp-att-2836"&gt;   &lt;img alt="005" height="173" src="https://blog.leancloud.cn/wp-content/uploads/2015/02/005.jpg" width="290"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;

 &lt;p&gt;这些选项默认是不选的，但把它们勾选上可以帮你更快定位到关键的代码上，往往这也是问题的源头。&lt;/p&gt;

 &lt;ul&gt;
  &lt;li&gt;Separate by Thread：按线程分开做分析，这样更容易揪出那些吃资源的问题线程。特别是对于主线程，它要处理和渲染所有的接口数据，一旦受到阻塞，程序必然卡顿或停止响应。&lt;/li&gt;
  &lt;li&gt;Invert Call Tree：反向输出调用树。把调用层级最深的方法显示在最上面，更容易找到最耗时的操作。&lt;/li&gt;
  &lt;li&gt;Hide Missing Symbols：隐藏缺失符号。如果 dSYM 文件或其他系统架构缺失，列表中会出现很多奇怪的十六进制的数值，用此选项把这些干扰元素屏蔽掉，让列表回归清爽。&lt;/li&gt;
  &lt;li&gt;Hide System Libraries：隐藏系统库文件。过滤掉各种系统调用，只显示自己的代码调用。&lt;/li&gt;
  &lt;li&gt;Flattern Recursion：拼合递归。将同一递归函数产生的多条堆栈（因为递归函数会调用自己）合并为一条。&lt;/li&gt;
  &lt;li&gt;Top Functions：找到最耗时的函数或方法。&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;需要添加其他工具的话：&lt;/p&gt;

 &lt;p&gt;  &lt;a href="https://blog.leancloud.cn/wp-content/uploads/2015/02/007.jpg" rel="attachment wp-att-2838"&gt;   &lt;img alt="007" height="263" src="https://blog.leancloud.cn/wp-content/uploads/2015/02/007.jpg" width="408"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;

 &lt;h2&gt;Allocations&lt;/h2&gt;

 &lt;p&gt;我们经常需要从服务器下载大量图片，特别是开发照片类的应用。但往往稍不注意，内存使用就会暴增，所以得保证把这些图片缓存下来以便重复使用。下面来看看演示程序中内存分配的例子。&lt;/p&gt;

 &lt;p&gt;  &lt;a href="https://blog.leancloud.cn/wp-content/uploads/2015/02/008.jpg" rel="attachment wp-att-2839"&gt;   &lt;img alt="008" height="403" src="https://blog.leancloud.cn/wp-content/uploads/2015/02/008-625x403.jpg" width="625"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;

 &lt;p&gt;从图中可以看到，每次点击「Reload」重新载入图片时，内存都会出现使用峰值。应用先分配大量内存来替换原有图片，然后再释放掉这部分内存，可想而知这样的操作效率高不了，而且如果要下载更大的文件，呃，局面大概会失控吧。&lt;/p&gt;

 &lt;p&gt;看一下堆栈列表第四行，ImageIO_PNG_Data 里有 9 张处于活动状态的图片，占用了12.38 MB 内存，这些都是没被系统释放或缓存的内存，所以导致堆内存分配升高。接下来再看看使用缓存后的效果。&lt;/p&gt;

 &lt;p&gt;  &lt;a href="https://blog.leancloud.cn/wp-content/uploads/2015/02/009.jpg" rel="attachment wp-att-2840"&gt;   &lt;img alt="009" height="417" src="https://blog.leancloud.cn/wp-content/uploads/2015/02/009-625x417.jpg" width="625"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;

 &lt;p&gt;使用了缓存库（  &lt;a href="https://github.com/Haneke/HanekeSwift" rel="nofollow"&gt;Swift&lt;/a&gt;   &lt;a href="https://github.com/Haneke/HanekeSwift" rel="nofollow"&gt;Haneke&lt;/a&gt;）后，点「Reload」五次，这回在 Allocations 列表中却看不到 ImageIO_PNG_Data 对象了，这说明它是空的，没有任何图像数据。同时，All Heap Allocations 的大小已从刚才的 14.61 MB 降到了 2.51 MB。Anonymous VM（匿名虚拟内存）是系统为程序预留的、可能会立即被重复使用的一部分可用内存。要防止程序崩溃，就别让堆的尺寸增长太快。&lt;/p&gt;

 &lt;p&gt;还有就是，例子用的是异步方式来加载图片，这样用不着等到所有图片下载完才能在界面中显示。大多数图像缓存库都会把加载工作放到后台，以避免延长主线程的响应周期。&lt;/p&gt;

 &lt;h2&gt;Leaks&lt;/h2&gt;

 &lt;p&gt;尽管 Apple 推出的 ARC 可以有效防范内存泄漏，但出问题的机率还是会有，Swift 也不例外。鉴于篇幅有限，本文就不涉及内存和 ARC 的工作原理了，具体可以参考  &lt;a href="https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/AutomaticReferenceCounting.html" rel="nofollow"&gt;官方文档&lt;/a&gt;。我会用代码来触发内存泄漏。&lt;/p&gt;

 &lt;p&gt;首先从最底层上说，当两个对象相互建立了强引用（strong reference），当一个对象被释放，另一个对象由于是强引用的关系不允许被释放，此时 ARC 无法确定没被释放的对象到底还有没有用，于是就导致了内存泄漏。&lt;/p&gt;

 &lt;p&gt;  &lt;a href="https://blog.leancloud.cn/wp-content/uploads/2015/02/010.jpg" rel="attachment wp-att-2841"&gt;   &lt;img alt="010" height="424" src="https://blog.leancloud.cn/wp-content/uploads/2015/02/010-625x424.jpg" width="625"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;

 &lt;p&gt;要解决这个问题，可以将其中的一个对象中变量设为 weak，不让它出现在保留周期中。很多开发者在管理 view controller 时常会在内存泄漏上中招，以为换了新的 controller，老的 controller 就被释放回收了，其实还没。这样代码一多，就会造成很多对象都没被释放。所以用这个工具把整个应用跑一遍，把那些断链的强引用清理干净，会大有裨益。&lt;/p&gt;

 &lt;p&gt;除了上述这三类工具，Instruments 还有很多实用的工具，推荐大家根据自己的关注点，花些时间去学学。比如：&lt;/p&gt;

 &lt;ul&gt;
  &lt;li&gt;Core Data：监测读取、缓存未命中、保存等操作，能直观显示是否保存次数远超实际需要。&lt;/li&gt;
  &lt;li&gt;Cocoa Layout：观察约束变化，找出布局代码的问题所在。&lt;/li&gt;
  &lt;li&gt;Network：跟踪 TCP / IP和 UDP / IP 连接。&lt;/li&gt;
  &lt;li&gt;Automations：创建和编辑测试脚本来自动化 iOS 应用的用户界面测试。&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;最后小总结下。我倒不想一味夸大 Instruments 的作用，如果应用跑得挺痛快，没出现啥调皮行为，大可把它忽略，等到问题来了再做优化。对于新手来说，花些时间了解 Instruments 的功能，多调试多积累经验，这样做出来的应用在用户体验上肯定错不了。&lt;/p&gt;

 &lt;p&gt;你最常用的 Instruments 工具都有哪些？欢迎与我们分享。&lt;/p&gt;

 &lt;hr&gt;&lt;/hr&gt;
 &lt;p&gt;原文：  &lt;a href="http://www.xmcgraw.com/how-to-use-the-3-instruments-you-should-be-using/" rel="nofollow"&gt;How To Use The 3 Instruments You Should Be Using&lt;/a&gt;  &lt;br /&gt;
译者：LeanCloud&lt;/p&gt;
&lt;div&gt; &lt;a href="https://itindex.net/"  title="IT 资讯"&gt;&lt;img src="https://itindex.net/images/iconWarning.gif" title="IT 资讯" border="0"/&gt; &lt;/a&gt;</description>
      <category>ios 性能优化 instruments</category>
      <guid isPermaLink="true">https://itindex.net/detail/52825-%E5%8E%9F%E5%88%9B-%E8%AF%91%E6%96%87-ios</guid>
      <pubDate>Sat, 28 Feb 2015 08:55:33 CST</pubDate>
    </item>
    <item>
      <title>HTTP中的ETag在移动客户端的应用</title>
      <link>https://itindex.net/detail/54756-http-etag-%E7%A7%BB%E5%8A%A8</link>
      <description>&lt;p&gt;绝大多数移动客户端在设计网络模块时，都会选用HTTP作为客户端和服务端通信的网络协议。随着业务的不断发展以及用户量的持续增长，整个客户端的稳定性和性能会逐渐成为关注的焦点，其中网络的性能优化更是重中之重，本文介绍的   &lt;em&gt;ETag&lt;/em&gt; 缓存技术，可以在缓存数据的同时做到数据的实时更新，适用于对数据实效性要求较高的业务。&lt;/p&gt;
 &lt;h2&gt;基本原理和概念&lt;/h2&gt;
 &lt;p&gt;相同的两次请求返回的结果相同时，第一次返回的结果缓存在客户端，第二次服务端不再返回结果，仅返回一个特殊的状态码，告诉客户端第二次请求的结果与上次相同，可以直接使用上次返回的数据。&lt;/p&gt;
 &lt;p&gt;实现中，会用到HTTP头中的两个字段：&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;   &lt;p&gt;    &lt;strong&gt;ETag&lt;/strong&gt; 返回应答数据的标记，服务端生成发送给客户端&lt;/p&gt;&lt;/li&gt;
  &lt;li&gt;   &lt;p&gt;    &lt;strong&gt;If-None-Match&lt;/strong&gt; 同样的请求，上一次返回的 ETag 值&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
 &lt;h2&gt;交互过程&lt;/h2&gt;
 &lt;ul&gt;
  &lt;li&gt;   &lt;p&gt;服务端在将数据发送给客户端之前，首先计算应答数据的摘要（通常是MD5），把计算结果作为     &lt;em&gt;ETag&lt;/em&gt; 的值，和数据一同发送给客户端。&lt;/p&gt;&lt;/li&gt;
  &lt;li&gt;   &lt;p&gt;客户端收到应答数据后，检测 HTTP Header 中是否有     &lt;em&gt;ETag&lt;/em&gt; 字段，如有则缓存应答数据和     &lt;em&gt;ETag&lt;/em&gt; 的值。&lt;/p&gt;&lt;/li&gt;
  &lt;li&gt;   &lt;p&gt;客户端再次发起同一请求时，读取上次缓存的     &lt;em&gt;ETag&lt;/em&gt; 值，将其作为     &lt;em&gt;If-None-Match&lt;/em&gt; 的值，并与请求数据一同发送给服务端。&lt;/p&gt;&lt;/li&gt;
  &lt;li&gt;   &lt;p&gt;服务端收到请求，执行请求，在把应答数据返回给客户端之前计算摘要，并与客户端上报的摘要比较，如果两次摘要相同，说明本次的应答数据与上一次请求的应答数据相同，且客户端已缓存该数据，则简单返回304错误。&lt;/p&gt;&lt;/li&gt;
  &lt;li&gt;   &lt;p&gt;客户端收到304错误，直接读取本地缓存的数据返回给调用网络模块的业务方。&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;交互过程总结如下图：&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="ETag" src="http://7xnua6.com1.z0.glb.clouddn.com/2015/etag.png" title="ETag"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;h2&gt;代码实现&lt;/h2&gt;
 &lt;p&gt;本文的示例代码使用   &lt;code&gt;NSURLSession&lt;/code&gt; 实现，由于   &lt;code&gt;NSURLSession&lt;/code&gt; 完善的缓存策略，为了演示   &lt;em&gt;ETag&lt;/em&gt; 的用法，需要先关闭缓存。&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;let config = NSURLSessionConfiguration.defaultSessionConfiguration()
config.requestCachePolicy = NSURLRequestCachePolicy.ReloadIgnoringCacheData
let session = NSURLSession(configuration: config)&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;  &lt;code&gt;NSURLSessionConfiguration&lt;/code&gt; 定义了   &lt;code&gt;NSURLSession&lt;/code&gt; 在上传和下载时的行为及策略，我们指定了请求的缓存策略为   &lt;code&gt;ReloadIgnoringCacheData&lt;/code&gt;, 意思就是在请求时不使用本地缓存。&lt;/p&gt;
 &lt;p&gt;创建请求的   &lt;code&gt;NSURLRequest&lt;/code&gt; 对象：&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;let url = NSURL(string: &amp;quot;http://www.joywek.com/50x.html&amp;quot;)!
let request = NSMutableURLRequest(URL: url)&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;上面会下载指定静态页面的内容，这个页面是放在 Nginx 的服务器上，Nginx 默认会对应答数据计算   &lt;em&gt;ETag&lt;/em&gt;。当然，在实际的应用中请求的都是动态数据，服务器要动态计算   &lt;em&gt;ETag&lt;/em&gt; 的值。&lt;/p&gt;
 &lt;p&gt;设置 HTTP Header 中的   &lt;em&gt;If-None-Match&lt;/em&gt; 字段：&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;if let tag = self.findTagByURL(url) {
    request.addValue(tag, forHTTPHeaderField: &amp;quot;If-None-Match&amp;quot;)
}&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;在发起请求时，先检查相同的请求是否存在   &lt;em&gt;ETag&lt;/em&gt;，如果存在就，就意味着上次请求的应答数据已缓存。&lt;/p&gt;
 &lt;p&gt;发起请求并处理应答数据：&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;self.dataTask = session.dataTaskWithRequest(request,
    completionHandler: { (var data, response, error) -&amp;gt; Void in
        data = self.handleResponse(response!, data!, request)
})
self.dataTask?.resume()&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;如果 HTTP 返回的状态码是 200，说明是服务器正常返回数据，此时记录   &lt;em&gt;ETag&lt;/em&gt; 的值并缓存应答数据：&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;if (resp.statusCode == 200) {
    self.etags[response.URL!] = resp.allHeaderFields[&amp;quot;ETag&amp;quot;] as? String
    let cachedResponse = NSCachedURLResponse(response: resp, data: data)
    NSURLCache.sharedURLCache().storeCachedResponse(cachedResponse, forRequest: request)
    return data
}&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;如果返回 304，说明应答数据没有变化，与上次请求的一样，则直接返回缓存中的数据：&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;else if (resp.statusCode == 304) {
    let cachedResponse = NSURLCache.sharedURLCache().cachedResponseForRequest(request)
    return cachedResponse?.data
}&lt;/code&gt;&lt;/pre&gt;
 &lt;h2&gt;关于演示 Demo&lt;/h2&gt;
 &lt;p&gt;下载地址：  &lt;a href="http://www.joywek.com/res/ETagExample.zip"&gt;http://www.joywek.com/res/ETagExample.zip&lt;/a&gt;&lt;/p&gt;
&lt;div&gt; &lt;a href="https://itindex.net/"  title="IT 资讯"&gt;&lt;img src="https://itindex.net/images/iconWarning.gif" title="IT 资讯" border="0"/&gt; &lt;/a&gt;</description>
      <category>http etag ios</category>
      <guid isPermaLink="true">https://itindex.net/detail/54756-http-etag-%E7%A7%BB%E5%8A%A8</guid>
      <pubDate>Mon, 30 Nov 2015 14:37:31 CST</pubDate>
    </item>
    <item>
      <title>如何把你的iPad变成一个桌面电脑？</title>
      <link>https://itindex.net/detail/49488-ipad-%E6%A1%8C%E9%9D%A2%E7%94%B5%E8%84%91</link>
      <description>&lt;p&gt;  &lt;a href="http://www.geekfan.net/wp-content/uploads/d11c237d7b841f1306d421ae2e2e9347.jpg" rel="lightbox[9087]" title="&amp;#22914;&amp;#20309;&amp;#25226;&amp;#20320;&amp;#30340;iPad&amp;#21464;&amp;#25104;&amp;#19968;&amp;#20010;&amp;#26700;&amp;#38754;&amp;#30005;&amp;#33041;&amp;#65311;"&gt;   &lt;img alt="20140505102903367" height="748" src="http://www.geekfan.net/wp-content/uploads/d11c237d7b841f1306d421ae2e2e9347.jpg" width="1024"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;
 &lt;p&gt;在使用iPad的时候，有时你会需要获得一份文件，或是访问一个程序；有时也会发现一些工作无法在iPad上面进行。以前，远程计算服务很少会应用在iPad上，但是随着人们工作范围越来越广，移动设备的远程连接逐渐变得越来越有用。&lt;/p&gt;
 &lt;p&gt;如果你的家中有一台个人桌面电脑，而且希望通过互联网将自己的iPad远程连接到PC上，不如来看看本文吧。&lt;/p&gt;
 &lt;h2&gt;屏幕分享和远程桌面&lt;/h2&gt;
 &lt;p&gt;苹果和微软在自己的移动设备里都提供了内置解决方案，允许用户远程连接到自己的桌面电脑上。苹果使用的技术大家已经比较熟悉了，该技术就是“虚拟网络计算(VNC)”，用户可以自行配置相关分享服务参数。微软使用的则是他们自己的“远程桌面协议(RDP)”，它可以在系统属性里面的“远程设置”里进行配置，用户可以右键点击“计算机”选择“属性”进入，或是在控制面板里搜索“远程配置”。&lt;/p&gt;
 &lt;p&gt;  &lt;a href="http://www.geekfan.net/wp-content/uploads/bc2fdbab1015c82ed10f50392c556ca6.jpg" rel="lightbox[9087]" title="&amp;#22914;&amp;#20309;&amp;#25226;&amp;#20320;&amp;#30340;iPad&amp;#21464;&amp;#25104;&amp;#19968;&amp;#20010;&amp;#26700;&amp;#38754;&amp;#30005;&amp;#33041;&amp;#65311;"&gt;   &lt;img alt="20140505100051651" height="338" src="http://www.geekfan.net/wp-content/uploads/bc2fdbab1015c82ed10f50392c556ca6.jpg" width="708"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;
 &lt;p&gt;一旦配置完成之后，你就可以在iPad上使用一些远程连接客户端了，比如  &lt;a href="https://itunes.apple.com/us/app/vnc-viewer/id352019548?mt=8" target="_blank"&gt;VNC Viewer&lt;/a&gt;(免费)和  &lt;a href="https://itunes.apple.com/us/app/microsoft-remote-desktop/id714464092?mt=8" target="_blank"&gt;微软远程桌面&lt;/a&gt;(免费)。这些应用可以把iPad远程连接到你的桌面电脑上，当然啦，前提是这些设备需要在相同的本地网络之内。&lt;/p&gt;
 &lt;h2&gt;通过互联网实现远程连接&lt;/h2&gt;
 &lt;p&gt;苹果为其iCloud用户提供跨互联网远程服务，该服务就是  &lt;a href="http://www.apple.com/support/icloud/back-to-my-mac/" target="_blank"&gt;Back to my Mac&lt;/a&gt;，它可以定位并连接到用户的Mac电脑。它的问题是，该服务只支持Mac电脑之间互联，而且经过这几年的使用，用户均反馈此服务速度很慢，而且稳定性不够可靠。另一方面，微软公司也提供了类似的远程探索服务，但是它要求用户安装配置一个  &lt;a href="http://windows.microsoft.com/en-us/windows7/what-is-a-remote-desktop-gateway-server" target="_blank"&gt;远程桌面网关&lt;/a&gt;(RDG)。&lt;/p&gt;
 &lt;p&gt;谷歌公司最近也推出了自己的远程连接服务解决方案，当然，它就是被人们熟知的  &lt;a href="https://support.google.com/chrome/answer/1649523?hl=en" target="_blank"&gt;Chrome Remote Desktop&lt;/a&gt;，该解决方案可以通过互联网进行远程连接桌面系统。谷歌公司最近发布了这个Chrome应用的安卓版本，目前虽然尚未提供iOS版本，不过预计会在今年年末发布。&lt;/p&gt;
 &lt;p&gt;  &lt;a href="http://www.geekfan.net/wp-content/uploads/e0bb090814db9be710523c97369d84e9.jpg" rel="lightbox[9087]" title="&amp;#22914;&amp;#20309;&amp;#25226;&amp;#20320;&amp;#30340;iPad&amp;#21464;&amp;#25104;&amp;#19968;&amp;#20010;&amp;#26700;&amp;#38754;&amp;#30005;&amp;#33041;&amp;#65311;"&gt;   &lt;img alt="20140505100051354" height="423" src="http://www.geekfan.net/wp-content/uploads/e0bb090814db9be710523c97369d84e9.jpg" width="708"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;
 &lt;p&gt;远程设备连接的目标，其实还是要能通过互联网，快速便捷地定位、并连接到你的桌面电脑上面。但是对于绝大多数用户来说，需要手工配置的地方非常多，比如需要处理防火墙，端口转发，静态IP地址等等，这些都非常麻烦。下面我们要提到的一些应用程序都有一个主机应用，并且需要用户安装在自己的桌面电脑上。这些应用程序会自动通过互联网，在iPad和桌面电脑之间实现互联。在大多数情况下，你需要做的，就是去下载这些应用程序，然后安装，并登陆到远程服务上去。&lt;/p&gt;
 &lt;h2&gt;支持远程连接的iPad App&lt;/h2&gt;
 &lt;p&gt;1、  &lt;a href="https://itunes.apple.com/us/app/pocketcloud-remote-desktop/id398798399?mt=8" target="_blank"&gt;PocketCloud&lt;/a&gt;。 虽然它是一款含广告的App，但是如果想通过互联网访问桌面计算机的话，这款免费App仍然是最佳选择。它提供了一个触摸指针(Touch Pointer)，感觉就像是在iPad上配了一个鼠标。用户可以在iPad屏幕上轻松移动虚拟鼠标，它还支持左键和右键点击，以及滚轮功能。&lt;/p&gt;
 &lt;p&gt;它提供了PC和Mac两个版本的App，该应用的免广告Pro版需要支付15美元，此外免费用户也可以升级成付费用户，年使用费为24美元，可以访问文件，流媒体视频，桌面搜索等功能。&lt;/p&gt;
 &lt;p&gt;2、  &lt;a href="https://itunes.apple.com/us/app/teamviewer-remote-control/id692035811?mt=8" target="_blank"&gt;TeamViewer&lt;/a&gt;。这款远程桌面App是为了大型团队和企业级客户而设计的，当然它也支持个人用户使用。TeamViewer对个人用户是免费提供的，他们这么做主要是出于非商业目的。TeamViewer Version 9支持Mac，Windows，以及Linux系统。&lt;/p&gt;
 &lt;p&gt;3、  &lt;a href="https://itunes.apple.com/us/app/desktop-connect/id364907570?mt=8" target="_blank"&gt;Desktop Connect&lt;/a&gt; 。这款应用是市面上最早的远程桌面App之一，当然也是最好的App之一。如果你想连接多台电脑，或是希望有高速连接，那么Desktop Connect绝对是一款值得尝试的App，它售价为14.99美元。相对而言，PocketCloud 和TeamViewer的连接速度真的很慢，但是Desktop Connect的速度真的，真的非常快。其客户端支持PC，Mac，和Linux操作系统，用户还可以使用谷歌帐号直接登录。&lt;/p&gt;
 &lt;p&gt;4、  &lt;a href="https://itunes.apple.com/us/app/parallels-access/id655527928?mt=8" target="_blank"&gt;Parallels Access for iPad&lt;/a&gt; 。 这款应用的开发商也开发了虚拟机软件，允许用户在自己的Mac电脑上运行Windows操作系统，Parallels Access应用是免费下载的，如果你认为这款应用是让你去访问虚拟机，那么就大错特错了。其客户端Parallels Access Agent可以在PC和Mac上安装，允许用户通过互联网访问任何桌面电脑，但是用户必须要每年支付49美元的订购费用。&lt;/p&gt;
 &lt;p&gt;5、  &lt;a href="https://itunes.apple.com/us/app/splashtop-2-remote-desktop/id382509315?mt=8" target="_blank"&gt;Splashtop 2 Remote Desktop&lt;/a&gt;。Splashtop 2的费用为19.99美元，它是一款非常稳定的App，其可调节触控板模式和屏幕滚动条两项功能也十分有用。这款应用的客户端Splashtop Streamer可以安装在Mac，PC，以及Linux操作系统上。用户如果需要使用还得支付每年16美元的订购费用，才能用自己的iPad连接到桌面电脑上。&lt;/p&gt;
 &lt;p&gt;  &lt;a href="http://www.geekfan.net/wp-content/uploads/529be4e967bf01217bed157ba3ac2e3a.png" rel="lightbox[9087]" title="&amp;#22914;&amp;#20309;&amp;#25226;&amp;#20320;&amp;#30340;iPad&amp;#21464;&amp;#25104;&amp;#19968;&amp;#20010;&amp;#26700;&amp;#38754;&amp;#30005;&amp;#33041;&amp;#65311;"&gt;   &lt;img alt="20140505100050378" height="234" src="http://www.geekfan.net/wp-content/uploads/529be4e967bf01217bed157ba3ac2e3a.png" width="708"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;
 &lt;p&gt;上述五款App都可以安装在用户桌面电脑上，在iPad上操作时，不仅控制便捷，而且，即便通过较慢的WiFi，LTE或是4G网络连接，他们的响应时间和连接时间也很快。当然最重要的是，这些App的价格也在用户的承受范围之内。&lt;/p&gt;

 &lt;div&gt;  &lt;div&gt;   &lt;h3&gt;相关文章&lt;/h3&gt;   &lt;ul&gt;    &lt;li&gt;     &lt;a href="http://www.geekfan.net/5281/"&gt;为什么Linux不需要碎片整理？&lt;/a&gt;&lt;/li&gt;    &lt;li&gt;     &lt;a href="http://www.geekfan.net/449/"&gt;如何为iPad添加游戏手柄&lt;/a&gt;&lt;/li&gt;    &lt;li&gt;     &lt;a href="http://www.geekfan.net/455/"&gt;胜者为王——那些向iPad叫板的竞争对手&lt;/a&gt;&lt;/li&gt;    &lt;li&gt;     &lt;a href="http://www.geekfan.net/1274/"&gt;iPad瘦身大法&lt;/a&gt;&lt;/li&gt;    &lt;li&gt;     &lt;a href="http://www.geekfan.net/459/"&gt;动漫迷的福音：让iPad成为漫画书&lt;/a&gt;&lt;/li&gt;    &lt;li&gt;     &lt;a href="http://www.geekfan.net/457/"&gt;限免应用抢购神器——AppsFire&lt;/a&gt;&lt;/li&gt;    &lt;li&gt;     &lt;a href="http://www.geekfan.net/6937/"&gt;把Windows打造为WIFI热点共享你的互联网连接&lt;/a&gt;&lt;/li&gt;    &lt;li&gt;     &lt;a href="http://www.geekfan.net/8490/"&gt;新技能：如何用iPhone远程控制电脑开机？&lt;/a&gt;&lt;/li&gt;    &lt;li&gt;     &lt;a href="http://www.geekfan.net/7846/"&gt;WiFi密码破解那些事&lt;/a&gt;&lt;/li&gt;    &lt;li&gt;     &lt;a href="http://www.geekfan.net/7971/"&gt;Dropbox的10个聪明应用&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;/div&gt;
 &lt;p&gt;  &lt;a href="http://www.geekfan.net/9087/"&gt;如何把你的iPad变成一个桌面电脑？&lt;/a&gt;，首发于  &lt;a href="http://www.geekfan.net"&gt;极客范 - GeekFan.net&lt;/a&gt;。&lt;/p&gt;&lt;div&gt; &lt;a href="https://itindex.net/"  title="IT 资讯"&gt;&lt;img src="https://itindex.net/images/iconWarning.gif" title="IT 资讯" border="0"/&gt; &lt;/a&gt;</description>
      <category>iOS Windows 互联网 iPad 远程桌面</category>
      <guid isPermaLink="true">https://itindex.net/detail/49488-ipad-%E6%A1%8C%E9%9D%A2%E7%94%B5%E8%84%91</guid>
      <pubDate>Wed, 07 May 2014 11:02:58 CST</pubDate>
    </item>
    <item>
      <title>React Native通信机制详解</title>
      <link>https://itindex.net/detail/53084-react-native-%E9%80%9A%E4%BF%A1</link>
      <description>&lt;p&gt;  &lt;a href="http://facebook.github.io/react-native/" target="_blank"&gt;React Native&lt;/a&gt;是facebook刚开源的框架，可以用javascript直接开发原生APP，先不说这个框架后续是否能得到大众认可，单从源码来说，这个框架源码里有非常多的设计思想和实现方式值得学习，本篇先来看看它最基础的JavaScript-ObjectC通信机制(以下简称JS/OC)。&lt;/p&gt;
 &lt;h2&gt;概览&lt;/h2&gt;
 &lt;p&gt;React Native用iOS自带的JavaScriptCore作为JS的解析引擎，但并没有用到JavaScriptCore提供的一些可以让JS与OC互调的特性，而是自己实现了一套机制，这套机制可以通用于所有JS引擎上，在没有JavaScriptCore的情况下也可以用webview代替，实际上项目里就已经有了用webview作为解析引擎的实现，应该是用于兼容iOS7以下没有JavascriptCore的版本。&lt;/p&gt;
 &lt;p&gt;普通的JS-OC通信实际上很简单，OC向JS传信息有现成的接口，像webview提供的-stringByEvaluatingJavaScriptFromString方法可以直接在当前context上执行一段JS脚本，并且可以获取执行后的返回值，这个返回值就相当于JS向OC传递信息。React Native也是以此为基础，通过各种手段，实现了在OC定义一个模块方法，JS可以直接调用这个模块方法并还可以无缝衔接回调。&lt;/p&gt;
 &lt;p&gt;举个例子，OC定义了一个模块RCTSQLManager，里面有个方法-query:successCallback:，JS可以直接调用RCTSQLManager.query并通过回调获取执行结果。：&lt;/p&gt;
 &lt;pre&gt;
//OC:
@implement RCTSQLManager
- (void)query:(NSString *)queryData successCallback:(RCTResponseSenderBlOCk)responseSender
{
     RCT_EXPORT();
     NSString *ret = @&amp;quot;ret&amp;quot;
     responseSender(ret);
}
@end
&lt;/pre&gt;
 &lt;pre&gt;
//JS:
RCTSQLManager.query(&amp;quot;SELECT * FROM table&amp;quot;, function(result) {
     //result == &amp;quot;ret&amp;quot;;
});
&lt;/pre&gt;
 &lt;p&gt;接下来看看它是怎样实现的。&lt;/p&gt;
 &lt;h2&gt;模块配置表&lt;/h2&gt;
 &lt;p&gt;首先OC要告诉JS它有什么模块，模块里有什么方法，JS才知道有这些方法后才有可能去调用这些方法。这里的实现是OC生成一份模块配置表传给JS，配置表里包括了所有模块和模块里方法的信息。例：&lt;/p&gt;
 &lt;pre&gt;
{
    &amp;quot;remoteModuleConfig&amp;quot;: {
        &amp;quot;RCTSQLManager&amp;quot;: {
            &amp;quot;methods&amp;quot;: {
                &amp;quot;query&amp;quot;: {
                    &amp;quot;type&amp;quot;: &amp;quot;remote&amp;quot;,
                    &amp;quot;methodID&amp;quot;: 0
                }
            },
            &amp;quot;moduleID&amp;quot;: 4
        },
        ...
     },
}
&lt;/pre&gt;
 &lt;p&gt;OC端和JS端分别各有一个bridge，两个bridge都保存了同样一份模块配置表，JS调用OC模块方法时，通过bridge里的配置表把模块方法转为模块ID和方法ID传给OC，OC通过bridge的模块配置表找到对应的方法执行之，以上述代码为例，流程大概是这样（先不考虑callback）：&lt;/p&gt;
 &lt;p&gt;  &lt;a href="http://blog.cnbang.net/wp-content/uploads/2015/03/ReactNative1.png"&gt;   &lt;img alt="ReactNative1" height="533" src="http://blog.cnbang.net/wp-content/uploads/2015/03/ReactNative1.png" width="514"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;
 &lt;p&gt;在了解这个调用流程之前，我们先来看看OC的模块配置表式怎么来的。我们在新建一个OC模块时，JS和OC都不需要为新的模块手动去某个地方添加一些配置，模块配置表是自动生成的，只要项目里有一个模块，就会把这个模块加到配置表上，那这个模块配置表是怎样自动生成的呢？分两个步骤：&lt;/p&gt;
 &lt;h3&gt;1.取所有模块类&lt;/h3&gt;
 &lt;p&gt;每个模块类都实现了RCTBridgeModule接口，可以通过runtime接口objc_getClassList或objc_copyClassList取出项目里所有类，然后逐个判断是否实现了RCTBridgeModule接口，就可以找到所有模块类，实现在  &lt;a href="https://github.com/facebook/react-native/blob/72d3d724a3a0c6bc46981efd0dad8f7f61121a47/React/Base/RCTBridge.m#L73" target="_blank"&gt;RCTBridgeModuleClassesByModuleID()&lt;/a&gt;方法里。&lt;/p&gt;
 &lt;h3&gt;2.取模块里暴露给JS的方法&lt;/h3&gt;
 &lt;p&gt;一个模块里可以有很多方法，一些是可以暴露给JS直接调用的，一些是私有的不想暴露给JS，怎样做到提取这些暴露的方法呢？我能想到的方法是对要暴露的方法名制定一些规则，比如用RCTExport_作为前缀，然后用runtime方法class_getInstanceMethod取出所有方法名字，提取以RCTExport_为前缀的方法，但这样做恶心的地方是每个方法必须加前缀。React Native用了另一种黑魔法似的方法解决这个问题：编译属性__attribute__。&lt;/p&gt;
 &lt;p&gt;在上述例子中我们看到模块方法里有句代码：RCT_EXPORT()，模块里的方法都加上这个宏，就可以实现暴露给JS，无需其他规则，那这个宏做了什么呢？来看看它的定义：&lt;/p&gt;
 &lt;pre&gt;
#define RCT_EXPORT(JS_name) __attribute__((used, section(&amp;quot;__DATA,RCTExport&amp;quot; \
))) static const char *__rct_export_entry__[] = { __func__, #JS_name }
&lt;/pre&gt;
 &lt;p&gt;这个宏的作用是用编译属性__attribute__给二进制文件新建一个section，属于__DATA数据段，名字为RCTExport，并在这个段里加入当前方法名。编译器在编译时会找到__attribute__进行处理，为生成的可执行文件加入相应的内容。效果可以从  &lt;a href="http://blog.cnbang.net/tech/2296/" target="_blank"&gt;linkmap&lt;/a&gt;看出来：&lt;/p&gt;
 &lt;pre&gt;
# Sections:
# Address Size Segment Section
0x100001670 0x000C0180 __TEXT __text
...
0x10011EFA0 0x00000330 __DATA RCTExport
0x10011F2D0 0x00000010 __DATA __common
0x10011F2E0 0x000003B8 __DATA __bss
...

0x10011EFA0 0x00000010 [ 4] -[RCTStatusBarManager setStyle:animated:].__rct_export_entry__
0x10011EFB0 0x00000010 [ 4] -[RCTStatusBarManager setHidden:withAnimation:].__rct_export_entry__
0x10011EFC0 0x00000010 [ 5] -[RCTSourceCode getScriptText:failureCallback:].__rct_export_entry__
0x10011EFD0 0x00000010 [ 7] -[RCTAlertManager alertWithArgs:callback:].__rct_export_entry__
...
&lt;/pre&gt;
 &lt;p&gt;可以看到可执行文件数据段多了个RCTExport段，内容就是各个要暴露给JS的方法。这些内容是可以在运行时获取到的，在RCTBridge.m的  &lt;a href="https://github.com/facebook/react-native/blob/72d3d724a3a0c6bc46981efd0dad8f7f61121a47/React/Base/RCTBridge.m#L296" target="_blank"&gt;RCTExportedMethodsByModuleID()&lt;/a&gt;方法里获取这些内容，提取每个方法的类名和方法名，就完成了提取模块里暴露给JS方法的工作。&lt;/p&gt;
 &lt;p&gt;整体的模块类/方法提取实现在  &lt;a href="https://github.com/facebook/react-native/blob/72d3d724a3a0c6bc46981efd0dad8f7f61121a47/React/Base/RCTBridge.m#L378" target="_blank"&gt;RCTRemoteModulesConfig()&lt;/a&gt;方法里。&lt;/p&gt;
 &lt;h2&gt;调用流程&lt;/h2&gt;
 &lt;p&gt;接下来看看JS调用OC模块方法的详细流程，包括callback回调。这时需要细化一下上述的调用流程图：&lt;/p&gt;
 &lt;p&gt;  &lt;a href="http://blog.cnbang.net/wp-content/uploads/2015/03/ReactNative2.png"&gt;   &lt;img alt="ReactNative2" height="643" src="http://blog.cnbang.net/wp-content/uploads/2015/03/ReactNative2.png" width="872"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;
 &lt;p&gt;看起来有点复杂，不过一步步说明，应该很容易弄清楚整个流程，图中每个流程都标了序号，从发起调用到执行回调总共有11个步骤，详细说明下这些步骤：&lt;/p&gt;
 &lt;p&gt;1.JS端调用某个OC模块暴露出来的方法。&lt;/p&gt;
 &lt;p&gt;2.把上一步的调用分解为ModuleName,MethodName,arguments，再扔给MessageQueue处理。&lt;/p&gt;
 &lt;div&gt;
  &lt;p&gt;在初始化时模块配置表上的每一个模块都生成了对应的remoteModule对象，对象里也生成了跟模块配置表里一一对应的方法，这些方法里可以拿到自身的模块名，方法名，并对callback进行一些处理，再移交给MessageQueue。具体实现在BatchedBridgeFactory.js的   &lt;a href="https://github.com/facebook/react-native/blob/72d3d724a3a0c6bc46981efd0dad8f7f61121a47/Libraries/BatchedBridge/BatchingImplementation/BatchedBridgeFactory.js#L37" target="_blank"&gt;_createBridgedModule&lt;/a&gt;里，整个实现区区24行代码，感受下JS的魔力吧。&lt;/p&gt;
&lt;/div&gt;
 &lt;p&gt;3.在这一步把JS的callback函数缓存在MessageQueue的一个成员变量里，用CallbackID代表callback。在通过保存在MessageQueue的模块配置表把上一步传进来的ModuleName和MethodName转为ModuleID和MethodID。&lt;/p&gt;
 &lt;p&gt;4.把上述步骤得到的ModuleID,MethodId,CallbackID和其他参数argus传给OC。至于具体是怎么传的，后面再说。&lt;/p&gt;
 &lt;p&gt;5.OC接收到消息，通过模块配置表拿到对应的模块和方法。&lt;/p&gt;
 &lt;div&gt;
  &lt;p&gt;实际上模块配置表已经经过处理了，跟JS一样，在初始化时OC也对模块配置表上的每一个模块生成了对应的实例并缓存起来，模块上的每一个方法也都生成了对应的RCTModuleMethod对象，这里通过ModuleID和MethodID取到对应的Module实例和RCTModuleMethod实例进行调用。具体实现在_handleRequestNumber:moduleID:methodID:params:。&lt;/p&gt;
&lt;/div&gt;
 &lt;p&gt;6.RCTModuleMethod对JS传过来的每一个参数进行处理。&lt;/p&gt;
 &lt;div&gt;
  &lt;p&gt;RCTModuleMethod可以拿到OC要调用的目标方法的每个参数类型，处理JS类型到目标类型的转换，所有JS传过来的数字都是NSNumber，这里会转成对应的int/long/double等类型，更重要的是会为block类型参数的生成一个block。&lt;/p&gt;
  &lt;p&gt;例如-(void)select:(int)index response:(RCTResponseSenderBlock)callback 这个方法，拿到两个参数的类型为int,block，JS传过来的两个参数类型是NSNumber,NSString(CallbackID)，这时会把NSNumber转为int，NSString(CallbackID)转为一个block，block的内容是把回调的值和CallbackID传回给JS。&lt;/p&gt;
  &lt;p&gt;这些参数组装完毕后，通过NSInvocation动态调用相应的OC模块方法。&lt;/p&gt;
&lt;/div&gt;
 &lt;p&gt;7.OC模块方法调用完，执行block回调。&lt;/p&gt;
 &lt;p&gt;8.调用到第6步说明的RCTModuleMethod生成的block。&lt;/p&gt;
 &lt;p&gt;9.block里带着CallbackID和block传过来的参数去调JS里MessageQueue的方法invokeCallbackAndReturnFlushedQueue。&lt;/p&gt;
 &lt;p&gt;10.MessageQueue通过CallbackID找到相应的JS callback方法。&lt;/p&gt;
 &lt;p&gt;11.调用callback方法，并把OC带过来的参数一起传过去，完成回调。&lt;/p&gt;
 &lt;p&gt;整个流程就是这样，估计大家都看累了:(，别急，还有最后一个点。&lt;/p&gt;
 &lt;h2&gt;事件响应&lt;/h2&gt;
 &lt;p&gt;上述第4步留下一个问题，JS是怎样把数据传给OC，让OC去调相应方法的？&lt;/p&gt;
 &lt;p&gt;答案是通过返回值。JS不会主动传递数据给OC，在调OC方法时，会在上述第4步把ModuleID,MethodID等数据加到一个队列里，等OC过来调JS的任意方法时，再把这个队列返回给OC，此时OC再执行这个队列里要调用的方法。&lt;/p&gt;
 &lt;p&gt;一开始不明白，设计成JS无法直接调用OC，需要在OC去调JS时才通过返回值触发调用，整个程序还能跑得通吗。后来想想纯native开发里的事件响应机制，就有点理解了。native开发里，什么时候会执行代码？只在有事件触发的时候，这个事件可以是启动事件，触摸事件，timer事件，系统事件，回调事件。而在React Native里，这些事件发生时OC都会调用JS相应的模块方法去处理，处理完这些事件后再执行JS想让OC执行的方法，而没有事件发生的时候，是不会执行任何代码的，这跟native开发里事件响应机制是一致的。&lt;/p&gt;
 &lt;p&gt;说到OC调用JS，再补充一下，实际上模块配置表除了有上述OC的模块remoteModules外，还保存了JS模块localModules，OC调JS某些模块的方法时，也是通过传递ModuleID和MethodID去调用的，都会走到  &lt;a href="https://github.com/facebook/react-native/blob/72d3d724a3a0c6bc46981efd0dad8f7f61121a47/React/Base/RCTBridge.m#L641" target="_blank"&gt;-enqueueJSCall:args:&lt;/a&gt;方法把两个ID和参数传给JS的  &lt;a href="https://github.com/facebook/react-native/blob/72d3d724a3a0c6bc46981efd0dad8f7f61121a47/Libraries/Utilities/MessageQueue.js#L298" target="_blank"&gt;BatchedBridge.callFunctionReturnFlushedQueue&lt;/a&gt;，跟JS调OC原理差不多，就不再赘述了。&lt;/p&gt;
 &lt;h2&gt;总结&lt;/h2&gt;
 &lt;p&gt;整个React Native的JS-OC通信机制大致就是这样了，关键点在于：模块化，模块配置表，传递ID，封装调用，事件响应，其设计思想和实现方法很值得学习借鉴。&lt;/p&gt;
 &lt;img alt="" height="1" src="http://feeds.feedburner.com/~r/webbang/~4/Dcxl4piNHLI" width="1"&gt;&lt;/img&gt;&lt;div&gt; &lt;a href="https://itindex.net/"  title="IT 资讯"&gt;&lt;img src="https://itindex.net/images/iconWarning.gif" title="IT 资讯" border="0"/&gt; &lt;/a&gt;</description>
      <category>技术文章 ios 源码解析</category>
      <guid isPermaLink="true">https://itindex.net/detail/53084-react-native-%E9%80%9A%E4%BF%A1</guid>
      <pubDate>Mon, 30 Mar 2015 11:31:08 CST</pubDate>
    </item>
    <item>
      <title>iOS 开发的9个超有用小技巧</title>
      <link>https://itindex.net/detail/53027-ios-%E5%BC%80%E5%8F%91-%E6%8A%80%E5%B7%A7</link>
      <description>&lt;ol&gt;
  &lt;li&gt;
   &lt;p&gt;如何快速的查看一段代码的执行时间。&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
 &lt;p&gt;&lt;/p&gt; &lt;pre&gt;#define TICK   NSDate *startTime = [NSDate date]
#define TOCK   NSLog(@&amp;quot;Time: %f&amp;quot;, -[startTime timeIntervalSinceNow])&lt;/pre&gt; &lt;p&gt;
&lt;/p&gt; &lt;p&gt;在想要查看执行时间的代码的地方进行这么处理&lt;/p&gt;
 &lt;p&gt;&lt;/p&gt; &lt;pre&gt;TICK
//do your work here
TOCK&lt;/pre&gt; &lt;p&gt;
&lt;/p&gt; &lt;p&gt;2.如何快速查看一个函数的调用次数，且不添加一句代码。 如下图&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="iOS &amp;#24320;&amp;#21457;&amp;#30340;9&amp;#20010;&amp;#36229;&amp;#26377;&amp;#29992;&amp;#23567;&amp;#25216;&amp;#24039; - &amp;#31532;1&amp;#24352;  | IT&amp;#27743;&amp;#28246;" src="http://cc.cocimg.com/api/uploads/20150320/1426819836546569.jpg" title="iOS &amp;#24320;&amp;#21457;&amp;#30340;9&amp;#20010;&amp;#36229;&amp;#26377;&amp;#29992;&amp;#23567;&amp;#25216;&amp;#24039; - &amp;#31532;1&amp;#24352;  | IT&amp;#27743;&amp;#28246;"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;这种方法适合于一个if方法，一个for循环，而且不会中断程序，切不需要加一句代码。但是一定要记得选中下面的automatically continue after evaluting actions;&lt;/p&gt;
 &lt;p&gt;3.在使用view的缩放的时候，layer.border.width随着view的放大，会出现锯齿化的问题，解决这个问题需要设置这个属性。&lt;/p&gt;
 &lt;p&gt;&lt;/p&gt; &lt;pre&gt;self.layer.allowsEdgeAntialiasing = YES;&lt;/pre&gt; &lt;p&gt;
&lt;/p&gt; &lt;p&gt;4.instrument中time profile中的self, #self,%self各代表什么 ?&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="iOS &amp;#24320;&amp;#21457;&amp;#30340;9&amp;#20010;&amp;#36229;&amp;#26377;&amp;#29992;&amp;#23567;&amp;#25216;&amp;#24039; - &amp;#31532;2&amp;#24352;  | IT&amp;#27743;&amp;#28246;" src="http://cc.cocimg.com/api/uploads/20150320/1426819891162281.jpg" title="iOS &amp;#24320;&amp;#21457;&amp;#30340;9&amp;#20010;&amp;#36229;&amp;#26377;&amp;#29992;&amp;#23567;&amp;#25216;&amp;#24039; - &amp;#31532;2&amp;#24352;  | IT&amp;#27743;&amp;#28246;"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;下面引用了一下网上的具体内容&lt;/p&gt;
 &lt;p&gt;“Self is &amp;quot;The number of times the symbol calls itself.&amp;quot; according to the Apple Docs on the Time Profiler.&lt;/p&gt;
 &lt;p&gt;From the way the numbers look though, it seems self is the summed duration of samples that had this symbol at the bottom of its stack trace. That would make:&lt;/p&gt;
 &lt;p&gt;self: the number of samples where this symbol was at the bottom of the stack trace&lt;/p&gt;
 &lt;p&gt;% self: the percent of self samples relative to total samples of currently displayed call tree&lt;/p&gt;
 &lt;p&gt;(eg – #self / total samples).&lt;/p&gt;
 &lt;p&gt;So this wouldn&amp;apos;t tell you how many times a method was called. But it would give you an idea how much time is spent in a method or lower in the call tree.”&lt;/p&gt;
 &lt;p&gt;5.如何快速添加一个全局异常断点，（一步顶三步）。和添加一个symbloic断点，（一步还是顶三步）添加一个断点不使用鼠标&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="iOS &amp;#24320;&amp;#21457;&amp;#30340;9&amp;#20010;&amp;#36229;&amp;#26377;&amp;#29992;&amp;#23567;&amp;#25216;&amp;#24039; - &amp;#31532;3&amp;#24352;  | IT&amp;#27743;&amp;#28246;" border="0" src="http://cc.cocimg.com/api/uploads/20150320/1426819934867664.gif" title="iOS &amp;#24320;&amp;#21457;&amp;#30340;9&amp;#20010;&amp;#36229;&amp;#26377;&amp;#29992;&amp;#23567;&amp;#25216;&amp;#24039; - &amp;#31532;3&amp;#24352;  | IT&amp;#27743;&amp;#28246;" vspace="0"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;看到图了吧，加这些断点完全不需要动一下鼠标（恕我吹牛B了），加单独断点的时候动了下鼠标，但那是我故意让你们看到我是有鼠标的。&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;
   &lt;p&gt;首先All Exception断点是我们很常用的，这个我是这样用一个快捷键做到的！通过改键command +p 为加异常断点的，要说怎么改，翻我    &lt;a href="http://www.jianshu.com/users/8d704c0faf00/latest_articles" target="_blank" title="&amp;#20197;&amp;#21069;&amp;#21338;&amp;#23458;"&gt;以前博客&lt;/a&gt;去！&lt;/p&gt;
&lt;/li&gt;
  &lt;li&gt;
   &lt;p&gt;然后加symbloic的快捷键为commnamd + option + \&lt;/p&gt;
&lt;/li&gt;
  &lt;li&gt;
   &lt;p&gt;加一个单独一行断点的快捷键为command + \&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;6.在iOS开发中我们在和产品和设计沟通的时候我们经常需要截取手机的屏幕或者模拟器上的屏幕，我们用手机可能会使用 Home 键 + 开机键，然后再通过 iPhoto 或者在手机用 qq 传过去，但是我教大家一个方法直接使用快捷键截取手机上的图到电脑桌面上。  &lt;br /&gt;&lt;/p&gt;
 &lt;p&gt;具体方法见下图 ：&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="iOS &amp;#24320;&amp;#21457;&amp;#30340;9&amp;#20010;&amp;#36229;&amp;#26377;&amp;#29992;&amp;#23567;&amp;#25216;&amp;#24039; - &amp;#31532;4&amp;#24352;  | IT&amp;#27743;&amp;#28246;" src="http://cc.cocimg.com/api/uploads/20150320/1426820096813789.jpg" title="iOS &amp;#24320;&amp;#21457;&amp;#30340;9&amp;#20010;&amp;#36229;&amp;#26377;&amp;#29992;&amp;#23567;&amp;#25216;&amp;#24039; - &amp;#31532;4&amp;#24352;  | IT&amp;#27743;&amp;#28246;"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;在 Xcode的 debug菜单中找到viewDebugging,即使当前程序没有运行，也可以直接截取手机上的图片直接到桌面。（哈哈哈这样再不需要TM的按TM的手机上的按键再用 iPhoto拷贝到桌面了）。年轻人你以为这样就完了吗！？你还是太稚嫩啊，谁TM的想找到这个debug菜单再找到下面的一堆东西，当然要改成快捷键了，如何做看下图。&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="iOS &amp;#24320;&amp;#21457;&amp;#30340;9&amp;#20010;&amp;#36229;&amp;#26377;&amp;#29992;&amp;#23567;&amp;#25216;&amp;#24039; - &amp;#31532;5&amp;#24352;  | IT&amp;#27743;&amp;#28246;" src="http://cc.cocimg.com/api/uploads/20150320/1426820115340471.jpg" title="iOS &amp;#24320;&amp;#21457;&amp;#30340;9&amp;#20010;&amp;#36229;&amp;#26377;&amp;#29992;&amp;#23567;&amp;#25216;&amp;#24039; - &amp;#31532;5&amp;#24352;  | IT&amp;#27743;&amp;#28246;"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;看到这个血淋漓的红色的箭头了嘛，你首先找到 debug 的快捷键菜单项，在把它改成 ?+?这个，这时候有冲突了怎么办？你不知道有没有影响到其他快捷键怎么办，小傻瓜，改呗！把以前的这个功能去掉?+?（ps:以前的就是 show complete list 如同点击一个?一个效果，那你还要它做嘛啊?），为什么改成这个份听哥的，你改成这个绝壁会用着特别爽。（好了以后要给产品还是设计发图分分钟的事情了~~）&lt;/p&gt;
 &lt;p&gt;7.iOS调试技巧只显示图片的对齐尺寸和 frame。&lt;/p&gt;
 &lt;p&gt;我记得以前一个说显示对齐尺寸的，他是这么做的：&lt;/p&gt;
 &lt;p&gt;“在应项目的Edit Scheme中设置一个启动参数 UIViewShowAlignmentRects并将参数值设置为YES，可以让程序在运行时显示视图的对齐矩阵（alignment rectangle）。”&lt;/p&gt;
 &lt;p&gt;我当时看完就不屑一顾，这么复杂谁用啊。你们真的以为我再装b嘛，好吧，你们猜对了，我确实在装b,你们看下面的效果就知道我为什么在装b了。&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="iOS &amp;#24320;&amp;#21457;&amp;#30340;9&amp;#20010;&amp;#36229;&amp;#26377;&amp;#29992;&amp;#23567;&amp;#25216;&amp;#24039; - &amp;#31532;6&amp;#24352;  | IT&amp;#27743;&amp;#28246;" border="0" src="http://cc.cocimg.com/api/uploads/20150320/1426820189693015.gif" title="iOS &amp;#24320;&amp;#21457;&amp;#30340;9&amp;#20010;&amp;#36229;&amp;#26377;&amp;#29992;&amp;#23567;&amp;#25216;&amp;#24039; - &amp;#31532;6&amp;#24352;  | IT&amp;#27743;&amp;#28246;" vspace="0"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;可能我图片切换的比较快，效果你们没看的明显。你们可以自己试下，这个可以随时切换是否显示ShowAlignmentRect，或者每一个控件的尺寸包括系统的控件（譬如系统的 uibutton 它会显示内部的 imageview 的尺寸和 label 的尺寸然后用不同的颜色区别，xcode 颜色区分的还是相当美观的）&lt;/p&gt;
 &lt;p&gt;这尼玛可是大杀器了，有了这个你的控件的大小位置是否显示都根本不用我前面所说的 lldb 了，而且方便快捷，你问我是怎么做的，我可没配置辣么多的一对参数，我也懒得记，当然我是用快捷键了！细心的同学可能会注意到前面的 截图viewDebuging中有showViewFrame 和ShowAlignmentRects,当然点击这些菜单就会出现我这些效果了，我当然又是改快捷键了，我时间很宝贵的。&lt;/p&gt;
 &lt;p&gt;8.在我们开发中经常使用 git，然后我们修改了这个文件不知道哪里改错了，我们经常需要恢复这个文件，这样我们可能会切到终端，也可能会直接使用 xcode来去放弃文件的修改，幸好xcode对 source Control支持的比较好，所以我们可以通过修改这些方式的快捷键来快速恢复文件，下面是我的一些设置，用好了绝壁提高你的效率.&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="iOS &amp;#24320;&amp;#21457;&amp;#30340;9&amp;#20010;&amp;#36229;&amp;#26377;&amp;#29992;&amp;#23567;&amp;#25216;&amp;#24039; - &amp;#31532;7&amp;#24352;  | IT&amp;#27743;&amp;#28246;" src="http://cc.cocimg.com/api/uploads/20150320/1426820313902206.jpg" title="iOS &amp;#24320;&amp;#21457;&amp;#30340;9&amp;#20010;&amp;#36229;&amp;#26377;&amp;#29992;&amp;#23567;&amp;#25216;&amp;#24039; - &amp;#31532;7&amp;#24352;  | IT&amp;#27743;&amp;#28246;"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;对于我来说常用的也就是放弃当前文件的修改和放弃所有文件的修改，和翻看git History如果当前文件修改的时候怎么都回退不到正确的，这几个东西可起大作用了!&lt;/p&gt;
 &lt;p&gt;9.在 iOS 中我们经常会碰到一些imagView的UIContentMode的显示方式，最初我怎么记也搞不太清楚，后来我看到了这幅图，我发现我遇到了指明灯了，一图解千惑，这里分享给大家。(ps：大家别说我菜啊)&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="iOS &amp;#24320;&amp;#21457;&amp;#30340;9&amp;#20010;&amp;#36229;&amp;#26377;&amp;#29992;&amp;#23567;&amp;#25216;&amp;#24039; - &amp;#31532;8&amp;#24352;  | IT&amp;#27743;&amp;#28246;" src="http://cc.cocimg.com/api/uploads/20150320/1426820333265272.jpg" title="iOS &amp;#24320;&amp;#21457;&amp;#30340;9&amp;#20010;&amp;#36229;&amp;#26377;&amp;#29992;&amp;#23567;&amp;#25216;&amp;#24039; - &amp;#31532;8&amp;#24352;  | IT&amp;#27743;&amp;#28246;"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;来源：  &lt;a href="http://www.jianshu.com/p/221507eb8590" target="_blank"&gt;kissGod的简书&lt;/a&gt;&lt;/p&gt;
 &lt;hr&gt;&lt;/hr&gt;
 &lt;p&gt;IT江湖iOS 客户端正式上线。你想要看IT资讯，精彩趣文，你想要分享，下载IT江湖iOS客户端.  &lt;br /&gt;&lt;/p&gt;
 &lt;p&gt;IT江湖，每一个IT人的江湖。&lt;/p&gt;
 &lt;p&gt;AppStore下载地址：  &lt;a href="http://url.cn/Ub94qF"&gt;http://url.cn/Ub94qF&lt;/a&gt;  或者直接搜索“IT江湖”&lt;/p&gt;
 &lt;p&gt;IT江湖iOS客户端是一个开源项目，Swift编写&lt;/p&gt;
 &lt;p&gt;源码地址：  &lt;a href="https://github.com/itjhDev/itjh"&gt;https://github.com/itjhDev/itjh&lt;/a&gt; ，开源＋＋&lt;/p&gt;
 &lt;p&gt;如果想加入IT江湖iOS客户端的开发,请联系 iosdev@itjh.com.cn  IT江湖期待你的加入&lt;/p&gt;
 &lt;h3&gt;
  &lt;hr&gt;&lt;/hr&gt;
  &lt;p&gt;欢迎来到IT江湖,加入我们官方群 383126909,学习更多,共同发展.   &lt;br /&gt;&lt;/p&gt;
  &lt;p&gt;关注“IT江湖”微信公众号,每日推送优质文章,丰富大家的知识.&lt;/p&gt;
  &lt;p&gt;微信扫一扫或者搜索 “itjh0223”   IT江湖,每一个IT人的江湖!&lt;/p&gt;
  &lt;p&gt;   &lt;a href="http://www.itjhwd.com/wp-content/uploads/2014/10/17341412928018.jpg"&gt;    &lt;img alt="iOS &amp;#24320;&amp;#21457;&amp;#30340;9&amp;#20010;&amp;#36229;&amp;#26377;&amp;#29992;&amp;#23567;&amp;#25216;&amp;#24039; - &amp;#31532;9&amp;#24352;  | IT&amp;#27743;&amp;#28246;" height="150" src="http://www.itjhwd.com/wp-content/uploads/2014/10/17341412928018.jpg" title="iOS &amp;#24320;&amp;#21457;&amp;#30340;9&amp;#20010;&amp;#36229;&amp;#26377;&amp;#29992;&amp;#23567;&amp;#25216;&amp;#24039; - &amp;#31532;9&amp;#24352;  | IT&amp;#27743;&amp;#28246;" width="715"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/h3&gt;
&lt;div&gt; &lt;a href="https://itindex.net/"  title="IT 资讯"&gt;&lt;img src="https://itindex.net/images/iconWarning.gif" title="IT 资讯" border="0"/&gt; &lt;/a&gt;</description>
      <category>ios</category>
      <guid isPermaLink="true">https://itindex.net/detail/53027-ios-%E5%BC%80%E5%8F%91-%E6%8A%80%E5%B7%A7</guid>
      <pubDate>Tue, 24 Mar 2015 11:46:51 CST</pubDate>
    </item>
    <item>
      <title>移动端图片格式调研</title>
      <link>https://itindex.net/detail/54615-%E7%A7%BB%E5%8A%A8-%E5%9B%BE%E7%89%87-%E6%A0%BC%E5%BC%8F</link>
      <description>&lt;p&gt;图片通常是移动端流量耗费最多的部分，并且占据着重要的视觉空间。合理的图片格式选用和优化可以为你节省带宽、提升视觉效果。在这篇文章里我会分析一下目前主流和新兴的几种图片格式的特点、性能分析、参数调优，以及相关开源库的选择。&lt;/p&gt;
 &lt;p&gt;Index  &lt;br /&gt;
  &lt;a href="http://blog.ibireme.com#intro"&gt;几种图片格式简介&lt;/a&gt;  &lt;br /&gt;
  &lt;a href="http://blog.ibireme.com#mobile"&gt;移动端图片类型的支持情况&lt;/a&gt;  &lt;br /&gt;
  &lt;a href="http://blog.ibireme.com#still"&gt;静态图片的编码与解码&lt;/a&gt;  &lt;br /&gt;
  &lt;a href="http://blog.ibireme.com#still_jpeg"&gt;JPEG&lt;/a&gt;  &lt;br /&gt;
  &lt;a href="http://blog.ibireme.com#still_png"&gt;PNG&lt;/a&gt;  &lt;br /&gt;
  &lt;a href="http://blog.ibireme.com#still_webp"&gt;WebP&lt;/a&gt;  &lt;br /&gt;
  &lt;a href="http://blog.ibireme.com#still_bpg"&gt;BPG&lt;/a&gt;  &lt;br /&gt;
  &lt;a href="http://blog.ibireme.com#ani"&gt;动态图片的编码与解码&lt;/a&gt;  &lt;br /&gt;
  &lt;a href="http://blog.ibireme.com#ani_gif"&gt;GIF&lt;/a&gt;  &lt;br /&gt;
  &lt;a href="http://blog.ibireme.com#ani_apng"&gt;APNG&lt;/a&gt;  &lt;br /&gt;
  &lt;a href="http://blog.ibireme.com#ani_webp"&gt;WebP&lt;/a&gt;  &lt;br /&gt;
  &lt;a href="http://blog.ibireme.com#ani_bpg"&gt;BPG&lt;/a&gt;  &lt;br /&gt;
  &lt;a href="http://blog.ibireme.com#ani_bench"&gt;动图性能对比&lt;/a&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;br /&gt;
  &lt;a name="intro"&gt;&lt;/a&gt;&lt;/p&gt;
 &lt;h2&gt;几种图片格式的简介&lt;/h2&gt;
 &lt;p&gt;首先谈一下大家耳熟能详的几种老牌的图片格式吧：&lt;/p&gt;
 &lt;p&gt;JPEG 是目前最常见的图片格式，它诞生于 1992 年，是一个很古老的格式。它只支持有损压缩，其压缩算法可以精确控制压缩比，以图像质量换得存储空间。由于它太过常见，以至于许多移动设备的 CPU 都支持针对它的硬编码与硬解码。&lt;/p&gt;
 &lt;p&gt;PNG 诞生在 1995 年，比 JPEG 晚几年。它本身的设计目的是替代 GIF 格式，所以它与 GIF 有更多相似的地方。PNG 只支持无损压缩，所以它的压缩比是有上限的。相对于 JPEG 和 GIF 来说，它最大的优势在于支持完整的透明通道。&lt;/p&gt;
 &lt;p&gt;GIF 诞生于 1987 年，随着初代互联网流行开来。它有很多缺点，比如通常情况下只支持 256 种颜色、透明通道只有 1 bit、文件压缩比不高。它唯一的优势就是支持多帧动画，凭借这个特性，它得以从 Windows 1.0 时代流行至今，而且仍然大受欢迎。&lt;/p&gt;
 &lt;p&gt;在上面这些图片格式诞生后，也有不少公司或团体尝试对他们进行改进，或者创造其他更加优秀的图片格式，比如 JPEG 小组的 JPEG 2000、微软的 JPEG-XR、Google 的 WebP、个人开发者发布的 BPG、FLIF 等。它们相对于老牌的那几个图片格式来说有了很大的进步，但出于各种各样的原因，只有少数几个格式能够流行开来。下面三种就是目前实力比较强的新兴格式了：&lt;/p&gt;
 &lt;p&gt;APNG 是 Mozilla 在 2008 年发布的一种图片格式，旨在替换掉画质低劣的 GIF 动画。它实际上只是相当于 PNG 格式的一个扩展，所以 Mozilla 一直想把它合并到 PNG 标准里面去。然而 PNG 开发组并没有接受 APNG 这个扩展，而是一直在推进它自己的 MNG 动图格式。MNG 格式过于复杂以至于并没有什么系统或浏览器支持，而 APNG 格式由于简单容易实现，目前已经渐渐流行开来。Mozilla 自己的 Firefox 首先支持了 APNG，随后苹果的 Safari 也开始有了支持， Chrome 目前也  &lt;a href="https://codereview.chromium.org/1250373006/" target="_blank"&gt;已经尝试&lt;/a&gt;开始支持 ，可以说未来前景很好。&lt;/p&gt;
 &lt;p&gt;WebP 是 Google 在 2010 年发布的图片格式，希望以更高的压缩比替代 JPEG。它用 VP8 视频帧内编码作为其算法基础，取得了不错的压缩效果。它支持有损和无损压缩、支持完整的透明通道、也支持多帧动画，并且没有版权问题，是一种非常理想的图片格式。借由 Google 在网络世界的影响力，WebP 在几年的时间内已经得到了广泛的应用。看看你手机里的 App：微博、微信、QQ、淘宝、网易新闻等等，每个 App 里都有 WebP 的身影。Facebook 则更进一步，用 WebP 来显示聊天界面的贴纸动画。&lt;/p&gt;
 &lt;p&gt;BPG 是著名程序员 Fabrice Bellard 在去年 (2014年) 发布的一款超高压缩比的图片格式。这个程序员大家可能有些面生，但说起他的作品 FFmpeg、QEMU 大家想必是都知道的。BPG 使用 HEVC (即 H.265) 帧内编码作为其算法基础，就这点而言，它毋庸置疑是当下最为先进的图片压缩格式。相对于 JP2、JPEG-XR、WebP 来说，同等体积下 BPG 能提供更高的图像质量。另外，得益于它本身基于视频编码算法的特性，它能以非常小的文件体积保存多帧动画。 Fabrice Bellard 聪明的地方在于，他知道自己一个人无法得到各大浏览器厂商的支持，所以他还特地开发了 Javascript 版的解码器，任何浏览器只要加载了这个 76KB 大小的 JS 文件，就可以直接显示 BPG 格式的图片了。目前阻碍它流行的原因就是 HEVC 的版权问题和它较长的编码解码时间。尽管这个图片格式才刚刚发布一年，但已经有不少厂子开始试用了，比如  &lt;a href="http://www.infoq.com/cn/news/2015/08/onesdk-shoutao" target="_blank"&gt;阿里&lt;/a&gt;和  &lt;a href="http://www.infoq.com/cn/articles/how-subject-test-works" target="_blank"&gt;腾讯&lt;/a&gt;。&lt;/p&gt;
 &lt;p&gt;  &lt;a name="mobile"&gt;&lt;/a&gt;&lt;/p&gt;
 &lt;h2&gt;移动端图片类型的支持情况&lt;/h2&gt;
 &lt;p&gt;目前主流的移动端对图片格式的支持情况如何呢？我们分别来看一下 Android 和 iOS 目前的图片编解码架构吧：&lt;/p&gt;
 &lt;p&gt;  &lt;a href="http://blog.ibireme.com/wp-content/uploads/2015/11/mobile_image_arch.png"&gt;   &lt;img alt="mobile_image_arch" height="229" src="http://blog.ibireme.com/wp-content/uploads/2015/11/mobile_image_arch.png" width="635"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;
 &lt;p&gt;Android 的图片编码解码是由 Skia 图形库负责的，Skia 通过挂接第三方开源库实现了常见的图片格式的编解码支持。目前来说，Android 原生支持的格式只有 JPEG、PNG、GIF、BMP 和 WebP (Android 4.0 加入)，在上层能直接调用的编码方式也只有 JPEG、PNG、WebP 这三种。目前来说 Android 还不支持直接的动图编解码。&lt;/p&gt;
 &lt;p&gt;iOS 底层是用 ImageIO.framework 实现的图片编解码。目前 iOS 原生支持的格式有：JPEG、JPEG2000、PNG、GIF、BMP、ICO、TIFF、PICT，自 iOS 8.0 起，ImageIO 又加入了 APNG、SVG、RAW 格式的支持。在上层，开发者可以直接调用 ImageIO 对上面这些图片格式进行编码和解码。对于动图来说，开发者可以解码动画 GIF 和 APNG、可以编码动画 GIF。&lt;/p&gt;
 &lt;p&gt;两个平台在导入第三方编解码库时，都多少对他们进行了一些修改，比如 Android 对 libjpeg 等进行的调整以更好的控制内存，iOS 对 libpng 进行了修改以支持 APNG，并增加了多线程编解码的特性。除此之外，iOS 专门针对 JPEG 的编解码开发了 AppleJPEG.framework，实现了性能更高的硬编码和硬解码，只有当硬编码解码失败时，libjpeg 才会被用到。&lt;/p&gt;
 &lt;p&gt;  &lt;a name="still"&gt;&lt;/a&gt;&lt;/p&gt;
 &lt;h2&gt;静态图片的编码与解码&lt;/h2&gt;
 &lt;p&gt;由于我目前主要是做 iOS 开发，所以下面的性能评测都是基于 iPhone 的，主要测试代码可以在  &lt;a href="https://github.com/ibireme/YYWebImage/blob/master/Demo/YYWebImageDemo/YYImageBenchmark.m" target="_blank"&gt;这里&lt;/a&gt;看到。测试素材很少，只有两个：&lt;/p&gt;
 &lt;p&gt;  &lt;a href="http://blog.ibireme.com/wp-content/uploads/2015/11/dribbble512_pngcrush.png"&gt;   &lt;img alt="dribbble512_pngcrush" height="256" src="http://blog.ibireme.com/wp-content/uploads/2015/11/dribbble512_pngcrush.png" width="256"&gt;&lt;/img&gt;   &lt;img alt="lena512_weibo" height="256" src="http://blog.ibireme.com/wp-content/uploads/2015/11/lena512_weibo.jpg" width="256"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;
 &lt;p&gt;第一张是Dribbble 的 Logo，包含 Alpha 通道，用于测试简单的、图形类的图像。  &lt;br /&gt;
第二张经典的 Lena 图，用于测试照片类的、具有丰富细节的图像。  &lt;br /&gt;
每个图像都有 64x64、128x128、256x256、512x512 四种分辨率。&lt;/p&gt;
 &lt;p&gt;  &lt;a name="still_jpeg"&gt;&lt;/a&gt;&lt;/p&gt;
 &lt;h3&gt;  &lt;strong&gt;JPEG&lt;/strong&gt;&lt;/h3&gt;
 &lt;p&gt;目前比较知名的 JPEG 库有以下三个：&lt;/p&gt;
 &lt;p&gt;      &lt;a href="http://libjpeg.sourceforge.net/" target="_blank"&gt;libjpeg&lt;/a&gt;：开发时间最早，使用最广泛的 JPEG 库。由于 JPEG 标准过于复杂和模糊，并没有其他人去实现，所以这个库是 JPEG 的事实标准。&lt;/p&gt;
 &lt;p&gt;      &lt;a href="http://libjpeg-turbo.virtualgl.org/" target="_blank"&gt;libjpeg-turbo&lt;/a&gt;：一个致力于提升编解码速度的 JPEG 库。它基于 libjpeg 进行了改造，用 SIMD 指令集 (MMX、SSE2、NEON) 重写了部分代码，官网称相对于 libjpeg 有 2 到 4 倍的性能提升。&lt;/p&gt;
 &lt;p&gt;      &lt;a href="http://mozjpeg.codelove.de/" target="_blank"&gt;MozJPEG&lt;/a&gt;： 一个致力于提升压缩比的 JPEG 库。它是 Mozilla 在 2014 年发布的基于 libjpeg-turbo 进行改造的库，相对于 libjpeg 有 5% ~ 15%  的压缩比提升，但相应的其编码速度也慢了很多。&lt;/p&gt;
 &lt;p&gt;除了上面这三个库，苹果自己也开发了一个 AppleJPEG，但并没有开源。其调用了芯片提供的 DSP 硬编码和硬解码的功能。虽然它不如上面这三个库功能完善，但其性能非常高。在我的测试中，其编解码速度通常是 libjpeg-turbo 的 1~2 倍。可惜的是，目前开发者并不能直接访问这个库。&lt;/p&gt;
 &lt;p&gt;下面是 ImageIO (AppleJPEG/libpng) 在 iPhone 6 上的编解码性能：&lt;/p&gt;
 &lt;p&gt;  &lt;a href="http://blog.ibireme.com/wp-content/uploads/2015/11/jpeg_bench_dribbble.png"&gt;   &lt;img alt="jpeg_bench_dribbble" height="242" src="http://blog.ibireme.com/wp-content/uploads/2015/11/jpeg_bench_dribbble.png" width="676"&gt;&lt;/img&gt;&lt;/a&gt;   &lt;a href="http://blog.ibireme.com/wp-content/uploads/2015/11/jpeg_bench_lena.png"&gt;   &lt;img alt="jpeg_bench_lena" height="240" src="http://blog.ibireme.com/wp-content/uploads/2015/11/jpeg_bench_lena.png" width="675"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;
 &lt;p&gt;可以看到，JPEG 编码中 quality 越小，图片体积就越小，质量越也差，编码时间也越短。解码时间并没有很大的差距，可能是其大部分时间消耗在了函数调用、硬件调用上。苹果在自己的相册 Demo 中提供的 quality 的默认值是 0.9，在这个值附近，图像质量和体积、编码解码时间之间都能取得不错的平衡。&lt;/p&gt;
 &lt;p&gt;  &lt;a name="still_png"&gt;&lt;/a&gt;&lt;/p&gt;
 &lt;h3&gt;  &lt;strong&gt;PNG&lt;/strong&gt;&lt;/h3&gt;
 &lt;p&gt;相对于 JPEG 来说，PNG 标准更为清晰和简单，因此有很多公司或个人都有自己的 PNG 编码解码实现。但目前使用最广的还是 PNG 官方发布的   &lt;a href="http://www.libpng.org/pub/png/libpng.html" target="_blank"&gt;libpng&lt;/a&gt; 库。iOS 和 Android 底层都是调用这个库实现的 PNG 编解码。&lt;/p&gt;
 &lt;p&gt;下面是 PNG 在 iPhone 6 上的编解码性能：&lt;/p&gt;
 &lt;p&gt;  &lt;a href="http://blog.ibireme.com/wp-content/uploads/2015/11/jpeg_png_bench.png"&gt;   &lt;img alt="jpeg_png_bench" height="260" src="http://blog.ibireme.com/wp-content/uploads/2015/11/jpeg_png_bench.png" width="676"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;
 &lt;p&gt;可以看到，在编解码图形类型（颜色少、细节少）的图片时，PNG 和 JPEG 差距并不大；但是对于照片类型（颜色和细节丰富）的图片来说，PNG 在文件体积、编解码速度上都差 JPEG 不少了。&lt;/p&gt;
 &lt;p&gt;和 JPEG 不同，PNG 是无损压缩，其并不能提供压缩比的选项，其压缩比是有上限的。目前网上有很多针对 PNG 进行优化的工具和服务，旨在提升 PNG 的压缩比。下面是常见的几个 PNG 压缩工具的性能对比：&lt;/p&gt;
 &lt;p&gt;  &lt;a href="http://blog.ibireme.com/wp-content/uploads/2015/11/png_tools_bench.png"&gt;   &lt;img alt="png_tools_bench" height="306" src="http://blog.ibireme.com/wp-content/uploads/2015/11/png_tools_bench.png" width="592"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;a href="http://pmt.sourceforge.net/pngcrush/" target="_blank"&gt;pngcrush&lt;/a&gt; 是 Xcode 自带的 PNG 压缩工具，相对于设计师用 Photoshop 生成的图片来说，它能取得不错的压缩效果。  &lt;a href="https://imageoptim.com/" target="_blank"&gt;ImageOptim&lt;/a&gt; 则更进一步，对每张图用多种缩算法进行比对，选择压缩比更高的结果，进一步缩小了文件体积。  &lt;a href="https://tinypng.com/" target="_blank"&gt;TinyPNG.com&lt;/a&gt; 相对于其他工具来说，压缩比高得不像话。它启用了类似 GIF 那样的颜色索引表对 PNG 进行压缩，所以会导致颜色丰富的图片丢失掉一部分细节。如果使用 TinyPNG 的话，最好在压缩完成后让设计师看一下颜色效果是否可以接受。&lt;/p&gt;
 &lt;p&gt;  &lt;a name="still_webp"&gt;&lt;/a&gt;&lt;/p&gt;
 &lt;h3&gt;  &lt;strong&gt;WebP&lt;/strong&gt;&lt;/h3&gt;
 &lt;p&gt;WebP 标准是 Google 定制的，迄今为止也只有 Google 发布的   &lt;a href="https://developers.google.com/speed/webp/" target="_blank"&gt;libwebp&lt;/a&gt; 实现了该的编解码 。 所以这个库也是该格式的事实标准。&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;WebP 编码主要有几个参数：&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;    lossless: YES:有损编码 NO:无损编码。WebP 主要优势在于有损编码，其无损编码的性能和压缩比表现一般。&lt;/p&gt;
 &lt;p&gt;    quality: [0~100] 图像质量，0表示最差质量，文件体积最小，细节损失严重，100表示最高图像质量，文件体积较大。该参数只针对有损压缩有明显效果。Google 官方的建议是 75，腾讯在  &lt;a href="http://isux.tencent.com/introduction-of-webp.html" target="_blank"&gt;对 WebP 评测&lt;/a&gt;时给出的建议也是 75。在这个值附近，WebP 能在压缩比、图像质量上取得较好的平衡。&lt;/p&gt;
 &lt;p&gt;    method: [0~6] 压缩比，0表示快速压缩，耗时短，压缩质量一般，6表示极限压缩，耗时长，压缩质量好。该参数也只针对有损压缩有明显效果。调节该参数最高能带来 20% ～ 40% 的更高压缩比，但相应的编码时间会增加 5～20 倍。Google 推荐的值是 4。&lt;/p&gt;
 &lt;p&gt;对于编码无损图片来说，quality=0, method=0~3 是相对来说比较合适的参数，能够节省编码时间，同时也有不错的压缩比。无损编码图片，quality=75, method=2~4 是比较合适的参数，能在编码时间、图片质量、文件体积之间有着不错的平衡。&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;WebP 解码有三个参数：&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;    use_threads: 是否启用 pthread 多线程解码。该参数只对宽度大于 512 的有损图片起作用。开启后内部会用多线程解码，CPU 占用会更高，解码时间平均能缩短 10%～20%。&lt;/p&gt;
 &lt;p&gt;    bypass_filtering: 是否禁用滤波。该参数只对有损图片起作用，开启后大约能缩短 5%～10% 的解码时间，但会造成一些颜色过渡平滑的区域产生色带（banding）。&lt;/p&gt;
 &lt;p&gt;    no_fancy_upsampling: 是否禁用上采样。该参数只对有损图片起作用。在我的测试中，开启该参数后，解码时间反而会增加 5～25%，同时会造成一些图像细节的丢失，线条边缘会增加杂色，显得不自然。&lt;/p&gt;
 &lt;p&gt;通常情况下，这三个参数都设为 NO 即可，如果要追求更高的解码速度，则可以尝试开启 use_threads 和 bypass_filtering 这两个参数。而 no_fancy_upsampling 在任何情况下都没必要开启。&lt;/p&gt;
 &lt;p&gt;由于 WebP 测试数据较多，这里只贴一下 512x512 大小的一部分测试结果，感兴趣的可以看文章结尾处的 Excel 附件。&lt;/p&gt;
 &lt;p&gt;  &lt;a href="http://blog.ibireme.com/wp-content/uploads/2015/11/webp_bench.png"&gt;   &lt;img alt="webp_bench" height="497" src="http://blog.ibireme.com/wp-content/uploads/2015/11/webp_bench.png" width="737"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;
 &lt;p&gt;对于简单的图形类型的图像（比如 App 内的各种 UI 素材），WebP 无损压缩的文件体积和解码速度已经比 PNG 还要理想了，如果你想要对 App 安装包体积进行优化，WebP 已经是个不错的选择了。&lt;/p&gt;
 &lt;p&gt;对于复杂的图像（比如照片）来说，WebP 无损编码表现并不好，但有损编码表现却非常棒。相近质量的图片解码速度 WebP 相距 JPEG 已经差不了太多了，而文件压缩比却能提升不少。&lt;/p&gt;
 &lt;p&gt;  &lt;a name="still_bpg"&gt;&lt;/a&gt;&lt;/p&gt;
 &lt;h3&gt;  &lt;strong&gt;BPG&lt;/strong&gt;&lt;/h3&gt;
 &lt;p&gt;BPG 是目前已知最优秀的有损压缩格式了，它能在相同质量下比 JPEG 减少 50% 的体积。下面是经典的 Lena 图的对比，你也可以在  &lt;a href="http://xooyoozoo.github.io/yolo-octo-bugfixes/#cologne-cathedral&amp;webp=s&amp;bpg=s" target="_blank"&gt;这里&lt;/a&gt;看到大量其他图片的 BPG、JPEG、JPEG2000、JPEG-XR、WebP 压缩效果的在线对比，效果非常明显。&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="bpg_demo" height="541" src="http://blog.ibireme.com/wp-content/uploads/2015/11/bpg_demo.png" width="520"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;BPG 目前只有作者发布的   &lt;a href="http://bellard.org/bpg/" target="_blank"&gt;libbpg&lt;/a&gt; 可用。但作者基于 libbpg 编译出了一个 Javascript 解码器，很大的扩展了可用范围。bpg 可以以无损和有损压缩两种方式进行编码，有损压缩时可以用 quality 参数控制压缩比，可选范围为 0～51，数值越大压缩比越高。通常来说，25 附近是一个不错的选择，BPG 官方工具默认值是 28。&lt;/p&gt;
 &lt;p&gt;libbpg 目前并没有针对 ARM NEON 做优化，所以其在移动端的性能表现一般。下面是 iPhone 6 上的性能测试：&lt;/p&gt;
 &lt;p&gt;  &lt;a href="http://blog.ibireme.com/wp-content/uploads/2015/11/bpg_bench.png"&gt;   &lt;img alt="bpg_bench" height="527" src="http://blog.ibireme.com/wp-content/uploads/2015/11/bpg_bench.png" width="524"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;
 &lt;p&gt;由于 bpg 编码时间太长，我并没有将数据放到表格里。可以看到相同质量下，BPG 的解码速度还是差 JPEG 太多，大约慢了 3～5 倍。目前来说，BPG 适用于那些对流量非常敏感，但对解码时间不敏感的地方。从网上的新闻来看，手机淘宝和手机QQ都已经有所尝试，但不清楚他们是否对 BPG 解码进行了优化。&lt;/p&gt;
 &lt;p&gt;  &lt;a name="ani"&gt;&lt;/a&gt;&lt;/p&gt;
 &lt;h2&gt;动态图片的编码与解码&lt;/h2&gt;
 &lt;p&gt;动图在网络上非常受欢迎，它近似视频，但通常实现简单、文件体积小，应用范围非常广泛。动图的始祖是 GIF，它自 Windows 1.0 时代就在互联网上流行开来，直到今天仍然难以被其他格式取代。尽管它非常古老，但其所用的原理和今天几种新兴格式几乎一样。&lt;/p&gt;
 &lt;p&gt;下面是一张 GIF 格式的 QQ 大表情：&lt;/p&gt;
 &lt;p&gt;  &lt;a href="http://blog.ibireme.com/wp-content/uploads/2015/11/bench_gif_demo.gif"&gt;   &lt;img alt="bench_gif_demo" height="110" src="http://blog.ibireme.com/wp-content/uploads/2015/11/bench_gif_demo.gif" width="110"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;
 &lt;p&gt;这张表情由 6 幅静态图构成，每幅图片有一定的存活时间，连贯播放就形成了动画：&lt;/p&gt;
 &lt;p&gt;  &lt;a href="http://blog.ibireme.com/wp-content/uploads/2015/11/bench_gif_demo1.png"&gt;   &lt;img alt="bench_gif_demo1" height="124" src="http://blog.ibireme.com/wp-content/uploads/2015/11/bench_gif_demo1.png" width="716"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;
 &lt;p&gt;这几张图中，大部分内容是相近的，为了压缩文件体积，通常动图格式都支持一些特殊的方式对相似图片进行裁剪，只保留前后帧不同的部分：&lt;/p&gt;
 &lt;p&gt;  &lt;a href="http://blog.ibireme.com/wp-content/uploads/2015/11/bench_gif_demo2.png"&gt;   &lt;img alt="bench_gif_demo2" height="124" src="http://blog.ibireme.com/wp-content/uploads/2015/11/bench_gif_demo2.png" width="700"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;
 &lt;p&gt;在解码动图时，解码器通常采用所谓&amp;quot;画布模式&amp;quot;进行渲染。想象一下：播放的区域是一张画布，第一帧播放前先把画布清空，然后完整的绘制上第一帧图；播放第二帧时，不再清空画布，而是只把和第一帧不同的区域覆盖到画布上，就像油画的创作那样。&lt;/p&gt;
 &lt;p&gt;像这样的第一帧就被称为关键帧（即 I 帧，帧内编码帧），而后续的那些通过补偿计算得到的帧被称为预测编码帧（P帧）。一个压缩的比较好的动图内，通常只有少量的关键帧，而其余都是预测编码帧；一个较差的压缩工具制作的动图内，则基本都是关键帧。不同的动图压缩工具通常能得到不同的结果。&lt;/p&gt;
 &lt;p&gt;除此之外，动图格式通常有更为详细的参数控制每一帧的绘制过程，下面是 GIF/APNG/WebP 通用的几个参数：&lt;/p&gt;
 &lt;p&gt;Disposal Method (清除方式)   &lt;br /&gt;
   Do Not Dispose：把当前帧增量绘制到画布上，不清空画布。  &lt;br /&gt;
   Restore to Background：绘制当前帧之前，先把画布清空为默认背景色。  &lt;br /&gt;
   Restore to Previous：绘制下一帧前，把先把画布恢复为当前帧的前一帧&lt;/p&gt;
 &lt;p&gt;Blend Mode (混合模式)   &lt;br /&gt;
   Blend None: 绘制时，全部通道（包含Alpha通道）都会覆盖到画布，相当于绘制前先清空画布的指定区域。  &lt;br /&gt;
   Blend over：绘制时，Alpha 通道会被合成到画布，即通常情况下两张图片重叠的效果。&lt;/p&gt;
 &lt;p&gt;上面这些技术，就是常见动图格式的基础了，下面分别介绍一下不同动图格式的特点。&lt;/p&gt;
 &lt;p&gt;  &lt;a name="ani_gif"&gt;&lt;/a&gt;&lt;/p&gt;
 &lt;h3&gt;  &lt;strong&gt;GIF&lt;/strong&gt;&lt;/h3&gt;
 &lt;p&gt;GIF 缺陷非常明显：它通常只支持 256 色索引颜色，这导致它只能通过抖动、差值等方式模拟较多丰富的颜色；它的 Alpha 通道只有 1 bit，这意味着一个像素只能是完全透明或者完全不透明。&lt;/p&gt;
 &lt;p&gt;  &lt;a href="http://blog.ibireme.com/wp-content/uploads/2015/11/gif_apng_demo.gif"&gt;   &lt;img alt="gif_apng_demo" height="134" src="http://blog.ibireme.com/wp-content/uploads/2015/11/gif_apng_demo.gif" width="220"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;
 &lt;p&gt;上面这是腾讯博客里的一张演示图，可以看到 GIF 由于 Alpha 通道的问题，产生了严重的&amp;quot;毛边&amp;quot;现象。目前通常的解决方案是在图片的边缘加一圈白边，以减轻这种视觉效果：&lt;/p&gt;
 &lt;p&gt;  &lt;a href="http://blog.ibireme.com/wp-content/uploads/2015/11/gif_wrong_demo.png"&gt;   &lt;img alt="gif_wrong_demo" height="125" src="http://blog.ibireme.com/wp-content/uploads/2015/11/gif_wrong_demo.png" width="200"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;
 &lt;p&gt;可以仔细观察一下 QQ、微信等 App 里面的动画表情，几乎每个表情都被一圈白边所环绕，不得不说是一种很无奈的解决方案。&lt;/p&gt;
 &lt;p&gt;GIF 的制作工具有很多，但效果好、压缩比高的工具非常少。对于已经制作好的 GIF 来说，用   &lt;a href="http://www.imagemagick.org/script/index.php" target="_blank"&gt;imagemagick&lt;/a&gt; 处理一下可以把文件体积压缩不少。如果需要将视频转为 GIF，  &lt;a href="https://flixel.com/products/mac/cinemagraph-pro/"&gt;Cinemagraph Pro&lt;/a&gt; 是个不错的傻瓜化工具。  &lt;a href="http://www.oschina.net/translate/high-quality-gif-with-ffmpeg"&gt;这里&lt;/a&gt;有一篇文章介绍如何用 ffmpeg 压缩 GIF，虽然参数调节有点麻烦，但效果非常理想。&lt;/p&gt;
 &lt;p&gt;下面是没有经过优化的 GIF 和经过 ffmpeg 优化编码的 GIF，可以看到差距非常大。&lt;/p&gt;
 &lt;p&gt;  &lt;a href="http://blog.ibireme.com/wp-content/uploads/2015/11/bbb-nodither.gif"&gt;   &lt;img alt="bbb-trans" height="169" src="http://blog.ibireme.com/wp-content/uploads/2015/11/bbb-trans.gif" width="300"&gt;&lt;/img&gt;    &lt;img alt="bbb-nodither" height="169" src="http://blog.ibireme.com/wp-content/uploads/2015/11/bbb-nodither.gif" width="300"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;a name="ani_apng"&gt;&lt;/a&gt;&lt;/p&gt;
 &lt;h3&gt;  &lt;strong&gt;APNG&lt;/strong&gt;&lt;/h3&gt;
 &lt;p&gt;APNG 目前并没有被 PNG 官方所接受，所以 libpng 并不能直接解码 APNG。但由于 APNG 只是基于 PNG 的一个简单扩展，所以在已经支持 PNG 的平台上，可以很轻松的用少量代码实现 APNG 的编解码。Chromium 为了支持 APNG 播放，只增加了  &lt;a href="https://codereview.chromium.org/1312843006/" target="_blank"&gt;不到 &lt;/a&gt;  &lt;a href="https://codereview.chromium.org/1312843006/" target="_blank"&gt;600&lt;/a&gt;  &lt;a href="https://codereview.chromium.org/1312843006/" target="_blank"&gt; 行代码&lt;/a&gt; ，我自己也用  &lt;a href="https://github.com/ibireme/YYWebImage/blob/master/YYWebImage/Image/YYImageCoder.m#L149-L628"&gt;大概 500 行 C 代码&lt;/a&gt;实现了一个简单的 APNG 编解码工具。另外，在支持 canvas 的浏览器上，可以用   &lt;a href="https://github.com/davidmz/apng-canvas" target="_blank"&gt;apng-canvas&lt;/a&gt; 直接显示 APNG 动画。APNG 压缩最好的工具目前是   &lt;a href="http://sourceforge.net/projects/apngasm/" target="_blank"&gt;apngasm&lt;/a&gt;，大部分图形化工具比如腾讯的   &lt;a href="http://isparta.github.io/index.html" target="_blank"&gt;iSparta&lt;/a&gt; 都是基于这个工具开发的。&lt;/p&gt;
 &lt;p&gt;就目前而言， APNG 是 GIF 最好的替代了：实现简单，可用范围广，压缩比不错，显示效果好。&lt;/p&gt;
 &lt;p&gt;  &lt;a name="ani_webp"&gt;&lt;/a&gt;&lt;/p&gt;
 &lt;h3&gt;  &lt;strong&gt;WebP&lt;/strong&gt;&lt;/h3&gt;
 &lt;p&gt;WebP 在 2010 年 发布时并没有支持动图。2012 年 libwebp v0.2 的时候，Google 才开始尝试支持动画，但其实现有很多问题，性能也非常差，以至于 Chrome 团队一直都没有接受。直到 2013 年，libwebp v0.4 时，动画格式才稳定下来才被 Chrome 所接受。&lt;/p&gt;
 &lt;p&gt;WebP 动图实际上是把多个单帧 WebP 数据简单打包到一个文件内，而并不是由单帧 WebP 扩展而来，以至于动图格式并不能向上兼容静态图。如果要支持动图，首先在编译 libwebp 时需要加上 demux 模块，解码 WebP 时需要先用 WebPDemuxer 尝试拆包，之后再把拆出来的单帧用 WebPDecode 解码。为了方便编译，我写了个  &lt;a href="https://github.com/ibireme/YYWebImage/blob/master/Vendor/WebP.sh" target="_blank"&gt;脚本&lt;/a&gt;用于打包 iOS 的静态库，加入了 mux 和 demux 模块。&lt;/p&gt;
 &lt;p&gt;Google 提供了两个简单的命令行工具用于制作动图：gif2webp 能把 GIF 转换为 WebP， webpmux 能把多个 WebP 图片打包为动态图，并且有着很多参数可以调节。这两个工具对相近帧的压缩并不太理想，以至于有的情况下压缩比还不如 APNG，但除此以外也没有其他什么更好的工具可以用了。&lt;/p&gt;
 &lt;p&gt;  &lt;a name="ani_bpg"&gt;&lt;/a&gt;&lt;/p&gt;
 &lt;h3&gt;  &lt;strong&gt;BPG&lt;/strong&gt;&lt;/h3&gt;
 &lt;p&gt;BPG 本身是基于 HEVC (H.265) 视频编码的，其最开始设计时就考虑到了动图的实现。由于它充分利用了 HEVC 的高压缩比和视频编码的特性，其动图压缩比远超其他格式。  &lt;a href="http://bellard.org/bpg/animation.html" target="_blank"&gt;这里&lt;/a&gt;和  &lt;a href="https://kippler.com/bpg/test.html" target="_blank"&gt;这里&lt;/a&gt;有几张 BPG 动图示例，可以看到相同质量下 BPG 动图只有 APNG/WebP/GIF 几十分之一的大小。&lt;/p&gt;
 &lt;p&gt;我在  &lt;a href="https://github.com/ibireme/YYWebImage/blob/master/Demo%2FYYWebImageDemo%2FYYBPGCoder.m#L188-L264" target="_blank"&gt;这里&lt;/a&gt;写了个简单的利用 libbpg 解码动图的方法，如有需要可以参考下。&lt;/p&gt;
 &lt;p&gt;  &lt;a name="ani_bench"&gt;&lt;/a&gt;&lt;/p&gt;
 &lt;h3&gt;动图性能对比&lt;/h3&gt;
 &lt;p&gt;我把下面这张 GIF 分别转为 WebP、APNG、BPG 动图，并在 iPhone 6 上对其所有帧进行解码。&lt;/p&gt;
 &lt;p&gt;  &lt;a href="http://blog.ibireme.com/wp-content/uploads/2015/11/gif_ermilio.gif"&gt;   &lt;img alt="gif_ermilio" height="215" src="http://blog.ibireme.com/wp-content/uploads/2015/11/gif_ermilio.gif" width="360"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;
 &lt;p&gt;评测结果如下：&lt;/p&gt;
 &lt;p&gt;  &lt;a href="http://blog.ibireme.com/wp-content/uploads/2015/11/anim_bench.png"&gt;   &lt;img alt="anim_bench" height="169" src="http://blog.ibireme.com/wp-content/uploads/2015/11/anim_bench.png" width="497"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;
 &lt;p&gt;APNG 在文件体积上比 GIF 略有优势，解码时间相差不多。WebP 在体积和解码时间上都具有较大的优势。BPG 在体积上优势最大，但解码时间也最长。这么看来，APNG 和 WebP 都是不错的选择，而 BPG 还有待性能优化。&lt;/p&gt;
 &lt;p&gt;最后做一个小广告：如果你是 iOS 平台的开发者，可以试试我开发的   &lt;a href="https://github.com/ibireme/YYWebImage" target="_blank"&gt;YYWebImage&lt;/a&gt;，它支持 APNG、WebP、GIF 动图的异步加载与播放、编码与解码，支持渐进式图像加载，可以替代 SDWebImage、PINRemoteImage、FLAnimatedImage 等开源库。&lt;/p&gt;
 &lt;p&gt;  &lt;a name="refer"&gt;&lt;/a&gt;&lt;/p&gt;
 &lt;h2&gt;评测数据&lt;/h2&gt;
 &lt;p&gt;上面提到的所有评测数据表格：  &lt;a href="http://blog.ibireme.com/wp-content/uploads/2015/11/image_benchmark.xlsx"&gt;image_benchmark.xlsx&lt;/a&gt; 推荐用 Excel 打开查看。&lt;/p&gt;
&lt;div&gt; &lt;a href="https://itindex.net/"  title="IT 资讯"&gt;&lt;img src="https://itindex.net/images/iconWarning.gif" title="IT 资讯" border="0"/&gt; &lt;/a&gt;</description>
      <category>iOS 技术</category>
      <guid isPermaLink="true">https://itindex.net/detail/54615-%E7%A7%BB%E5%8A%A8-%E5%9B%BE%E7%89%87-%E6%A0%BC%E5%BC%8F</guid>
      <pubDate>Mon, 02 Nov 2015 00:21:47 CST</pubDate>
    </item>
    <item>
      <title>iOS应用架构谈 网络层设计方案</title>
      <link>https://itindex.net/detail/53690-ios-%E5%BA%94%E7%94%A8-%E6%9E%B6%E6%9E%84</link>
      <description>&lt;p&gt;  &lt;br /&gt;
  &lt;br /&gt;
  &lt;br /&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;a href="http://casatwy.com/iosying-yong-jia-gou-tan-kai-pian.html"&gt;iOS应用架构谈 开篇&lt;/a&gt;
  &lt;br /&gt;
  &lt;a href="http://casatwy.com/iosying-yong-jia-gou-tan-viewceng-de-zu-zhi-he-diao-yong-fang-an.html"&gt;iOS应用架构谈 view层的组织和调用方案&lt;/a&gt;
  &lt;br /&gt;
  &lt;a href="http://casatwy.com/iosying-yong-jia-gou-tan-wang-luo-ceng-she-ji-fang-an.html"&gt;iOS应用架构谈  网络层设计方案&lt;/a&gt;
  &lt;br /&gt;
iOS应用架构谈 动态部署方案
  &lt;br /&gt;
iOS应用架构谈 本地持久化方案&lt;/p&gt;
 &lt;p&gt;  &lt;br /&gt;
  &lt;br /&gt;
  &lt;br /&gt;&lt;/p&gt;
 &lt;h1&gt;前言&lt;/h1&gt;
 &lt;p&gt;  &lt;br /&gt;&lt;/p&gt;
 &lt;p&gt;网络层在一个App中也是一个不可缺少的部分，工程师们在网络层能够发挥的空间也比较大。另外，苹果对网络请求部分已经做了很好的封装，业界的AFNetworking也被广泛使用。其它的ASIHttpRequest，MKNetworkKit啥的其实也都还不错，但前者已经弃坑，后者也在弃坑的边缘。在实际的App开发中，Afnetworking已经成为了事实上各大App的标准配置。&lt;/p&gt;
 &lt;p&gt;网络层在一个App中承载了API调用，用户操作日志记录，甚至是即时通讯等任务。我接触过一些App（开源的和不开源的）的代码，在看到网络层这一块时，尤其是在看到各位架构师各显神通展示了各种技巧，我非常为之感到兴奋。但有的时候，往往也对于其中的一些缺陷感到失望。&lt;/p&gt;
 &lt;p&gt;  &lt;br /&gt;&lt;/p&gt;
 &lt;p&gt;关于网络层的设计方案会有很多，需要权衡的地方也会有很多，甚至于争议的地方都会有很多。但无论如何，我都不会对这些问题做出任何逃避，我会在这篇文章中给出我对它们的看法和解决方案，观点绝不中立，不会跟大家打太极。&lt;/p&gt;
 &lt;p&gt;这篇文章就主要会讲这些方面：&lt;/p&gt;
 &lt;p&gt;  &lt;br /&gt;&lt;/p&gt;
 &lt;ol&gt;
  &lt;li&gt;网络层跟业务对接部分的设计&lt;/li&gt;
  &lt;li&gt;网络层的安全机制实现&lt;/li&gt;
  &lt;li&gt;网络层的优化方案&lt;/li&gt;
&lt;/ol&gt;
 &lt;p&gt;  &lt;br /&gt;
  &lt;br /&gt;
  &lt;br /&gt;&lt;/p&gt;
 &lt;h1&gt;网络层跟业务对接部分的设计&lt;/h1&gt;
 &lt;p&gt;  &lt;br /&gt;&lt;/p&gt;
 &lt;p&gt;在安居客App的架构更新换代的时候，我深深地感觉到网络层跟业务对接部分的设计有多么重要，因此我对它做的最大改变就是针对网络层跟业务对接部分的改变。网络层跟业务层对接部分设计的好坏，会直接影响到业务工程师实现功能时的心情。&lt;/p&gt;
 &lt;p&gt;  &lt;br /&gt;&lt;/p&gt;
 &lt;p&gt;在正式开始讲设计之前，我们要先讨论几个问题：&lt;/p&gt;
 &lt;p&gt;  &lt;br /&gt;&lt;/p&gt;
 &lt;ol&gt;
  &lt;li&gt;使用哪种交互模式来跟业务层做对接？&lt;/li&gt;
  &lt;li&gt;是否有必要将API返回的数据封装成对象然后再交付给业务层？&lt;/li&gt;
  &lt;li&gt;使用集约化调用方式还是离散型调用方式去调用API？&lt;/li&gt;
&lt;/ol&gt;
 &lt;p&gt;  &lt;br /&gt;&lt;/p&gt;
 &lt;p&gt;这些问题讨论完毕之后，我会给出一个完整的设计方案来给大家做参考，设计方案是鱼，讨论的这些问题是渔，我什么都授了，大家各取所需。&lt;/p&gt;
 &lt;p&gt;  &lt;br /&gt;&lt;/p&gt;
 &lt;blockquote&gt;
  &lt;p&gt;使用哪种交互模式来跟业务层做对接？&lt;/p&gt;
&lt;/blockquote&gt;
 &lt;p&gt;  &lt;br /&gt;&lt;/p&gt;
 &lt;p&gt;这里其实有两个问题：&lt;/p&gt;
 &lt;p&gt;  &lt;br /&gt;&lt;/p&gt;
 &lt;ol&gt;
  &lt;li&gt;以什么方式将数据交付给业务层？&lt;/li&gt;
  &lt;li&gt;交付什么样的数据给业务层？&lt;/li&gt;
&lt;/ol&gt;
 &lt;p&gt;  &lt;br /&gt;&lt;/p&gt;
 &lt;h5&gt;以什么方式将数据交付给业务层？&lt;/h5&gt;
 &lt;p&gt;  &lt;br /&gt;&lt;/p&gt;
 &lt;p&gt;iOS开发领域有很多对象间数据的传递方式，我看到的大多数App在网络层所采用的方案主要集中于这三种：Delegate，Notification，Block。KVO和Target-Action我目前还没有看到有使用的。&lt;/p&gt;
 &lt;p&gt;目前我知道边锋主要是采用的block，大智慧主要采用的是Notification，安居客早期以Block为主，后面改成了以Delegate为主，阿里没发现有通过Notification来做数据传递的地方（可能有），Delegate、Block以及target-action都有，阿里iOS App网络层的作者说这是为了方便业务层选择自己合适的方法去使用。这里大家都是各显神通，每次我看到这部分的时候，我都喜欢问作者为什么采用这种交互方案，但很少有作者能够说出个条条框框来。&lt;/p&gt;
 &lt;p&gt;然而在我这边，我的意见是以Delegate为主，Notification为辅。原因如下：&lt;/p&gt;
 &lt;p&gt;  &lt;br /&gt;&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;尽可能减少跨层数据交流的可能，限制耦合&lt;/li&gt;
  &lt;li&gt;统一回调方法，便于调试和维护&lt;/li&gt;
  &lt;li&gt;在跟业务层对接的部分只采用一种对接手段（在我这儿就是只采用delegate这一个手段）限制灵活性，以此来交换应用的可维护性&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;  &lt;br /&gt;&lt;/p&gt;
 &lt;blockquote&gt;
  &lt;p&gt;尽可能减少跨层数据交流的可能，限制耦合&lt;/p&gt;
&lt;/blockquote&gt;
 &lt;p&gt;  &lt;br /&gt;&lt;/p&gt;
 &lt;p&gt;什么叫跨层数据交流？就是某一层（或模块）跟另外的与之没有直接对接关系的层（或模块）产生了数据交换。为什么这种情况不好？严格来说应该是大部分情况都不好，有的时候跨层数据交流确实也是一种需求。之所以说不好的地方在于，  &lt;code&gt;它会导致代码混乱，破坏模块的封装性&lt;/code&gt;。我们在做分层架构的目的其中之一就在于下层对上层有一次抽象，让上层可以不必关心下层细节而执行自己的业务。&lt;/p&gt;
 &lt;p&gt;所以，如果下层细节被跨层暴露，一方面你很容易因此失去邻层对这个暴露细节的保护；另一方面，你又不可能不去处理这个细节，所以处理细节的相关代码就会散落各地，最终难以维护。&lt;/p&gt;
 &lt;p&gt;说得具象一点就是，我们考虑这样一种情况：A&amp;lt;-B&amp;lt;-C。当C有什么事件，通过某种方式告知B，然后B执行相应的逻辑。一旦告知方式不合理，让A有了跨层知道C的事件的可能，你
就很难保证A层业务工程师在将来不会对这个细节作处理。一旦业务工程师在A层产生处理操作，有可能是补充逻辑，也有可能是执行业务，那么这个细节的相关处理代码就会有一部分散落在A层。然而前者是不应该散落在A层的，后者有可能是需求。另外，因为B层是对A层抽象的，执行补充逻辑的时候，有可能和B层针对这个事件的处理逻辑产生冲突，这是我们很不希望看到的。&lt;/p&gt;
 &lt;p&gt;那么什么情况跨层数据交流会成为需求？在网络层这边，信号从2G变成3G变成4G变成Wi-Fi，这个是跨层数据交流的其中一个需求。不过其他的跨层数据交流需求我暂时也想不到了，哈哈，应该也就这一个吧。&lt;/p&gt;
 &lt;p&gt;  &lt;br /&gt;&lt;/p&gt;
 &lt;hr&gt;&lt;/hr&gt;
 &lt;p&gt;  &lt;br /&gt;&lt;/p&gt;
 &lt;p&gt;严格来说，使用Notification来进行网络层和业务层之间数据的交换，并不代表这一定就是跨层数据交流，但是使用Notification给跨层数据交流开了一道口子，因为Notification的影响面不可控制，只要存在实例就存在被影响的可能。另外，这也会导致谁都不能保证相关处理代码就在唯一的那个地方，进而带来维护灾难。作为架构师，在这里给业务工程师限制其操作的灵活性是必要的。另外，Notification也支持一对多的情况，这也给代码散落提供了条件。同时，Notification所对应的响应方法很难在编译层面作限制，不同的业务工程师会给他取不同的名字，这也会给代码的可维护性带来灾难。&lt;/p&gt;
 &lt;p&gt;手机淘宝架构组的侠武同学曾经给我分享过一个问题，在这里我也分享给大家：曾经有一个工程师在监听Notification之后，没有写释放监听的代码，当然，找到这个原因又是很漫长的一段故事，现在找到原因了，然而监听这个Notification的对象有那么多，不知道具体是哪个Notificaiton，也不知道那个没释放监听的对象是谁。后来折腾了很久大家都没办法的时候，有一个经验丰富的工程师提出用hook（Method Swizzling）的方式，最终找到了那个没释放监听的对象，bug修复了。&lt;/p&gt;
 &lt;p&gt;我分享这个问题的目的并不是想强调Notification多么多么不好，Notification本身就是一种设计模式，在属于他的问题领域内，Notification是非常好的一种解决方案。但我想强调的是，对于网络层这个问题领域内来看，架构师首先一定要限制代码的影响范围，在能用影响范围小的方案的时候就尽量采用这种小的方案，否则将来要是有什么奇怪需求或者出了什么小问题，维护起来就非常麻烦。因此Notification这个方案不能作为首选方案，只能作为备选。&lt;/p&gt;
 &lt;p&gt;那么Notification也不是完全不能使用，当需求要求跨层时，我们就可以使用Notification，比如前面提到的网络条件切换，而且这个需求也是需要满足一对多的。&lt;/p&gt;
 &lt;p&gt;所以，为了符合前面所说的这些要求，使用Delegate能够很好地避免跨层访问，同时限制了响应代码的形式，相比Notification而言有更好的可维护性。&lt;/p&gt;
 &lt;p&gt;  &lt;br /&gt;&lt;/p&gt;
 &lt;hr&gt;&lt;/hr&gt;
 &lt;p&gt;  &lt;br /&gt;&lt;/p&gt;
 &lt;blockquote&gt;
  &lt;p&gt;然后我们顺便来说说为什么尽量不要用block。&lt;/p&gt;
&lt;/blockquote&gt;
 &lt;p&gt;  &lt;br /&gt;&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;block很难追踪，难以维护&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;  &lt;br /&gt;&lt;/p&gt;
 &lt;p&gt;我们在调试的时候经常会单步追踪到某一个地方之后，发现尼玛这里有个block，如果想知道这个block里面都做了些什么事情，这时候就比较蛋疼了。&lt;/p&gt;
 &lt;p&gt;  &lt;br /&gt;&lt;/p&gt;
 &lt;div&gt;  &lt;pre&gt;- (void)someFunctionWithBlock:(SomeBlock *)block
{
    ... ...

 -&amp;gt; block();  //当你单步走到这儿的时候，要想知道block里面都做了哪些事情的话，就很麻烦。

    ... ...
}
&lt;/pre&gt;&lt;/div&gt;


 &lt;p&gt;  &lt;br /&gt;&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;block会延长相关对象的生命周期&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;  &lt;br /&gt;&lt;/p&gt;
 &lt;p&gt;block会给内部所有的对象引用计数加一，这一方面会带来潜在的retain cycle，不过我们可以通过Weak Self的手段解决。另一方面比较重要就是，它会延长对象的生命周期。&lt;/p&gt;
 &lt;p&gt;在网络回调中使用block，是block导致对象生命周期被延长的其中一个场合，当ViewController从window中卸下时，如果尚有请求带着block在外面飞，然后block里面引用了ViewController（这种场合非常常见），那么ViewController是不能被及时回收的，即便你已经取消了请求，那也还是必须得等到请求着陆之后才能被回收。&lt;/p&gt;
 &lt;p&gt;然而使用delegate就不会有这样的问题，delegate是弱引用，哪怕请求仍然在外面飞，，ViewController还是能够及时被回收的，回收之后指针自动被置为了nil，无伤大雅。&lt;/p&gt;
 &lt;p&gt;  &lt;br /&gt;&lt;/p&gt;
 &lt;p&gt;所以平时尽量不要滥用block，尤其是在网络层这里。&lt;/p&gt;
 &lt;p&gt;  &lt;br /&gt;&lt;/p&gt;
 &lt;hr&gt;&lt;/hr&gt;
 &lt;p&gt;  &lt;br /&gt;
  &lt;br /&gt;&lt;/p&gt;
 &lt;blockquote&gt;
  &lt;p&gt;统一回调方法，便于调试和维护&lt;/p&gt;
&lt;/blockquote&gt;
 &lt;p&gt;  &lt;br /&gt;&lt;/p&gt;
 &lt;p&gt;前面讲的是跨层问题，区分了Delegate和Notification，顺带谈了一下Block。然后现在谈到的这个情况，就是另一个采用Block方案不是很合适的情况。首先，Block本身无好坏对错之分，只有合适不合适。在这一节要讲的情况里，Block无法做到回调方法的统一，调试和维护的时候也很难在调用栈上显示出来，找的时候会很蛋疼。&lt;/p&gt;
 &lt;p&gt;在网络请求和网络层接受请求的地方时，使用Block没问题。但是在获得数据交给业务方时，最好还是通过Delegate去通知到业务方。因为Block所包含的回调代码跟调用逻辑放在同一个地方，会导致那部分代码变得很长，因为这里面包括了调用前和调用后的逻辑。从另一个角度说，这在一定程度上违背了  &lt;code&gt;single function，single task&lt;/code&gt;的原则，在需要调用API的地方，就只要写API调用相关的代码，在回调的地方，写回调的代码。&lt;/p&gt;
 &lt;p&gt;然后我看到大部分App里，当业务工程师写代码写到这边的时候，也意识到了这个问题。因此他们会在block里面写个一句话的方法接收参数，然后做转发，然后就可以把这个方法放在其他地方了，绕过了  &lt;code&gt;Block的回调着陆点不统一&lt;/code&gt;的情况。比如这样：&lt;/p&gt;
 &lt;p&gt;  &lt;br /&gt;&lt;/p&gt;
 &lt;div&gt;  &lt;pre&gt;    [API callApiWithParam:param successed:^(Response *response){
        [self successedWithResponse:response];
    } failed:^(Request *request, NSError *error){
        [self failedWithRequest:request error:error];
    }];
&lt;/pre&gt;&lt;/div&gt;


 &lt;p&gt;  &lt;br /&gt;&lt;/p&gt;
 &lt;p&gt;这实质上跟使用Delegate的手段没有什么区别，只是绕了一下，不过还是没有解决统一回调方法的问题，因为block里面写的方法名字可能在不同的ViewController对象中都会不一样，毕竟业务工程师也是很多人，各人有各人的想法。所以架构师在这边不要贪图方便，还是使用delegate的手段吧，业务工程师那边就能不用那么绕了。Block是目前大部分第三方网络库都采用的方式，因为在发送请求的那一部分，使用Block能够比较简洁，因此在请求那一层是没有问题的，只是在交换数据之后，还是转变成delegate比较好，比如AFNetworking里面：&lt;/p&gt;
 &lt;p&gt;  &lt;br /&gt;&lt;/p&gt;
 &lt;div&gt;  &lt;pre&gt;    [AFNetworkingAPI callApiWithParam:self.param successed:^(Response *response){
        if ([self.delegate respondsToSelector:@selector(successWithResponse:)]) {
            [self.delegate successedWithResponse:response];
        }
    } failed:^(Request *request, NSError *error){
        if ([self.delegate respondsToSelector:@selector(failedWithResponse:)]) {
            [self failedWithRequest:request error:error];
        }
    }];
&lt;/pre&gt;&lt;/div&gt;


 &lt;p&gt;  &lt;br /&gt;&lt;/p&gt;
 &lt;p&gt;这样在业务方这边回调函数就能够比较统一，便于维护。&lt;/p&gt;
 &lt;p&gt;  &lt;br /&gt;&lt;/p&gt;
 &lt;hr&gt;&lt;/hr&gt;
 &lt;p&gt;  &lt;br /&gt;&lt;/p&gt;
 &lt;p&gt;综上，对于  &lt;code&gt;以什么方式将数据交付给业务层？&lt;/code&gt;这个问题的回答是这样：&lt;/p&gt;
 &lt;p&gt;尽可能通过Delegate的回调方式交付数据，这样可以避免不必要的跨层访问。当出现跨层访问的需求时（比如信号类型切换），通过Notification的方式交付数据。正常情况下应该是避免使用Block的。&lt;/p&gt;
 &lt;p&gt;  &lt;br /&gt;
  &lt;br /&gt;
  &lt;br /&gt;&lt;/p&gt;
 &lt;h4&gt;交付什么样的数据给业务层？&lt;/h4&gt;
 &lt;p&gt;  &lt;br /&gt;&lt;/p&gt;
 &lt;p&gt;我见过非常多的App的网络层在拿到JSON数据之后，会将数据转变成对应的对象原型。注意，我这里指的不是NSDictionary，而是类似Item这样的对象。这种做法是能够提高后续操作代码的可读性的。在比较直觉的思路里面，是需要这部分转化过程的，但这部分转化过程的成本是很大的，主要成本在于：&lt;/p&gt;
 &lt;p&gt;  &lt;br /&gt;&lt;/p&gt;
 &lt;ol&gt;
  &lt;li&gt;数组内容的转化成本较高：数组里面每项都要转化成Item对象，如果Item对象中还有类似数组，就很头疼。&lt;/li&gt;
  &lt;li&gt;转化之后的数据在大部分情况是不能直接被展示的，为了能够被展示，还需要第二次转化。&lt;/li&gt;
  &lt;li&gt;只有在API返回的数据高度标准化时，这些对象原型（Item）的可复用程度才高，否则容易出现类型爆炸，提高维护成本。&lt;/li&gt;
  &lt;li&gt;调试时通过对象原型查看数据内容不如直接通过NSDictionary/NSArray直观。&lt;/li&gt;
  &lt;li&gt;同一API的数据被不同View展示时，难以控制数据转化的代码，它们有可能会散落在任何需要的地方。&lt;/li&gt;
&lt;/ol&gt;
 &lt;p&gt;  &lt;br /&gt;&lt;/p&gt;
 &lt;p&gt;其实我们的理想情况是希望API的数据下发之后就能够直接被View所展示。首先要说的是，这种情况非常少。另外，这种做法使得View和API联系紧密，也是我们不希望发生的。&lt;/p&gt;
 &lt;p&gt;在设计安居客的网络层数据交付这部分时，我添加了reformer（名字而已，叫什么都好）这个对象用于封装数据转化的逻辑，这个对象是一个独立对象，事实上，它是作为Adaptor模式存在的。我们可以这么理解：想象一下我们洗澡时候使用的莲蓬头，水管里出来的水是API下发的原始数据。reformer就是莲蓬头上的不同水流挡板，需要什么模式，就拨到什么模式。&lt;/p&gt;
 &lt;p&gt;在实际使用时，代码观感是这样的：&lt;/p&gt;
 &lt;p&gt;  &lt;br /&gt;&lt;/p&gt;
 &lt;div&gt;  &lt;pre&gt;先定义一个protocol：

@protocol ReformerProtocol &amp;lt;NSObject&amp;gt;
- (NSDictionary)reformDataWithManager:(APIManager *)manager;
@end


在Controller里是这样：

@property (nonatomic, strong) id&amp;lt;ReformerProtocol&amp;gt; XXXReformer;
@property (nonatomic, strong) id&amp;lt;ReformerProtocol&amp;gt; YYYReformer;

#pragma mark - APIManagerDelegate
- (void)apiManagerDidSuccess:(APIManager *)manager
{
    NSDictionary *reformedXXXData = [manager fetchDataWithReformer:self.XXXReformer];
    [self.XXXView configWithData:reformedXXXData];

    NSDictionary *reformedYYYData = [manager fetchDataWithReformer:self.YYYReformer];
    [self.YYYView configWithData:reformedYYYData];
}


在APIManager里面，fetchDataWithReformer是这样：
- (NSDictionary)fetchDataWithReformer:(id&amp;lt;ReformerProtocol&amp;gt;)reformer
{
    if (reformer == nil) {
        return self.rawData;
    } else {
        return [reformer reformDataWithManager:self];
    }
}
&lt;/pre&gt;&lt;/div&gt;


 &lt;p&gt;  &lt;br /&gt;&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;要点1：reformer是一个符合ReformerProtocol的对象，它提供了通用的方法供Manager使用。&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;  &lt;br /&gt;&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;要点2：API的原始数据（JSON对象）由Manager实例保管，reformer方法里面取Manager的原始数据(manager.rawData)做转换，然后交付出去。莲蓬头的水管部分是Manager，负责提供原始水流（数据流），reformer就是不同的模式，换什么reformer就能出来什么水流。&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;  &lt;br /&gt;&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;要点3：例子中举的场景是一个API数据被多个View使用的情况，体现了reformer的一个特点：可以根据需要改变同一数据来源的展示方式。比如API数据展示的是“附近的小区”，那么这个数据可以被列表（XXXView）和地图（YYYView）共用，不同的view使用的数据的转化方式不一样，这就通过不同的reformer解决了。&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;  &lt;br /&gt;&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;要点4：在一个view用来同一展示不同API数据的情况，reformer是绝佳利器。比如安居客的列表view的数据来源可能有三个：二手房列表API，租房列表API，新房列表API。这些API返回来的数据的value可能一致，但是key都是不一致的。这时候就可以通过同一个reformer来做数据的标准化输出，这样就使得view代码复用成为可能。这体现了reformer另外一个特点：同一个reformer出来的数据是高度标准化的。形象点说就是：只要莲蓬头不换，哪怕水管的水变成海水或者污水了，也依旧能够输出符合洗澡要求的淡水水流。举个例子：&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;  &lt;br /&gt;&lt;/p&gt;
 &lt;div&gt;  &lt;pre&gt;- (void)apiManagerDidSuccess:(APIManager *)manager
{
    // 这个回调方法有可能是来自二手房列表APIManager的回调，也有可能是租房，也有可能是新房。但是在Controller层面我们不需要对它做额外区分，只要是同一个reformer出来的数据，我们就能保证是一定能被self.XXXView使用的。这样的保证由reformer的实现者来提供。
    NSDictionary *reformedXXXData = [manager fetchDataWithReformer:self.XXXReformer];
    [self.XXXView configWithData:reformedXXXData];
}
&lt;/pre&gt;&lt;/div&gt;


 &lt;p&gt;  &lt;br /&gt;&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;要点5：有没有发现，使用reformer之后，Controller的代码简洁了很多？而且，数据原型在这种情况下就没有必要存在了，随之而来的成本也就被我们绕过了。&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;  &lt;br /&gt;&lt;/p&gt;
 &lt;hr&gt;&lt;/hr&gt;
 &lt;p&gt;  &lt;br /&gt;&lt;/p&gt;
 &lt;p&gt;reformer本质上就是一个符合某个protocol的对象，在controller需要从api manager中获得数据的时候，顺便把reformer传进去，于是就能获得经过reformer重新洗过的数据，然后就可以直接使用了。&lt;/p&gt;
 &lt;p&gt;更抽象地说，reformer其实是对数据转化逻辑的一个封装。在controller从manager中取数据之后，并且把数据交给view之前，这期间或多或少都是要做一次数据转化的，有的时候不同的view，对应的转化逻辑还不一样，但是展示的数据是一样的。而且往往这一部分代码都非常复杂，且跟业务强相关，直接上代码，将来就会很难维护。所以我们可以考虑采用不同的reformer封装不同的转化逻辑，然后让controller根据需要选择一个合适的reformer装上，就像洗澡的莲蓬头，需要什么样的水流（数据的表现形式）就换什么样的头，然而水（数据）都是一样的。这种做法能够大大提高代码的可维护性，以及减少ViewController的体积。&lt;/p&gt;
 &lt;p&gt;总结一下，reformer事实上是把转化的代码封装之后再从主体业务中拆分了出来，拆分出来之后不光降低了原有业务的复杂度，更重要的是，它提高了数据交付的灵活性。另外，由于Controller负责调度Manager和View，因此它是知道Manager和View之间的关系的，Controller知道了这个关系之后，就有了充要条件来为不同的View选择不同的Reformer，并用这个Reformer去改造Mananger的数据，然后ViewController获得了经过reformer处理过的数据之后，就可以直接交付给view去使用。Controller因此得到瘦身，负责业务数据转化的这部分代码也不用写在Controller里面，提高了可维护性。&lt;/p&gt;
 &lt;p&gt;  &lt;br /&gt;&lt;/p&gt;
 &lt;hr&gt;&lt;/hr&gt;
 &lt;p&gt;  &lt;br /&gt;&lt;/p&gt;
 &lt;p&gt;所以reformer机制能够带来以下好处：&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;
   &lt;p&gt;好处1：绕开了API数据原型的转换，避免了相关成本。&lt;/p&gt;
&lt;/li&gt;
  &lt;li&gt;
   &lt;p&gt;好处2：在处理单View对多API，以及在单API对多View的情况时，reformer提供了非常优雅的手段来响应这种需求，隔离了转化逻辑和主体业务逻辑，避免了维护灾难。&lt;/p&gt;
&lt;/li&gt;
  &lt;li&gt;
   &lt;p&gt;好处3：转化逻辑集中，且将转化次数转为只有一次。使用数据原型的转化逻辑至少有两次，第一次是把JSON映射成对应的原型，第二次是把原型转变成能被View处理的数据。reformer一步到位。另外，转化逻辑在reformer里面，将来如果API数据有变，就只要去找到对应reformer然后改掉就好了。&lt;/p&gt;
&lt;/li&gt;
  &lt;li&gt;
   &lt;p&gt;好处4：Controller因此可以省去非常多的代码，降低了代码复杂度，同时提高了灵活性，任何时候切换reformer而不必切换业务逻辑就可以应对不同View对数据的需要。&lt;/p&gt;
&lt;/li&gt;
  &lt;li&gt;
   &lt;p&gt;好处5：业务数据和业务有了适当的隔离。这么做的话，将来如果业务逻辑有修改，换一个reformer就好了。如果其他业务也有相同的数据转化逻辑，其他业务直接拿这个reformer就可以用了，不用重写。另外，如果controller有修改（比如UI交互方式改变），可以放心换controller，完全不用担心业务数据的处理。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;  &lt;br /&gt;&lt;/p&gt;
 &lt;hr&gt;&lt;/hr&gt;
 &lt;p&gt;  &lt;br /&gt;&lt;/p&gt;
 &lt;blockquote&gt;
  &lt;p&gt;在不使用特定对象表征数据的情况下，如何保持数据可读性？&lt;/p&gt;
&lt;/blockquote&gt;
 &lt;p&gt;  &lt;br /&gt;&lt;/p&gt;
 &lt;p&gt;不使用对象来表征数据的时候，事实上就是使用NSDictionary的时候。事实上，这个问题就是，如何在NSDictionary表征数据的情况下保持良好的可读性？&lt;/p&gt;
 &lt;p&gt;苹果已经给出了非常好的做法，用固定字符串做key，比如你在接收到KeyBoardWillShow的Notification时，带了一个userInfo，他的key就都是类似UIKeyboardAnimationCurveUserInfoKey这样的，所以我们采用这样的方案来维持可读性。下面我举一个例子：&lt;/p&gt;
 &lt;p&gt;  &lt;br /&gt;&lt;/p&gt;
 &lt;div&gt;  &lt;pre&gt;PropertyListReformerKeys.h

extern NSString * const kPropertyListDataKeyID;
extern NSString * const kPropertyListDataKeyName;
extern NSString * const kPropertyListDataKeyTitle;
extern NSString * const kPropertyListDataKeyImage;
&lt;/pre&gt;&lt;/div&gt;


 &lt;p&gt;  &lt;br /&gt;&lt;/p&gt;
 &lt;div&gt;  &lt;pre&gt;PropertyListReformer.h

#import &amp;quot;PropertyListReformerKeys.h&amp;quot;

... ...
&lt;/pre&gt;&lt;/div&gt;


 &lt;p&gt;  &lt;br /&gt;&lt;/p&gt;
 &lt;div&gt;  &lt;pre&gt;PropertyListReformer.m

NSString * const kPropertyListDataKeyID = @&amp;quot;kPropertyListDataKeyID&amp;quot;;
NSString * const kPropertyListDataKeyName = @&amp;quot;kPropertyListDataKeyName&amp;quot;;
NSString * const kPropertyListDataKeyTitle = @&amp;quot;kPropertyListDataKeyTitle&amp;quot;;
NSString * const kPropertyListDataKeyImage = @&amp;quot;kPropertyListDataKeyImage&amp;quot;;

- (NSDictionary *)reformData:(NSDictionary *)originData fromManager:(APIManager *)manager
{
    ... ...
    ... ...

    NSDictionary *resultData = nil;

    if ([manager isKindOfClass:[ZuFangListAPIManager class]]) {
        resultData = @{
            kPropertyListDataKeyID:originData[@&amp;quot;id&amp;quot;],
            kPropertyListDataKeyName:originData[@&amp;quot;name&amp;quot;],
            kPropertyListDataKeyTitle:originData[@&amp;quot;title&amp;quot;],
            kPropertyListDataKeyImage:[UIImage imageWithUrlString:originData[@&amp;quot;imageUrl&amp;quot;]]
        };
    }

    if ([manager isKindOfClass:[XinFangListAPIManager class]]) {
        resultData = @{
            kPropertyListDataKeyID:originData[@&amp;quot;xinfang_id&amp;quot;],
            kPropertyListDataKeyName:originData[@&amp;quot;xinfang_name&amp;quot;],
            kPropertyListDataKeyTitle:originData[@&amp;quot;xinfang_title&amp;quot;],
            kPropertyListDataKeyImage:[UIImage imageWithUrlString:originData[@&amp;quot;xinfang_imageUrl&amp;quot;]]
        };
    }

    if ([manager isKindOfClass:[ErShouFangListAPIManager class]]) {
        resultData = @{
            kPropertyListDataKeyID:originData[@&amp;quot;esf_id&amp;quot;],
            kPropertyListDataKeyName:originData[@&amp;quot;esf_name&amp;quot;],
            kPropertyListDataKeyTitle:originData[@&amp;quot;esf_title&amp;quot;],
            kPropertyListDataKeyImage:[UIImage imageWithUrlString:originData[@&amp;quot;esf_imageUrl&amp;quot;]]
        };
    }

    return resultData;
}
&lt;/pre&gt;&lt;/div&gt;


 &lt;p&gt;  &lt;br /&gt;&lt;/p&gt;
 &lt;div&gt;  &lt;pre&gt;PropertListCell.m

#import &amp;quot;PropertyListReformerKeys.h&amp;quot;

- (void)configWithData:(NSDictionary *)data
{
    self.imageView.image = data[kPropertyListDataKeyImage];
    self.idLabel.text = data[kPropertyListDataKeyID];
    self.nameLabel.text = data[kPropertyListDataKeyName];
    self.titleLabel.text = data[kPropertyListDataKeyTitle];
}
&lt;/pre&gt;&lt;/div&gt;


 &lt;p&gt;  &lt;br /&gt;&lt;/p&gt;
 &lt;p&gt;这一大段代码看下来，我如果不说一下要点，那基本上就白写了哈：&lt;/p&gt;
 &lt;p&gt;我们先看一下结构：&lt;/p&gt;
 &lt;p&gt;  &lt;br /&gt;&lt;/p&gt;
 &lt;div&gt;  &lt;pre&gt;    ----------------------------------          -----------------------------------------
    |                                |          |                                       |
    | PropertyListReformer.m         |          | PropertyListReformer.h                |
    |                                |          |                                       |
    | #import PropertyListReformer.h | &amp;lt;------- |  #import &amp;quot;PropertyListReformerKeys.h&amp;quot; |
    | NSString * const key = @&amp;quot;key&amp;quot;  |          |                                       |
    |                                |          |                                       |
    ----------------------------------          -----------------------------------------
                                                                    .
                                                                   /|\
                                                                    |
                                                                    |
                                                                    |
                                                                    |
                                                    ---------------------------------
                                                    |                               |
                                                    | PropertyListReformerKeys.h    |
                                                    |                               |
                                                    | extern NSString * const key;  |
                                                    |                               |
                                                    ---------------------------------
&lt;/pre&gt;&lt;/div&gt;


 &lt;p&gt;  &lt;br /&gt;&lt;/p&gt;
 &lt;p&gt;使用Const字符串来表征Key，字符串的定义跟着reformer的实现文件走，字符串的extern声明放在独立的头文件内。&lt;/p&gt;
 &lt;p&gt;这样reformer生成的数据的key都使用Const字符串来表示，然后每次别的地方需要使用相关数据的时候，把PropertyListReformerKeys.h这个头文件import进去就好了。&lt;/p&gt;
 &lt;p&gt;另外要注意的一点是，如果一个OriginData可能会被多个Reformer去处理的话，Key的命名规范需要能够表征出其对应的reformer名字。如果reformer是  &lt;code&gt;PropertyListReformer&lt;/code&gt;，那么Key的名字就是  &lt;code&gt;PropertyListKeyXXXX&lt;/code&gt;。&lt;/p&gt;
 &lt;p&gt;这么做的好处就是，将来迁移的时候相当方便，只要扔头文件就可以了，只扔头文件是不会导致拔出萝卜带出泥的情况的。而且也避免了自定义对象带来的额外代码体积。&lt;/p&gt;
 &lt;p&gt;  &lt;br /&gt;&lt;/p&gt;
 &lt;hr&gt;&lt;/hr&gt;
 &lt;p&gt;  &lt;br /&gt;&lt;/p&gt;
 &lt;p&gt;另外，关于交付的NSDictionary，其实具体还是看view的需求，reformer的设计初衷是：  &lt;code&gt;通过reformer转化出来的可以直接是View，或者是view直接可以使用的对象（包括NSDictionary）&lt;/code&gt;。比如地图标点列表API的数据，通过reformer转化之后就可以直接变成MKAnnotation，然后MKMapView就可以直接使用了。这里说的只是当你的需求是交付NSDictionary时，如何保证可读性的情况，再强调一下哈，reformer交付的是view直接可以使用的对象，交付出去的可以是NSDictionary，也可以是UIView，跟DataSource结合之后交付的甚至可以是UITableViewCell/UICollectionViewCell。不要被NSDictionary或所谓的  &lt;code&gt;转化成model再交付&lt;/code&gt;的思想局限。&lt;/p&gt;
 &lt;p&gt;  &lt;br /&gt;&lt;/p&gt;
 &lt;hr&gt;&lt;/hr&gt;
 &lt;p&gt;  &lt;br /&gt;&lt;/p&gt;
 &lt;p&gt;综上，我对  &lt;code&gt;交付什么样的数据给业务层？&lt;/code&gt;这个问题的回答就是这样：&lt;/p&gt;
 &lt;p&gt;对于业务层而言，由Controller根据View和APIManager之间的关系，选择合适的reformer将View可以直接使用的数据（甚至reformer可以用来直接生成view）转化好之后交付给View。对于网络层而言，只需要保持住原始数据即可，不需要主动转化成数据原型。然后数据采用NSDictionary加Const字符串key来表征，避免了使用对象来表征带来的迁移困难，同时不失去可读性。&lt;/p&gt;
 &lt;p&gt;  &lt;br /&gt;&lt;/p&gt;
 &lt;hr&gt;&lt;/hr&gt;
 &lt;p&gt;  &lt;br /&gt;
  &lt;br /&gt;
  &lt;br /&gt;&lt;/p&gt;
 &lt;blockquote&gt;
  &lt;p&gt;集约型API调用方式和离散型API调用方式的选择？&lt;/p&gt;
&lt;/blockquote&gt;
 &lt;p&gt;  &lt;br /&gt;&lt;/p&gt;
 &lt;p&gt;集约型API调用其实就是所有API的调用只有一个类，然后这个类接收API名字，API参数，以及回调着陆点（可以是target-action，或者block，或者delegate等各种模式的着陆点）作为参数。然后执行类似  &lt;code&gt;startRequest&lt;/code&gt;这样的方法，它就会去根据这些参数起飞去调用API了，然后获得API数据之后再根据指定的着陆点去着陆。比如这样：&lt;/p&gt;
 &lt;p&gt;  &lt;br /&gt;&lt;/p&gt;
 &lt;div&gt;  &lt;pre&gt;集约型API调用方式：

[APIRequest startRequestWithApiName:@&amp;quot;itemList.v1&amp;quot; params:params success:@selector(success:) fail:@selector(fail:) target:self];
&lt;/pre&gt;&lt;/div&gt;


 &lt;p&gt;  &lt;br /&gt;
  &lt;br /&gt;&lt;/p&gt;
 &lt;p&gt;离散型API调用是这样的，一个API对应于一个APIManager，然后这个APIManager只需要提供参数就能起飞，API名字、着陆方式都已经集成入APIManager中。比如这样：&lt;/p&gt;
 &lt;p&gt;  &lt;br /&gt;&lt;/p&gt;
 &lt;div&gt;  &lt;pre&gt;离散型API调用方式：

@property (nonatomic, strong) ItemListAPIManager *itemListAPIManager;

// getter
- (ItemListAPIManager *)itemListAPIManager
{
    if (_itemListAPIManager == nil) {
        _itemListAPIManager = [[ItemListAPIManager alloc] init];
        _itemListAPIManager.delegate = self;
    }

    return _itemListAPIManager;
}

// 使用的时候就这么写：
[self.itemListAPIManager loadDataWithParams:params];
&lt;/pre&gt;&lt;/div&gt;


 &lt;p&gt;  &lt;br /&gt;
  &lt;br /&gt;&lt;/p&gt;
 &lt;p&gt;集约型API调用和离散型API调用这两者实现方案不是互斥的，单看下层，大家都是集约型。因为发起一个API请求之后，除去业务相关的部分（比如参数和API名字等），剩下的都是要统一处理的：加密，URL拼接，API请求的起飞和着陆，这些处理如果不用集约化的方式来实现，作者非癫即痴。然而对于整个网络层来说，尤其是业务方使用的那部分，我倾向于提供离散型的API调用方式，并不建议在业务层的代码直接使用集约型的API调用方式。原因如下：&lt;/p&gt;
 &lt;p&gt;  &lt;br /&gt;&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;原因1：当前请求正在外面飞着的时候，根据不同的业务需求存在两种不同的请求起飞策略：一个是取消新发起的请求，等待外面飞着的请求着陆。另一个是取消外面飞着的请求，让新发起的请求起飞。集约化的API调用方式如果要满足这样的需求，那么每次要调用的时候都要多写一部分判断和取消的代码，手段就做不到很干净。&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;  &lt;br /&gt;&lt;/p&gt;
 &lt;p&gt;前者的业务场景举个例子就是刷新页面的请求，刷新详情，刷新列表等。后者的业务场景举个例子是列表多维度筛选，比如你先筛选了商品类型，然后筛选了价格区间。当然，后者的情况不一定每次筛选都要调用API，我们先假设这种筛选每次都必须要通过调用API才能获得数据。&lt;/p&gt;
 &lt;p&gt;如果是离散型的API调用，在编写不同的APIManager时候就可以针对不同的API设置不同的起飞策略，在实际使用的时候，就可以不必关心起飞策略了，因为APIMananger里面已经写好了。&lt;/p&gt;
 &lt;p&gt;  &lt;br /&gt;
  &lt;br /&gt;&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;原因2：便于针对某个API请求来进行AOP。在集约型的API调用方式下，如果要针对某个API请求的起飞和着陆过程进行AOP，这代码得写成什么样。。。噢，尼玛这画面太美别说看了，我都不敢想。&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;  &lt;br /&gt;
  &lt;br /&gt;&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;原因3：当API请求的着陆点消失时，离散型的API调用方式能够更加透明地处理这种情况。&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;  &lt;br /&gt;&lt;/p&gt;
 &lt;p&gt;当一个页面的请求正在天上飞的时候，用户等了好久不耐烦了，小手点了个back，然后ViewController被pop被回收。此时请求的着陆点就没了。这是很危险的情况，着陆点要是没了，就很容易crash的。一般来说处理这个情况都是在dealloc的时候取消当前页面所有的请求。如果是集约型的API调用，这个代码就要写到ViewController的dealloc里面，但如果是离散型的API调用，这个代码写到APIManager里面就可以了，然后随着ViewController的回收进程，APIManager也会被跟着回收，这部分代码就得到了调用的机会。这样业务方在使用的时候就可以不必关心着陆点消失的情况了，从而更加关注业务。&lt;/p&gt;
 &lt;p&gt;  &lt;br /&gt;
  &lt;br /&gt;&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;原因4：离散型的API调用方式能够最大程度地给业务方提供灵活性，比如reformer机制就是基于离散型的API调用方式的。另外，如果是针对提供翻页机制的API，APIManager就能简单地提供   &lt;code&gt;loadNextPage&lt;/code&gt;方法去加载下一页，页码的管理就不用业务方去管理了。还有就是，如果要针对业务请求参数进行验证，比如用户填写注册信息，在离散型的APIManager里面实现就会非常轻松。&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;  &lt;br /&gt;
  &lt;br /&gt;&lt;/p&gt;
 &lt;p&gt;综上，关于集约型的API调用和离散型的API调用，我倾向于这样：对外提供一个BaseAPIManager来给业务方做派生，在BaseManager里面采用集约化的手段组装请求，放飞请求，然而业务方调用API的时候，则是以离散的API调用方式来调用。如果你的App只提供了集约化的方式，而没有离散方式的通道，那么我建议你再封装一层，便于业务方使用离散的API调用方式来放飞请求。&lt;/p&gt;
 &lt;p&gt;  &lt;br /&gt;&lt;/p&gt;
 &lt;hr&gt;&lt;/hr&gt;
 &lt;p&gt;  &lt;br /&gt;
  &lt;br /&gt;
  &lt;br /&gt;&lt;/p&gt;
 &lt;blockquote&gt;
  &lt;p&gt;怎么做APIManager的继承？&lt;/p&gt;
&lt;/blockquote&gt;
 &lt;p&gt;  &lt;br /&gt;&lt;/p&gt;
 &lt;p&gt;如果要做成离散型的API调用，那么使用继承是逃不掉的。BaseAPIManager里面负责集约化的部分，外部派生的XXXAPIManager负责离散的部分，对于BaseAPIManager来说，离散的部分有一些是必要的，比如API名字等，而我们派生的目的，也是为了提供这些数据。&lt;/p&gt;
 &lt;p&gt;我在  &lt;a href="http://casatwy.com/tiao-chu-mian-xiang-dui-xiang-si-xiang-yi-ji-cheng.html"&gt;这篇&lt;/a&gt;文章里面列举了种种继承的坏处，呼吁大家尽量不要使用继承。但是现在到了不得不用继承的时候，所以我得提醒一下大家别把继承用坏了。&lt;/p&gt;
 &lt;p&gt;在APIManager的情况下，我们最直觉的思路是BaseAPIManager提供一些空方法来给子类做重载，比如  &lt;code&gt;apiMethodName&lt;/code&gt;这样的函数，然而我的建议是，不要这么做。我们可以用IOP的方式来限制派生类的重载。&lt;/p&gt;
 &lt;p&gt;大概就是长这样：&lt;/p&gt;
 &lt;p&gt;  &lt;br /&gt;&lt;/p&gt;
 &lt;div&gt;  &lt;pre&gt;BaseAPIManager的init方法里这么写：

// 注意是weak。
@property (nonatomic, weak) id&amp;lt;APIManager&amp;gt; child;

- (instancetype)init
{
    self = [super init];
    if ([self confirmsToProtocol:@protocol(APIManager)]) {
        self.child = (id&amp;lt;APIManager&amp;gt;)self;
    } else {
        // 不遵守这个protocol的就让他crash，防止派生类乱来。
        NSAssert(NO, &amp;quot;子类必须要实现APIManager这个protocol。&amp;quot;);
    }
    return self;
}

protocol这么写，把原本要重载的函数都定义在这个protocol里面，就不用在父类里面写空方法了：
@protocol APIManager &amp;lt;NSObject&amp;gt;

@required
- (NSString *)apiMethodName;
...

@end

然后在父类里面如果要使用的话，就这么写：

[self requestWithAPIName:[self.child apiMethodName] ......];
&lt;/pre&gt;&lt;/div&gt;


 &lt;p&gt;  &lt;br /&gt;&lt;/p&gt;
 &lt;p&gt;简单说就是在init的时候检查自己是否符合预先设计的子类的protocol，这就要求所有子类必须遵守这个protocol，所有针对父类的重载、覆盖也都以这个protocol为准，protocol以外的方法不允许重载、覆盖。而在父类的代码里，可以不必遵守这个protocol，保持了未来维护的灵活性。&lt;/p&gt;
 &lt;p&gt;这么做的好处就是避免了父类写空方法，同时也给子类带上了紧箍咒：要想当我的孩子，就要遵守这些规矩，不能乱来。业务方在实现子类的时候，就可以根据protocol中的方法去一一实现，然后约定就比较好做了：不允许重载父类方法，只允许选择实现或不实现protocol中的方法。&lt;/p&gt;
 &lt;p&gt;关于这个的具体的论述在  &lt;a href="http://casatwy.com/tiao-chu-mian-xiang-dui-xiang-si-xiang-er-duo-tai.html"&gt;这篇文章&lt;/a&gt;里面有，感兴趣的话可以看看。&lt;/p&gt;
 &lt;p&gt;  &lt;br /&gt;
  &lt;br /&gt;
  &lt;br /&gt;&lt;/p&gt;
 &lt;blockquote&gt;
  &lt;p&gt;网络层与业务层对接部分的小总结&lt;/p&gt;
&lt;/blockquote&gt;
 &lt;p&gt;  &lt;br /&gt;&lt;/p&gt;
 &lt;p&gt;这一节主要是讲了以下这些点：&lt;/p&gt;
 &lt;p&gt;  &lt;br /&gt;&lt;/p&gt;
 &lt;ol&gt;
  &lt;li&gt;使用delegate来做数据对接，仅在必要时采用Notification来做跨层访问&lt;/li&gt;
  &lt;li&gt;交付NSDictionary给业务层，使用Const字符串作为Key来保持可读性&lt;/li&gt;
  &lt;li&gt;提供reformer机制来处理网络层反馈的数据，这个机制很重要，好处极多&lt;/li&gt;
  &lt;li&gt;网络层上部分使用离散型设计，下部分使用集约型设计&lt;/li&gt;
  &lt;li&gt;设计合理的继承机制，让派生出来的APIManager受到限制，避免混乱&lt;/li&gt;
  &lt;li&gt;应该不止这5点...&lt;/li&gt;
&lt;/ol&gt;
 &lt;p&gt;  &lt;br /&gt;
  &lt;br /&gt;
  &lt;br /&gt;&lt;/p&gt;
 &lt;h1&gt;网络层的安全机制&lt;/h1&gt;
 &lt;p&gt;  &lt;br /&gt;
  &lt;br /&gt;&lt;/p&gt;
 &lt;h3&gt;判断API的调用请求是来自于经过授权的APP&lt;/h3&gt;
 &lt;p&gt;  &lt;br /&gt;&lt;/p&gt;
 &lt;p&gt;使用这个机制的目的主要有两点：&lt;/p&gt;
 &lt;p&gt;  &lt;br /&gt;&lt;/p&gt;
 &lt;ol&gt;
  &lt;li&gt;确保API的调用者是来自你自己的APP，防止竞争对手爬你的API&lt;/li&gt;
  &lt;li&gt;如果你对外提供了需要注册才能使用的API平台，那么你需要有这个机制来识别是否是注册用户调用了你的API&lt;/li&gt;
&lt;/ol&gt;
 &lt;p&gt;  &lt;br /&gt;
  &lt;br /&gt;&lt;/p&gt;
 &lt;blockquote&gt;
  &lt;p&gt;解决方案：设计签名&lt;/p&gt;
&lt;/blockquote&gt;
 &lt;p&gt;  &lt;br /&gt;&lt;/p&gt;
 &lt;p&gt;要达到第一个目的其实很简单，服务端需要给你一个密钥，每次调用API时，你使用这个密钥再加上API名字和API请求参数算一个hash出来，然后请求的时候带上这个hash。服务端收到请求之后，按照同样的密钥同样的算法也算一个hash出来，然后跟请求带来的hash做一个比较，如果一致，那么就表示这个API的调用者确实是你的APP。为了不让别人也获取到这个密钥，你最好不要把这个密钥存储在本地，直接写死在代码里面就好了。另外适当增加一下求Hash的算法的复杂度，那就是各种Hash算法（比如MD5）加点盐，再回炉跑一次Hash啥的。这样就能解决第一个目的了：确保你的API是来自于你自己的App。&lt;/p&gt;
 &lt;p&gt;一般情况下大部分公司不会出现需要满足第二种情况的需求，除非公司开发了自己的API平台给第三方使用。这个需求跟上面的需求有一点不同：符合授权的API请求者不只是一个。所以在这种情况下，需要的安全机制会更加复杂一点。&lt;/p&gt;
 &lt;p&gt;这里有一个较容易实现的方案：客户端调用API的时候，把自己的密钥通过一个可逆的加密算法加密后连着请求和加密之后的Hash一起送上去。当然，这个可逆的加密算法肯定是放在在调用API的SDK里面，编译好的。然后服务端拿到加密后的密钥和加密的Hash之后，解码得到原始密钥，然后再用它去算Hash，最后再进行比对。&lt;/p&gt;
 &lt;p&gt;  &lt;br /&gt;
  &lt;br /&gt;&lt;/p&gt;
 &lt;h3&gt;保证传输数据的安全&lt;/h3&gt;
 &lt;p&gt;  &lt;br /&gt;&lt;/p&gt;
 &lt;p&gt;使用这个机制的主要目的有两点：&lt;/p&gt;
 &lt;p&gt;  &lt;br /&gt;&lt;/p&gt;
 &lt;ol&gt;
  &lt;li&gt;防止中间人攻击，比如说运营商很喜欢往用户的Http请求里面塞广告...&lt;/li&gt;
  &lt;li&gt;SPDY依赖于HTTPS，而且是未来HTTP/2的基础，他们能够提高你APP在网络层整体的性能。&lt;/li&gt;
&lt;/ol&gt;
 &lt;p&gt;  &lt;br /&gt;
  &lt;br /&gt;&lt;/p&gt;
 &lt;blockquote&gt;
  &lt;p&gt;解决方案：HTTPS&lt;/p&gt;
&lt;/blockquote&gt;
 &lt;p&gt;目前使用HTTPS的主要目的在于防止运营商往你的Response Data里面加广告啥的（中间人攻击），面对的威胁范围更广。从2011年开始，国外业界就已经提倡所有的请求（不光是API，还有网站）都走HTTPS，国内差不多晚了两年（2013年左右）才开始提倡这事，天猫是这两个月才开始做HTTPS的全APP迁移。&lt;/p&gt;
 &lt;p&gt;关于速度，HTTPS肯定是比HTTP慢的，毕竟多了一次握手，但挂上SPDY之后，有了链接复用，这方面的性能就有了较大提升。这里的性能提升并不是说一个请求原来要500ms能完成，然后现在只要300ms，这是不对的。所谓整体性能是基于大量请求去讨论的：同样的请求量（假设100个）在短期发生时，挂上SPDY之后完成这些任务所要花的时间比不用SPDY要少。SPDY还有Header压缩的功能，不过因为一个API请求本身已经比较小了，压缩数据量所带来的性能提升不会特别明显，所以就单个请求来看，性能的提升是比较小的。不过这是下一节要讨论的事儿了，这儿只是顺带说一下。&lt;/p&gt;
 &lt;p&gt;  &lt;br /&gt;
  &lt;br /&gt;&lt;/p&gt;
 &lt;h3&gt;安全机制小总结&lt;/h3&gt;
 &lt;p&gt;  &lt;br /&gt;&lt;/p&gt;
 &lt;p&gt;这一节说了两种安全机制，一般来说第一种是标配，第二种属于可选配置。不过随着我国互联网基础设施的完善，移动设备性能的提高，以及优化技术的提高，第二种配置的缺点（速度慢）正在越来越微不足道，因此HTTPS也会成为不久之后的未来App的网络层安全机制标配。各位架构师们，如果你的App还没有挂HTTPS，现在就已经可以开始着手这件事情了。&lt;/p&gt;
 &lt;p&gt;  &lt;br /&gt;
  &lt;br /&gt;
  &lt;br /&gt;&lt;/p&gt;
 &lt;h1&gt;网络层的优化方案&lt;/h1&gt;
 &lt;p&gt;  &lt;br /&gt;&lt;/p&gt;
 &lt;p&gt;网络层的优化手段主要从以下三方面考虑：&lt;/p&gt;
 &lt;p&gt;  &lt;br /&gt;&lt;/p&gt;
 &lt;ol&gt;
  &lt;li&gt;针对链接建立环节的优化&lt;/li&gt;
  &lt;li&gt;针对链接传输数据量的优化&lt;/li&gt;
  &lt;li&gt;针对链接复用的优化&lt;/li&gt;
&lt;/ol&gt;
 &lt;p&gt;  &lt;br /&gt;&lt;/p&gt;
 &lt;p&gt;这三方面是所有优化手段的内容，各种五花八门的优化手段基本上都不会逃脱这三方面，下面我就会分别针对这三方面讲一下各自对应的优化手段。&lt;/p&gt;
 &lt;p&gt;  &lt;br /&gt;&lt;/p&gt;
 &lt;h3&gt;1. 针对链接建立环节的优化&lt;/h3&gt;
 &lt;p&gt;在API发起请求建立链接的环节，大致会分这些步骤：&lt;/p&gt;
 &lt;p&gt;  &lt;br /&gt;&lt;/p&gt;
 &lt;ol&gt;
  &lt;li&gt;发起请求&lt;/li&gt;
  &lt;li&gt;DNS域名解析得到IP&lt;/li&gt;
  &lt;li&gt;根据IP进行三次握手（HTTPS四次握手），链接建立成功&lt;/li&gt;
&lt;/ol&gt;
 &lt;p&gt;  &lt;br /&gt;&lt;/p&gt;
 &lt;p&gt;其实第三步的优化手段跟第二步的优化手段是一致的，我会在讲第二步的时候一起讲掉。&lt;/p&gt;
 &lt;p&gt;  &lt;br /&gt;
  &lt;br /&gt;&lt;/p&gt;
 &lt;blockquote&gt;
  &lt;p&gt;1.1 针对发起请求的优化手段&lt;/p&gt;
&lt;/blockquote&gt;
 &lt;p&gt;  &lt;br /&gt;&lt;/p&gt;
 &lt;p&gt;其实要解决的问题就是网络层该不该为此API调用发起请求。&lt;/p&gt;
 &lt;p&gt;  &lt;br /&gt;
  &lt;br /&gt;&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;1.1.1 使用缓存手段减少请求的发起次数&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;  &lt;br /&gt;&lt;/p&gt;
 &lt;p&gt;对于大部分API调用请求来说，有些API请求所带来的数据的时效性是比较长的，比如商品详情，比如App皮肤等。那么我们就可以针对这些数据做本地缓存，这样下次请求这些数据的时候就可以不必再发起新的请求。&lt;/p&gt;
 &lt;p&gt;一般是把API名字和参数拼成一个字符串然后取MD5作为key，存储对应返回的数据。这样下次有同样请求的时候就可以直接读取这里面的数据。关于这里有一个缓存策略的问题需要讨论：  &lt;code&gt;什么时候清理缓存？&lt;/code&gt;要么就是根据超时时间限制进行清理，要么就是根据缓存数据大小进行清理。这个策略的选择要根据具体App的操作日志来决定。&lt;/p&gt;
 &lt;p&gt;比如安居客App，日志数据记录显示用户平均使用时长不到3分钟，但是用户查看房源详情的次数比较多，而房源详情数据量较大。那么这个时候，就适合根据使用时长来做缓存，我当时给安居客设置的缓存超时时间就是3分钟，这样能够保证这个缓存能够在大部分用户使用时间产生作用。嗯，极端情况下做什么缓存手段不考虑，只要能够服务好80%的用户就可以了，而且针对极端情况采用的优化手段对大部分普通用户而言是不必要的，做了反而会对他们有影响。&lt;/p&gt;
 &lt;p&gt;再比如网络图片缓存，数据量基本上都特别大，这种就比较适合针对缓存大小来清理缓存的策略。&lt;/p&gt;
 &lt;p&gt;另外，之前的缓存的前提都是基于内存的。我们也可以把需要清理的缓存存储在硬盘上（APP的本地存储，我就先用硬盘来表示了，虽然很少有手机硬盘的说法，哈哈），比如前面提到的图片缓存，因为图片很有可能在很长时间之后，再被显示的，那么原本需要被清理的图片缓存，我们就可以考虑存到硬盘上去。当下次再有显示网络图片的需求的时候，我们可以先从内存中找，内存找不到那就从硬盘上找，这都找不到，那就发起请求吧。&lt;/p&gt;
 &lt;p&gt;当然，有些时效性非常短的API数据，就不能使用这个方法了，比如用户的资金数据，那就需要每次都调用了。&lt;/p&gt;
 &lt;p&gt;  &lt;br /&gt;
  &lt;br /&gt;&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;1.1.2 使用策略来减少请求的发起次数&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;  &lt;br /&gt;&lt;/p&gt;
 &lt;p&gt;这个我在前面提到过，就是针对重复请求的发起和取消，是有对应的请求策略的。我们先说取消策略。&lt;/p&gt;
 &lt;p&gt;如果是界面刷新请求这种，而且存在重复请求的情况（下拉刷新时，在请求着陆之前用户不断执行下拉操作），那么这个时候，后面重复操作导致的API请求就可以不必发送了。&lt;/p&gt;
 &lt;p&gt;如果是条件筛选这种，那就取消前面已经发送的请求。虽然很有可能这个请求已经被执行了，那么取消所带来的性能提升就基本没有了。但如果这个请求还在队列中待执行的话，那么对应的这次链接就可以省掉了。&lt;/p&gt;
 &lt;p&gt;  &lt;br /&gt;&lt;/p&gt;
 &lt;p&gt;以上是一种，另外一种情况就是请求策略：类似用户操作日志的请求策略。&lt;/p&gt;
 &lt;p&gt;用户操作会触发操作日志上报Server，这种请求特别频繁，但是是暗地里进行的，不需要用户对此有所感知。所以也没必要操作一次就发起一次的请求。在这里就可以采用这样的策略：在本地记录用户的操作记录，当记录满30条的时候发起一次请求将操作记录上传到服务器。然后每次App启动的时候，上传一次上次遗留下来没上传的操作记录。这样能够有效降低用户设备的耗电量，同时提升网络层的性能。&lt;/p&gt;
 &lt;p&gt;  &lt;br /&gt;
  &lt;br /&gt;&lt;/p&gt;
 &lt;blockquote&gt;
  &lt;p&gt;小总结&lt;/p&gt;
&lt;/blockquote&gt;
 &lt;p&gt;  &lt;br /&gt;&lt;/p&gt;
 &lt;p&gt;针对建立连接这部分的优化就是这样的原则：能不发请求的就尽量不发请求，必须要发请求时，能合并请求的就尽量合并请求。然而，任何优化手段都是有前提的，而且也不能保证对所有需求都能起作用，有些API请求就是不符合这些优化手段前提的，那就老老实实发请求吧。不过这类API请求所占比例一般不大，大部分的请求都或多或少符合优化条件，所以针对发送请求的优化手段还是值得做的。&lt;/p&gt;
 &lt;p&gt;  &lt;br /&gt;
  &lt;br /&gt;&lt;/p&gt;
 &lt;blockquote&gt;
  &lt;p&gt;1.2 &amp;amp; 1.3 针对DNS域名解析做的优化，以及建立链接的优化&lt;/p&gt;
&lt;/blockquote&gt;
 &lt;p&gt;  &lt;br /&gt;&lt;/p&gt;
 &lt;p&gt;其实在整个DNS链路上也是有DNS缓存的，理论上也是能够提高速度的。这个链路上的DNS缓存在PC用户上效果明显，因为PC用户的DNS链路相对稳定，信号源不会变来变去。但是在移动设备的用户这边，链路上的DNS缓存所带来的性能提升就不太明显了。因为移动设备的实际使用场景比较复杂，网络信号源会经常变换，信号源每变换一次，对应的DNS解析链路就会变换一次，那么原链路上的DNS缓存就不起作用了。而且信号源变换的情况特别特别频繁，所以对于移动设备用户来说，链路的DNS缓存我们基本上可以默认为没有。所以大部分时间是手机系统自带的本地DNS缓存在起作用，但是一般来说，移动设备上网的需求也特别频繁，专门为我们这个App所做的DNS缓存很有可能会被别的DNS缓存给挤出去被清理掉，这种情况是特别多的，用户看一会儿知乎刷一下微博查一下地图逛一逛点评再聊个Q，回来之后很有可能属于你自己的App的本地DNS缓存就没了。这还没完，这里还有一个只有在中国特色社会主义的互联网环境中才会有的问题：国内的互联网环境由于GFW的存在，就使得DNS服务速度会比正常情况慢不少。&lt;/p&gt;
 &lt;p&gt;基于以上三个原因所导致的最终结果就是，API请求在DNS解析阶段的耗时会很多。&lt;/p&gt;
 &lt;p&gt;那么针对这个的优化方案就是，索性直接走IP请求，那不就绕过DNS服务的耗时了嘛。&lt;/p&gt;
 &lt;p&gt;  &lt;br /&gt;&lt;/p&gt;
 &lt;hr&gt;&lt;/hr&gt;
 &lt;p&gt;  &lt;br /&gt;&lt;/p&gt;
 &lt;p&gt;另外一个，就是上面提到的建立链接时候的第三步，国内的网络环境分北网通南电信（当然实际情况更复杂，这里随便说说），不同服务商之间的连接，延时是很大的，我们需要想办法让用户在最适合他的IP上给他提供服务，那么就针对我们绕过DNS服务的手段有一个额外要求：尽可能不要让用户使用对他来说很慢的IP。&lt;/p&gt;
 &lt;p&gt;所以综上所述，方案就应该是这样：本地有一份IP列表，这些IP是所有提供API的服务器的IP，每次应用启动的时候，针对这个列表里的所有IP取ping延时时间，然后取延时时间最小的那个IP作为今后发起请求的IP地址。&lt;/p&gt;
 &lt;p&gt;  &lt;br /&gt;&lt;/p&gt;
 &lt;hr&gt;&lt;/hr&gt;
 &lt;p&gt;  &lt;br /&gt;&lt;/p&gt;
 &lt;p&gt;针对建立连接的优化手段其实是跟DNS域名解析的优化手段是一样的。不过这需要你的服务器提供服务的网络情况要多，一般现在的服务器都是双网卡，电信和网通。由于中国特色的互联网ISP分布，南北网络之间存在瓶颈，而我们App针对链接的优化手段主要就是着手于如何减轻这个瓶颈对App产生的影响，所以需要维护一个IP列表，这样就能就近连接了，就起到了优化的效果。&lt;/p&gt;
 &lt;p&gt;  &lt;br /&gt;&lt;/p&gt;
 &lt;p&gt;我们一般都是在应用启动的时候获得本地列表中所有IP的ping值，然后通过NSURLProtocol的手段将URL中的HOST修改为我们找到的最快的IP。另外，这个本地IP列表也会需要通过一个API来维护，一般是每天第一次启动的时候读一次API，然后更新到本地。&lt;/p&gt;
 &lt;p&gt;如果你还不熟悉NSURLProtocol应该怎么玩，看完  &lt;a href="https://developer.apple.com/library/mac/documentation/Cocoa/Reference/Foundation/Classes/NSURLProtocol_Class/"&gt;官方文档&lt;/a&gt;和  &lt;a href="http://nshipster.com/nsurlprotocol/"&gt;这篇文章&lt;/a&gt;以及  &lt;a href="https://github.com/rmls/NSURLProtocolExample"&gt;这个Demo&lt;/a&gt;之后，你肯定就会了，其实很简单的。另外，刚才提到  &lt;a href="http://nshipster.com/nsurlprotocol/"&gt;那篇文章&lt;/a&gt;的作者(mattt)还写了  &lt;a href="https://github.com/mattt/NSEtcHosts"&gt;这个基于NSURLProtocol的工具&lt;/a&gt;，相当好用，是可以直接拿来集成到项目中的。&lt;/p&gt;
 &lt;p&gt;不用NSURLProtocol的话，用其他手段也可以做到这一点，但那些手段未免又比较愚蠢。&lt;/p&gt;
 &lt;p&gt;  &lt;br /&gt;
  &lt;br /&gt;&lt;/p&gt;
 &lt;h3&gt;2. 针对链接传输数据量的优化&lt;/h3&gt;
 &lt;p&gt;  &lt;br /&gt;&lt;/p&gt;
 &lt;p&gt;这个很好理解，传输的数据少了，那么自然速度就上去了。这里没什么花样可以讲的，就是压缩呗。各种压缩。&lt;/p&gt;
 &lt;p&gt;  &lt;br /&gt;
  &lt;br /&gt;&lt;/p&gt;
 &lt;h3&gt;3. 针对链接复用的优化&lt;/h3&gt;
 &lt;p&gt;  &lt;br /&gt;&lt;/p&gt;
 &lt;p&gt;建立链接本身是属于比较消耗资源的操作，耗电耗时。SPDY自带链接复用以及数据压缩的功能，所以服务端支持SPDY的时候，App直接挂SPDY就可以了。如果服务端不支持SPDY，也可以使用PipeLine，苹果原生自带这个功能。&lt;/p&gt;
 &lt;p&gt;一般来说业界内普遍的认识是SPDY优于PipeLine，然后即便如此，SPDY能够带来的网络层效率提升其实也没有文献上的图表那么明显，但还是有性能提升的。还有另外一种比较笨的链接复用的方法，就是维护一个队列，然后将队列里的请求压缩成一个请求发出去，之所以会存在滞留在队列中的请求，是因为在上一个请求还在外面飘的时候。这种做法最终的效果表面上看跟链接复用差别不大，但并不是真正的链接复用，只能说是请求合并。&lt;/p&gt;
 &lt;p&gt;还是说回来，我建议最好是用SPDY，SPDY和pipeline虽然都属于链接复用的范畴，但是pipeline并不是真正意义上的链接复用，SPDY的链接复用相对pipeline而言更为彻底。SPDY目前也有现成的客户端SDK可以使用，一个是twitter的  &lt;a href="https://github.com/twitter/CocoaSPDY"&gt;CocoaSPDY&lt;/a&gt;，另一个是  &lt;a href="https://github.com/Voxer/iSPDY"&gt;Voxer/iSPDY&lt;/a&gt;，这两个库都很活跃，大家可以挑合适的采用。&lt;/p&gt;
 &lt;p&gt;不过目前业界趋势是倾向于使用HTTP/2.0来代替SPDY，不过目前HTTP/2.0还没有正式出台，相关实现大部分都处在demo阶段，所以我们还是先SPDY搞起就好了。未来很有可能会放弃SPDY，转而采用HTTP/2.0来实现网络的优化。这是要提醒各位架构师注意的事情。嗯，我也不知道HTTP/2.0什么时候能出来。&lt;/p&gt;
 &lt;p&gt;  &lt;br /&gt;
  &lt;br /&gt;
  &lt;br /&gt;&lt;/p&gt;
 &lt;hr&gt;&lt;/hr&gt;
 &lt;p&gt;  &lt;br /&gt;&lt;/p&gt;
 &lt;blockquote&gt;
  &lt;p&gt;渔说完了，鱼来了&lt;/p&gt;
&lt;/blockquote&gt;
 &lt;p&gt;  &lt;br /&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;a href="https://github.com/casatwy/RTNetworking"&gt;这里&lt;/a&gt;是我当年设计并实现的安居客的网络层架构代码。当然，该脱敏的地方我都已经脱敏了，所以编不过是正常的，哈哈哈。但是代码比较齐全，重要地方注释我也写了很多。另外，为了让大家能够把这些代码看明白，我还附带了当年介绍这个框架演讲时的PPT。(  &lt;code&gt;补充说明一下，评论区好多人问PPT找不着在哪儿，PPT也在上面提到的repo里面，是个key后缀名的文件，用keynote打开&lt;/code&gt;)&lt;/p&gt;
 &lt;p&gt;然后就是，当年也有很多问题其实考虑得并没有现在清楚，所以有些地方还是做得不够好，比如拦截器和继承。而且当时的优化手段只有本地cache，安居客没有那么多IP可以给我ping，当年也没流行SPDY，而且API也还不支持HTTPS，所以当时的代码里面没有在这些地方做优化，比较原始。然而整个架构的基本思路一直没有变化：优先服务于业务方。另外，安居客的网络层多了一个service的概念，这是我这篇文章中没有讲的。主要是因为安居客的API提供方很多，二手房，租房，新房，X项目等等API都是不同的API team提供的，以service作区分，如果你的app也是类似的情况，我也建议你设计一套service机制。现在这些service被我删得只剩下一个google的service，因为其他service都属于敏感内容。&lt;/p&gt;
 &lt;p&gt;另外，这里面提供的PPT我很希望大家能够花时间去看看，在PPT里面有些更加细的东西我在博客里没有写，主要是我比较懒，然后这篇文章拖的时间比较长了，花时间搬运这个没什么意思，不过内容还是值得各位读者去看的。关于PPT里面大家有什么问题的，也可以在评论区问，我都会回答。&lt;/p&gt;
 &lt;p&gt;  &lt;br /&gt;
  &lt;br /&gt;
  &lt;br /&gt;&lt;/p&gt;
 &lt;h1&gt;总结&lt;/h1&gt;
 &lt;p&gt;  &lt;br /&gt;&lt;/p&gt;
 &lt;p&gt;第一部分主要讲了网络层应当如何跟业务层进行数据交互，进行数据交互时采用怎样的数据格式，以及设计时代码结构上的一些问题，诸如继承的处理，回调的处理，交互方式的选择，reformer的设计，保持数据可读性等等等等，主要偏重于设计（这可是艺术活，哈哈哈）。&lt;/p&gt;
 &lt;p&gt;第二部分讲了网络安全上，客户端要做的两点。当然，从网络安全的角度上讲，服务端也要做很多很多事情，客户端要做的一些边角细节的事情也还会有很多，比如做一些代码混淆，尽可能避免代码中明文展示key。不过大头主要就是这两个，而且也都是需要服务端同学去配合的。主要偏重于介绍。（主要是也没啥好实践的，google一下教程照着来就好了）。&lt;/p&gt;
 &lt;p&gt;第三部分讲了优化，优化的所有方面都已经列出来了，如果业界再有七七八八的别的手段，也基本逃离不出本文的范围。这里有些优化手段是需要服务端同学配合的，有些不需要，大家看各自情况来决定。主要偏重于实践。&lt;/p&gt;
 &lt;p&gt;最后给出了我之前在安居客做的网络层架构的主要代码，以及当时演讲时的PPT。关于代码或PPT中有任何问题，都可以在评论区问我。&lt;/p&gt;
 &lt;p&gt;这一篇文章出得比较晚，因为公司的事情，中间间隔了一个礼拜，希望大家谅解。另外，隔了一个礼拜之后我再写，发现有些地方我已经想不起来当初是应该怎么行文下去的了，然后发之前我把文章又看了几遍，尽可能把断片的地方抹平了，如果大家读起来有什么地方感觉奇怪的，或者讲到一半就没了的，那应该就是断片了。在评论区跟我说一下，我补上去。&lt;/p&gt;
 &lt;p&gt;然后如果有需要勘误的地方，也请在评论区指出，帮助我把错的地方订正回来，如果有没讲到的地方，但你又特别想要了解的，也可以在评论区提出来，我会补上去。说不定看完之后你脑袋里还会有很多个问号，也请在评论区问出来哈，说不定别人也有跟你一样的问题，他就能在评论区找到答案了。&lt;/p&gt;
 &lt;p&gt;  &lt;br /&gt;
  &lt;br /&gt;
  &lt;br /&gt;&lt;/p&gt;
 &lt;hr&gt;&lt;/hr&gt;
 &lt;p&gt;  &lt;br /&gt;&lt;/p&gt;
 &lt;p&gt;在第二篇文章的评论区里面出现了喷子，遇到这种情况我怎么可能删帖呢？那根本就不是我的风格哇，哈哈哈。我肯定是会喷回去的，并且还会把链接传播给周围人，发动周围朋友来看:&amp;quot;快看，这儿有2B，哈哈哈&amp;quot;。&lt;/p&gt;
 &lt;p&gt;嗯，所以评论的时候你一定要想清楚哈，我写代码的实力不差，打嘴仗的实力那可比写代码强多了。评论区同样欢迎切磋。&lt;/p&gt;
 &lt;p&gt;  &lt;br /&gt;&lt;/p&gt;
 &lt;hr&gt;&lt;/hr&gt;
 &lt;p&gt;  &lt;br /&gt;
  &lt;br /&gt;
  &lt;br /&gt;&lt;/p&gt;
 &lt;p&gt;本文遵守CC-BY。 请保持转载后文章内容的完整，以及文章出处。本人保留所有版权相关权利。&lt;/p&gt;&lt;div&gt; &lt;a href="https://itindex.net/"  title="IT 资讯"&gt;&lt;img src="https://itindex.net/images/iconWarning.gif" title="IT 资讯" border="0"/&gt; &lt;/a&gt;</description>
      <category>iOS architect thoughts</category>
      <guid isPermaLink="true">https://itindex.net/detail/53690-ios-%E5%BA%94%E7%94%A8-%E6%9E%B6%E6%9E%84</guid>
      <pubDate>Mon, 01 Jun 2015 00:00:00 CST</pubDate>
    </item>
    <item>
      <title>10个 iOS 用户暂可以嘲笑 Android 的特点</title>
      <link>https://itindex.net/detail/53625-ios-%E7%94%A8%E6%88%B7-android</link>
      <description>&lt;p&gt;Android 与 iOS 设备之间的争斗从未停止，毕竟一切高科技产品的理念和实际表现方式都不相同。就拿 Android 来说，很多功能令用户并  &lt;br /&gt;
不太开心，甚至是令人愤怒，下面让我们来简单的盘点 10 个 iOS 比 Android 优秀的特征。当然，这并不意味  &lt;br /&gt;
着 Android 比 iOS 差，因为每天让库克最为头痛的事情，就是每天都会有用户转投 Android，反之亦然，因为我们还会盘  &lt;br /&gt;
点 Android 比 iOS 更好的 10 个特征。&lt;/p&gt;
 &lt;p&gt;1、设计不一致&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" height="417" src="http://www.techug.com/wordpress/wp-content/uploads/2015/06/06074351_QO5Z.jpg?9bae72" width="550"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;让  &lt;br /&gt;
我们面对现实，即使谷歌公布了 Material Design（材料设计）作为设计语言，并希望开发者能够遵循，但是目前真正采  &lt;br /&gt;
用 Material Design 风格界面的应用程序非常少，而大多数仍然使用老旧的 Holo Design 设计语言。不清楚是否是因  &lt;br /&gt;
为  Material Design 不佳还是开发者认为没必要，Google Play 上还是有很多不同风格应用程序，而且看起来统一设计的道路还  &lt;br /&gt;
非常长。就设计语言凝聚力和统一性而言，苹果的应用程序做得好很多，大多数应用程序都已经专门针对新的系统风格调整用户界面设计。&lt;/p&gt;
 &lt;p&gt;2、本身就不像精简的 iOS&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" height="375" src="http://www.techug.com/wordpress/wp-content/uploads/2015/06/06074351_watw.jpg?9bae72" width="600"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;Android 操  &lt;br /&gt;
作系统远不及 iOS 直观，随便一台 Android 设备总能找到不同的选项或功能，而苹果的 iOS  系统上做每一件事情都使用了同样的方式。当  &lt;br /&gt;
然，原生的 Android 操作系统可能最为直观，但设备制造商就是不喜欢原生 Android，几乎每一个品牌的智能手机都有自家定制的用户界面。这  &lt;br /&gt;
意味着，一个用户如果要换不同品牌的手机，必须要通过一定的学习才能适应，这个学习过程有可能是轻量级，也可能难以使用。更重要的是，同一品牌的智能手  &lt;br /&gt;
机，每一款的界面还不一样，这一点与 iOS 用户更换新   &lt;a href="http://aos.prf.hn/click/camref:100lcC/creativeref:305226" rel="nofollow" target="_blank"&gt;iPhone&lt;/a&gt; 直接上手相比体验大为不同。&lt;/p&gt;
 &lt;p&gt;3、系统更新升级&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" height="340" src="http://www.techug.com/wordpress/wp-content/uploads/2015/06/06074351_Vyzl.png?9bae72" width="600"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;这  &lt;br /&gt;
一点也是事实。每当 Android 的新版本出来时，大多数 Android 用户必须等待很长一段时间，才能够获得来自官方的正式版系统升级，而这已  &lt;br /&gt;
经是幸运儿了，如不幸可能永久等待也无任何升级希望。反观 iOS 设备，在规定新系统版本发布之日，总是会有大量用户直接升级，随后新系统的人数占据绝  &lt;br /&gt;
大多数。当然，如果用户手持 Nexus 设备的话，将最快获得升级，不过 Nexus 在全球 Android 手机之中市场份额相当之低。&lt;/p&gt;
 &lt;p&gt;4、内置大量臃肿的应用程序&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" height="288" src="http://www.techug.com/wordpress/wp-content/uploads/2015/06/06074352_XtGj.png?9bae72" width="600"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;内  &lt;br /&gt;
置应用程序的数量和臃肿程度，完全取决于用户购买的是哪一款 Android 智能手机。品牌制造商和运营商，两者都非常喜欢在智能手机里预装各种应用程  &lt;br /&gt;
序，而且大部分没办法直接禁用或者完全手动删除，不过其中也有一些设备不会太过于臃肿。反观 iOS 设备，虽然也有不少预装应用程序，一些对个人可能没  &lt;br /&gt;
有多大用处，比如 Apple Watch，但所预装的应用程序并没有让用户感受到十分臃肿。总之，虽然 iOS 系统正在变大，但不可否认很  &lt;br /&gt;
多 Android 设备的体验的确毁在大量预装应用上。&lt;/p&gt;
 &lt;p&gt;5、控制中心更直观易用&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" height="533" src="http://www.techug.com/wordpress/wp-content/uploads/2015/06/06074352_j4OF.jpg?9bae72" width="391"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;这  &lt;br /&gt;
一点不同的用户感受不同，反应有好有坏。iOS 设备支持直接从任何界面向上滑动访问控制中心，并提供一些重要的开关，包括音乐控制和音量调节。  &lt;br /&gt;
Android 是最早提供快捷设置和通知栏智能手机，但是一些用户认为其排列混乱不够直观，比如 Android Lollipop 系统里，用户必须  &lt;br /&gt;
滑动那个两次顶部或者使用双指手势，才能找到需要的快捷开关，而 iOS 只要简单的从底部滑动，显得更简单也更人性化，只是缺陷在于快捷开关无法自定  &lt;br /&gt;
义。&lt;/p&gt;
 &lt;p&gt;6、内置的相机应用功能有限&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" height="678" src="http://www.techug.com/wordpress/wp-content/uploads/2015/06/06074352_D77H.jpg?9bae72" width="600"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;就默认相机功能而言，iOS 相比 Android 看起来更直观功能也更多，比如手动控制曝光，自动曝光/自动对焦等，很多功能在 Android 设备上的默认相机里缺失。不过，新版 Android 以及大多数设备制造商提供的相机应用，可以作为弥补。&lt;/p&gt;
 &lt;p&gt;7、无 iCloud 备份功能&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" height="395" src="http://www.techug.com/wordpress/wp-content/uploads/2015/06/06074352_C1p1.jpg?9bae72" width="600"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;iOS 自  &lt;br /&gt;
带的强大的数据备份功能莫过于 iCloud，而且直观性和易用性良好，用户只要打开开关，选择需要备份的应用和数据即可，在 Wi-Fi 环境下还能自  &lt;br /&gt;
动备份，随时还原。Android 上也有相类似的解决方案，只是大多数难以完整的备份，真要完美则需要 Root 获取权限，然后再通过第三  &lt;br /&gt;
方 Recovery 备份和恢复，比如 Nandroid 和钛备份的方案。&lt;/p&gt;
 &lt;p&gt;8、应用程序更新&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" height="400" src="http://www.techug.com/wordpress/wp-content/uploads/2015/06/06074352_cCMO.jpg?9bae72" width="592"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;这  &lt;br /&gt;
一点也是事实，Google Play 现在的确比苹果 App Store 拥有更多的游戏和应用，但后者总是更讨开发者欢迎，尤其当涉及到发布新款或  &lt;br /&gt;
新版应用程序，iOS 总是开发者优先首选，Android 才紧随其后，很多用户对开发者或开发商的“应用无更新”不满也源于此，不过苹果的确为开发者  &lt;br /&gt;
带来了更多的收入。&lt;/p&gt;
 &lt;p&gt;9、缺乏连续互通功能&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" height="487" src="http://www.techug.com/wordpress/wp-content/uploads/2015/06/06074352_snkj.jpg?9bae72" width="599"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;如  &lt;br /&gt;
果家里有苹果家族全套产品，比如 Mac、iPad 和 iPhone，只要移动设备升级到了新版 iOS 8，很多工作和生活上的任务处理将变得更方便  &lt;br /&gt;
一些，比如 Mac 未完成的工作可以在 iPad 上继续，iPhone 来电时 Mac 可以代替接电话，就算没有 Wi-Fi 也能共享热点等等，  &lt;br /&gt;
很多功能在苹果设备之间都能实现无缝衔接。而这一点在 Android 上还无法真正实现，谷歌也正在完善 Chrome OS，让其代替接受通知，甚至  &lt;br /&gt;
就直接运行 Android 应用。&lt;/p&gt;
 &lt;p&gt;10、苹果的健康应用目前比 Google Fit 完善&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" height="494" src="http://www.techug.com/wordpress/wp-content/uploads/2015/06/06074353_rfAY.jpg?9bae72" width="599"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;提到苹果的健康应用就难免不让人想起 Google Fit。相比苹果而言，谷歌的方案目前在功能上还是令很多很多用户失望，尤其是生态系统不够完善。苹果  &lt;br /&gt;
的健康功能更为丰富一些，同时还拥有很多配套的第三方应用程序和配件产品，得益于完善的 HealthKit 和 ResearchKit，iOS 设备  &lt;br /&gt;
就像是一个强大的医学平台。&lt;/p&gt;
&lt;div&gt; &lt;a href="https://itindex.net/"  title="IT 资讯"&gt;&lt;img src="https://itindex.net/images/iconWarning.gif" title="IT 资讯" border="0"/&gt; &lt;/a&gt;</description>
      <category>娱乐幽默 android ios</category>
      <guid isPermaLink="true">https://itindex.net/detail/53625-ios-%E7%94%A8%E6%88%B7-android</guid>
      <pubDate>Tue, 09 Jun 2015 15:39:55 CST</pubDate>
    </item>
    <item>
      <title>Apple 发布 iOS 8.0.2 更新</title>
      <link>https://itindex.net/detail/51218-apple-ios-%E6%9B%B4%E6%96%B0</link>
      <description>&lt;p&gt;在昨天 Apple 闹出 iOS 8.0.1 的”乌龙”事件之后，今天 Apple 上线了 iOS 8.0.2 的更新，修正了 iOS 8.0.1 导致的 Touch ID 和 蜂窝数据网络不能在 iPhone 6 机型上工作的问题，同时还修复了其他一系列 Bug。主要更新内容如下：&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;修正了 iOS 8.0.1 在 iPhone 6 和 iPhone 6 Plus 上蜂窝数据和 Touch ID 不工作的问题；&lt;/li&gt;
  &lt;li&gt;修正了采用 HealthKit 的应用在 App Store 中不能正常下载的问题；&lt;/li&gt;
  &lt;li&gt;解决了第三方键盘在用户输入密码时的问题；&lt;/li&gt;
  &lt;li&gt;修正了一个可能导致部分应用不能从照片库中访问照片的问题；&lt;/li&gt;
  &lt;li&gt;改进了 iPhone 6 和 iPhone 6 Plus 上 Reachability 功能的可靠性；&lt;/li&gt;
  &lt;li&gt;修正了一个在接收短信/彩信时会产生不正常的蜂窝数据流量问题；&lt;/li&gt;
  &lt;li&gt;更好的支持家庭共享中对应用内购买的”Ask To Buy”功能；&lt;/li&gt;
  &lt;li&gt;修正有时候铃声无法正常从 iCloud 云备份回复的问题；&lt;/li&gt;
  &lt;li&gt;修正一个会导致从 Safari 中上传照片或视频时不能正常工作的问题。&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;已经更新到 iOS 8.0 或 iOS 8.0.1 的果迷，可直接在设备上点击”设置——通用——软件更新”升级到 iOS 8.0.2。还在使用 iOS 7 的果迷，建议通过  &lt;a href="http://www.guomii.com/posts/38852" title="&amp;#21319;&amp;#32423; iOS 8 &amp;#31354;&amp;#38388;&amp;#19981;&amp;#22815;&amp;#65311;&amp;#29992; iTunes &amp;#21319;&amp;#21543;"&gt;我们之前的教程&lt;/a&gt;升级。&lt;/p&gt;
&lt;div&gt; &lt;a href="https://itindex.net/"  title="IT 资讯"&gt;&lt;img src="https://itindex.net/images/iconWarning.gif" title="IT 资讯" border="0"/&gt; &lt;/a&gt;</description>
      <category>分享 iOS 8.0.2</category>
      <guid isPermaLink="true">https://itindex.net/detail/51218-apple-ios-%E6%9B%B4%E6%96%B0</guid>
      <pubDate>Fri, 26 Sep 2014 10:04:18 CST</pubDate>
    </item>
    <item>
      <title>Apple：明年起所有 Apps 都必须支持 64-bit</title>
      <link>https://itindex.net/detail/51473-apple-apps-bit</link>
      <description>&lt;p&gt;今天，Apple 在开发者中心发布了一条通知，表示从2015年2月1日起，所有上传到 App Store 的应用都必须支持 64-bit 运算。具体通知内容如下，各开发者请知悉。&lt;/p&gt;
 &lt;blockquote&gt;  &lt;p&gt;从 2015 年 2 月 1 日起，所有上传到 App Store 的新 iOS  Apps 都必须包含 64-bit 支持，且必须通过 Xcode 6 或更新版本中的 iOS 8 SDK 进行编译。为了在你的项目中开启 64-bit 支持，我们推荐你使用 Xcode 默认编译设置中的” Standard architectures”来编译既支持 32-bit 和 64-bit 都支持的二进制代码。&lt;/p&gt;
&lt;/blockquote&gt;
 &lt;p&gt;从这条新闻来看， Apple 未来将全面转向 64-bit 计算架构，并逐步抛弃 32-bit。&lt;/p&gt;
&lt;div&gt; &lt;a href="https://itindex.net/"  title="IT 资讯"&gt;&lt;img src="https://itindex.net/images/iconWarning.gif" title="IT 资讯" border="0"/&gt; &lt;/a&gt;</description>
      <category>新闻 64Bit iOS 8</category>
      <guid isPermaLink="true">https://itindex.net/detail/51473-apple-apps-bit</guid>
      <pubDate>Tue, 21 Oct 2014 13:31:21 CST</pubDate>
    </item>
    <item>
      <title>【苹果发布会】外媒评论：Apple Watch 看不透，于行业有益</title>
      <link>https://itindex.net/detail/51011-%E8%8B%B9%E6%9E%9C-apple-watch</link>
      <description>&lt;p&gt;  &lt;a href="http://www.ifanr.com/451047/apple_iwatch_f_00" rel="attachment wp-att-451048"&gt;   &lt;img alt="apple_iwatch_f_00" height="375" src="http://cdn.ifanr.cn/wp-content/uploads/2014/09/apple_iwatch_f_00.jpg" width="600"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;
 &lt;p&gt;一年一度的科技狂欢夜落幕，对于苹果，我们总是期待它能带来惊喜，这次发布了早已泄露无遗的 iPhone 6 以及呼声更高的 Apple Watch，我们来看看外媒是怎么评价苹果本次的新品。&lt;/p&gt;
 &lt;p&gt;基本上，大部分的注意力都放在 Apple Watch 上，因为 iPhone 在经过了长达半年的精准泄露之后，已经没有什么好谈的了。当然还是要提及一下 iPhone 的领先之处，比如生态、用户忠诚度。  &lt;a href="http://online.wsj.com/news/technology?refresh=on"&gt;华尔街日报&lt;/a&gt;认为 iPhone 6 仍然是一款突破性产品，继续引领行业，其壁垒在于出色的体验很难被复制，用户忠诚度极高，尽管 Android 已经有 85% 的份额。&lt;/p&gt;
 &lt;p&gt;  &lt;a href="http://www.bloomberg.com/technology/"&gt;彭博社&lt;/a&gt;则认为，苹果仍然将希望押注在 iPhone 身上，寄希望 iPhone 的成功来提携其他产品，因为 iPhone 是刚需，而手表、支付系统、健康软件仍然充满不确定性。&lt;/p&gt;
 &lt;p&gt;来看看重头戏 Apple Watch，从社交媒体的反馈来看，Apple Watch 并没有收到太多赞誉，大家本来期待苹果能重新定义智能手表，结果发现 Apple Watch 竟然和三星 Gear 手表十分相似。  &lt;a href="http://www.reuters.com/article/2014/09/09/us-apple-launch-idUSKBN0H41XG20140909"&gt;路透社&lt;/a&gt;的观点是“好坏参半，看不透”，鉴于 Jonathan Ive 之前  &lt;a href="http://www.ifanr.com/449553"&gt;警示&lt;/a&gt;过瑞士手表，路透社援引了瑞士手表公司（算是苹果同行了吧）分析师 Jon Cox 的观点，认为它“让人摸不着头脑，特别是只能和 iPhone 配对，没有击中痛点，噱头意义更大。”Swatch 总裁 Nick Hayek 可以睡个安稳觉了。&lt;/p&gt;
 &lt;p&gt;看不透或者轻视，这事在 iPhone 刚发布时很多人做过，不知道这次会不会被打脸。&lt;/p&gt;
 &lt;p&gt;  &lt;a href="http://www.forbes.com/sites/ewanspence/2014/09/09/how-the-apple-watch-legitimizes-and-enhances-the-entire-smartwatch-scene/"&gt;福布斯&lt;/a&gt;则对 Apple Watch 持乐观态度，它的乐观不是对苹果而言，而是整个行业，因为 Apple Watch 的发布“使得大众对智能手表以及可穿戴的认识’合法化’，智能手表不再是一群极客的玩具，而是面向大众消费者。”&lt;/p&gt;
 &lt;p&gt;更重要的是，苹果可带动上游制造业提升容量，降低材料成本，其他厂商可基于个人喜好、价格、平台提供更多选择。&lt;/p&gt;
 &lt;p&gt;而 Pebble 更有理由欢迎 Apple Watch，因为 Pebble 售价才 149 美元，在应用数量、口碑上都不错，而在 Apple Watch 漫长的等待期中，还要度过圣诞购物季，很多用户可能会购买一款低价产品来尝鲜。&lt;/p&gt;
 &lt;p&gt;福布斯认为，这是一个巨大的潜在市场，苹果瞄准了高价，每一个参与智能手表行业的人都将受益。&lt;/p&gt;
 &lt;p&gt;题图来自   &lt;a href="http://www.forbes.com/sites/ewanspence/2014/09/09/how-the-apple-watch-legitimizes-and-enhances-the-entire-smartwatch-scene/"&gt;福布斯&lt;/a&gt;&lt;/p&gt;
   &lt;div&gt;
      &lt;div&gt;    &lt;a href="http://www.ifanr.com/author/wangchaowen" target="_blank"&gt;    &lt;img height="50" src="http://cdn.ifanr.cn/wp-content/uploads/2014/06/IMG_05621.jpg" width="50"&gt;&lt;/img&gt;&lt;/a&gt;
         &lt;div&gt;
            &lt;div&gt;
               &lt;div&gt;      &lt;strong&gt;       &lt;a href="http://www.ifanr.com/author/wangchaowen" target="_blank"&gt;王超文&lt;/a&gt;&lt;/strong&gt;&lt;/div&gt;
               &lt;div&gt;关注科技，热血而沉着，极致而纯粹。努力做一个理想主义者。&lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
      &lt;div&gt;
         &lt;div&gt;
                      
              
                                          &lt;a href="mailto:455932600@qq.com" target="_blank"&gt;邮箱&lt;/a&gt;

              
              &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;
 &lt;p&gt;#欢迎关注爱范儿认证微信公众号：AppSolution（微信号：appsolution），发现新酷精华应用。&lt;/p&gt; &lt;img border="0" src="http://ifanr.feedsportal.com/c/33866/f/642084/s/451047/mf.gif"&gt;&lt;/img&gt; &lt;br /&gt; &lt;br /&gt; &lt;a href="http://da.feedsportal.com/r/144540365956/u/362/f/642084/c/33866/s/451047/a2.htm"&gt;  &lt;img border="0" src="http://da.feedsportal.com/r/144540365956/u/362/f/642084/c/33866/s/451047/a2.img"&gt;&lt;/img&gt;&lt;/a&gt; &lt;img border="0" height="1" src="http://pi.feedsportal.com/r/144540365956/u/362/f/642084/c/33866/s/451047/a2t.img" width="1"&gt;&lt;/img&gt; &lt;a href="http://www.ifanr.com/special/iphone6live"&gt;
                      &lt;img src="http://cdn.ifanr.cn/wp-content/uploads/2014/09/appleiPhone6-01.png"&gt;&lt;/img&gt;
                &lt;/a&gt; &lt;p&gt;
  &lt;a href="http://www.ifanr.com"&gt;爱范儿 · Beats of Bits&lt;/a&gt; |
  &lt;a href="http://www.ifanr.com/451047"&gt;原文链接&lt;/a&gt; ·
  &lt;a href="http://www.ifanr.com/451047#comments"&gt;查看评论&lt;/a&gt; ·
  &lt;a href="http://www.weibo.com/ifanr"&gt;新浪微博&lt;/a&gt; ·
  &lt;a href="http://www.ifanr.com/weixin"&gt;微信订阅&lt;/a&gt; ·
  &lt;a href="http://bbs.ifanr.com/"&gt;加入爱范社区！&lt;/a&gt; 
&lt;/p&gt;

 &lt;br /&gt;
 &lt;div&gt;
&lt;/div&gt; &lt;div&gt; &lt;a href="https://itindex.net/"  title="IT 资讯"&gt;&lt;img src="https://itindex.net/images/iconWarning.gif" title="IT 资讯" border="0"/&gt; &lt;/a&gt;</description>
      <category>观点 Apple Watch Apple&amp;iOS iPhone 6 iPhone 6 Plus</category>
      <guid isPermaLink="true">https://itindex.net/detail/51011-%E8%8B%B9%E6%9E%9C-apple-watch</guid>
      <pubDate>Wed, 10 Sep 2014 08:06:28 CST</pubDate>
    </item>
    <item>
      <title>大屏iPhone的适配</title>
      <link>https://itindex.net/detail/52069-iphone</link>
      <description>&lt;p&gt;自从苹果出了大屏iPhone后，iOS开发也要做适配了，想必Android程序员正在偷着乐呢;) 这里大概总结下这几天了解到的大屏适配的注意事项。&lt;/p&gt;
 &lt;p&gt; &lt;/p&gt;
 &lt;h3&gt;启用高分辨率模式&lt;/h3&gt;
 &lt;p&gt;从Xcode6 GM版本开始，模拟器新增了iPhone6和iPhone6 Plus两种，如果旧的工程直接跑到这两个模拟器中时，默认是&amp;quot;兼容模式&amp;quot;，即系统会简单的把内容等比例放大，显示效果有些模糊但尚可接受。此时App内部获取到的设备分辨率和iPhone5是一样的：320*568 point。&lt;/p&gt;
 &lt;p&gt;启用高分辨率模式有2个方法(目前我能找到的)：&lt;/p&gt;
 &lt;p&gt;1.添加大屏的LaunchImage:  &lt;br /&gt;
在Images.xcassets里，删除旧的LaunchImage组，然后新建LaunchImage组，添加对应高分辨率的图片。对此，这里有一篇更详细的图文介绍：  &lt;a href="http://matthewpalmer.net/blog/2014/09/10/iphone-6-plus-launch-image-adaptive-mode/" target="_blank"&gt;How to Add a Launch Image for the iPhone 6&lt;/a&gt;。如果想要快速测试一下新的效果，  &lt;a href="http://blog.ibireme.com/wp-content/uploads/2014/09/iPhone6LaunchBlanks.zip" target="_blank"&gt;这里&lt;/a&gt;有3张示例图片下载。&lt;/p&gt;
 &lt;p&gt;2.添加Launch Screen File&lt;/p&gt;
 &lt;p&gt;Launch Screen是Xcode6和iOS8新加的功能，它用一个xib文件来作为启动画面。App在旧版iOS启动时，该属性会被自动忽略，不会造成异常。  &lt;br /&gt;
首先，点击New File -&amp;gt;iOS User Interface -&amp;gt;Launch Screen，然后在工程设置项里启用它：&lt;/p&gt;
 &lt;p&gt;  &lt;a href="http://blog.ibireme.com/wp-content/uploads/2014/09/LaunchFile.png"&gt;   &lt;img alt="LaunchFile" height="128" src="http://blog.ibireme.com/wp-content/uploads/2014/09/LaunchFile.png" width="539"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;
 &lt;p&gt; &lt;/p&gt;
 &lt;p&gt;上面两处设置，只要启用任意一个即可让App进入高分辨率模式；但如果两处都没有设置，则App会回退到兼容模式。鉴于现在不少App还需要兼容iOS5，而第一种方法在iOS5上可能有  &lt;a href="http://stackoverflow.com/questions/19220082/support-of-ios-5-0-icons-with-xcode-5" target="_blank"&gt;bug&lt;/a&gt;，所以这里推荐用第二种方法。&lt;/p&gt;
 &lt;p&gt; &lt;/p&gt;
 &lt;p&gt; &lt;/p&gt;
 &lt;h3&gt;资源的显示 &lt;/h3&gt;
 &lt;p&gt;一图胜千言，首先这里是一个完整的图表：    &lt;a href="http://www.paintcodeapp.com/news/iphone-6-screens-demystified" target="_blank"&gt;http://www.paintcodeapp.com/news/iphone-6-screens-demystified&lt;/a&gt;。&lt;/p&gt;
 &lt;p&gt; &lt;/p&gt;
 &lt;p&gt;简单的说：iPhone4、iPhone5、iPhone6这几个设备的ppi都是相同的，默认图片优先是@2x。iPhone6 Plus的像素密度更高，默认图片优先是@3x。&lt;/p&gt;
 &lt;p&gt;另外，iPhone6 Plus有一点和其他设备不同：在App内部获得的屏幕分辨率是1242*2208，但设备实际分辨率是1920*1080，这时系统会把整体的显示内容做一个缩放，downscale到1/1.15。这个特性在OSX上也有出现过：&lt;/p&gt;
 &lt;p&gt;  &lt;a href="http://blog.ibireme.com/wp-content/uploads/2014/09/DownScale.png"&gt;   &lt;img alt="DownScale" height="155" src="http://blog.ibireme.com/wp-content/uploads/2014/09/DownScale.png" width="345"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;
 &lt;p&gt; &lt;/p&gt;
 &lt;p&gt; &lt;/p&gt;
 &lt;p&gt;下面就是一些可能有用的数据：&lt;/p&gt;

 &lt;table&gt;

  &lt;tr&gt;
   &lt;th&gt;&lt;/th&gt;
   &lt;th&gt;iPhone&lt;/th&gt;
   &lt;th&gt;iPhone4&lt;/th&gt;
   &lt;th&gt;iPhone5&lt;/th&gt;
   &lt;th&gt;iPhone6&lt;/th&gt;
   &lt;th&gt;iPhone6+&lt;/th&gt;
&lt;/tr&gt;
  &lt;tr&gt;
   &lt;th&gt;Point&lt;/th&gt;
   &lt;th&gt;320*480&lt;/th&gt;
   &lt;th&gt;320*480&lt;/th&gt;
   &lt;th&gt;320*568&lt;/th&gt;
   &lt;th&gt;375*667&lt;/th&gt;
   &lt;th&gt;414*736&lt;/th&gt;
&lt;/tr&gt;
  &lt;tr&gt;
   &lt;th&gt;Pixel&lt;/th&gt;
   &lt;th&gt;320*480&lt;/th&gt;
   &lt;th&gt;640*960&lt;/th&gt;
   &lt;th&gt;640*1136&lt;/th&gt;
   &lt;th&gt;750*1334&lt;/th&gt;
   &lt;th&gt;1242*2208&lt;/th&gt;
&lt;/tr&gt;
  &lt;tr&gt;
   &lt;th&gt;Pexel(设备)&lt;/th&gt;
   &lt;th&gt; ～&lt;/th&gt;
   &lt;th&gt;~&lt;/th&gt;
   &lt;th&gt;~&lt;/th&gt;
   &lt;th&gt;~&lt;/th&gt;
   &lt;th&gt;1920*1080&lt;/th&gt;
&lt;/tr&gt;
  &lt;tr&gt;
   &lt;th&gt;Scale&lt;/th&gt;
   &lt;th&gt;1&lt;/th&gt;
   &lt;th&gt;2&lt;/th&gt;
   &lt;th&gt;2&lt;/th&gt;
   &lt;th&gt;2&lt;/th&gt;
   &lt;th&gt;3&lt;/th&gt;
&lt;/tr&gt;
  &lt;tr&gt;
   &lt;th&gt;PPI&lt;/th&gt;
   &lt;th&gt;163&lt;/th&gt;
   &lt;th&gt;326&lt;/th&gt;
   &lt;th&gt;326&lt;/th&gt;
   &lt;th&gt;326&lt;/th&gt;
   &lt;th&gt;401&lt;/th&gt;
&lt;/tr&gt;

&lt;/table&gt;
 &lt;p&gt; &lt;/p&gt;
&lt;div&gt; &lt;a href="https://itindex.net/"  title="IT 资讯"&gt;&lt;img src="https://itindex.net/images/iconWarning.gif" title="IT 资讯" border="0"/&gt; &lt;/a&gt;</description>
      <category>iOS 技术</category>
      <guid isPermaLink="true">https://itindex.net/detail/52069-iphone</guid>
      <pubDate>Tue, 16 Sep 2014 01:04:31 CST</pubDate>
    </item>
    <item>
      <title>iOS App开发那些事</title>
      <link>https://itindex.net/detail/52007-ios-app-%E5%BC%80%E5%8F%91</link>
      <description>&lt;p&gt;自从做Team Leader之后，身上权责发生了变化，于是让我烦恼的不再是具体某个功能，某个界面的实现，而是如何在现有代码的基础上做渐进式的改进，创造出比较合适规范和框架，使得组内成员更快更好地完成任务。一年下来，颇有点想法，于是啰嗦几句关于iOS App开发的那些事。&lt;/p&gt;
 &lt;h2&gt;合适的人&lt;/h2&gt;
 &lt;p&gt;首先明确一点，合适的人是指纯技术团队的建设。一支战斗力再强的技术团队，面对一个朝三暮四，分分钟推翻自己原有想法的产品经理/项目经理，再好的戏也唱不出来。花几个月加班加点做项目，还没发布，直接推翻重做，这时候你就得去楼下ATM机看看卡内余额了：余额够了，收拾收拾好找下一家了。&lt;/p&gt;
 &lt;p&gt;计算机界有句名言：计算机相关的所有问题都可以通过增加一个额外的抽象层来解决。但是软件开发却不是这样：增加层(人手)在一定程度上可以加快开发进度，当过了某个阈值后其效果就显得不是那么明显，甚至会起反效果。对于一个项目而言需要的往往不是更多的成员，而是适量的合适成员。每一个人因为不同的教育背景，从业背景，项目经历(技术选型，学习经历，项目管理)对程序开发都会有不同的理解和思维模式。反应在业务上就是各种各样的代码风格。举例来说：有些人恨不得把所有单一功能都一一独立出来封装成类，而有些人却喜欢一个大类洋洋洒洒写上上千行。大部分情况下我都是倾向于前者，但是就像我时常吐槽的那样：It depends。不仅仅是软件开发，几乎所有的事到最终都会归结到一个统一的问题上：怎样才是一个度？一群理念相去甚远的人在一起工作是件异常痛苦的事：相当一部分的时间会浪费在解释，争论和排遣由此带来的沮丧和愤怒上。古人语：道不同，不相为谋。但到了真正的工作中却不能如此随性，缺乏足够动力的老人，能力出众的技术骨干，干劲十足却缺乏经验的新人都需要互相体谅，学习和磨合。所以大部分的创业团队的技术团队因为理念相近，往往效率会足够高，而大公司内的开发小组却永远无法达到那样的效率，更需要相应的规范和程序框架。&lt;/p&gt;
 &lt;p&gt;得出上面这个结论的另一个理由是我对人的可塑造性是持悲观态度的：多数人并没有跳出自己思维局限性的意愿，动力和能力。少数人在没有任何外界压力的情况下仍会不断总结学习进步(主动学习型)，而其余的人要么没有任何意愿，关心的只是完成任务和拿到工资而已，要么想要进步而不得法。而你的团队不可能全由主动学习型的成员组成，这时候规范和程序框架的引入才能够让各种类型的人更好的合作。&lt;/p&gt;
 &lt;h2&gt;合适的规范&lt;/h2&gt;
 &lt;p&gt;大家都理解软件开发需要合适的规范：代码规范，程序规范，流程规范等等，以此来减少意外的出现：最少惊讶原则。但在实际执行中却会碰到各种情况，其中最大的问题是：怎么鉴别哪些规范是需要强制执行，那些规范是推荐执行。规范的强制执行带来的是代码的可读性提升和二义性减少，而坏处也是显而易见的：对于大部分有想法的程序员而言这种规定太死板，容易引起抵触心理，产生不安定因素。这种情况常见于各种标准的外包公司。而如果大部分的规范设定为推荐执行，在没有良好的引导下，规范容易被忽视。 网上有很多关于ObjC的代码规范，比如苹果自家的规范和《Google Objective-C Style Guide》等。这些规范一般只有两种分级:推荐和不推荐。而我更推荐把代码规范分成五个等级：强制要求，强烈推荐(但不强制)，良好，可接受和不可接受。以下仅举部分例子加以说明。&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;
   &lt;p&gt;符合苹果规范的命名方式。&lt;/p&gt;
&lt;/li&gt;
  &lt;ul&gt;
   &lt;li&gt;
    &lt;p&gt;类名开头大写，方法和变量名以驼峰法命名。强烈要求，这没有什么好说的，苹果系统类库和绝大多数的第三方开源库都是如此。但在部分苹果的sample中也看到过用m做前缀表示类成员变量的写法，这些都是属于遗产代码的问题，仍旧是可接受范围，但是自己代码内部使用类似匈牙利的命名法就是不可接受。&lt;/p&gt;
&lt;/li&gt;
   &lt;li&gt;
    &lt;p&gt;类名使用至少三个字符做前缀，内部方法使用两个下划线做前缀。强烈推荐。上面的做法可以最大程度避免和系统类库发生重名的情况：因为苹果宣称保留所有两位字符前缀的使用权，同时其内部方法命名以一个下划线做前缀。&lt;/p&gt;
&lt;/li&gt;
   &lt;li&gt;
    &lt;p&gt;无论使用K&amp;amp;R Style还是Allman Style都是可接受的范围，但是强烈推荐在一个文件内保持一种形式。&lt;/p&gt;
&lt;/li&gt;
   &lt;li&gt;
    &lt;p&gt;在保证代码可读性的基础上保持代码的简短和统一性：小类，小方法，统一的函数返回。小类，小方法可以保证他人阅读时更方便地关注类逻辑，而不是具体细节，而统一的函数返回可以减少意外错误和降低错误排查的难度。而保证代码的简短和不罗嗦也是很重要一点，经常会看到如下代码： &amp;gt; (count &amp;gt; 1) { return YES; } { return NO; }&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/ul&gt;
 &lt;p&gt;真心无法直视。&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;
   &lt;p&gt;良好的代码/工程结构&lt;/p&gt;
&lt;/li&gt;
  &lt;ul&gt;
   &lt;li&gt;
    &lt;p&gt;为整个工程创建worksapce。&lt;/p&gt;
&lt;/li&gt;
   &lt;li&gt;
    &lt;p&gt;按照权责分门别类存放资源文件：每种类型的资源存放于独立的目录下：图片，声音，配置文件等等。而图片又可以按照类型分别存放在不同的子目录下：全局类型，背景图，logo，登录等等。&lt;/p&gt;
&lt;/li&gt;
   &lt;li&gt;
    &lt;p&gt;合理的代码结构。推荐如下的工程目录结构&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/ul&gt;
 &lt;p&gt;  &lt;img alt="iOS App&amp;#24320;&amp;#21457;&amp;#37027;&amp;#20123;&amp;#20107; - &amp;#31532;1&amp;#24352;  | IT&amp;#27743;&amp;#28246;" src="http://xiangwangfeng.com/images/ios_arch.jpg" title="iOS App&amp;#24320;&amp;#21457;&amp;#37027;&amp;#20123;&amp;#20107; - &amp;#31532;1&amp;#24352;  | IT&amp;#27743;&amp;#28246;"&gt;&lt;/img&gt;Core：工程内一些通用的机制实现类：统一的任务管理，模块管理，服务管理。&lt;/p&gt;
 &lt;p&gt;General：公用类和方法，包括工程内ViewController,UITableViewCell基类(Base)，公用Category(Category)，公用UI组件(CustomUI)，公用辅助方法(Helper)和宏定义(Marco)。&lt;/p&gt;
 &lt;p&gt;Model：公用数据模型&lt;/p&gt;
 &lt;p&gt;Sections：不同程序单元。如登录，设置等等。其下又可以按照功能分成不同的子目录：当前单元使用的自定义UI组件，管理类，数据模型和ViewController等等。&lt;/p&gt;
 &lt;p&gt;Vendors：第三方库。&lt;/p&gt;
 &lt;h2&gt;合适的框架&lt;/h2&gt;
 &lt;p&gt;一个合适的框架不是银弹，在我看来框架要解决的问题从来不是：有了框架之后，工程就能无比正确地进行下去。好的框架能够做到的事仅仅只是：降低通用问题的复杂度和减少发生错误的可能性。个人认为一个良好iOS App框架应该是有如下特点：&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;
   &lt;p&gt;定义清晰的层次结构。&lt;/p&gt;
&lt;/li&gt;
  &lt;ul&gt;
   &lt;li&gt;
    &lt;p&gt;展现层(Presentation layer)，负责管理UI和UIViewController&lt;/p&gt;
&lt;/li&gt;
   &lt;li&gt;
    &lt;p&gt;逻辑层(Business/Service Layer)，负责逻辑数据的定义和转发，起到承上启下的作用。&lt;/p&gt;
&lt;/li&gt;
   &lt;li&gt;
    &lt;p&gt;数据访问层(Data Access Layer)，负责具体API构造，网络请求，数据持久化等。&lt;/p&gt;
&lt;/li&gt;
   &lt;li&gt;
    &lt;p&gt;各层根据业务逻辑的复杂性内部又会使用单层或者多层结构。以数据访问层为例，一般又可以细分为网络层，持久化层。而一般而言，展现层(UIView和UIViewController)都是直接使用逻辑层提供的Model进行展现，但是某些场景下往往需要不同的Model有相同的界面展示(如我们的App易信中，会话界面，收藏界面，问一问功能都需要进行图片的展现，但这三个模块下的Model定义并不一致)，这就需要增加额外的ViewModel层用于粘合展现层和逻辑Model。&lt;/p&gt;
&lt;/li&gt;
   &lt;li&gt;
    &lt;p&gt;横向上，各模块互相独立，仅通过有限的几个接口进行通讯。最理想的状态是除核心模块外，其他模块都是可拔插。纵向上，各层次间依赖关系清晰，基本不出现逆向依赖的情况。&lt;/p&gt;
&lt;/li&gt;
   &lt;li&gt;
    &lt;p&gt;横向模块一般依赖于业务需求，常被定义成各种Service或Manager。一种好的做法是有个统一的Service管理器负责相应Serivce的加载，卸载，监听和分发App级别的通知给相应Service，如前后台切换，收到内存警告等。这样做一方面容易实现上面说的模块的可插拔化，另一方面也保证了公用特性处理的一致性。在这方面微信就做得不错，基本所有的模块都是从MMService继承而来，由MMServiceCenter进行管理。当然从dump出来的头文件也可以发现一些管理上的紊乱，比如一些ViewController都是继承自MMService。&lt;/p&gt;
&lt;/li&gt;
   &lt;li&gt;
    &lt;p&gt;纵向的层次划分基本各个App不会有太大区别，一般可以分为三个层次：&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
  &lt;li&gt;
   &lt;p&gt;遵守SOLID原则和慎用各种设计模式。这是个老生常谈的话题了，并不是iOS开发独有，展开讲可以讲上几天几夜，不赘述。&lt;/p&gt;
&lt;/li&gt;
  &lt;li&gt;
   &lt;p&gt;定义自己的UI基类：UIView，UIViewController，UITableviewCell。这一点的好处不言而喻，所有的子View，Controller，Cell都能够很方便的继承基类的共有的行为，样式。但也会引进很大的管理风险：组内成员总会经不起诱惑往基类塞各种并不普适的特性，引起基类权责的无限膨胀。大基类不仅增加组内成员对代码的理解难度，同时也增加出现问题时的排查难度。从这方面讲，微信的UIViewController基类设计就极端失败：MMUIViewController。&lt;/p&gt;
&lt;/li&gt;
  &lt;li&gt;
   &lt;p&gt;提供方便好用的工具类。一些好用的工具类往往会成为框架重要的有机组成部分，方便快捷地解决局部问题，同时又不引入过多的复杂度。NSTimer的retain cycle是个很容易掉去的坑，那么提供一个基于Block或者weak delegate的NSTimer的封装就是不错的选择。使用KVO容易发生add和remove的不配对调用，那么就引入THObserversAndBinders或者FB的KVOContorller。某些核心模块需要被多个模块依赖时，引入类似XMPP的GCDMulticastDelegate就能够方便地进行解耦。&lt;/p&gt;
&lt;/li&gt;
  &lt;li&gt;
   &lt;p&gt;好的范例。在前几年使用C++的那段暗无天日的日子里，我常想的一个问题是：如何在API层面去限制和规避一些错误。比如往线程池里面扔的task必须是堆上分配的对象，那么如何去强制传入的指针指向的是堆地址而不是栈地址呢？这种傻问题大部分情况下是无解的，有时候有解却是个异常别扭的解。而现在我更相信破窗理论所提供的可能性：做好示范，接下来的一切都会水到渠成。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;  &lt;br /&gt;&lt;/p&gt;
&lt;div&gt; &lt;a href="https://itindex.net/"  title="IT 资讯"&gt;&lt;img src="https://itindex.net/images/iconWarning.gif" title="IT 资讯" border="0"/&gt; &lt;/a&gt;</description>
      <category>ios</category>
      <guid isPermaLink="true">https://itindex.net/detail/52007-ios-app-%E5%BC%80%E5%8F%91</guid>
      <pubDate>Mon, 01 Dec 2014 11:56:36 CST</pubDate>
    </item>
    <item>
      <title>DHH 谈混合移动应用开发</title>
      <link>https://itindex.net/detail/52190-dhh-%E6%B7%B7%E5%90%88-%E7%A7%BB%E5%8A%A8%E5%BA%94%E7%94%A8</link>
      <description>&lt;p&gt; &lt;/p&gt;
 &lt;p&gt;  &lt;img alt="1053-DHH" height="80" src="http://coolshell.cn//wp-content/uploads/2014/12/1053-DHH-150x150.jpg" width="80"&gt;&lt;/img&gt;David，Ruby on Rails 作者，37signals 合伙人&lt;/p&gt;
 &lt;p&gt;畅销书作家、演说家、赛车手、业余摄影师、顾家好男人&lt;/p&gt;
 &lt;p&gt; &lt;/p&gt;
 &lt;p&gt;  &lt;a href="http://37signals.com/" target="_blank"&gt;37signals&lt;/a&gt; 在2013年2月发布了 Basecamp 的 iPhone app，在此之前我们就使用原生开发（native）还是混合开发（hybrid）做了许多尝试。在2012年项目启动的时候，大多数人都倾向于原生开发。&lt;/p&gt;
 &lt;p&gt;Facebook 在2012年发布了他们新的 iOS app，为了获得更好的用户体验，他们放弃了原来的 HTML5 混合开发方式。考虑到2010～2011年的时候，HTML 在移动端的性能确实不尽如人意，这个决定在当时看来也在情理之中。2010年的时候我们觉得 iPhone 3G/3GS 够眩够快，但按照现在的标准来看它们就太慢了。因此在为移动应用开发做架构设计时，我们需要考虑新的移动设备的计算能力，而不是那些老的过时的设备。&lt;/p&gt;
 &lt;h4&gt;移动开发架构设计不需要过多考虑设备的性能&lt;/h4&gt;
 &lt;p&gt;我们从一些测试中得出的一个结论是：现在的移动设备计算能力都很强，运行原生应用和 HTML 应用的效果差别不大，而 HTML 开发的成本则要比原生开发小得多。&lt;/p&gt;
 &lt;p&gt;当然这个结论在某些领域并不太适用。如果你要开发一个 3D 游戏，原生开发方式能够带来更好的游戏体验。但如果你的移动应用象 Basecamp 一样侧重信息处理，为了降低开发成本，你就可以考虑混合开发方式。我们就是如此，下面是我们三代移动产品的发展轨迹：&lt;/p&gt;
 &lt;p&gt;&lt;/p&gt;
 &lt;h4&gt;第一代产品：原生外壳(native shell)＋嵌套WebView&lt;/h4&gt;
 &lt;p&gt;  &lt;img alt="1159-basecamp-app-phones" height="242" src="http://coolshell.cn//wp-content/uploads/2014/12/1159-basecamp-app-phones-300x242.jpg" width="300"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;这个版本就是一个简单的原生外壳负责界面导航，嵌套一个 WebView 来显示 Basecamp Rail application，显示的基本上都是我们移动网站页面，再加上一些特殊的样式。&lt;/p&gt;
 &lt;p&gt;在移动网站的页面上嵌套一个原生的壳，听起来还是 Web 页面，但实际带给用户的体验确是非常不同。用户可以在 Apple App Store 找到我们的 app，他们一旦登录 app 后可以再也不用重新登录（移动版本的 Safari 似乎会经常清空 cookie，让你不得不重新登录）。我们的 app 大受欢迎，用户评分在4和5之间。&lt;/p&gt;
 &lt;p&gt;整个 app 由一名程序员和一名设计师开发，成本不高，因为我们可以在已有的移动网站的基础上开发。&lt;/p&gt;
 &lt;p&gt;如果我们当初开发完全原生的 app，用10个人的团队1年半的时间也未必能完成。&lt;/p&gt;
 &lt;p&gt; &lt;/p&gt;
 &lt;h4&gt;第二代产品：原生外壳＋原生导航界面&lt;/h4&gt;
 &lt;p&gt;  &lt;img alt="1543-unnamed" height="300" src="http://coolshell.cn//wp-content/uploads/2014/12/1543-unnamed-187x300.png" width="187"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;几个月前发布的 Basecamp Android app 是我们的第二代产品，我们在其中做了大量的改进。&lt;/p&gt;
 &lt;p&gt;从第一代 iPhone app 中我们感受到了原生导航界面的威力，所以在 Android 版本中，我们由 HTML 页面导航转向了原生导航界面。我们从 HTML 页面生成原生导航界面，用户体验更加流畅，原生界面和 HTML 页面的体验差别越来越小，甚至很难区分哪些是原生部分，哪些是 HTML 。&lt;/p&gt;
 &lt;p&gt;Android 版本是由一两个程序员和一个设计师开发（50%投入）完成的。我们重用了移动站点和 iPhone app 中使用的所有 webview，大大提高了开发效率，同时用户也很买账，超过1000名用户打了4.5~5的高分。&lt;/p&gt;
 &lt;p&gt;很多公司在抱怨他们的 iOS 移动项目进展缓慢，Android 项目似乎更是如此。或许他们已经习惯了 iOS 项目的开发流程，也许是因为 Android 的屏幕碎片化问题，但是这些对我们来说那都不是事。我们推出的 Android app 表现良好，重用了95%的代码，开发团队也一直保持在小规模。&lt;/p&gt;
 &lt;p&gt; &lt;/p&gt;
 &lt;h4&gt;因地制宜地运用原生开发方式&lt;/h4&gt;
 &lt;p&gt;目前我们正在开发第三代产品，发布的平台暂时保密，不过你应该也不难猜到。在前两代产品中，我们增加了原生导航界面的使用，同时进一步确定了以 webview 为核心的整体架构。在第三代产品中，我们将因地制宜地选择需要使用原生开发的功能，好钢要用在刀刃上。&lt;/p&gt;
 &lt;p&gt;从之前的100% HTML，到现在的90% HTML +10%原生，我们会选择最值得做原生开发的那10%的部分，最终目的是让 app 原生部分和 HTML 部分的体验没有太大区别。&lt;/p&gt;
 &lt;p&gt; &lt;/p&gt;
 &lt;h4&gt;混合开发模式使用的技术&lt;/h4&gt;
 &lt;p&gt;混合开发模式在技术很简单，主要是处理 webview 的集成、Web 页面的加载，以及原生内容和 HTML 内容之间的交叉链接，其实可能比你想像的还要简单得多。&lt;/p&gt;
 &lt;p&gt;HTML 方面，我们的 Rails Web 应用支持 Web 和移动两大平台，其中   &lt;a href="http://edgeguides.rubyonrails.org/4_1_release_notes.html#action-pack-variants" target="_blank"&gt;Rails 4.1 feature of variants&lt;/a&gt; 起了很大的作用。&lt;/p&gt;
 &lt;p&gt;这也很大程度上有助于我们发布新功能。设想一下如果我们每次需要更新这么多平台：Rails desktop app, a Rails API app, a client-side MVC app, a mobile web wrapper app, an Android app, and an iPhone app，像我们这样只有10个程序员和7个设计师的公司根本无力承担如此巨大的工作量。&lt;/p&gt;
 &lt;p&gt;除了工作量的减轻，bug 修复效率也提高了，因为大部分的代码逻辑是在 Web 服务器端，我们可以随时修改代码并发布，不用通过 Apple App Store 的审批流程。所以我们的移动 app 和 Web 应用一样，也是持续部署。&lt;/p&gt;
 &lt;p&gt;就如我之前提到的，混合模式开发并不适用于所有情况。在2010年以前，那时手机的处理能力都不强，所以 HTML/JS 的体验并不好，用户也不喜欢。但是时过境迁，现在手机的处理能力大大提高了，HTML/JS 的性能也不再是一个问题。&lt;/p&gt;
 &lt;p&gt; &lt;/p&gt;
 &lt;h4&gt;混合开发模式对原生开发模式的挑战&lt;/h4&gt;
 &lt;p&gt;混合开发模式在降低开发复杂度方面有它的优势，如果你的产品是以显示和处理信息为主，我认为都可以不同程度地采用这个模式。&lt;/p&gt;
 &lt;p&gt;对于小型团队和公司而言，并不一定需要采用 iOS 原生 app 先行的模式。使用混合模式，不需要你重头开发一个 app，这样可以降低维护成本，将来扩展到其他平台也更为方便。&lt;/p&gt;
 &lt;p&gt;当然我知道会有很多人质疑这个模式，或许因为他们的 app 中有很多地方需要原生开发（也许仅仅是他们自己这样认为罢了）。又或许他们已经花了很多时间让 app 里的 UITableView 看起来非常漂亮，以致如果其他地方不这样的话显得不是太完美。再或许大公司就是喜欢耗时耗力的原生开发，有钱就是这么任性。&lt;/p&gt;
 &lt;p&gt;无论怎样，混合开发当下应该能够成为我们移动开发策略的一个选择。如果你认为这是一个好的选择，那么恭喜你，尽情愉快地玩耍吧！&lt;/p&gt;
 &lt;p&gt; &lt;/p&gt;
 &lt;p&gt;  &lt;em&gt;原文链接：   &lt;a href="https://signalvnoise.com/posts/3743?utm_campaign=iOS_Dev_Weekly_Issue_175&amp;utm_medium=email&amp;utm_source=iOS%2BDev%2BWeekly" target="_blank"&gt;Hybrid sweet spot: Native navigation, web content&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;
 &lt;p&gt; &lt;/p&gt;
 &lt;p&gt;下面补充一些 David 答读者问：&lt;/p&gt;
 &lt;p&gt; &lt;/p&gt;
 &lt;p&gt;Mike Waite @ 2014-05-08：我很好奇你是如何决定哪些功能要用原生开发？  &lt;br /&gt;
David @ 2014-05-08：主要靠感觉，这毕竟不是一门科学。如果你感觉你app的某一部分如果用原生开发会更好些，可以尝试做快速原型（spike）。很多时候我们通过这种方式证明我们的想法其实是错的。当然如果你需要使用到手机上的功能如：摄像和其他设备时，HTML目前还不太适用，不过永远也不要把话说死。&lt;/p&gt;
 &lt;p&gt; &lt;/p&gt;
 &lt;p&gt;Mike Parsons @ 2014-05-08：好文。很好奇你们是否使用 PhoneGap 或者 Cordova 这样的框架，或者你们自己开发了一个？  &lt;br /&gt;
David @ 2014-05-08：我们没有使用任何框架。（此处省去xxx字）&lt;/p&gt;
 &lt;p&gt; &lt;/p&gt;
 &lt;p&gt;Derick @ 2014-05-08：你怎样解决 Android 浏览器渲染速度慢的问题？这也是 Android 平台上更多人倾向开发原生app得原因。  &lt;br /&gt;
David @ 2014-05-08：不知道你这个结论是近期的还是以前的？Basecamp 的 Android app 在我的 Nexus 5 和 HTC One 上面运行得非常流畅。  &lt;br /&gt;
Derick @ 2014-05-08：就是最近。我猜测可能和你使用JavaScript的多少有关系。因为以我个人的经验，Android 上 JavaScript 的运行速度非常慢。如果你感兴趣可以看看下面的文章：  &lt;a href="https://www.timroes.de/2013/11/23/old-webview-vs-chromium-webview/" target="_blank"&gt;https://www.timroes.de/2013/11/23/old-webview-vs-chromium-webview/&lt;/a&gt;  &lt;br /&gt;
David @ 2014-05-08：我们使用了很多JavaScript，当然没有 Web MVC 客户端用得那样多。另外我们使用了 Turbolinks ：）&lt;/p&gt;
 &lt;p&gt; 
  &lt;div&gt;
&lt;/div&gt;&lt;/p&gt; &lt;p align="center"&gt;  &lt;strong&gt;（转载本站文章请注明作者和出处    &lt;a href="http://coolshell.cn/"&gt;酷 壳 – CoolShell.cn&lt;/a&gt; ，请勿用于任何商业用途）&lt;/strong&gt;
  &lt;div&gt;——===    &lt;strong&gt;访问     &lt;a href="http://coolshell.cn/404/" target="_blank"&gt;酷壳404页面&lt;/a&gt; 寻找遗失儿童。&lt;/strong&gt; ===——&lt;/div&gt;

  &lt;div&gt;   &lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/p&gt; &lt;h3&gt;相关文章&lt;/h3&gt; &lt;ul&gt;  &lt;li&gt;   &lt;small&gt;2014年11月26日&lt;/small&gt;    &lt;a href="http://coolshell.cn/articles/12136.html"&gt;Google Inbox如何跨平台重用代码？&lt;/a&gt;&lt;/li&gt;  &lt;li&gt;   &lt;small&gt;2010年03月10日&lt;/small&gt;    &lt;a href="http://coolshell.cn/articles/2117.html"&gt;Titanium – 桌面和移动应用开发平台&lt;/a&gt;&lt;/li&gt;  &lt;li&gt;   &lt;small&gt;2010年08月26日&lt;/small&gt;    &lt;a href="http://coolshell.cn/articles/2853.html"&gt;实用Android开发工具和资源精选&lt;/a&gt;&lt;/li&gt;  &lt;li&gt;   &lt;small&gt;2011年08月02日&lt;/small&gt;    &lt;a href="http://coolshell.cn/articles/5089.html"&gt;10个必需的iOS开发工具和资源&lt;/a&gt;&lt;/li&gt;  &lt;li&gt;   &lt;small&gt;2011年04月06日&lt;/small&gt;    &lt;a href="http://coolshell.cn/articles/4220.html"&gt;一些有意思的文章和资源&lt;/a&gt;&lt;/li&gt;  &lt;li&gt;   &lt;small&gt;2010年03月25日&lt;/small&gt;    &lt;a href="http://coolshell.cn/articles/2155.html"&gt;别只谈系统备份，谈谈怎样恢复系统吧！&lt;/a&gt;&lt;/li&gt;  &lt;li&gt;   &lt;small&gt;2014年09月28日&lt;/small&gt;    &lt;a href="http://coolshell.cn/articles/11973.html"&gt;bash代码注入的安全漏洞&lt;/a&gt;&lt;/li&gt;  &lt;li&gt;   &lt;small&gt;2009年03月26日&lt;/small&gt;    &lt;a href="http://coolshell.cn/articles/247.html"&gt;基于JVM的语言正在开始流行&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt;
&lt;div&gt; &lt;a href="https://itindex.net/"  title="IT 资讯"&gt;&lt;img src="https://itindex.net/images/iconWarning.gif" title="IT 资讯" border="0"/&gt; &lt;/a&gt;</description>
      <category>技术读物 杂项资源 Android Hybrid iOS</category>
      <guid isPermaLink="true">https://itindex.net/detail/52190-dhh-%E6%B7%B7%E5%90%88-%E7%A7%BB%E5%8A%A8%E5%BA%94%E7%94%A8</guid>
      <pubDate>Mon, 15 Dec 2014 10:57:20 CST</pubDate>
    </item>
    <item>
      <title>深度调查 iPhone 真的不拼硬件吗？A8 处理器解析</title>
      <link>https://itindex.net/detail/51179-%E6%B7%B1%E5%BA%A6-iphone-%E7%A1%AC%E4%BB%B6</link>
      <description>&lt;p&gt;  &lt;a href="http://www.geekfan.net/wp-content/uploads/246c1b1b56887798f91da0d19edfce2a.jpg" rel="lightbox[12193]" title="&amp;#28145;&amp;#24230;&amp;#35843;&amp;#26597; iPhone &amp;#30495;&amp;#30340;&amp;#19981;&amp;#25340;&amp;#30828;&amp;#20214;&amp;#21527;&amp;#65311;A8 &amp;#22788;&amp;#29702;&amp;#22120;&amp;#35299;&amp;#26512;"&gt;   &lt;img alt="a73d18446f20a30be28c26ab513da8d8_w_680_h_425" height="425" src="http://www.geekfan.net/wp-content/uploads/246c1b1b56887798f91da0d19edfce2a.jpg" width="680"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;
 &lt;p&gt; &lt;/p&gt;
 &lt;h2&gt;iPhone 不堆硬件?&lt;/h2&gt;
 &lt;p&gt;一直以来都有这么一种说法：iPhone 不像 Android 手机那样碓硬件。这句话一层含义是指 Android 手机大都爱拼硬件，多为硬件怪兽，动辄四核、八核处理器，3G 内存，2000w 像素，2k 屏幕；另一层是指 iPhone 常年使用双核处理器，1G 内存，800w 像素，直到在最新的 iPhone 6 plus 上才使用了 1080p 屏幕。网上甚至有段子打趣道： iPhone 6 的硬件配置刚追上 nexus 4，欢迎 iPhone 6 来到 2012 年。那么 iPhone 的硬件真的是如此不堪吗？ iPhone 一贯优秀的用户体验真的只是依靠神优化和优秀的软件体验实现的吗？这里笔者便根据目前已有资料，简单分析一下 iPhone 6 所使用的 A8 处理器，而关于摄像头部分，煮机已有文章做过分析。&lt;/p&gt;
 &lt;p&gt;虽然 iPhone 与 Android 手机使用的是不同的操作系统，各自对硬件的调动也有所不同，底层驱动差异也不小，但是回归到处理器本身，iPhone 所采用的双核处理器和 Android 所常用的四核、八核处理器还是可以一较高下的。&lt;/p&gt;
 &lt;h2&gt;先进的 A8，更快的速度&lt;/h2&gt;
 &lt;p&gt;iPhone 6 用的是 A8 处理器，这是苹果基于 ARM 授权，使用 ARM V8 架构，自行研发的一颗芯片。苹果基于对 ARM V8 的研究与调整，得出 cyclone（飓风）架构，支持 64 位指令集。此架构首次出现是在 iPhone 5S 上，当时采用 cyclone 架构的 A7 芯片是移动端第一款 64 位处理器，虽然是双核处理器，运行频率也只有 1.3Ghz，相比较安卓机中常见的 2.0Ghz 明显低了不少，但是因为每个时钟周期最多可以同时解码、发射、执行、收回6个指令/微操作，排序缓冲大小是 A6 处理器的四倍多，而在当时 Intel Haswell 桌面架构也不过如此的排序缓冲大小也不过如此。&lt;/p&gt;
 &lt;p&gt;  &lt;a href="http://www.geekfan.net/wp-content/uploads/580300d1a0b4847e4fd80fe837d7ba6e.png" rel="lightbox[12193]" title="&amp;#28145;&amp;#24230;&amp;#35843;&amp;#26597; iPhone &amp;#30495;&amp;#30340;&amp;#19981;&amp;#25340;&amp;#30828;&amp;#20214;&amp;#21527;&amp;#65311;A8 &amp;#22788;&amp;#29702;&amp;#22120;&amp;#35299;&amp;#26512;"&gt;   &lt;img alt="2a2f98d3597419498e4d734d8c2dd106_mw_680_wm_0_wmp_3" height="364" src="http://www.geekfan.net/wp-content/uploads/580300d1a0b4847e4fd80fe837d7ba6e.png" width="567"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;
 &lt;p&gt; &lt;/p&gt;
 &lt;p&gt;到了 A8 处理器上，苹果依旧是发挥强大的芯片设计能力，第二代 cyclone 架构，同样是 64 位，缓存也还是 64k、1MB、4MB，不过在制造工艺上已经进入了 20nm 时代，这又将同期的骁龙处理器的 28nm 制程甩在身后，目前也就猎户座 5433 能够勉强追上。&lt;/p&gt;
 &lt;p&gt;虽然不如 A6 升级至 A7 那样性能翻倍，但是 A8 性能对于 A7 还是有了 25% 左右的提升，而根据真机实测，A8 在单线程上相比较于 A7 有近 18% 的提升，多线程则有 15% 的提升。在带宽上达到了 12.8GB/S,这种水平虽然还不及高通骁龙 801 这样的带宽狂魔的 14.9GB/S，但是由于 iPhone 分辨率最高只有 1080p，其所需要的带宽约为 8.3GB/S，因此 12.8GB/S 这样的带宽已经足够。从测试成绩上来看，A8 对于 A7 也是确实有着明显的升级，单线程成绩更加突出，而安卓阵营的新旗舰韩版 Note 4，其采用的猎户座 5433 处理器也是一颗 64 位处理器，但在 CPU 单线程上还处于落后位置。&lt;/p&gt;
 &lt;p&gt;  &lt;a href="http://www.geekfan.net/wp-content/uploads/84a7c34173786e767943dbae7f8ac53c.jpg" rel="lightbox[12193]" title="&amp;#28145;&amp;#24230;&amp;#35843;&amp;#26597; iPhone &amp;#30495;&amp;#30340;&amp;#19981;&amp;#25340;&amp;#30828;&amp;#20214;&amp;#21527;&amp;#65311;A8 &amp;#22788;&amp;#29702;&amp;#22120;&amp;#35299;&amp;#26512;"&gt;   &lt;img alt="2d7f3f6e23f7cb31f2209ab8cc8f47a4_mw_680_wm_0_wmp_3" height="344" src="http://www.geekfan.net/wp-content/uploads/84a7c34173786e767943dbae7f8ac53c.jpg" width="580"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;
 &lt;p&gt; &lt;/p&gt;
 &lt;p&gt;除了这些性能上的提升之外，A8芯片也是目前制造工艺最为先进的移动处理器，20nm制程，由台积电负责制造。在芯片的大小上，比A7缩小13%，但是晶体管数量却由10亿翻倍至20亿。这种制造工艺的提升带来最大的好处便是发热与功耗的降低。&lt;/p&gt;
 &lt;p&gt;  &lt;a href="http://www.geekfan.net/wp-content/uploads/8224760afa0c77fa2977b76151a22983.jpg" rel="lightbox[12193]" title="&amp;#28145;&amp;#24230;&amp;#35843;&amp;#26597; iPhone &amp;#30495;&amp;#30340;&amp;#19981;&amp;#25340;&amp;#30828;&amp;#20214;&amp;#21527;&amp;#65311;A8 &amp;#22788;&amp;#29702;&amp;#22120;&amp;#35299;&amp;#26512;"&gt;   &lt;img alt="8c86c617f818ea789cd189816f16455f_mw_680_wm_0_wmp_3" height="426" src="http://www.geekfan.net/wp-content/uploads/8224760afa0c77fa2977b76151a22983.jpg" width="639"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;
 &lt;p&gt; &lt;/p&gt;
 &lt;h2&gt;图形性能傲视群雄&lt;/h2&gt;
 &lt;p&gt;除了 CPU 性能强大之外，苹果也一贯重视 GPU 性能，作为图像处理单元，其好坏直接影响着用户操作体验。虽然 A8 没有以往那样的 GPU 性能野蛮增长一倍，但也有了 50% 的增长，要知道 A7 所使用的 GPU 是 Imagination PowerVR G6430，本身也是颗一流的 GPU，所以能有 50% 的增长，笔者相信其实力不可小觑。&lt;/p&gt;
 &lt;p&gt;由于苹果一贯对于硬件的保密，目前我们还无法得知 A8 具体使用的 GPU 是什么型号，不过根据苹果以往的风格，在 GPU 的使用上从不手软，可以说非旗舰不用。再结合苹果在 iPhone 6 发布会后更新的 iOS 开发文档，其中跟骁龙 805 上所用的 Adreno 420 相同，增加了对下一代纹理压缩格式 ASTC 的支持，这与如出一辙，而拥有这一特性正是 PowerVR Series6XT 系列。&lt;/p&gt;
 &lt;p&gt;Seiries6XT 有双核、四核以及六核三个版本。根据比 A7 GPU 提高 50% 性能这个数据来猜测，A8 确实很可能使用了当下 PowverVR 系列中的旗舰 G6650，其运行频率约为 450mhz，32bit 浮点计算水平达到了恐怖的 172.8GFLOPS。如果真的是这颗 GPU 的话，笔者只想说：苹果干（sang）得（xin）漂（bing）亮（kuang）。这颗 GPU 是 Imagination Technologies 公司在今年 2 月份 WMC 上发布的，设计上和英伟达的 k1 有着异曲同工之妙。有 6 个 unified shading cluster，共 192 个核心，每个时钟周期处理 12 个像素点，根据 Imagination 官方文档其性能是竞争对手的 3 倍。这种高端移动显示芯片主要应用对象是高分辨率平板或 4K 智能电视。并且在功耗上也做了一些功夫，能够使得在较低功耗下仍然能发挥强劲的性能。&lt;/p&gt;
 &lt;p&gt;  &lt;a href="http://www.geekfan.net/wp-content/uploads/574b4869c6d64155912e0ede3be1cfcc.jpg" rel="lightbox[12193]" title="&amp;#28145;&amp;#24230;&amp;#35843;&amp;#26597; iPhone &amp;#30495;&amp;#30340;&amp;#19981;&amp;#25340;&amp;#30828;&amp;#20214;&amp;#21527;&amp;#65311;A8 &amp;#22788;&amp;#29702;&amp;#22120;&amp;#35299;&amp;#26512;"&gt;   &lt;img alt="daed210307f1dbc6f1dd9551408d999f_mw_680_wm_0_wmp_3 (1)" height="484" src="http://www.geekfan.net/wp-content/uploads/574b4869c6d64155912e0ede3be1cfcc.jpg" width="600"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;
 &lt;h2&gt;不堆硬件？只是不拿来夸耀罢了&lt;/h2&gt;
 &lt;p&gt;写到这里，各位读者应该有所明白， iPhone 的内部芯片实际上也是业界领先的，这种领先并不像体现在四核、八核这样简单粗暴的数量上，而是不断深挖芯片潜力，在尽可能低功耗的基础下，最大化程度榨出其性能。这和苹果的制造哲学也息息相关，曾未将参数作为宣传的卖点，而是用软件驯服硬件，让实际用户体验证明自己，所以 iPhone 并不是不拼硬件，只是不愿意这些冰冷的参数作为噱头罢了。&lt;/p&gt;

 &lt;div&gt;  &lt;div&gt;   &lt;h3&gt;相关文章&lt;/h3&gt;   &lt;ul&gt;    &lt;li&gt;     &lt;a href="http://www.geekfan.net/6705/"&gt;不越狱也能玩 GBA 模拟器：GBA4iOS 2.0&lt;/a&gt;&lt;/li&gt;    &lt;li&gt;     &lt;a href="http://www.geekfan.net/8292/"&gt;iPhone/iPad上有哪些不为人知的小技巧？&lt;/a&gt;&lt;/li&gt;    &lt;li&gt;     &lt;a href="http://www.geekfan.net/10791/"&gt;只要168,只要168:让iPhone变身家庭影院&lt;/a&gt;&lt;/li&gt;    &lt;li&gt;     &lt;a href="http://www.geekfan.net/11163/"&gt;再也不怕忘带 iPhone 数据线的手机壳：CasePlug&lt;/a&gt;&lt;/li&gt;    &lt;li&gt;     &lt;a href="http://www.geekfan.net/8632/"&gt;iPhone 无需越狱也能玩 NDS 游戏，NDS4iOS正式发布&lt;/a&gt;&lt;/li&gt;    &lt;li&gt;     &lt;a href="http://www.geekfan.net/7455/"&gt;iPhone防盗指南&lt;/a&gt;&lt;/li&gt;    &lt;li&gt;     &lt;a href="http://www.geekfan.net/3391/"&gt;要不要买iOS7系统的二手iPhone或iPad呢？先看看这篇文章吧！&lt;/a&gt;&lt;/li&gt;    &lt;li&gt;     &lt;a href="http://www.geekfan.net/1186/"&gt;iPhone新玩法——网络摄像头&lt;/a&gt;&lt;/li&gt;    &lt;li&gt;     &lt;a href="http://www.geekfan.net/7463/"&gt;iPhone上传全景照片到谷歌街景地图攻略&lt;/a&gt;&lt;/li&gt;    &lt;li&gt;     &lt;a href="http://www.geekfan.net/8605/"&gt;iPhone丢了怎么办？&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;/div&gt;
 &lt;p&gt;  &lt;a href="http://www.geekfan.net/12193/"&gt;深度调查 iPhone 真的不拼硬件吗？A8 处理器解析&lt;/a&gt;，首发于  &lt;a href="http://www.geekfan.net"&gt;极客范 - GeekFan.net&lt;/a&gt;。&lt;/p&gt;&lt;div&gt; &lt;a href="https://itindex.net/"  title="IT 资讯"&gt;&lt;img src="https://itindex.net/images/iconWarning.gif" title="IT 资讯" border="0"/&gt; &lt;/a&gt;</description>
      <category>iOS iphone</category>
      <guid isPermaLink="true">https://itindex.net/detail/51179-%E6%B7%B1%E5%BA%A6-iphone-%E7%A1%AC%E4%BB%B6</guid>
      <pubDate>Tue, 23 Sep 2014 13:38:08 CST</pubDate>
    </item>
    <item>
      <title>iOS 8与安卓系统有哪些相似点与不同点</title>
      <link>https://itindex.net/detail/49870-ios-%E5%AE%89%E5%8D%93-%E7%B3%BB%E7%BB%9F</link>
      <description>&lt;p&gt;今天凌晨，苹果在 WWDC 2014 大会发布了 iOS 8 新系统，关于新系统的新功能和新特性，想必大家都已经初步了解。在初步了解之后，下面我们要讨论的是，iOS 8 的发布对于安卓系统来说意味着什么，它与安卓系统又有哪些异同。&lt;/p&gt;
 &lt;p&gt;  &lt;a href="http://ki.ki.ki/files/2014/06/03/d4fe2b2a50d6e2af29a511bc69a1e614.png" title="&amp;#33821;&amp;#21340;&amp;#32593;"&gt;   &lt;img alt="&amp;#33821;&amp;#21340;&amp;#32593;" border="0" src="http://ki.ki.ki/files/2014/06/03/d4fe2b2a50d6e2af29a511bc69a1e614.png" title="&amp;#33821;&amp;#21340;&amp;#32593;"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;
 &lt;p&gt;今天的 WWDC 2014 上，苹果用诸多的权限和接口开放接超出了我们的预期，iOS 8 的SDK 含有超过 4000 个新的 API，而且它还有一个 Extensibility 计划，不同的应用可以通过iOS系统级的安全机制，与其它应用进行互动：分享内容至其它应用、在照片应用里使用其它应用的滤镜、在通知栏里添加插件，更夸张的是，支持第三方输入法。&lt;/p&gt;
 &lt;p&gt;iOS 8 的发布让我们对苹果的看法稍有改观，主要还是与“开放”相关，Extensibility 项目意味着 iOS 8 支持应用间通讯，可以更加开放的共享功能；iOS 8 支持第三方键盘输入法；iOS 8 内嵌的 Touch ID 指纹扫描功能将向应用开放，第三方应用也可以使用。&lt;/p&gt;
 &lt;p&gt;  &lt;a href="http://ki.ki.ki/files/2014/06/03/064494d6118b8a2a04c71e691104849d.jpg" title="&amp;#33821;&amp;#21340;&amp;#32593;"&gt;   &lt;img alt="&amp;#33821;&amp;#21340;&amp;#32593;" border="0" src="http://ki.ki.ki/files/2014/06/03/064494d6118b8a2a04c71e691104849d.jpg" title="&amp;#33821;&amp;#21340;&amp;#32593;"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;
 &lt;p&gt;原本，“开放”的特性是安卓的骄傲，现在苹果也渐渐参与进来，对此，有不少网友表示，iOS 8 加入了“开放性”给了安卓沉重一击，安卓可以洗洗睡了。其实不然，市场往往要比我们所认为的要复杂得多，也要灵活得多。如果你一味的认为 iOS 8 将终结安卓系统，那么请点击电脑右上方的小X。&lt;/p&gt;
 &lt;p&gt;iOS 8 的发布不只对苹果有利，同时也对安卓有益。iOS 8 的发布对谷歌来说意味着安卓不能懈怠，需要时刻保持警惕。在移动系统市场，苹果与谷歌相互竞争，同时也相互促使彼此进步。通过 Jelly Bean 和 Kitkat 系统，谷歌发布了一些优秀的功能与服务，苹果现在以 iOS 8 的发布给予有力回应，在 iOS 8 发布之后，谷歌又将会在下一代系统中回应 iOS 8，这是一种良性竞争。&lt;/p&gt;
 &lt;p&gt;下面我们来对比 iOS 8 和安卓系统之间功能的异同。&lt;/p&gt;
 &lt;p&gt;  &lt;a href="http://ki.ki.ki/files/2014/06/03/b2bf7f186872f3a13ed98ab47a5d9982.jpg" title="&amp;#33821;&amp;#21340;&amp;#32593;"&gt;   &lt;img alt="&amp;#33821;&amp;#21340;&amp;#32593;" border="0" src="http://ki.ki.ki/files/2014/06/03/b2bf7f186872f3a13ed98ab47a5d9982.jpg" title="&amp;#33821;&amp;#21340;&amp;#32593;"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;
 &lt;p&gt;交互通知（Interactive Notifications），iOS 8 的交互通知功能允许用户直接在通知中心回复信息，而当 Facebook 等社交网络更新时，用户可以直接在通知栏评论或者点赞，在锁屏界面也可以直接回复或删除信息和 iMessage 音频内容。&lt;/p&gt;
 &lt;p&gt;这个功能很酷，虽然安卓早就已经拥有类似功能，但 iOS 8 的似乎更加完善或者说更加便捷。在安卓系统上，当你点击通知中心里的按钮时，你需要退出当前应用，然后进入附在通知中心中的应用。&lt;/p&gt;
 &lt;p&gt;在 iOS 8 上，用户不需要离开当前应用也可在通知内回复短信息，这就是 iOS 8 交互通知更为方便的体现。&lt;/p&gt;
 &lt;p&gt;自动热点和拨打电话&lt;/p&gt;
 &lt;p&gt;苹果为 iOS 8 加入了自动热点和 Wi-Fi 拨号功能，将 iPhone 自动变成电脑热点，不需要人工设置，然后通过电脑拨打和接听电话。等等，我听到安卓粉丝在说：我们安卓早就有 Google Voice、Hang Outs 和热点应用啦！&lt;/p&gt;
 &lt;p&gt;没错，安卓早就已经有这些应用，但是 iOS 8 是无缝集成的，无需启动应用，也无需注册 Google Voice，不需要手动设置。iOS 8 新的语音通话和热点集成功能为用户时刻准备着。&lt;/p&gt;
 &lt;p&gt;  &lt;a href="http://ki.ki.ki/files/2014/06/03/1ca3817787fd0a828af4b7cabf834fa2.jpg" title="&amp;#33821;&amp;#21340;&amp;#32593;"&gt;   &lt;img alt="&amp;#33821;&amp;#21340;&amp;#32593;" border="0" src="http://ki.ki.ki/files/2014/06/03/1ca3817787fd0a828af4b7cabf834fa2.jpg" title="&amp;#33821;&amp;#21340;&amp;#32593;"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;
 &lt;p&gt;通过 Automatic Hotpot 热点功能，用户电脑会识别 iPhone 就在附近，然后允许用户通过热点连接它来使用手机的数据连接，它就出现在 Wi-Fi 选项列表中。在安卓系统中，你需要点击一个部件图标来加载热点，然后激活它。&lt;/p&gt;
 &lt;p&gt;使用 iOS 8 的语音通话功能，只要用户 iPhone 与电脑相连，用户就可以在电脑上拨打和接听电话。虽然安卓集成的 Google Voice 和 Hangouts 也可以实现这一功能，但安卓用户需要先注册另一个应用或者服务，而 iOS 8 不需要注册。谷歌 Hangouts 未来也打算往这个方向走，在谷歌新 Hangouts（环聊）到来之前，iOS 8 先来了。&lt;/p&gt;
 &lt;p&gt;  &lt;a href="http://ki.ki.ki/files/2014/06/03/2e28c79cc9c77207f07dce9e0b715086.jpg" title="&amp;#33821;&amp;#21340;&amp;#32593;"&gt;   &lt;img alt="&amp;#33821;&amp;#21340;&amp;#32593;" border="0" src="http://ki.ki.ki/files/2014/06/03/2e28c79cc9c77207f07dce9e0b715086.jpg" title="&amp;#33821;&amp;#21340;&amp;#32593;"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;
 &lt;p&gt;快速访问常用联系人&lt;/p&gt;
 &lt;p&gt;在 iOS 8 系统中，用户双击 iPhone 的 Home 键即可调出用户常用应用，还可以调出常用联系人，点击并进行联系。说句调侃的话，这一功能不止方便我们联系朋友，同时也方便了福尔摩斯女友们抓情敌。&lt;/p&gt;
 &lt;p&gt;在安卓上，用户不是需要通过 Hangouts（环聊）就是通过 People（通讯录）应用开进行对话，如果有更快捷的联系方式岂不是更好？虽然安卓也有诸多可访问最常用联系人的第三方应用，但 iOS 8 为原生的。&lt;/p&gt;
 &lt;p&gt;  &lt;a href="http://ki.ki.ki/files/2014/06/03/871d59bad8c303372da77db20199a2d7.jpg" title="&amp;#33821;&amp;#21340;&amp;#32593;"&gt;   &lt;img alt="&amp;#33821;&amp;#21340;&amp;#32593;" border="0" src="http://ki.ki.ki/files/2014/06/03/871d59bad8c303372da77db20199a2d7.jpg" title="&amp;#33821;&amp;#21340;&amp;#32593;"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;
 &lt;p&gt;信息、群组信息&lt;/p&gt;
 &lt;p&gt;Hangouts（环聊）是安卓系统的信息应用，虽然整合了通讯录和 Google +，但 iOS 8 上的 iMessage 或 Messages 更好一些。苹果今天发布了新的群组控制功能，比如在群组信息中，地图可以显示参与者的位置，或者也可以在群组中分享相册。&lt;/p&gt;
 &lt;p&gt;以上这一功能未来安卓也会增加，谷歌或许正在努力。&lt;/p&gt;
 &lt;p&gt;  &lt;a href="http://ki.ki.ki/files/2014/06/03/930f1dc51880d5572287c6ec2bdc20e3.jpg" title="&amp;#33821;&amp;#21340;&amp;#32593;"&gt;   &lt;img alt="&amp;#33821;&amp;#21340;&amp;#32593;" border="0" src="http://ki.ki.ki/files/2014/06/03/930f1dc51880d5572287c6ec2bdc20e3.jpg" title="&amp;#33821;&amp;#21340;&amp;#32593;"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;
 &lt;p&gt;Family Sharing（家庭分享功能）&lt;/p&gt;
 &lt;p&gt;苹果为 iOS 8 加入了 Family Share（家庭分享功能），允许用户分享位置、照片、日历、应用程序、音乐和视频等等。家庭分享功能中最重要的是 iTunes 购买内容的分享，也就是说同一张信用卡的所购买的专辑或者电影可以分享给家人（最多不超过 6 人），家人无需再次购买。&lt;/p&gt;
 &lt;p&gt;安卓也有类似的分享功能，不同的是，安卓用户需要再多款不同的设备上添加相同的 Gmail 账户之后才可以分享。&lt;/p&gt;
 &lt;p&gt;  &lt;a href="http://ki.ki.ki/files/2014/06/03/e4393cb8c40b9bc432223dc2ee8fc1d5.jpg" title="&amp;#33821;&amp;#21340;&amp;#32593;"&gt;   &lt;img alt="&amp;#33821;&amp;#21340;&amp;#32593;" border="0" src="http://ki.ki.ki/files/2014/06/03/e4393cb8c40b9bc432223dc2ee8fc1d5.jpg" title="&amp;#33821;&amp;#21340;&amp;#32593;"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;
 &lt;p&gt;应用程序包&lt;/p&gt;
 &lt;p&gt;App Bundle 是捆缚式的应用出售，开发者可以将几个应用捆绑在一起，以一个比单独分别购买便宜的价格出售。&lt;/p&gt;
 &lt;p&gt;这样的应用促销模式安卓 Google Play 或许也可以借鉴，在节日的时候将受欢迎的数款应用捆绑出售。&lt;/p&gt;
 &lt;p&gt;Hey，Siri 和 Ok，Google&lt;/p&gt;
 &lt;p&gt;最后我们要讲的是是Hey，Siri和安卓的 Ok，Google 功能。一听到 iOS 8 添加 Hey，Siri 的时候，我们第一反应是 Ok，Google。&lt;/p&gt;
 &lt;p&gt;Siri的新版本可以不接触手机就被激活，用户只要说“Hey，Siri”就可以把它唤醒，让它听从你的命令为你工作。其实这与 Ok，Google 的功能一模一样，用过的人都知道。&lt;/p&gt;
 &lt;table border="0" cellpadding="3" cellspacing="0"&gt;
    
      &lt;tr&gt;
           &lt;td colspan="5"&gt;    &lt;strong&gt;您可能对以下文章感兴趣：&lt;/strong&gt;&lt;/td&gt;
    &lt;/tr&gt;
    
          &lt;tr&gt;
                   &lt;td valign="top" width="106"&gt;
                        &lt;a href="http://app.wumii.com/ext/redirect?url=http%3A%2F%2Fluo.bo%2F54020%2F&amp;from=http%3A%2F%2Fluo.bo%2F54044%2F" target="_blank" title="&amp;#26159;&amp;#21542;&amp;#20540;&amp;#24471;&amp;#21319;&amp;#32423;&amp;#65306;iOS 8&amp;#31995;&amp;#32479;&amp;#25130;&amp;#22270;&amp;#30011;&amp;#24266;"&gt;
                             &lt;img height="100px" src="http://static.wumii.cn/site_images/ti/uMEAFQLa.jpg?i=AgZND3aO" width="100px"&gt;&lt;/img&gt;     &lt;br /&gt;
                        是否值得升级：iOS 8系统截图画廊
                    &lt;/a&gt;
                &lt;/td&gt;
                   &lt;td valign="top" width="106"&gt;
                        &lt;a href="http://app.wumii.com/ext/redirect?url=http%3A%2F%2Fluo.bo%2F46456%2F&amp;from=http%3A%2F%2Fluo.bo%2F54044%2F" target="_blank" title="iOS7&amp;#26368;&amp;#20982;&amp;#27531;&amp;#21151;&amp;#33021;"&gt;
                             &lt;img height="100px" src="http://static.wumii.cn/site_images/ti/CYhNjSoR.jpg?i=scxhBgPU" width="100px"&gt;&lt;/img&gt;     &lt;br /&gt;
                        iOS7最凶残功能
                    &lt;/a&gt;
                &lt;/td&gt;
                   &lt;td valign="top" width="106"&gt;
                        &lt;a href="http://app.wumii.com/ext/redirect?url=http%3A%2F%2Fluo.bo%2F44024%2F&amp;from=http%3A%2F%2Fluo.bo%2F54044%2F" target="_blank" title="&amp;#20260;&amp;#23475;&amp;#23567;&amp;#20249;&amp;#20276;&amp;#20204;&amp;#30340;&amp;#26426;&amp;#20250;&amp;#26469;&amp;#21862;&amp;#65281;IOS/MacOS&amp;#31995;&amp;#32479;&amp;#21457;&amp;#29616;&amp;#23384;&amp;#22312;&amp;#36828;&amp;#31243;&amp;#25298;&amp;#32477;&amp;#26381;&amp;#21153;&amp;#28431;&amp;#27934;"&gt;
                             &lt;img height="100px" src="http://static.wumii.cn/site_images/ti/kjaJgx8h.gif?i=8APrnZ4h" width="100px"&gt;&lt;/img&gt;     &lt;br /&gt;
                        伤害小伙伴们的机会来啦！IOS/MacOS系统发现存在远程拒绝服务漏洞
                    &lt;/a&gt;
                &lt;/td&gt;
                   &lt;td valign="top" width="106"&gt;
                        &lt;a href="http://app.wumii.com/ext/redirect?url=http%3A%2F%2Fluo.bo%2F46224%2F&amp;from=http%3A%2F%2Fluo.bo%2F54044%2F" target="_blank" title="&amp;#29275;&amp;#20154;&amp;#29992;Word&amp;#25784;&amp;#20986;&amp;#19968;&amp;#20010;iOS 7&amp;#20986;&amp;#26469; &amp;#20013;&amp;#33521;&amp;#23383;&amp;#24149;"&gt;
                             &lt;img height="100px" src="http://static.wumii.cn/site_images/ti/fRhOCuAQ.jpg?i=biOZkPUe" width="100px"&gt;&lt;/img&gt;     &lt;br /&gt;
                        牛人用Word撸出一个iOS 7出来 中英字幕
                    &lt;/a&gt;
                &lt;/td&gt;
                   &lt;td valign="top" width="106"&gt;
                        &lt;a href="http://app.wumii.com/ext/redirect?url=http%3A%2F%2Fluo.bo%2F10192%2F&amp;from=http%3A%2F%2Fluo.bo%2F54044%2F" target="_blank" title="&amp;#20174;&amp;#22478;&amp;#24066;&amp;#24223;&amp;#27700;&amp;#21040;Isar&amp;#27827;&amp;#27700;&amp;#8212;&amp;#8212;&amp;#24917;&amp;#23612;&amp;#40657;&amp;#30340;&amp;#25490;&amp;#27700;&amp;#31995;&amp;#32479;"&gt;
                             &lt;img height="100px" src="http://static.wumii.cn/site_images/ti/Brgy3A8.jpg?i=pam2Qkg5" width="100px"&gt;&lt;/img&gt;     &lt;br /&gt;
                        从城市废水到Isar河水——慕尼黑的排水系统
                    &lt;/a&gt;
                &lt;/td&gt;
        &lt;/tr&gt;
          &lt;br /&gt;
      &lt;tr&gt;
           &lt;td colspan="5"&gt;    &lt;strong&gt;来自无觅网络的相关文章：&lt;/strong&gt;&lt;/td&gt;
    &lt;/tr&gt;
    
          &lt;tr&gt;
                   &lt;td valign="top" width="106"&gt;
                        &lt;a href="http://app.wumii.com/ext/redirect?url=http%3A%2F%2Fuuhy.com%2Fhtml%2F18208.html&amp;from=http%3A%2F%2Fluo.bo%2F54044%2F" target="_blank" title="30&amp;#20010;iOS&amp;#31995;&amp;#32479;&amp;#30340;App&amp;#24212;&amp;#29992;&amp;#31243;&amp;#24207;Icon&amp;#35774;&amp;#35745;(@uuhy)"&gt;
                             &lt;img height="100px" src="http://static.wumii.cn/site_images/ti/knJ8I8dd.jpg?i=I7AmLwiK" width="100px"&gt;&lt;/img&gt;     &lt;br /&gt;
                        30个iOS系统的App应用程序Icon设计(@uuhy)
                    &lt;/a&gt;
                &lt;/td&gt;
                   &lt;td valign="top" width="106"&gt;
                        &lt;a href="http://app.wumii.com/ext/redirect?url=http%3A%2F%2Fwww.syncoo.com%2Fhow-to-solve-ios-43-syncing-issues.htm&amp;from=http%3A%2F%2Fluo.bo%2F54044%2F" target="_blank" title="&amp;#12304;&amp;#25216;&amp;#24039;&amp;#12305;&amp;#22914;&amp;#20309;&amp;#35299;&amp;#20915; iOS 4.3 &amp;#31995;&amp;#32479;&amp;#21516;&amp;#27493;&amp;#22833;&amp;#36133;&amp;#38382;&amp;#39064;(@syncoo)"&gt;
                             &lt;img height="100px" src="http://static.wumii.cn/site_images/ti/iTrZ3G98.jpg?i=l0pR4LKs" width="100px"&gt;&lt;/img&gt;     &lt;br /&gt;
                        【技巧】如何解决 iOS 4.3 系统同步失败问题(@syncoo)
                    &lt;/a&gt;
                &lt;/td&gt;
                   &lt;td valign="top" width="106"&gt;
                        &lt;a href="http://app.wumii.com/ext/redirect?url=http%3A%2F%2Fwww.syncoo.com%2Fios-5-ipad-glance.htm&amp;from=http%3A%2F%2Fluo.bo%2F54044%2F" target="_blank" title="&amp;#33529;&amp;#26524; iOS5 &amp;#31995;&amp;#32479;&amp;#20307;&amp;#39564;&amp;#35780;&amp;#27979; [iPad] &amp;#65288;&amp;#22810;&amp;#22270;+&amp;#38468;&amp;#19979;&amp;#36733;&amp;#22320;&amp;#22336;&amp;#65289;(@syncoo)"&gt;
                             &lt;img height="100px" src="http://static.wumii.cn/site_images/ti/z3bKjJmB.png?i=dknbhbp2" width="100px"&gt;&lt;/img&gt;     &lt;br /&gt;
                        苹果 iOS5 系统体验评测 [iPad] （多图+附下载地址）(@syncoo)
                    &lt;/a&gt;
                &lt;/td&gt;
                   &lt;td valign="top" width="106"&gt;
                        &lt;a href="http://app.wumii.com/ext/redirect?url=http%3A%2F%2Fwww.syncoo.com%2Fashun-on-what-if-crash-484.htm&amp;from=http%3A%2F%2Fluo.bo%2F54044%2F" target="_blank" title="[&amp;#24449;&amp;#25991;] A.shun&amp;#65306;&amp;#20551;&amp;#22914;&amp;#19968;&amp;#23567;&amp;#26102;&amp;#21518;&amp;#25105;&amp;#30340;&amp;#31995;&amp;#32479;&amp;#23849;&amp;#28291;&amp;#8230;?(@syncoo)"&gt;
                             &lt;img height="100px" src="http://static.wumii.cn/site_images/ti/aODNkQMo.jpg?i=cHJMAEdQ" width="100px"&gt;&lt;/img&gt;     &lt;br /&gt;
                        [征文] A.shun：假如一小时后我的系统崩溃…?(@syncoo)
                    &lt;/a&gt;
                &lt;/td&gt;
                   &lt;td valign="top" width="106"&gt;
                        &lt;a href="http://app.wumii.com/ext/redirect?url=http%3A%2F%2Fwww.fanjian.net%2Fpost%2F11535.html&amp;from=http%3A%2F%2Fluo.bo%2F54044%2F" target="_blank" title="&amp;#36825;&amp;#21488;&amp;#31508;&amp;#35760;&amp;#26412;&amp;#29992;&amp;#30340;&amp;#26159;win7&amp;#31995;&amp;#32479;&amp;#21527;&amp;#65311;(@fanjian)"&gt;
                             &lt;img height="100px" src="http://static.wumii.cn/site_images/ti/3Q1THD1q.jpg?i=19f2eaenZ" width="100px"&gt;&lt;/img&gt;     &lt;br /&gt;
                        这台笔记本用的是win7系统吗？(@fanjian)
                    &lt;/a&gt;
                &lt;/td&gt;
        &lt;/tr&gt;
    
      &lt;tr&gt;
           &lt;td align="right" colspan="5"&gt;
                &lt;a href="http://www.wumii.com/widget/relatedItems" target="_blank" title="&amp;#26080;&amp;#35269;&amp;#20851;&amp;#32852;&amp;#25512;&amp;#33616;"&gt;
                无觅
            &lt;/a&gt;
        &lt;/td&gt;
    &lt;/tr&gt;
&lt;/table&gt; &lt;div&gt;
  &lt;a href="http://feeds.feedburner.com/~ff/tamd?a=CyFxEq1lxDg:zh92QpVSCXA:yIl2AUoC8zA"&gt;   &lt;img border="0" src="http://feeds.feedburner.com/~ff/tamd?d=yIl2AUoC8zA"&gt;&lt;/img&gt;&lt;/a&gt;   &lt;a href="http://feeds.feedburner.com/~ff/tamd?a=CyFxEq1lxDg:zh92QpVSCXA:qj6IDK7rITs"&gt;   &lt;img border="0" src="http://feeds.feedburner.com/~ff/tamd?d=qj6IDK7rITs"&gt;&lt;/img&gt;&lt;/a&gt;   &lt;a href="http://feeds.feedburner.com/~ff/tamd?a=CyFxEq1lxDg:zh92QpVSCXA:-BTjWOF_DHI"&gt;   &lt;img border="0" src="http://feeds.feedburner.com/~ff/tamd?i=CyFxEq1lxDg:zh92QpVSCXA:-BTjWOF_DHI"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;&lt;div&gt; &lt;a href="https://itindex.net/"  title="IT 资讯"&gt;&lt;img src="https://itindex.net/images/iconWarning.gif" title="IT 资讯" border="0"/&gt; &lt;/a&gt;</description>
      <category>科技 iOS 8 安卓系统</category>
      <guid isPermaLink="true">https://itindex.net/detail/49870-ios-%E5%AE%89%E5%8D%93-%E7%B3%BB%E7%BB%9F</guid>
      <pubDate>Tue, 03 Jun 2014 20:40:48 CST</pubDate>
    </item>
    <item>
      <title>iOS 8 新改进(一)：相机与照片</title>
      <link>https://itindex.net/detail/49862-ios-%E7%9B%B8%E6%9C%BA-%E7%85%A7%E7%89%87</link>
      <description>&lt;p&gt;  &lt;a href="http://img.guomii.com/2014/06/hero_2x.jpg"&gt;   &lt;img alt="hero_2x" height="1240" src="http://img.guomii.com/2014/06/hero_2x.jpg" width="2124"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;
 &lt;p&gt;对于很喜欢用 iPhone 拍照，并且有大量照片保存的果迷来说，iOS 8 中的相机与照片改进可以说是非常值得一提的。&lt;/p&gt;
 &lt;p&gt;首先，iOS 8 的相机加入了延时摄影(Time-lapse)功能，这应该是继 Apple 加入“全景图拍摄”功能之后有一个炫酷且实用的功能。简单的说，延时摄影就是隔一段时间拍摄一张照片，最后合并成一个快速播放的视频。比如，你可以用演示摄影拍摄星斗转移、拍摄植物生长的过程、拍摄城市中熙熙攘攘的人群等等。&lt;/p&gt;
 &lt;p&gt;  &lt;a href="http://img.guomii.com/2014/06/12322.jpg"&gt;   &lt;img alt="12322" height="356" src="http://img.guomii.com/2014/06/12322.jpg" width="638"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;
 &lt;p&gt;其次，iOS 8 中存储的照片和 iCloud 存储进行了完美整合。你用 iPhone 拍摄的任何一张照片，都会立刻自动同步到你的其他 Apple 设备中，比如 Mac、iPad，甚至网络相册中。&lt;/p&gt;
 &lt;p&gt;iCloud 照片库远不仅仅是帮你存储/同步那么简单，而且它还会自动将全尺寸的源文件存放于 iCloud 照片库中(甚至 RAW 格式的源文件)，而在你设备上显示的其实是适合你屏幕分辨率的轻量级照片，这样一来不仅会让本机照片库中的照片加载的更加流畅，而且也能节约你本机的空间。&lt;/p&gt;
 &lt;p&gt;  &lt;a href="http://img.guomii.com/2014/06/library_gallery2.jpg"&gt;   &lt;img alt="library_gallery2" height="537" src="http://img.guomii.com/2014/06/library_gallery2.jpg" width="877"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;
 &lt;p&gt;当然，这样一来 iCloud 的存储空间就会费得很快，这应该是 Apple 故意这样的，要不怎么卖 iCloud 存储呢？用户存放在 iCloud 中的所有文件如果不超过5GB则免费，想要存放更多就需要付费了。Apple 宣布的两档套餐分别是：&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;20GB，0.99美元/月&lt;/li&gt;
  &lt;li&gt;100GB，3.99美元/月&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;第三，照片加入搜索功能，找照片更加容易。搜索可以根据拍摄的时间(包括大概时间)、地点、相册名等等作为关键词进行搜索。比如，你只记得之前在纽约拍摄了一张照片，有了搜索功能之后，就可以直接在搜索框中输入“纽约”，就可以筛选出所有你在纽约拍摄的照片，定位速度大大提高。&lt;/p&gt;
 &lt;p&gt;  &lt;a href="http://img.guomii.com/2014/06/find_2x.jpg"&gt;   &lt;img alt="find_2x" height="1608" src="http://img.guomii.com/2014/06/find_2x.jpg" width="2014"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;
 &lt;p&gt;第四，智能编辑。这一点主要包含两个部分，一方面是智能调整，主要用于调整照片的亮度、颜色，并且还能进行对比度、曝光、阴影等多方面进行调整；另一方面是智能构图，比如当你拍摄了一张照片，但是由于持握手机的时候没有水平，导致找出的照片有点倾斜，拍摄之后程序可以自动帮你用旋转+裁切的方式修正。&lt;/p&gt;
 &lt;p&gt;  &lt;a href="http://img.guomii.com/2014/06/capture_composition.jpg"&gt;   &lt;img alt="capture_composition" height="444" src="http://img.guomii.com/2014/06/capture_composition.jpg" width="918"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;
 &lt;p&gt;第五，滤镜。目前在使用 iOS 7 的用户应该知道 Apple 已经有一些内置照片滤镜，但如果你对 Apple 的这些滤镜不满意的话，在 iOS 8 中甚至可以直接调用第三方 App 的滤镜。要使用这一功能，需要第三方 App 开发者允许他们的滤镜被 Apple 官方的”照片”应用访问。&lt;/p&gt;
&lt;div&gt; &lt;a href="https://itindex.net/"  title="IT 资讯"&gt;&lt;img src="https://itindex.net/images/iconWarning.gif" title="IT 资讯" border="0"/&gt; &lt;/a&gt;</description>
      <category>分享 iOS 8</category>
      <guid isPermaLink="true">https://itindex.net/detail/49862-ios-%E7%9B%B8%E6%9C%BA-%E7%85%A7%E7%89%87</guid>
      <pubDate>Tue, 03 Jun 2014 04:36:42 CST</pubDate>
    </item>
    <item>
      <title>iOS 8 新改进(二)：信息</title>
      <link>https://itindex.net/detail/49861-ios-%E4%BF%A1%E6%81%AF</link>
      <description>&lt;p&gt;  &lt;a href="http://img.guomii.com/2014/06/hero_image.jpg"&gt;   &lt;img alt="hero_image" height="571" src="http://img.guomii.com/2014/06/hero_image.jpg" width="1442"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;
 &lt;p&gt;用上 iPhone 之后，我们几乎已经习惯发短信不要钱了(虽然必须要求双方都是 Apple 设备且必须启用 iMessage)。&lt;/p&gt;
 &lt;p&gt;实际上，这个功能本身并没有什么值得说的，QQ、微信或者几乎任何一个即时通软件都是发信息不要钱，还能跨平台。而 Apple 最厉害的地方在于他们直接将”即时通软件”和”信息”整合在一起，且你的手机号就是你的”即时通”号码，甚至连登陆、启动程序的过程都省掉了。&lt;/p&gt;
 &lt;p&gt;而在 iOS 8 中，Apple 将再一次对“信息”进行革命性的改进。最明显的就是支持发送语音和视频短信。&lt;/p&gt;
 &lt;p&gt;需要注意的是，发送语音短信并不同于发送语音文件和视频，而是像微信那样，直接在需要发送语音短信的时候，按住麦克风图标，对着手机讲话，然后上划即可发送出去。发视频短信也是类似的。这一功能被介绍之后，微博上立刻有很多果迷表示微信可以卸载了。&lt;/p&gt;
 &lt;p&gt;iMessage 群聊也有了新功能。如果说以前的群聊还多少有点类似于”短信群发”功能的话，现在的 iMessage 群聊则更像是 QQ 的”讨论组”，而且功能更加强大。比如你可以给”讨论组”命名、可以从”讨论”组里删除掉某人，也可以在讨论组中分享自己的位置(包括当前位置)等等。&lt;/p&gt;
&lt;div&gt; &lt;a href="https://itindex.net/"  title="IT 资讯"&gt;&lt;img src="https://itindex.net/images/iconWarning.gif" title="IT 资讯" border="0"/&gt; &lt;/a&gt;</description>
      <category>分享 iMessage iOS 8</category>
      <guid isPermaLink="true">https://itindex.net/detail/49861-ios-%E4%BF%A1%E6%81%AF</guid>
      <pubDate>Tue, 03 Jun 2014 05:04:41 CST</pubDate>
    </item>
    <item>
      <title>10个我们希望成为现实的苹果专利</title>
      <link>https://itindex.net/detail/50253-%E5%B8%8C%E6%9C%9B-%E7%8E%B0%E5%AE%9E-%E8%8B%B9%E6%9E%9C</link>
      <description>&lt;p&gt;  &lt;a href="http://www.geekfan.net/wp-content/uploads/39719d2d8cdb2cbee8c4b0bf73c9afa0.jpg" rel="lightbox[10115]" title="10&amp;#20010;&amp;#25105;&amp;#20204;&amp;#24076;&amp;#26395;&amp;#25104;&amp;#20026;&amp;#29616;&amp;#23454;&amp;#30340;&amp;#33529;&amp;#26524;&amp;#19987;&amp;#21033;"&gt;   &lt;img alt="9851365_980x1200_0" height="823" src="http://www.geekfan.net/wp-content/uploads/39719d2d8cdb2cbee8c4b0bf73c9afa0.jpg" width="858"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;
 &lt;p&gt;苹果各种五花八门的专利一直都是媒体所关注的对象，虽然这些专利并不一定会最终成为现实，但它们至少可以帮助外界了解到一向神秘的苹果都在捣鼓些什么。不过话说回来，我们依然希望那些有趣的专利可以成为现实。下面就是10个我们最希望可以变成现实的苹果专利。&lt;/p&gt;
 &lt;p&gt;1.触觉显示屏技术：&lt;/p&gt;
 &lt;p&gt;如今的全触屏手机让盲操作变得几乎不可能，但苹果在2012年5月申请的触觉显示屏技术或许可以改变这一点。&lt;/p&gt;
 &lt;p&gt;苹果这套精密的多层系统可让OLED屏幕与多个传感器/促动器同步工作，并通过数个弹力屏幕层的彼此层叠来实现按键或物体的三维化，或是为图像（比如地质图）带来纹理感。这套系统还可以对使用者的物理触碰作出回应，并分辨手指接触屏幕时的力度是轻还是重。&lt;/p&gt;
 &lt;p&gt;  &lt;a href="http://www.geekfan.net/wp-content/uploads/a7e4bfcaf997cd37b6e1a66a3c347834.jpg" rel="lightbox[10115]" title="10&amp;#20010;&amp;#25105;&amp;#20204;&amp;#24076;&amp;#26395;&amp;#25104;&amp;#20026;&amp;#29616;&amp;#23454;&amp;#30340;&amp;#33529;&amp;#26524;&amp;#19987;&amp;#21033;"&gt;   &lt;img alt="9851366_980x1200_0" height="462" src="http://www.geekfan.net/wp-content/uploads/a7e4bfcaf997cd37b6e1a66a3c347834.jpg" width="852"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;
 &lt;p&gt;2.防碎玻璃：&lt;/p&gt;
 &lt;p&gt;手机在使用时难免会产生磕碰或不慎坠地，但如果苹果的这项专利成为现实，我们不必再担心由此对手机带来的损伤了。&lt;/p&gt;
 &lt;p&gt;这项防碎玻璃专利获批于2011年11月，具体方式是使用钾和钠离子来处理iPhone 4和4s所使用的铝硅酸盐玻璃。这种处理可以提高后者表面和边缘的压缩阈值，从而使其更加不易破碎。&lt;/p&gt;
 &lt;p&gt;专利当中还提到了另一个有趣的功能：在设备的屏幕玻璃和金属外壳之间放置了一个减震架。当设备的加速度计检测到机身正在坠落时，这个减震架便会立刻膨胀起来，并包裹屏幕玻璃，这样也就起到了保护的目的。&lt;/p&gt;
 &lt;p&gt;  &lt;a href="http://www.geekfan.net/wp-content/uploads/8f31ceac282e8866c26ed89dcd46c1fa.jpg" rel="lightbox[10115]" title="10&amp;#20010;&amp;#25105;&amp;#20204;&amp;#24076;&amp;#26395;&amp;#25104;&amp;#20026;&amp;#29616;&amp;#23454;&amp;#30340;&amp;#33529;&amp;#26524;&amp;#19987;&amp;#21033;"&gt;   &lt;img alt="9851367_980x1200_0" height="568" src="http://www.geekfan.net/wp-content/uploads/8f31ceac282e8866c26ed89dcd46c1fa.jpg" width="858"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;
 &lt;p&gt;3.iWallet：&lt;/p&gt;
 &lt;p&gt;虽然苹果目前正在拓展自己的Touch ID技术，来让用户可以在iPhone 5s及未来的设备上进行轻松购物，但在此之前，苹果实际上曾经设计过一个完整的数字钱包系统，可让用户在iPhone上直接控制自己的财务账户。&lt;/p&gt;
 &lt;p&gt;这项名为“iWallet”的专利在2012年3月获批。该系统可让用户轻松查看自己信用卡的完整信息，并浏览来自银行的通知和信息。此外，iWallet还具备家长控制功能，以供家长限制子女的花费。&lt;/p&gt;
 &lt;p&gt;  &lt;a href="http://www.geekfan.net/wp-content/uploads/cf3b2df395ec7758628837b008527339.jpg" rel="lightbox[10115]" title="10&amp;#20010;&amp;#25105;&amp;#20204;&amp;#24076;&amp;#26395;&amp;#25104;&amp;#20026;&amp;#29616;&amp;#23454;&amp;#30340;&amp;#33529;&amp;#26524;&amp;#19987;&amp;#21033;"&gt;   &lt;img alt="9851368_1200x1000_0" height="574" src="http://www.geekfan.net/wp-content/uploads/cf3b2df395ec7758628837b008527339.jpg" width="849"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;
 &lt;p&gt;4.虚拟购物伴侣：&lt;/p&gt;
 &lt;p&gt;早在亚马逊发布Firefly技术的数年之前，苹果就已经设计了一项名为“Products+”的专利，让用户可以扫描商店中的任意商品以获取更多信息。该功能当中还包含了某种游戏化的元素，当消费者在扫描特定物品时，系统会给予一些免费的奖励，比如iTunes音乐或商品等。&lt;/p&gt;
 &lt;p&gt;  &lt;a href="http://www.geekfan.net/wp-content/uploads/873468235867a3d3c41db29e4860d2d5.jpg" rel="lightbox[10115]" title="10&amp;#20010;&amp;#25105;&amp;#20204;&amp;#24076;&amp;#26395;&amp;#25104;&amp;#20026;&amp;#29616;&amp;#23454;&amp;#30340;&amp;#33529;&amp;#26524;&amp;#19987;&amp;#21033;"&gt;   &lt;img alt="9851369_1200x1000_0" height="556" src="http://www.geekfan.net/wp-content/uploads/873468235867a3d3c41db29e4860d2d5.jpg" width="856"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;
 &lt;p&gt;5.3D图像：&lt;/p&gt;
 &lt;p&gt;亚马逊或许先于苹果推出了首款“3D”智能手机，但苹果未来的设备或许也会具备拍摄3D图像的能力，这要多亏了他们在2012年3月获取的一项拍照专利。&lt;/p&gt;
 &lt;p&gt;虽然目前的市面上已经存在不少的3D相机，但这些设备基本上都无法捕捉到形状、表面和景深方面的足够信息。而苹果的解决方案是利用多种传感器和摄像头：一个传感器用于捕捉偏振图像，其他两个则会捕捉两种不同的非偏振图像，系统则会将这些图像相结合。&lt;/p&gt;
 &lt;p&gt; &lt;/p&gt;
 &lt;p&gt; &lt;/p&gt;
 &lt;p&gt;  &lt;a href="http://www.geekfan.net/wp-content/uploads/49eae9cea3d09b0cb06e3d9dce1d78b8.jpg" rel="lightbox[10115]" title="10&amp;#20010;&amp;#25105;&amp;#20204;&amp;#24076;&amp;#26395;&amp;#25104;&amp;#20026;&amp;#29616;&amp;#23454;&amp;#30340;&amp;#33529;&amp;#26524;&amp;#19987;&amp;#21033;"&gt;   &lt;img alt="9851370_1200x1000_0" height="808" src="http://www.geekfan.net/wp-content/uploads/49eae9cea3d09b0cb06e3d9dce1d78b8.jpg" width="1200"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;
 &lt;p&gt; &lt;/p&gt;
 &lt;p&gt;6.交互式全息图：&lt;/p&gt;
 &lt;p&gt;全息图的概念很早就已经流行开来，但我们能见到它的地方主要还是在科幻电影里。而苹果对于全息图也产生了兴趣：他们不仅想让用户看到全息图，还能够与之进行互动。&lt;/p&gt;
 &lt;p&gt;在2012年10月的一项专利当中，苹果描述了了一种可以将图像投影在半空中，并允许用户与之进行互动的系统。用户还可以通过一些常用的手势，比如滑动和捏拉缩放，来对图像进行控制。&lt;/p&gt;
 &lt;p&gt;  &lt;a href="http://www.geekfan.net/wp-content/uploads/74870f07b0ec590a05aad2bb11f41baa.jpg" rel="lightbox[10115]" title="10&amp;#20010;&amp;#25105;&amp;#20204;&amp;#24076;&amp;#26395;&amp;#25104;&amp;#20026;&amp;#29616;&amp;#23454;&amp;#30340;&amp;#33529;&amp;#26524;&amp;#19987;&amp;#21033;"&gt;   &lt;img alt="9851371_1200x1000_0" height="420" src="http://www.geekfan.net/wp-content/uploads/74870f07b0ec590a05aad2bb11f41baa.jpg" width="695"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;
 &lt;p&gt;7.液态金属：&lt;/p&gt;
 &lt;p&gt;苹果在2010年购买了LiquidMetal旗下液态金属的独家使用权。这是一种非常强韧的合金，因其超高的抗划伤/腐蚀性而非常适合消费类电子设备的生产。&lt;/p&gt;
 &lt;p&gt;但无论是处于何种原因，苹果目前还尚未推出一款整体采用液态金属材质的设备。而根据今年5月份公布的一项专利，苹果正在思考一种“将玻璃完整嵌入金属边框”的方式，这将会造就一部极为强韧的iPhone，至少是从结构上讲。专利称苹果会使用一种新型的显示屏玻璃，这不禁让我们想起了最近有关iPhone 6将采用蓝宝石屏幕的消息。&lt;/p&gt;
 &lt;p&gt;  &lt;a href="http://www.geekfan.net/wp-content/uploads/2f39adc1c685278ee9f317f2df5e4be8.jpg" rel="lightbox[10115]" title="10&amp;#20010;&amp;#25105;&amp;#20204;&amp;#24076;&amp;#26395;&amp;#25104;&amp;#20026;&amp;#29616;&amp;#23454;&amp;#30340;&amp;#33529;&amp;#26524;&amp;#19987;&amp;#21033;"&gt;   &lt;img alt="9851372_1200x1000_0" height="436" src="http://www.geekfan.net/wp-content/uploads/2f39adc1c685278ee9f317f2df5e4be8.jpg" width="823"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;
 &lt;p&gt;8.智能互动地图：&lt;/p&gt;
 &lt;p&gt;苹果的地图技术或许永远不会赶上谷歌，但在去年12月公布的一项专利展示了苹果对于其地图服务的一些不同想法。&lt;/p&gt;
 &lt;p&gt;据悉，苹果的“互动地图”解决方案将会引入一种可定制的动态涂层系统，它只会显示用户需要的内容，从而让地图不显杂乱和信息过剩。比如说，如果你触摸了地图上的一个点，苹果地图会提供不同的路线图，并显示出沿途最主要的兴趣点。&lt;/p&gt;
 &lt;p&gt;  &lt;a href="http://www.geekfan.net/wp-content/uploads/1fecef163d69950fe4d8623c5657f404.jpg" rel="lightbox[10115]" title="10&amp;#20010;&amp;#25105;&amp;#20204;&amp;#24076;&amp;#26395;&amp;#25104;&amp;#20026;&amp;#29616;&amp;#23454;&amp;#30340;&amp;#33529;&amp;#26524;&amp;#19987;&amp;#21033;"&gt;   &lt;img alt="9851373_1200x1000_0" height="570" src="http://www.geekfan.net/wp-content/uploads/1fecef163d69950fe4d8623c5657f404.jpg" width="848"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;
 &lt;p&gt;9.苹果自行车：&lt;/p&gt;
 &lt;p&gt;自行车看上去并不像是苹果感兴趣的产品类型，但2012年的一项专利展示了一架可以和苹果移动设备进行通讯的自行车。在彼此相连之后，这些移动设备的屏幕上便会显示出“速度、距离、时间、高度、心率和电量”等信息。&lt;/p&gt;
 &lt;p&gt;  &lt;a href="http://www.geekfan.net/wp-content/uploads/879d426a442eb6f4041a184894cfbb51.jpg" rel="lightbox[10115]" title="10&amp;#20010;&amp;#25105;&amp;#20204;&amp;#24076;&amp;#26395;&amp;#25104;&amp;#20026;&amp;#29616;&amp;#23454;&amp;#30340;&amp;#33529;&amp;#26524;&amp;#19987;&amp;#21033;"&gt;   &lt;img alt="9851374_1200x1000_0" height="565" src="http://www.geekfan.net/wp-content/uploads/879d426a442eb6f4041a184894cfbb51.jpg" width="908"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;
 &lt;p&gt;10.iWatch：&lt;/p&gt;
 &lt;p&gt;苹果传闻当中的iWatch智能手表是时下人们所讨论的热门话题，而这项在2月份公布的专利也引发了众人的无限遐想。&lt;/p&gt;
 &lt;p&gt;专利中的这款智能手表配备了连续、柔性的显示屏，其内部是柔性钢带，外部则由织物所包裹。这款设备和与移动设备进行通讯，其配备的太阳能面板还能够提升设备的续航。如果你认为在智能手表上加入太阳能面板有些古怪，苹果在上个月也升级了自己的太阳能专利，为其加入了多点触控和柔性显示屏，这些也都是iWatch会具备的能力。&lt;/p&gt;
 &lt;p&gt; &lt;/p&gt;

 &lt;div&gt;  &lt;div&gt;   &lt;h3&gt;相关文章&lt;/h3&gt;   &lt;ul&gt;    &lt;li&gt;     &lt;a href="http://www.geekfan.net/9454/"&gt;稀世品: 和美国收藏家共赏那些经典苹果产品的原型机&lt;/a&gt;&lt;/li&gt;    &lt;li&gt;     &lt;a href="http://www.geekfan.net/4763/"&gt;Google和苹果的新战役——谁的智能汽车是部好手机？ &lt;/a&gt;&lt;/li&gt;    &lt;li&gt;     &lt;a href="http://www.geekfan.net/9755/"&gt;你可能不知道 智能手机还有这10个神奇功能&lt;/a&gt;&lt;/li&gt;    &lt;li&gt;     &lt;a href="http://www.geekfan.net/6240/"&gt;iPhone 6 真机外壳谍照首次出现&lt;/a&gt;&lt;/li&gt;    &lt;li&gt;     &lt;a href="http://www.geekfan.net/6705/"&gt;不越狱也能玩 GBA 模拟器：GBA4iOS 2.0&lt;/a&gt;&lt;/li&gt;    &lt;li&gt;     &lt;a href="http://www.geekfan.net/9087/"&gt;如何把你的iPad变成一个桌面电脑？&lt;/a&gt;&lt;/li&gt;    &lt;li&gt;     &lt;a href="http://www.geekfan.net/6725/"&gt;教你如何安装Flappy Bird，即使它已经下架！&lt;/a&gt;&lt;/li&gt;    &lt;li&gt;     &lt;a href="http://www.geekfan.net/7463/"&gt;iPhone上传全景照片到谷歌街景地图攻略&lt;/a&gt;&lt;/li&gt;    &lt;li&gt;     &lt;a href="http://www.geekfan.net/9982/"&gt;手机急救：哪种家居用品干燥浸水手机效果最好？&lt;/a&gt;&lt;/li&gt;    &lt;li&gt;     &lt;a href="http://www.geekfan.net/8146/"&gt;试试看Chrome浏览器最神翻译：全球共通的「表情符号语言」！&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;/div&gt;
 &lt;p&gt;  &lt;a href="http://www.geekfan.net/10115/"&gt;10个我们希望成为现实的苹果专利&lt;/a&gt;，首发于  &lt;a href="http://www.geekfan.net"&gt;极客范 - GeekFan.net&lt;/a&gt;。&lt;/p&gt;&lt;div&gt; &lt;a href="https://itindex.net/"  title="IT 资讯"&gt;&lt;img src="https://itindex.net/images/iconWarning.gif" title="IT 资讯" border="0"/&gt; &lt;/a&gt;</description>
      <category>iOS 苹果</category>
      <guid isPermaLink="true">https://itindex.net/detail/50253-%E5%B8%8C%E6%9C%9B-%E7%8E%B0%E5%AE%9E-%E8%8B%B9%E6%9E%9C</guid>
      <pubDate>Thu, 03 Jul 2014 21:42:51 CST</pubDate>
    </item>
  </channel>
</rss>

