<?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/categories/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/categories/ios</link>
    </image>
    <item>
      <title>Unity 引擎植入 App 渲染方案正式发布：可与原生 UI 无缝融合，丝滑交互，支持 iOS / 安卓</title>
      <link>https://itindex.net/detail/62371-unity-%E5%BC%95%E6%93%8E-%E6%A4%8D%E5%85%A5</link>
      <description>&lt;p&gt;IT之家 8 月 16 日消息，据 Unity 中国官方开发者平台消息，Unity 正式推出了“Unity 植入 App 渲染方案”，开发者可将 3D 内容渲染到 App 中的小窗口，用户可以进行点击、拖拽等交互。&lt;/p&gt; &lt;p&gt;  &lt;img src="https://img.ithome.com/newsuploadfiles/2022/8/e70dd787-3b2f-4e61-bf93-7f53c44bb30e.png"&gt;&lt;/img&gt;&lt;/p&gt; &lt;p&gt;据官方介绍，“Unity 植入 App 渲染方案”克服了此前民间试水 App 植入 Unity 的诸多困难，可与 App 原生 UI 无缝融合，官方演示了将 3D 内容直接嵌入电商平台的顶部轮播图 Banner 中。&lt;/p&gt; &lt;p&gt;▼ 图自“Unity 官方平台”微信公众号，下同&lt;/p&gt; &lt;p&gt;  &lt;img alt="&amp;#22270;&amp;#33258;&amp;#8220;Unity &amp;#23448;&amp;#26041;&amp;#24179;&amp;#21488;&amp;#8221;&amp;#24494;&amp;#20449;&amp;#20844;&amp;#20247;&amp;#21495;" src="https://img.ithome.com/newsuploadfiles/2022/8/00bb6369-c512-49df-843a-457a1e6f7519.gif"&gt;&lt;/img&gt;&lt;/p&gt; &lt;p&gt;此外，“Unity 植入 App 渲染方案”可以让用户在 App 中与 3D 内容交互，比如查看鞋子的细节，和目前部分电商平台已经上线的功能类似。&lt;/p&gt; &lt;p&gt;该方案下，3D 内容还可以像游戏一样交互，下图是一个场景浏览的示例。&lt;/p&gt; &lt;p&gt;  &lt;img alt="&amp;#22270;&amp;#29255;" src="https://img.ithome.com/newsuploadfiles/2022/8/ac2ca8bf-43fb-4ed6-a852-106894269f5e.gif"&gt;&lt;/img&gt;&lt;/p&gt; &lt;p&gt;IT之家了解到，此前虚幻引擎和 Cocos-2dx 都有过嵌入 App 的使用，不过需要进行界面单独加载，Unity 的方案看起来确实要先进一些。&lt;/p&gt; &lt;p&gt;“Unity 植入 App 渲染方案”支持 iOS / 安卓两大 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 />
      <guid isPermaLink="true">https://itindex.net/detail/62371-unity-%E5%BC%95%E6%93%8E-%E6%A4%8D%E5%85%A5</guid>
      <pubDate>Wed, 17 Aug 2022 00:04:48 CST</pubDate>
    </item>
    <item>
      <title>iOS - 冷启动优化</title>
      <link>https://itindex.net/detail/62167-ios-%E5%86%B7%E5%90%AF%E5%8A%A8-%E4%BC%98%E5%8C%96</link>
      <description>&lt;p&gt;随着App不断迭代，使的业务模块增加，逻辑变得复杂，集成了更多的第三方库，App 启动也会越来越慢，因此我们希望能在业务扩张的同时，保持较好的启动速度，给用户带来良好的体验。&lt;/p&gt;
 &lt;h1&gt;一、名词概念理论&lt;/h1&gt;
 &lt;p&gt;为了更准确地了解 App 冷启动的流程，我们需要掌握一些基本的概念&lt;/p&gt;
 &lt;h2&gt;1.1.Mach-O&lt;/h2&gt;
 &lt;p&gt;  &lt;a href="https://links.jianshu.com/go?to=https://developer.apple.com/library/archive/documentation/Performance/Conceptual/CodeFootprint/Articles/MachOOverview.html"&gt;Mach-O&lt;/a&gt;（Mach Object File Format)是一种用于记录可执行文件、对象代码、共享库、动态加载代码和内存转储的文件格式。App 编译生成的二进制  &lt;strong&gt;可执行文件&lt;/strong&gt;就是 Mach-O 格式的，iOS 工程所有的类编译后会生成对应的目标文件   &lt;code&gt;.o&lt;/code&gt; 文件，而这个可执行文件就是这些   &lt;code&gt;.o&lt;/code&gt; 文件的集合。&lt;/p&gt;
 &lt;p&gt;在 Xcode 的控制台输入以下命令，可以打印出运行时所有加载进应用程序的 Mach-O 文件。&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;image list -o -f
&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;Mach-O 文件主要由三部分组成：&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;Mach header：描述 Mach-O 的 CPU 架构、文件类型以及加载命令等；&lt;/li&gt;
&lt;/ul&gt;

 &lt;ul&gt;
  &lt;li&gt;Load commands：描述了文件中数据的具体组织结构，不同的数据类型使用不同的加载命令；&lt;/li&gt;
&lt;/ul&gt;

 &lt;ul&gt;
  &lt;li&gt;
   &lt;p&gt;Data：Data 中的每个段（segment）的数据都保存在这里，每个段都有一个或多个 Section，它们存放了具体的数据与代码，主要包含这三种类型：&lt;/p&gt;
   &lt;ul&gt;
    &lt;li&gt;     &lt;code&gt;__TEXT&lt;/code&gt; 包含 Mach header，被执行的代码和只读常量（如C 字符串）。只读可执行（r-x）。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;

 &lt;ul&gt;
  &lt;li&gt;   &lt;code&gt;__DATA&lt;/code&gt; 包含全局变量，静态变量等。可读写（rw....）。&lt;/li&gt;
&lt;/ul&gt;

 &lt;ul&gt;
  &lt;li&gt;   &lt;code&gt;__LINKEDIT&lt;/code&gt; 包含了加载程序的   &lt;strong&gt;元数据&lt;/strong&gt;，比如函数的名称和地址。只读（r...）。&lt;/li&gt;
&lt;/ul&gt;
 &lt;h2&gt;1.2.dylib&lt;/h2&gt;
 &lt;p&gt;dylib 也是一种 Mach-O 格式的文件，后缀名为   &lt;code&gt;.dylib&lt;/code&gt; 的文件就是动态库（也叫动态链接库）。动态库是运行时加载的，可以被多个 App 的进程共用。&lt;/p&gt;
 &lt;p&gt;如果想知道 TestDemo 中依赖的所有动态库，可以通过下面的指令实现：&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;otool -L /TestDemo.app/TestDemo
&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;动态链接库分为  &lt;strong&gt;系统 dylib&lt;/strong&gt; 和  &lt;strong&gt;内嵌 dylib&lt;/strong&gt;（embed dylib，即开发者手动引入的动态库）。系统 dylib 有：&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;iOS 中用到的所有系统 framework，比如 UIKit、Foundation；&lt;/li&gt;
&lt;/ul&gt;

 &lt;ul&gt;
  &lt;li&gt;系统级别的 libSystem（如 libdispatch(GCD) 和 libsystem_blocks(Block)）；&lt;/li&gt;
&lt;/ul&gt;

 &lt;ul&gt;
  &lt;li&gt;加载 OC runtime 方法的 libobjc；&lt;/li&gt;
&lt;/ul&gt;
 &lt;h3&gt;1.2.1.dyld&lt;/h3&gt;
 &lt;p&gt;  &lt;a href="https://links.jianshu.com/go?to=https://opensource.apple.com/tarballs/dyld/"&gt;dyld&lt;/a&gt;（Dynamic Link Editor）：动态链接器，其本质也是 Mach-O 文件，一个专门用来加载 dylib 文件的库。 dyld 位于   &lt;code&gt;/usr/lib/dyld&lt;/code&gt;，可以在 mac 和越狱机中找到。dyld 会将 App 依赖的动态库和 App 文件加载到内存后执行。&lt;/p&gt;
 &lt;h3&gt;1.2.2.dyld shared cache&lt;/h3&gt;
 &lt;p&gt;dyld shared cache 就是动态库共享缓存。当需要加载的动态库非常多时，相互依赖的符号也更多了，为了节省解析处理符号的时间，OS X 和 iOS 上的动态链接器使用了共享缓存。OS X 的共享缓存位于   &lt;code&gt;/private/var/db/dyld/&lt;/code&gt;，iOS 的则在   &lt;code&gt;/System/Library/Caches/com.apple.dyld/&lt;/code&gt;。&lt;/p&gt;
 &lt;p&gt;当加载一个 Mach-O 文件时，dyld 首先会检查是否存在于共享缓存，存在就直接取出使用。每一个进程都会把这个共享缓存映射到了自己的地址空间中。这种方法大大优化了 OS X 和 iOS 上程序的启动时间。&lt;/p&gt;
 &lt;h3&gt;1.2.3.dyld shared cache&lt;/h3&gt;
 &lt;p&gt;dyld shared cache 就是动态库共享缓存。当需要加载的动态库非常多时，相互依赖的符号也更多了，为了节省解析处理符号的时间，OS X 和 iOS 上的动态链接器使用了共享缓存。OS X 的共享缓存位于   &lt;code&gt;/private/var/db/dyld/&lt;/code&gt;，iOS 的则在   &lt;code&gt;/System/Library/Caches/com.apple.dyld/&lt;/code&gt;。&lt;/p&gt;
 &lt;p&gt;当加载一个 Mach-O 文件时，dyld 首先会检查是否存在于共享缓存，存在就直接取出使用。每一个进程都会把这个共享缓存映射到了自己的地址空间中。这种方法大大优化了 OS X 和 iOS 上程序的启动时间。&lt;/p&gt;
 &lt;h3&gt;1.2.4.images&lt;/h3&gt;
 &lt;p&gt;images 在这里不是指图片，而是  &lt;strong&gt;镜像&lt;/strong&gt;。每个 App 都是以 images 为单位进行加载的。images 类型包括：&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;executable：应用的二进制可执行文件；&lt;/li&gt;
&lt;/ul&gt;

 &lt;ul&gt;
  &lt;li&gt;dylib：动态链接库；&lt;/li&gt;
&lt;/ul&gt;

 &lt;ul&gt;
  &lt;li&gt;bundle：资源文件，属于不能被链接的 dylib，只能在运行时通过    &lt;code&gt;dlopen()&lt;/code&gt; 加载。&lt;/li&gt;
&lt;/ul&gt;
 &lt;h3&gt;1.2.5.framework&lt;/h3&gt;
 &lt;p&gt;framework 可以是动态库，也是静态库，是一个包含 dylib、bundle 和头文件的文件夹。&lt;/p&gt;
 &lt;h1&gt;二、冷启动相关（首页为原生）&lt;/h1&gt;
 &lt;p&gt;当用户按下 home 键，iOS App 不会立刻被 kill，而是存活一段时间，这段时间里用户再打开 App，App 基本上不需要做什么，就能还原到退到后台前的状态。我们把 App 进程还在系统中，无需开启新进程的启动过程称为  &lt;strong&gt;热启动&lt;/strong&gt;。&lt;/p&gt;
 &lt;p&gt;而  &lt;strong&gt;冷启动&lt;/strong&gt;则是指 App 不在系统进程中，比如设备重启后，或是手动杀死 App 进程，又或是 App 长时间未打开过，用户再点击启动 App 的过程，这时需要创建一个新进程分配给 App。我们可以将冷启动看作一次完整的 App 启动过程，本文讨论的就是冷启动的优化。&lt;/p&gt;
 &lt;h2&gt;1.冷启动：&lt;/h2&gt;
 &lt;h3&gt;1.1冷启动的出处&lt;/h3&gt;
 &lt;p&gt;WWDC 2016 中首次出现了 App 启动优化的话题，其中提到：&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;App 启动最佳速度是400ms以内，因为从点击 App 图标启动，然后 Launch Screen 出现再消失的时间就是400ms；&lt;/li&gt;
&lt;/ul&gt;

 &lt;ul&gt;
  &lt;li&gt;App 启动最慢不得大于20s，否则进程会被系统杀死；（启动时间最好以 App 所支持的最低配置设备为准。）&lt;/li&gt;
&lt;/ul&gt;
 &lt;h4&gt;1.1.1关于冷启动的两种说法：&lt;/h4&gt;
 &lt;p&gt;说法一：&lt;/p&gt;
 &lt;p&gt;冷启动的整个过程是指从用户唤起 App 开始到 AppDelegate 中的   &lt;code&gt;didFinishLaunchingWithOptions&lt;/code&gt; 方法执行完毕为止，并以执行   &lt;code&gt;main()&lt;/code&gt; 函数的时机为分界点，分为   &lt;code&gt;pre-main&lt;/code&gt; 和   &lt;code&gt;main()&lt;/code&gt; 两个阶段。&lt;/p&gt;
 &lt;p&gt;说法二：&lt;/p&gt;
 &lt;p&gt;也有一种说法是将整个冷启动阶段以主 UI 框架的   &lt;code&gt;viewDidAppear&lt;/code&gt; 函数执行完毕才算结束。这两种说法都可以，前者的界定范围是 App 启动和初始化完毕，后者的界定范围是用户视角的启动完毕，也就是首屏已经被加载出来。&lt;/p&gt;
 &lt;blockquote&gt;
  &lt;p&gt;   &lt;strong&gt;注意&lt;/strong&gt;：这里很多文章都会把第二个阶段描述为    &lt;strong&gt;main 函数之后&lt;/strong&gt;，个人认为这种说法不是很好，容易让人误解。要知道 main 函数在 App 运行过程中是不会退出的，无论是 AppDelegate 中的    &lt;code&gt;didFinishLaunchingWithOptions&lt;/code&gt; 方法还是 ViewController 中的   &lt;code&gt;viewDidAppear&lt;/code&gt; 方法，都还是在 main 函数内部执行的。&lt;/p&gt;
&lt;/blockquote&gt;
 &lt;h3&gt;1.2.pre-main 阶段&lt;/h3&gt;
 &lt;p&gt;  &lt;code&gt;pre-main&lt;/code&gt; 阶段指的是从用户唤起 App 到   &lt;code&gt;main()&lt;/code&gt; 函数执行之前的过程。&lt;/p&gt;
 &lt;h4&gt;1.2.1查看阶段耗时（以xcode13为‘分水岭’）&lt;/h4&gt;
 &lt;h5&gt;1.2.1.1. Xcode13之前&lt;/h5&gt;
 &lt;p&gt;1.我们可以在 Xcode 中配置环境变量&lt;/p&gt;
 &lt;p&gt;Product -&amp;gt; Edit Scheme -&amp;gt; Run -&amp;gt; Arguments -&amp;gt;Environment Variables -&amp;gt; +&lt;/p&gt;
 &lt;p&gt;DYLD_PRINT_STATISTICS 设置为 1&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="image.png" src="https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/4f17001cf7524420a5f44ed682942e44~tplv-k3u1fbpfcp-watermark.image?"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="image.png" src="https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/b18220852a104f088ffe65a8b7ccf5e1~tplv-k3u1fbpfcp-watermark.image?"&gt;&lt;/img&gt;
这时在 iOS 10 以上系统中运行这个 Demo，  &lt;code&gt;pre-main&lt;/code&gt; 阶段的启动时间会在控制台中打印出来（备注：本人x-code 已经升级到13.3，无法打印出日志）&lt;/p&gt;
 &lt;p&gt;如果要更详细的信息，就设置   &lt;code&gt;DYLD_PRINT_STATISTICS_DETAILS&lt;/code&gt; 为 1。&lt;/p&gt;
 &lt;h5&gt;1.2.1.2在Xcode13之后上面的方法就失效了&lt;/h5&gt;
 &lt;p&gt;可以采用下面的方法&lt;/p&gt;
 &lt;p&gt;代码贴出来如下&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;#import &amp;lt;Foundation/Foundation.h&amp;gt;



NS_ASSUME_NONNULL_BEGIN



 @interface AppLaunchTime : NSObject



+ (void)mark;



 @end
&lt;/code&gt;&lt;/pre&gt;
 &lt;pre&gt;  &lt;code&gt;#import &amp;quot;AppLaunchTime.h&amp;quot;

#import &amp;lt;sys/sysctl.h&amp;gt;

#import &amp;lt;mach/mach.h&amp;gt;

 @implementation AppLaunchTime



double __t1; // 创建进程时间
double __t2; // before main
double __t3; // didfinsh

/// 获取进程创建时间

+ (CFAbsoluteTime)processStartTime 
{
  if (__t1 == 0) 
  {
    struct kinfo_proc procInfo;
    int pid = [[NSProcessInfo processInfo] processIdentifier];
    int cmd[4] = {CTL_KERN, KERN_PROC, KERN_PROC_PID, pid};
    size_t size = sizeof(procInfo);
    if (sysctl(cmd, sizeof(cmd)/sizeof(*cmd), &amp;amp;procInfo, &amp;amp;size, NULL, 0) == 0) {
      __t1 = procInfo.kp_proc.p_un.__p_starttime.tv_sec * 1000.0 + procInfo.kp_proc.p_un.__p_starttime.tv_usec / 1000.0;
    }
  }
  return __t1;
}

/// 开始记录：在DidFinish中调用
+ (void)mark 
{
  double __t1 = [AppLaunchTime processStartTime];
  dispatch_async(dispatch_get_main_queue(), ^{ // 确保didFihish代码执行后调用
    if (__t3 == 0) 
    {
      __t3 = CFAbsoluteTimeGetCurrent() + kCFAbsoluteTimeIntervalSince1970;
    }

    double pret = __t2 - __t1 / 1000;
    double didfinish = __t3 - __t2;
    double total = __t3 - __t1 / 1000;

    NSLog(@&amp;quot;----------App启动---------耗时:pre-main:%f&amp;quot;,pret);
    NSLog(@&amp;quot;----------App启动---------耗时:didfinish:%f&amp;quot;,didfinish);
    NSLog(@&amp;quot;----------App启动---------耗时:total:%f&amp;quot;,total);
  });
}
// 构造方法在main调用前调用

// 获取pre-main()阶段的结束时间点相对容易，可以直接取main()主函数的开始执行时间点.推荐使用__attribute__((constructor)) 构建器函数的被调用时间点作为pre-main()阶段结束时间点：__t2能最大程度实现解耦：

void static __attribute__ ((constructor)) before_main() 
{
  if (__t2 == 0) 
  {
    __t2 = CFAbsoluteTimeGetCurrent() + kCFAbsoluteTimeIntervalSince1970;
  }
}
&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;代用运行打印&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {

  [AppLaunchTime mark];

  return YES;

}
&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;日志打印&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;冷启动优化[3454:116391] ----------App启动---------耗时:pre-main:0.718716
冷启动优化[3454:116391] ----------App启动---------耗时:didfinish:0.028895
冷启动优化[3454:116391] ----------App启动---------耗时:total:0.747611
&lt;/code&gt;&lt;/pre&gt;
 &lt;h4&gt;1.2.2.启动过程分析与优化&lt;/h4&gt;
 &lt;h5&gt;1.2.2.1.总体启动过程分析&lt;/h5&gt;
 &lt;p&gt;启动一个应用时，系统会通过   &lt;code&gt;fork()&lt;/code&gt; 方法来新创建一个进程，然后执行镜像通过   &lt;code&gt;exec()&lt;/code&gt; 来替换为另一个可执行程序，然后执行如下操作：&lt;/p&gt;
 &lt;ol&gt;
  &lt;li&gt;把可执行文件加载到内存空间，从可执行文件中能够分析出 dyld 的路径；&lt;/li&gt;
  &lt;li&gt;把 dyld 加载到内存；&lt;/li&gt;
  &lt;li&gt;dyld 从可执行文件的依赖开始，递归加载所有的依赖动态链接库 dylib 并进行相应的初始化操作。&lt;/li&gt;
&lt;/ol&gt;
 &lt;p&gt;结合上面   &lt;code&gt;pre-main&lt;/code&gt; 打印的结果，我们可以大致了解整个启动过程如下图所示：&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="image.png" src="https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/37a7d3b1846a43d3947a8ce2c3cd9f41~tplv-k3u1fbpfcp-watermark.image?"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;h5&gt;1.2.2.2.Load Dylibs&lt;/h5&gt;
 &lt;p&gt;这一步，指的是  &lt;strong&gt;动态库加载&lt;/strong&gt;。在此阶段，dyld 会：&lt;/p&gt;
 &lt;ol&gt;
  &lt;li&gt;分析 App 依赖的所有 dylib；&lt;/li&gt;
  &lt;li&gt;找到 dylib 对应的 Mach-O 文件；&lt;/li&gt;
  &lt;li&gt;打开、读取这些 Mach-O 文件，并验证其有效性；&lt;/li&gt;
  &lt;li&gt;在系统内核中注册代码签名；&lt;/li&gt;
  &lt;li&gt;对 dylib 的每一个 segment 调用    &lt;code&gt;mmap()&lt;/code&gt;。&lt;/li&gt;
&lt;/ol&gt;
 &lt;p&gt;一般情况下，iOS App 需要加载 100-400 个 dylibs。这些动态库包括系统的，也包括开发者手动引入的。其中大部分 dylib 都是系统库，系统已经做了优化，因此开发者更应关心自己手动集成的内嵌 dylib，加载它们时性能开销较大。&lt;/p&gt;
 &lt;p&gt;App 中依赖的 dylib 越少越好，Apple 官方建议尽量将内嵌 dylib 的个数维持在6个以内。&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;优化方案&lt;/strong&gt;：&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;尽量不使用内嵌 dylib；&lt;/li&gt;
&lt;/ul&gt;

 &lt;ul&gt;
  &lt;li&gt;合并已有内嵌 dylib；&lt;/li&gt;
&lt;/ul&gt;

 &lt;ul&gt;
  &lt;li&gt;检查 framework 的    &lt;code&gt;optional&lt;/code&gt; 和    &lt;code&gt;required&lt;/code&gt; 设置，如果 framework 在当前的 App 支持的 iOS 系统版本中都存在，就设为    &lt;code&gt;required&lt;/code&gt;，因为设为    &lt;code&gt;optional&lt;/code&gt; 会有额外的检查；&lt;/li&gt;
&lt;/ul&gt;

 &lt;ul&gt;
  &lt;li&gt;使用静态库作为代替；（不过静态库会在编译期被打进可执行文件，造成可执行文件体积增大，两者各有利弊，开发者自行权衡。）&lt;/li&gt;
&lt;/ul&gt;

 &lt;ul&gt;
  &lt;li&gt;懒加载 dylib。（但使用    &lt;code&gt;dlopen()&lt;/code&gt; 对性能会产生影响，因为 App 启动时是原本是单线程运行，系统会取消加锁，但    &lt;code&gt;dlopen()&lt;/code&gt; 开启了多线程，系统不得不加锁，这样不仅会使性能降低，可能还会造成死锁及未知的后果，不是很推荐这种做法。）&lt;/li&gt;
&lt;/ul&gt;
 &lt;h5&gt;1.2.2.3.Rebase/Binding&lt;/h5&gt;
 &lt;p&gt;这一步，做的是  &lt;strong&gt;指针重定位&lt;/strong&gt;。&lt;/p&gt;
 &lt;p&gt;在 dylib 的加载过程中，系统为了安全考虑，引入了 ASLR（Address Space Layout Randomization）技术和代码签名。由于 ASLR 的存在，镜像会在新的随机地址（actual_address）上加载，和之前指针指向的地址（preferred_address）会有一个偏差（slide，slide=actual_address-preferred_address），因此 dyld 需要修正这个偏差，指向正确的地址。具体通过这两步实现：&lt;/p&gt;
 &lt;p&gt;第一步：  &lt;strong&gt;Rebase&lt;/strong&gt;，在 image 内部调整指针的指向。将 image 读入内存，并以 page 为单位进行加密验证，保证不会被篡改，性能消耗主要在 IO。&lt;/p&gt;
 &lt;p&gt;第二步：  &lt;strong&gt;Binding&lt;/strong&gt;，符号绑定。将指针指向 image 外部的内容。查询符号表，设置指向镜像外部的指针，性能消耗主要在 CPU 计算。&lt;/p&gt;
 &lt;p&gt;通过以下命令可以查看 rebase 和 bind 等信息：&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;xcrun dyldinfo -rebase -bind -lazy_bind TestDemo.app/TestDemo
&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;通过 LC_DYLD_INFO_ONLY 可以查看各种信息的偏移量和大小。如果想要更方便直观地查看，推荐使用   &lt;a href="https://links.jianshu.com/go?to=https://github.com/fiteen/fiteen.github.io/releases/tag/v0.1.2"&gt;MachOView&lt;/a&gt; 工具。&lt;/p&gt;
 &lt;p&gt;指针数量越少，指针修复的耗时也就越少。所以，优化该阶段的关键就是减少   &lt;code&gt;__DATA&lt;/code&gt; 段中的指针数量。&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;优化方案&lt;/strong&gt;：&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;减少 ObjC 类（class）、方法（selector）、分类（category）的数量，比如合并一些功能，删除无效的类、方法和分类等（可以借助 AppCode 的 Inspect Code 功能进行代码瘦身）；&lt;/li&gt;
&lt;/ul&gt;

 &lt;ul&gt;
  &lt;li&gt;减少 C++ 虚函数；（虚函数会创建 vtable，这也会在    &lt;code&gt;__DATA&lt;/code&gt; 段中创建结构。）&lt;/li&gt;
&lt;/ul&gt;

 &lt;ul&gt;
  &lt;li&gt;多用 Swift Structs。（因为 Swift Structs 是静态分发的，它的结构内部做了优化，符号数量更少。）&lt;/li&gt;
&lt;/ul&gt;
 &lt;h5&gt;1.2.2.4.ObjC Setup&lt;/h5&gt;
 &lt;p&gt;完成 Rebase 和 Bind 之后，通知 runtime 去做一些代码运行时需要做的事情：&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;dyld 会注册所有声明过的 ObjC 类；&lt;/li&gt;
&lt;/ul&gt;

 &lt;ul&gt;
  &lt;li&gt;将分类插入到类的方法列表中；&lt;/li&gt;
&lt;/ul&gt;

 &lt;ul&gt;
  &lt;li&gt;检查每个 selector 的唯一性。&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;  &lt;strong&gt;优化方案&lt;/strong&gt;：&lt;/p&gt;
 &lt;p&gt;Rebase/Binding 阶段优化好了，这一步的耗时也会相应减少。&lt;/p&gt;
 &lt;h5&gt;1.2.2.5.Initializers&lt;/h5&gt;
 &lt;p&gt;Rebase 和 Binding 属于静态调整（fix-up），修改的是   &lt;code&gt;__DATA&lt;/code&gt; 段中的内容，而这里则开始动态调整，往堆和栈中写入内容。具体工作有：&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;调用每个 Objc 类和分类中的    &lt;code&gt;+load&lt;/code&gt; 方法；&lt;/li&gt;
&lt;/ul&gt;

 &lt;ul&gt;
  &lt;li&gt;调用 C/C++ 中的构造器函数（用    &lt;code&gt;attribute((constructor))&lt;/code&gt; 修饰的函数）；&lt;/li&gt;
&lt;/ul&gt;

 &lt;ul&gt;
  &lt;li&gt;创建非基本类型的 C++ 静态全局变量。&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;  &lt;strong&gt;优化方案&lt;/strong&gt;：&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;尽量避免在类的    &lt;code&gt;+load&lt;/code&gt; 方法中初始化，可以推迟到    &lt;code&gt;+initiailize&lt;/code&gt; 中进行；（因为在一个    &lt;code&gt;+load&lt;/code&gt; 方法中进行运行时方法替换操作会带来 4ms 的消耗）&lt;/li&gt;
&lt;/ul&gt;

 &lt;ul&gt;
  &lt;li&gt;避免使用    &lt;code&gt;__atribute__((constructor))&lt;/code&gt; 将方法显式标记为初始化器，而是让初始化方法调用时再执行。比如用    &lt;code&gt;dispatch_once()&lt;/code&gt;、   &lt;code&gt;pthread_once()&lt;/code&gt; 或    &lt;code&gt;std::once()&lt;/code&gt;，相当于在第一次使用时才初始化，推迟了一部分工作耗时。：&lt;/li&gt;
&lt;/ul&gt;

 &lt;ul&gt;
  &lt;li&gt;减少非基本类型的 C++ 静态全局变量的个数。（因为这类全局变量通常是类或者结构体，如果在构造函数中有繁重的工作，就会拖慢启动速度）&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;总结一下   &lt;code&gt;pre-main&lt;/code&gt; 阶段可行的优化方案：&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;重新梳理架构，减少不必要的内置动态库数量&lt;/li&gt;
&lt;/ul&gt;

 &lt;ul&gt;
  &lt;li&gt;进行代码瘦身，合并或删除无效的ObjC类、Category、方法、C++ 静态全局变量等&lt;/li&gt;
&lt;/ul&gt;

 &lt;ul&gt;
  &lt;li&gt;将不必须在    &lt;code&gt;+load&lt;/code&gt; 方法中执行的任务延迟到    &lt;code&gt;+initialize&lt;/code&gt; 中&lt;/li&gt;
&lt;/ul&gt;

 &lt;ul&gt;
  &lt;li&gt;减少 C++ 虚函数&lt;/li&gt;
&lt;/ul&gt;
 &lt;h3&gt;1.3.main() 阶段&lt;/h3&gt;
 &lt;p&gt;对于   &lt;code&gt;main()&lt;/code&gt; 阶段，主要测量的就是从   &lt;code&gt;main()&lt;/code&gt; 函数开始执行到   &lt;code&gt;didFinishLaunchingWithOptions&lt;/code&gt; 方法执行结束的耗时。&lt;/p&gt;
 &lt;h4&gt;1.3.1.查看阶段耗时&lt;/h4&gt;
 &lt;p&gt;这里介绍两种查看   &lt;code&gt;main()&lt;/code&gt; 阶段耗时的方法。&lt;/p&gt;
 &lt;h5&gt;  &lt;strong&gt;方法一：手动插入代码，进行耗时计算。&lt;/strong&gt;&lt;/h5&gt;
 &lt;p&gt;第一步：在 main() 函数里用变量 MainStartTime 记录当前时间&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;#import &amp;lt;UIKit/UIKit.h&amp;gt;

#import &amp;quot;AppDelegate.h&amp;quot;



CFAbsoluteTime MainStartTime;



int main(int argc, char * argv[]) 

{

  NSString * appDelegateClassName;

  MainStartTime = CFAbsoluteTimeGetCurrent();

  

  @autoreleasepool {

    appDelegateClassName = NSStringFromClass([AppDelegate class]);

  }

  return UIApplicationMain(argc, argv, nil, appDelegateClassName);

}
&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;第二步：在 AppDelegate.m 文件中用 extern 声明全局变量&lt;/p&gt;
 &lt;p&gt;第三步：在 didFinishLaunchingWithOptions 方法结束前，再获取一下当前时间，与 MainStartTime 的差值就是 main() 函数阶段的耗时&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;#import &amp;quot;AppDelegate.h&amp;quot;

#import &amp;quot;AppLaunchTime.h&amp;quot;

extern CFAbsoluteTime MainStartTime;

 @interface AppDelegate ()

 @end



 @implementation AppDelegate



- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {

  

  [AppLaunchTime mark];

   

  double mainLaunchTime = (CFAbsoluteTimeGetCurrent() - MainStartTime);

  NSLog(@&amp;quot;main() 阶段耗时：%.2fms&amp;quot;, mainLaunchTime * 1000);

   

  return YES;

}



//日志：

 //冷启动优化[3616:126842] main() 阶段耗时：21.92ms
&lt;/code&gt;&lt;/pre&gt;
 &lt;h5&gt;  &lt;strong&gt;方法二：借助 Instruments 的 Time Profiler 工具查看耗时。&lt;/strong&gt;&lt;/h5&gt;
 &lt;p&gt;打开方式为：  &lt;code&gt;Xcode → Open Developer Tool → Instruments → Time Profiler&lt;/code&gt;。&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="image.png" src="https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/26c3c6ab4cc94eb6bce682ea47ccc9c5~tplv-k3u1fbpfcp-watermark.image?"&gt;&lt;/img&gt;
操作步骤：&lt;/p&gt;
 &lt;ol&gt;
  &lt;li&gt;配置 Scheme。点击    &lt;code&gt;Edit Scheme&lt;/code&gt; 找到    &lt;code&gt;Profile&lt;/code&gt; 下的    &lt;code&gt;Build Configuration&lt;/code&gt;，设置为    &lt;code&gt;Debug&lt;/code&gt;。&lt;/li&gt;
&lt;/ol&gt;
 &lt;p&gt;  &lt;img alt="image.png" src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/9cdd8a1c64b343dfb6a9737cf4c13ff5~tplv-k3u1fbpfcp-watermark.image?"&gt;&lt;/img&gt;
2.  配置 PROJECT。点击 PROJECT，在   &lt;code&gt;Build Settings&lt;/code&gt; 中找到   &lt;code&gt;Build Options&lt;/code&gt; 选项里的   &lt;code&gt;Debug Information Format&lt;/code&gt;，把   &lt;code&gt;Debug&lt;/code&gt; 对应的值改为   &lt;code&gt;DWARF with dSYM File&lt;/code&gt;。&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="image.png" src="https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/ae57b986b7704c92a7c80e2e1c5f5f7a~tplv-k3u1fbpfcp-watermark.image?"&gt;&lt;/img&gt;
3.  启动 Time Profiler，点击左上角红色圆形按钮开始检测，然后就可以看到执行代码的完整路径和对应的耗时。&lt;/p&gt;
 &lt;p&gt;为了方面查看应用程序中实际代码的执行耗时和代码路径实际所在的位置，可以勾选上   &lt;code&gt;Call Tree&lt;/code&gt; 中的   &lt;code&gt;Separate Thread&lt;/code&gt; 和   &lt;code&gt;Hide System Libraries&lt;/code&gt;。&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="image.png" src="https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/a00d684ccf564f57868c0a88a4bf6493~tplv-k3u1fbpfcp-watermark.image?"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;h4&gt;1.3.2.启动优化&lt;/h4&gt;
 &lt;p&gt;  &lt;code&gt;main()&lt;/code&gt; 被调用之后，  &lt;code&gt;didFinishLaunchingWithOptions&lt;/code&gt; 阶段，App 会进行必要的初始化操作，而   &lt;code&gt;viewDidAppear&lt;/code&gt;执行结束之前则是做了首页内容的加载和显示。&lt;/p&gt;
 &lt;p&gt;关于   &lt;strong&gt;App 的初始化&lt;/strong&gt;，除了统计、日志这种须要在 App 一启动就配置的事件，有一些配置也可以考虑延迟加载。如果你在   &lt;code&gt;didFinishLaunchingWithOptions&lt;/code&gt; 中同时也涉及到了  &lt;strong&gt;首屏的加载&lt;/strong&gt;，那么可以考虑从这些角度优化：&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;用纯代码的方式，而不是 xib/Storyboard，来加载首页视图&lt;/li&gt;
&lt;/ul&gt;

 &lt;ul&gt;
  &lt;li&gt;延迟暂时不需要的二方/三方库加载；&lt;/li&gt;
&lt;/ul&gt;

 &lt;ul&gt;
  &lt;li&gt;延迟执行部分业务逻辑和 UI 配置；&lt;/li&gt;
&lt;/ul&gt;

 &lt;ul&gt;
  &lt;li&gt;延迟加载/懒加载部分视图；&lt;/li&gt;
&lt;/ul&gt;

 &lt;ul&gt;
  &lt;li&gt;避免首屏加载时大量的本地/网络数据读取；&lt;/li&gt;
&lt;/ul&gt;

 &lt;ul&gt;
  &lt;li&gt;在 release 包中移除 NSLog 打印；&lt;/li&gt;
&lt;/ul&gt;

 &lt;ul&gt;
  &lt;li&gt;在视觉可接受的范围内，压缩页面中的图片大小；&lt;/li&gt;
&lt;/ul&gt;

 &lt;ul&gt;
  &lt;li&gt;……&lt;/li&gt;
&lt;/ul&gt;
 &lt;h1&gt;三.首页为H5 页面优化&lt;/h1&gt;
 &lt;p&gt;可参考 VasSonic 的原理
Sonic是腾讯团队研发的一个轻量级的高性能的Hybrid框架，专注于提升页面首屏加载速度&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;
   &lt;p&gt;终端耗时&lt;/p&gt;
   &lt;ul&gt;
    &lt;li&gt;webView 预加载：在 App 启动时期预先加载了一次 webView，通过创建空的 webView，预先启动 Web 线程，完成一些全局性的初始化工作，对二次创建 webView 能有数百毫秒的提升。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;

 &lt;ul&gt;
  &lt;li&gt;
   &lt;p&gt;页面耗时（静态页面）&lt;/p&gt;
   &lt;ul&gt;
    &lt;li&gt;静态直出：服务端拉取数据后通过 Node.js 进行渲染，生成包含首屏数据的 HTML 文件，发布到 CDN 上，webView 直接从 CDN 上获取；&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;

 &lt;ul&gt;
  &lt;li&gt;离线预推：使用离线包。&lt;/li&gt;
&lt;/ul&gt;

 &lt;ul&gt;
  &lt;li&gt;
   &lt;p&gt;页面耗时（经常需要动态更新的页面）&lt;/p&gt;
   &lt;ul&gt;
    &lt;li&gt;并行加载：WebView 的打开和资源的请求并行；&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;

 &lt;ul&gt;
  &lt;li&gt;动态缓存：动态页面缓存在客户端，用户下次打开的时候先打开缓存页面，然后再刷新；&lt;/li&gt;
&lt;/ul&gt;

 &lt;ul&gt;
  &lt;li&gt;动静分离：将页面分为静态模板和动态数据，根据不同的启动场景进行不同的刷新方案；&lt;/li&gt;
&lt;/ul&gt;

 &lt;ul&gt;
  &lt;li&gt;预加载：提前拉取需要的增量更新数据。&lt;/li&gt;
&lt;/ul&gt;
 &lt;h1&gt;四.总结&lt;/h1&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 />
      <guid isPermaLink="true">https://itindex.net/detail/62167-ios-%E5%86%B7%E5%90%AF%E5%8A%A8-%E4%BC%98%E5%8C%96</guid>
      <pubDate>Fri, 25 Mar 2022 06:30:56 CST</pubDate>
    </item>
    <item>
      <title>Android/iOS判断是否使用代理或VPN</title>
      <link>https://itindex.net/detail/62008-android-ios-%E4%BB%A3%E7%90%86</link>
      <description>&lt;p&gt;针对APP的黑产，我们提到部分用户会通过改变IP来绕过风控策略。更改IP比较方便的方法是使用代理IP或VPN。&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" height="286" src="https://www.biaodianfu.com/wp-content/uploads/2022/01/proxy-vpn.png" width="800"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;在检测APP安全性是需要对是否使用代码和VPN做判断。以下为整理的一些代码供参考。&lt;/p&gt;
 &lt;h2&gt;Android判断是否使用代理IP&lt;/h2&gt;
 &lt;pre&gt;private boolean isWifiProxy(Context context) {
    final boolean IS_ICS_OR_LATER = Build.VERSION.SDK_INT &amp;gt;= Build.VERSION_CODES.ICE_CREAM_SANDWICH;
    String proxyAddress;
    int proxyPort;

    if (IS_ICS_OR_LATER) {
        proxyAddress = System.getProperty(&amp;quot;http.proxyHost&amp;quot;);
        String portStr = System.getProperty(&amp;quot;http.proxyPort&amp;quot;);
        proxyPort = Integer.parseInt((portStr != null ? portStr : &amp;quot;-1&amp;quot;));
    } else {
        proxyAddress = android.net.Proxy.getHost(context);
        proxyPort = android.net.Proxy.getPort(context);
    }
    return (!TextUtils.isEmpty(proxyAddress)) &amp;amp;&amp;amp; (proxyPort != -1);
}
&lt;/pre&gt;
 &lt;h2&gt;Android判断是否使用VPN&lt;/h2&gt;
 &lt;pre&gt;boolean checkVPN(ConnectivityManager connMgr) {
    //don&amp;apos;t know why always returns null:
    NetworkInfo networkInfo = connMgr.getNetworkInfo(ConnectivityManager.TYPE_VPN);

    boolean isVpnConn = networkInfo == null ? false : networkInfo.isConnected();
    return isVpnConn;
}
&lt;/pre&gt;
 &lt;h2&gt;iOS判断是否使用代理IP&lt;/h2&gt;
 &lt;pre&gt;#import &amp;quot;CETCProxyStatus.h&amp;quot;

@implementation CETCProxyStatus

+ (BOOL)getProxyStatus {
    NSDictionary *proxySettings = NSMakeCollectable([(NSDictionary *)CFNetworkCopySystemProxySettings() autorelease]);

    NSArray *proxies = NSMakeCollectable([(NSArray *)CFNetworkCopyProxiesForURL((CFURLRef)[NSURL URLWithString:@&amp;quot;http://www.baidu.com&amp;quot;], (CFDictionaryRef)proxySettings) autorelease]);
    NSDictionary *settings = [proxies objectAtIndex:0];

    NSLog(@&amp;quot;host=%@&amp;quot;, [settings objectForKey:(NSString *)kCFProxyHostNameKey]);
    NSLog(@&amp;quot;port=%@&amp;quot;, [settings objectForKey:(NSString *)kCFProxyPortNumberKey]);
    NSLog(@&amp;quot;type=%@&amp;quot;, [settings objectForKey:(NSString *)kCFProxyTypeKey]);

    if ([[settings objectForKey:(NSString *)kCFProxyTypeKey] isEqualToString:@&amp;quot;kCFProxyTypeNone&amp;quot;]) {
        //没有设置代理
        return NO;
    } else {
        //设置代理了
        return YES;
    }
}
&lt;/pre&gt;
 &lt;h2&gt;iOS判断是否使用VPN&lt;/h2&gt;
 &lt;pre&gt;- (BOOL)isVPNOn
{
   BOOL flag = NO;
   NSString *version = [UIDevice currentDevice].systemVersion;
   // need two ways to judge this.
   if (version.doubleValue &amp;gt;= 9.0)
   {
       NSDictionary *dict = CFBridgingRelease(CFNetworkCopySystemProxySettings());
       NSArray *keys = [dict[@&amp;quot;__SCOPED__&amp;quot;] allKeys];
       for (NSString *key in keys) {
           if ([key rangeOfString:@&amp;quot;tap&amp;quot;].location != NSNotFound ||
               [key rangeOfString:@&amp;quot;tun&amp;quot;].location != NSNotFound ||
               [key rangeOfString:@&amp;quot;ipsec&amp;quot;].location != NSNotFound ||
               [key rangeOfString:@&amp;quot;ppp&amp;quot;].location != NSNotFound){
               flag = YES;
               break;
           }
       }
   }
   else
   {
       struct ifaddrs *interfaces = NULL;
       struct ifaddrs *temp_addr = NULL;
       int success = 0;
       
       // retrieve the current interfaces - returns 0 on success
       success = getifaddrs(&amp;amp;interfaces);
       if (success == 0)
       {
           // Loop through linked list of interfaces
           temp_addr = interfaces;
           while (temp_addr != NULL)
           {
               NSString *string = [NSString stringWithFormat:@&amp;quot;%s&amp;quot; , temp_addr-&amp;gt;ifa_name];
               if ([string rangeOfString:@&amp;quot;tap&amp;quot;].location != NSNotFound ||
                   [string rangeOfString:@&amp;quot;tun&amp;quot;].location != NSNotFound ||
                   [string rangeOfString:@&amp;quot;ipsec&amp;quot;].location != NSNotFound ||
                   [string rangeOfString:@&amp;quot;ppp&amp;quot;].location != NSNotFound)
               {
                   flag = YES;
                   break;
               }
               temp_addr = temp_addr-&amp;gt;ifa_next;
           }
       }
       
       // Free memory
       freeifaddrs(interfaces);
   }


   return flag;
}
&lt;/pre&gt;
 &lt;div&gt;
  &lt;h3&gt;相关文章:&lt;/h3&gt;  &lt;ol&gt;
   &lt;li&gt;    &lt;a href="https://www.biaodianfu.com/django-tutorial.html" rel="bookmark" title="Django&amp;#23454;&amp;#25112;&amp;#65306;&amp;#25237;&amp;#31080;&amp;#31995;&amp;#32479;&amp;#25645;&amp;#24314;"&gt;Django实战：投票系统搭建 &lt;/a&gt;&lt;/li&gt;
   &lt;li&gt;    &lt;a href="https://www.biaodianfu.com/google-protocol-buffers.html" rel="bookmark" title="Google Protocol Buffers&amp;#20351;&amp;#29992;&amp;#25351;&amp;#21335;"&gt;Google Protocol Buffers使用指南 &lt;/a&gt;&lt;/li&gt;
   &lt;li&gt;    &lt;a href="https://www.biaodianfu.com/ios-jailbreak-check.html" rel="bookmark" title="iOS&amp;#26159;&amp;#21542;&amp;#36234;&amp;#29425;&amp;#21028;&amp;#26029;&amp;#26041;&amp;#27861;"&gt;iOS是否越狱判断方法 &lt;/a&gt;&lt;/li&gt;
&lt;/ol&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>数据 术→技巧 研发 Android APP</category>
      <guid isPermaLink="true">https://itindex.net/detail/62008-android-ios-%E4%BB%A3%E7%90%86</guid>
      <pubDate>Fri, 07 Jan 2022 18:57:06 CST</pubDate>
    </item>
    <item>
      <title>安卓邀请追踪技术和iOS渠道追踪和来源统计的几种原理 - neveraway1993的博客 - CSDN博客</title>
      <link>https://itindex.net/detail/59439-%E5%AE%89%E5%8D%93-%E6%8A%80%E6%9C%AF-ios</link>
      <description>&lt;div&gt;    &lt;p&gt;在开始之前，我们先来看看安卓的渠道统计。Google官方的应用商店Google Play在国内一直是无法使用的状态，所以国内的安卓App分发，都是依托数十个不同的应用市场或发行渠道，如百度、360、腾讯等互联网企业以及小米、华为、魅族等手机生产商。对于安卓App的渠道追踪，主要是围绕上面这些大的渠道来进行，并且这些渠道自己一般也会提供非常详尽和周全的数据分析给应用开发者。&lt;/p&gt;    &lt;p&gt;iOS的发行渠道则与安卓有很大的不同，除了少数越狱的机器之外，大部分用户的App都是从App Store下载的。iOS的“渠道”其实通常是指那些在其它App或者网页内部，提供到AppStore的链接的页面。因此，在iOS中追踪发行渠道，主要是追踪进入App Store相关页面的渠道信息。&lt;/p&gt;    &lt;p&gt;但iOS的渠道追踪面临着一道无法逾越的鸿沟。正因为iOS的渠道分发都有跳转到App Store这一步，而Apple本身是不会提供太多信息给开发者，所以，对于整个流程的三个步骤：在某个渠道点击下载链接并跳转到App Store ---&amp;gt; App Store内下载App ---&amp;gt;用户激活App，这其中的第二步，开发者无法获取相关信息，所以，没有办法精确地追踪一个用户在这三个步骤中的完整轨迹，也即没有办法精确地衡量渠道的具体推广效果。同时，安卓渠道效果分析中，常见的对于不同渠道打不同包的方案，在iOS分发时也是不可行的。&lt;/p&gt;    &lt;p&gt;对于iOS的困境，该如何解决呢？现在市场上大概有以下三种方式：&lt;/p&gt;    &lt;p&gt;通过IDFA进行追踪：这个方案一般用在App里面打开下载链接这种推广方式。基本的方案是，推广渠道的App（例如微信），会详细记录哪个IDFA点击了待推广App（例如聚美）的链接（或是在微信中嵌入SDK去记录），而聚美本身，也会记录具体的哪个IDFA激活了聚美App，两者都将记录下来的IDFA上传至指定的服务器，进行对比，即可确定下载来源。在用户不重置系统，不还原广告的情况下，这种方式精准度比较高。&lt;/p&gt;    &lt;p&gt;通过模糊特征匹配的方式来进行追踪：点击下载链接，会跳转到appstore页面，这个过程会触发一个服务端的请求，服务器来记录这次点击的设备信息，包括ip地址、机型等。同时，被推广App这边，也可以记录用户激活App时机器的一些基本信息，并上传至服务器。结合下载和激活的时间差，再结合设备的IP地址和机型等信息，大概可以模糊地识别出同一个用户先点击了下载链接，再激活了App，从而确定下载渠道。这种方式的精确度较低。&lt;/p&gt;    &lt;p&gt;通过SFSafariViewController进行追踪：iOS 9中新增的SFSafariViewController，这个类的API允许在app内打开一个safari浏览器，而不是一个app内部的webview。这个app内的safari和外面系统的safari是同一个，共享同一个沙盒，可以操作同一个Cookie，也就是说它可以跨App与Safari实现共享Cookie。&lt;/p&gt;    &lt;p&gt; 基于SFSafariViewController控件，当用户在App中通过它打开渠道页面时，我们可以将渠道信息写入Cookie中，并设置生效时间。当用户安装并激活App后，再次使用SFSafariViewController上报激活信息，同时将Cookie中的渠道信息上传，通过匹配，便可确定下载来源。由于渠道信息保存在设备本地，因此匹配是100%准确的。&lt;/p&gt;    &lt;p&gt;但是基于SFSafariViewController这种方式也有一定的弊端。首先，这个方案只能支持iOS9及以上版本的设备，大约占全部苹果设备的85%左右，覆盖了绝大部分用户，已经具有很好的分析价值了。但对于剩余的15%的用户，该方案无法满足。此外，对于目前业界主流的一些推广渠道，如微信、朋友圈，它们尚未在App中使用SFSafariViewController控件访问网页，因此这部分渠道也无法使用精准匹配的方案。&lt;/p&gt;    &lt;p&gt;市面上的做法有的是上述三种方式单一出现，有的是两两组合，总之不管是通过哪种方式，这都是我们想象出来的间接的方式，只能说是尽量的去接近准确，但不能做到100%准确。在今年的4月15日，苹果低调发布了一项重大功能，开始提供渠道来源的数据，就以往而言，苹果仅开放有限的数据统计，很容易让从业人员在工作遇到窘境，我们该如何统计到来源渠道。而此次推出的用户来源统计，对App推广人员来说，无疑是一项重大举措。&lt;/p&gt;    &lt;p&gt;关于苹果推出的这项功能，我会在另一篇文章中      &lt;a href="http://blog.csdn.net/neveraway1993/article/details/72461438" rel="nofollow"&gt;http://blog.csdn.net/neveraway1993/article/details/72461438&lt;/a&gt;，详细介绍，欢迎大家前来探讨！&lt;/p&gt;  &lt;p&gt;   &lt;br /&gt;&lt;/p&gt;  &lt;p&gt;每次分享时生成一个带参数的url，只要想办法在app首次启动时恢复这个参数即可，恢复的方法大致有以下几种：&lt;/p&gt;  &lt;p&gt;１．根据ip与user-agent中的设备信息做匹配，访问url时服务器记录ip与设备信息，app首次启动再去请求服务器匹配一次&lt;/p&gt;  &lt;p&gt;２．ios9开始，可以利用cookie来跟踪，使用safari访问url时写入cookie，app首次启动时使用SFSafariViewController控件访问同一个域名，这个控件会将之前写入的cookie一并带给服务器&lt;/p&gt;  &lt;p&gt;３．更暴力的方法，下载时将信息直接写入安装包中；android下生成一个新的apk；ios下可利用企业证书签名，通过ad-hoc分发的方式，实时生成一个新的ipa文件，不过自ios8开始，苹果对企业证书有了更严格的限制，用户体验不好&lt;/p&gt;  &lt;p&gt;4.还见过更奇葩的android实现方法，下载apk时将参数放到apk本地文件名中(通过http头部，Content-Disposition:attachment;filename=xxxx)，安装后启动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;br /&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 />
      <guid isPermaLink="true">https://itindex.net/detail/59439-%E5%AE%89%E5%8D%93-%E6%8A%80%E6%9C%AF-ios</guid>
      <pubDate>Thu, 11 Apr 2019 14:58:32 CST</pubDate>
    </item>
    <item>
      <title>[分享创造] 可能是 iOS 上最好用的电视直播软件</title>
      <link>https://itindex.net/detail/58868-%E5%88%86%E4%BA%AB-%E5%88%9B%E9%80%A0-ios</link>
      <description>&lt;p&gt;因为平时喜欢看电视，在 app store 上又找不到适合的，就想着自己造个轮子，临时抱佛脚学了三天 iOS 开发，写了个 app  &lt;br /&gt;功能：  &lt;br /&gt;1.自己添加管理 m3u8 直播源，这个没什么好说的，适合动手能力强的  &lt;br /&gt;2.订阅列表，订阅后，只要负责维护列表的大神列表更新，用户的列表就会自动更新  &lt;br /&gt;目前我自己维护了 4 个列表，加起来频道大概六七十个  &lt;br /&gt;  &lt;br /&gt;  &lt;br /&gt;放几张截图  &lt;br /&gt;  &lt;br /&gt;  &lt;img alt="V2er" src="https://i.loli.net/2018/10/13/5bc116212f76c.png"&gt;&lt;/img&gt;  &lt;br /&gt;  &lt;img alt="V2er" src="https://i.loli.net/2018/10/13/5bc11628ac930.png"&gt;&lt;/img&gt;  &lt;br /&gt;  &lt;img alt="V2er" src="https://i.loli.net/2018/10/13/5bc1162fd71d4.png"&gt;&lt;/img&gt;  &lt;br /&gt;  &lt;img alt="V2er" src="https://i.loli.net/2018/10/13/5bc11635605f5.png"&gt;&lt;/img&gt;  &lt;br /&gt;  &lt;img alt="V2er" src="https://i.loli.net/2018/10/13/5bc1163aa39fa.png"&gt;&lt;/img&gt;  &lt;br /&gt;  &lt;br /&gt;tf： https://testflight.top/q/Y3yqIj  &lt;br /&gt;tg 群   &lt;a href="http://t.me/conchplayer" rel="nofollow"&gt;http://t.me/conchplayer&lt;/a&gt;  &lt;br /&gt;  &lt;br /&gt;  &lt;br /&gt;另外请教大神，提交审核 24 小时就被拒，理由  &lt;br /&gt;Guideline 4.2 - Design - Minimum Functionality  &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 />
      <guid isPermaLink="true">https://itindex.net/detail/58868-%E5%88%86%E4%BA%AB-%E5%88%9B%E9%80%A0-ios</guid>
      <pubDate>Sat, 13 Oct 2018 05:50:29 CST</pubDate>
    </item>
    <item>
      <title>流行 iOS Apps 被发现将用户位置数据发送给第三方数据分析公司</title>
      <link>https://itindex.net/detail/58716-%E6%B5%81%E8%A1%8C-ios-apps</link>
      <description>GuardianApp 的安全研究人员 &lt;a href="https://guardianapp.com/ios-app-location-report-sep2018.html" target="_blank"&gt;发现&lt;/a&gt;，数十款流行 iOS Apps 被发现会将用户位置数据发送给第三方数据分析公司。这些应用都需要位置数据才能正常工作，它们是气象、交友或健身类应用，而与第三方公司分享数据可以为免费应用产生收入。这些应用收集的数据包括低功耗蓝牙信标数据，GPS 经维度数据，Wi-Fi SSID 和 BSSID，部分应用还收集加速计，广告标识符，电池状态和蜂窝网络信息等。研究人员利用流量监视识别出 24 款收集和分享位置数据的应用，其中包括 ASKfm、C25K 5K Trainer、Classifieds 2.0 Marketplace、MyRadar NOAA Weather Radar、NOAA Weather Radar、Perfect365、Photobucket、QuakeFeed Earthquake Alerts，等等。 &lt;p&gt;  &lt;img height="120" src="https://img.solidot.org/0/446/liiLIZF8Uh6yM.jpg"&gt;&lt;/img&gt;&lt;/p&gt; &lt;div&gt;
  &lt;a href="http://feeds.feedburner.com/~ff/solidot?a=uJxizhJA4H8:KCym8d1mEoY:yIl2AUoC8zA"&gt;   &lt;img border="0" src="http://feeds.feedburner.com/~ff/solidot?d=yIl2AUoC8zA"&gt;&lt;/img&gt;&lt;/a&gt;   &lt;a href="http://feeds.feedburner.com/~ff/solidot?a=uJxizhJA4H8:KCym8d1mEoY:7Q72WNTAKBA"&gt;   &lt;img border="0" src="http://feeds.feedburner.com/~ff/solidot?d=7Q72WNTAKBA"&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 />
      <guid isPermaLink="true">https://itindex.net/detail/58716-%E6%B5%81%E8%A1%8C-ios-apps</guid>
      <pubDate>Sat, 08 Sep 2018 20:04:43 CST</pubDate>
    </item>
    <item>
      <title>谷歌高管：安卓现在和iOS一样安全 甚至更好</title>
      <link>https://itindex.net/detail/58148-%E8%B0%B7%E6%AD%8C-%E5%AE%89%E5%8D%93-ios</link>
      <description>&lt;div&gt;  &lt;p&gt;   &lt;em&gt;&lt;/em&gt;Android安全主管：“虽然存在两个问题，但安卓安全性已经不会比iOS差，只会更好！”&lt;/p&gt;  &lt;p&gt;iOS 和 Android 谁更安全？相信即使是 Android 用户都会把自己的选票投给前者，但现在的 Android 安全主管并不这么想，他声称现在谷歌的操作系统安全性已经不会比 iOS 差了，只会更好！&lt;/p&gt;  &lt;p&gt;   &lt;img src="https://img0.tuicool.com/Y7NjMvm.jpg!web"&gt;&lt;/img&gt;&lt;/p&gt;  &lt;p&gt;在2017年 Android 安全评估中，谷歌宣布自己在安全性方面已经领先竞争对手，因为在2017年中，Android 系统引入了很多安全保障措施，很多措施甚至引领了行业的发展。&lt;/p&gt;  &lt;p&gt;Android 安全主管 David Kleidermacher 明确地告诉 CNBC，现在的 Android 安全性已经能够媲美 iOS 了。不过他言语中还是非常克制的：“不指名道姓地说，现在 Android 系统已经和竞争对手一样安全了，甚至更好。”&lt;/p&gt;  &lt;p&gt;不过在 CNBC 的采访中，Kleidermacher 还是承认了 Android 存在的一个问题：当有人发现 Android 的系统漏洞时，谷歌必须把软件更新发送给众多 Android 手机厂商，比如三星和华为，然后敦促他们尽快提供安全更新，但即使如此，这一过程往往需要很长时间，或者有些厂商压根就不会听谷歌的话，安全更新？不存在的！&lt;/p&gt;  &lt;p&gt;更重要的是，Android 的开放特性给了用户100%的自由选择权，他们能够从 Google Play 之外的地方下载 App，所以 Android 用户往往会无意下载很多恶意软件。&lt;/p&gt;  &lt;p&gt;   &lt;img src="https://img2.tuicool.com/fqEJV3v.jpg!web"&gt;&lt;/img&gt;&lt;/p&gt;  &lt;p&gt;但是 Kleidermacher 表示，Android 平台的“ Google Play Protect ”能够有效解决这一问题，它不仅能够对 Google Play 的应用进行审查，并且还会对其他平台下载的 App 进行检测，如果发现有害 App，会自动删除它们。所以即使 Android 存在前面那些缺陷，也是可以轻易堵上的。只可惜，这项功能对于我们来说完全起不了作用。&lt;/p&gt;  &lt;p&gt;甚至谷歌的报告表明，Android 的开源方法可能比 Apple 的闭源代码更安全。因为作为全球性开源项目，Android 拥有一个开发者社区，大家会协助发现深层漏洞并很快进行处理，和闭源项目相比，这显然更加高效。&lt;/p&gt;  &lt;h4&gt;欢迎关注威锋网官方微信：威锋网（weiphone_2007) 汇聚最新Apple动态，精选最热科技资讯。&lt;/h4&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>tuicool</category>
      <guid isPermaLink="true">https://itindex.net/detail/58148-%E8%B0%B7%E6%AD%8C-%E5%AE%89%E5%8D%93-ios</guid>
      <pubDate>Sat, 17 Mar 2018 00:00:00 CST</pubDate>
    </item>
    <item>
      <title>树莓派打造的家庭环境监控 | iOS开发和七七八八</title>
      <link>https://itindex.net/detail/57518-%E6%A0%91%E8%8E%93%E6%B4%BE-%E5%AE%B6%E5%BA%AD-%E7%8E%AF%E5%A2%83</link>
      <description>&lt;div class="post-body"&gt;&lt;span itemprop="articleBody"&gt;&lt;p&gt;最近空气质量又有下降的趋势，想想手头的RPI2B，干脆拿来改造成一个环境监控系统吧，放在客厅随时可以看到，情况不对马上开净化器😊。传感器部分都是以前就买了的，平时拿来偶尔玩一下。主要是三个：一个空气质量检测的激光传感器，一个温湿度的DHT22，一个凑数的带光感的数模转换。屏幕是一块5寸的HDMI破电阻屏，用来搭建本体的是两包白色塑料小积木，所有东西均购自淘宝，包括树莓派物料成本大约500+。说起来这个成本很高了，主要是树莓派本身加一个屏幕，光用来做这个有点浪费。好在我本来也是要放在那边当小服务器用，跑个定时脚本，偶尔看个kernel什么的。&lt;/p&gt;&lt;h2 id="u786C_u4EF6_u90E8_u5206"&gt;&lt;a class="headerlink" href="#u786C_u4EF6_u90E8_u5206" title="" data-original-title="硬件部分"&gt;&lt;/a&gt;硬件部分&lt;/h2&gt;&lt;a id="more"&gt;&lt;/a&gt;&lt;p&gt;因为传感器都是做好了的，树莓派自己也引出了两排Pin脚，硬件其实就是连几根线而已。只有一个问题需要考虑就是传感器都是上电即工作的，如果一直通电的话，过不了多久估计就烧了。所以我决定用GPIO当作开关来控制sensor的电源，只在需要获得数据时打开。这样一来那两个只需要3.3V的sensor还好，直接用GPIO给电即可，空气质量那个传感器需要5V的就麻烦了。还有LCD屏幕，也是要求5V，并且本身是USB供电，只好把原有的拨动开关拆了，小改电路，使用一个接入的5V电源来解决。&lt;br&gt;&lt;br&gt;最终的硬件连线图如下：&lt;/p&gt;&lt;span class="source"&gt;&lt;blockquote&gt;&lt;p&gt;你看到的是非授权版本！爬虫凶猛，请尊重知识产权！&lt;/p&gt;&lt;p&gt;转载请注明出处：http://conanwhf.github.io/2017/09/16/EnvMonitor/&lt;/p&gt;&lt;p&gt;访问原文「&lt;a href="http://conanwhf.github.io/2017/09/16/EnvMonitor/"&gt;树莓派打造的家庭环境监控&lt;/a&gt;」获取最佳阅读体验并参与讨论&lt;/p&gt;&lt;/blockquote&gt;&lt;/span&gt;&lt;br&gt;&lt;br&gt;&lt;img alt="" src="http://whf.d.pr/hhTgSG+"&gt;&lt;br&gt;&lt;br&gt;图中的空气质量sensor和屏幕的电源都外接了一个场效应管，用来决定5V电源Pin的通断，由一个GPIO接入来控制。空气质量的数据是UART传输，PCF8591的接口是I2C，而DHT11则直接使用GPIO的一个Pin来传输数据（总感觉有点不靠谱的），另外添加了三个LED灯来实时反应空气质量的等级。&lt;p&gt;&lt;/p&gt;&lt;h2 id="u8F6F_u4EF6_u90E8_u5206"&gt;&lt;a class="headerlink" href="#u8F6F_u4EF6_u90E8_u5206" title="" data-original-title="软件部分"&gt;&lt;/a&gt;软件部分&lt;/h2&gt;&lt;p&gt;软件语言使用的是Python3，一个不在乎速度的上上之选。我虽然是那种一边百度一边写python的超级新手，但用过以后就再也不想换回繁琐的C了，更别说在树莓派上有那么多现成的库可以给你用。&lt;/p&gt;&lt;h3 id="u6574_u4F53_u7ED3_u6784"&gt;&lt;a class="headerlink" href="#u6574_u4F53_u7ED3_u6784" title="" data-original-title="整体结构"&gt;&lt;/a&gt;整体结构&lt;/h3&gt;&lt;p&gt;软件方面只有三个主要的线程，两个负责拿数据，一个负责UI：&lt;br&gt;&lt;br&gt;&lt;img alt="" src="http://whf.d.pr/QctyUV+"&gt;&lt;br&gt;&lt;br&gt;程序跑起来后除了UI，会自己开两个线程进行循环，在循环中获取系统、sensor的状态并刷新在UI上。运行的模式有两种：实时和日常。实时模式主要是给需要迅速获得状态的时候使用，例如刚刚打开净化器想看看质量是否在持续改善，或者是网络有问题的时候看看能否ping通google；而日常模式则是无人关注的日常行为。主界面有一个按钮可以切换这两种模式，线程内部会根据切换后的状态判断自己应该睡眠多久再进行下一轮状态更新。&lt;br&gt;&lt;br&gt;在日常模式中，系统每个循环会获取所有传感器的数据并更新上报至Yeelink，以及根据空气质量调整相对应的LED灯。而在实时模式，每个循环内则会另外获取一些操作系统状态来显示，并刷新ping服务器（Google）的结果。除了日常和实时两个模式中获取的状态，还有一个单独的线程用来刷新开机时间和公网IP。这两个数据对实时的要求不高，更新频率也很低，就直接固定为30分钟一次了。&lt;/p&gt;&lt;h3 id="u4F20_u611F_u5668_u6570_u636E_u83B7_u53D6"&gt;&lt;a class="headerlink" href="#u4F20_u611F_u5668_u6570_u636E_u83B7_u53D6" title="" data-original-title="传感器数据获取"&gt;&lt;/a&gt;传感器数据获取&lt;/h3&gt;&lt;p&gt;传感器方面，以前都是用过的，底层的库也很成熟，所以代码量很小，主要是封装一下变成类库。要说重新写的部分，就是自己写了个Power的类，免得分散在GPIO的相关代码里面，很难看。以前用的DHT11，这次换成DHT22，懒得再自己写程序了，直接用的官方库。&lt;/p&gt;&lt;h3 id="UI_u8BBE_u8BA1"&gt;&lt;a class="headerlink" href="#UI_u8BBE_u8BA1" title="" data-original-title="UI设计"&gt;&lt;/a&gt;UI设计&lt;/h3&gt;&lt;p&gt;UI部分我用的Python自带的tkinter，虽然是头一次用，但毕竟是Python，随便看看文档就搞定了，API简单得不得了。当然，主要是因为我的UI很简单：显示label，button，监听点击和关闭事件就是所有需求了。而这些对于任何一个语言的UI来说，用法基本都是通用的。唯一有点不同的是它对于控件的排列方式，使用了个叫&lt;strong&gt;grid&lt;/strong&gt;的方法，实际上是将整个界面划分为N*M的小方格，再通过对控件所占用方格的位置、大小、对齐属性，来决定控件的具体位置。&lt;br&gt;&lt;br&gt;另外一个小问题是在调试时发生的，我发现在SSH的状态下，无法从命令行启动UI，报错的具体内容我忘了，大概意思是找不到具体设备。网上搜了一下有人遇到同样的问题，年代久远，且无人回答。后来看了下API的文档，发现窗口的初始化函数Tk是可以带参数的：&lt;/p&gt;&lt;ol&gt;&lt;li&gt;在有UI的桌面环境下打开终端，运行&lt;code&gt;export&lt;/code&gt;&lt;/li&gt;&lt;li&gt;在输出结果中找到&lt;code&gt;DISPLAY=XXXX&lt;/code&gt;的部分，比如说我的是&lt;code&gt;DISPLAY=":0.0"&lt;/code&gt;&lt;/li&gt;&lt;li&gt;在代码中添加参数：&lt;code&gt;tk.Tk(screenName=":0.0")&lt;/code&gt;&lt;br&gt;&lt;br&gt;问题解决。&lt;/li&gt;&lt;/ol&gt;&lt;h3 id="u6570_u636E_u4E0A_u4F20"&gt;&lt;a class="headerlink" href="#u6570_u636E_u4E0A_u4F20" title="" data-original-title="数据上传"&gt;&lt;/a&gt;数据上传&lt;/h3&gt;&lt;p&gt;Yeelink是一个对个人用户免费的物联网服务，你可以将自己的数据上传，并在Web端查看。虽然时常抽风，但既然是免费的我还能抱怨什么呢？感谢天感谢地，感谢Yeelink让我和家人可以随时随地用手机查看家里的环境数据😜。系统每次获取到sensor的数据后会自动上报给Yeelink，让我奇怪的是官方好像只有接口文档而没有给出范例代码，网上的也大都有点过时了，害得我想偷个懒都不成。虽然GitHub上有，但我还是占用一些篇幅放出这部份代码，以造福想用搜索引擎偷懒的小朋友们吧：&lt;/p&gt;&lt;pre&gt;&lt;code&gt;#!/usr/bin/python3&#xD;
import time&#xD;
import json&#xD;
import requests&#xD;
&#xD;
#yeelink api配置&#xD;
apiUrl='http://api.yeelink.net/v1.1/device/%s/sensor/%s/datapoints'&#xD;
apiKey='123456' #请填入专属的api key&#xD;
apiHeaders={'U-ApiKey':apiKey,'content-type': 'application/json'}&#xD;
deviceID = 1232312&#xD;
sensorID = {'pm25':123, 'pm10': 456,'temperature':789, 'humidity':111}&#xD;
&#xD;
#上传sensor数据到yeelink&#xD;
def upload_to_yeelink(name, value):&#xD;
    url= apiUrl % (deviceID,sensorID[name])&#xD;
    strftime=time.strftime("%Y-%m-%dT%H:%M:%S")&#xD;
    #print(url, strftime)&#xD;
    data={"timestamp":strftime , "value": value}&#xD;
    try:&#xD;
        res=requests.post(url,headers=apiHeaders,json=data, timeout=3.0)&#xD;
        if res.status_code!=200:&#xD;
            print("status_code:",res.status_code)&#xD;
        else:&#xD;
            pass&#xD;
    except:&#xD;
        print("report to yeelink fail")&#xD;
&#xD;
if __name__ == "__main__":&#xD;
    upload_to_yeelink('pm25', 100)&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;从哪里获取deviceID、sensorID我就不多说了，注册和新建的时候自然都知道了。值得一提的是其中的timeout参数（3.0那个），是因为我发现即使网络没问题，有时候也会无限卡死在等response的地方，不知道是不是服务器会抽风。&lt;br&gt;&lt;br&gt;使用这段代码需要先装requests库，原因？自然是为了偷懒。不然原生的http api处理什么json, error，timeout要多写好几行呢！&lt;/p&gt;&lt;h3 id="u5176_u4ED6"&gt;&lt;a class="headerlink" href="#u5176_u4ED6" title="" data-original-title="其他"&gt;&lt;/a&gt;其他&lt;/h3&gt;&lt;p&gt;软件方面的其他内容还有三个小地方值得注意：&lt;/p&gt;&lt;ol&gt;&lt;li&gt;桌面图标。要想完全脱离键盘用UI来操作，一个桌面的打开快捷方式必不可少。原来我以为直接放一个main的软链接就行了，结果人家直接不显示，原来需要固定格式的桌面图标文件才行。我以chrome的图标为基准修改了一个，还能自己定义ico，挺好玩。&lt;/li&gt;&lt;li&gt;循环睡眠的代码。之前我想当然地在日常模式中直接写了sleep(600)，结果发现这个bug太蠢了：在UI上切换入实时模式时根本无法实时响应变化，非要等10分钟后睡醒了才知道。后来就改成了无论那种模式都是每次睡一秒，醒来看看状态是否变为实时模式，以决定是否要继续睡。&lt;/li&gt;&lt;li&gt;循环时间。在循环中，每个cycle的时间并不真的是1秒或者10分钟，那个只是睡眠的时间；整个cycle还包括了获取数据的时间、上报数据的时间、以及其他开销。&lt;/li&gt;&lt;/ol&gt;&lt;h2 id="u78B0_u5230_u7684_u95EE_u9898"&gt;&lt;a class="headerlink" href="#u78B0_u5230_u7684_u95EE_u9898" title="" data-original-title="碰到的问题"&gt;&lt;/a&gt;碰到的问题&lt;/h2&gt;&lt;p&gt;改造系统的过程中碰到了不少问题，记得起来的写一下。&lt;/p&gt;&lt;h3 id="u7535_u538B_u95EE_u9898"&gt;&lt;a class="headerlink" href="#u7535_u538B_u95EE_u9898" title="" data-original-title="电压问题"&gt;&lt;/a&gt;电压问题&lt;/h3&gt;&lt;p&gt;我的树莓派上除了接这些sensor和屏幕，还有一个蓝牙、一个USB WIFI、一个摄像头，可想而知耗电量是相当大的了。如果只提供5V1A的供电，系统则会在屏幕右上角显示一个黄色的闪电符号，提醒你电量不足。但即使是电量不足，程序也都是能运行的，只是……会影响sensor的读数。&lt;br&gt;&lt;br&gt;刚开始放到电视柜上的时候，发现空气质量的读数突然变得巨大，完全不正常，而拿去书房debug又降下来很多（依然不正常），百思不得其解，直到把屏幕拿掉读数完全正常，才发现是给电的问题。最后我用了两个5V2A的USB供电（一个专供显示屏），才算解决了这个问题。&lt;/p&gt;&lt;h3 id="DHT22_u7684_u5B98_u65B9_u5E93"&gt;&lt;a class="headerlink" href="#DHT22_u7684_u5B98_u65B9_u5E93" title="" data-original-title="DHT22的官方库"&gt;&lt;/a&gt;DHT22的官方库&lt;/h3&gt;&lt;p&gt;说起来虽然DHT系列的sensor本身质量不怎么样，公司还不错，能提供python的库直接用，而且同时支持DHT22和DHT11。DHT11之前我是自己写的，拿一个GPIO Pin来发命令、收数据，该说这想法很👍呢还是很😫呢？其实用C还好，用python就总觉的太不……专业。DHT11的数据特别是湿度实在是太不准了，差了十万八千里，有个Pin脚好像也有点问题，于是打算换个DHT22试试。结果嘛，虽然比原来准了一点但依然有25%-40%的误差🙄，并且这个官方给的库坑得我不轻。&lt;br&gt;&lt;br&gt;首先是无响应问题。在硬件都正常的情况下，API很偶尔会无响应，估计是因为timming的问题miss掉了某个状态之类的。其实timeout是有的，但是那时间长得令人发指，直接可以当作死循环了。这个问题我也懒得去搞什么花样来解决了，死等就死等吧，别真死就行。&lt;br&gt;&lt;br&gt;然后是返回值的问题。返回值一般是float的，比如35.4这类，坑的是如果它是35.0，就会直接给你个int的35，害得我用.1f输出就死了，需要自己强制转换下类型；这还不够奇葩，奇葩的是如果有什么问题拿不到数据，它直接给你个NULL，害得我在转换类型时又死了😳……心累！&lt;/p&gt;&lt;h3 id="u6563_u70ED_u95EE_u9898"&gt;&lt;a class="headerlink" href="#u6563_u70ED_u95EE_u9898" title="" data-original-title="散热问题"&gt;&lt;/a&gt;散热问题&lt;/h3&gt;&lt;p&gt;除了电压，散热也是会影响sensor工作的。而最直接的影响，就是温度检测了。机器跑了两天后，我突然发现上报的温度太高，比普通温度计测量的结果高差不多4度。而同样的，当我拿去书房debug，一切又恢复了正常😓……这次我没那么傻了，估计是散热问题，之前做的积木壳，只留出一面开口用来插数据线和通风，确实散热太差，CPU长期保持在59℃也是挺吓人的。于是我将外壳加高了两层，让里面的各种线和元器件不要那么拥挤，又在侧面加了个小风扇，果然解决问题，CPU降到了45℃，温度测量也正常了。&lt;br&gt;&lt;br&gt;值得一提的是这个风扇是当初买rpi亚克力壳子送的，接上之后持续发出我无法忍受的高频噪音，我这才想起来为什么当初被我扔到角落里了😌。想来想去，采取了折中的手段：将原本需要5V的风扇电源接到3.3V上，风扇转速降低，但是一点都不响了。CPU这次大概在48℃左右还可以接受，温度的sensor也读数正常。&lt;/p&gt;&lt;h2 id="u6700_u7EC8_u6548_u679C"&gt;&lt;a class="headerlink" href="#u6700_u7EC8_u6548_u679C" title="" data-original-title="最终效果"&gt;&lt;/a&gt;最终效果&lt;/h2&gt;&lt;p&gt;整个项目中，组装其实是最大的挑战：用那个小积木拼东西手太疼了啊！拼了我整整一天，手指头都脱皮了，才搞定整个造型。&lt;br&gt;&lt;br&gt;这是没有盖上屏幕的样子：&lt;br&gt;&lt;br&gt;&lt;img alt="" src="http://whf.d.pr/135Aov+"&gt;&lt;br&gt;&lt;br&gt;以及最终成品：&lt;br&gt;&lt;br&gt;&lt;img alt="" src="http://whf.d.pr/VaydLR+"&gt;&lt;br&gt;&lt;br&gt;项目源码已上传至GitHub：&lt;a href="https://github.com/conanwhf/Rpi-envMonitor" rel="external" target="_blank" title="" data-original-title="Rpi-envMonitor"&gt;Rpi-envMonitor&lt;/a&gt;，欢迎Star！&lt;/p&gt;&lt;/span&gt;&lt;/div&gt;&#xD;
    &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 />
      <guid isPermaLink="true">https://itindex.net/detail/57518-%E6%A0%91%E8%8E%93%E6%B4%BE-%E5%AE%B6%E5%BA%AD-%E7%8E%AF%E5%A2%83</guid>
      <pubDate>Thu, 28 Sep 2017 18:04:05 CST</pubDate>
    </item>
    <item>
      <title>从简书iOS客户端，来谈谈Hybrid方案细节设计 - 简书</title>
      <link>https://itindex.net/detail/57328-ios-%E5%AE%A2%E6%88%B7%E7%AB%AF-hybrid</link>
      <description>&lt;div class="show-content" data-note-content=""&gt;&lt;blockquote&gt;&lt;p&gt;作为一位 iOS 开发人员，你应该已经敏感地发现，自己的工作涉及内容已经不止于 Native 的部分，因为 Hybrid App 和 ReactNative 等技术方案已经不仅仅是概念，越来越多的公司开始着手自己的 Hybrid 方案以及 ReactNative 本地化工作。&lt;/p&gt;&lt;/blockquote&gt;&lt;h3&gt;一、引言&lt;/h3&gt;&lt;p&gt;介绍相关概念的优秀文章已经有许多，方案的实现原理你也应该已经或多或少有了一些理解。不了解也没有关系，在这篇文章里，我将用简书 iOS 客户端的有关特性，来探索一下 Hybrid 方案的技术细节。&lt;strong&gt;文章的目的是抛砖引玉&lt;/strong&gt;，用一个具体的项目，大家很熟悉的简书客户端，来帮助大家认识 Hybrid 方案，&lt;strong&gt;然后亲自实现它&lt;/strong&gt;。&lt;/p&gt;&lt;p&gt;&lt;strong&gt;从现在开始，不再着眼于某一个 feature ，你需要站在一个客户端架构师的角度来看待问题。&lt;/strong&gt;&lt;/p&gt;&lt;h3&gt;二、我们用到的简书客户端特性&lt;/h3&gt;&lt;h5&gt;1.界面构成分析&lt;/h5&gt;&lt;ul&gt;&lt;li&gt;&lt;p&gt;&lt;/p&gt;&lt;p&gt;&lt;/p&gt;本文的主角是简书 iOS 客户端的文章展示页面，这是我的一篇文章的展示页面：&lt;div class="image-package"&gt;&lt;img alt="文章展示页面" data-original-src="http://upload-images.jianshu.io/upload_images/3587644-78730e3580b67f7f.PNG?imageMogr2/auto-orient/strip" src="//upload-images.jianshu.io/upload_images/3587644-78730e3580b67f7f.PNG?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240"&gt;&lt;br&gt;&lt;br&gt;&lt;div class="image-caption"&gt;文章展示页面&lt;/div&gt;&lt;/div&gt;&lt;/li&gt;&lt;li&gt;如你所见，文章内容的展示是使用&lt;code&gt;webview&lt;/code&gt;控件，具体是&lt;code&gt;UIWebview&lt;/code&gt;还是&lt;code&gt;WKWebview&lt;/code&gt;按下不表，这不是本文的关键。在我的demo中，我使用了&lt;code&gt;UIWebview&lt;/code&gt;。&lt;/li&gt;&lt;li&gt;在简书&lt;code&gt;发现&lt;/code&gt;tab栏的内容顶部，还有一个热门内容推荐的轮播图。与它类似是一些app内的活动推介轮播图，以及广告页面，它们的详情页内容展示多使用webview。在简书中，这个轮播图对应的下一级页面也是文章展示页面，特性基本一致。&lt;br&gt;&lt;br&gt;&lt;div class="image-package"&gt;&lt;img alt="热门内容推荐轮播图" data-original-src="http://upload-images.jianshu.io/upload_images/3587644-96dad601faee90da.PNG?imageMogr2/auto-orient/strip" src="//upload-images.jianshu.io/upload_images/3587644-96dad601faee90da.PNG?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240"&gt;&lt;br&gt;&lt;br&gt;&lt;div class="image-caption"&gt;热门内容推荐轮播图&lt;/div&gt;&lt;/div&gt;&lt;/li&gt;&lt;li&gt;在 webview 的基础上，添加了符合浏览器用户习惯的导航栏按钮。包括左侧的&lt;code&gt;返回&lt;/code&gt;和&lt;code&gt;关闭&lt;/code&gt;按钮。以及右侧的功能列表按钮。&lt;/li&gt;&lt;li&gt;页面底部，是一个工具栏，提供了四个常用的操作。注意这里的评论按钮，它是我们下文的一个谈论点。&lt;/li&gt;&lt;/ul&gt;&lt;h5&gt;2.界面特性分析&lt;/h5&gt;&lt;ul&gt;&lt;li&gt;&lt;p&gt;一般各家客户端的内容页，都会有一些适于自己功能点的设计。简书也不例外。比如，在文章内容区域点击作者的头像（它本身也是网页的一部分，暂且理解为对应一个链接），跳转到了作者的个人主页，注意，容易发现它是一个客户端的原生页面，也就是一个VC。&lt;/p&gt;&lt;br&gt;&lt;br&gt;&lt;div class="image-package"&gt;&lt;img alt="作者个人主页" data-original-src="http://upload-images.jianshu.io/upload_images/3587644-752968a1024c89a4.PNG?imageMogr2/auto-orient/strip" src="//upload-images.jianshu.io/upload_images/3587644-752968a1024c89a4.PNG?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240"&gt;&lt;br&gt;&lt;br&gt;&lt;div class="image-caption"&gt;作者个人主页&lt;/div&gt;&lt;/div&gt;&lt;/li&gt;&lt;li&gt;点击底部工具栏中的&lt;code&gt;评论&lt;/code&gt;按钮（原生组件），页面（web页面）会滑动到评论区域，如图&lt;br&gt;&lt;br&gt;&lt;div class="image-package"&gt;&lt;img alt="点击评论按钮，页面滑动" data-original-src="http://upload-images.jianshu.io/upload_images/3587644-74772b4605444f7e.PNG?imageMogr2/auto-orient/strip" src="//upload-images.jianshu.io/upload_images/3587644-74772b4605444f7e.PNG?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240"&gt;&lt;br&gt;&lt;br&gt;&lt;div class="image-caption"&gt;点击评论按钮，页面滑动&lt;/div&gt;&lt;/div&gt;&lt;/li&gt;&lt;li&gt;对一篇文章写下自己的评论（使用了原生组件），评论列表（网页内容）进行更新。&lt;/li&gt;&lt;li&gt;简书对于展示内容作了内外站的区分。据我自己的简要测试，来自简书域名&lt;code&gt;www.jianshu.com&lt;/code&gt;下的内容，在加载过程中，是没有进度条的，用户体验非常接近原生页面。而第三方的内容，则在加载过程中会出现一般浏览器中常见的加载进度条，如图：&lt;br&gt;&lt;br&gt;&lt;div class="image-package"&gt;&lt;img alt="第三方内容的加载进度条" data-original-src="http://upload-images.jianshu.io/upload_images/3587644-86acad0561b68cce.PNG?imageMogr2/auto-orient/strip" src="//upload-images.jianshu.io/upload_images/3587644-86acad0561b68cce.PNG?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240"&gt;&lt;br&gt;&lt;br&gt;&lt;div class="image-caption"&gt;第三方内容的加载进度条&lt;/div&gt;&lt;/div&gt;&lt;/li&gt;&lt;li&gt;对于简书域名下的内容，不会出现叉号的&lt;code&gt;关闭&lt;/code&gt;按钮，这也是为了营造接近原生页面的用户体验，让用户不会察觉到这是一个 web 界面。而第三方内容，则会出现符合浏览器使用习惯的&lt;code&gt;关闭&lt;/code&gt;按钮，如上图。&lt;/li&gt;&lt;/ul&gt;&lt;h3&gt;三、我们需要的储备知识&lt;/h3&gt;&lt;h5&gt;1.Hybrid相关&lt;/h5&gt;&lt;ul&gt;&lt;li&gt;在Hybrid架构中，原生界面和web页面需要频繁地沟通，并且是双向的沟通。原生代码可以构建&lt;code&gt;JavaScript&lt;/code&gt;语句，交由webview进行执行，从而在web页面上实现需要的效果。而在web页面的js文件中，也可以调用原生的&lt;code&gt;Objective-C&lt;/code&gt;方法，从而执行一些原生方法才能完成的操作。与此相关的库有&lt;a href="https://github.com/marcuswestin/WebViewJavascriptBridge" target="_blank"&gt;WebViewJavascriptBridge&lt;/a&gt;以及&lt;code&gt;JavaScriptCore&lt;/code&gt;，有需要的同学可以自行了解。&lt;/li&gt;&lt;/ul&gt;&lt;h5&gt;2.UIWebview的相关特性&lt;/h5&gt;&lt;ul&gt;&lt;li&gt;UIWebviewDelegate&lt;br&gt;&lt;br&gt;webview的代理方法大家想必非常熟悉，我们可以在页面加载前、开始加载时、加载完成时以及失败时进行需要的操作。这里我们需要用到的是这一条代理方法：&lt;br&gt;&lt;br&gt;&lt;code&gt;- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType;&lt;/code&gt;&lt;br&gt;&lt;br&gt;webview根据它的返回结果来决定是否进行加载。&lt;/li&gt;&lt;li&gt;执行JS语句的方法：&lt;br&gt;&lt;br&gt;&lt;code&gt;- (nullable NSString *)stringByEvaluatingJavaScriptFromString:(NSString *)script;&lt;/code&gt;&lt;br&gt;&lt;br&gt;我们可以自行构建一条JS语句，通过这个方法交由webview执行&lt;/li&gt;&lt;li&gt;goback相关&lt;br&gt;&lt;br&gt;&lt;code&gt;UIWebview&lt;/code&gt;拥有布尔类型的&lt;code&gt;canGoBack&lt;/code&gt;、&lt;code&gt;loading&lt;/code&gt;等属性，通过监测它们的值我们可以知道当前页面是否可以进行回退，以及页面是否正在加载。&lt;br&gt;&lt;br&gt;与之对应，拥有&lt;code&gt;- (void)goBack;&lt;/code&gt;等方法，调用之页面会进行返回，就像我们在浏览器中常见的那样。&lt;/li&gt;&lt;/ul&gt;&lt;h3&gt;四、相关特性的模仿实现&lt;/h3&gt;&lt;p&gt;&lt;strong&gt;对于上文中提到的相关特性，我出写了一个demo，对它们进行了简要的模仿实现。当然简书官方的实现会考虑到方方面面，而我的demo仅是从Hybrid架构的思想出发，盼能够抛砖引玉。&lt;/strong&gt;&lt;br&gt;&lt;br&gt;这是demo中对该页面的模仿实现：&lt;br&gt;&lt;br&gt;&lt;/p&gt;&lt;div class="image-package"&gt;&lt;img alt="demo页面" data-original-src="http://upload-images.jianshu.io/upload_images/3587644-6f0cded455343a6e.PNG?imageMogr2/auto-orient/strip" src="//upload-images.jianshu.io/upload_images/3587644-6f0cded455343a6e.PNG?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240"&gt;&lt;br&gt;&lt;br&gt;&lt;div class="image-caption"&gt;demo页面&lt;/div&gt;&lt;/div&gt;&lt;h5&gt;1.页面初始化&lt;/h5&gt;&lt;p&gt;在demo中，使用一条web页面的URL来初始化VC：&lt;code&gt;- (instancetype)initWithURL:(NSURL *)URL;&lt;/code&gt;这条URL对应文章的链接。&lt;br&gt;&lt;br&gt;顶部导航栏和底部工具栏都是系统原生的&lt;code&gt;UINavigationBar&lt;/code&gt;和&lt;code&gt;UIToolBar&lt;/code&gt;，按钮素材使用阿里巴巴的iconfont字体。&lt;/p&gt;&lt;h5&gt;2.点击作者头像进入个人主页&lt;/h5&gt;&lt;p&gt;关于这个特性的实现，如果按照 Hybrid 架构的思想，属于 Web 页面调用原生方法，进入一个原生的VC。点击头像，JS脚本执行相关代码，调用原生方法暴露出来的接口，执行原生方法。&lt;br&gt;&lt;br&gt;我在这里用一种简要的方法实现：原生代码利用之前提到的代理方法，在用户点击头像后，拦截该URL，分析URL为头像部分，直接执行原生方法跳转到个人主页VC。&lt;br&gt;&lt;br&gt;通过分析简书文章页面的网页源代码，我发现用户头像对应的&lt;code&gt;URL&lt;/code&gt;中的&lt;code&gt;Query&lt;/code&gt;部分，有一个参数为&lt;code&gt;utm_medium=note-author-link&lt;/code&gt;。据此，在&lt;code&gt;- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType&lt;/code&gt;代理方法中加以判断，若是头像链接，则跳转到个人主页VC。下面是相关代码：&lt;/p&gt;&lt;pre&gt;&lt;code&gt;NSURL *destinationURL = request.URL;&#xD;
    NSString *URLQuery = destinationURL.query;&#xD;
//    简书点击文章中头像时跳转至原生页面。此处利用头像链接中的一个参数作判断&#xD;
    if ([URLQuery containsString:@"utm_medium=note-author-link"])&#xD;
    {&#xD;
        NSLog(@"我跳转到个人主页啦");&#xD;
        AvatorViewController *avatorVC = [[AvatorViewController alloc] init];&#xD;
        [self.navigationController pushViewController:avatorVC animated:YES];&#xD;
        return NO;&#xD;
    }&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;最后返回NO是因为若是头像链接，该web页面是不需要做跳转操作的。&lt;/p&gt;&lt;blockquote&gt;&lt;p&gt;这里顺便讲一个小tips：如果想要在Mac端查看移动端的网页源代码，那么你只需要在Safari中输入该页面，并且在&lt;code&gt;开发&lt;/code&gt;选项下的&lt;code&gt;用户代理&lt;/code&gt;中，选择iOS系统下的Safari作为代理，这时再使用源代码查看，看到的就是移动端的网页源代码了。&lt;/p&gt;&lt;/blockquote&gt;&lt;h5&gt;3.点击评论按钮，页面滑动到评论区域&lt;/h5&gt;&lt;p&gt;这个特性的实现方式和上面类似，点击评论按钮，原生代码构建一条JS语句，交由&lt;code&gt;- (nullable NSString *)stringByEvaluatingJavaScriptFromString:(NSString *)script;&lt;/code&gt;方法进行执行，由web页面执行滑动操作。代码如下：&lt;/p&gt;&lt;pre&gt;&lt;code&gt;- (void)scrollToCommentField&#xD;
{&#xD;
    [self stringByEvaluatingJavaScriptFromString:@"scrollTo(0,20500)"];&#xD;
}&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;这里的JS语句非常简单，由于笔者的前端知识还有所欠缺，没有想到可以精确滑动的评论区域的JS语句，所以简要实现，点到为止。&lt;/p&gt;&lt;h5&gt;4.原生组件写评论，web页面更新&lt;/h5&gt;&lt;p&gt;这里首先需要贴一下文章页面的网页源代码：&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&amp;lt;!-- 评论列表 --&amp;gt;&#xD;
  &amp;lt;div data-vcomp="comments-list" data-lazy="3"&amp;gt;&#xD;
    &amp;lt;script type="application/json"&amp;gt;&#xD;
      {"likedNote":true,"commentable":true,"publicCommentsCount":3,"noteId":2491941,"likesCount":43}&#xD;
    &amp;lt;/script&amp;gt;&#xD;
  &amp;lt;/div&amp;gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;可以看到，页面的评论内容是异步加载的。所以这个功能的实现，我&lt;br&gt;&lt;br&gt;认为比较合理的逻辑是原生组件向服务器提交一条新的评论，收到成功回调之后，原生组件和web页面进行交互，执行更新并加载评论列表的JS代码，从而看到自己发的新评论。&lt;/p&gt;&lt;h5&gt;5.内外站页面的区分&lt;/h5&gt;&lt;p&gt;这里和点击头像的实现方法类似，通过拦截链接的URL，区分内部链接和第三方链接，从而在开始加载的时候采用不同的加载界面，或者对于第三方链接单独开启一个第三方VC。&lt;br&gt;&lt;br&gt;demo中关于第三方链接的关闭按钮的显示逻辑，做出了相应的处理。&lt;/p&gt;&lt;h3&gt;五、demo中的不足&lt;/h3&gt;&lt;p&gt;看到这里大家应该就会发现，对于提到Hybrid我们就会想到的Bridge、Router等模块，我并没有做明显的限定。这样也是为了方便大家用一种更接近以往原生代码编写的思维，来理解Hybrid模式。&lt;br&gt;&lt;br&gt;同时，demo中较多涉及了原生代码对web页面做出的沟通操作。而没有JS代码对原生代码的调用，这是因为一来站在一个简书客户端的用户和iOS开发的角度，对于JS端执行的操作，有些力不能及，这本是和你共同工作的前端伙伴的任务，二来对于一篇帮助大家入门Hybrid的文章来说，从这个单方面的交互来入手，管中窥豹，已是足够。&lt;/p&gt;&lt;h3&gt;六、一些感悟&lt;/h3&gt;&lt;p&gt;其实，写了这么多，我觉得收获到一些感悟是最重要的，&lt;strong&gt;下面的要讲的，可能是我觉得更为重要的思想性的东西。&lt;/strong&gt;&lt;/p&gt;&lt;h5&gt;1.未来的趋势之一，便是大前端团队进行客户端开发。&lt;/h5&gt;&lt;ul&gt;&lt;li&gt;看到这里你发现，如果你们的团队想要采用Hybrid模式进行产品的开发，光靠iOS或者是安卓的客户端工程师是不可能完成的。在客户端框架的开发过程中，需要和前端的工程师沟通具体的技术细节。比如怎样设计接口能够更好地兼顾客户端和前端特点，对于某个问题，如何能把握全局而不是单单从客户端的角度来看待。这些可能是普通的iOS开发工程师和大牛的差距所在之一。&lt;/li&gt;&lt;li&gt;越来越多的客户端工程师招聘要求中，出现了熟悉前端语言的要求。如果你能在精通客户端开发之余，对前端语言也游刃有余，那么在接下来的发展趋势中，就会有更多的可能性。所以，请开始你的前端学习吧~。&lt;/li&gt;&lt;/ul&gt;&lt;h5&gt;2.在Hybrid模式下，如何进行产品技术方案的取舍&lt;/h5&gt;&lt;ul&gt;&lt;li&gt;如前文所见，简书客户端对于内部域名的内容和第三方内容，在展示方式上是有明显不同的。在阅读简书的文章时，让用户发现不了自己是在一个浏览器上进行阅读，这在方方面面就极大改善了用户体验。为了做到这一点，我推测简书首先需要对自己的内容进行非常良好的CDN加速，以保证内容加载时不会耗时过长，同时采取一些预加载策略，二是在内容加载时，采用与原生界面部分相同的loading界面，去掉进度条，模拟原生界面的加载过程。而对于第三方的链接，采用&lt;code&gt;进度条+返回、关闭按钮&lt;/code&gt;的设计，则更符合用户在浏览器中进行阅读的习惯，也可以和自己的内容进行直观区分，这也改善了用户体验。&lt;/li&gt;&lt;li&gt;对于某些原生和Web页面都可以实现的特性如何取舍，这也是需要考虑的问题。比如，点击评论按钮页面滑动，这个功能使用web页面的滑动而非原生的控制显然更为自然也更符合用户习惯。而对于撰写评论的功能，使用原生的键盘、编辑器组件，当然就比使用web页面的键入更加稳定可控了。&lt;/li&gt;&lt;/ul&gt;&lt;h3&gt;七、文章的demo&lt;/h3&gt;&lt;h5&gt;对于文章内容，我写了一个demo，这是&lt;a href="https://github.com/halohily/LYWebviewController" target="_blank"&gt;demo地址&lt;/a&gt;。&lt;/h5&gt;&lt;p&gt;为了便于理解，我为代码写了详尽的注释。&lt;br&gt;&lt;br&gt;如果觉得它对你有帮助，不妨在github上为我点一个star~非常感谢！&lt;/p&gt;&lt;/div&gt;&#xD;
    &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 />
      <guid isPermaLink="true">https://itindex.net/detail/57328-ios-%E5%AE%A2%E6%88%B7%E7%AB%AF-hybrid</guid>
      <pubDate>Mon, 07 Aug 2017 13:43:27 CST</pubDate>
    </item>
    <item>
      <title>The Right Way to Architect iOS App with Swift</title>
      <link>https://itindex.net/detail/57083-the-right-way</link>
      <description>&lt;p&gt;关于 iOS 架构的文章感觉已经泛滥了，前一阵正好 Android 官方推了一套    &lt;a href="https://developer.android.com/topic/libraries/architecture/guide.html"&gt;App Architecture&lt;/a&gt; ，于是就在想，对于 iOS 来说，怎样的架构才是最适合的。带着这个问题，我开始了探索。&lt;/p&gt;

 &lt;h2&gt;Why Architecture Matters?&lt;/h2&gt;
 &lt;p&gt;这是第一个也是最重要的问题，为什么会出现各种 Architecture Pattern？真的那么重要么？&lt;/p&gt;

 &lt;p&gt;我们来想一下，无论是做一个 App 还是搭一套后台系统，如果是一次性的，今天用完明天就可以扔掉，那么怎么快怎么来，代码重复、代码逻辑、代码格式统统不重要。&lt;/p&gt;

 &lt;p&gt;这种场景比较适合黑客马拉松，而真实情况往往是我们的代码需要上线，要对用户负责，而一套好的架构会让这些事情变得更加容易。&lt;/p&gt;

 &lt;h3&gt;好的架构简洁且整洁&lt;/h3&gt;

 &lt;p&gt;  &lt;img alt="" src="http://s3.mogucdn.com/mlcdn/c45406/170619_4e7gif674kdad56l6iek5lj8i7dl9_984x329.png"&gt;&lt;/img&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;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;好的架构能对 2 和 3 有一定的作用，对于第 1 点还是要看程序员的能力和经验。&lt;/p&gt;

 &lt;h4&gt;抽象程度低&lt;/h4&gt;
 &lt;p&gt;这样的代码往往是命令式编程产生的，也就是像 CPU 那样的思考方式，把产品经理的需求直观地翻译成代码，而不对其中的共性、本质进行抽离和抽象，时间一长就容易看不懂其中的逻辑，需求一变就要改核心代码。&lt;/p&gt;

 &lt;p&gt;比如下面这段代码，不知道具体要完成什么任务。&lt;/p&gt;

 &lt;p&gt;  &lt;img alt="" src="http://ww1.sinaimg.cn/large/afe37136gy1fgtcs43v88j20nk0damzy.jpg"&gt;&lt;/img&gt;&lt;/p&gt;

 &lt;h4&gt;职责不明确&lt;/h4&gt;
 &lt;p&gt;这也是产生「一大坨代码」的原因之一，就像 MVC 模式里，没有说明用户的操作应该在哪里处理，业务逻辑放在什么地方，这样就容易走捷径，怎么方便怎么来，而越是方便到后来就越容易出问题。&lt;/p&gt;

 &lt;h4&gt;喜欢走捷径&lt;/h4&gt;
 &lt;p&gt;这是我们的天性，毕竟能够更快更方便地达到目标，为什么不做呢？&lt;/p&gt;

 &lt;p&gt;比如我们都知道「通知」用起来很方便，所有涉及到单向数据传递的地方都可以使用，比如 Cell 通过通知向 VC 传递点击事件信息、Model 通过通知向 VC 传递数据信息、VC 之间通过通知进行解耦等等。&lt;/p&gt;

 &lt;p&gt;又比如可以很方便地在 VC 存储状态信息，慢慢地 VC 里这些状态变量就多了起来，到后来要维护这些变量就变得非常困难，出了问题也不好排查。&lt;/p&gt;

 &lt;p&gt;Clojure 的作者 Rich Hickey 有一个非常著名的   &lt;a href="https://www.infoq.com/presentations/Simple-Made-Easy"&gt;Simple Made Easy&lt;/a&gt; 分享&lt;/p&gt;

 &lt;blockquote&gt;
    &lt;p&gt;Simple is often erroneously mistaken for easy. “Easy” means “to be at hand”, “to be approachable”. “Simple” is the opposite of “complex” which means “being intertwined”, “being tied together”. Simple != easy.&lt;/p&gt;
&lt;/blockquote&gt;

 &lt;p&gt;Simple 是我们所追求的，而 Easy 往往会让事情往反方向发展。&lt;/p&gt;

 &lt;h3&gt;好的架构能够覆盖大多数场景&lt;/h3&gt;
 &lt;p&gt;产品经理：老板说要做一个插座，具体怎么实现我不管，下周一就要。拿到这个需求之后，你觉得很简单，完美符合需求，就像这样：&lt;/p&gt;

 &lt;p&gt;  &lt;img alt="" src="http://ww1.sinaimg.cn/large/afe37136gy1fgtctgc56dj206u06ujrp.jpg"&gt;&lt;/img&gt;&lt;/p&gt;

 &lt;p&gt;可是好景不长，老板新买了一个电脑，只支持两相的插座，而且现在就要，作为工程师，你不能被这么简单朴实的需求难倒，于是稍微动了下脑筋，就出了一个解决方案：&lt;/p&gt;

 &lt;p&gt;  &lt;img alt="" src="http://ww1.sinaimg.cn/large/afe37136gy1fgtctvnyggj206c065dhe.jpg"&gt;&lt;/img&gt;&lt;/p&gt;

 &lt;p&gt;虽然丑陋，但是可以工作。但我们的目标不只是可以工作（紧急情况除外），更要优雅地工作。&lt;/p&gt;

 &lt;p&gt;举一个现实的例子，比如页面间支持通过 Router 进行跳转，但有一天发现有页面间通信的需求，然后就会出来一些 trick 的解决方案，比如发通知或者给 Router 加一个   &lt;code&gt;- (id)objectForURL:&lt;/code&gt; 的方法，本质上跟上图的解决方案没什么区别。&lt;/p&gt;

 &lt;h3&gt;好的架构能够提升开发效率，方便定位问题&lt;/h3&gt;
 &lt;p&gt;好的架构能够支持多人并行开发、一定程度的代码复用、单元测试，出了问题能比较方便地找到原因。这几点是架构要解决的主要问题。&lt;/p&gt;

 &lt;h2&gt;当前的状态&lt;/h2&gt;
 &lt;p&gt;目前主流的主要有 MVC 和 MVVM，VIPER 用的会少一些，它们之间的优劣对比这里就不展开了，可以查看这篇文章来了解：  &lt;a href="https://blog.coding.net/blog/ios-architecture-patterns"&gt;iOS 架构模式 - 简述 MVC, MVP, MVVM 和 VIPER (译) - Coding 博客&lt;/a&gt;&lt;/p&gt;

 &lt;p&gt;简单总结下：&lt;/p&gt;

 &lt;ul&gt;
    &lt;li&gt;MVC 模式过于简单，定的标准过于粗放， 容易滋生捷径。&lt;/li&gt;
    &lt;li&gt;MVVM 会好很多，但场景的覆盖还不够全，比如缺少页面间跳转／通信、数据获取等。&lt;/li&gt;
    &lt;li&gt;VIPER 更加细致，但有点臃肿。&lt;/li&gt;
&lt;/ul&gt;

 &lt;h2&gt;How to Define “Right”&lt;/h2&gt;
 &lt;p&gt;每种架构都有自己的特点，如果要定义「Right」的话，至少要符合一些标准，以下是我整理的觉得比较重要的几条：&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;符合 GUI 编程的特点&lt;/li&gt;
&lt;/ul&gt;

 &lt;h3&gt;尽量简单&lt;/h3&gt;
 &lt;p&gt;简单的事物容易理解，也比较容易接受，用爱因斯坦的话来说「尽量简单，但不要过于简单」。VIPER 其实已经挺完善的了，但就是有点复杂，可以看  &lt;a href="https://www.objc.io/issues/13-architecture/viper/"&gt;这篇文章&lt;/a&gt;感受下。&lt;/p&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;Separation of Concern&lt;/code&gt; ，每个单元只需要关心自己的事情，跟外部尽量解耦，这样无论是对代码复用和测试都会很有帮助。&lt;/p&gt;

 &lt;h3&gt;符合 GUI 编程的特点&lt;/h3&gt;
 &lt;p&gt;GUI 编程和其他的非界面编程还是有差异的，对 GUI 编程的特点进行合适地抽象，并在此基础上形成的架构才更有「对」的感觉。&lt;/p&gt;

 &lt;p&gt;我比较认同   &lt;code&gt;view = render(state) + handle(event)&lt;/code&gt; 这个定义，view 本身只做两件事，给 state 包一层漂亮的外衣，同时对用户的操作做出响应。&lt;/p&gt;

 &lt;h2&gt;Inspiring&lt;/h2&gt;
 &lt;p&gt;差不多心里有谱了，现在来看看相关领域的架构大概是怎样的，找点启发。&lt;/p&gt;

 &lt;h3&gt;Android Architecture&lt;/h3&gt;
 &lt;p&gt;Android 最近出了一套官方推荐的  &lt;a href="https://developer.android.com/topic/libraries/architecture/index.html"&gt;架构&lt;/a&gt;，挺细致的，主要的流程如下图所示&lt;/p&gt;

 &lt;p&gt;  &lt;img alt="" src="https://developer.android.com/topic/libraries/architecture/images/final-architecture.png"&gt;&lt;/img&gt;&lt;/p&gt;

 &lt;p&gt;大意就是   &lt;code&gt;ViewModel&lt;/code&gt; 通过调用   &lt;code&gt;Repository&lt;/code&gt; 从   &lt;code&gt;Model&lt;/code&gt; 或   &lt;code&gt;Remote&lt;/code&gt; 中获取数据，然后放到内置的   &lt;code&gt;LiveData&lt;/code&gt; 里，而   &lt;code&gt;LiveData&lt;/code&gt; 在   &lt;code&gt;Activity&lt;/code&gt; 初始化时即被绑定，因此当    &lt;code&gt;LiveData&lt;/code&gt; 变化时，可以马上反馈到界面。&lt;/p&gt;

 &lt;p&gt;当用户操作界面时，  &lt;code&gt;Activity&lt;/code&gt; 会捕获到这些事件，然后调用   &lt;code&gt;ViewModel&lt;/code&gt; 的特定方法，这些方法最终会导致   &lt;code&gt;LiveData&lt;/code&gt; 发生改变，再次反馈到界面。&lt;/p&gt;

 &lt;p&gt;整体也是 MVVM 的模式，但也有自己的特点：&lt;/p&gt;

 &lt;ul&gt;
    &lt;li&gt;通过    &lt;code&gt;LiveData&lt;/code&gt; 来做单向绑定。&lt;/li&gt;
    &lt;li&gt;使用    &lt;code&gt;Repository&lt;/code&gt; 来统一数据的交互。&lt;/li&gt;
    &lt;li&gt;内置     &lt;code&gt;Room&lt;/code&gt; 作为持久层。&lt;/li&gt;
    &lt;li&gt;内置    &lt;code&gt;ViewModel&lt;/code&gt; 供使用。&lt;/li&gt;
    &lt;li&gt;内置    &lt;code&gt;LifeCycle&lt;/code&gt; 来简化跟生命周期相关的对象的操作，避免内存泄漏。（比如 ViewModel）&lt;/li&gt;
    &lt;li&gt;使用    &lt;code&gt;Dagger2&lt;/code&gt; 这个依赖注入工具来避免依赖。&lt;/li&gt;
&lt;/ul&gt;

 &lt;h3&gt;Elm Architecture&lt;/h3&gt;
 &lt;blockquote&gt;
    &lt;p&gt;Elm is a functional language that compiles to JavaScript. It competes with projects like React as a tool for creating websites and web apps. Elm has a very strong emphasis on simplicity, ease-of-use, and quality tooling.&lt;/p&gt;
&lt;/blockquote&gt;

 &lt;p&gt;Elm 是一个主打函数式编程，同时通过强大的编译器来尽量确保没有 runtime error 的编程语言，著名的 Redux 就是受它启发。来感受下它的代码：&lt;/p&gt;

 &lt;pre&gt;  &lt;code&gt;import Html exposing (Html, button, div, text)
import Html.Events exposing (onClick)

main =
  Html.beginnerProgram { model = 0, view = view, update = update }

type Msg = Increment | Decrement

update msg model =
  case msg of
    Increment -&amp;gt;
      model + 1

    Decrement -&amp;gt;
      model - 1

view model =
  div []
    [ button [ onClick Decrement ] [ text &amp;quot;-&amp;quot; ]
    , div [] [ text (toString model) ]
    , button [ onClick Increment ] [ text &amp;quot;+&amp;quot; ]
    ]
&lt;/code&gt;&lt;/pre&gt;

 &lt;p&gt;主要分为 4 块，  &lt;code&gt;model&lt;/code&gt; ,   &lt;code&gt;view&lt;/code&gt; ,   &lt;code&gt;update&lt;/code&gt; ,   &lt;code&gt;message&lt;/code&gt;&lt;/p&gt;

 &lt;ul&gt;
    &lt;li&gt;view 展示 model 数据，同时将用户的操作作为 message 抛出。&lt;/li&gt;
    &lt;li&gt;model 包含了页面所需的所有信息。&lt;/li&gt;
    &lt;li&gt;当 message 被抛出时，会自动进入到 update 方法，update 返回的新 model 自动进入到 view 里被展示。&lt;/li&gt;
&lt;/ul&gt;

 &lt;p&gt;  &lt;img alt="" src="http://ww1.sinaimg.cn/large/afe37136gy1fgtcussf1ij20e00983yw.jpg"&gt;&lt;/img&gt;&lt;/p&gt;

 &lt;p&gt;跟其他的前端框架不同，Elm 不喜欢 parent-child communication, 也不提倡 components，作为函数式编程语言，它在乎的就是创建 function，通过   &lt;a href="https://guide.elm-lang.org/reuse/"&gt;helper function&lt;/a&gt; 来达到类似的效果。&lt;/p&gt;

 &lt;h3&gt;Vue Architecture&lt;/h3&gt;
 &lt;p&gt;  &lt;img alt="" src="http://ww1.sinaimg.cn/large/afe37136gy1fgtcv8rgczj20io09wq3l.jpg"&gt;&lt;/img&gt;&lt;/p&gt;

 &lt;p&gt;Vue 也是采用的 MVVM 模式，把数据绑定在内部处理了，对外部来说只要在   &lt;code&gt;data&lt;/code&gt; 里声明特定的 key，在   &lt;code&gt;view&lt;/code&gt; 里就可以直接使用，并且实时响应。对于   &lt;code&gt;view&lt;/code&gt;  的事件，也会映射到   &lt;code&gt;ViewModel&lt;/code&gt; 的特定方法。&lt;/p&gt;

 &lt;p&gt;Vue 的    &lt;code&gt;Router&lt;/code&gt; 是把 path 映射到 component 上，看着也比较清晰。&lt;/p&gt;

 &lt;div&gt;  &lt;div&gt;   &lt;table&gt;    &lt;tr&gt;     &lt;td&gt;      &lt;pre&gt;1
2
3
4
5&lt;/pre&gt;&lt;/td&gt;     &lt;td&gt;      &lt;pre&gt;const routes = [
  { path: &amp;apos;/foo&amp;apos;, component: Foo },
  { path: &amp;apos;/bar&amp;apos;, component: Bar },
	{ path: &amp;apos;/user/:id&amp;apos;, component: User }
]
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;

 &lt;h2&gt;The Right Way (IMO)&lt;/h2&gt;

 &lt;h3&gt;目录结构&lt;/h3&gt;

 &lt;p&gt;  &lt;img alt="" src="http://ww1.sinaimg.cn/large/afe37136gy1fgtcvlcjgyj20dw0xg76t.jpg"&gt;&lt;/img&gt;&lt;/p&gt;

 &lt;p&gt;目录结构需要能够让不同职责的文件找到自己的归属，同时尽量清晰。这个是我目前觉得还不错的分类&lt;/p&gt;

 &lt;ul&gt;
    &lt;li&gt;   &lt;code&gt;External&lt;/code&gt; ：一些第三方的 framework。&lt;/li&gt;
    &lt;li&gt;   &lt;code&gt;Extensions&lt;/code&gt; : 针对当前 App 做的一些针对性扩展。&lt;/li&gt;
    &lt;li&gt;   &lt;code&gt;Infrastructure&lt;/code&gt; : 比较重要的基础组件，在前期就要管控起来。&lt;/li&gt;
    &lt;li&gt;   &lt;code&gt;Models&lt;/code&gt; : 对应服务端的 Objects。&lt;/li&gt;
    &lt;li&gt;   &lt;code&gt;Views&lt;/code&gt; : 页面。&lt;/li&gt;
    &lt;li&gt;   &lt;code&gt;Shared&lt;/code&gt; : 会在 App 内部被公用的部分，方便统一管控。&lt;/li&gt;
    &lt;li&gt;   &lt;code&gt;Utilities&lt;/code&gt; : 一些帮助类。&lt;/li&gt;
&lt;/ul&gt;

 &lt;h3&gt;Architecture&lt;/h3&gt;
 &lt;p&gt;  &lt;img alt="" src="http://ww1.sinaimg.cn/large/afe37136gy1fgtcxzpz4yj218m0mw0vs.jpg"&gt;&lt;/img&gt;&lt;/p&gt;

 &lt;p&gt;本质上跟 MVVM 差不多，只是多补充了些细节。之前也有考虑过采用 ReSwift + RxSwift 的方式，也就是 Redux，后来写下来发现还是有点复杂：比如下拉刷新的 3 个 state （ loading / loaded / failed），action 要定义（毕竟获取数据的逻辑写在 Action 中），state 中也要定义（视图最终关心的是 state 的变化）；没有很方便的 diff 支持等。于是就回归到了 MVVM 模式。&lt;/p&gt;

 &lt;h4&gt;ViewModel&lt;/h4&gt;
 &lt;p&gt;ViewModel 主要有 3 个职责：&lt;/p&gt;

 &lt;ul&gt;
    &lt;li&gt;通过 Repository 获取/修改数据。&lt;/li&gt;
    &lt;li&gt;提供    &lt;code&gt;Observable Properties&lt;/code&gt; 供 View 使用。&lt;/li&gt;
    &lt;li&gt;提供    &lt;code&gt;Functions&lt;/code&gt; 供 View 调用，通常会导致    &lt;code&gt;Observable Properties&lt;/code&gt; 的改变。&lt;/li&gt;
&lt;/ul&gt;

 &lt;p&gt;这块也算是常规手法，需要注意的一点是 Repository 的初始化，如果要方便测试的话，最好提供注入点（比如初始化时注入或提供 set 方法注入）。&lt;/p&gt;

 &lt;h4&gt;Repository&lt;/h4&gt;
 &lt;p&gt;Repository 的职责就是跟数据打交道，获取远程／本地数据，并将其转换成 Model 返回给 ViewModel。&lt;/p&gt;

 &lt;h4&gt;页面间跳转和通信&lt;/h4&gt;
 &lt;p&gt;使用 Router 即可，如果是内部的 VC 之间跳转，还可以携带 model 信息。&lt;/p&gt;

 &lt;h4&gt;通用的小模块( Components )&lt;/h4&gt;
 &lt;p&gt;我发现前端开发里，  &lt;code&gt;Components&lt;/code&gt; 用得还蛮多的，客户端开发倒不那么常见。这些小模块其实就是一些可在多个页面复用的业务相关的视图（Widget），可能带有业务逻辑，方便复用，比如「赞」按钮。&lt;/p&gt;

 &lt;h4&gt;服务调用&lt;/h4&gt;
 &lt;p&gt;比如在详情页要使用购物车的「加购」功能，通常做法是采用   &lt;code&gt;Register Procotol&lt;/code&gt; 方式，维护一个 Protocol 和 Class 的注册表，并且在 App 启动时进行注册。我发现使用 Swift 的 POP 就不需要这么麻烦了，具体怎么做，我们后面讲。&lt;/p&gt;

 &lt;h3&gt;Demo&lt;/h3&gt;

 &lt;p&gt;这个 Demo 演示了知乎日报的列表和详情页：&lt;/p&gt;

 &lt;p&gt;  &lt;img alt="" src="http://ww1.sinaimg.cn/large/afe37136gy1fgtcx5hukwj20lt0ijq3x.jpg"&gt;&lt;/img&gt;&lt;/p&gt;

 &lt;p&gt;看起来蛮简单的，不过事实可能并非如此，我们来慢慢捋一下。&lt;/p&gt;

 &lt;h4&gt;初始页&lt;/h4&gt;

 &lt;p&gt;  &lt;img alt="" src="http://ww1.sinaimg.cn/large/afe37136gy1fgtcxfxauej20af0ijwek.jpg"&gt;&lt;/img&gt;&lt;/p&gt;

 &lt;p&gt;刚进来时，会处于原始的 loading 状态，这个状态不同于下拉刷新，可能是一个萌萌的 loading 图。&lt;/p&gt;

 &lt;p&gt;首先这个页面属于   &lt;code&gt;NewsFeed&lt;/code&gt; 页，因此在该目录下新建 3 个文件&lt;/p&gt;

 &lt;div&gt;  &lt;div&gt;   &lt;table&gt;    &lt;tr&gt;     &lt;td&gt;      &lt;pre&gt;1
2
3&lt;/pre&gt;&lt;/td&gt;     &lt;td&gt;      &lt;pre&gt;|- NewsFeedViewModel.swift
|- NewsFeedViewController.swift
|- NewsFeedRepository.swift
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;

 &lt;p&gt;本着 view 只是展示 state 的原则，我们首先要处理的就是 state，那么怎么处理？ 这个 Event 是从 View 那边触发的，触发之后，对于 View 来说只能求助于 ViewModel，于是 VM 就提供了一个   &lt;code&gt;initialLoading&lt;/code&gt; 方法。&lt;/p&gt;

 &lt;p&gt;那这个   &lt;code&gt;initialLoading&lt;/code&gt; 里该做些什么呢？其实也就是根据 repository 的不同结果，设置不同的 state，然后 view 来响应这些 state。同时考虑到之后的「下拉刷新」和「加载更多」，顺便分离出一个通用的   &lt;code&gt;loadData:&lt;/code&gt; 方法&lt;/p&gt;

 &lt;h4&gt;ViewModel&lt;/h4&gt;

 &lt;div&gt;  &lt;div&gt;   &lt;table&gt;    &lt;tr&gt;     &lt;td&gt;      &lt;pre&gt;1
2
3
4
5
6
7
8
9&lt;/pre&gt;&lt;/td&gt;     &lt;td&gt;      &lt;pre&gt;class NewsFeedViewModel {
	func initialLoading() {
        loadData(.initial)
    }

	func loadData(_ loadingType: LoadingType, offset: String = &amp;quot;&amp;quot;) {
		// todo
	}
}
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;

 &lt;p&gt;那么   &lt;code&gt;Observable Properties&lt;/code&gt; 应该是怎样的呢？在 OC 时代，只要简单的暴露 readonly 的 property，外部无论是 KVO 还是 RAC 都能很方便地进行绑定，到了 swift 时代，如果要做 KVO 就要继承   &lt;code&gt;NSObject&lt;/code&gt;，还要加一个   &lt;code&gt;@dynamic&lt;/code&gt;  前缀，不优雅。比较理想的状态是使用 RxSwift 的   &lt;code&gt;Observable&lt;/code&gt; 作为属性，外部只要   &lt;code&gt;subscribe&lt;/code&gt; 就行了。不过在内部如何给这个   &lt;code&gt;Observable&lt;/code&gt;  塞数据又有点小问题。最终决定使用   &lt;code&gt;Variable&lt;/code&gt; 作为暴露的属性，它的好处是内部不需要再新建一个变量，直接设置这个   &lt;code&gt;Variable&lt;/code&gt; 的   &lt;code&gt;value&lt;/code&gt; 即可，弊端就是对于使用方需要先通过   &lt;code&gt;asObservable()&lt;/code&gt; 转一下再进行 subscribe，并且只要愿意，也可以设置   &lt;code&gt;value&lt;/code&gt; 值，存在误操作的风险。在这里我们先简单起见用   &lt;code&gt;Variable&lt;/code&gt; 来做。&lt;/p&gt;

 &lt;p&gt;接下来的问题就是这个   &lt;code&gt;Variable&lt;/code&gt; 里应该放什么？肯定要放一些当前的 loading 状态，比如 loaded，failed，loading 这些，那么要不要带上 data？如果不一起带上 data，那么状态的改变和数据的改变就不是一个原子操作，有可能会带来一些异常（比如 view 发现 loading 状态变为 loaded，自动去取最新的 data，但此时 data 可能还没有改变）。因此，我把它们都放到了一起，首先来看一下   &lt;code&gt;ResultModel&lt;/code&gt;&lt;/p&gt;

 &lt;h4&gt;Model&lt;/h4&gt;
 &lt;p&gt;这是一个通用的数据结构&lt;/p&gt;

 &lt;div&gt;  &lt;div&gt;   &lt;table&gt;    &lt;tr&gt;     &lt;td&gt;      &lt;pre&gt;1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33&lt;/pre&gt;&lt;/td&gt;     &lt;td&gt;      &lt;pre&gt;// ResultModel.swift

enum LoadingType {
    case initial, refresh, more
}

enum LoadingStatus: Equatable {
    case none
    case loading
    case loaded
    case failure(Error)
    
    static func ==(lhs: LoadingStatus, rhs: LoadingStatus) -&amp;gt; Bool {
        switch (lhs, rhs) {
        case (.none, .none):
            return true
        case (.loading, .loading):
            return true
        case (.loaded, .loaded):
            return true
        default:
            return false
        }
    }
}

struct ResultModel&amp;lt;T&amp;gt; {
    var loadingStatus: LoadingStatus = .none
    var loadingType: LoadingType = .initial
    
    var previousItems = [T]()
    var currentItems = [T]()
}
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;

 &lt;div&gt;  &lt;div&gt;   &lt;table&gt;    &lt;tr&gt;     &lt;td&gt;      &lt;pre&gt;1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25&lt;/pre&gt;&lt;/td&gt;     &lt;td&gt;      &lt;pre&gt;// NewsModel.swift

class NewsFeedViewModel {
    // 1
	  static var news:Variable&amp;lt;ResultModel&amp;lt;NewsItem&amp;gt;&amp;gt; = Variable(ResultModel())

    func initialLoading() {
        loadData(.initial)
    }

    func loadData(_ loadingType: LoadingType, offset: String = &amp;quot;&amp;quot;) {
        // 2 如果当前处于 loading 状态，就不继续处理了
        if (NewsFeedViewModel.news.value.loadingStatus == .loading) {
            return
        }

        // 3 设置新的 loading 类型和状态
        var value = NewsFeedViewModel.news.value
        value.loadingStatus = .loading
        value.loadingType = loadingType
        NewsFeedViewModel.news.value = value
        
        // 4 接下来就是发网络请求，根据不同的请求结果设置 state
    }
}
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;

 &lt;ol&gt;
    &lt;li&gt;这里使用    &lt;code&gt;static&lt;/code&gt; 主要是出于方便。&lt;/li&gt;
    &lt;li&gt;这里纠结了一段时间，之前是新建了 3 个 loading status（initial, refresh, loadmore），然后每个 status 再细分为 3 种状态(loading, loaded, error)，后来发现这样的话，「当前是哪个 loading status，该 status 目前处于什么状态」判断起来会比较麻烦。于是就按照现在这样进行了拆分。&lt;/li&gt;
    &lt;li&gt;在这里对状态进行更改之后，UI 那边可以自动收到更新。&lt;/li&gt;
    &lt;li&gt;这里会调用 Repository 来获取数据。&lt;/li&gt;
&lt;/ol&gt;

 &lt;h4&gt;Repository&lt;/h4&gt;
 &lt;p&gt;Repository 这块由于是异步交互，因此直接就上 RxSwift 了，返回一个   &lt;code&gt;Observable&lt;/code&gt; ，VM 作为消费方来订阅。&lt;/p&gt;

 &lt;div&gt;  &lt;div&gt;   &lt;table&gt;    &lt;tr&gt;     &lt;td&gt;      &lt;pre&gt;1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21&lt;/pre&gt;&lt;/td&gt;     &lt;td&gt;      &lt;pre&gt;import Foundation
import RxSwift

class NewsFeedRepository {
    static func news(_ offset: String = &amp;quot;&amp;quot;) -&amp;gt; Observable&amp;lt;[String:Any]?&amp;gt; {
        return Observable.create({ observer in
            let path = offset.characters.count &amp;gt; 0 ? &amp;quot;/api/4/news/before/\(offset)&amp;quot; : &amp;quot;/api/4/news/latest&amp;quot;
            let resource = Resource(path: path, method: .GET, requestBody: nil, headers: [&amp;quot;Content-Type&amp;quot;: &amp;quot;application/json&amp;quot;], parse: decodeJSON)
            
            // 这个用的是 chris 开源的简单的 API 请求封装 http://chris.eidhof.nl/posts/tiny-networking-in-swift.html
            apiRequest(baseURL: URL(string: &amp;quot;https://news-at.zhihu.com&amp;quot;)!, resource: resource, failure: { (reason, result) in
                observer.on(.error(reason))
            }, success: { result in
                observer.on(.next(result))
                observer.on(.completed)
            })
            
            return Disposables.create()
        })
    }
}
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;

 &lt;p&gt;也可以在这里直接返回解析后的 Model，这样 VM 那边就不用处理了。&lt;/p&gt;

 &lt;h4&gt;ViewModel 调用 Repository&lt;/h4&gt;

 &lt;div&gt;  &lt;div&gt;   &lt;table&gt;    &lt;tr&gt;     &lt;td&gt;      &lt;pre&gt;1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28&lt;/pre&gt;&lt;/td&gt;     &lt;td&gt;      &lt;pre&gt;class NewsFeedViewModel {
    // 4
    NewsFeedRepository.news(offset).asObservable().subscribe(onNext: {[unowned self] (result) in
        // 把 json 转换为 model
        let parsedResult = self._parseResult(result: result)
        var value = NewsFeedViewModel.news.value
        value.previousItems = NewsFeedViewModel.news.value.currentItems
        
        // 设置对应的 value
        if value.loadingType == .more {
            value.currentItems = value.previousItems + (parsedResult?.news ?? [])
        } else {
            value.currentItems = parsedResult?.news ?? []
        }
            
        value.loadingStatus = .loaded
        NewsFeedViewModel.news.value = value
        self.offset = parsedResult?.date ?? &amp;quot;&amp;quot;
        value.loadingStatus = .none
        
        // 统一设置 value，对外部 subscriber 来说就是原子操作
        NewsFeedViewModel.news.value = value
    }, onError: { (error) in
        NewsFeedViewModel.news.value.loadingStatus = .failure(error)
    }, onCompleted: {  
    }) {
    }.addDisposableTo(disposeBag)
}
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;

 &lt;p&gt;这里你会注意到有一个   &lt;code&gt;previousItems&lt;/code&gt; 和   &lt;code&gt;currentItems&lt;/code&gt; ，这个主要是提供灵活性，避免暴力的   &lt;code&gt;reloadData()&lt;/code&gt; ，比如获取到了更多的数据之后，可以只 reload 新的数据。&lt;/p&gt;

 &lt;h4&gt;View&lt;/h4&gt;

 &lt;div&gt;  &lt;div&gt;   &lt;table&gt;    &lt;tr&gt;     &lt;td&gt;      &lt;pre&gt;1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24&lt;/pre&gt;&lt;/td&gt;     &lt;td&gt;      &lt;pre&gt;// NewsFeedViewController.swift
class NewsFeedViewController: UITableViewController {
    override func viewDidLoad() {
        handleDataChange()
        viewModel.initialLoading()
    }

    func handleDataChange() {
        NewsFeedViewModel.news.asObservable()
            .observeOn(MainScheduler.instance)
            .subscribe(onNext: {[unowned self] item in
                if item.loadingStatus != .loading {
                    self.initialLoadingIndicator.stopAnimating()
                }
                if item.loadingStatus == .loaded {
                    // 这里调用 Diff 这个 framework 提供的 extension
                    self.tableView.animateRowChanges(oldData: item.previousItems, newData: item.currentItems)
                }
                if item.loadingType == .initial &amp;amp;&amp;amp; item.loadingStatus == .loading {
                    self.initialLoadingIndicator.startAnimating()
                }
            }).addDisposableTo(disposeBag)
    }
}
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;

 &lt;p&gt;「正在加载」和「已经加载」的场景已经处理完了，「加载失败」的处理也类似，比如失败之后显示一个 reload button，点击 reload button 之后，再调用一下   &lt;code&gt;viewModel.initialLoading()&lt;/code&gt;&lt;/p&gt;

 &lt;h4&gt;TableView&lt;/h4&gt;
 &lt;p&gt;接下来就来看看如何处理 TableView 的数据展示，其实就是消费 VM 的 property&lt;/p&gt;

 &lt;div&gt;  &lt;div&gt;   &lt;table&gt;    &lt;tr&gt;     &lt;td&gt;      &lt;pre&gt;1
2
3
4
5
6
7
8
9
10
11
12&lt;/pre&gt;&lt;/td&gt;     &lt;td&gt;      &lt;pre&gt;extension NewsFeedViewController {
    override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -&amp;gt; Int {
        return NewsFeedViewModel.news.value.currentItems.count
    }
    
    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -&amp;gt; UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: &amp;quot;Cell&amp;quot;) as! NewsCell
        let newsItem: NewsItem = NewsFeedViewModel.news.value.currentItems[indexPath.row]
        cell.configure(newsItem)
        return cell
    }
}
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;

 &lt;p&gt;到这里最基本的首页数据展示就基本完成了。&lt;/p&gt;

 &lt;h3&gt;加载更多&lt;/h3&gt;
 &lt;p&gt;之前一直在纠结这块到底该怎么做才比较合适，如果直接把 newItems append 到原有的 items 列表，形成新的列表，UI 那边拿到之后就只能   &lt;code&gt;reloadData()&lt;/code&gt; 了，最好能让 UI 那边知道新的和旧的之间发生了哪些变化，于是就找到了   &lt;a href="https://github.com/wokalski/Diff.swift"&gt;Diff&lt;/a&gt; 这个 framework，它能够定位出两个 collection 之间的差异，但前提是 collection item 要实现   &lt;code&gt;Equatable&lt;/code&gt; 协议。于是就有了   &lt;code&gt;previousItems&lt;/code&gt; 和   &lt;code&gt;currentItems&lt;/code&gt; 的设计。&lt;/p&gt;

 &lt;h3&gt;喜欢功能&lt;/h3&gt;
 &lt;p&gt;喜欢功能本质上是修改 NewsItem 的   &lt;code&gt;hasFaved&lt;/code&gt; 属性，然后让 UI 可以感知到这个变化。这里问题就来了：如何对列表中的一个   &lt;code&gt;struct&lt;/code&gt; 进行调整？我们知道   &lt;code&gt;struct&lt;/code&gt; 是值拷贝的，只要发生赋值行为，拿到的就不再是原先的那个 struct 了（比如把 items 通过参数传递，要修改的话就要进行拷贝，除非设置为   &lt;code&gt;inout&lt;/code&gt;）。&lt;/p&gt;

 &lt;p&gt;这个问题本质上是如何操作 Immutable Objects，然后就想到了   &lt;a href="https://facebook.github.io/immutable-js/"&gt;Immutable.js&lt;/a&gt;，它也提供了一些修改 List 的方法，只不过都是返回一个新的：&lt;/p&gt;

 &lt;div&gt;  &lt;div&gt;   &lt;table&gt;    &lt;tr&gt;     &lt;td&gt;      &lt;pre&gt;1
2
3
4&lt;/pre&gt;&lt;/td&gt;     &lt;td&gt;      &lt;pre&gt;const { List } = require(&amp;apos;immutable&amp;apos;);
const list = List([ 0, 1, 2, List([ 3, 4 ])])
list.setIn([3, 0], 999);
// List [ 0, 1, 2, List [ 999, 4 ] ]
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;

 &lt;p&gt;因此，这里简单的处理方式就是通过传进来的   &lt;code&gt;newsItem&lt;/code&gt; 找到它在 list 中的 index（  &lt;code&gt;newsItem&lt;/code&gt; 已经实现了   &lt;code&gt;Equatable&lt;/code&gt; 协议），然后把修改过   &lt;code&gt;hasFaved&lt;/code&gt; 属性的新的   &lt;code&gt;newsItem&lt;/code&gt; 放到 index 位置来达到替换的效果。&lt;/p&gt;

 &lt;div&gt;  &lt;div&gt;   &lt;table&gt;    &lt;tr&gt;     &lt;td&gt;      &lt;pre&gt;1
2
3
4
5
6
7
8
9
10
11
12
13&lt;/pre&gt;&lt;/td&gt;     &lt;td&gt;      &lt;pre&gt;class NewsFeedViewModel {
    func toggleFav(_ newsItem: NewsItem) {
        if let newsIndex = NewsFeedViewModel.news.value.currentItems.index(of: newsItem) {
            var _newsItem = NewsFeedViewModel.news.value.currentItems[newsIndex]
            _newsItem.hasFaved = !_newsItem.hasFaved

            var value = NewsFeedViewModel.news.value
            value.currentItems[newsIndex] = _newsItem

            NewsFeedViewModel.news.value = value
        }
    }
}
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;

 &lt;h4&gt;Components&lt;/h4&gt;
 &lt;p&gt;由于新闻列表和喜欢的新闻列表表现上一致，那么就可以进行一些复用，比如可以把 Cell 作为 Component。&lt;/p&gt;

 &lt;p&gt;那对于一个 Component 来说，需要具备哪些特性呢？这个并没有什么约定，本质上就是一个或几个函数，外部调用后会返回一个 view，或者提供一些 block 回调，仅此而已。&lt;/p&gt;

 &lt;h4&gt;Truth and Computed Properties&lt;/h4&gt;
 &lt;p&gt;这里的   &lt;code&gt;Truth&lt;/code&gt; 是指最源头的数据，比如一个数组，  &lt;code&gt;Computed Properties&lt;/code&gt; 是指对源头数据进行消费可以得到的结果，比如数组的长度，或数组中的正数等。&lt;/p&gt;

 &lt;p&gt;在这个例子中，  &lt;code&gt;Truth&lt;/code&gt; 就是   &lt;code&gt;newsItems&lt;/code&gt; 列表，而喜欢的   &lt;code&gt;newsItems&lt;/code&gt; 就是   &lt;code&gt;Computed Properties&lt;/code&gt; 。因此只要 newsItems 发生变化，就重新计算喜欢的 NewsItems。&lt;/p&gt;

 &lt;div&gt;  &lt;div&gt;   &lt;table&gt;    &lt;tr&gt;     &lt;td&gt;      &lt;pre&gt;1
2
3
4
5&lt;/pre&gt;&lt;/td&gt;     &lt;td&gt;      &lt;pre&gt;NewsFeedViewModel.news.asObservable().subscribe(onNext: { item in
    NewsFeedViewModel.favedNews.value = NewsFeedViewModel.news.value.currentItems.filter { (item) -&amp;gt; Bool in
         return item.hasFaved
    }
}).addDisposableTo(disposeBag)
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;

 &lt;h4&gt;喜欢功能的 View&lt;/h4&gt;
 &lt;p&gt;主要就是两件事：&lt;/p&gt;

 &lt;ol&gt;
    &lt;li&gt;点击 Fav 按钮时，调用 VM 的    &lt;code&gt;toggleFav&lt;/code&gt; 方法。&lt;/li&gt;
    &lt;li&gt;当 Fav 列表更新时，刷新 TableView。&lt;/li&gt;
&lt;/ol&gt;

 &lt;div&gt;  &lt;div&gt;   &lt;table&gt;    &lt;tr&gt;     &lt;td&gt;      &lt;pre&gt;1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32&lt;/pre&gt;&lt;/td&gt;     &lt;td&gt;      &lt;pre&gt;extension FavedViewController {
    func handleDataChange() {
        NewsFeedViewModel.favedNews.asObservable().subscribe(onNext:{[unowned self] item in
            self.tableView.reloadData()
        }).addDisposableTo(disposeBag)
    }
}

extension FavedViewController {
    override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -&amp;gt; Int {
        return NewsFeedViewModel.favedNews.value.count
    }
    
    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -&amp;gt; UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: &amp;quot;Cell&amp;quot;) as! NewsCell
        var newsItem: NewsItem = NewsFeedViewModel.favedNews.value[indexPath.row]
        
        cell.configure(newsItem) { [unowned self] (button) in
            if button.tag == 0 {
                button.tag = 1
                button.setTitle(&amp;quot;♥︎&amp;quot;, for: .normal)
            } else {
                button.tag = 0
                button.setTitle(&amp;quot;♡&amp;quot;, for: .normal)
            }
            self.viewModel.toggleFav(newsItem)
            self.tableView.reloadData()
        }
        
        return cell
    }
}
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;

 &lt;h3&gt;页面跳转&lt;/h3&gt;
 &lt;p&gt;页面间的跳转用到了   &lt;code&gt;Router&lt;/code&gt; ，也就是 open 一个 url 就能到达特定的页面，这么做的好处是可以和外部跳转进来的情况统一处理（因为从外部跳到某个 app 只能通过 openURL）。&lt;/p&gt;

 &lt;p&gt;但在内部直接输入 URL 总觉得不优雅，而且容易出错，将来如果要修改 URL 也不方便。因此做了一个简单的   &lt;code&gt;Router&lt;/code&gt; 来达到这个效果：&lt;/p&gt;

 &lt;div&gt;  &lt;div&gt;   &lt;table&gt;    &lt;tr&gt;     &lt;td&gt;      &lt;pre&gt;1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45&lt;/pre&gt;&lt;/td&gt;     &lt;td&gt;      &lt;pre&gt;
import Foundation
import UIKit

// 1
enum RouterTable: String {
    case home = &amp;quot;home&amp;quot;
    case detail = &amp;quot;detail/:id&amp;quot;
    
    func asController() -&amp;gt; UIViewController.Type {
        switch self {
        case .home:
            return NewsFeedViewController.self
        case .detail:
            return NewsDetailViewController.self
        }
    }
}

// 2
class Router {
    static func to(_ route: RouterTable, parameters: Dictionary&amp;lt;String, Any&amp;gt;?) -&amp;gt; Void {
        let viewController = route.asController().init()

        // 2.1
        if let parameters = parameters {
            for (key, value) in parameters {
                viewController.putExtra(key, value)
            }
        }

        //TODO: 添加 shouldBePushed 调用，比如有些页面需要先登录
        DispatchQueue.main.async {
            UINavigationController.current().pushViewController(viewController, animated: true)
        }
    }
}

// 3
extension Router {
    func parseURL(_ url: String) -&amp;gt; (RouterTable, Dictionary&amp;lt;String, String&amp;gt;?) {
        //TODO: add implementation
        return (.home, nil)
    }
}
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;

 &lt;p&gt;主要分为 3 部分：&lt;/p&gt;

 &lt;ol&gt;
    &lt;li&gt;这个跟 vue-router 里定义 url 和 components 的关系一样，主要是为了方便统一管理。&lt;/li&gt;
    &lt;li&gt;这里主要是把 enum 转换为对应的 Controller，因为限制了类型，也就不会出现找不到 VC 的情况。&lt;/li&gt;
    &lt;li&gt;这个是用来应对外部跳转进来的 URL，把它解析成    &lt;code&gt;RouterTable&lt;/code&gt;，统一逻辑。&lt;/li&gt;
&lt;/ol&gt;

 &lt;p&gt;针对 2 重点说一下，这个是最简实现，真实场景会比这复杂得多，比如有些页面是 present 出来的，有些页面 push 前需要先判断是否登录等等。&lt;/p&gt;

 &lt;p&gt;注意到   &lt;code&gt;2.1&lt;/code&gt; 的部分，这里有一个   &lt;code&gt;putExtra&lt;/code&gt; 方法，这是新添加的一个扩展，参考了 Android 的   &lt;code&gt;Intent&lt;/code&gt;    &lt;code&gt;putExtra&lt;/code&gt; 。实现如下：&lt;/p&gt;

 &lt;div&gt;  &lt;div&gt;   &lt;table&gt;    &lt;tr&gt;     &lt;td&gt;      &lt;pre&gt;1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19&lt;/pre&gt;&lt;/td&gt;     &lt;td&gt;      &lt;pre&gt;protocol ViewCotrollerIntent {
    func putExtra(_ key: String, _ value: Any)
    func getExtra(_ key: String) -&amp;gt; Any?
}

extension UIViewController: ViewCotrollerIntent {
    
    private struct IntentStorage {
        static var extra: [String:Any] = [:]
    }
    
    func putExtra(_ key: String, _ value: Any) {
        IntentStorage.extra[key] = value
    }
    
    func getExtra(_ key: String) -&amp;gt; Any? {
        return IntentStorage.extra[key]
    }
}
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;

 &lt;p&gt;由于 extension 不支持 associated properties，因此用 struct 做了个中转。这样，VC 之间的跳转如果要带上额外的参数，只要放到 extra 里即可。&lt;/p&gt;

 &lt;h3&gt;详情页&lt;/h3&gt;
 &lt;p&gt;详情页比较简单，只是展示一个 webview，这里比较棘手的问题是 model 数据的同步。由于详情页也可以修改   &lt;code&gt;NewsItem&lt;/code&gt; 的   &lt;code&gt;hasFaved&lt;/code&gt; 属性，这个改变需要能够实时同步到列表页，不然就会出现状态不同步的情况。&lt;/p&gt;

 &lt;p&gt;这块的设计也想了一段时间，Pinterest 采用的是  &lt;a href="https://medium.com/@Pinterest_Engineering/immutable-models-and-data-consistency-in-our-ios-app-d10e248bfef8"&gt;通知的方式&lt;/a&gt;，并且额外开发了一个用来支持这种方式的  &lt;a href="https://github.com/pinterest/plank"&gt;库&lt;/a&gt;，不想整的这么麻烦。本质需求是：当传过去的 model 发生变化时通知我。而 RxSwift 里的   &lt;code&gt;Variable&lt;/code&gt; 不是正好可以达到这个效果么？于是就有了基于   &lt;code&gt;Variable&lt;/code&gt; 的解决方案。&lt;/p&gt;

 &lt;div&gt;  &lt;div&gt;   &lt;table&gt;    &lt;tr&gt;     &lt;td&gt;      &lt;pre&gt;1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16&lt;/pre&gt;&lt;/td&gt;     &lt;td&gt;      &lt;pre&gt;extension NewsFeedViewController {
    override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        let newsItem: NewsItem = NewsFeedViewModel.news.value.currentItems[indexPath.row]
        let newsItemVariable = Variable&amp;lt;NewsItem&amp;gt;(newsItem)

        // 详情页可能会对这个 newsItemVariable 进行调整
        newsItemVariable.asObservable().subscribe(onNext: { [unowned self] item in
            // 找到这个 item 所在的 index，并进行替换
            self.viewModel.update(item: item)
            self.tableView.reloadData()
        }).addDisposableTo(disposeBag)

        // 带上这个 Variable 到新的 VC
        Router.to(.detail, parameters: [&amp;quot;model&amp;quot;: newsItemVariable])
    }
}
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;

 &lt;h4&gt;详情页 View 的处理&lt;/h4&gt;

 &lt;div&gt;  &lt;div&gt;   &lt;table&gt;    &lt;tr&gt;     &lt;td&gt;      &lt;pre&gt;1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43&lt;/pre&gt;&lt;/td&gt;     &lt;td&gt;      &lt;pre&gt;class NewsDetailViewController: UIViewController {
    override func viewDidLoad() {
        // favButton
        navigationItem.rightBarButtonItem = favButton
        favButton.rx.tap
            .subscribe(onNext: { [unowned self] item in
                self.viewModel.toggleFav()
            })
            .addDisposableTo(disposeBag)
        
        // 1
        if let id = self.getExtra(&amp;quot;id&amp;quot;) as? Int {
            // viewModel.load(id)
        }
        
        // 2
        if let model = self.getExtra(&amp;quot;model&amp;quot;) as? Variable&amp;lt;NewsItem&amp;gt; {
            favButton.title = model.value.hasFaved ? &amp;quot;♥︎&amp;quot; : &amp;quot;♡&amp;quot;
            viewModel.load(Int(model.value.id))
            NewsDetailViewModel.newsItem = model
        }
        
        handleDataChange()
    }

    // 3
    func handleDataChange() {
        NewsDetailViewModel.newsDetail.asObservable()
            .subscribe(onNext:{ [unowned self] item in
                if let item = item {
                    let request = URLRequest(url: URL(string: item.shareURL)!)
                    self.webView.loadRequest(request)
                }
            })
            .addDisposableTo(disposeBag)
        
        NewsDetailViewModel.newsItem?.asObservable()
            .subscribe(onNext: { [unowned self] item in
                self.favButton.title = item.hasFaved ? &amp;quot;♥︎&amp;quot; : &amp;quot;♡&amp;quot;
            })
            .addDisposableTo(disposeBag)
    }
}
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;

 &lt;ol&gt;
    &lt;li&gt;这里为通过外部 URL 进来的留一个入口。&lt;/li&gt;
    &lt;li&gt;通过    &lt;code&gt;getExtra&lt;/code&gt; 拿到    &lt;code&gt;Variable&lt;/code&gt; 后，接下来就交给 VM 了。&lt;/li&gt;
    &lt;li&gt;   &lt;code&gt;handleDataChange&lt;/code&gt; 做的事情就是响应 VM 的 properties 的变化，做一些 UI 上的调整。&lt;/li&gt;
&lt;/ol&gt;

 &lt;h3&gt;Service&lt;/h3&gt;
 &lt;p&gt;之前说过使用 Swift 提供 Service 会比较方便，都不需要在 App 启动时进行注册，利用自带的 Protocol Extension 就能达到效果。这个例子中没有用到，就举个其他的例子吧，以购物车为例：&lt;/p&gt;

 &lt;div&gt;  &lt;div&gt;   &lt;table&gt;    &lt;tr&gt;     &lt;td&gt;      &lt;pre&gt;1
2
3
4
5
6
7
8
9
10
11&lt;/pre&gt;&lt;/td&gt;     &lt;td&gt;      &lt;pre&gt;// 放在 Services 目录下的 Protocols.swift
protocol Cart {
    public func add(_ item: Item) -&amp;gt; Bool
}

// 具体的实现可以放到对应的页面
extension Cart {
    public func add(_ item: Item) -&amp;gt; Bool {
        // business logic
    }
}
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;

 &lt;p&gt;对于想要使用这个功能的开发来说，只要看   &lt;code&gt;Services/Protocols.swift&lt;/code&gt; 就行了。跟 Objective-C 不同，extension 里如果有两个相同的方法，编译器会直接报错，这样就避免了运行期可能出现多个实现的问题。&lt;/p&gt;

 &lt;h3&gt;Local Reasoning&lt;/h3&gt;
 &lt;p&gt;Local Reasoning 的意思是对于数据的改动都发生在某一个特定的单元。这也是使用 Value Type 的好处，因为如果使用 Reference Type，只要把其中的一个 Reference 给了出去，就不知道什么时间什么场景下数据会在外部被改变，就像给了你一张银行卡，今天看还剩 1 万，可能明天再去看就只剩 1 千了。&lt;/p&gt;

 &lt;p&gt;使用 VM 后，所有对数据的改动都发生在 VM 里面，同时对数据的消费也尽量在一个地方，方便维护。&lt;/p&gt;

 &lt;h2&gt;小结&lt;/h2&gt;
 &lt;p&gt;以上是我自己对「Right Architecture」的一些理解和实践，实际过程中肯定还有很多细节要调整，如果你有什么想法欢迎交流～&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 />
      <guid isPermaLink="true">https://itindex.net/detail/57083-the-right-way</guid>
      <pubDate>Thu, 22 Jun 2017 02:29:11 CST</pubDate>
    </item>
    <item>
      <title>iOS界面布局的核心以及TangramKit的介绍</title>
      <link>https://itindex.net/detail/56267-ios-%E7%95%8C%E9%9D%A2-%E5%B8%83%E5%B1%80</link>
      <description>&lt;div&gt;
  &lt;div&gt; &lt;/div&gt;
  &lt;div&gt;
   &lt;div&gt;    &lt;img alt="" src="http://upload-images.jianshu.io/upload_images/1432482-dcc38746f56a89ab.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240"&gt;&lt;/img&gt;&lt;/div&gt;
   &lt;h2&gt;前言&lt;/h2&gt;
   &lt;p&gt;    &lt;strong&gt;     &lt;em&gt;      &lt;a href="https://github.com/youngsoft/TangramKit" target="_blank"&gt;TangramKit&lt;/a&gt;&lt;/em&gt;&lt;/strong&gt;是iOS系统下用Swift编写的第三方界面布局框架。他集成了iOS的AutoLayout和SizeClass以及Android的五大容器布局体系以及HTML/CSS中的float和flex-box的布局功能和思想，目的是为iOS开发人员提供一套功能强大、多屏幕灵活适配、简单易用的UI布局解决方案。Tangram的中文即七巧板的意思，取名的寓意表明这个布局库可以非常灵巧和简单的解决各种复杂界面布局问题。他的同胞框架：    &lt;strong&gt;     &lt;em&gt;      &lt;a href="https://github.com/youngsoft/MyLinearLayout" target="_blank"&gt;MyLayout&lt;/a&gt;&lt;/em&gt;&lt;/strong&gt;是一套用objective-C实现的界面布局框架。二者的主体思想相同，实现原理则是通过扩展UIView的属性，以及重载    &lt;code&gt;layoutSubviews&lt;/code&gt;方法来完成界面布局，只不过在一些语法和属性设置上略有一些差异。可以这么说TangramKit是MyLayout布局库的一个升级版本。大家可以通过访问下面的github站点去下载最新的版本：&lt;/p&gt;
   &lt;ul&gt;
    &lt;li&gt;
     &lt;strong&gt;Swift版本TangramKit:&lt;/strong&gt;      &lt;a href="https://github.com/youngsoft/TangramKit" target="_blank"&gt;https://github.com/youngsoft/TangramKit&lt;/a&gt;
&lt;/li&gt;
    &lt;li&gt;
     &lt;strong&gt;OC版本MyLayout:&lt;/strong&gt;      &lt;a href="https://github.com/youngsoft/MyLinearLayout" target="_blank"&gt;https://github.com/youngsoft/MyLinearLayout&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;
   &lt;div&gt;
    &lt;img alt="" src="http://upload-images.jianshu.io/upload_images/1432482-3bfd0855a51b6d8e.gif?imageMogr2/auto-orient/strip"&gt;&lt;/img&gt;    &lt;br /&gt;    &lt;div&gt;TangramKit演示效果图&lt;/div&gt;
&lt;/div&gt;
   &lt;h2&gt;所见即所得和编码之争以及屏幕的适配&lt;/h2&gt;
   &lt;p&gt;在我10多年的开发生涯中，大部分时间都工作在客户端上。从DOS到Windows再到UNIX再到2010年接触iOS开发这6年多的时间中，总感觉一无所获，原因呢是觉没有什么积累。作为一个以编程为职业的人来说如果不留下什么可以值得为大家所知的东西的话，那将是一种职业上的遗憾。    &lt;br /&gt;就像每个领域都有工作细分一样，现在的编程人员也有明确分工：有一部分人做的是后端开发的工作，而有一部分人做的是前端开发的工作。二者相辅相成而完成了整个系统。后端开发的重点在于实现高性能和高可用，在数据处理上通常都是一个输入一个加工然后一个输出；而前端开发的重点在于实现界面流畅性和美观性，在数据处理上往往是多个输入一个加工和多个输出。在技术层面上后端处理的对象是多线程多进程以及数据，而前端处理的对象则是图形绘制和以及界面布局和动画特效。    &lt;br /&gt;这篇文章的重点是介绍界面布局的核心，因此其他部分就不再展开去说了。对于一个UI界面来说，好的界面布局体系往往能起到事半工倍的作用。PC设备上因为屏幕总是够大，比如VB,VF,PB,Dephi,AWT,Swing等语言或者环境下的应用开发非常方便，IDE环境中提供一个所见即所得的开发面板(form)，人们只要使用简单的拖拉拽动作就可把各种界面元素加入到form中就可以形成一个小程序了。而开发VC程序则相对麻烦，系统的IDE环境对可视化编程的支持没有那么的完善，因此大部分界面的构建都需要通过编码来完成。同时因为PC设备屏幕较大而且标准统一，因此几乎不存在界面要在各种屏幕尺寸适配的问题。唯一引起争议是可视化编程和纯代码编程的方式之争，这种争议也体现在iOS应用的开发身上，那就是用XIB和SB以及纯代码编写界面的好坏争议。关于这个问题个人的意见是各有各好：XIB/SB进行布局时容易上手且所见即所得，但缺乏灵活性和可定制化；而纯代码则灵活性高可定制化强，缺点是不能所见即所得和代码维护以及系统分层模糊。    &lt;br /&gt;再回到屏幕适配的话题来说，如果说PC时代编程屏幕尺寸适配不是很重要的工作，那么到了移动设备时代则不一样了，适配往往成为整个工作的重点和难点。主要的原因是设备的屏幕尺寸和设备分辨率的多样性的差异，而且要求在这么小的屏幕上布局众多的要素，同时又要求界面美观和友好的用户体验，这就非常考验产品以及UI/UE人员和开发人员的水平，同时这部分工作也占用了开发者的大部分时间。在现有的两个主流的移动平台上，Android系统因为本身硬件平台差异性的原因，为了解决这些差异性而设计了一套非常方便的和友好的界面布局体系。它提出了布局容器的概念，也就是有专门职责的布局容器视图来管理和排列里面的子视图，根据实际中的应用场景而把这些负责布局的容器视图分类抽象出了线性布局、相对布局、框架布局、表格布局、绝对布局这5大容器布局，而这些也就构成了Android系统布局体系的核心实现。也正是这套布局机制使得Android系统能够方便的胜任多种屏幕尺寸和分辨率在不同硬件设备上的UI界面展示。而对于iOS的开发人员来说，早期的设备只有单一的3.5in大小且分辨率也只有480x320和960x640这两种类型的设备，因此开发人员只需要采用绝对定位的方式通过视图的    &lt;code&gt;frame&lt;/code&gt;属性设置来实现界面的布局，根本不需要考虑到屏幕的适配问题。但是这一切从苹果后续依次发布iPhone4/5/6/7系列的设备后被打破了，整个iOS应用的开发也需要考虑到多屏幕尺寸和多分辨率的问题了，这样原始的    &lt;code&gt;frame&lt;/code&gt;方法进行布局设置将不能满足这些多屏幕的适配问题了，因此iOS提出了一套新的界面布局体系：AutoLayout以及SizeClass. 这套机制通过设置视图之间的位置和尺寸的约束以及对屏幕尺寸进行分类的方式来完成界面的布局和屏幕的适配工作。    &lt;br /&gt;尽管如此, 虽然两个移动端平台都提供了自己独有且丰富的界面布局体系，但对于移动客户端开发人员来说界面布局和适配仍然是我们在开发中需要重点关注的因素之一。&lt;/p&gt;
   &lt;h2&gt;布局的核心&lt;/h2&gt;
   &lt;p&gt;我们知道，在界面开发中我们直接操作的对象是视图，    &lt;strong&gt;视图可以理解为一个具有特定功能的矩形区块，因此所谓的布局的本质就是为视图指定某个具体的尺寸以及指定其排列在屏幕上的位置&lt;/strong&gt;。因此布局的动作就分为两个方面：一个是指定视图的尺寸，一个是指定视图的位置。&lt;/p&gt;
   &lt;div&gt;
    &lt;img alt="" src="http://upload-images.jianshu.io/upload_images/1432482-7001d042ccb9040f.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240"&gt;&lt;/img&gt;    &lt;br /&gt;    &lt;div&gt;视图的尺寸和位置&lt;/div&gt;
&lt;/div&gt;
   &lt;h3&gt;视图的尺寸&lt;/h3&gt;
   &lt;p&gt;视图的尺寸就是指视图矩形块的大小，为了表征视图的大小我们称在屏幕水平方向的尺寸大小为    &lt;strong&gt;宽度&lt;/strong&gt;，而称在屏幕垂直方向的尺寸大小为    &lt;strong&gt;高度&lt;/strong&gt;，因此一个视图的尺寸我们就可以用宽度和高度两个维度的值来描述了，宽度和高度的单位我们称之为点。    &lt;strong&gt;UIView中用     &lt;code&gt;bounds&lt;/code&gt;属性的size部分来描述视图的尺寸(bounds属性的origin部分后面会介绍到)&lt;/strong&gt;。 对于屏幕尺寸来说同样也用宽度和高度来描述。在视图层次体系结构中的顶层视图的尺寸和屏幕的尺寸是一致的，为了描述这个特殊的顶层视图我们将这个顶层根视图称之为    &lt;strong&gt;窗口&lt;/strong&gt;，窗口的尺寸和屏幕的尺寸一样大，同时窗口是一切视图的容器视图。一个视图的尺寸我们可以用一个具体的数值来描述，比如某个视图的宽度和高度分别为:    &lt;em&gt;100x200&lt;/em&gt;。我们称这种定义的方式为    &lt;strong&gt;绝对值类型的尺寸&lt;/strong&gt;。但是在实际中我们的一些视图的尺寸并不能够一开始就被明确，原因是这些视图的尺寸大小和其他视图的尺寸大小有关，也就是说视图的尺寸依赖于另外一个视图或者另外一组视图。比如说有A和B两个视图，我们定义A视图的宽度和B视图的宽度相等，而A视图的高度则是B视图高度的一半。也就是可以表述为如下：&lt;/p&gt;
   &lt;pre&gt;    &lt;code&gt;A.bounds.size.width = B.bounds.size.width
A.bounds.size.height = B.bounds.size.height /2

//父视图S的高度等于里面子视图A,B的高度的总和
S.bounds.size.height = A.bounds.size.height + B.bounds.size.height&lt;/code&gt;&lt;/pre&gt;
   &lt;p&gt;我们称为这种尺寸的定义方式为    &lt;strong&gt;相对值类型的尺寸&lt;/strong&gt;。在相对值类型的尺寸中, 视图某个维度的尺寸所依赖的另外一个视图可以是它的兄弟视图，也可以是它的父视图，也可以是它的子视图，甚至可以是它自身的其他维度。 这种视图尺寸的依赖关系是可以传递和递归的，比如A依赖于B，而B右依赖于C。 但是这种递归和传递关系不能形成一个闭环依赖，也就是说在依赖关系的最终节点视图的尺寸的值必须是一个绝对值类型或者特定的相对值类型(wrap包裹值)，否则的话我们将形成约束冲突而进入死循环的场景。&lt;/p&gt;
   &lt;div&gt;
    &lt;img alt="" src="http://upload-images.jianshu.io/upload_images/1432482-8ab503add7455171.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240"&gt;&lt;/img&gt;    &lt;br /&gt;    &lt;div&gt;两种尺寸约束依赖&lt;/div&gt;
&lt;/div&gt;
   &lt;p&gt;视图的尺寸之间的依赖关系还有两种特定的场景：&lt;/p&gt;
   &lt;ul&gt;
    &lt;li&gt;某个视图的尺寸依赖于里面所有子视图的尺寸的大小或者依赖于视图内所展示的内容的尺寸，我们称这种依赖为     &lt;strong&gt;包裹(wrap)&lt;/strong&gt;。&lt;/li&gt;
    &lt;li&gt;某个视图的尺寸依赖于所在父视图的尺寸减去其他兄弟视图所占用的尺寸的剩余尺寸也就是说尺寸等于父视图的尺寸和其兄弟视图尺寸的差集，我们称这种依赖为     &lt;strong&gt;填充(fill)&lt;/strong&gt;。&lt;/li&gt;
&lt;/ul&gt;
   &lt;p&gt;可以看出包裹和填充尺寸是相对值类型中的两种特殊的类型，他所依赖的视图并不是某个具体的视图，而是一些相关的视图的集合。&lt;/p&gt;
   &lt;p&gt;为了表征视图的尺寸以及尺寸可以设置的值的类型，我们就需要对尺寸进行建模，在TangramKit框架中    &lt;strong&gt;     &lt;code&gt;TGLayoutSize&lt;/code&gt;&lt;/strong&gt;类就是一个尺寸类，这个类里面的equal方法则是用来设置视图尺寸的各种类型的值：包括绝对值类型，相对值类型，以及包裹和填充的值类型等等。同时我们对UIView扩展出了两个属性    &lt;code&gt;tg_width, tg_height&lt;/code&gt;分别用来表示视图的布局宽度和布局高度。他其实是对原生的视图    &lt;code&gt;bounds&lt;/code&gt;属性中的size部分进行了扩充和延展。原始的    &lt;code&gt;bounds&lt;/code&gt;属性中的size部分只能设置绝对值类型的尺寸，而不能设置相对值类型的尺寸。&lt;/p&gt;
   &lt;h3&gt;视图的位置&lt;/h3&gt;
   &lt;p&gt;当一个视图的尺寸确定后，接下来我们就需要确定视图所在的位置了。所谓位置就是指视图在屏幕中的坐标位置，屏幕中的坐标分为水平坐标也就是x轴坐标，和垂直坐标也就是y轴坐标。而这个坐标原点在不同的系统中有区别：iOS系统采用左手坐标系，原点都是在左上角，并且规定y轴在原点以下是正坐标轴，而原点以上是负坐标轴，而x轴则在原点右边是正坐标轴，原点左边是负坐标轴。OSX系统则采用右手坐标系，原点在左下角，并且规定y轴在原点以上是正坐标轴，而在原点以下是负坐标轴，而x轴则在原点右边是正坐标轴，原点左边是负坐标轴。&lt;/p&gt;
   &lt;div&gt;
    &lt;img alt="" src="http://upload-images.jianshu.io/upload_images/1432482-806f592075482456.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240"&gt;&lt;/img&gt;    &lt;br /&gt;    &lt;div&gt;不同的坐标系&lt;/div&gt;
&lt;/div&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;ul&gt;
    &lt;li&gt;
     &lt;p&gt;第一种以屏幕坐标系作为参照来确定的位置称为绝对位置，也就是以屏幕的左上角作为原点，每个视图的位置都是距离屏幕左上角原点的一个偏移值。这种绝对位置的设置方式的优点是所有视图的参照物都是一致的，便于比较和计算，但缺点是对于那些多层次结构的视图以及带滚动效果的视图来说位置的确定则总是需要进行动态的变化和计算。比如某个滚动视图内的所有子视图在滚动时都需要重新去计算自己的位置。&lt;/p&gt;
&lt;/li&gt;
    &lt;li&gt;
     &lt;p&gt;第二种以父视图坐标系作为参照来确定的位置称为相对位置，每个子视图的位置都是距离父视图左上角原点的一个偏移值。这样的好处就是每个子视图都不再需要关心屏幕的原点，而只需要以自己的父视图为原点进行位置的计算就可以了，这种方式是目前大部分布局体系里面采用的定位方式，也是最方便的定位方式，缺点是不同层次之间的视图的位置在进行比较时需要一步步的往上进行转换，直到转换到在窗口中的位置为止。我们称这种以父视图坐标系为原点进行定位的位置称为      &lt;strong&gt;边距&lt;/strong&gt;，也就是离父视图边缘的距离。&lt;/p&gt;
&lt;/li&gt;
    &lt;li&gt;第三种以兄弟视图坐标系作为参照来确定的位置称为偏移位置，子视图的位置是在关联的兄弟视图的位置的基础之上的一个偏移值。比如A视图在B视图的右边偏移5个点，则表示为A视图的左边距离B视图的右边5个点的距离。我们称这种坐标体系下的位置为     &lt;strong&gt;间距&lt;/strong&gt;，也就是指定的是视图之间的距离作为视图的位置。采用间距的方式进行定位只适合于同一个父视图之间的兄弟视图之间的定位方式。     &lt;br /&gt;     &lt;div&gt;
      &lt;img alt="" src="http://upload-images.jianshu.io/upload_images/1432482-a1b7c854def1df80.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240"&gt;&lt;/img&gt;      &lt;br /&gt;      &lt;div&gt;各种坐标系下的定位值&lt;/div&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;/ul&gt;
   &lt;p&gt;上面的三种定位方式各有优缺点，我们可以在实际中结合各种定位方式来完成视图的位置设定。&lt;/p&gt;
   &lt;p&gt;上面我们介绍了定位时位置所基于的坐标系，因为视图并不是一个点而是一个矩形区块，所以我们必须要明确的是视图本身这个区块的哪个点来进行位置的设定。 在这里我们就要介绍视图内的坐标系。我们知道视图是一个矩形的区域，里面由无数个点构成。假如我们以视图左上角作为坐标原点的话，那么视图内的任何一点都可以用水平方向的坐标值和垂直方向的坐标值来表示。对于水平方向的坐标值来说最左边位置的点的坐标值是0，最右边位置的点的坐标值是视图的宽度，中间位置的坐标点的值是宽度的一半，对于垂直方向的坐标值来说最上边位置的点的坐标值是0，最下边位置的点的坐标值是视图的高度，中间位置的坐标点的值是高度的一半。我们称这几个特殊的坐标点为    &lt;strong&gt;方位&lt;/strong&gt;。因此一个视图一共有9个方位点分别是：左上、左中、左下、中上、中中、中下、右上、右中、右下。&lt;/p&gt;
   &lt;div&gt;
    &lt;img alt="" src="http://upload-images.jianshu.io/upload_images/1432482-baea2afd7ebf5552.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240"&gt;&lt;/img&gt;    &lt;br /&gt;    &lt;div&gt;视图的九个方位&lt;/div&gt;
&lt;/div&gt;
   &lt;p&gt;通过对方位点的定义，我们就不再需要去关心这些点的具体的坐标值了，因为他描述了视图的某个特定的部位。而为了方便计算和处理，我们一般只需要指出视图内某个方位点在参照视图的坐标系里面的水平坐标轴和垂直坐标轴中的位置就可以完成视图的位置定位了，因为只要确定了这个方位点的在参照视图坐标系里面的位置，就可以计算出这个视图内的任意的一个点在参照视图坐标轴里面的位置。    &lt;strong&gt;所谓的位置定位就是把一个视图内坐标系的某个点的坐标值映射为参照视图坐标系里面的坐标值的过程&lt;/strong&gt;。&lt;/p&gt;
   &lt;div&gt;
    &lt;img alt="" src="http://upload-images.jianshu.io/upload_images/1432482-e7f60ace997be0cc.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240"&gt;&lt;/img&gt;    &lt;br /&gt;    &lt;div&gt;视图的坐标转换&lt;/div&gt;
&lt;/div&gt;
   &lt;p&gt;iOS中UIView提供了一个属性    &lt;code&gt;center&lt;/code&gt;，    &lt;strong&gt;     &lt;code&gt;center&lt;/code&gt;属性的意义就是定义视图内中心点这个方位在父视图坐标系中的坐标值&lt;/strong&gt;。我们再来考察一下UIView的    &lt;code&gt;bounds&lt;/code&gt;属性，上面的章节中我们有介绍    &lt;code&gt;bounds&lt;/code&gt;中的size部分用来描述一个视图的尺寸，而origin部分又是用来描述什么呢？ 我们知道在左手坐标系里面，一个视图内的左上角方位的坐标值就是原点的坐标值，默认情况下原点的坐标值是(0,0)。但是这个定义不是一成不变的，也就是说原点的坐标值不一定是(0,0)。    &lt;strong&gt;一个视图     &lt;code&gt;bounds&lt;/code&gt;里面的origin部分所表达的意义就是视图内左上角的坐标值，size部分所表达的意义就是视图本身的尺寸&lt;/strong&gt;。这样我们就可以通过下面的公式得出一个视图内9个方位(再次强调方位的概念是一个视图内的坐标点的位置)的坐标值：&lt;/p&gt;
   &lt;pre&gt;    &lt;code&gt;左上方位 = (A.bounds.origin.x, A.bounds.origin.y)
左中方位 = (A.bounds.origin.x,  A.bounds.origin.y + A.bounds.size.height / 2)
左下方位 = (A.bounds.origin.x, A.bounds.origin.y + A.bounds.size.height)
中上方位 = (A.bounds.origin.x + A.bounds.size.width/2, A.bounds.origin.y)
中中方位 = (A.bounds.origin.x + A.bounds.size.width/2, A.bounds.origin.y + A.bounds.size.height/2)
中下方位 = (A.bounds.origin.x + A.bounds.size.width/2, A.bounds.origin.y + A.bounds.size.height)
右上方位 = (A.bounds.origin.x + A.bounds.size.width, A.bounds.origin.y)
右中方位 = (A.bounds.origin.x + A.bounds.size.width,A.bounds.origin.y + A.bounds.size.height/2)
右下方位 = (A.bounds.origin.x + A.bounds.size.width,A.bounds.origin.y + A.bounds.size.height)&lt;/code&gt;&lt;/pre&gt;
   &lt;p&gt;对于位置定义来说TangramKit中的    &lt;strong&gt;     &lt;code&gt;TGLayoutPos&lt;/code&gt;&lt;/strong&gt;类就是一个对位置进行建模的类。TGLayoutPos类同时支持采用父视图作为参考系和以兄弟视图作为参考系的定位方式，这可以通过为其中的equal方法设置不同类型的值来决定其定位方式。为了实现视图定位我们也为UIView扩展出了3个水平方位的属性：    &lt;strong&gt;tg_left, tg_centerX,tg_right&lt;/strong&gt;来表示左中右三个方位对象。3垂直方位的属性：    &lt;strong&gt;tg_top, tg_centerY,tg_bottom&lt;/strong&gt;来表示上、中、下三个方位。这6个方位对象将比原生的    &lt;code&gt;center&lt;/code&gt;属性提供更加强大和丰富的位置定位能力。&lt;/p&gt;
   &lt;p&gt;iOS系统的原生布局体系里面是通过    &lt;code&gt;bounds&lt;/code&gt;属性和    &lt;code&gt;center&lt;/code&gt;属性来进行视图的尺寸设置和位置设置的。bounds用来指定视图内的左上角方位的坐标值，以及视图的尺寸，而center则用来指定视图的中心点方位在父视图这个坐标体系里面的坐标值。为了简化设置UIView提供了一个简易的属性    &lt;code&gt;frame&lt;/code&gt;可以用来直接设置一个视图的尺寸和位置，    &lt;strong&gt;frame中的origin部分指定视图左上角方位在父视图坐标系里面的坐标值，而size部分则指定了视图本身的尺寸&lt;/strong&gt;。    &lt;code&gt;frame&lt;/code&gt;属性并不是一个实体属性而是一个计算类型的属性，在我们没有对视图进行坐标变换时(视图的transform未设置时)我们可以得到如下的    &lt;code&gt;frame&lt;/code&gt;属性的伪代码实现:&lt;/p&gt;
   &lt;pre&gt;    &lt;code&gt;public var frame:CGRect
{
   get {
       let x = self.center.x  - self.bounds.size.width / 2
       let y = self.center.y  - self.bounds.size.height / 2
       let width = self.bounds.size.width
       let height = self.bounds.size.height
       return CGRect(x:x, y:y, width:width, height:height) 
  }
  set {
       self.center = CGPoint(x:newValue.origin.x  +  newValue.size.width / 2, y: newValue.origin.y +  newValue.size.height / 2)
       self.bounds.size  = newValue.size
  }
}&lt;/code&gt;&lt;/pre&gt;
   &lt;p&gt;综上所述，我们可以看出，所谓视图布局的核心，就是确定一个视图的尺寸，和确定视图在参考视图坐标系里面的坐标位置。为了灵活处理和计算，视图的尺寸可以设置为绝对值类型，也可以设置为相对值类型，也可以设置为特殊的包裹或者填充值类型；视图的位置则可以指定视图中的任意的方位，以及设置这个方位的点在窗口坐标系或者父视图坐标系或者兄弟坐标系中的坐标值。正是提供的这些多样的设置方式，我们就可以在不同的场景中使用不同的设置来完成各种复杂界面的布局。&lt;/p&gt;
   &lt;h2&gt;Android的布局体系&lt;/h2&gt;
   &lt;h3&gt;屏幕尺寸、PPI、DPI&lt;/h3&gt;
   &lt;h3&gt;布局框架结构&lt;/h3&gt;
   &lt;h3&gt;layout布局文件。&lt;/h3&gt;
   &lt;h3&gt;5大布局类&lt;/h3&gt;
   &lt;p&gt;...敬请期待&lt;/p&gt;
   &lt;h2&gt;HTML/CSS的布局体系&lt;/h2&gt;
   &lt;h3&gt;CSS定位方式&lt;/h3&gt;
   &lt;h3&gt;浮动float&lt;/h3&gt;
   &lt;h3&gt;flex-box bootstrap&lt;/h3&gt;
   &lt;p&gt;...敬请期待&lt;/p&gt;
   &lt;h2&gt;iOS布局体系&lt;/h2&gt;
   &lt;h3&gt;frame,bounds,center&lt;/h3&gt;
   &lt;h3&gt;XIB和storyboard&lt;/h3&gt;
   &lt;h3&gt;AutoLayout和SizeClass&lt;/h3&gt;
   &lt;p&gt;...敬请期待&lt;/p&gt;
   &lt;h2&gt;TangramKit布局框架&lt;/h2&gt;
   &lt;p&gt;在您不了解TangramKit之前，可以先通过下面一个例子来感受和体验一下TangramKit的布局构建语法：&lt;/p&gt;
   &lt;blockquote&gt;
    &lt;ul&gt;
     &lt;li&gt;有一个容器视图S的宽度是100而高度则等于由四个从上到下依次排列的子视图A,B,C,D的高度总和。&lt;/li&gt;
     &lt;li&gt;子视图A的左边距占用父视图宽度的20%，而右边距则占用父视图宽度的30%，高度则等于自身的宽度。&lt;/li&gt;
     &lt;li&gt;子视图B的左边距是40，宽度则占用父视图的剩余宽度，高度是40。&lt;/li&gt;
     &lt;li&gt;子视图C的宽度占用父视图的所有宽度，高度是40。&lt;/li&gt;
     &lt;li&gt;子视图D的右边距是20，宽度是父视图宽度的50%，高度是40。&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
   &lt;div&gt;
    &lt;img alt="" src="http://upload-images.jianshu.io/upload_images/1432482-786218241645d3c4.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240"&gt;&lt;/img&gt;    &lt;br /&gt;    &lt;div&gt;演示效果图&lt;/div&gt;
&lt;/div&gt;
   &lt;p&gt;代码实现如下：&lt;/p&gt;
   &lt;pre&gt;    &lt;code&gt;    let S = TGLinearLayout(.vert)
    S.tg_vspace = 10
    S.tg_width.equal(100)
    S.tg_height.equal(.wrap)

    let A = UIView()
    A.tg_left.equal(20%)
    A.tg_right.equal(30%)
    A.tg_height.equal(A.tg_width)
    S.addSubview(A)

    let B = UIView()
    B.tg_left.equal(40)
    B.tg_width.equal(.fill)
    B.tg_height.equal(40)
    S.addSubview(B)

    let C = UIView()
    C.tg_width.equal(.fill)
    C.tg_height.equal(40)
    S.addSubview(C)

    let D = UIView()
    D.tg_right.equal(20)
    D.tg_width.equal(50%)
    D.tg_height.equal(40)
    S.addSubview(D)&lt;/code&gt;&lt;/pre&gt;
   &lt;p&gt;因为TangramKit对布局位置类和布局尺寸类的方法重载了运算符:    &lt;code&gt;~=、&amp;gt;=、&amp;lt;=、+=、-=、*=、/=&lt;/code&gt; 所以您可以用更加简洁的代码进行编写：&lt;/p&gt;
   &lt;pre&gt;    &lt;code&gt;    let S = TGLinearLayout(.vert)
    S.tg_vspace = 10
    S.tg_width ~=100
    S.tg_height ~=.wrap

    let A = UIView()
    A.tg_left ~=20%
    A.tg_right ~=30%
    A.tg_height ~=A.tg_width
    S.addSubview(A)

    let B = UIView()
    B.tg_left ~=40
    B.tg_width ~=.fill
    B.tg_height ~=40
    S.addSubview(B)

    let C = UIView()
    C.tg_width ~=.fill
    C.tg_height ~=40
    S.addSubview(C)

    let D = UIView()
    D.tg_right ~=20
    D.tg_width ~=50%
    D.tg_height ~=40
    S.addSubview(D)&lt;/code&gt;&lt;/pre&gt;
   &lt;p&gt;通过上面的代码，您可以看出用TangramKit实现的布局代码和上面场景描述文本几乎相同，非常的利于阅读和理解。那么这些系统又是如何实现的呢？&lt;/p&gt;
   &lt;h3&gt;实现原理&lt;/h3&gt;
   &lt;p&gt;我们知道在对任何一个视图进行布局时，最终都是通过设置视图的尺寸和视图的位置来完成的。在iOS中我们可以通过UIView的    &lt;code&gt;bounds&lt;/code&gt;属性来完成视图的尺寸设置，而通过    &lt;code&gt;center&lt;/code&gt;属性来完成视图的位置设置。为了进行简单的操作，系统提供了    &lt;code&gt;frame&lt;/code&gt;这个属性来简化对尺寸和位置的设置。这个过程不管是原始的方法还是后续的AutoLayout其实现的最终机制都是一致的。每当一个视图的尺寸改变或者要求重新布局时，系统都会调用视图的方法:&lt;/p&gt;
   &lt;pre&gt;    &lt;code&gt;open func layoutSubviews()&lt;/code&gt;&lt;/pre&gt;
   &lt;p&gt;而我们可以在UIView的派生类中重载上面的方法来实现对这个视图里面的所有子视图的重新布局，至于如何布局子视图则是需要根据应用场景而定。在编程时我们经常会用到一些视图，这种视图只是负责将里面的子视图按照某种规则进行排列和布局，而别无其他的作用。因此我们称这种视图为    &lt;strong&gt;容器视图&lt;/strong&gt;或者称为    &lt;strong&gt;布局视图&lt;/strong&gt;。TangramKit框架对种视图进行了建模而提供了一个从UIView派生的布局视图基类    &lt;strong&gt;TGBaseLayout&lt;/strong&gt;。这个类的作用就是专门负责对加入到其中的所有子视图进行布局排列，它是通过重载    &lt;code&gt;layoutSubviews方法&lt;/code&gt;来完成这个工作的。刚才我们说过如何排列容器视图中的子视图是要根据具体的应用场景而定， 比如有可能是所有子视图从上往下按照添加的顺序依次排列，或者子视图按照某种约束依赖关系来进行布局排列，或者子视图需要多行多列的排列等等。因此我们对常见的布局应用场景进行了抽象，通过建立不同的TGBaseLayout的派生类来实现不同的布局处理：&lt;/p&gt;
   &lt;ul&gt;
    &lt;li&gt;线性布局TGLinearLayout：线性布局里面的所有子视图都按照添加的顺序依次从上到下或者依次从左到右进行排列。根据排列的方向可以分为垂直线性布局和水平线性布局。线性布局和iOS9上的UIStackView以及Android中的线性布局LinearLayout提供一样的功能。&lt;/li&gt;
    &lt;li&gt;框架布局TGFrameLayout: 框架布局里面的所有子视图布局时和添加的顺序无关，而是按照设定的位置停靠在布局视图的：左上、左中、左下、中上、中中、中下、右上、右中、右下、填充这个10个方位中的任何一个位置上。框架布局里面的子视图只跟框架布局视图的边界建立约束关系。框架布局和Android中的框架布局FrameLayout提供一样的功能。&lt;/li&gt;
    &lt;li&gt;表格布局TGTableLayout：表格布局里面的子视图可以进行多行多列的排列。在使用时要先添加行，然后再在行里面添加列，每行的列数可以随意确定。因为表格布局是线性布局TGLinearLayout的派生类，所以表格布局也分为垂直表格布局和水平表格布局。垂直表格布局中的行是从上到下，而列则是从左到右排列；水平表格布局中的行是从左到右，而列是从上到下排列的。表格布局和Android中的表格布局TableLayout以及HTML中的table,tr,td元素提供一样的功能。&lt;/li&gt;
    &lt;li&gt;相对布局TGRelativeLayout: 相对布局里面的子视图和添加的顺序无关，而是按照子视图之间设定的尺寸约束依赖和位置约束依赖进行布局排列。因此相对布局里面的所有子视图都要设置位置和尺寸的约束和依赖关系。相对布局和iOS的AutoLayout以及Android中的相对布局RelativeLayout提供一样的功能。&lt;/li&gt;
    &lt;li&gt;
     &lt;p&gt;流式布局TGFlowLayout: 流式布局里面的子视图按照添加的顺序依次从某个方向排列，而当遇到了这个方向上的排列数量限制或者容器的尺寸限制后将会另起      &lt;em&gt;一行&lt;/em&gt;，而重新按照原先的方向依次排列。最终这个布局中的子视图将形成多行多列的排列展示。流式布局和线性布局的区别是，线性布局只是单行或者单列的，而流式布局则是多行多列。流式布局和表格布局的区别是，表格布局有明确行的概念，在使用前要添加行再添加列，而流式布局则没有明确行的概念，由布局自动生成行和列。根据排列的方向和限制的规则，流式布局分为垂直数量约束布局、垂直内容约束布局、水平数量约束布局、水平内容约束布局四种布局。流式布局实现了HTML/CSS3中的flex-box的子集的功能。&lt;/p&gt;
&lt;/li&gt;
    &lt;li&gt;
     &lt;p&gt;浮动布局TGFloatLayout：浮动布局里面的子视图按照添加的顺序，并且按照每个子视图自身设定的浮动规则向某个方向进行浮动停靠。当子视图的尺寸无法容纳到布局视图的剩余空间时，则会自动寻找一个能够容纳自身尺寸的最佳位置进行浮动停靠。浮动布局里面的子视图并不是有规则的多行多列的排列。根据子视图可以浮动的方向浮动布局分为垂直浮动布局和水平浮动布局。浮动布局和HTML/CSS中的float定位实现了相同的功能。&lt;/p&gt;
&lt;/li&gt;
    &lt;li&gt;路径布局TGPathLayout: 路径布局里面的子视图按照一个提供的数学函数得到的曲线路径等距离的根据添加的顺序依次排列。所有的子视图的位置都是根据函数曲线中距离相等的点而确定的。路径布局提供了直角坐标系、参数方式、极坐标系三种曲线的构建方法。路径布局是TangramKit中的独有的一种布局。&lt;/li&gt;
&lt;/ul&gt;
   &lt;p&gt;上述的7个派生类分别的实现了大部分的不同的应用场景。在每个派生类的    &lt;code&gt;layoutSubviews&lt;/code&gt;的实现中都按照描述的规则来设置子视图的尺寸    &lt;code&gt;bounds&lt;/code&gt;和位置    &lt;code&gt;center&lt;/code&gt;属性。也就是说最终的子视图的尺寸和位置是在布局视图中的    &lt;code&gt;layoutSubviews&lt;/code&gt;中进行设置的。那么我们就必须要提供另外一套子视图的布局尺寸和布局位置的设置方法，以便在布局视图布局时将子视图设置好的布局尺寸和布局位置转化为真实的视图尺寸和视图位置。为此TangramKit专门提供了一个视图的布局尺寸类    &lt;strong&gt;TGLayoutSize&lt;/strong&gt;用来进行子视图的布局尺寸的设置，一个视图的布局位置类    &lt;strong&gt;TGLayoutPos&lt;/strong&gt;用来进行子视图的布局位置的设置。我们对UIView建立了一个extension。分别扩展出了2个布局尺寸对象和6个布局位置对象：&lt;/p&gt;
   &lt;pre&gt;    &lt;code&gt;extension UIView
{

   //左边位置
  var tg_left:TGLayoutPos{get}
   //上边位置   
  var tg_top:TGLayoutPos{get}
   //右边位置    
  var tg_right:TGLayoutPos{get}
   //下边位置    
  var tg_bottom:TGLayoutPos{get}
   //水平中心点位置    
  var tg_centerX:TGLayoutPos{get}
   //垂直中心点位置    
  var tg_centerY:TGLayoutPos{get}

  //宽度尺寸
  var tg_width:TGLayoutSize{get}
  //高度尺寸    
  var tg_height:TGLayoutSize{get}
}&lt;/code&gt;&lt;/pre&gt;
   &lt;p&gt;也就是说我们将不再直接设置子视图的    &lt;code&gt;bounds&lt;/code&gt;和    &lt;code&gt;center&lt;/code&gt;(这两个属性只会在布局视图中的    &lt;code&gt;layoutSubviews&lt;/code&gt;中设置)属性了，而是直接操作UIView扩展出来的布局位置对象和布局尺寸对象。如果把布局视图的    &lt;code&gt;layoutSubviews&lt;/code&gt;比作一个数学函数的话，那么我们就能得到如下的方程式：&lt;/p&gt;
   &lt;pre&gt;    &lt;code&gt;UIView.center = TGXXXLayout.layoutSubviews(UIView.tg_left, UIView.tg_top, UIView.tg_right, UIView.tg_bottom,UIView.tg_centerX,UIView.tg_centerY)&lt;/code&gt;&lt;/pre&gt;
   &lt;pre&gt;    &lt;code&gt;UIView.bounds = TGXXXLayout.layoutSubviews(UIView.tg_width, UIView.tg_height)&lt;/code&gt;&lt;/pre&gt;
   &lt;p&gt;因此我们可以看出不同的TGBaseLayout的派生类因为里面的布局方法不相同，而导致子视图的位置和尺寸的计算方法不同，从而得到了我们想要的效果。那么为什么要用6个布局位置对象和2个布局尺寸对象来设置子视图的位置和尺寸而不直接用    &lt;code&gt;bounds&lt;/code&gt;和    &lt;code&gt;center&lt;/code&gt;呢？ 原因在于bounds和center只提供了有限的设置方法而布局位置对象和布局尺寸对象则提供了功能更加强大的设置方法，而这些方法又可以简化我们的编程，以及可以很方便的适配各种不同尺寸的屏幕。(还记得我们上面的例子里面，尺寸和位置可以设置为数值,.wrap, .fill,以及百分比的值吗？)。&lt;/p&gt;
   &lt;p&gt;TangramKit为了存储这些扩展的布局位置和布局尺寸对象，内部是使用了objc的runtime机制提供的动态属性创建的方法：&lt;/p&gt;
   &lt;pre&gt;    &lt;code&gt;public func objc_getAssociatedObject(_ object: Any!, _ key: UnsafeRawPointer!) -&amp;gt; Any!&lt;/code&gt;&lt;/pre&gt;
   &lt;p&gt;系统通过这个方法来关联视图对象的那6个布局位置和2个布局尺寸对象。&lt;/p&gt;
   &lt;p&gt;上面的代码中我们看到了布局容器视图通过    &lt;code&gt;layoutSubviews&lt;/code&gt;方法来实现对子视图的重新布局。而且也提到了当容器视图的尺寸发生变化时也会激发对    &lt;code&gt;layoutSubviews&lt;/code&gt;的调用。除了自动激发外，我们可以通过手动调用布局视图的    &lt;code&gt;setNeedLayout&lt;/code&gt;方法来实现布局视图的    &lt;code&gt;layoutSubviews&lt;/code&gt;调用。当我们在设置子视图的布局位置和布局尺寸时，系统内部会在设置完成后调用布局视图的    &lt;code&gt;setNeedLayout&lt;/code&gt;的方法，因此只要对子视图的布局位置和布局尺寸进行设置都会重新激发布局视图的布局视图。那么对子视图的frame,bounds,center真实位置和尺寸的改变呢？我们也要激发布局视图的重新布局。为了解决这个问题，我们引入了    &lt;code&gt;KVO&lt;/code&gt;的机制。布局视图在添加子视图时会监听加入到其中的子视图的frame,bounds,center的变化，并在其变化时调用布局视图的    &lt;code&gt;setNeedLayout&lt;/code&gt;来激发布局视图的重新布局。我们知道每次当一个视图调用addSubview添加子视图时都会激发调用者的方法：    &lt;code&gt;didAddSubview&lt;/code&gt;。为了实现对子视图的变化的监控，布局视图重载了这个方法并对子视图的    &lt;code&gt;isHidden,frame,center&lt;/code&gt;进行监控：&lt;/p&gt;
   &lt;pre&gt;    &lt;code&gt; override open func didAddSubview(_ subview: UIView) {
        super.didAddSubview(subview)

        subview.addObserver(self, forKeyPath:&amp;quot;isHidden&amp;quot;, options: NSKeyValueObservingOptions.new, context: nil)
        subview.addObserver(self, forKeyPath:&amp;quot;frame&amp;quot;, options: NSKeyValueObservingOptions.new, context: nil)
        subview.addObserver(self, forKeyPath:&amp;quot;center&amp;quot;, options: NSKeyValueObservingOptions.new, context: nil)

    }

    override open func willRemoveSubview(_ subview: UIView) {
        super.willRemoveSubview(subview)        
        subview.removeObserver(self, forKeyPath: &amp;quot;isHidden&amp;quot;)
        subview.removeObserver(self, forKeyPath: &amp;quot;frame&amp;quot;)
        subview.removeObserver(self, forKeyPath: &amp;quot;center&amp;quot;)

    }&lt;/code&gt;&lt;/pre&gt;
   &lt;p&gt;当子视图的frame或者center变更时，将会激发布局视图的重新布局。上面曾经说过，在布局视图重新布局子视图时最终会调整子视图的bounds和center.那么这样就有可能会形成循环的重新布局，为了解决这种循环递归的情况，布局视图在layoutSubviews调用进行布局前设置了一个布局中的标志，而在所有子视图布局完成后将恢复这个布局中的标志。因此当我们布局视图通过KVO监控到子视图的位置和尺寸变化时，则会判断那个布局中的标志，如果当前是在布局中则不会再次激发布局视图的重新布局，从而防止了死循环的发生。&lt;/p&gt;
   &lt;p&gt;这就是TangramKit布局实现的原理，下面的图表列出了TangramKit的整个布局框架的类体系结构：&lt;/p&gt;
   &lt;div&gt;
    &lt;img alt="" src="http://upload-images.jianshu.io/upload_images/1432482-af22121c2ec7606d.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240"&gt;&lt;/img&gt;    &lt;br /&gt;    &lt;div&gt;TangramKit布局框架体系架构&lt;/div&gt;
&lt;/div&gt;
   &lt;h3&gt;布局位置类和布局尺寸类&lt;/h3&gt;
   &lt;p&gt;在前面的介绍布局核心的章节以及布局实现原理的章节里面我们有说道布局位置类和布局尺寸类。之所以系统不直接操作视图的    &lt;code&gt;bounds和center&lt;/code&gt;属性而是通过扩展视图的2个布局尺寸属性和6个布局位置属性来进行子视图的布局设置。原因是后者能够提供丰富和多样的设置。而且我们在编程时也不再需要通过设置视图的frame来实现布局了，即使设置也可能会失效。&lt;/p&gt;
   &lt;h4&gt;比重类TGWeight&lt;/h4&gt;
   &lt;p&gt;TGWeight类的值表示尺寸或者位置的大小是父布局视图的尺寸或者剩余空间的尺寸的比例值，也就是说值的大小依赖于父布局视图的尺寸或者剩余空间的尺寸的大小而确定，这样子视图就不需要明确的指定位置和尺寸的大小了，非常适合那些需要适配屏幕的尺寸和位置的场景。 至于是父视图的尺寸还是父视图剩余空间的尺寸则要根据其所在的布局视图的上下文而确定。比如：&lt;/p&gt;
   &lt;pre&gt;    &lt;code&gt;//假如A,b是在一个垂直线性布局下的子视图
A.tg_width.equal(TGWeight(20))   //A的宽度是父布局视图宽度的20%
A.tg_height.equal(TGWeight(30))  //A的高度是父布局视图剩余高度的30%
B.tg_left.equal(TGWeight(40))  //B的左边距是父视图宽度的40%
B.tg_top.equal(TGWeight(10))  //B的顶部间距时父视图的剩余高度的10%&lt;/code&gt;&lt;/pre&gt;
   &lt;p&gt;为了简化和更加直观的表示比重类型的值，我们重载%运算符，这样上面的代码就可以简写为如下更加直观的方式：&lt;/p&gt;
   &lt;pre&gt;    &lt;code&gt;//假如A是在一个垂直线性布局下的子视图
A.tg_width.equal(20%)   //A的宽度是父布局视图宽度的20%
A.tg_height.equal(30%)  //A的高度是父布局视图剩余高度的30%
B.tg_left.equal(40%)  //B的左边距是父视图宽度的40%
B.tg_top.equal(10%)  //B的顶部间距时父视图的剩余高度的10%&lt;/code&gt;&lt;/pre&gt;
   &lt;p&gt;下面的列表中列出了在各种布局下视图的尺寸和位置的TGWeight类型值所代表的意义：&lt;/p&gt;
   &lt;blockquote&gt;
    &lt;p&gt;为了表示方便，我们把：&lt;/p&gt;
    &lt;ul&gt;
     &lt;li&gt;线性布局简称L
      &lt;ul&gt;
       &lt;li&gt;垂直线性布局简称为LV&lt;/li&gt;
       &lt;li&gt;水平线性布局简称为LH&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
     &lt;li&gt;框架布局简称为FR&lt;/li&gt;
     &lt;li&gt;垂直表格布局简称为TV&lt;/li&gt;
     &lt;li&gt;水平表格布局简称为TH&lt;/li&gt;
     &lt;li&gt;相对布局简称为R&lt;/li&gt;
     &lt;li&gt;浮动布局简称FO&lt;/li&gt;
     &lt;li&gt;流式布局FL&lt;/li&gt;
     &lt;li&gt;路径布局简称P&lt;/li&gt;
     &lt;li&gt;布局视图的非布局父视图S&lt;/li&gt;
     &lt;li&gt;所有布局简称ALL&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
   &lt;table&gt;
    &lt;tr&gt;
位置尺寸\类型
父视图尺寸
父视图剩余空间尺寸
&lt;/tr&gt;

    &lt;tr&gt;
     &lt;td&gt;tg_left&lt;/td&gt;
     &lt;td&gt;LV/FR/S/TH&lt;/td&gt;
     &lt;td&gt;LH/TV&lt;/td&gt;
&lt;/tr&gt;
    &lt;tr&gt;
     &lt;td&gt;tg_top&lt;/td&gt;
     &lt;td&gt;LH/FR/S/TV&lt;/td&gt;
     &lt;td&gt;LV/TH&lt;/td&gt;
&lt;/tr&gt;
    &lt;tr&gt;
     &lt;td&gt;tg_right&lt;/td&gt;
     &lt;td&gt;LV/FR/S/TH&lt;/td&gt;
     &lt;td&gt;LH/TV&lt;/td&gt;
&lt;/tr&gt;
    &lt;tr&gt;
     &lt;td&gt;tg_bottom&lt;/td&gt;
     &lt;td&gt;LH/FR/S/TV&lt;/td&gt;
     &lt;td&gt;LV/TH&lt;/td&gt;
&lt;/tr&gt;
    &lt;tr&gt;
     &lt;td&gt;tg_centerX&lt;/td&gt;
     &lt;td&gt;LV/FR/TH&lt;/td&gt;
     &lt;td&gt;-&lt;/td&gt;
&lt;/tr&gt;
    &lt;tr&gt;
     &lt;td&gt;tg_centerY&lt;/td&gt;
     &lt;td&gt;LH/FR/TV&lt;/td&gt;
     &lt;td&gt;-&lt;/td&gt;
&lt;/tr&gt;
    &lt;tr&gt;
     &lt;td&gt;tg_width&lt;/td&gt;
     &lt;td&gt;LV/FR/S/R/TH/P&lt;/td&gt;
     &lt;td&gt;LH/TV/FO/FL&lt;/td&gt;
&lt;/tr&gt;
    &lt;tr&gt;
     &lt;td&gt;tg_height&lt;/td&gt;
     &lt;td&gt;LH/FR/S/R/TV/P&lt;/td&gt;
     &lt;td&gt;LV/TH/FO/FL&lt;/td&gt;
&lt;/tr&gt;

&lt;/table&gt;
   &lt;h4&gt;布局尺寸类TGLayoutSize&lt;/h4&gt;
   &lt;p&gt;布局尺寸类用来描述视图布局核心中的视图尺寸。我们对UIView扩展出了2个布局尺寸对象 ：&lt;/p&gt;
   &lt;pre&gt;    &lt;code&gt;    public var tg_width:TGLayoutSize
    public var tg_height:TGLayoutSize&lt;/code&gt;&lt;/pre&gt;
   &lt;p&gt;分别用来实现视图的宽度和高度的布局尺寸设置。在TGLayoutSize类中，我们可以通过方法    &lt;code&gt;equal&lt;/code&gt;来设置视图尺寸的多种类型的值，类中是通过重载equal方法来实现多种类型的值的设置的。&lt;/p&gt;
   &lt;pre&gt;    &lt;code&gt;    public func equal(_ size:CGFloat, increment:CGFloat = 0, multiple:CGFloat = 1) -&amp;gt;TGLayoutSize
    public func equal(_ weight:TGWeight, increment:CGFloat = 0, multiple:CGFloat = 1) -&amp;gt;TGLayoutSize
    public func equal(_ array:[TGLayoutSize], increment:CGFloat = 0, multiple:CGFloat = 1) -&amp;gt;TGLayoutSize
    public func equal(_ view:UIView,increment:CGFloat = 0, multiple:CGFloat = 1) -&amp;gt;TGLayoutSize
    public func equal(_ dime:TGLayoutSize!, increment:CGFloat = 0, multiple:CGFloat = 1) -&amp;gt;TGLayoutSize&lt;/code&gt;&lt;/pre&gt;
   &lt;p&gt;上面的方法中我们可以通过equal方法来设置：&lt;/p&gt;
   &lt;ul&gt;
    &lt;li&gt;CGFloat类型的值表示视图的尺寸是一个绝对值类型的尺寸值。比如：
     &lt;pre&gt;      &lt;code&gt;A.tg_width.equal(100)  //A的宽度为100
A.tg_height.equal(200) //A的高度为200&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
    &lt;li&gt;
     &lt;p&gt;TGWeight类型的值表示视图的尺寸是一个依赖于父视图尺寸的相对比例值。(具体见上面TGWeight类型值的定义和使用)&lt;/p&gt;
     &lt;pre&gt;      &lt;code&gt;//假如A是在一个垂直线性布局下的子视图
A.tg_width.equal(20%)   //A的宽度是父布局视图宽度的20%
A.tg_height.equal(30%)  //A的高度是父布局视图剩余高度的30%&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
    &lt;li&gt;
     &lt;p&gt;TGLayoutSize类型的值表示视图的尺寸和另外一个尺寸对象的值相等，这也是一种相对值类型的尺寸值，通过设置这种尺寸的依赖我们就可以不必要明确的指定一个具体的值，而是会随着所以依赖的尺寸变化而变化。设置为TGLayoutSize类型的值通常用于在相对布局中的子视图，当然也可以在其他类型的布局中使用。下面是一个展示的例子：&lt;/p&gt;
     &lt;pre&gt;      &lt;code&gt; A.tg_width.equal(B.tg_width)  //A的宽度等于B的宽度
 A.tg_height.equal(A.tg_width)  //A的高度等于A的宽度&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
    &lt;li&gt;
     &lt;p&gt;UIView类型的值其实就是TGLayoutSize的简化版本设置，表示某个维度的尺寸值等于指定视图的相同维度的尺寸值。比如：&lt;/p&gt;
     &lt;pre&gt;      &lt;code&gt; A.tg_width.equal(B)   //表示A视图的宽度等于B视图的宽度
 A.tg_height.equal(A.superview)  //表示A视图的高度等于父视图的高度。&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
    &lt;li&gt;
     &lt;p&gt;[TGLayoutSize]数组类型的值，只用在相对布局里面的子视图设置才有意义，其他的类型的布局中设置这种类型的值无效。他表示子视图的尺寸和数组里面的所有子视图来等分父布局视图的尺寸。比如：&lt;/p&gt;
     &lt;pre&gt;      &lt;code&gt;//A,B,C,D都是相对布局视图里面的子视图，我们希望A,B,C,D这四个子视图来均分父视图的宽度,这样A,B,C,D都不需要明确的指定宽度了。
A.tg_width.equal([B.tg_width, C.tg_width, D.tg_width])
A.tg_width.equal(B.tg_width)  //A和B的宽度相等
A.tg_width.equal([B.tg_width]) //A和B的宽度相等并且平分布局视图的宽度，也就是A,B的宽度都是布局视图的宽度的一半&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
    &lt;li&gt;
     &lt;p&gt;特殊类型的值。为了简化尺寸的设置我们定义了三种特殊类型的尺寸值:&lt;/p&gt;
     &lt;ul&gt;
      &lt;li&gt;
       &lt;strong&gt;wrap&lt;/strong&gt;: 他表示尺寸的值由布局视图的所有子视图的尺寸或者由子视图的内容包裹而成。也就是尺寸的大小是由子视图或者视图的内容共同决定的，这样视图的尺寸将依赖其内部的子视图的尺寸或者子视图内容的大小。&lt;/li&gt;
&lt;/ul&gt;
     &lt;ul&gt;
      &lt;li&gt;
       &lt;p&gt;        &lt;strong&gt;fill&lt;/strong&gt;: 他表示视图的尺寸的值将会填充满父视图的剩余空间，也就是说视图的尺寸值是依赖于父视图的尺寸的大小。&lt;/p&gt;
&lt;/li&gt;
      &lt;li&gt;
       &lt;p&gt;        &lt;strong&gt;average&lt;/strong&gt;:他表示视图的尺寸将和其兄弟视图一起来均分父视图的尺寸，这样所有兄弟视图的尺寸都将相等。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
     &lt;p&gt;下面是这三个特殊值使用的例子：&lt;/p&gt;
     &lt;pre&gt;      &lt;code&gt;A.tg_width.equal(.wrap)  //A视图的宽度由里面的所有子视图或者内容包裹而确定。
A.tg_height.equal(.fill)   //A视图的高度填充满父视图的剩余高度空间。
B.tg_width.equal(.average)  //B视图的宽度将会和其他兄弟视图均分父视图的宽度。&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
   &lt;p&gt;上面列出了布局尺寸类中的equal方法可以设置的值的类型，我们还看到了方法中存在着另外两个默认的参数：    &lt;code&gt;increment 和multiple&lt;/code&gt; 这两个参数的意义表示在尺寸等于上述类型的值的基础上的增量值和倍数值。增量值默认是0，而倍数值则默认是1。比如某个子视图的宽度等于另外一个子视图的宽度值加20的时，可以通过equal方法设置如下：&lt;/p&gt;
   &lt;pre&gt;    &lt;code&gt;   A.tg_width.equal(B.tg_width, increment:20)  //A的宽度等于B的宽度加20&lt;/code&gt;&lt;/pre&gt;
   &lt;p&gt;除了可以在equal方法中指定增量值外，布局尺寸类还单独提供一个    &lt;code&gt;add&lt;/code&gt;方法来实现增量值的设置：&lt;/p&gt;
   &lt;pre&gt;    &lt;code&gt;    public func add(_ val:CGFloat) -&amp;gt;TGLayoutSize&lt;/code&gt;&lt;/pre&gt;
   &lt;p&gt;这样上述的代码也可以用如下的方式设置：&lt;/p&gt;
   &lt;pre&gt;    &lt;code&gt;A.tg_width.equal(B.tg_width).add(20)&lt;/code&gt;&lt;/pre&gt;
   &lt;p&gt;在equal方法中的multiple值则是指定尺寸等于另外一个尺寸的倍数值。比如某个子视图的高度等于另外一个子视图的高度的一半时，可以通过equal方法设置如下：&lt;/p&gt;
   &lt;pre&gt;    &lt;code&gt;A.tg_height.equal(B.tg_height, multiple:0.5);  //A的高度等于B的高度的一半。&lt;/code&gt;&lt;/pre&gt;
   &lt;p&gt;除了可以在equal方法中指定倍数值外，布局尺寸类还单独提供一个    &lt;code&gt;multiply&lt;/code&gt;方法来实现倍数值的设置:&lt;/p&gt;
   &lt;pre&gt;    &lt;code&gt;    public func multiply(_ val:CGFloat) -&amp;gt;TGLayoutSize&lt;/code&gt;&lt;/pre&gt;
   &lt;p&gt;这样上述的代码也可以用如下的方式设置：&lt;/p&gt;
   &lt;pre&gt;    &lt;code&gt;  A.tg_height.equal(B.tg_height).multiply(0.5)&lt;/code&gt;&lt;/pre&gt;
   &lt;p&gt;在布局尺寸类中我们除了可以用    &lt;code&gt;equal, add, multiply&lt;/code&gt; 方法来设置视图的尺寸依赖值以及增量和倍数外，我们还可以对视图尺寸的最大最小值进行控制处理。    &lt;strong&gt;比如在实践中我们希望某个视图的宽度等于另外一个兄弟视图的宽度，但是最小不能小于20，而最大则不能超过父视图的宽度的一半。&lt;/strong&gt; 这时候我们就需要用到布局尺寸类的另外两个方法了：&lt;/p&gt;
   &lt;pre&gt;    &lt;code&gt;    public func min(_ size:CGFloat, increment:CGFloat = 0, multiple:CGFloat = 1) -&amp;gt;TGLayoutSize
    public func min(_ dime:TGLayoutSize!, increment:CGFloat = 0, multiple:CGFloat = 1) -&amp;gt;TGLayoutSize
    public func min(_ view:UIView, increment:CGFloat = 0, multiple:CGFloat = 1) -&amp;gt;TGLayoutSize
    public func max(_ size:CGFloat, increment:CGFloat = 0, multiple:CGFloat = 1) -&amp;gt;TGLayoutSize
    public func max(_ view:UIView, increment:CGFloat = 0, multiple:CGFloat = 1) -&amp;gt;TGLayoutSize
    public func max(_ dime:TGLayoutSize!, increment:CGFloat = 0, multiple:CGFloat = 1) -&amp;gt;TGLayoutSize&lt;/code&gt;&lt;/pre&gt;
   &lt;p&gt;上述的两个方法    &lt;code&gt;min,max&lt;/code&gt;分别用来设置视图尺寸最小不能小于的值以及最大不能超过的值。方法中我们可以看出最大最小值除了可以设置具体的数值外还可以设置为另外一个布局尺寸对象，同样我们还可以设置增量和倍数值。因此我们可以通过对    &lt;code&gt;min&lt;/code&gt;和    &lt;code&gt;max&lt;/code&gt;方法的使用来解决上述的问题：&lt;/p&gt;
   &lt;pre&gt;    &lt;code&gt;  //A的宽度等于B的宽度，最小为20，最大为父视图宽度的一半。
  A.tg_width.equal(B.tg_width).min(20).max(A.superview,multiple:0.5)&lt;/code&gt;&lt;/pre&gt;
   &lt;p&gt;最后我们列出视图的扩展属性tg_width, tg_height在各布局视图下equal方法能够设置的值的类型，我们这里设置B为一个兄弟视图，S为父视图&lt;/p&gt;
   &lt;table&gt;
    &lt;tr&gt;
属性/值
CGFloat/TGWeight/wrap/fill
A.tg_width
A.tg_height
B.tg_width
B.tg_height
S.tg_width
S.tg_height
[TGLayoutSize]
&lt;/tr&gt;

    &lt;tr&gt;
     &lt;td&gt;A.tg_width&lt;/td&gt;
     &lt;td&gt;ALL&lt;/td&gt;
     &lt;td&gt;-&lt;/td&gt;
     &lt;td&gt;FR/R/FLH/FO&lt;/td&gt;
     &lt;td&gt;FR/R/FO/P&lt;/td&gt;
     &lt;td&gt;R&lt;/td&gt;
     &lt;td&gt;ALL&lt;/td&gt;
     &lt;td&gt;R&lt;/td&gt;
     &lt;td&gt;R&lt;/td&gt;
&lt;/tr&gt;
    &lt;tr&gt;
     &lt;td&gt;A.tg_height&lt;/td&gt;
     &lt;td&gt;ALL&lt;/td&gt;
     &lt;td&gt;FR/R/FLV/FO/LV&lt;/td&gt;
     &lt;td&gt;-&lt;/td&gt;
     &lt;td&gt;R&lt;/td&gt;
     &lt;td&gt;FR/R/FO/P&lt;/td&gt;
     &lt;td&gt;R&lt;/td&gt;
     &lt;td&gt;ALL&lt;/td&gt;
     &lt;td&gt;R&lt;/td&gt;
&lt;/tr&gt;

&lt;/table&gt;
   &lt;h4&gt;布局位置类TGLayoutPos&lt;/h4&gt;
   &lt;p&gt;布局位置类用来描述视图布局核心中的视图的位置。我们对UIView扩展出了6个布局位置对象：&lt;/p&gt;
   &lt;pre&gt;    &lt;code&gt;    public var tg_left:TGLayoutPos       //视图左边布局位置
    public var tg_top:TGLayoutPos      //视图上边布局位置
    public var tg_right:TGLayoutPos   //视图右边布局位置
    public var tg_bottom:TGLayoutPos  //视图下边布局位置
    public var tg_centerX:TGLayoutPos  //视图水平中心点布局位置
    public var tg_centerY:TGLayoutPos   //视图垂直中心点布局位置&lt;/code&gt;&lt;/pre&gt;
   &lt;p&gt;分别用来实现视图的水平维度的左、中、右三个方位以及视图垂直维度的上、中、下三个方位的布局位置设置。在TGLayoutPos类中，我们可以通过方法    &lt;code&gt;equal&lt;/code&gt;来设置视图位置的多种类型的值，类中是通过重载equal方法来实现多种类型的值的设置的。&lt;/p&gt;
   &lt;pre&gt;    &lt;code&gt;    public func equal(_ origin:CGFloat, offset:CGFloat = 0) -&amp;gt;TGLayoutPos
    public func equal(_ weight:TGWeight, offset:CGFloat = 0) -&amp;gt;TGLayoutPos
    public func equal(_ array:[TGLayoutPos], offset:CGFloat = 0) -&amp;gt;TGLayoutPos
    public func equal(_ view: UIView, offset:CGFloat = 0) -&amp;gt;TGLayoutPos
    public func equal(_ pos:TGLayoutPos!, offset:CGFloat = 0) -&amp;gt;TGLayoutPos&lt;/code&gt;&lt;/pre&gt;
   &lt;p&gt;我们可以通过上面定义的equal方法来设置：&lt;/p&gt;
   &lt;ul&gt;
    &lt;li&gt;
     &lt;p&gt;CGFloat类型的值表示视图的位置是一个绝对值类型的位置值。 比如：&lt;/p&gt;
     &lt;pre&gt;      &lt;code&gt;   A.tg_left.equal(10)          //A视图的左边位置是10
   A.tg_right.equal(20)        //A视图的右边位置是20
   A.tg_centerX.equal(5)     //A视图的水平中心点的偏移位置是5&lt;/code&gt;&lt;/pre&gt;
     &lt;p&gt;我们知道在视图定位时位置的概念根据参考坐标系不同而不同：&lt;/p&gt;
     &lt;ul&gt;
      &lt;li&gt;       &lt;strong&gt;定位的值如果是以父视图作为参考系坐标那么视图的位置就叫做边距 ,边距描述的是视图距离父视图的距离。&lt;/strong&gt;&lt;/li&gt;
      &lt;li&gt;       &lt;strong&gt;定位的值如果是以兄弟视图作为参考系坐标那么视图的位置就叫做间距，间距描述的是视图距离兄弟视图的距离        &lt;em&gt;(垂直线性布局中虽然第一个子视图的顶部是距离父视图但是我们仍然称为间距)&lt;/em&gt;。&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
     &lt;p&gt;对于绝对值类型的位置值，他所表示的意义是边距还是间距这个要看他所加入的布局视图的类型而不同。下面的列表中展示了位置在不同的布局中描述的是间距还是边距:&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
   &lt;table&gt;
    &lt;tr&gt;
位置/布局
边距
间距
&lt;/tr&gt;

    &lt;tr&gt;
     &lt;td&gt;tg_left/tg_right&lt;/td&gt;
     &lt;td&gt;LV/FR/R/TH/S&lt;/td&gt;
     &lt;td&gt;LH/FO/FL/P/TV&lt;/td&gt;
&lt;/tr&gt;
    &lt;tr&gt;
     &lt;td&gt;tg_top/tg_bottom&lt;/td&gt;
     &lt;td&gt;LH/FR/R/TV/S&lt;/td&gt;
     &lt;td&gt;LV/FO/FL/P/TH&lt;/td&gt;
&lt;/tr&gt;
    &lt;tr&gt;
     &lt;td&gt;tg_centerX&lt;/td&gt;
     &lt;td&gt;LV/FR/R/TH/S&lt;/td&gt;
     &lt;td&gt;-&lt;/td&gt;
&lt;/tr&gt;
    &lt;tr&gt;
     &lt;td&gt;tg_centerY&lt;/td&gt;
     &lt;td&gt;LH/FR/R/TV/S&lt;/td&gt;
     &lt;td&gt;-&lt;/td&gt;
&lt;/tr&gt;

&lt;/table&gt;
   &lt;ul&gt;
    &lt;li&gt;
     &lt;p&gt;TGWeight类型的值表示视图的位置是一个依赖于父视图尺寸的相对比例值。目前只有在线性布局、框架布局、和非布局父视图中才支持这种类型的值的设置(具体见上面TGWeight类型值的定义和使用)&lt;/p&gt;
     &lt;pre&gt;      &lt;code&gt;   //假如A视图是在一个垂直线性布局里面，垂直线性布局的宽度为50
   A.tg_left.equal(20%)   //A视图的左边距占用父视图宽度的20%也就是10
   A.tg_right.equal(30%)  //A视图的右边距占用父视图宽度的30%也就是15&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
    &lt;li&gt;
     &lt;p&gt;TGLayoutPos类型的值表示视图的位置依赖另外一个视图的位置。这种类型的值大部分用于在相对布局中使用的子视图，但是有几个特殊的位置就是父视图的位置是几乎在所有布局视图中都支持。比如：&lt;/p&gt;
     &lt;pre&gt;      &lt;code&gt;  A.tg_left.equal(B.tg_right)   //A视图在B视图的右边
  A.tg_top.equal(A.superview.tg_top)  //A视图的顶部和父视图对齐
  A.tg_centerX.equal(B.tg_right)      //A视图的水平中心点和B视图的右边对齐&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
    &lt;li&gt;
     &lt;p&gt;UIView类型的值其实就是TGLayoutPos的简化版本设置，标识某个方位的位置等于指定视图的相同方法的位置值。比如：&lt;/p&gt;
     &lt;pre&gt;      &lt;code&gt; A.tg_left.equal(B)   //A的左边位置和B的左边位置相等&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
   &lt;ul&gt;
    &lt;li&gt;
     &lt;p&gt;[TGLayoutPos]数组类型的值，只能用在相对布局里面的子视图的tg_centerX,tg_centerY这两个属性的equal方法中才有意义，他表示子视图和数组里面其他所有子视图的位置在相对布局中整体水平居中或者垂直居中。比如：&lt;/p&gt;
     &lt;pre&gt;      &lt;code&gt;    //相对布局里面有A,B,C,D四个子视图，想让这四个子视图在布局视图里面整体水平居中。
  A.tg_centerX.equal([B.tg_centerX,C.tg_centerX,D.tg_centerX])

  A.tg_centerX.equal(B.tg_centerX) //这个意义和上面是不同的，他表示A视图的水平中心点和B视图的水平中心点是对齐的。
  A.tg_centerX.equal([B.tg_centerX]) //这个表示A,B在布局视图里面整体水平居中&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
   &lt;p&gt;上面列出了布局位置类中的equal方法可以设置的值的类型，我们还看到了方法中存在着另外一个默认的参数：    &lt;code&gt;offset&lt;/code&gt; 这个参数的意义表示在位置等于上述类型的值的基础上的偏移值。偏移默认是0。比如某个子视图的左边位置等于另外一个子视图的右边的位置再往右偏移20时，可以通过equal方法设置如下：&lt;/p&gt;
   &lt;pre&gt;    &lt;code&gt;  A.tg_left.equal(B.tg_right, offset:20)  //A在B视图的右边再往右偏移20
  A.tg_top.equal(A.superview.tg_top, offset:20) //A在父视图顶部往下偏移20的位置&lt;/code&gt;&lt;/pre&gt;
   &lt;p&gt;除了可以在equal方法中指定偏移量值外，布局位置类还单独提供了一个    &lt;code&gt;offset&lt;/code&gt;方法来实现偏移量的设置：&lt;/p&gt;
   &lt;pre&gt;    &lt;code&gt;    public func offset(_ val:CGFloat) -&amp;gt;TGLayoutPos&lt;/code&gt;&lt;/pre&gt;
   &lt;p&gt;这样上述的代码也可以用如下方法设置：&lt;/p&gt;
   &lt;pre&gt;    &lt;code&gt;  A.tg_left.equal(B.tg_right).offset(20)
  A.tg_top.equal(A.superview.tg_top).offset(20)&lt;/code&gt;&lt;/pre&gt;
   &lt;p&gt;通过偏移量的设置，我们可以发现    &lt;strong&gt;那些表示的是边距意义的位置值，其实就是等于位置依赖于父视图对应位置的偏移值&lt;/strong&gt;。比如某个子视图的左边距是20，其实就是等价于子视图的左边等于父视图的左边再偏移20。下面的代码其实是等价的。&lt;/p&gt;
   &lt;pre&gt;    &lt;code&gt;  //A是一个相对布局里面的子视图
  A.tg_left.equal(20)
  A.tg_left.equal(A.superview.tg_left).offset(20)   //这句代码和上句是等价的

  A.tg_centerY.equal(0)
  A.tg_centerY.equal(A.superview.tg_centerY).offset(0) //这句代码和上句是等价的

  A.tg_bottom.equal(20)
  A.tg_bottom.equal(A.superview.tg_bottom).offset(20) //这句代码和上句是等价的&lt;/code&gt;&lt;/pre&gt;
   &lt;p&gt;在布局位置类中我们除了可以用    &lt;code&gt;equal,offset&lt;/code&gt;方法设置视图的位置依赖及偏移量外，我们还可以对视图位置的最大最小值进行控制处理。    &lt;strong&gt;比如在实践中我们希望某个子视图的左边距等于父视图的宽度的20%，但是最小不能小于20，最大不能超过30。&lt;/strong&gt; 这时候我们就需要用到布局位置类的另外两个方法了：&lt;/p&gt;
   &lt;pre&gt;    &lt;code&gt;    public func min(_ val:CGFloat, offset:CGFloat = 0) -&amp;gt;TGLayoutPos
    public func max(_ val:CGFloat, offset:CGFloat = 0) -&amp;gt;TGLayoutPos&lt;/code&gt;&lt;/pre&gt;
   &lt;p&gt;上述的两个方法    &lt;code&gt;min,max&lt;/code&gt;分别用来设置视图位置最小不能小于的值以及最大不能超过的值。方法中我们可以设置一个具体的数值以及偏移量，因此我们可以通过对    &lt;code&gt;min&lt;/code&gt;和    &lt;code&gt;max&lt;/code&gt;方法的使用来解决上述的问题：&lt;/p&gt;
   &lt;pre&gt;    &lt;code&gt;   //A的左边距等于父视图的宽度的20%，最小为20，最大为30
  A.tg_left.equal(20%).min(20).max(30)&lt;/code&gt;&lt;/pre&gt;
   &lt;p&gt;最后我们列出子视图的6个扩展属性在各布局视图下equal方法能够设置的值的类型:&lt;/p&gt;
   &lt;table&gt;
    &lt;tr&gt;
属性/值
CGFloat
TGWeight
TGLayoutPos
[TGLayoutPos]
&lt;/tr&gt;

    &lt;tr&gt;
     &lt;td&gt;tg_left&lt;/td&gt;
     &lt;td&gt;ALL&lt;/td&gt;
     &lt;td&gt;L/FR/T/R/S&lt;/td&gt;
     &lt;td&gt;R&lt;/td&gt;
     &lt;td&gt;-&lt;/td&gt;
&lt;/tr&gt;
    &lt;tr&gt;
     &lt;td&gt;tg_top&lt;/td&gt;
     &lt;td&gt;ALL&lt;/td&gt;
     &lt;td&gt;L/FR/T/R/S&lt;/td&gt;
     &lt;td&gt;R&lt;/td&gt;
     &lt;td&gt;-&lt;/td&gt;
&lt;/tr&gt;
    &lt;tr&gt;
     &lt;td&gt;tg_right&lt;/td&gt;
     &lt;td&gt;ALL&lt;/td&gt;
     &lt;td&gt;L/FR/T/R/S&lt;/td&gt;
     &lt;td&gt;R&lt;/td&gt;
     &lt;td&gt;-&lt;/td&gt;
&lt;/tr&gt;
    &lt;tr&gt;
     &lt;td&gt;tg_bottom&lt;/td&gt;
     &lt;td&gt;ALL&lt;/td&gt;
     &lt;td&gt;L/FR/T/R/S&lt;/td&gt;
     &lt;td&gt;R&lt;/td&gt;
     &lt;td&gt;-&lt;/td&gt;
&lt;/tr&gt;
    &lt;tr&gt;
     &lt;td&gt;tg_centerX&lt;/td&gt;
     &lt;td&gt;ALL&lt;/td&gt;
     &lt;td&gt;L/FR/T/R/S&lt;/td&gt;
     &lt;td&gt;R&lt;/td&gt;
     &lt;td&gt;R&lt;/td&gt;
&lt;/tr&gt;
    &lt;tr&gt;
     &lt;td&gt;tg_centerY&lt;/td&gt;
     &lt;td&gt;ALL&lt;/td&gt;
     &lt;td&gt;L/FR/T/R/S&lt;/td&gt;
     &lt;td&gt;R&lt;/td&gt;
     &lt;td&gt;R      &lt;br /&gt;      &lt;br /&gt;
&lt;/td&gt;
&lt;/tr&gt;

&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;
          
           &lt;br /&gt; &lt;br /&gt;
          
             &lt;a href="http://yangtiang.iteye.com/blog/2341897#comments"&gt;已有   &lt;strong&gt;0&lt;/strong&gt; 人发表留言，猛击-&amp;gt;&amp;gt;  &lt;strong&gt;这里&lt;/strong&gt;&amp;lt;&amp;lt;-参与讨论&lt;/a&gt;
          
           &lt;br /&gt; &lt;br /&gt; &lt;br /&gt;
ITeye推荐
 &lt;br /&gt;
 &lt;ul&gt;  &lt;li&gt;   &lt;a href="http://www.iteye.com/clicks/433" target="_blank"&gt;—软件人才免语言低担保 赴美带薪读研！— &lt;/a&gt;&lt;/li&gt;&lt;/ul&gt;
 &lt;br /&gt; &lt;br /&gt; &lt;br /&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 />
      <guid isPermaLink="true">https://itindex.net/detail/56267-ios-%E7%95%8C%E9%9D%A2-%E5%B8%83%E5%B1%80</guid>
      <pubDate>Tue, 29 Nov 2016 21:09:02 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/56723-%E7%A1%85%E8%B0%B7-%E5%9B%BD%E5%86%85-ios</link>
      <description>前段时间在国内各大互联网公司转了一圈。与各位 iOS 业界大佬交流了之后，深感国内变化之大，敬佩诸位国内开发者的实力和韧劲。除此之外，我还发现硅谷和国内的 iOS 开发还是差别很大，且听我慢慢道来。
 &lt;br /&gt;
 &lt;br /&gt; &lt;strong&gt;国内使用 SDK 和 硅谷大为不同&lt;/strong&gt;
 &lt;br /&gt;首先是最本质的三个不同：国内的支付使用的是支付宝和微信，地图使用的高德和百度导航，国内的第三方登录主要是微博，微信，和 QQ。而硅谷的在线支付方式是信用卡，地图使用的是苹果自带亦或是谷歌地图，第三方登录就是 Facebook 和 Twitter。
 &lt;br /&gt;
 &lt;br /&gt;这三点不同意味着开发引入的 SDK 完全不同。在 Uber 被滴滴收购前，其美国的 App 和 中国的 App 完全是两个不同的 App -- 因为大量 SDK 不同导致架构和接口需要重新设计。再加上国内对于数据的严格掌控，很多 App 后台 API 的设计需要单独处理，流量需要导入到中国境内的数据中心，App 的界面亦要根据中国的网速针对优化。
 &lt;br /&gt;
 &lt;br /&gt;另外，国内开发经常大量的调用第三方的库。而硅谷的大厂开发基本都是自己开发内部的工具和库。可能调用开源库确实比较方便快捷，但是硅谷的大厂考虑更多的是版权和代码质量的问题，所以在开源或是使用第三方库方面格外谨慎。
 &lt;br /&gt;
 &lt;br /&gt; &lt;strong&gt;国内注重 HotPatch，硅谷注重原生态&lt;/strong&gt;
 &lt;br /&gt;据我所知，国内开发对于热补丁情有独钟。滴滴就做出了 DynamicCocoa，通过转化 Objective-C 到 Javascript 进行热修复；饿了么大量使用 Weex 进行移动开发；美团也已经在主 App 里尝试了 React Native。
 &lt;br /&gt;
 &lt;br /&gt;相比硅谷，也只有少量小公司开始尝试 React Native。其主要原因也是 App 需求相对简单，跨平台开发相对轻松。大公司几乎很少使用，就连 RN 的母公司 Facebook 也只是在1到2个小 App 上使用了 React Native。
 &lt;br /&gt;
 &lt;br /&gt;我个人推测这其中的主要原因在，国内开发需求量又多又急，加上前些年 App Store 的审核非常之慢，所以国人在开发上才对 HotPatch 趋之若骛。
 &lt;br /&gt;
 &lt;br /&gt; &lt;strong&gt;国内要求快速迭代，硅谷要求测试覆盖&lt;/strong&gt;
 &lt;br /&gt;与百度的开发者交流中，他们经常提到“业务太多，根本来不及做”。所以基本上会有一个单独的 QA 团队负责测试，而开发者则是不停的写新的代码。
 &lt;br /&gt;
 &lt;br /&gt;这一点与硅谷在对测试的态度上大相径庭。Google 对于代码的测试覆盖率有严格的要求和审核标准，Yahoo! 甚至在开发中要求采用 TDD (Test-Driven Development)，Facebook 所有的代码也都用持续集成测试来保证其质量。在 《The Clean Coder》一书中，作者也多次强调代码质量的测试的重要性。我之前在工作中，有时候甚至出现写的测试代码数量超过开发代码的时候。
 &lt;br /&gt;
 &lt;br /&gt;造成这一差异的本质在于两国竞争模式的不同。中国人口巨大，竞争对手太多，所以资本的打法就是快速迭代，小步快跑，挤垮对手。面对这样的模式，中国的工程师也只能暂时放弃完善测试代码，将有限的精力集中在开发上。
 &lt;br /&gt; &lt;div&gt;  &lt;img src="http://dl2.iteye.com/upload/attachment/0123/4441/1325d5b0-c0d4-319c-ad44-1fc41f9b6554.png"&gt;&lt;/img&gt;&lt;/div&gt;
 &lt;br /&gt; &lt;div&gt;Swift 与 Objective-C 的争论一直不绝于耳&lt;/div&gt;
 &lt;br /&gt; &lt;strong&gt;国内和硅谷对于 Swift 的看法大同小异&lt;/strong&gt;
 &lt;br /&gt;前段时间唐巧老师发表了他对 Swift 的看法，他认为 Swift 是未来，但是现在不太完善，要“再等等”。无独有偶，卓同学发文则认为，Swift 已经开始流行起来，应该“快上车”。
 &lt;br /&gt;
 &lt;br /&gt;我在这半个月杭州、北京、上海之行中发现，几乎大厂开发都用 Objective-C，他们对 Swift 依然心存芥蒂；而小公司和独立开发者，则是对 Swift 充满期待。原因很简单，大厂需要的是稳定的产品来维持口碑，对于 Swift 这样重写并不能带来巨大好处的冒险之举自然是讳莫如深。而这个原因对于小公司或者个人来说并不成立。
 &lt;br /&gt;
 &lt;br /&gt;其实国外对此也一样。唯一不同的是，可能硅谷要略微激进一点 -- 大厂已经开始部分尝试 Swift 了。Google 在某些新产品和新功能上已经开始用 Swift，Facebook 和 Twitter 都放出了自己的 Swift iOS SDK；LinkedIn 开源了 LayoutKit，Lyft 开源了 mapper，而这些都是用 Swift 写成。
 &lt;br /&gt;
 &lt;br /&gt;就连硅谷的猎头都开始急着寻找拥有 Swift 开发技能的工程师了，而就在去年，Swift 在职场上还是被作为一项可有可无的加分技能来对待。
 &lt;br /&gt;
 &lt;br /&gt;虽然硅谷在 Swift 上走在了前面，但是不得不说开创性的尝试总是要付出代价的。当年 Uber 在开发新 App 时采用了全 Swift 模式，结果因为 Swift 编译速度不佳和语言功能不全，开发效率大打折扣，内部工程师在采坑过程中无比头疼。所以 Swift 虽好，可不要贪快哦。
 &lt;br /&gt;
 &lt;br /&gt; &lt;strong&gt;国内 iOS 职场与硅谷有很大差别&lt;/strong&gt;
 &lt;br /&gt;这个话题有点大，我从四个方面来说。
 &lt;br /&gt; &lt;strong&gt;1、两者对于 iOS 工程师的需求量不同&lt;/strong&gt;
 &lt;br /&gt;国内现在处于一个 iOS 工程师饱和的状态，水平一般的 iOS 开发者多如牛毛，而高手却屈指可数。这就造成了一个情况，公司招不到素质过硬的工程师，而很多新手找不到工作。
 &lt;br /&gt;
 &lt;br /&gt;作为生活在美帝多年的土包子，我对这个问题百思不得其解。因为硅谷一直是程序员的天堂，一个美帝计算机专业的毕业生，可以随便就找到一个年薪10万刀的工作。在这之中，iOS 工程师更是奇货可居。按照道理来讲，美国这么多年大量输出计算机本科生，硅谷居然还缺工程师，而且连刚毕业的新手都抢手。为什么国内反而却饱和了呢？
 &lt;br /&gt;
 &lt;br /&gt;这个问题直到我遇到了滴滴的 Sunny 才想明白。
 &lt;br /&gt;
 &lt;br /&gt;他告诉我，国内有 iOS 培训班这种东西。这样，工程师可以流水线快速训练出来，他们会带你刷面试题，教你如何拿 Offer，甚至帮你把 Github 和 博客都弄好。再加上前段时间中国处于全民创业的狂潮之中，各种初创企业对 iOS 工程师需求巨大，导致这种培训班居然大行其道。而现在市场回归理性，对于程序员的需求量减少，于是很多刚刚流水线出来的 iOS 菜鸟自然无处可去。
 &lt;br /&gt;
 &lt;br /&gt; &lt;strong&gt;2、产品经理 （PM/PD）素质的差异&lt;/strong&gt;
 &lt;br /&gt;之前老听说国内程序员追着产品经理砍的故事，我只当成是个事故，一笑置之。因为硅谷的产品经理大多和程序员和睦相处，至少在我印象中，工程师和产品经理的矛盾要远远小于上下级的矛盾。
 &lt;br /&gt;
 &lt;br /&gt;后来发现，在国内，我以为的并不是我以为的。
 &lt;br /&gt;
 &lt;br /&gt;国内产品经理基本上就是刚毕业的新人，没有什么实战经验，有些都不懂技术。而最重要的开发需求和任务往往是他们提出和分配。这就造成了一个奇怪的现象：一群经验丰富的 iOS 专家，团结在一个不怎么懂技术的产品经理周围，做开发。
 &lt;br /&gt;
 &lt;br /&gt;硅谷则对产品经理要求颇为严格：口才和技术是两个必备的技能，很多产品经理甚至是资深程序员转型。一般产品经理也是作为部门经理的接班人来培养的。
 &lt;br /&gt;
 &lt;br /&gt; &lt;strong&gt;3、面试流程不一样&lt;/strong&gt;
 &lt;br /&gt;我说实话，国内大厂考得要比硅谷难。我回国之前就发现腾讯笔试好几张卷子真不好做，面试考得也异常全面。百度甚至考出了红黑树这种变态的玩意，还有公司问 autorelease pool 是用什么数据结构写的。
 &lt;br /&gt;
 &lt;br /&gt;硅谷每个公司的面试流程则不尽相同。谷歌是比较极端的考 4 到 5 轮算法，亚马逊与此类似，这种标准化流程让这两家损失掉很多优秀的工程师 -- Homebrew 的作者 Max Howell 因为不会在白板上翻转二叉树而被谷歌拒绝的事情现在还被大家拿来吐槽。相比 Facebook 的面试还比较靠谱，一轮交流，问问简历和文化；一轮系统设计；两轮算法。我个人面过最实际的还是 Uber 的 iOS 面试：一轮交流，一轮系统设计，一轮上机实战写 App，一轮算法。
 &lt;br /&gt;
 &lt;br /&gt;总体来讲，国内面试偏向考试，难度大，要求全面。硅谷的面试侧重算法和基本功，有时候脱离实际。
 &lt;br /&gt; &lt;div&gt;  &lt;img src="http://dl2.iteye.com/upload/attachment/0123/4443/17fbfe11-bed4-318e-8623-bde22d8d13e2.png"&gt;&lt;/img&gt;&lt;/div&gt;
 &lt;br /&gt; &lt;strong&gt;4、职业走向&lt;/strong&gt;
 &lt;br /&gt;据我所知，国内很少有干了10年以上的开发者，很多程序员干了几年就做管理了。这可能是因为国内程序员确实很辛苦，阿里这样的大厂996都是常态，在这种情况下码农、搬砖这类热词应用而生。但同时中国很多优秀的开发者，可能是前端、后端、移动端都有几年经验，技能十分全面扎实。
 &lt;br /&gt;
 &lt;br /&gt;而硅谷有很多写了10年以上经验的极客程序员，他们热衷写代码却不喜欢管理工作。我在美帝待得这几年，几乎没有听到国外程序员抱怨自己辛苦，像Google，Facebook 这样成熟的美国互联网公司很少出现加班情况。硅谷的开发者可能一辈子只钻研一块，比如只会前端或者后端。但这并不妨碍他们在喜欢的领域成为超级专家，这也十分受人敬仰。
 &lt;br /&gt;
 &lt;br /&gt; &lt;strong&gt;总结&lt;/strong&gt;
 &lt;br /&gt;虽然中国的网民数量在 2008 年就超过了美国，尽管中国的互联网公司是唯一同美国一样使用10亿作为单位来衡量业绩的存在。但是不可否认，由于政策和文化的巨大差异，导致两国的开发环境有巨大的差别。本文抛砖引用，疏漏之处在所难免，我衷心希望国内外能够取长补短，因为互联网终将拉平整个世界。
           &lt;br /&gt; &lt;br /&gt;
          感谢  &lt;a href="http://jihong10102006.iteye.com"&gt;jihong10102006&lt;/a&gt; 投递这篇资讯
                     &lt;br /&gt; &lt;br /&gt;资讯来源： &lt;a href="http://www.jianshu.com/p/63aec174bdb7" target="_blank"&gt;简书&lt;/a&gt;
          
           &lt;br /&gt; &lt;br /&gt;
          
             &lt;a href="http://www.iteye.com/news/32184#comments"&gt;已有   &lt;strong&gt;0&lt;/strong&gt; 人发表留言，猛击-&amp;gt;&amp;gt;  &lt;strong&gt;这里&lt;/strong&gt;&amp;lt;&amp;lt;-参与讨论&lt;/a&gt;
          
           &lt;br /&gt; &lt;br /&gt; &lt;br /&gt;
ITeye推荐
 &lt;br /&gt;
 &lt;ul&gt;  &lt;li&gt;   &lt;a href="http://www.iteye.com/clicks/433" target="_blank"&gt;—软件人才免语言低担保 赴美带薪读研！— &lt;/a&gt;&lt;/li&gt;&lt;/ul&gt;
 &lt;br /&gt; &lt;br /&gt; &lt;br /&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 />
      <guid isPermaLink="true">https://itindex.net/detail/56723-%E7%A1%85%E8%B0%B7-%E5%9B%BD%E5%86%85-ios</guid>
      <pubDate>Thu, 02 Mar 2017 17:43:59 CST</pubDate>
    </item>
    <item>
      <title>iPhone 4s/5在iOS 9.1和iOS 9下表现如何？</title>
      <link>https://itindex.net/detail/54381-iphone-4s-ios</link>
      <description>&lt;p&gt;  &lt;a href="http://www.feng.com/iPhone/news/2015-09-20/The-iPhone-4-s-5-performance-under-the-iOS-9.1-and-iOS-9_625131.shtml"&gt;   &lt;img alt="iPhone 4s/5&amp;#22312;iOS 9.1&amp;#21644;iOS 9&amp;#19979;&amp;#34920;&amp;#29616;&amp;#22914;&amp;#20309;&amp;#65311;" border="0" hspace="4" src="http://resource.feng.com/resource/h060/h05/img201509202226170.png" vspace="4"&gt;&lt;/img&gt; &lt;/a&gt;&lt;/p&gt; &lt;p&gt;   &lt;/p&gt; &lt;p align="center"&gt;  &lt;img src="http://resource.feng.com/resource/h060/h05/img201509202226180.png"&gt;&lt;/img&gt;&lt;/p&gt; &lt;br /&gt;　　威锋网 9 月 20 日消息　苹果已经先后公布了 iOS 9.1 公测版和 iOS 9 正式版系统，一些较新的设备自然是能体验到新系统带来的各种福利了，那么老款 iPhone 搭配最新系统会有怎样的表现呢？ &lt;br /&gt; &lt;br /&gt;　　近日外媒 EverythingApplePro 将两款 iPhone 4s 和两款 iPhone 5 摆在一起做了一次直观的比较，相同的手机中左边运行着 iOS 9 正式版、右边运行着 iOS 9.1 公测版 Beta1，它们都只安装了两款应用，并均为插 SIM 卡。 &lt;br /&gt; &lt;br /&gt; &lt;br /&gt; &lt;br /&gt;　　可以看到，在同时通电后开机速度最快的是 iPhone 5，这点没有悬念，其中更快进入锁屏界面的是左边运行的 iOS 9 的手机。而在 iPhone 4s 中，同样是运行 iOS 9 的版本稍快。 &lt;br /&gt; &lt;br /&gt;　　无论是运行 iOS 9 还是 iOS 9.1，iPhone 4s 在下拉菜单时都出现了延迟，动画有些卡顿，并且在打开其它应用时也需要花费一些时间，不过依然是 iOS 9 稍胜。 &lt;br /&gt; &lt;br /&gt;　　考虑到 iPhone 4s 是一款将近 4 岁的老设备，其 800MHz 双核处理器和 512MB 内存的确会有些吃力。在最后的 GeekBench 跑分中，各自机型在 iOS 9 和 iOS 9.1 下的所得到的单核、多核成绩差距十分细微，但 iPhone 5s 是 iPhone 4 的 3 倍以上。 &lt;p&gt;&lt;/p&gt; &lt;p&gt;   &lt;a href="http://www.feng.com/iPhone/news/2015-09-20/The-iPhone-4-s-5-performance-under-the-iOS-9.1-and-iOS-9_625131.shtml"&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>新闻</category>
      <guid isPermaLink="true">https://itindex.net/detail/54381-iphone-4s-ios</guid>
      <pubDate>Sun, 20 Sep 2015 22:23:54 CST</pubDate>
    </item>
    <item>
      <title>iOS 9到底卡不卡？iPhone 4s、5、5S运行新系统实测</title>
      <link>https://itindex.net/detail/54348-ios-iphone-4s</link>
      <description>&lt;p&gt;  &lt;img src="http://7te8bu.com1.z0.glb.clouddn.com/uploads/new/article/740_740/201509/55fa68b21e414.jpg"&gt;&lt;/img&gt;&lt;/p&gt; &lt;p&gt;iOS 8对老iPhone并不友好，所以当我们得知iOS 9还支持iPhone 4s时，无疑感觉是个天大的消息。不过，4s不能像6或 6s那样获得同等的iOS 9使用体验，毕竟硬件已落后4年有余了。就拿处理器来说，iPhone 4s搭载的A5处理器，运行速度速度上就连2012年推出的iPhone 5也是它的两倍。&lt;/p&gt; &lt;p&gt;那回到老iPhone用户最关心的问题上，iPhone 4s、5、5s等升级至iOS 9后，手机使用起来会不会卡呢？日前，国外科技媒体  &lt;a href="http://arstechnica.com/apple/2015/09/ios-9-on-the-iphone-4s-a-stay-of-execution-nothing-more/" target="_self"&gt;Ars Technica对iPhone 4s和iPad 2在iOS 9以及iOS 8.4.1系统上的性能进行了对比实测&lt;/a&gt;。&lt;/p&gt; &lt;p&gt;  &lt;strong&gt;应用程序的运行反应&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;该测试记录了设备升级至iOS 9和iOS 8.4.1系统后，完全打开某些应用的界面所需的时间。为了减少误差，每个应用都进行了“打开-退出”重复3次的测试，记录它们的平均时间。如下表：&lt;/p&gt; &lt;p&gt;  &lt;img src="http://7te8bu.com1.z0.glb.clouddn.com/uploads/new/article/740_740/201509/55fa60f1a842b.jpg"&gt;&lt;/img&gt;&lt;/p&gt; &lt;p&gt;iPhone 4s&lt;/p&gt; &lt;p&gt;  &lt;em&gt;   &lt;img src="http://7te8bu.com1.z0.glb.clouddn.com/uploads/new/article/740_740/201509/55fa610c2ed48.jpg"&gt;&lt;/img&gt;&lt;/em&gt;&lt;/p&gt; &lt;p&gt;ipad 2  &lt;br /&gt;&lt;/p&gt; &lt;p&gt;从表格可见，在打开Safari、相机、邮件、设置、消息等应用，甚至是重启的情况下，iOS 9的运行速度要比iOS 8.4.1慢，但是其中的差距都不过是零点几秒（重启除外），用户在平时的使用中是几乎察觉不出的。但是，这并没有说，iOS 8本来算得上流畅哦，具体用心感受。&lt;/p&gt; &lt;p&gt;  &lt;strong&gt;电池续航力&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;据介绍，iOS 9的一大特性是能够增强苹果设备的续航。那这是不是真的呢？本轮测试同样对iPhone 4S、5、5S、6、6 Plus等在iOS 8.4.1和iOS 9.0系统下的续航进行了测试。根据时间数据，大部分设备都有微量的提升，手机方面iPhone 6 Plus的提升最为明显。&lt;/p&gt; &lt;p&gt;  &lt;img src="http://7te8bu.com1.z0.glb.clouddn.com/uploads/new/article/740_740/201509/55fa61353a3fc.png"&gt;&lt;/img&gt;&lt;/p&gt; &lt;p&gt;  &lt;strong&gt;那应该升级吗？&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;外媒分析称，对于iPhone 4s的用户来说，如果正运行的是iOS 8，那尽情升级吧。即使细小的屏幕用着不太爽，但仍旧可以获得很多此前介绍过的iOS 9的新特性，比如更清晰美观的键盘等等。至少，升级之后你还可以说iPhone 4s是一款长寿手机。&lt;/p&gt; &lt;p&gt;不过值得一提的是，由于缺乏某些硬件驱动，有许多iOS 9的新功能iPhone 4s是不支持的，比如：&lt;/p&gt; &lt;blockquote&gt;  &lt;p&gt;·新的 Spotlight 屏幕、新的 Siri 功能以及第三方 Spotlight 搜索。&lt;/p&gt;  &lt;p&gt;·AirDrop&lt;/p&gt;  &lt;p&gt;·TouchID&lt;/p&gt;  &lt;p&gt;·应用程序切换&lt;/p&gt;  &lt;p&gt;·TouchID/Apple Pay&lt;/p&gt;&lt;/blockquote&gt; &lt;p&gt;但如果目前运行的是iOS7，那建议你尽可能地升级。这倒不是说为了功能上的提升，而是开发者将很快放弃对老系统的应用更新了。&lt;/p&gt; &lt;p&gt;明年，苹果或者会带来新的惊喜，但对iPhone 4s来说，这次恐怕是尽头了。如果真心希望有焕然一新的感觉，最直接的方式是把手机升级至iPhone 6s。&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>新鲜</category>
      <guid isPermaLink="true">https://itindex.net/detail/54348-ios-iphone-4s</guid>
      <pubDate>Thu, 17 Sep 2015 16:07:00 CST</pubDate>
    </item>
    <item>
      <title>更注重稳定性提升的iOS 9 正式版如约而至</title>
      <link>https://itindex.net/detail/54342-%E7%A8%B3%E5%AE%9A%E6%80%A7-%E6%8F%90%E5%8D%87-ios</link>
      <description>&lt;p&gt;  &lt;a href="http://www.feng.com/iPhone/news/2015-09-17/Pay-more-attention-to-improve-stability-of-iOS-officially-version-and-to-9_624774.shtml"&gt;   &lt;img alt="&amp;#26356;&amp;#27880;&amp;#37325;&amp;#31283;&amp;#23450;&amp;#24615;&amp;#25552;&amp;#21319;&amp;#30340;iOS 9 &amp;#27491;&amp;#24335;&amp;#29256;&amp;#22914;&amp;#32422;&amp;#32780;&amp;#33267;" border="0" hspace="4" src="http://resource.feng.com/resource/h059/h97/img201509170141080.png" vspace="4"&gt;&lt;/img&gt; &lt;/a&gt;&lt;/p&gt; &lt;p&gt;   &lt;/p&gt; &lt;p align="center"&gt;  &lt;img src="http://resource.feng.com/resource/h059/h97/img201509170141090.jpg"&gt;&lt;/img&gt;&lt;/p&gt; &lt;br /&gt;　　威锋网讯，就在刚刚苹果如期推送了 iOS 9 的正式版本，相比于 iOS 8 的众多新功能，iOS 9 主打稳定性和性能提升，能够安装 iOS 8 的设备都可以顺利安装 iOS 9。用户可以通过 OTA 的方式，又或者是通过 iTunes 下载固件进行更新。 &lt;strong&gt;注意：目前更新的用户可能较多，造成网络拥挤，如果现在无法更新的话，可选择 iTunes 下载固件或等到早上再进行更新。&lt;/strong&gt; &lt;br /&gt; &lt;br /&gt; &lt;p align="center"&gt;  &lt;img src="http://resource.feng.com/resource/h059/h97/img201509170141091.png"&gt;&lt;/img&gt;&lt;/p&gt; &lt;br /&gt;　　本次更新推出了强大的搜索功能和更完善的 Siri 功能，iPhone、iPad 和 iPod touch 因而更加智能、主动。新增的 iPad 多任务功能让两个应用能并行排列或通过全新的画中画功能同步运行。此外，各种内建应用也更为强大，例如“地图”提供了详细的公交信息，“备忘录”应用已经重新设计，而且推出了全新的 News 应用。操作系统基础得以改善，提高了性能，增强了安全性并增加了最多一个小时的电池寿命。 &lt;br /&gt; &lt;br /&gt;　　 &lt;strong&gt;智能&lt;/strong&gt; &lt;br /&gt; &lt;br /&gt;　　主动协助 &lt;br /&gt; &lt;br /&gt;　　在您开始键入之前即提供有关信息 &lt;br /&gt;　　在您需要动身赴约时发出提醒，同时将交通状况考虑在内 &lt;br /&gt;　　了解您在特定地点或时间通常听些什么，可以自动显示首选音乐和视频应用的回放控制选项 &lt;br /&gt;　　根据“邮件”中找到的信息，向各种应用建议可增加的事件和联系人资料 &lt;br /&gt; &lt;br /&gt;　　改进了 Siri &lt;br /&gt; &lt;br /&gt;　　基于日期、位置和相簿名称来搜索个人照片和视频 &lt;br /&gt;　　要求 Siri 来提醒您在 Safari、“邮件”、“备忘录”和“信息”等应用中看到的有关内容 &lt;br /&gt;　　提供大众公交路线 &lt;br /&gt; &lt;br /&gt;　　增强 Spotlight 搜索功能 &lt;br /&gt; &lt;br /&gt;　　获取体育比分、天气状况和股票价格 &lt;br /&gt;　　执行简单的数学计算和单位换算 &lt;br /&gt;　　在联系人的搜索结果中发送信息、拨打电话和 FaceTime 通话 &lt;br /&gt; &lt;br /&gt;　　 &lt;strong&gt;新增 iPad 功能&lt;/strong&gt; &lt;br /&gt; &lt;br /&gt;　　Slide Over &lt;br /&gt; &lt;br /&gt;　　不用离开正在使用的应用即可快速使用另一款应用 &lt;br /&gt;　　在 Slide Over 应用之间轻松切换 &lt;br /&gt;　　支持 Apple 应用和已启用的第三方应用 &lt;br /&gt; &lt;br /&gt;　　Split View &lt;br /&gt; &lt;br /&gt;　　可同时查看两个应用并与之互动 &lt;br /&gt;　　能够调整视图，让两个应用大小相同，或一大一小 &lt;br /&gt;　　支持 Apple 应用和已启用的第三方应用 &lt;br /&gt;　 &lt;br /&gt;　　画中画 &lt;br /&gt; &lt;br /&gt;　　一边使用您喜爱的应用一边继续观看视频 &lt;br /&gt;　　支持 Safari 视频、FaceTime、视频和播客及已启用的第三方应用 &lt;br /&gt; &lt;br /&gt;　　改进了 QuickType &lt;br /&gt; &lt;br /&gt;　　利用 Multi-Touch 手势，在 iPad 上选择文本更为轻松 &lt;br /&gt;　　在 iPad 上利用快捷工具栏来快速访问文本编辑工具 &lt;br /&gt;　　支持硬件键盘快捷键 &lt;br /&gt;　　支持所有 Unicode 旗帜表情符号 &lt;br /&gt; &lt;br /&gt;　　 &lt;strong&gt;内建应用&lt;/strong&gt; &lt;br /&gt; &lt;br /&gt;　　改进了“地图” &lt;br /&gt; &lt;br /&gt;　　支持特选大城市中的大众公交路线、车站进出站口信息、时刻安排和路线 &lt;br /&gt;　　根据诸如食物、饮料、购物和娱乐之类的类别来浏览您附近的地点 &lt;br /&gt;　　在加盟零售方的地点卡上显示该处是否支持 Apple Pay &lt;br /&gt;　　地点卡包括维基百科上关于地标和城市的信息 &lt;br /&gt; &lt;br /&gt;　　重新设计了“备忘录”应用 &lt;br /&gt; &lt;br /&gt;　　用内建相机或从“照片图库”将照片来添加到您的备忘录中 &lt;br /&gt;　　创建实用的核对清单，轻点一下即可勾选已完成的项目 &lt;br /&gt;　　仅用单指速绘，即可记下闪现的想法 &lt;br /&gt;　　利用其他应用中的“共享”菜单直接将感兴趣的项目存储到“备忘录”中 &lt;br /&gt; &lt;br /&gt;　　全新的 News 新闻应用 &lt;br /&gt; &lt;br /&gt;　　阅读喜爱的报纸、杂志、博客，或从一百万多个主题中选读 &lt;br /&gt;　　精美的设计排版、布局、图库、视频、动画及更多内容 &lt;br /&gt;　　在“为你推荐”中浏览根据您的兴趣爱好挑选出来的文章。在“探索”中查找推荐的频道和主题。您阅读的内容越多，News 新闻就越个性化 &lt;br /&gt;　　轻松与朋友共享文章或将文章存储起来稍后阅读－您甚至可以离线阅读 &lt;br /&gt; &lt;br /&gt;　　改进了“邮件” &lt;br /&gt; &lt;br /&gt;　　搜索功能可根据发件人、收件人、主题或选项组合来过滤搜索结果，帮您快速找到要寻找的内容 &lt;br /&gt;支持“标记”功能，让您无需离开“邮件”即可给图像或 PDF 附件添加文本、形状，甚至签名等注释，并发回邮件 &lt;br /&gt;轻松存储收到的附件，或者在编写新邮件时添加来自 iCloud Drive 或其他文稿提供者的文件 &lt;br /&gt; &lt;br /&gt;　　改进了 Apple Pay 和 Wallet &lt;br /&gt; &lt;br /&gt;　　支持 Discover 卡 &lt;br /&gt;　　支持回馈卡、商店信用卡和借记卡 &lt;br /&gt;　　若要加速结账，您可以在锁屏连点两下主屏幕按钮，并将手指放在 Touch ID 上来准备付款 &lt;br /&gt; &lt;br /&gt;　　新建 iCloud Drive 应用 &lt;br /&gt; &lt;br /&gt;　　按照日期、名称或您在 Mac 上添加的标记来轻松搜索文件，或者浏览新 iCloud Drive &lt;br /&gt;　　在兼容的应用中打开文件，或者与您选择的对象共享 &lt;br /&gt;　　整理文件夹和文件 &lt;br /&gt;　　在“设置”&amp;gt; iCloud &amp;gt; iCloud Drive 中获取 iCloud Drive 应用，然后选择“在主屏幕上显示” &lt;br /&gt; &lt;br /&gt;　　改进了 CarPlay &lt;br /&gt; &lt;br /&gt;　　回放音频留言，听取留言者自己的声音 &lt;br /&gt;　　完全支持车内旋钮控制，您可以倾斜或旋转来滚动浏览列表或者平移查看“地图” &lt;br /&gt;　　支持汽车制造商的 CarPlay 应用 &lt;br /&gt; &lt;br /&gt;　　 &lt;strong&gt;基础&lt;/strong&gt; &lt;br /&gt; &lt;br /&gt;　　延长电池使用寿命 &lt;br /&gt; &lt;br /&gt;　　在需要充电前增加了最多一小时的可用时间 &lt;br /&gt;　　检测屏幕朝下的状态，在不用时关闭显示器 &lt;br /&gt;　　低电量模式下最优化设备性能，可将电池电量延长最多三小时 &lt;br /&gt; &lt;br /&gt;　　下载软件更新项目需要更少空间，并可选择稍后安装 &lt;br /&gt; &lt;br /&gt;　　iOS 应用和用户界面现在使用 Metal 来实现更快的滚动速度、更流畅的动画效果，和更好的整体性能 &lt;br /&gt; &lt;br /&gt;　　增强了安全性能，支持双重鉴定并在 Touch ID 设备中默认六位密码 &lt;br /&gt; &lt;br /&gt;　　 &lt;strong&gt;其他改进功能&lt;/strong&gt; &lt;br /&gt; &lt;br /&gt;　　全新 San Francisco 系统字体 &lt;br /&gt;　　拨打 FaceTime 通话时若无人接听，您可以选择留言 &lt;br /&gt;　　使用“电话”应用中的共享表单来共享语音留言 &lt;br /&gt;　　数据监测器可跟踪航班和包裹 &lt;br /&gt;　　“健康”应用新增对生殖健康、紫外线照射、摄水量和久坐状态等数据类型的支持 &lt;br /&gt;　　HomeKit 新增对电动窗户和窗帘，动态感应器和家庭安全系统等配件的支持 &lt;br /&gt;　　“播客”的设计焕然一新，让您能轻松找到喜爱节目的最新单集，并在新单集可用后及时收到通知 &lt;br /&gt;　　支持在 iPad、iPod touch 和 Mac 上通过合作的运营商进行 Wi-Fi 通话，iPhone 无需在附近 &lt;br /&gt;　　“Wi-Fi 助理”可在 Wi-Fi 连接较弱时自动使用蜂窝移动数据 &lt;br /&gt;　　运用“转移到 iOS”应用来转移安卓设备上的内容，请参阅  &lt;a href="http://www.apple.com/cn/move-to-ios"&gt;http://www.apple.com/cn/move-to-ios&lt;/a&gt; 来获取更多详细信息 &lt;br /&gt; &lt;br /&gt;　　 &lt;strong&gt;支持更多语言&lt;/strong&gt; &lt;br /&gt; &lt;br /&gt;　　新增“苹方”中文系统字体 &lt;br /&gt;　　改进了 QuickType 中的中文预测、学习和自动改正功能 &lt;br /&gt;　　重新设计了九宫格中文键盘上的标点符号输入 &lt;br /&gt;　　为从右向左的语言重新设计了用户界面 &lt;br /&gt;　　新增对奥地利、比利时（法语和荷兰语）及挪威 Siri 功能的支持 &lt;br /&gt;　　新增对墨西哥的 Spotlight 搜索功能的支持 &lt;br /&gt;　　新增下列语言的键盘：法文（比利时）、德文（奥地利）、古吉拉特文、印地文（转写）、印度英文、旁遮普文、西班牙文（墨西哥）和泰卢固文 &lt;br /&gt;　　支持下列语言的预测输入：法文（比利时）、德文（奥地利）、韩文、俄文、西班牙文（墨西哥）和土耳其文 &lt;br /&gt;　　支持下列语言的听写功能：荷兰文（比利时）、英文（爱尔兰、菲律宾、南非）、法文（比利时）、德文（奥地利）、西班牙文（智利、哥伦比亚） &lt;br /&gt;　　芬兰文和韩文的拼写检查 &lt;br /&gt;　　印地文、挪威文和瑞典文的定义字典 &lt;br /&gt;　法文－英文和德文－英文双语字典 &lt;br /&gt;　　QuickType 中新增日文自动改正功能，并改进了输入预测和学习功能 &lt;br /&gt;　　新增阿拉伯文和印地文中切换数字系统的选项 &lt;br /&gt; &lt;br /&gt;　　 &lt;strong&gt;企业与教育&lt;/strong&gt; &lt;br /&gt; &lt;br /&gt;　　无需配置 iTunes Store 帐户，即可将应用直接指定给 iOS 设备 &lt;br /&gt;　　改进了 Microsoft Exchange ActiveSync v16 用户日历的可靠性 &lt;br /&gt;　　扩大了“单应用 微披恩”对内建 IPSec 和 IKEv2 微披恩 客户端的支持 &lt;br /&gt;　　新增对受管理的应用的网络控制，帮助限制国际数据漫游成本 &lt;br /&gt;　　新增限制来防止更改密码、设备和墙纸，或在受管理的设备上停用 AirDrop &lt;br /&gt;　　支持 微披恩 的第三方应用扩展、高级内容过滤和强制 Wi-Fi 网络 &lt;br /&gt; &lt;br /&gt;　　 &lt;strong&gt;辅助功能&lt;/strong&gt; &lt;br /&gt; &lt;br /&gt;　　“触摸调节”给那些有肢体动作限制的用户提供更多触摸控制选项 &lt;br /&gt;　　“切换控制方案”自定功能或者创建您自己的功能 &lt;br /&gt;　　支持 VoiceOver 用户的 Siri 声音 &lt;br /&gt;　　其他 AssistiveTouch 的自定选项 &lt;br /&gt;　　硬件键盘支持 “按键重复”、“慢速键”和“粘滞键” &lt;br /&gt;　　改进了 MFi 助听器的音频路径，来选择音频播放方式 &lt;br /&gt; &lt;br /&gt;　　部分功能不适用于所有国家或地区，如需更多信息，请访问： &lt;a href="http://www.apple.com/cn/ios/feature-availability"&gt;http://www.apple.com/cn/ios/feature-availability&lt;/a&gt;和  &lt;a href="http://www.apple.com/cn/ios/whats-new"&gt;http://www.apple.com/cn/ios/whats-new&lt;/a&gt; &lt;br /&gt;　　如需本更新的安全性内容，请访问此网站： &lt;br /&gt;　　 &lt;a href="http://support.apple.com/kb/HT1222?viewlocale=zh_CN"&gt;http://support.apple.com/kb/HT1222?viewlocale=zh_CN&lt;/a&gt; &lt;br /&gt; &lt;br /&gt;　　不知道今晚等待升级的朋友有多少呢？如果 &lt;a href="http://www.weiphone.com/newbie/iPhone/terminology/2009-08-31/What_is_escape_JailBreak_205530.shtml"&gt;越狱&lt;/a&gt;用户想要保留 &lt;a href="http://www.weiphone.com/newbie/iPhone/terminology/2009-08-31/What_is_escape_JailBreak_205530.shtml"&gt;越狱&lt;/a&gt;状态的话，就暂时略过今晚的更新吧。关于的 iOS 9 正式版体验，请继续关注威锋网的后续报道。 &lt;p&gt;&lt;/p&gt; &lt;p&gt;   &lt;a href="http://www.feng.com/iPhone/news/2015-09-17/Pay-more-attention-to-improve-stability-of-iOS-officially-version-and-to-9_624774.shtml"&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>新闻</category>
      <guid isPermaLink="true">https://itindex.net/detail/54342-%E7%A8%B3%E5%AE%9A%E6%80%A7-%E6%8F%90%E5%8D%87-ios</guid>
      <pubDate>Thu, 17 Sep 2015 01:33:58 CST</pubDate>
    </item>
    <item>
      <title>iOS 统计打点那些事</title>
      <link>https://itindex.net/detail/54317-ios-%E7%BB%9F%E8%AE%A1-%E6%89%93%E7%82%B9</link>
      <description>&lt;p&gt;统计打点是 App 开发里很重要的一个环节，App 的运行状态、改版后的效果、用户的各种行为等都需要打点，市面上也有不少可供选择的第三方库。 假设产品有这么个需求：当用户在详情页点击购买按钮时，记录一下事件。我们实现起来大概会是这样&lt;/p&gt;
 &lt;div&gt;  &lt;pre&gt;   &lt;code&gt;// DetailViewController.m

- (void)onBuyButtonTapped:(UIButton *)button
{
    // do some stuff, maybe send a request to server
    [XXXAnalytics event:kSomeEventYouDefined];
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
 &lt;p&gt;这个需求就这样轻松搞定了，但细细想想还是有不少问题的：&lt;/p&gt;

 &lt;ol&gt;
  &lt;li&gt;页面上会有其他的 Button，可能每个 Button 都要放上这么一段代码。&lt;/li&gt;
  &lt;li&gt;这些统计其实跟具体的业务无关，没必要跟业务代码混杂在一起，不优雅。&lt;/li&gt;
  &lt;li&gt;当改版或者重构时，有可能忘了把相应的打点代码迁移过去。&lt;/li&gt;
&lt;/ol&gt;

 &lt;p&gt;所以需要一种更好的方式来做这件事，这就是使用 AOP(  &lt;a href="http://en.wikipedia.org/wiki/Aspect-oriented_programming"&gt;Aspect-Oriented-Programming&lt;/a&gt;)，翻译过来就是「面向切面编程」&lt;/p&gt;

 &lt;blockquote&gt;
  &lt;p&gt;通过预编译方式和运行期动态代理实现在不修改源代码的情况下给程序动态统一添加功能的一种技术。&lt;/p&gt;
&lt;/blockquote&gt;

 &lt;p&gt;简单来说，就是可以动态的在函数调用的前后插一段代码。iOS 可以使用 Pete Steinberger 开发的   &lt;a href="https://github.com/steipete/Aspects"&gt;Aspects&lt;/a&gt; 这个库，大致原理是在 runtime 层，通过 swizzle method 来实现的。&lt;/p&gt;

 &lt;p&gt;来看一个小 Demo&lt;/p&gt;
 &lt;div&gt;  &lt;pre&gt;   &lt;code&gt;[UIViewController aspect_hookSelector:@selector(viewWillAppear:) withOptions:AspectPositionAfter usingBlock:^(id&amp;lt;AspectInfo&amp;gt; aspectInfo, BOOL animated) {
    NSLog(@&amp;quot;View Controller %@ will appear animated: %tu&amp;quot;, aspectInfo.instance, animated);
} error:NULL];
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
 &lt;p&gt;这样在   &lt;code&gt;UIViewController&lt;/code&gt; 的   &lt;code&gt;viewWillAppear:&lt;/code&gt; 被调用后，还会再调一下我们定义的 Block，这段日志就会被输出。而打点正好符合这种场景：正事干完之后，额外干一些跟业务无关的事情。&lt;/p&gt;

 &lt;p&gt;上面的例子，我们通过 AOP 来做的话，大概就是这样&lt;/p&gt;
 &lt;div&gt;  &lt;pre&gt;   &lt;code&gt;// DetailViewController.m
- (void)onBuyButtonTapped:(UIButton *)button
{
    // do some stuff, maybe send a request to server
    // no need to call [XXXAnalytics event:]
}

// AppDelegate.m
- (void)setupAnalytics
{
    [DetailViewController aspect_hookSelector:@selector(onBuyButtonTapped:) withOptions:AspectPositionAfter usingBlock:^(id&amp;lt;AspectInfo&amp;gt; aspectInfo, BOOL animated) {
        [XXXAnalytics event:kSomeEventYouDefined];
    } error:NULL];
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
 &lt;p&gt;这样统计代码就从业务代码中剥离出来了。但是又产生了一个新问题，多个 Button Event，岂不是要写很多行这样的代码，「重复」这样的事情，作为一个程序员怎么能忍，简单，造一个方法&lt;/p&gt;
 &lt;div&gt;  &lt;pre&gt;   &lt;code&gt;- (void)trackEventWithClass:(Class)klass selector:(SEL)selector event:(NSString *)event
{
    [klass aspect_hookSelector:@selector(selector) withOptions:AspectPositionAfter usingBlock:^(id&amp;lt;AspectInfo&amp;gt; aspectInfo, BOOL animated) {
        [XXXAnalytics event:event];
    } error:NULL];
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
 &lt;p&gt;使用起来就像这样&lt;/p&gt;
 &lt;div&gt;  &lt;pre&gt;   &lt;code&gt;- (void)setupAnalytics
{
    [self trackEventWithClass:DetailViewController selector:@seletor(onBuyButtonTapped:) event:kSomeEventYouDefined];
    [self trackEventWithClass:ListViewController selector:@seletor(followButtonTapped:) event:kAnotherEventYouDefined];
    // ...
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
 &lt;p&gt;看起来又干净了些。这时，产品经理又提了个需求：当这个按钮点击时，如果已经登录了，发送 EventA，如果没有登录则发送 EventB，也就是说，不再只是   &lt;code&gt;[XXXAnalytics event:]&lt;/code&gt; 这么简单了，还需要加上额外的逻辑，这也难不倒我们，加上一个   &lt;code&gt;block&lt;/code&gt; 即可。&lt;/p&gt;
 &lt;div&gt;  &lt;pre&gt;   &lt;code&gt;- (void)trackEventWithClass:(Class)klass
                   selector:(SEL)selector
               eventHandler:(void (^)(id&amp;lt;AspectInfo&amp;gt; aspectInfo))eventHandler
{
    [klass aspect_hookSelector:@selector(selector) withOptions:AspectPositionAfter usingBlock:^(id&amp;lt;AspectInfo&amp;gt; aspectInfo, BOOL animated) {
        if (eventHandler) {
            eventHandler(aspectInfo);
        }
    } error:NULL];
}

// 使用
[self trackEventWithClass:DetailViewController selector:@seletor(onBuyButtonTapped:) eventHandler:^(id&amp;lt;AspectInfo&amp;gt; aspectInfo){
    user.loggedIn ? [XXXAnalytics event:EventA] : [XXXAnalytics event:EventB];
}];
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
 &lt;p&gt;好了，现在只要不是太复杂的打点逻辑(那些需要方法上下文变量的)我们都能应付了，接下来就该等产品来验收了。产品搬了个凳子坐在身边，然后点一下 Button，看一下 Console，被几轮蹂躏后，产品也慢慢地接受了这种验收方式。后来某一天，忽然发现某一项或某几项数据有异常，然后找到开发，瞄了一眼：哦，这个方法被重构了。或者新加的方法忘了加统计了。只能等到下个版本再加上了，如果只是一般的统计数据倒还好，跟钱相关的就麻烦了。&lt;/p&gt;

 &lt;p&gt;那么有没有一种直观的验证方式呢？当然，程序员是万能的呀。一个理想的状况是，产品打开 App 后，开启某个开关就能看到所有会发送 Event 的按钮，就像这样&lt;/p&gt;

 &lt;p&gt;  &lt;img alt="" src="http://blog.leezhong.com/image/analytics_highlight.jpg"&gt;&lt;/img&gt;&lt;/p&gt;

 &lt;p&gt;其中数字代表了   &lt;code&gt;EventID&lt;/code&gt;。如何实现呢？还记得注册事件时，我们有传入   &lt;code&gt;class&lt;/code&gt; 和   &lt;code&gt;selector&lt;/code&gt; 么，一般我们都会有一个   &lt;code&gt;BaseViewController&lt;/code&gt;，那么就可以在   &lt;code&gt;BaseViewController&lt;/code&gt; 的   &lt;code&gt;viewDidAppear:&lt;/code&gt; 里做点文章了。&lt;/p&gt;
 &lt;div&gt;  &lt;pre&gt;   &lt;code&gt;// BaseViewController.m

- (void)viewDidAppear:(BOOL)animated
{
    [super viewDidAppear:animated];
    // 获取已经注册过的 classes
    NSDictionary *registeredClasses = [OurAnalytics sharedInstance].registeredClasses;

    [registeredClasses enumerateKeysAndObjectsUsingBlock:^(NSString *className, NSArray *selectors, BOOL *stop) {
        if ([self isKindOfClass:NSClassFromString(className)]) {
            // 如何根据 selector 找到它的宿主？
        }
    }];
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
 &lt;p&gt;所以现在问题就剩下，如何根据   &lt;code&gt;selector&lt;/code&gt; 找到对应的 Button，这里要注意，有些 Button 可能要等网络请求完成才会出现，比如   &lt;code&gt;TableViewCell&lt;/code&gt; 里的 Button。&lt;/p&gt;

 &lt;p&gt;没有想到太方便的方法，简单粗暴点就是设置个 Timer 每隔一段时间扫一下 subviews，如果是 button 或 包含 tapGesture 的，就拿它们的 action 对比一下，如果 match 就可以高亮那个 button / view 了。&lt;/p&gt;

 &lt;p&gt;EventID 也一样，之前在注册时也会传一个 EventID 过来，这里直接显示出来即可。对于那些传   &lt;code&gt;eventHandler&lt;/code&gt; 的就不行了。&lt;/p&gt;

 &lt;p&gt;所以理论上是可行的，性能上会稍微有点损耗，尤其是当 subViews 的结构比较复杂时，不过只是内部用来做验证，所以这也不是什么问题。&lt;/p&gt;

 &lt;p&gt;看起来效果已经不错了，有没有可能让这套体系再灵活一些？比如可以从后端制定打点规则？客户端只是读取一个配置文件，就像这样&lt;/p&gt;
 &lt;div&gt;  &lt;pre&gt;   &lt;code&gt;- (void)setupAnalytics
{
    // analyticsRules 是从配置文件中读取出来的
    [analyticsRules enumerateObjectsUsingBlock:^(NSDictionary *rules, NSUInteger idx, BOOL *stop) {
        Class klass = NSClassFromString(rules[@&amp;quot;class&amp;quot;]);
        SEL selector = NSSelectorFromString(rules[@&amp;quot;selector&amp;quot;]);
        NSString *eventID = rules[@&amp;quot;eventID&amp;quot;];
        [self trackEventWithClass:klass seletor:seletor event: eventID];
    }];
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
 &lt;p&gt;那如果在后台的时候填错了 Class 或 Selector 怎么办？还好有   &lt;code&gt;objc_getClassList&lt;/code&gt; 和   &lt;code&gt;class_copyMethodList&lt;/code&gt; 这两个运行时方法，有了它们就可以在 App 启动时扫一遍已注册的类（过滤掉 UI / NS 开头的），然后将它们的 seletor 也一并保存下来发送给服务端，当然这种操作只需在适当的时机做一下就可以了，比如集成打包时。&lt;/p&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 />
      <guid isPermaLink="true">https://itindex.net/detail/54317-ios-%E7%BB%9F%E8%AE%A1-%E6%89%93%E7%82%B9</guid>
      <pubDate>Wed, 09 Sep 2015 08:00:00 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>iOS客户端hack的两种姿势</title>
      <link>https://itindex.net/detail/55126-ios-%E5%AE%A2%E6%88%B7%E7%AB%AF-hack</link>
      <description>&lt;blockquote&gt;
  &lt;p&gt;分析某商城漏洞，在漏洞验证时采用了两种iOS上的hack工具：cycript和reveal，各有风情，均能攻城拔寨，实乃我辈日常居家、杀人越货之利刃，现与诸君共享之。&lt;/p&gt;
&lt;/blockquote&gt;
 &lt;h1&gt;0x00 漏洞缘起&lt;/h1&gt;
 &lt;hr&gt;&lt;/hr&gt;
 &lt;p&gt;该商城的iOS版app为用户提供了找回密码的功能，用户需通过三个步骤找回密码：&lt;/p&gt;
 &lt;ol&gt;
  &lt;li&gt;输入一个本地的图形辨识验证码（多余？）&lt;/li&gt;
  &lt;li&gt;提供用户手机号，输入一个短信验证码&lt;/li&gt;
  &lt;li&gt;输入新的密码、确认密码，然后Bingo&lt;/li&gt;
&lt;/ol&gt;
 &lt;p&gt;&lt;/p&gt;
 &lt;p&gt;要找漏洞，先抓包看看，先易后难，实在不行再做逆向分析。burp架起先，开代理、关闭intercept、ssl协议算法全勾上（免得碰上奇葩ssl协商参数，以前遭过冤枉）。iPhone上在wifi选项中配置代理，指向burp。然后开app，burp报错。&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="enter description here" src="http://static.wooyun.org//drops/20160126/2016012610354022621180.png"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;难道客户端对证书进行了pin？冷笑三声，开启我的ssl kill switch。再开程序，登录，进商城，一切顺利。ok，现在进入正题，找漏洞。进入找回密码功能，按步骤进行提交，重设了密码，然后到burp中查看抓到的数据包，下面是重置密码第三步发出的post数据包。&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="enter description here" src="http://static.wooyun.org//drops/20160126/2016012610354252940249.png"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;仔细分析这个数据包，发现除了一个签名，其它跟第二步获得的手机短信验证码没有一毛钱的关系，难道这个“你妈是你妈”问题的验证是在客户端做的？那我们是不是改改电话号码就能重置任意用户密码了？在burp中改包试了试，不行的，服务器返回错误。看来在Sign中有文章，看来需要从客户端想办法了。&lt;/p&gt;
 &lt;p&gt;丢到ida中跑，找到如下关键函数：&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="enter description here" src="http://static.wooyun.org//drops/20160126/2016012610354325593330.png"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;然后再初略的溜了一下该函数涉及到的各种参数。&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="enter description here" src="http://static.wooyun.org//drops/20160126/2016012610354517627434.png"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;好像没有涉及到第二步中的短信验证码，应该只是把电话号码、新口令作为签名的可变输入。不错，省得逆向签名算法了，人生苦短，可以节约一堆脑细胞了。基本思路出来了，就是正常执行前两步，然后在第三步中将界面电话号码（用户账号）改为其它被攻击的账号。&lt;/p&gt;
 &lt;h1&gt;0x01 cycript大法&lt;/h1&gt;
 &lt;hr&gt;&lt;/hr&gt;
 &lt;p&gt;先介绍一下cycript，官方解释：&lt;/p&gt;
 &lt;blockquote&gt;
  &lt;p&gt;Cycript allows developers to explore and modify running applications on either iOS or Mac OS X using a hybrid of Objective-C++ and JavaScript syntax through an interactive console that features syntax highlighting and tab completion.&lt;/p&gt;
&lt;/blockquote&gt;
 &lt;p&gt;简而言之：插入进程，想改就改。不过这货是命令行模式，混合了ObjectC和JS，官方帮助写的精简了点，还是稍微有点烦。不过自带了tab键补全功能，略感欣慰。下面一步步来使用。&lt;/p&gt;
 &lt;p&gt;实现目标的先决条件是要找到找回密码过程第三步界面对应的对象，然后对该对象中与注册手机号码相关的属性进行操作。&lt;/p&gt;
 &lt;p&gt;先要将app运行到我们要攻击的第三步，也就是输入新口令和确认新口令的界面，我们的目标是使用hack方法修改不可编辑的注册手机号。&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="enter description here" src="http://static.wooyun.org//drops/20160126/2016012610354715520527.png"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;当然，我们也要先将app使用classdump将app的类信息获取，发现了其中与口令重置相关的信息如下，可以辅助下一步的攻击工作。&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="enter description here" src="http://static.wooyun.org//drops/20160126/2016012610354955225623.png"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;使用ps命令获得被注入的进程信息，然后使用如下命令启动cycript，涂红部分为进程名称，当然，使用pid也可以。&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="enter description here" src="http://static.wooyun.org//drops/20160126/2016012610355063279722.png"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;然后获得当前app的UI句柄。&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="enter description here" src="http://static.wooyun.org//drops/20160126/2016012610355216286820.png"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;获得app的keyWindow。&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="enter description here" src="http://static.wooyun.org//drops/20160126/2016012610355335470920.png"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;获得keyWindow的rootViewController。&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="enter description here" src="http://static.wooyun.org//drops/20160126/20160126103555374001022.png"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;获得当前可视的ViewController。做界面hack，前几步基本是一样的，目的就是找到当前显示界面对应的对象。&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="enter description here" src="http://static.wooyun.org//drops/20160126/20160126103556551861125.png"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;各位看官发现没的，这个FindPwdViewController正是我们使用classdump发现的密码重置类，看来接近问题答案了。&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="enter description here" src="http://static.wooyun.org//drops/20160126/20160126103558859771219.png"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;接下来可以直接根据classdump出的类信息敲入如下命令：&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="enter description here" src="http://static.wooyun.org//drops/20160126/20160126103559891851318.png"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;看到text没得，就是界面显示的注册手机号，改改改！&lt;/p&gt;
 &lt;p&gt;再看手机上，注册手机号已经修改成了目标手机号。点击完成，成功！&lt;/p&gt;
 &lt;h1&gt;0x02 reveal大法&lt;/h1&gt;
 &lt;hr&gt;&lt;/hr&gt;
 &lt;p&gt;reveal工具其实应该被奉为界面hack的第一利器，无它，太好用了。各位可能还在纠结上面敲入的各种烦人命令，如果对ObjectC和面向对象思想以及各类砸壳、dump工具不熟的化，要完成cycript大法，还真不是一件容易活。但reveal不同，在安装好后，就是轻轻的点点鼠标，敲个电话号码的事。&lt;/p&gt;
 &lt;p&gt;不过还是有石头要挡道，就是reveal在手机端的安装。下面是笔者的安装经验:&lt;/p&gt;
 &lt;p&gt;reveal原本是作为xcode的辅助工具，为app开发者使用的，本不具备插入任意程序的功能，但在越狱的iphone上借助cydia substrate可以插入任意app，包括springboard，安装方法参见  &lt;a href="http://42.96.192.22/?tag=%E8%B0%83%E8%AF%95%E5%88%A9%E5%99%A8reveal"&gt;参考1&lt;/a&gt;。笔者是在iOS9上装的，一定要按这篇文章说得，把framework搞到手机端，另外，plist文件要用plisteditor等专用工具编写，还有就是Filter一定要写，要不然就是iPhone6S Plus也会慢的无法忍受！如果出现这种窘境，立即home+power硬关机，按音量+开机跳过substrate，重新进行Filter配置。最后提醒一句，reveal所在的主机和被调试手机要在同一局域网。&lt;/p&gt;
 &lt;p&gt;reveal安装好之后，需要找到被注入程序的bundle id，就在app的ipa包的info.plist文件中，使用plisteditor等工具打开，搜CFBundleIdentifier就能找到，下面涂红的部分即是。&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="enter description here" src="http://static.wooyun.org//drops/20160126/20160126103601583831416.png"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;找到bundle id后将其填到Filter中即可。启动程序，在reveal的connect目标中会发现目标app，直接connect，reveal中会出现手机端app的界面。借一张上面文章的图，各位感受一下，各种界面元素一览无余，而且可以3d显示层级关系，酷。&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="enter description here" src="http://static.wooyun.org//drops/20160126/20160126103602903151515.png"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;更酷的是，它可以直接修改界面元素的内容，不管在原手机端该元素是否可编辑，并且编辑后直接反应到手机端界面。&lt;/p&gt;
 &lt;p&gt;有了这种功能，要应付我们本漏洞验证的目标就太容易了，啪啪啪走到第三步，刷新reveal段界面，在如下图中直接编辑注册手机号内容，回车，再在手机端点击完成，ok。&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="enter description here" src="http://static.wooyun.org//drops/20160126/20160126103604527711613.png"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;h1&gt;0x03 总结&lt;/h1&gt;
 &lt;hr&gt;&lt;/hr&gt;
 &lt;p&gt;cycript和reveal其实都根植于cydia substrate这棵大树，不过风格迥异罢了。一个是玲珑剑，需要舞者精心驾驭，一个是大砍刀，直接快意杀伐。不过爽的是我可以左手玲珑剑，右手大砍刀，仰天长啸，快意恩仇，不亦乐乎！&lt;/p&gt;
 &lt;h1&gt;0x04 参考&lt;/h1&gt;
 &lt;hr&gt;&lt;/hr&gt;
 &lt;ul&gt;
  &lt;li&gt;
   &lt;p&gt;调试利器Reveal&lt;/p&gt;
   &lt;p&gt;    &lt;a href="http://42.96.192.22/?tag=%E8%B0%83%E8%AF%95%E5%88%A9%E5%99%A8reveal"&gt;http://42.96.192.22/?tag=%E8%B0%83%E8%AF%95%E5%88%A9%E5%99%A8reveal&lt;/a&gt;&lt;/p&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>移动安全</category>
      <guid isPermaLink="true">https://itindex.net/detail/55126-ios-%E5%AE%A2%E6%88%B7%E7%AB%AF-hack</guid>
      <pubDate>Wed, 27 Jan 2016 10:24:39 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>检测iOS的APP性能的一些方法</title>
      <link>https://itindex.net/detail/55396-ios-app-%E6%80%A7%E8%83%BD</link>
      <description>&lt;div&gt;
  &lt;p&gt;首先如果遇到应用卡顿或者因为内存占用过多时一般使用Instruments里的来进行检测。但对于复杂情况可能就需要用到子线程监控主线程的方式来了，下面我对这些方法做些介绍：&lt;/p&gt;
  &lt;h2&gt;Time Profiler  &lt;/h2&gt;
  &lt;p&gt;可以查看多个线程里那些方法费时过多的方法。先将右侧Hide System Libraries打上勾，这样能够过滤信息。然后在Call Tree上会默认按照费时的线程进行排序，单个线程中会也会按照对应的费时方法排序，选择方法后能够通过右侧Heaviest Stack Trace里双击查看到具体的费时操作代码，从而能够有针对性的优化，而不需要在一些本来就不会怎么影响性能的地方过度优化。&lt;/p&gt;
  &lt;h2&gt;Allocations  &lt;/h2&gt;
  &lt;p&gt;这里可以对每个动作的前后进行Generations，对比内存的增加，查看使内存增加的具体的方法和代码所在位置。具体操作是在右侧Generation Analysis里点击Mark Generation，这样会产生一个Generation，切换到其他页面或一段时间产生了另外一个事件时再点Mark Generation来产生一个新的Generation，这样反复，生成多个Generation，查看这几个Generation会看到Growth的大小，如果太大可以点进去查看相应占用较大的线程里右侧Heaviest Stack Trace里查看对应的代码块，然后进行相应的处理。&lt;/p&gt;
  &lt;h2&gt;Leak  &lt;/h2&gt;
  &lt;p&gt;可以在上面区域的Leaks部分看到对应的时间点产生的溢出，选择后在下面区域的Statistics&amp;gt;Allocation Summary能够看到泄漏的对象，同样可以通过Stack Trace查看到具体对应的代码区域。&lt;/p&gt;
  &lt;h2&gt;监控卡顿的方法  &lt;/h2&gt;
  &lt;p&gt;还有种方法是在程序里去监控性能问题，这样在上线后可以通过这个程序将用户的卡顿操作记录下来，定时发到自己的服务器上，这样能够更大范围的收集性能问题。众所周知，用户层面感知的卡顿都是来自处理所有UI的主线程上，包括在主线程上进行的大计算，大量的IO操作，或者比较重的绘制工作。如何监控主线程呢，首先需要知道的是主线程和其它线程一样都是靠NSRunLoop来驱动的。可以先看看CFRunLoopRun的大概的逻辑&lt;/p&gt;
  &lt;pre&gt;int32_t __CFRunLoopRun() {     __CFRunLoopDoObservers(KCFRunLoopEntry);     do     {         __CFRunLoopDoObservers(kCFRunLoopBeforeTimers);         __CFRunLoopDoObservers(kCFRunLoopBeforeSources); //这里开始到kCFRunLoopBeforeWaiting之间处理时间是感知卡顿的关键地方          __CFRunLoopDoBlocks();         __CFRunLoopDoSource0(); //处理UI事件          //GCD dispatch main queue         CheckIfExistMessagesInMainDispatchQueue();          //休眠前         __CFRunLoopDoObservers(kCFRunLoopBeforeWaiting);          //等待msg         mach_port_t wakeUpPort = SleepAndWaitForWakingUpPorts();          //等待中          //休眠后，唤醒         __CFRunLoopDoObservers(kCFRunLoopAfterWaiting);          //定时器唤醒         if (wakeUpPort == timerPort)             __CFRunLoopDoTimers();          //异步处理         else if (wakeUpPort == mainDispatchQueuePort)             __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__()          //UI，动画         else             __CFRunLoopDoSource1();          //确保同步         __CFRunLoopDoBlocks();      } while (!stop &amp;amp;&amp;amp; !timeout);      //退出RunLoop     __CFRunLoopDoObservers(CFRunLoopExit); }&lt;/pre&gt;
  &lt;p&gt;根据这个RunLoop我们能够通过CFRunLoopObserverRef来度量。用GCD里的dispatch_semaphore_t开启一个新线程，设置一个极限值和出现次数的值，然后获取主线程上在kCFRunLoopBeforeSources到kCFRunLoopBeforeWaiting再到kCFRunLoopAfterWaiting两个状态之间的超过了极限值和出现次数的场景，将堆栈dump下来，最后发到服务器做收集，通过堆栈能够找到对应出问题的那个方法。&lt;/p&gt;
  &lt;pre&gt;static void runLoopObserverCallBack(CFRunLoopObserverRef observer, CFRunLoopActivity activity, void *info) {     MyClass *object = (__bridge MyClass*)info;     object-&amp;gt;activity = activity; }  - (void)registerObserver {     CFRunLoopObserverContext context = {0,(__bridge void*)self,NULL,NULL};     CFRunLoopObserverRef observer = CFRunLoopObserverCreate(kCFAllocatorDefault,                                                             kCFRunLoopAllActivities,                                                             YES,                                                             0,                                                             &amp;amp;runLoopObserverCallBack,                                                             &amp;amp;context);     CFRunLoopAddObserver(CFRunLoopGetMain(), observer, kCFRunLoopCommonModes); }  static void runLoopObserverCallBack(CFRunLoopObserverRef observer, CFRunLoopActivity activity, void *info) {     MyClass *object = (__bridge MyClass*)info;      object-&amp;gt;activity = activity;      dispatch_semaphore_t semaphore = moniotr-&amp;gt;semaphore;     dispatch_semaphore_signal(semaphore); }  - (void)registerObserver {     CFRunLoopObserverContext context = {0,(__bridge void*)self,NULL,NULL};     CFRunLoopObserverRef observer = CFRunLoopObserverCreate(kCFAllocatorDefault,                                                             kCFRunLoopAllActivities,                                                             YES,                                                             0,                                                             &amp;amp;runLoopObserverCallBack,                                                             &amp;amp;context);     //检测主线程     CFRunLoopAddObserver(CFRunLoopGetMain(), observer, kCFRunLoopCommonModes);      semaphore = dispatch_semaphore_create(0);      //开始开启一个新线程来监控主线程两个状态消耗的时间     dispatch_async(dispatch_get_global_queue(0, 0), ^{         while (YES)         {             //设置30ms连续3次就报卡顿             long semaphoreWait = dispatch_semaphore_wait(semaphore, dispatch_time(DISPATCH_TIME_NOW, 30*NSEC_PER_MSEC));             if (semaphoreWait != 0)             {                 if (activity==kCFRunLoopBeforeSources || activity==kCFRunLoopAfterWaiting)                 {                     if (++timeoutCount &amp;lt; 3)                         continue;                     //dump堆栈，上报服务器的处理放在这                 }             }             timeoutCount = 0;         }     }); }&lt;/pre&gt;
  &lt;p&gt;有时候造成卡顿是因为数据异常，过多，或者过大造成的，亦或者是操作的异常出现的，这样的情况可能在平时日常开发测试中难以遇到，但是在真实的特别是用户受众广的情况下会有人出现，这样这种收集卡顿的方式还是有价值的。&lt;/p&gt;
  &lt;h2&gt;开发时需要注意如何避免一些性能问题  &lt;/h2&gt;
  &lt;h2&gt;NSDateFormatter  &lt;/h2&gt;
  &lt;p&gt; 通过Instruments的检测会发现创建NSDateFormatter或者设置NSDateFormatter的属性的耗时总是排在前面，如何处理这个问题呢，比较推荐的是添加属性或者创建静态变量，这样能够使得创建初始化这个次数降到最低。还有就是可以直接用C，或者这个NSData的Category来解决       &lt;a href="https://github.com/samsoffes/sstoolkit/blob/master/SSToolkit/NSData%2BSSToolkitAdditions.m" rel="nofollow,noindex" target="_blank"&gt;https://github.com/samsoffes/sstoolkit/blob/master/SSToolkit/NSData%2BSSToolkitAdditions.m&lt;/a&gt;   &lt;/p&gt;
  &lt;h2&gt;UIImage  &lt;/h2&gt;
  &lt;p&gt;这里要主要是会影响内存的开销，需要权衡下imagedNamed和imageWithContentsOfFile，了解两者特性后，在只需要显示一次的图片用后者，这样会减少内存的消耗，但是页面显示会增加Image IO的消耗，这个需要注意下。由于imageWithContentsOfFile不缓存，所以需要在每次页面显示前加载一次，这个IO的操作也是需要考虑权衡的一个点。&lt;/p&gt;
  &lt;h2&gt;页面加载  &lt;/h2&gt;
  &lt;p&gt;如果一个页面内容过多，view过多，这样将长页面中的需要滚动才能看到的那个部分视图内容通过开启新的线程同步的加载。&lt;/p&gt;
  &lt;h2&gt;优化首次加载时间  &lt;/h2&gt;
  &lt;p&gt;通过Time Profier可以查看到启动所占用的时间，如果太长可以通过Heaviest Stack Trace找到费时的方法进行改造。&lt;/p&gt;
  &lt;p&gt;&lt;/p&gt;&lt;/div&gt;
 &lt;p&gt;转载本站任何文章请注明：转载至神刀安全网，谢谢  &lt;a href="http://www.shellsec.com"&gt;神刀安全网&lt;/a&gt; »   &lt;a href="http://www.shellsec.com/news/5912.html"&gt;检测iOS的APP性能的一些方法&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>极客互联</category>
      <guid isPermaLink="true">https://itindex.net/detail/55396-ios-app-%E6%80%A7%E8%83%BD</guid>
      <pubDate>Sat, 26 Mar 2016 07:33:42 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 开发笔记-andriod/java/iOS三方AES通用加密 - jiangys</title>
      <link>https://itindex.net/detail/54260-ios-%E5%BC%80%E5%8F%91-%E7%AC%94%E8%AE%B0</link>
      <description>&lt;p&gt;AES在线加解密验证工具:   &lt;a href="http://www.seacha.com/tools/aes.html"&gt;http://www.seacha.com/tools/aes.html&lt;/a&gt;&lt;/p&gt; &lt;p&gt;AES加密有多种算法模式，下面提供两套模式的可用源码。&lt;/p&gt; &lt;p&gt;加密方式：&lt;/p&gt; &lt;ol&gt;  &lt;li&gt;先将文本AES加密&lt;/li&gt;  &lt;li&gt;返回Base64转码&lt;/li&gt;&lt;/ol&gt; &lt;p&gt;解密方式：&lt;/p&gt; &lt;ol&gt;  &lt;li&gt;将数据进行Base64解码&lt;/li&gt;  &lt;li&gt;进行AES解密&lt;/li&gt;&lt;/ol&gt; &lt;p&gt;  &lt;strong&gt;一、CBC（Cipher Block Chaining，加密块链）模式&lt;/strong&gt;&lt;/p&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;p&gt;java/adriod加密AESOperator类：&lt;/p&gt; &lt;div&gt;  &lt;div&gt;package com.bci.wx.base.util;   &lt;br /&gt;   &lt;br /&gt;import javax.crypto.Cipher;   &lt;br /&gt;import javax.crypto.spec.IvParameterSpec;   &lt;br /&gt;import javax.crypto.spec.SecretKeySpec;   &lt;br /&gt;   &lt;br /&gt;import sun.misc.BASE64Decoder;   &lt;br /&gt;import sun.misc.BASE64Encoder;   &lt;br /&gt;   &lt;br /&gt;   &lt;br /&gt;/**   &lt;br /&gt; * AES 是一种可逆加密算法，对用户的敏感信息加密处理 对原始数据进行AES加密后，在进行Base64编码转化；   &lt;br /&gt; */   &lt;br /&gt;public class AESOperator {   &lt;br /&gt;   &lt;br /&gt;    /*   &lt;br /&gt;     * 加密用的Key 可以用26个字母和数字组成 此处使用AES-128-CBC加密模式，key需要为16位。   &lt;br /&gt;     */   &lt;br /&gt;    private String sKey = &amp;quot;&amp;quot;;   &lt;br /&gt;    private String ivParameter = &amp;quot;&amp;quot;;   &lt;br /&gt;    private static AESOperator instance = null;   &lt;br /&gt;   &lt;br /&gt;    private AESOperator() {   &lt;br /&gt;   &lt;br /&gt;    }   &lt;br /&gt;   &lt;br /&gt;    public static AESOperator getInstance() {   &lt;br /&gt;        if (instance == null)   &lt;br /&gt;            instance = new AESOperator();   &lt;br /&gt;        return instance;   &lt;br /&gt;    }   &lt;br /&gt;       &lt;br /&gt;public static String Encrypt(String encData ,String secretKey,String vector) throws Exception {   &lt;br /&gt;           &lt;br /&gt;        if(secretKey == null) {   &lt;br /&gt;            return null;   &lt;br /&gt;        }   &lt;br /&gt;        if(secretKey.length() != 16) {   &lt;br /&gt;            return null;   &lt;br /&gt;        }   &lt;br /&gt;        Cipher cipher = Cipher.getInstance(&amp;quot;AES/CBC/PKCS5Padding&amp;quot;);   &lt;br /&gt;        byte[] raw = secretKey.getBytes();   &lt;br /&gt;        SecretKeySpec skeySpec = new SecretKeySpec(raw, &amp;quot;AES&amp;quot;);   &lt;br /&gt;        IvParameterSpec iv = new IvParameterSpec(vector.getBytes());// 使用CBC模式，需要一个向量iv，可增加加密算法的强度   &lt;br /&gt;        cipher.init(Cipher.ENCRYPT_MODE, skeySpec, iv);   &lt;br /&gt;        byte[] encrypted = cipher.doFinal(encData.getBytes(&amp;quot;utf-8&amp;quot;));   &lt;br /&gt;        return new BASE64Encoder().encode(encrypted);// 此处使用BASE64做转码。   &lt;br /&gt;    }   &lt;br /&gt;   &lt;br /&gt;   &lt;br /&gt;    // 加密   &lt;br /&gt;    public String encrypt(String sSrc) throws Exception {   &lt;br /&gt;        Cipher cipher = Cipher.getInstance(&amp;quot;AES/CBC/PKCS5Padding&amp;quot;);   &lt;br /&gt;        byte[] raw = sKey.getBytes();   &lt;br /&gt;        SecretKeySpec skeySpec = new SecretKeySpec(raw, &amp;quot;AES&amp;quot;);   &lt;br /&gt;        IvParameterSpec iv = new IvParameterSpec(ivParameter.getBytes());// 使用CBC模式，需要一个向量iv，可增加加密算法的强度   &lt;br /&gt;        cipher.init(Cipher.ENCRYPT_MODE, skeySpec, iv);   &lt;br /&gt;        byte[] encrypted = cipher.doFinal(sSrc.getBytes(&amp;quot;utf-8&amp;quot;));   &lt;br /&gt;        return new BASE64Encoder().encode(encrypted);// 此处使用BASE64做转码。   &lt;br /&gt;    }   &lt;br /&gt;   &lt;br /&gt;    // 解密   &lt;br /&gt;    public String decrypt(String sSrc) throws Exception {   &lt;br /&gt;        try {   &lt;br /&gt;            byte[] raw = sKey.getBytes(&amp;quot;ASCII&amp;quot;);   &lt;br /&gt;            SecretKeySpec skeySpec = new SecretKeySpec(raw, &amp;quot;AES&amp;quot;);   &lt;br /&gt;            Cipher cipher = Cipher.getInstance(&amp;quot;AES/CBC/PKCS5Padding&amp;quot;);   &lt;br /&gt;            IvParameterSpec iv = new IvParameterSpec(ivParameter.getBytes());   &lt;br /&gt;            cipher.init(Cipher.DECRYPT_MODE, skeySpec, iv);   &lt;br /&gt;            byte[] encrypted1 = new BASE64Decoder().decodeBuffer(sSrc);// 先用base64解密   &lt;br /&gt;            byte[] original = cipher.doFinal(encrypted1);   &lt;br /&gt;            String originalString = new String(original, &amp;quot;utf-8&amp;quot;);   &lt;br /&gt;            return originalString;   &lt;br /&gt;        } catch (Exception ex) {   &lt;br /&gt;            return null;   &lt;br /&gt;        }   &lt;br /&gt;    }   &lt;br /&gt;       &lt;br /&gt;    public String decrypt(String sSrc,String key,String ivs) throws Exception {   &lt;br /&gt;        try {   &lt;br /&gt;            byte[] raw = key.getBytes(&amp;quot;ASCII&amp;quot;);   &lt;br /&gt;            SecretKeySpec skeySpec = new SecretKeySpec(raw, &amp;quot;AES&amp;quot;);   &lt;br /&gt;            Cipher cipher = Cipher.getInstance(&amp;quot;AES/CBC/PKCS5Padding&amp;quot;);   &lt;br /&gt;            IvParameterSpec iv = new IvParameterSpec(ivs.getBytes());   &lt;br /&gt;            cipher.init(Cipher.DECRYPT_MODE, skeySpec, iv);   &lt;br /&gt;            byte[] encrypted1 = new BASE64Decoder().decodeBuffer(sSrc);// 先用base64解密   &lt;br /&gt;            byte[] original = cipher.doFinal(encrypted1);   &lt;br /&gt;            String originalString = new String(original, &amp;quot;utf-8&amp;quot;);   &lt;br /&gt;            return originalString;   &lt;br /&gt;        } catch (Exception ex) {   &lt;br /&gt;            return null;   &lt;br /&gt;        }   &lt;br /&gt;    }   &lt;br /&gt;       &lt;br /&gt;    public static String encodeBytes(byte[] bytes) {   &lt;br /&gt;        StringBuffer strBuf = new StringBuffer();   &lt;br /&gt;   &lt;br /&gt;        for (int i = 0; i &amp;lt; bytes.length; i++) {   &lt;br /&gt;            strBuf.append((char) (((bytes[i] &amp;gt;&amp;gt; 4) &amp;amp; 0xF) + ((int) &amp;apos;a&amp;apos;)));   &lt;br /&gt;            strBuf.append((char) (((bytes[i]) &amp;amp; 0xF) + ((int) &amp;apos;a&amp;apos;)));   &lt;br /&gt;        }   &lt;br /&gt;   &lt;br /&gt;        return strBuf.toString();   &lt;br /&gt;    }   &lt;br /&gt;   &lt;br /&gt;    public static void main(String[] args) throws Exception {   &lt;br /&gt;        // 需要加密的字串   &lt;br /&gt;        String cSrc = &amp;quot;[{\&amp;quot;request_no\&amp;quot;:\&amp;quot;1001\&amp;quot;,\&amp;quot;service_code\&amp;quot;:\&amp;quot;FS0001\&amp;quot;,\&amp;quot;contract_id\&amp;quot;:\&amp;quot;100002\&amp;quot;,\&amp;quot;order_id\&amp;quot;:\&amp;quot;0\&amp;quot;,\&amp;quot;phone_id\&amp;quot;:\&amp;quot;13913996922\&amp;quot;,\&amp;quot;plat_offer_id\&amp;quot;:\&amp;quot;100094\&amp;quot;,\&amp;quot;channel_id\&amp;quot;:\&amp;quot;1\&amp;quot;,\&amp;quot;activity_id\&amp;quot;:\&amp;quot;100045\&amp;quot;}]&amp;quot;;   &lt;br /&gt;           &lt;br /&gt;        // 加密   &lt;br /&gt;        long lStart = System.currentTimeMillis();   &lt;br /&gt;        String enString = AESOperator.getInstance().encrypt(cSrc);   &lt;br /&gt;        System.out.println(&amp;quot;加密后的字串是：&amp;quot; + enString);   &lt;br /&gt;   &lt;br /&gt;        long lUseTime = System.currentTimeMillis() - lStart;   &lt;br /&gt;        System.out.println(&amp;quot;加密耗时：&amp;quot; + lUseTime + &amp;quot;毫秒&amp;quot;);   &lt;br /&gt;        // 解密   &lt;br /&gt;        lStart = System.currentTimeMillis();   &lt;br /&gt;        String DeString = AESOperator.getInstance().decrypt(enString);   &lt;br /&gt;        System.out.println(&amp;quot;解密后的字串是：&amp;quot; + DeString);   &lt;br /&gt;        lUseTime = System.currentTimeMillis() - lStart;   &lt;br /&gt;        System.out.println(&amp;quot;解密耗时：&amp;quot; + lUseTime + &amp;quot;毫秒&amp;quot;);   &lt;br /&gt;    }   &lt;br /&gt;   &lt;br /&gt;}&lt;/div&gt;&lt;/div&gt; &lt;p&gt;iOS源码，直接下载：  &lt;a href="http://download.csdn.net/detail/jys1216/9059595" target="_blank"&gt;源码下载&lt;/a&gt;&lt;/p&gt; &lt;p&gt;  &lt;strong&gt;二、ECB（Electronic Code Book，电子密码本）模式&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;是一种基础的加密方式，密文被分割成分组长度相等的块（不足补齐），然后单独一个个加密，一个个输出组成密文。&lt;/p&gt; &lt;p&gt;只需要提供密码即可。&lt;/p&gt; &lt;p&gt;iOS,android,java已调通源码：  &lt;a href="http://download.csdn.net/detail/jys1216/9059595" target="_blank"&gt;源码下载&lt;/a&gt;&lt;/p&gt; &lt;p&gt; &lt;/p&gt; &lt;img alt="" height="1" src="http://counter.cnblogs.com/blog/rss/4767037" width="1"&gt;&lt;/img&gt; &lt;br /&gt; &lt;p&gt;本文链接：  &lt;a href="http://www.cnblogs.com/jys509/p/4767037.html" target="_blank"&gt;iOS 开发笔记-andriod/java/iOS三方AES通用加密&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 />
      <guid isPermaLink="true">https://itindex.net/detail/54260-ios-%E5%BC%80%E5%8F%91-%E7%AC%94%E8%AE%B0</guid>
      <pubDate>Fri, 28 Aug 2015 22:07:00 CST</pubDate>
    </item>
    <item>
      <title>揭晓iOS 10五大最隐蔽却最值得拥有的功能</title>
      <link>https://itindex.net/detail/55710-ios-%E6%9C%80%E5%80%BC-%E6%8B%A5%E6%9C%89</link>
      <description>&lt;p&gt;苹果即将到来的 iOS 10 升级了许多新功能，除了有给短信应用强行加特效之类的展现苹果脑洞够大的技能之外，其实 iOS 10 还有更多非常实用的新功能。本文将为你带来 iOS 10 最令人激动的新功能中期待值排名前五的功能：&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;第五名：iPad 将成为智能家居中控平台&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;在发布会上，苹果已经表示过 Apple TV 可以成为接下来家庭智能家居的中控平台。但是它并没有提及 iPad 升级 iOS 10 后同样可以成为智能家居的中控平台，操作所有智能应用。&lt;/p&gt;
 &lt;p&gt;作为控制平台，无论 Apple TV 还是 iPad 都可以帮助主人在外出时依然操控智能配件。除此外，Apple 似乎也发现了多数人会将 iPad 留在家中，更利于操控应用和硬件。&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" src="http://images2015.cnblogs.com/news/66372/201606/66372-20160622171551656-557923181.jpg"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;第四名：带 3D touch 的控制中心&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;苹果的控制中心在 iOS 10 升级了不少功能，从发布会上可以知道新控制中心可以操作的内容不止一页，其中包括漂亮的音乐操作区以及苹果新智能家居应用。除此外，控制中心还会加入新的 3D touch 功能。当你用力按下手电筒图标时，可以调节闪光灯的明亮度；当你用力按下拍照图标时，可以直接选择摄像还是自拍。遗憾的是现在的开发者预览版，控制中心上面一层设置按钮并没有任何 3D touch 反应，比如按压 wifi 图标就不会出现任何可选项。苹果，你发布会上可不是这么说的。&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" src="http://images2015.cnblogs.com/news/66372/201606/66372-20160622171551641-578010695.jpg"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;第三名：系统地图可以标记停车地址&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" src="http://images2015.cnblogs.com/news/66372/201606/66372-20160622171551625-375047974.png"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;在 iOS 10 中，使用者可以在系统地图应用里标记停车地点。如果你的导航终点不是家里的地址，系统地图会自动为你标记停车点。甚至回来取车时，地图也会帮你导航找到你的车是停在了哪儿。目测可以帮助不少车主节省每天的找车时间。&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;第二名：优化音乐存储&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;在今年的 WWDC 上 Apple 为 MacOS 优化了硬盘存储，让电脑系统可以自动将旧文件上传 iCloud 从而释放本地硬盘空间。但你不知道的是，iOS 10 也为音乐带来了自动优化存储空间的功能。如果你的 iPhone 配置较低，iOS 10 将从本地自动移除用户最近不常播放的歌曲。而且用户可以决定为音乐分配 4G 到 32G 不等的存储空间，一旦超过这一设置大小，系统将开始进行存储优化。&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;第一名：iOS 10 起将可以卸载系统自带应用&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" src="http://images2015.cnblogs.com/news/66372/201606/66372-20160622171551641-460771148.jpg"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;是不是很激动！但是，苹果高级副总裁 Craig Federighi 说，事实上这些应用并不是真的被卸载了，只是被删除了相关用户数据并从主屏隐藏了而已。但这已经是苹果迈出的第一步，说不定等到 iOS 10 正式版发布就可以修改默认应用设置了，相当令人期待！&lt;/p&gt;
 &lt;p&gt;本文来源：  &lt;a href="http://www.cnet.com/videos/top-5-hidden-ios-10-features/"&gt;cnet&lt;/a&gt; 译文创见首发由 TECH2IPO/创见 &lt;/p&gt; &lt;p&gt;  &lt;a href="http://news.cnblogs.com/n/547899/" target="_blank"&gt;本文链接&lt;/a&gt;&lt;/p&gt; &lt;img alt="" height="1" src="http://news.cnblogs.com/news/rssclick.aspx?id=547899" 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 />
      <guid isPermaLink="true">https://itindex.net/detail/55710-ios-%E6%9C%80%E5%80%BC-%E6%8B%A5%E6%9C%89</guid>
      <pubDate>Wed, 22 Jun 2016 17:16:04 CST</pubDate>
    </item>
    <item>
      <title>IOS开发60分钟入门</title>
      <link>https://itindex.net/detail/53589-ios-%E5%BC%80%E5%8F%91</link>
      <description>&lt;h1&gt;IOS开发60分钟入门&lt;/h1&gt;
 &lt;p&gt;本文面向已有其它语言（如Java，C，PHP，Javascript）编程经验的iOS开发初学者，初衷在于让我的同事一小时内了解如何开始开发iOS App，学习目标包括：&lt;/p&gt;

 &lt;ul&gt;
    &lt;li&gt;能使用Xcode IDE、模拟器&lt;/li&gt;
    &lt;li&gt;能修改、调试已有iOS App&lt;/li&gt;
    &lt;li&gt;能在已有应用内创建新模块&lt;/li&gt;
    &lt;li&gt;能创建新应用&lt;/li&gt;
    &lt;li&gt;能发布应用到App Store&lt;/li&gt;
&lt;/ul&gt;

 &lt;p&gt;本文不包含任何高级的iOS开发知识，已学会iOS开发的同学不要看，看完这篇文章学会了的同学也不用再看了。&lt;/p&gt;

 &lt;h2&gt;不仅是学习一门新语言&lt;/h2&gt;
 &lt;p&gt;有过脚本开发经验的人（如Javascript，PHP，Shell）在刚开始学习iOS开发的时候，会觉得iOS开发的学习曲线比脚本语言要高，是的，这种感觉是对的。因为学iOS开发，不仅是学习一门新语言，它包括：&lt;/p&gt;

 &lt;ul&gt;
    &lt;li&gt;一门语言：Objective-C&lt;/li&gt;
    &lt;li&gt;一个框架：Cocoa Touch&lt;/li&gt;
    &lt;li&gt;一个IDE：Xcode&lt;/li&gt;
&lt;/ul&gt;

 &lt;p&gt;初学脚本语言通常不会来绘制图形界面、与人交互，iOS如果不做图形界面，像脚本语言一样处理文本操作数据库，就没啥意思了。&lt;/p&gt;

 &lt;p&gt;所以，过去我写别的新手入门教程，通常都是写《XXX入门15分钟教程》，而iOS就要花数倍的时间来写了。&lt;/p&gt;

 &lt;h2&gt;环境准备&lt;/h2&gt;
 &lt;p&gt;做iOS开发一定要有苹果的软件环境：Mac OS操作系统、Objective-C编译器、设备模拟器等，开发工具倒不一定要用Xcode，只要是个源代码编辑工具就行（vim都行，只是没Xcode那么多功能）。&lt;/p&gt;

 &lt;h3&gt;Mac OS&lt;/h3&gt;
 &lt;p&gt;拥有Mac OS环境最简单的方法是找一台苹果电脑，包括iMac, MacBook Pro, MacBook Air, Mac Mini，但不包括苹果的移动设备（iPod Touch, iPhone, iPad, iPad Mini，它们运行的是iOS系统，不是Mac OS），苹果电脑在出厂的时候就会预装Mac OS，目前最新版本是Mac OS X 10.8，主流的版本还有Mac OS X 10.6、Max OS X 10.7。&lt;/p&gt;

 &lt;p&gt;如果囊中羞涩，可以借一台，或者上淘宝买个二手的。&lt;/p&gt;

 &lt;h4&gt;黑苹果&lt;/h4&gt;
 &lt;p&gt;提到iOS开发入门，似乎没办法不说黑苹果。所谓黑苹果，就是把Mac OS改造后安装在非苹果的硬件上，这是违反DMCA法案的，黑苹果的更多资料，  &lt;a href="http://en.wikipedia.org/wiki/OSx86"&gt;可以在维基上找到&lt;/a&gt;&lt;/p&gt;

 &lt;p&gt;苹果电脑价格高，国内软件开发者生存压力大，所以黑苹果在国内也有一些真实的存在，国外当然也有啦。&lt;/p&gt;

 &lt;p&gt;黑苹果基本可以胜任iOS开发，但有一些问题：&lt;/p&gt;

 &lt;ul&gt;
    &lt;li&gt;安装黑苹果是非法的&lt;/li&gt;
    &lt;li&gt;个人行为苹果公司一般不会追究，但会遭同行的鄙视&lt;/li&gt;
    &lt;li&gt;黑苹果超级难装，挑硬件。即使完全相同的型号，相同的批次，也有可能A机器装上了，B机器装不上&lt;/li&gt;
    &lt;li&gt;黑苹果系统多少都存在一些使用上的问题，像驱动Bug啦、待机恢复蓝屏啦、上网浏览有问题啦&lt;/li&gt;
    &lt;li&gt;黑苹果不能随意升级，可能升级一次safari就导致整个系统崩溃了&lt;/li&gt;
&lt;/ul&gt;

 &lt;p&gt;上面这些虽然不会直接影响Xcode写代码、模拟器测试，但写着写着想上网查个东西的时候，safari不能翻页，确实挺影响心情的。所以，钱包允许的前提下，还是搞个苹果电脑省心一些。&lt;/p&gt;

 &lt;h3&gt;Xcode 和 模拟器&lt;/h3&gt;
 &lt;p&gt;Xcode可以在苹果官网免费下载：  &lt;a href="https://developer.apple.com/Xcode/index.php"&gt;Xcode下载地址&lt;/a&gt;&lt;/p&gt;

 &lt;p&gt;安装Xcode时会自动安装iOS SDK和模拟器。&lt;/p&gt;

 &lt;p&gt;这么强大的IDE居然是免费的，还是挺让人开心的。&lt;/p&gt;

 &lt;h2&gt;从改一个现成的应用开始吧&lt;/h2&gt;
 &lt;p&gt;学一门新软件开发技能，能够第一时间做出一个可运行的产品非常重要，有助于给自己正面激励，我上大学的时候，有很多次想学一门新语言，往往花了半个月，还沉浸在数据类型和语法字典里，连第一个Hello World都没做出来。&lt;/p&gt;

 &lt;p&gt;这一次，就让我们从改一个现成的应用开始吧。&lt;/p&gt;

 &lt;h3&gt;下载&lt;/h3&gt;
 &lt;p&gt;首先，我们从苹果开发者中心下载一个示例代码回来。我选了  &lt;a href="https://developer.apple.com/library/ios/samplecode/ToolbarSearch/ToolbarSearch.zip"&gt;ToolBarSearch&lt;/a&gt;。&lt;/p&gt;

 &lt;p&gt;在本文档的末尾，还有一些其它的网址可以下载开源iOS产品或者代码段，但我试了一下，还是Apple Sample Code最容易成功。&lt;/p&gt;

 &lt;p&gt;下载回来的zip文件最好保存在”下载”或者”文稿”目录里，因为在Mac OS 10.8以前，有些目录（例如/var/private/tmp）在Finder中是看不到的，要通过Finder的“前往 &amp;gt; 前往文件夹”功能才能进入。&lt;/p&gt;

 &lt;h3&gt;打开&lt;/h3&gt;
 &lt;p&gt;有三种方式可以打开一个iOS Project
#### 双击project文件
打开Finder，进入刚刚下载解压的ToolBarSearch目录，找到ToolBarSearch.Xcodeproj文件，双击之，Xcode会自动启动，并打开这个项目&lt;/p&gt;

 &lt;h4&gt;在Xcode里选择Project打开&lt;/h4&gt;
 &lt;ul&gt;
    &lt;li&gt;
       &lt;p&gt;在Xcode没启动的情况下（如果Xcode已经启动了，就先按Command Q退出），启动Xcode，会弹出“Welcome to Xcode”的欢迎页，点击左下角的“Open Other”按钮，找到ToolBarSearch目录，双击ToolBarSearch目录，或者双击ToolBarSearch.Xcodeproj文件都可以&lt;/p&gt;
  &lt;/li&gt;
    &lt;li&gt;
       &lt;p&gt;如果Xcode处于打开状态，可以点击其菜单栏的File -&amp;gt; Open，或者File -&amp;gt; Open Recent，然后再选择要打开的项目&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

 &lt;h4&gt;通过命令行打开&lt;/h4&gt;
 &lt;p&gt;在Mac OS 10.8以前，有些目录（例如/var/private/tmp），在Finder和Xcode的File &amp;gt; Open对话框中，点击鼠标是找不到的，这时候就要通过命令行终端来打开了。&lt;/p&gt;

 &lt;p&gt;打开终端，执行：&lt;/p&gt;

 &lt;pre&gt;  &lt;code&gt;cd /ToolBarSearch的父目录/ToolBarSearch
open -a Xcode
&lt;/code&gt;&lt;/pre&gt;

 &lt;p&gt;open -a是mac os的系统命令，除了iOS项目，别的项目也可以这样打开。&lt;/p&gt;

 &lt;h3&gt;运行刚下载的应用&lt;/h3&gt;
 &lt;p&gt;点击Xcode左上角的Run按钮（或者同时按下Comman和R键），Xcode会编译源码并在模拟器中运行这个应用。&lt;/p&gt;

 &lt;p&gt;编译成功会在屏幕上淡淡地显示“Build Succeeded”。反之，失败就显示“Build Failed”且不启动模拟器。&lt;/p&gt;

 &lt;h3&gt;修改&lt;/h3&gt;
 &lt;p&gt;在模拟器上看到“Performed search using…”了吧，下面我们改掉它。&lt;/p&gt;

 &lt;ul&gt;
    &lt;li&gt;
       &lt;p&gt;在Xcode左上角的Run按钮下方，有一排小按钮，从左到右第三个是一个放大镜图标，鼠标移上去会显示“Show the Search Navigator”，点一下它，打开搜索界面，在它下方出现的Find输入框中输入“performed”&lt;/p&gt;
  &lt;/li&gt;
    &lt;li&gt;
       &lt;p&gt;搜索结果只有一条：ToolbarSearchViewController.m，点文件名下方被高亮的“Performed”字串，右侧代码编辑区会自动打开这个文件，并滚动屏幕，使包含“Performed”的这一行出现在编辑区的中间。&lt;/p&gt;
  &lt;/li&gt;
    &lt;li&gt;
       &lt;p&gt;修改双引号里的字串，随便改成啥，然后按“Command S”保存。&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

 &lt;p&gt;当然，这些操作，你也可以在终端下通过grep和vim完成。&lt;/p&gt;

 &lt;h3&gt;运行修改后的应用&lt;/h3&gt;
 &lt;p&gt;按Command R运行，看看，是不是看到效果啦？&lt;/p&gt;

 &lt;p&gt;是的，修改一个应用就这么简单。&lt;/p&gt;

 &lt;h2&gt;Objective-C&lt;/h2&gt;
 &lt;p&gt;Objective-C是苹果应用软件（包括苹果电脑上的Mac OS App和移动设备上的iOS App）的开发语言。它是一种面向对象的编程语言。&lt;/p&gt;

 &lt;p&gt;苹果公司还提供了一个软件，叫Interface Builder，简称IB，用于可视化的界面制作，就像用Dreamweaver做网页，或者像Visual Basic做桌面软件一样。后来IB就整合进了Xcode，成了Xcode的一部分。这篇文档不讲IB，只讲Objective-C，因为：&lt;/p&gt;

 &lt;ul&gt;
    &lt;li&gt;基本上，每一本讲iOS开发的书（纸质书、电子书），都有大量的截图一步一步教如何用IB开发iOS应用，而讲Objective-C开发应用的书却没有那么多。&lt;/li&gt;
    &lt;li&gt;IB可以用来直观方便地画界面、设置控件属性、建立代码与控件的联系，但后台的业务逻辑和数据处理仍然要靠Objective-C，可见，不管用不用IB，Objective-C都是绕不过去的。&lt;/li&gt;
&lt;/ul&gt;

 &lt;h3&gt;C的超集&lt;/h3&gt;
 &lt;p&gt;Objective-C扩展了ANSI C，是C的超集，也就是说：&lt;/p&gt;

 &lt;ul&gt;
    &lt;li&gt;任何C源程序，不经修改，即可通过Objective-C编译器成功编译&lt;/li&gt;
    &lt;li&gt;Objective-C源程序中可以直接使用任何C语言代码&lt;/li&gt;
&lt;/ul&gt;

 &lt;p&gt;除了面向对象有语法是SmallTalk风格的（下面会讲到），其它非面向对象的语法、数据类型，与C完全相同，所以本文就不再赘述。
来看一个经典的Hello World示例吧：&lt;/p&gt;

 &lt;pre&gt;  &lt;code&gt;#import &amp;lt;Foundation/Foundation.h&amp;gt;
int main(int argc, char *argv[]){
	@autoreleasepool{
		NSLog(@&amp;quot;Hello World!&amp;quot;);
	}
	return 0;
} 是不是仿佛穿越回了大一学习C语言的时代，看起来和C几乎没有区别，是吧？是的，因为还没用到它的面向对象特性，哈哈！
&lt;/code&gt;&lt;/pre&gt;

 &lt;h3&gt;SmallTalk的消息传递语法风格&lt;/h3&gt;
 &lt;p&gt;Objective-C的面向对象语法源自SmallTalk，消息传递（Message Passing）风格。在源码风格方面，这是它与C Family语言（包括C/C++、Java、PHP）差别最大的地方。&lt;/p&gt;

 &lt;p&gt;在Java、C++世界，我们调用一个对象的某方法，在Objective-C里，这称作给类型发送一个消息，这可不仅仅是文字游戏，他们的技术细节也是不同的。&lt;/p&gt;

 &lt;p&gt;在Java里，对象和方法关系非常严格，一个方法必须属于一个类/对象，否则编译是要报错的。而在Objective-C里，类型和消息的关系比较松散，消息处理到运行时（runtime）才会动态决定，给类型发送一个它无法处理的消息，也只会抛出一个异常，而不会挂掉。&lt;/p&gt;

 &lt;pre&gt;  &lt;code&gt;[obj undefinedMethod];
&lt;/code&gt;&lt;/pre&gt;

 &lt;p&gt;在代码里调用没定义的方法（这是Java世界的习惯说法啊，专业的叫法是，给obj对象传递它无法处理的消息），Xcode会警告，但编译能成功，运行的时候会出错。它会输出这样一个错误：&lt;/p&gt;

 &lt;pre&gt;  &lt;code&gt;Terminating app due to uncaught exception &amp;apos;NSInvalidArgumentException&amp;apos;, reason: &amp;apos;-[NSObject undefinedMethod]: unrecognized selector sent to instance 0x8871710&amp;apos;
&lt;/code&gt;&lt;/pre&gt;

 &lt;h3&gt;类似Java的OOP概念&lt;/h3&gt;
 &lt;p&gt;Objective-C中一些面向对象的概念，也可以在Java中找到类似的实现（只能说是类似，不是完全相同），我的读者基本都是Java和PHP程序员，我会在下文中尽量用Java的概念来类比。&lt;/p&gt;

 &lt;p&gt;GoogleCode上有人整理了Java和Objective-C的概念、数据类型对应表，  &lt;a href="http://code.google.com/p/j2objc/wiki/JavaConversions"&gt;参见这里&lt;/a&gt;&lt;/p&gt;

 &lt;h3&gt;字符串&lt;/h3&gt;
 &lt;p&gt;Objective-C里有字符串是由双引号包裹，并在引号前加一个@符号，例如：&lt;/p&gt;

 &lt;pre&gt;  &lt;code&gt;title = @&amp;quot;Hello&amp;quot;;
if(title == @&amp;quot;hello&amp;quot;) {}
&lt;/code&gt;&lt;/pre&gt;

 &lt;p&gt;PHP程序员要注意，在这里不能用单引号，即使只有一个字符也不能用。Objective-C与Java、C一样，双引号表示字符串。&lt;/p&gt;

 &lt;h3&gt;函数调用&lt;/h3&gt;
 &lt;p&gt;前文述及，不涉及面向对象时，它和C是完全一样的。以下是几个函数调用的示例：&lt;/p&gt;

 &lt;h4&gt;不带参数&lt;/h4&gt;

 &lt;pre&gt;  &lt;code&gt;startedBlock();
&lt;/code&gt;&lt;/pre&gt;

 &lt;h4&gt;带参数&lt;/h4&gt;

 &lt;pre&gt;  &lt;code&gt;NSLog(@&amp;quot;decrypted string: %@&amp;quot;, str);
CGRectMake(0,0,0,0);
&lt;/code&gt;&lt;/pre&gt;

 &lt;h3&gt;传递消息给类/实例方法&lt;/h3&gt;

 &lt;h4&gt;不带参数&lt;/h4&gt;

 &lt;pre&gt;  &lt;code&gt;[obj method];
&lt;/code&gt;&lt;/pre&gt;

 &lt;p&gt;对应的Java版本&lt;/p&gt;

 &lt;pre&gt;  &lt;code&gt;obj.method();
&lt;/code&gt;&lt;/pre&gt;

 &lt;h4&gt;带一个参数：&lt;/h4&gt;

 &lt;pre&gt;  &lt;code&gt;[counter increase:1];
&lt;/code&gt;&lt;/pre&gt;

 &lt;p&gt;对应的Java版本&lt;/p&gt;

 &lt;pre&gt;  &lt;code&gt;counter.increase(1);
&lt;/code&gt;&lt;/pre&gt;

 &lt;h4&gt;带多个参数&lt;/h4&gt;
 &lt;p&gt;对C Family程序员来说，这是最难接受的，最反人类的：&lt;/p&gt;

 &lt;pre&gt;  &lt;code&gt;- (void) setColorToRed: (float)red Green: (float)green Blue:(float)blue {...} //定义方法
[myObj setColorToRed: 1.0 Green: 0.8 Blue: 0.2]; //调用方法
&lt;/code&gt;&lt;/pre&gt;

 &lt;p&gt;对应的Java版&lt;/p&gt;

 &lt;pre&gt;  &lt;code&gt;public void setColorToRedGreenBlue(float red, float green, float blue) {...}
myObj.setColorToRedGreenBlue(1.0, 0.8, 0.2);
&lt;/code&gt;&lt;/pre&gt;

 &lt;h4&gt;消息嵌套&lt;/h4&gt;

 &lt;pre&gt;  &lt;code&gt;UINavigationBar *bar = [[[UINavigationBar alloc] init] autorelease];
&lt;/code&gt;&lt;/pre&gt;

 &lt;p&gt;对应的Java版&lt;/p&gt;

 &lt;pre&gt;  &lt;code&gt;UINavigationBar bar = UINavigationBar.alloc().init().autorelease();//Java没有指针，所以星号去掉了
&lt;/code&gt;&lt;/pre&gt;

 &lt;h3&gt;类&lt;/h3&gt;

 &lt;h4&gt;接口和实现&lt;/h4&gt;
 &lt;p&gt;Objective-C的类分为接口定义和实现两个部分。接口定义（Interface）放在头文件中，文件扩展名是.h，实现（implementation）放在实现文件中，文件扩展名是.m（也有.mm的扩展名，表示Objective-C和C++混编的代码）。&lt;/p&gt;

 &lt;p&gt;  &lt;code&gt;接口定义也可以写在.m文件中，但最好不要这么干&lt;/code&gt;&lt;/p&gt;

 &lt;p&gt;需要注意的是，与Objective-C的interface概念最接近的是C和C++里的头文件，它与implementation是成双成对出现的，作用是声明类的成员变量和方法。它与Java的interface概念完全不同：&lt;/p&gt;

 &lt;ul&gt;
    &lt;li&gt;Objective-C里，interface有且只有一个实现，Java的interface可以有0-N个实现&lt;/li&gt;
    &lt;li&gt;Objective-C里，interface可以定义成员属性，Java里不可以&lt;/li&gt;
&lt;/ul&gt;

 &lt;p&gt;在Objective-C里，和Java的Interface概念相似的是Protocol，下文会讲到。&lt;/p&gt;

 &lt;p&gt;请看示例：&lt;/p&gt;

 &lt;p&gt;Interface&lt;/p&gt;

 &lt;pre&gt;  &lt;code&gt;@interface MyClass {
	int memberVar1;
	id  memberVar2;
}

-(return_type) instance_method1; 
-(return_type) instance_method2: (int) p1;
-(return_type) instance_method3: (int) p1 andPar: (int) p2;
@end
&lt;/code&gt;&lt;/pre&gt;

 &lt;p&gt;Implementation&lt;/p&gt;

 &lt;pre&gt;  &lt;code&gt;@implementation MyClass {
	int memberVar3;
}
 
-(return_type) instance_method1 {
	....
}
-(return_type) instance_method2: (int) p1 {
	....
}
-(return_type) instance_method3: (int) p1 andPar: (int) p2 {
	....
}
@end
&lt;/code&gt;&lt;/pre&gt;

 &lt;p&gt;接口和实现以@interface、@implementation开头，都以@end结束。“@”符号在Objective-C中是个很神奇的符号。&lt;/p&gt;

 &lt;p&gt;冒号也是方法名的一部分，method和method:是两个不同的方法名，不是overload，第二个带参数。&lt;/p&gt;

 &lt;p&gt;上述代码对应的Java版：&lt;/p&gt;

 &lt;pre&gt;  &lt;code&gt;public class MyClass {
	protected int memberVar1;
	protected pointer memberVar2;
	private int memberVar3;
	
	public (return_type) instance_method1() {
		....
	}
	
	public (return_type) instance_method2(int p1) {
		....
	}
	
	public (return_type) instance_method3andPar(int p1, int p2) {
		....
	}
}
&lt;/code&gt;&lt;/pre&gt;

 &lt;h4&gt;私有方法和公开方法&lt;/h4&gt;
 &lt;p&gt;写在.h头文件里的方法都是公开的，Objective-C里没有私有方法的概念（没有你说个蛋啊，哈哈哈哈）。&lt;/p&gt;

 &lt;p&gt;官方并没有提到Objective-C怎么实现私有方法，我查阅了stackoverflow，统一的答案是，要实现私有方法的效果只能借助Category，不过，根据我的测试，即使采用了Category，也不能阻止外部的代码调用这个“私有方法”，只是Xcode不支持“私有方法”的自动完成，并会有警告提示，运行的时候，还是会成功的。各位看官知道有这么回事就可以了，这里不深讲。&lt;/p&gt;

 &lt;h4&gt;变量和属性&lt;/h4&gt;

 &lt;h4&gt;类方法和实例方法&lt;/h4&gt;
 &lt;p&gt;##### 类方法
类方法就是Java、PHP里的Static Method，不用实例化就能调。类方法有一个加号前缀。
示例：&lt;/p&gt;

 &lt;p&gt;类定义&lt;/p&gt;

 &lt;pre&gt;  &lt;code&gt;@interface MyClass
	+(void) sayHello;
@end

@implementation MyClass
 
+(void) sayHello {
	NSLog(@&amp;quot;Hello, World&amp;quot;);
}
@end
&lt;/code&gt;&lt;/pre&gt;

 &lt;p&gt;使用&lt;/p&gt;

 &lt;pre&gt;  &lt;code&gt;[MyClass sayHello];
&lt;/code&gt;&lt;/pre&gt;

 &lt;h5&gt;实例方法&lt;/h5&gt;
 &lt;p&gt;实例方法就是Java、PHP里的普通方法，必须实例化才能调。实例方法有一个减号前缀。
示例：&lt;/p&gt;

 &lt;p&gt;类定义&lt;/p&gt;

 &lt;pre&gt;  &lt;code&gt;@interface MyClass : NSObject
-(void) sayHello;
@end

@implementation MyClass
 
-(void) sayHello {
	NSLog(@&amp;quot;Hello, World&amp;quot;);
}
@end
&lt;/code&gt;&lt;/pre&gt;

 &lt;p&gt;使用&lt;/p&gt;

 &lt;pre&gt;  &lt;code&gt;mycls = [MyClass new];
[mycls sayHello];
&lt;/code&gt;&lt;/pre&gt;

 &lt;h4&gt;Selector&lt;/h4&gt;
 &lt;p&gt;selector就是一个方法指针，类似PHP里的动态方法名：&lt;/p&gt;

 &lt;pre&gt;  &lt;code&gt;&amp;lt;?php
class Hello {
	public function sayHello() {}
	
	public function test() {
		$fun_name = &amp;quot;sayHello&amp;quot;;
		$this-&amp;gt;$fun_name();
	}
}
&lt;/code&gt;&lt;/pre&gt;

 &lt;p&gt;在Objective-C里，selector主要用来做两类事情：&lt;/p&gt;

 &lt;h5&gt;绑定控件触发的动作&lt;/h5&gt;

 &lt;pre&gt;  &lt;code&gt;@implementation DemoViewController
- (void)downButtonPressed:(id)sender {//响应“按钮被按下事件”的方法
	UIButton *button = (UIButton*)sender;
	[button setSelected:YES];
}

- (void)drawAnButton {
	UIButton *btn = [UIButton buttonWithType:UIButtonTypeCustom]; 
	btn.frame = _frame; 
	btn.tag = 1;
	btn.backgroundColor = [UIColor clearColor];
	[btn addTarget: self
		 action: @selector(downButtonPressed:)
		 forControlEvents: UIControlEventTouchUpInside];//当这个按钮被按下时，触发downButtonPressed:方法
}
@end
&lt;/code&gt;&lt;/pre&gt;

 &lt;h5&gt;延时异步执行&lt;/h5&gt;

 &lt;pre&gt;  &lt;code&gt;@implementation ETHotDealViewController
- (void)viewDidLoad {
	
	//获取数据源
	HotDealDataSource *ds = [[HotDealDataSource alloc]init];
	[ds reload];
	_items = ds.items;
	
	[self performSelector: @selector(refreshTable)
		  withObject: self
		  afterDelay: 0.5];//延迟0.5秒调用refreshTable方法
}

-(void)refreshTable
{
	[self.tableView reloadData];
}
@end
&lt;/code&gt;&lt;/pre&gt;

 &lt;p&gt;这个例子中，获取数据源是通过ASIHTTP组件异步调用服务端HTTP接口，refreshTable要用到数据源返回回来的数据，如果不延迟0.5秒，就会立刻执行，执行的时候数据还在路上呢，页面就要变空白了。&lt;/p&gt;

 &lt;h3&gt;继承&lt;/h3&gt;
 &lt;p&gt;继承是写在Interface定义里面的。语法为：子类名在左，父类名在右，中间用冒号分隔。
示例：&lt;/p&gt;

 &lt;pre&gt;  &lt;code&gt;@interface MyClass : NSObject
@end
&lt;/code&gt;&lt;/pre&gt;

 &lt;p&gt;对应的Java版本是：&lt;/p&gt;

 &lt;pre&gt;  &lt;code&gt;public class MyClass extends NSObject {
}
&lt;/code&gt;&lt;/pre&gt;

 &lt;h3&gt;协议（Protocol）&lt;/h3&gt;
 &lt;p&gt;就是Java、PHP里的Interface。&lt;/p&gt;

 &lt;h4&gt;协议的定义&lt;/h4&gt;
 &lt;p&gt;协议的定义用@protocol关键字：&lt;/p&gt;

 &lt;pre&gt;  &lt;code&gt;@protocol Printable
	-(void)print:(NSString)str;
@end
&lt;/code&gt;&lt;/pre&gt;

 &lt;p&gt;对应的Java版本是：&lt;/p&gt;

 &lt;pre&gt;  &lt;code&gt;publilc interface Printable {
	public void print(String str);
}
&lt;/code&gt;&lt;/pre&gt;

 &lt;h5&gt;协议的继承&lt;/h5&gt;
 &lt;p&gt;协议本身也可以继承别的协议：&lt;/p&gt;

 &lt;pre&gt;  &lt;code&gt;@protocol Printable &amp;lt;NSObject&amp;gt;
	-(void)print:(NSString)str;
@end
&lt;/code&gt;&lt;/pre&gt;

 &lt;p&gt;对应的Java版本：&lt;/p&gt;

 &lt;pre&gt;  &lt;code&gt;public interface Printable extends NSObject {
	public void print (String str);
}
&lt;/code&gt;&lt;/pre&gt;

 &lt;h5&gt;可选方法&lt;/h5&gt;
 &lt;p&gt;协议可以包含可选方法，顾名思义，可选方法可以不被类实现：&lt;/p&gt;

 &lt;pre&gt;  &lt;code&gt;@protocol Printable
@optional
	-(void)print:(NSString)str;
@end
&lt;/code&gt;&lt;/pre&gt;

 &lt;p&gt;加了@optional关键字，一个类在implements这个协议时，便可以不实现print:方法。&lt;/p&gt;

 &lt;p&gt;Java里没有类似的实现，除了Collection里会有一些方法带有optional的注释，但Collection是个特例。&lt;/p&gt;

 &lt;h4&gt;协议的实现&lt;/h4&gt;
 &lt;p&gt;一个类实现某些协议是写在Interface定义里面的。语法为：协议名用尖括号包裹，多个协议名用逗号隔开，协议写在父类的右边（如果没有父类就直接写在子类右边）。&lt;/p&gt;

 &lt;p&gt;示例：&lt;/p&gt;

 &lt;pre&gt;  &lt;code&gt;@interface  class MyClass : NSObject &amp;lt;Printable, Drawable&amp;gt;
@end
&lt;/code&gt;&lt;/pre&gt;

 &lt;p&gt;Printable, Drawablw就是两个协议。&lt;/p&gt;

 &lt;p&gt;对应的Java版本是：&lt;/p&gt;

 &lt;pre&gt;  &lt;code&gt;public class MyClass extends NSObject implements Printable, Drawable {
}
&lt;/code&gt;&lt;/pre&gt;

 &lt;h3&gt;分类（Category）&lt;/h3&gt;
 &lt;p&gt;分类可以给一个已经存在的类增加方法，而不用去改它的源码。Java和PHP中都没有类似的特性。&lt;/p&gt;

 &lt;p&gt;比如说，NSObject是一个Objective-C内置的系统类，我们想给它增加toJson方法，就像这样：&lt;/p&gt;

 &lt;p&gt;头文件：NSObject+Json.h&lt;/p&gt;

 &lt;pre&gt;  &lt;code&gt;@interface NSObject (Json)
	-(NSString)toJson;
@end
&lt;/code&gt;&lt;/pre&gt;

 &lt;p&gt;实现文件：NSObject+Json.m&lt;/p&gt;

 &lt;pre&gt;  &lt;code&gt;@implementation NSObject (Json)
	-(NSString)toJson {
		//...
	}
@end
&lt;/code&gt;&lt;/pre&gt;

 &lt;p&gt;使用的时候，只要包含NSObject+Json.h，实例化NSObject类，就可以使用toJson方法了：&lt;/p&gt;

 &lt;pre&gt;  &lt;code&gt;import &amp;quot;NSObject+Json.h&amp;quot;
@implatementation XYZController
	-(void)test {
		NSObject *obj = [[NSObject alloc]init];
		NSString *str = [obj toJson];
	}
@end
&lt;/code&gt;&lt;/pre&gt;

 &lt;p&gt;当然了，NSObject本来的那些方法依然还是可以用的，什么都没变，只是多了个toJson方法。看起来是不是和继承没太多差别呢（除了使用的时候实例化的是NSObject，而不是JsonObject）？再看一个继承实现不了的例子：&lt;/p&gt;

 &lt;p&gt;头文件：NSObject+Json+XML.h&lt;/p&gt;

 &lt;pre&gt;  &lt;code&gt;@interface NSObject (Json)
	-(NSString)toJson;
@end

@interface NSObject (XML)
	-(NSString)toXML;
@end
&lt;/code&gt;&lt;/pre&gt;

 &lt;p&gt;实现文件：NSObject+Json+XML.m&lt;/p&gt;

 &lt;pre&gt;  &lt;code&gt;@implementation NSObject (Json)
	-(NSString)toJson {
		//...
	}
@end

@implementation NSObject (XML)
	-(NSString)toXML {
		//...
	}
@end
&lt;/code&gt;&lt;/pre&gt;

 &lt;p&gt;使用：&lt;/p&gt;

 &lt;pre&gt;  &lt;code&gt;import &amp;quot;NSObject+Json+XML.h&amp;quot;
@implatementation XYZController
	-(void)test {
		NSObject *obj = [[NSObject alloc]init];
		NSString *json = [obj toJson];
		NSString *xml = [obj toXML];
	}
@end
&lt;/code&gt;&lt;/pre&gt;

 &lt;h2&gt;Cocoa Touch&lt;/h2&gt;
 &lt;p&gt;Cocoa是Mac OS App的开发框架，Cocoa Touch是iOS开发用的框架，Cocoa Touch和Cocoa大部分是一样的，只是Cocoa Touch多了一些移动设备特有的东西，如：触摸屏、加速度传感器、GPS定位。Cocoa中多任务、多窗口的特性，在Cocoa Touch中也是没有的（或者跟Cocoa不完全一样的）。&lt;/p&gt;

 &lt;p&gt;就像学了Java语言还要再学一些Spring、Hibernate、Struts（或者其它类似的Java类库）才能开始做J2EE应用一样，学过Objective-C语言之后，也要再学习Cocoa Touch框架才能顺利地开发iOS应用。&lt;/p&gt;

 &lt;h3&gt;最常用设计模式之Delegate&lt;/h3&gt;
 &lt;p&gt;Cocoa Touch大量使用Delegate（委派）设计模式。&lt;/p&gt;

 &lt;h3&gt;常用控件：按钮、文本块、图片、输入框&lt;/h3&gt;
 &lt;p&gt;### TableView
### WebView
### 导航条&lt;/p&gt;

 &lt;h2&gt;Xcode&lt;/h2&gt;

 &lt;h3&gt;运行&lt;/h3&gt;
 &lt;p&gt;快捷键：Comman R&lt;/p&gt;

 &lt;h3&gt;搜索&lt;/h3&gt;
 &lt;p&gt;#### 搜索文本
#### 搜索文件
### 新建文件/目录
推荐在Finder中新建好的再添加进来
### 断点&lt;/p&gt;

 &lt;h2&gt;模拟器和真机测试&lt;/h2&gt;
 &lt;p&gt;### 模拟器测试
在Xcode中打开你的项目，在Xcode顶部工具栏的Stop按钮（Run按钮右边那个黑色正方形按钮）右边，有个下拉菜单，显示着 “ToolBarSearch &amp;gt; iPhone 5.0 Simulator” （即 你的应用英文名 &amp;gt; 当前选中的调试 ），点击这个下拉菜单，选中iPhone 5.0 Simulator（这里的5.0是指iOS版本，不是iPhone5的意思，如果你的项目是iPad应用，请选iPad 5.0 Simulator），再按“Run”按钮，Xcode就会自动把当前正在编辑开发的应用编译并安装到模拟器上。&lt;/p&gt;

 &lt;p&gt;在模拟器上操作时，如果执行过程中遇到了你在Xcode里设置的断点，模拟器会暂停运行，并将当前活动窗口切换回Xcode，供你调试。&lt;/p&gt;

 &lt;p&gt;在Xcode里增加或者取消了断点，不需要重新编译和安装应用即可生效。&lt;/p&gt;

 &lt;h4&gt;切换被模拟的设备&lt;/h4&gt;
 &lt;p&gt;模拟器的“硬件”菜单，可以选择想要模拟什么设备，有iPad、iPhone可选。&lt;/p&gt;

 &lt;ul&gt;
    &lt;li&gt;Retina：表示视网膜屏，iPhone(Retina)代表iPhone4，iPhone4S&lt;/li&gt;
    &lt;li&gt;4-Inch：表示4英寸的iPhone，iPhone(Retina 4-Inch)就是iPhone 5&lt;/li&gt;
&lt;/ul&gt;

 &lt;h4&gt;切换模拟的iOS版本&lt;/h4&gt;
 &lt;p&gt;在模拟器的“版本”菜单，可以选择要模拟什么版本的iOS。设备和版本是彼此独立的，iPhone 4S可以有5.0，5.1，6.1几种iOS版本，当然了，iPhone 5不可能有4.3的iOS版本。&lt;/p&gt;

 &lt;h4&gt;触摸屏&lt;/h4&gt;
 &lt;p&gt;用鼠标点击（不区分左右键）模拟器上的iPhone、iPad屏幕，就是在模拟用手指触摸iPhone，iPad的屏幕，可以实现一些触摸效果比如：&lt;/p&gt;

 &lt;ul&gt;
    &lt;li&gt;鼠标单击 等于 手指轻触&lt;/li&gt;
    &lt;li&gt;鼠标长按 等于 手指长按（例如你可以在模拟器上长按应用icon调出删除应用的确认框）&lt;/li&gt;
    &lt;li&gt;鼠标按住拖动 等于 手指拖动&lt;/li&gt;
    &lt;li&gt;双击和单击模拟器的Home键也等于双击和单击真机的Home键&lt;/li&gt;
&lt;/ul&gt;

 &lt;h5&gt;多指手势&lt;/h5&gt;
 &lt;p&gt;多指手势比较复杂，在白苹果笔记本上可以模拟简单的双指手势，白苹果的触控板天然支持多指触摸，但要定位到模拟器的区域再响应多指手势就需要借助一些额外的键啦：&lt;/p&gt;

 &lt;ul&gt;
    &lt;li&gt;按住Option键，再用两个手指去操作触摸板，可模拟双指拖动、旋转&lt;/li&gt;
    &lt;li&gt;按住Option+Shift，可模拟双指合拢&lt;/li&gt;
&lt;/ul&gt;

 &lt;h4&gt;输入法和键盘&lt;/h4&gt;
 &lt;p&gt;##### 输入中文
手机上特有的输入法（比如九宫格输入法）不能模拟。模拟器默认的iOS软键盘只有英文输入，在测试应用的时候，我们要用到中文，有两个办法：&lt;/p&gt;

 &lt;ul&gt;
    &lt;li&gt;使用剪贴板，在Mac OS里复制，再到模拟器运行的应用中的输入框上长按鼠标（模拟手指长按）3秒以上，等弹出“粘贴”的时候选择之，即可。&lt;/li&gt;
    &lt;li&gt;在模拟器里，按Home键，找到Setting那个App icon（不是Mac OS顶部的模拟器菜单啊，那里没有Setting的），打开被模拟iOS设备的设置，依次点击”General - Keyboard - International Keyboards - Add New Keyboard…”，加个中文键盘，以后就可以使用被模拟iOS设备软件盘输入中文了，跟在iPhone/iPad真机上一样。&lt;/li&gt;
&lt;/ul&gt;

 &lt;h4&gt;使用Mac电脑的键盘&lt;/h4&gt;
 &lt;p&gt;如果要输入大量文本，使用模拟器里的软键盘效率太低，这时候可以使用物理键盘，方法是：在Mac OS顶部的模拟器菜单栏，点击”硬件”菜单，勾选下拉菜单中的“模拟硬件键盘”。以后再用模拟器运行iOS应用时，点击iOS应用中的输入框，软键盘就不弹出来了，可直接使用Mac电脑的物理键盘输入。&lt;/p&gt;

 &lt;p&gt;  &lt;em&gt;注意&lt;/em&gt;：&lt;/p&gt;

 &lt;ul&gt;
    &lt;li&gt;模拟器中的iOS接管了物理键盘输入，所以，调用的是模拟器iOS的输入法，不是你的Mac电脑的输入法。打个比方，你的Mac OS装的是搜狗五笔，模拟器中iOS加了个拼音输入法（Add New Keyboard），那么，在iOS应用中输入中文会调用拼音输入法。&lt;/li&gt;
    &lt;li&gt;要切换模拟器中iOS的中英文输入法，也只能按iOS设备软键盘上的小地球图标，按Mac电脑上的Command+空格键是不行的。&lt;/li&gt;
&lt;/ul&gt;

 &lt;h4&gt;地理位置&lt;/h4&gt;
 &lt;p&gt;但Mac电脑没有定位用的硬件（GPS）和软件基础，因此模拟器不能自动获得当前的地理位置，不能用模拟器测试定位功能。（注意，虽然WiFi也可以独立定位——iPad WiFi版没有GPS也可以定位，但Mac电脑的WiFi不具备定位相关的软件）&lt;/p&gt;

 &lt;p&gt;要在模拟器里测试依赖地理位置的功能（如”我附近的xx”），可以手工指定一个经纬度给模拟器，方法：在Mac电脑顶部的模拟器菜单，点击”调试 - 位置 - 自定位置”，会弹出一个对话框，在弹出的框内填入经纬度即可。&lt;/p&gt;

 &lt;p&gt;如何获得经纬度？
上谷歌地图（ditu.google.cn），在地图上找到你想要的位置（比如你想知道杭州大厦的位置，就在通过搜索框找到杭州大厦），点击右键，选择“这儿是什么”，搜索框中就会出现这个位置的经纬度了，前面是纬度，后面是经度。咱们天朝的版图，都是北纬和东经。&lt;/p&gt;

 &lt;h4&gt;摄像头&lt;/h4&gt;
 &lt;p&gt;Mac电脑有摄像头，但Mac OS没有设计API给iOS模拟器调用，所以，不能用模拟器测试对焦闪光灯等功能。&lt;/p&gt;

 &lt;p&gt;要在模拟器上测试依赖照片的功能，可以在代码里做一个workaround，即当代码检测到摄像头不可用时，弹出一个照片选择器，让测试人员从相册里选择一幅照片，来进行后续的操作（如照片美化、人脸识别、条码扫描）。&lt;/p&gt;

 &lt;h3&gt;真机测试&lt;/h3&gt;
 &lt;p&gt;模拟器能验证你开发的iOS应用的大部分功能，但有些Mac设备上不具备的硬件，模拟器是不能模拟的。前文提到了一个绕过这些限制的办法，但获取当前位置、拍照、加速度感应这些是模拟不了的，一款应用发布给消费者之前，必须要在真实设备上验证过。&lt;/p&gt;

 &lt;p&gt;将未提交App Store审核通过的应用安装到iOS设备上测试，有三种办法：&lt;/p&gt;

 &lt;ul&gt;
    &lt;li&gt;加入苹果的Developer Program，成为付费会员，有了这个付费会员资格，就可以直接在Xcode中点击”Run”将刚刚改过的代码编译打包安装到开发测试用的iOS设备上。在iOS真机上操作被测试的程序能激活Xcode中设置的断点。&lt;/li&gt;
    &lt;li&gt;越狱iOS设备。将iPhone和iPad越狱后，可以通过SSH直接上传Xcode编译好的ipa包（一个iOS App本质上就是一个ipa包）。&lt;/li&gt;
    &lt;li&gt;越狱的iOS设备，配合破解过的Xcode，甚至可以实现和付费开发者计划一样的功能：在Xcode上点击”Run”，就自动编译安装到iOS设备上去运行了&lt;/li&gt;
    &lt;li&gt;企业部署方案。就像阿里巴巴的   &lt;a href="http://xyj.im"&gt;轩辕剑&lt;/a&gt;一样，用iPhone/iPad访问这个网址，点击里面的轩辕剑链接就可以安装轩辕剑这个应用了。&lt;/li&gt;
&lt;/ul&gt;

 &lt;p&gt;破解Xcode是违法行为（越狱是合法的），而且挑版本挑得厉害，不是所有Xcode版本都能破解，也不是所有Xcode的破解版都能和越狱的iOS配合好。越狱+SSH上传跟企业部署一样效率低（部署效率低，无法激活Xcode中的断点），只能用于QA验收，不适合开发自测。综上所述，最适合开发实时测试的就是第一个正规途径了。下面重点讲这个：&lt;/p&gt;

 &lt;h4&gt;拥有一个开发者账号&lt;/h4&gt;
 &lt;p&gt;苹果的Developer Program分为个人开发者和公司开发者，分别是每年99美元和每年299美元，分别可以注册100台和500台苹果测试设备。这个台数限制在一个付费年度内不会清空，比如说，2013年4月1日付费成功的，付费会员资格在2014年3月31日之前有效，这期间，注册一台就少一个名额，哪怕这个设备注册进来用了之后一分钟马上又删掉了，减少的这个名额也不会回来。&lt;/p&gt;

 &lt;p&gt;在交钱之前，最好问一下，周围的同事，有没有已经交了钱的。如果有，你只需要注册一个免费的Apple ID（就是你在App Store安装软件用的Apple ID），请他发个邀请邮件给你，把你的Apple ID加入他的团队就可以了，苹果会认为你们两个人是一个团队的，你们分别用自己的账号，共享100台设备的限额，这是合法的。&lt;/p&gt;

 &lt;h4&gt;安装证书和私钥&lt;/h4&gt;
 &lt;p&gt;##### 证书
不想看下面各种点击各种页面跳转的直接用浏览器访问  &lt;a href="https://developer.apple.com/ios/manage/certificates/team/index.action"&gt;证书管理&lt;/a&gt;，你要登录你就用Apple ID登录（前提是交过钱，或者找交了钱的人把你加入团队了）。
&amp;gt;
不嫌烦，或者想知道下次没我这个文档的时候怎么进证书管理吗？按这个步骤操作：&lt;/p&gt;

 &lt;ul&gt;
    &lt;li&gt;进入    &lt;a href="https://developer.apple.com/"&gt;苹果开发者中心&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;点击iOS Dev Center&lt;/li&gt;
    &lt;li&gt;点蓝色“Login”按钮，用你的Apple ID登录，登录成功会跳到    &lt;a href="https://developer.apple.com/devcenter/ios/index.action"&gt;开发者首页&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;点击右上角的   &lt;a href="https://developer.apple.com/ios/manage/overview/index.action"&gt;iOS Provisioning Portal&lt;/a&gt;（别找了，直接Command F搜索多好）&lt;/li&gt;
    &lt;li&gt;点左侧菜单栏里的   &lt;a href="https://developer.apple.com/ios/manage/certificates/team/index.action"&gt;Certificates&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

 &lt;p&gt;页面上有一个“Your Certificate”区域，下方有个Download圆角按钮，这是你的个人证书，下载下来。再下面一行，有一句“If you do not have the WWDR intermediate certificate installed,   &lt;a href="https://developer.apple.com/certificationauthority/AppleWWDRCA.cer"&gt;click here to download now&lt;/a&gt;”，这个是苹果的公共证书，也下下来。&lt;/p&gt;

 &lt;p&gt;双击下载回来的证书，装证书时，会提示你输入密码，这是【钥匙串访问工具】在问你要你的Mac OS账号开机密码（相当于linux里面的sudo），不是Apple ID的密码，不要搞错了。&lt;/p&gt;

 &lt;h5&gt;安装私钥&lt;/h5&gt;
 &lt;p&gt;如果你是和其它同事公用的账号，让他给你一个私钥即可，就是一个扩展名为p12的文件，双击之，钥匙串访问会自动出来，需要你输入一个密码，这个密码问给你p12文件的人要，不是你的Mac OS系统开机密码，也不是你的Apple ID密码。&lt;/p&gt;

 &lt;h4&gt;将设备注册到Provisioning Portal&lt;/h4&gt;

 &lt;ul&gt;
    &lt;li&gt;打开Xcode，从Xcode的Window菜单中找到Organizer，打开之（Shift Command 2）。&lt;/li&gt;
    &lt;li&gt;把iOS设备连上电脑，Organizer会自动识别出你的设备，并显示在左侧边栏。&lt;/li&gt;
    &lt;li&gt;在Organizer左侧边栏找到你的设备，右键，点击“Add Device to Provisioning Portal”，然后等Organizer提示你操作成功即可。（选中设备后，右边设备详情区域会显示一个按钮“Use for Development”，点它也可以）。&lt;/li&gt;
&lt;/ul&gt;

 &lt;h4&gt;到iOS真机上运行测试版程序&lt;/h4&gt;
 &lt;p&gt;回到Xcode主界面，在Stop按钮（Run按钮右边那个黑色正方形按钮）右边，有个下拉菜单，显示着 “ToolBarSearch &amp;gt; iPhone 5.0 Simulator” （即 你的应用英文名 &amp;gt; 当前选中的调试 ），点击这个下拉菜单，选中你的真机设备名，再按“Run”按钮，Xcode就会自动把当前正在编辑开发的应用编译并安装到真机上测试啦！&lt;/p&gt;

 &lt;h4&gt;发布到App Store&lt;/h4&gt;

 &lt;h4&gt;打IPA包&lt;/h4&gt;
 &lt;p&gt;IPA包本质上是一个ZIP压缩包，只不过它有着特殊的目录结构，扩展名是ipa，制作方法如下：&lt;/p&gt;

 &lt;ul&gt;
    &lt;li&gt;在Xcode中Build项目，快捷键Command B&lt;/li&gt;
    &lt;li&gt;在左侧项目导航器中，展开Products文件夹，找到你要打包的应用，你的应用名.app，右键，选择show in finder&lt;/li&gt;
    &lt;li&gt;到Finder中Copy这个.app目录（选中，按Command C），复制到一个你新建的名为Payload（区分大小写）的文件夹中&lt;/li&gt;
    &lt;li&gt;找到你的应用Logo，即一个512 * 512像素的PNG文件，copy到与Payload一起（与Payload并列，不要放进Payload了），并重命名为iTunesArtwork（区分大小写，没有扩展名）&lt;/li&gt;
    &lt;li&gt;将Payload目录、ItunesArtwork文件打成一个zip包，并更改扩展名为ipa&lt;/li&gt;
    &lt;li&gt;双击这个ipa文件，会用iTunes打开，如果打开成功，且在iTunes里有应用Logo显示，就成功了                &lt;/li&gt;
&lt;/ul&gt;

 &lt;h4&gt;批量自动打包&lt;/h4&gt;
 &lt;p&gt;除App Store外，还有许多其它的iOS应用市场（如91助手，同步推等等），如果一个应用需要发布到很多个应用市场，且他们的代码略有不同（比如说，统计代码不同），按上述方法手工修改源码再打包，再还原，比较容易出错。好消息是，Xcode是有命令行的，我们可以写一个shell脚本，先用se自动修改源码，再调用Xcode的命令行来编译以得到your——app.app目录，最后调用zip、mv等命令把上一个章节讲的ipa打包动作自动执行。&lt;/p&gt;

 &lt;h2&gt;阅读应用代码&lt;/h2&gt;

 &lt;h2&gt;从头新建一个应用：Hello World&lt;/h2&gt;

 &lt;h2&gt;其它&lt;/h2&gt;
 &lt;p&gt;### 代码里的控件尺寸
iOS App里的控件尺寸和字体大小都是指Point，Retina设备（iPhone 4，4S，5；the new Pad）和非Retina设备（iPhone 3GS，iPad，iPad 2）的Point数是一样的，尽管iPhone 4的分辨率是3GS的2倍。比如说，10point在Retina设备里是20 pixel，在非Retina设备（iPhone 3G）上则是10 pixel。&lt;/p&gt;

 &lt;p&gt;项目成员间交流时，应使用Point，不要使用pixel。&lt;/p&gt;

 &lt;h3&gt;SVN操作含有@符号的文件&lt;/h3&gt;
 &lt;p&gt;iOS应用中经常出现xxxx@2x.png这样的文件名,它们是给retina设备用的高分辨率大图，用svn命令行操作它们的时候会被@符号干扰,解决方案是在svn命令末尾加上一个@符号,如:&lt;/p&gt;

 &lt;pre&gt;  &lt;code&gt;svn del icon@2x.png@
svn info Default@2x.png@
&lt;/code&gt;&lt;/pre&gt;

 &lt;p&gt;如果一次移动了几十个png文件再svn commit的,可以用shell批处理:&lt;/p&gt;

 &lt;pre&gt;  &lt;code&gt;svn status | awk &amp;apos;($1==&amp;quot;!&amp;quot;){print $2}&amp;apos; | grep -v @ | xargs svn del 上面这个命令是将文件名不包含@符号的,且已经不在硬盘上的文件从svn version controll中删掉

for file in `svn status | awk &amp;apos;($1==&amp;quot;!&amp;quot;){print $2}&amp;apos; `; do svn del $file&amp;quot;@&amp;quot;; done     
&lt;/code&gt;&lt;/pre&gt;

 &lt;p&gt;上面这个命令是将文件名包含@符号的,且已经不在硬盘上的文件从svn version controll中删掉&lt;/p&gt;

 &lt;p&gt;svn add同上, 如法炮制即可.&lt;/p&gt;

 &lt;h3&gt;Xcode中的代码结构与操作系统上的文件系统并不一致&lt;/h3&gt;
 &lt;p&gt;推荐在Finder里建好目录再到Xcode的Project Navigator中点击“Add Files to”添加到项目中&lt;/p&gt;

 &lt;h3&gt;iPhone 5适配&lt;/h3&gt;
 &lt;p&gt;iPhone 5与之前的iPhone不一样，采用了4寸Retina屏，所以它的Point数变成了320 * 568 points&lt;/p&gt;

 &lt;h3&gt;开源代码&lt;/h3&gt;
 &lt;ul&gt;
    &lt;li&gt;   &lt;a href="https://developer.apple.com/library/ios/navigation/#section=Resource%20Types&amp;topic=Sample%20Code"&gt;Apple官方的Sample Code&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;   &lt;a href="http://en.wikipedia.org/wiki/List_of_free_and_open_source_iOS_applications"&gt;维基百科上的开源iOS App&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;   &lt;a href="http://www.iosopensource.com/"&gt;iOS Opensource&lt;/a&gt; –Domain Parking了，以前可以下载Twitter和Wordpress客户端的&lt;/li&gt;
    &lt;li&gt;   &lt;a href="http://code4app.com/"&gt;code 4 app&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;   &lt;a href="http://ui4app.com/"&gt;UI 4 app&lt;/a&gt;， code4app的姐妹站&lt;/li&gt;
&lt;/ul&gt;

 &lt;h3&gt;Objective-C教程&lt;/h3&gt;
 &lt;ul&gt;
    &lt;li&gt;   &lt;a href="http://developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/ProgrammingWithObjectiveC/Introduction/Introduction.html"&gt;Apple官方教程&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;   &lt;a href="http://cocoadevcentral.com/d/learn_objectivec/"&gt;Cocoa Dev Center&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;   &lt;a href="http://zh.wikipedia.org/wiki/Objective-C"&gt;维基上的Objective-C语言简介&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 />
      <guid isPermaLink="true">https://itindex.net/detail/53589-ios-%E5%BC%80%E5%8F%91</guid>
      <pubDate>Thu, 04 Jun 2015 08:00:00 CST</pubDate>
    </item>
    <item>
      <title>ios消息推送 - Gen_0</title>
      <link>https://itindex.net/detail/53562-ios-%E6%B6%88%E6%81%AF-%E6%8E%A8%E9%80%81</link>
      <description>&lt;p&gt;本文章只提供学习！！&lt;/p&gt; &lt;p&gt;      iOS的消息推送（推送通知）有两种，一种是本地推送通知，另一种是远程推送通知。所谓本地推送通知就是使用代码推送消到用户设备中提醒用户一些信息，推送形式请查看手机设置。远程推送通知是通过服务器发送消息到用户设备中，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;// 1.创建本地推送通知&lt;/p&gt; &lt;p&gt;    UILocalNotification *localNotification = [[UILocalNotification alloc] init];&lt;/p&gt; &lt;p&gt;    // 2.设置一些属性&lt;/p&gt; &lt;p&gt;    // 通知发出的时间(5秒后)&lt;/p&gt; &lt;p&gt;    localNotification.fireDate = [NSDate dateWithTimeIntervalSinceNow:5];&lt;/p&gt; &lt;p&gt;    // 设置时区（跟随手机的时区）&lt;/p&gt; &lt;p&gt;    localNotification.timeZone = [NSTimeZone defaultTimeZone];&lt;/p&gt; &lt;p&gt;    // 音乐文件名&lt;/p&gt; &lt;p&gt;    localNotification.soundName = @&amp;quot;xxxxx.wav&amp;quot;;&lt;/p&gt; &lt;p&gt;    // 通知的内容&lt;/p&gt; &lt;p&gt;    localNotification.alertBody = @&amp;quot;内容&amp;quot;;&lt;/p&gt; &lt;p&gt;    // 锁屏界面显示的标题 如下面的写法将显示：滑动来查看内容   格式：&amp;quot;滑动来&amp;quot; + 标题&lt;/p&gt; &lt;p&gt;    localNotification.alertAction = @&amp;quot;查看内容&amp;quot;    &lt;/p&gt; &lt;p&gt;    // 设置app图标数字&lt;/p&gt; &lt;p&gt;    localNotification.applicationIconBadgeNumber = 10;&lt;/p&gt; &lt;p&gt;    // 设置通知的其他信息&lt;/p&gt; &lt;p&gt;    localNotification.userInfo = @{&lt;/p&gt; &lt;p&gt;                    @&amp;quot;title&amp;quot; : @&amp;quot;好消息&amp;quot;,&lt;/p&gt; &lt;p&gt;                    };//可随意添加&lt;/p&gt; &lt;p&gt;    // 设置启动图片&lt;/p&gt; &lt;p&gt;    localNotification.alertLaunchImage = @&amp;quot;Default.png&amp;quot;;&lt;/p&gt; &lt;p&gt;    // 设置重复发出通知的时间间隔&lt;/p&gt; &lt;p&gt;//    localNotification.repeatInterval = NSCalendarUnitMinute;&lt;/p&gt; &lt;p&gt;    // 3.发通知&lt;/p&gt; &lt;p&gt;    [[UIApplication sharedApplication] scheduleLocalNotification:localNotification];&lt;/p&gt; &lt;p&gt;//－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－&lt;/p&gt; &lt;p&gt;点击发送通知后把程序退出或者挂到后台，等待5秒就会有通知，如果觉得5太长或太短请根据个人需要该上面到代码。&lt;/p&gt; &lt;p&gt;当用户点击通知进入app的时候或者程序运行在前台的时候会在appdelegate中调用下面这个方法&lt;/p&gt; &lt;p&gt;- (void)application:(UIApplication *)application didReceiveLocalNotification:(UILocalNotification *)notification&lt;/p&gt; &lt;p&gt;如果想当app在前台的时候不做任何事情可以在上面的方法里面最前面加上这句代码：&lt;/p&gt; &lt;p&gt;// 程序正处在前台运行，直接返回&lt;/p&gt; &lt;p&gt;    if (application.applicationState == UIApplicationStateActive) return;&lt;/p&gt; &lt;p&gt;也可以在&lt;/p&gt; &lt;p&gt;- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions方法中判断是否是点击通知进入app，如下：&lt;/p&gt; &lt;p&gt;UILocalNotification *ln = launchOptions[UIApplicationLaunchOptionsLocalNotificationKey];&lt;/p&gt; &lt;p&gt;    if (ln) {&lt;/p&gt; &lt;p&gt;       //点击通知进入app&lt;/p&gt; &lt;p&gt;    } else {&lt;/p&gt; &lt;p&gt;        //点击app图标进入app&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;1.首先在钥匙串中配置电脑签名文件&lt;/p&gt; &lt;p&gt;  &lt;img alt="" src="http://images0.cnblogs.com/blog2015/744056/201505/312211216575628.png"&gt;&lt;/img&gt;&lt;/p&gt; &lt;p&gt;  &lt;img alt="" src="http://images0.cnblogs.com/blog2015/744056/201505/312211410942201.png"&gt;&lt;/img&gt;&lt;/p&gt; &lt;p&gt;邮件地址一般写公司的就好，选择存储到磁盘，继续。&lt;/p&gt; &lt;p&gt;  &lt;img alt="" src="http://images0.cnblogs.com/blog2015/744056/201505/312213496737582.png"&gt;&lt;/img&gt;&lt;/p&gt; &lt;p&gt;我就在桌面新建了个叫push到文件夹，点击存储。&lt;/p&gt; &lt;p&gt;2.需要一个AppIDs  &lt;/p&gt; &lt;p&gt;(如果在测试阶段已经有了就不需要了，到时候直接用那个就可以,注意：AppIDs一定要是完整的，不能是xxx.xxx.*  做消息推送一定要是完整的ID)，没有就弄一个，过程跟弄测试这书一样。反正做远程消息推送一定要真机测试，在做真机测试的时候已经有AppIDs了，这个过程就不贴出来了，不会的同学可以先去查看怎么配置真机测试证书。&lt;/p&gt; &lt;p&gt;3.配置一个开发证书&lt;/p&gt; &lt;p&gt;  &lt;img alt="" src="http://images0.cnblogs.com/blog2015/744056/201505/312234262359258.png"&gt;&lt;/img&gt;&lt;/p&gt; &lt;p&gt;点击加号&lt;/p&gt; &lt;p&gt;  &lt;img alt="" src="http://images0.cnblogs.com/blog2015/744056/201505/312236200633600.png"&gt;&lt;/img&gt;&lt;/p&gt; &lt;p&gt;选择APNs 然后continue&lt;/p&gt; &lt;p&gt;  &lt;img alt="" src="http://images0.cnblogs.com/blog2015/744056/201505/312239347983860.png"&gt;&lt;/img&gt;&lt;/p&gt; &lt;p&gt;选择自己应用的AppID   之后continue 继续continue&lt;/p&gt; &lt;p&gt;  &lt;img alt="" src="http://images0.cnblogs.com/blog2015/744056/201505/312244125325708.png"&gt;&lt;/img&gt;&lt;/p&gt; &lt;p&gt;选择第一步的时候保存证桌面push文件夹中的电脑签名文件&lt;/p&gt; &lt;p&gt;  &lt;img alt="" src="http://images0.cnblogs.com/blog2015/744056/201505/312309024389216.png"&gt;&lt;/img&gt;&lt;/p&gt; &lt;p&gt;最好我开发证书也保存到push文件夹中，方便管理。&lt;/p&gt; &lt;p&gt;4.接下来配置生产证书&lt;/p&gt; &lt;p&gt;  &lt;img alt="" src="http://images0.cnblogs.com/blog2015/744056/201505/312315474851961.png"&gt;&lt;/img&gt;&lt;/p&gt; &lt;p&gt;点击production  点击加号&lt;/p&gt; &lt;p&gt;  &lt;img alt="" src="http://images0.cnblogs.com/blog2015/744056/201505/312316416261003.png"&gt;&lt;/img&gt;&lt;/p&gt; &lt;p&gt;选择production中的APNs  接下来所有的步骤跟配置开发证书一模一样，就不一一贴过程来。&lt;/p&gt; &lt;p&gt;5.这步骤才是最重要的。&lt;/p&gt; &lt;p&gt;  &lt;img alt="" src="http://images0.cnblogs.com/blog2015/744056/201505/312329286414829.png"&gt;&lt;/img&gt;&lt;/p&gt; &lt;p&gt;点击加号&lt;/p&gt; &lt;p&gt;  &lt;img alt="" src="http://images0.cnblogs.com/blog2015/744056/201505/312335291265126.png"&gt;&lt;/img&gt;&lt;/p&gt; &lt;p&gt;点击continue&lt;/p&gt; &lt;p&gt;  &lt;img alt="" src="http://images0.cnblogs.com/blog2015/744056/201505/312336254699339.png"&gt;&lt;/img&gt;&lt;/p&gt; &lt;p&gt;选择你应用的APP ID&lt;/p&gt; &lt;p&gt;  &lt;img alt="" src="http://images0.cnblogs.com/blog2015/744056/201505/312337262827083.png"&gt;&lt;/img&gt;&lt;/p&gt; &lt;p&gt;选择配置测试的时候生成的这书（本文没说明怎么配置测试证书，不知道请先查看如何配置测试证书）&lt;/p&gt; &lt;p&gt;  &lt;img alt="" src="http://images0.cnblogs.com/blog2015/744056/201505/312340047192588.png"&gt;&lt;/img&gt;&lt;/p&gt; &lt;p&gt;选择测试设备，全部选择都没关系&lt;/p&gt; &lt;p&gt;  &lt;img alt="" src="http://images0.cnblogs.com/blog2015/744056/201505/312346472044117.png"&gt;&lt;/img&gt;&lt;/p&gt; &lt;p&gt;按意起个名字就好了点击generate  下载到push文件夹中。&lt;/p&gt; &lt;p&gt;6.安装所有到证书，可以直接拖到我到证书中&lt;/p&gt; &lt;p&gt;  &lt;img alt="" src="http://images0.cnblogs.com/blog2015/744056/201505/312348176731455.png"&gt;&lt;/img&gt;&lt;/p&gt; &lt;p&gt;  &lt;img alt="" src="http://images0.cnblogs.com/blog2015/744056/201505/312352380632805.png"&gt;&lt;/img&gt;&lt;/p&gt; &lt;p&gt;  &lt;img alt="" src="http://images0.cnblogs.com/blog2015/744056/201505/312358503608410.png"&gt;&lt;/img&gt;&lt;/p&gt; &lt;p&gt;导出开发证书&lt;/p&gt; &lt;p&gt;  &lt;img alt="" src="http://images0.cnblogs.com/blog2015/744056/201505/312359255483195.png"&gt;&lt;/img&gt;&lt;/p&gt; &lt;p&gt;起个名developmentPush，文件格式选择.p12&lt;/p&gt; &lt;p&gt;  &lt;img alt="" src="http://images0.cnblogs.com/blog2015/744056/201506/010001042669453.png"&gt;&lt;/img&gt;&lt;/p&gt; &lt;p&gt;我这里输入abcd1234，你们随意记住就行，&lt;/p&gt; &lt;p&gt;接下来也把生产证书导出来，我把名字起为productionPush,密码设置和开发证书一样（只是为了不混淆，不一样也可以），过程一样，就不贴图片了。&lt;/p&gt; &lt;p&gt;到这里就要所有到准备工作做好了，接下来是测试。&lt;/p&gt; &lt;p&gt;测试远程推送时候我是使用极光推送，你们有什么好的开源都可以使用。地址：www.jpush.cn ，注册个用户登录。&lt;/p&gt; &lt;p&gt;  &lt;img alt="" src="http://images0.cnblogs.com/blog2015/744056/201506/010010385016052.png"&gt;&lt;/img&gt;&lt;/p&gt; &lt;p&gt;登录上去点击创建应用&lt;/p&gt; &lt;p&gt;  &lt;img alt="" src="http://images0.cnblogs.com/blog2015/744056/201506/010012363131611.png"&gt;&lt;/img&gt;&lt;/p&gt; &lt;p&gt;只要填写我选中都部分就可以了，开发证书就是developmentPush.p12文件，密码就是自己设置的密码。生产证书就是productionPush.p12文件。&lt;/p&gt; &lt;p&gt;创建好之后会得到一个appkey   记住有用到，没记住也没事再选择应用中点击你添加到应用也可以看到。&lt;/p&gt; &lt;p&gt;  &lt;img alt="" src="http://images0.cnblogs.com/blog2015/744056/201506/010025253605025.png"&gt;&lt;/img&gt;&lt;/p&gt; &lt;p&gt;然后下载iOS  SDK 地址：www.jpush.cn/common/products#product-sdk&lt;/p&gt; &lt;p&gt;下载好之后把里面的lib文件夹添加到工程中&lt;/p&gt; &lt;p&gt;  &lt;img alt="" src="http://images0.cnblogs.com/blog2015/744056/201506/010020537827023.png"&gt;&lt;/img&gt;&lt;/p&gt; &lt;p&gt;再在工程中创建plist文件&lt;/p&gt; &lt;p&gt;  &lt;img alt="" src="http://images0.cnblogs.com/blog2015/744056/201506/010028091885976.png"&gt;&lt;/img&gt;名字一定要是PushConfig.plist&lt;/p&gt; &lt;p&gt;  &lt;img alt="" src="http://images0.cnblogs.com/blog2015/744056/201506/010029424692774.png"&gt;&lt;/img&gt;&lt;/p&gt; &lt;p&gt;添加三个键值对，内容一定要一样，最后一个就是在极光推送创建应用的appkey&lt;/p&gt; &lt;h4&gt;必要的框架  &lt;p&gt;&lt;/p&gt;  &lt;ul&gt;   &lt;li&gt;CFNetwork.framework&lt;/li&gt;   &lt;li&gt;CoreFoundation.framework&lt;/li&gt;   &lt;li&gt;CoreTelephony.framework&lt;/li&gt;   &lt;li&gt;SystemConfiguration.framework&lt;/li&gt;   &lt;li&gt;CoreGraphics.framework&lt;/li&gt;   &lt;li&gt;Foundation.framework&lt;/li&gt;   &lt;li&gt;UIKit.framework&lt;/li&gt;   &lt;li&gt;Security.framework&lt;/li&gt;   &lt;li&gt;libz.dylib&lt;/li&gt;&lt;/ul&gt;  &lt;p&gt;然后在工程中的appdelegate中添加以下代码：&lt;/p&gt;&lt;/h4&gt; &lt;h4&gt;#import &amp;quot;APService.h&amp;quot;  &lt;p&gt;&lt;/p&gt;  &lt;p&gt;在- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions方法中添加以下代码：&lt;/p&gt;  &lt;p&gt;// Required&lt;/p&gt;  &lt;p&gt;#if __IPHONE_OS_VERSION_MAX_ALLOWED &amp;gt; __IPHONE_7_1&lt;/p&gt;  &lt;p&gt;    if ([[UIDevice currentDevice].systemVersion floatValue] &amp;gt;= 8.0) {&lt;/p&gt;  &lt;p&gt;        //可以添加自定义categories&lt;/p&gt;  &lt;p&gt;        [APService registerForRemoteNotificationTypes:(UIUserNotificationTypeBadge |&lt;/p&gt;  &lt;p&gt;                                                       UIUserNotificationTypeSound |&lt;/p&gt;  &lt;p&gt;                                                       UIUserNotificationTypeAlert)&lt;/p&gt;  &lt;p&gt;                                           categories:nil];&lt;/p&gt;  &lt;p&gt;    } else {&lt;/p&gt;  &lt;p&gt;        //categories 必须为nil&lt;/p&gt;  &lt;p&gt;        [APService registerForRemoteNotificationTypes:(UIRemoteNotificationTypeBadge |&lt;/p&gt;  &lt;p&gt;                                                       UIRemoteNotificationTypeSound |&lt;/p&gt;  &lt;p&gt;                                                       UIRemoteNotificationTypeAlert)&lt;/p&gt;  &lt;p&gt;                                           categories:nil];&lt;/p&gt;  &lt;p&gt;    }&lt;/p&gt;  &lt;p&gt;#else&lt;/p&gt;  &lt;p&gt;    //categories 必须为nil&lt;/p&gt;  &lt;p&gt;    [APService registerForRemoteNotificationTypes:(UIRemoteNotificationTypeBadge |&lt;/p&gt;  &lt;p&gt;                                                   UIRemoteNotificationTypeSound |&lt;/p&gt;  &lt;p&gt;                                                   UIRemoteNotificationTypeAlert)&lt;/p&gt;  &lt;p&gt;                                       categories:nil];&lt;/p&gt;  &lt;p&gt;#endif&lt;/p&gt;  &lt;p&gt;    // Required&lt;/p&gt;  &lt;p&gt;    [APService setupWithOption:launchOptions];&lt;/p&gt;  &lt;p&gt;//－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－－&lt;/p&gt;  &lt;p&gt;最后添加以下方法：&lt;/p&gt;  &lt;p&gt;#pragma mark - 获取DeviceToken&lt;/p&gt;  &lt;p&gt;- (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken {&lt;/p&gt;  &lt;p&gt;    &lt;/p&gt;  &lt;p&gt;    // Required&lt;/p&gt;  &lt;p&gt;    [APService registerDeviceToken:deviceToken];&lt;/p&gt;  &lt;p&gt;}&lt;/p&gt;  &lt;p&gt; &lt;/p&gt;  &lt;p&gt;- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo {&lt;/p&gt;  &lt;p&gt;    &lt;/p&gt;  &lt;p&gt;    // Required&lt;/p&gt;  &lt;p&gt;    [APService handleRemoteNotification:userInfo];&lt;/p&gt;  &lt;p&gt;}&lt;/p&gt;  &lt;p&gt; &lt;/p&gt;  &lt;p&gt;- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler {&lt;/p&gt;  &lt;p&gt;  &lt;/p&gt;  &lt;p&gt;    // IOS 7 Support Required&lt;/p&gt;  &lt;p&gt;    [APService handleRemoteNotification:userInfo];&lt;/p&gt;  &lt;p&gt;    completionHandler(UIBackgroundFetchResultNewData);&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;img alt="" src="http://images0.cnblogs.com/blog2015/744056/201506/010040500949970.png"&gt;&lt;/img&gt;&lt;/p&gt;  &lt;p&gt;好了，完成。如果从下载iOS SDK开始不懂的同学可以查看教程，地址：http://docs.jpush.io/guideline/ios_guide/&lt;/p&gt;  &lt;p&gt; &lt;/p&gt;  &lt;p&gt;以上的文章只是我个人学习笔记，有什么错误的地方不要喷，请您提出来让我改正，写博文不易，请不要乱复制拷贝别人的心得为自己的东西，感谢大家。&lt;/p&gt;  &lt;p&gt; &lt;/p&gt;  &lt;img alt="" height="1" src="http://counter.cnblogs.com/blog/rss/4543057" width="1"&gt;&lt;/img&gt;  &lt;br /&gt;  &lt;p&gt;本文链接：   &lt;a href="http://www.cnblogs.com/qq9070/p/4543057.html" target="_blank"&gt;ios消息推送&lt;/a&gt;，转载请注明。&lt;/p&gt;&lt;/h4&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 />
      <guid isPermaLink="true">https://itindex.net/detail/53562-ios-%E6%B6%88%E6%81%AF-%E6%8E%A8%E9%80%81</guid>
      <pubDate>Mon, 01 Jun 2015 00:50:00 CST</pubDate>
    </item>
    <item>
      <title>[绍棠] iOS远程推送原理及实现过程</title>
      <link>https://itindex.net/detail/55566-ios-%E6%8E%A8%E9%80%81-%E5%8E%9F%E7%90%86</link>
      <description>&lt;p&gt;
推送通知，是现在的应用必不可少的功能。那么在 iOS 中，我们是如何实现远程推送的呢？iOS 的远程推送原理又是什么呢？在做 iOS 远程推送时，我们会遇到各种各样的问题。那么首先让我们准备一些做推送需要的东西。我们需要一个付费的苹果开发者账号（免费的不可以做远程推送），有了开发者账号，我们可以去苹果开发者网站，配置自己所需要的推送的相关证书。然后下载证书，供我们后面使用，详细的证书配置过程，我们下面再说。  &lt;br /&gt;
&lt;/p&gt;
 &lt;p&gt;
首先我们要说说iOS推送通知的基本原理：&lt;/p&gt;
 &lt;p&gt;
苹果的推送服务通知是由自己专门的推送服务器APNs （Apple Push Notification service）来完成的，其过程是 APNs 接收到我们自己的应用服务器发出的被推送的消息，将这条消息推送到指定的 iOS 的设备上，然后再由 iOS设备通知到我们的应用程序，我们将会以通知或者声音的形式收到推送回来的消息。 iOS 远程推送的前提是，装有我们应用程序的 iOS 设备，需要向 APNs 服务器注册，注册成功后，APNs 服务器将会给我们返回一个 devicetoken，我们获取到这个 token
 后会将这个 token 发送给我们自己的应用服务器。当我们需要推送消息时，我们的应用服务器将消息按照指定的格式进行打包，然后结合 iOS 设备的 devicetoken 一起发给 APNs 服务器。我们的应用会和 APNs 服务器维持一个基于 TCP 的长连接，APNs 服务器将新消息推送到iOS 设备上，然后在设备屏幕上显示出推送的消息。&lt;/p&gt;
 &lt;p&gt;
设备注册APNs的流程图：&lt;/p&gt;
 &lt;p&gt;
  &lt;a href="http://blog.tingyun.com/dynamic/transitionResourcePath?key=image/forumImage20160511110244548.png&amp;filename=1.2222.png" rel="image_group" target="_blank" title=""&gt;   &lt;img alt="1.2222.png" src="http://blog.tingyun.com/dynamic/transitionResourcePath?key=image/forumImage20160511110244548.png&amp;filename=1.2222.png" title="&amp;#21548;&amp;#20113;Blog&amp;#21442;&amp;#32771;&amp;#22270;&amp;#29255;"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;
 &lt;p&gt;
上图完成了如下步骤：&lt;/p&gt;
 &lt;p&gt;
1.Device（设备）连接APNs服务器并携带设备序列号（UUID）&lt;/p&gt;
 &lt;p&gt;
2.连接成功，APNs经过打包和处理产生devicetoken并返回给注册的Device（设备）&lt;/p&gt;
 &lt;p&gt;
3.Device（设备）携带获取的devicetoken发送到我们自己的应用服务器&lt;/p&gt;
 &lt;p&gt;
4.完成需要被推送的Device（设备）在APNs服务器和我们自己的应用服务器的注册&lt;/p&gt;
 &lt;p&gt;
推送过程图:&lt;/p&gt;
 &lt;p&gt;
  &lt;a href="http://blog.tingyun.com/dynamic/transitionResourcePath?key=image/forumImage20160511110304267.png&amp;filename=1.333.png" rel="image_group" target="_blank" title=""&gt;   &lt;img alt="1.333.png" src="http://blog.tingyun.com/dynamic/transitionResourcePath?key=image/forumImage20160511110304267.png&amp;filename=1.333.png" title="&amp;#21548;&amp;#20113;Blog&amp;#21442;&amp;#32771;&amp;#22270;&amp;#29255;"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;
 &lt;p&gt;
推送的过程经过如下步骤：&lt;/p&gt;
 &lt;p&gt;
1.首先，我们的设备安装了具有推送功能的应用（应用程序要用代码注册消息推动），我们的 iOS设备在有网络的情况下会连接APNs推送服务器，连接过程中，APNS 服务器会验证devicetoken，连接成功后维持一个基于TCP 的长连接；&lt;/p&gt;
 &lt;p&gt;
2.Provider(我们自己的应用服务器)收到需要被推送的消息并结合被推送的 iOS设备的devicetoken一起打包发送给APNS服务器；&lt;/p&gt;
 &lt;p&gt;
3.APNS服务器将推送信息推送给指定devicetoken的iOS设备；&lt;/p&gt;
 &lt;p&gt;
4.iOS设备收到推送消息后通知我们的应用程序并显示和提示用户（声音、弹出框）&lt;/p&gt;
 &lt;p&gt;
比较直观的流程图:&lt;/p&gt;
 &lt;p&gt;
  &lt;a href="http://blog.tingyun.com/dynamic/transitionResourcePath?key=image/forumImage20160511110323527.png&amp;filename=1.33.png" rel="image_group" target="_blank" title=""&gt;   &lt;img alt="1.33.png" src="http://blog.tingyun.com/dynamic/transitionResourcePath?key=image/forumImage20160511110323527.png&amp;filename=1.33.png" title="&amp;#21548;&amp;#20113;Blog&amp;#21442;&amp;#32771;&amp;#22270;&amp;#29255;"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;
 &lt;p&gt;
信息包结构图：&lt;/p&gt;
 &lt;p&gt;
  &lt;a href="http://blog.tingyun.com/dynamic/transitionResourcePath?key=image/forumImage20160511110345337.png&amp;filename=1.2323.png" rel="image_group" target="_blank" title=""&gt;   &lt;img alt="1.2323.png" src="http://blog.tingyun.com/dynamic/transitionResourcePath?key=image/forumImage20160511110345337.png&amp;filename=1.2323.png" title="&amp;#21548;&amp;#20113;Blog&amp;#21442;&amp;#32771;&amp;#22270;&amp;#29255;"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;
 &lt;p&gt;
上图显示的这个消息体就是我们的应用服务器（Provider）发送给APNs服务器的消息结构，APNs验证这个结构正确并提取其中的信息后，再将消息推送到指定的iOS设备。这个结构体包括五个部分，第一个部分是命令标示符，第二个部分是我们的devicetoken的长度，第三部分是我们的devicetoken字符串，第四部分是推送消 息体（Payload）的长度，最后一部分也就是真正的消息内容了，里面包含了推送消息的基本信息，比如消息内容，应用Icon右上角显示多少数字以及推送消息到达时所播放的声音等&lt;/p&gt;
 &lt;p&gt;
Payload（消息体）的结构:&lt;/p&gt;
 &lt;pre&gt;{
     “aps”:{
     “alert”:“听云给您发送了新消息”,
     “badge”:1,
     “sound”:“default”
     },
}&lt;/pre&gt;
 &lt;p&gt;
这其实就是个JSON结构体，alert标签的内容就是会显示在用户手机上的推送信息，badge显示的数量（注意是整型）是会在应用Icon右上角显示的数量，提示有多少条未读消息等，sound就是当推送信息送达是手机播放的声音，传defalut就标明使用系统默认声音。&lt;/p&gt;
 &lt;p&gt;
下面就是我们推送通知所需要的证书的推送过程：&lt;/p&gt;
 &lt;p&gt;
1.首先我们要新建一个Certificate Signing Request(也就是CSR)的请求文件&lt;/p&gt;
 &lt;p&gt;
在应用程序里的使用工具中找到钥匙串访问，选择从证书颁发机构请求证书&lt;/p&gt;
 &lt;p&gt;
  &lt;a href="http://blog.tingyun.com/dynamic/transitionResourcePath?key=image/forumImage20160511110436064.png&amp;filename=1.45.png" rel="image_group" target="_blank" title=""&gt;   &lt;img alt="1.45.png" src="http://blog.tingyun.com/dynamic/transitionResourcePath?key=image/forumImage20160511110436064.png&amp;filename=1.45.png" title="&amp;#21548;&amp;#20113;Blog&amp;#21442;&amp;#32771;&amp;#22270;&amp;#29255;"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;
 &lt;p&gt;
  &lt;a href="http://blog.tingyun.com/dynamic/transitionResourcePath?key=image/forumImage20160511110452523.png&amp;filename=1.44.png" rel="image_group" target="_blank" title=""&gt;   &lt;img alt="1.44.png" src="http://blog.tingyun.com/dynamic/transitionResourcePath?key=image/forumImage20160511110452523.png&amp;filename=1.44.png" title="&amp;#21548;&amp;#20113;Blog&amp;#21442;&amp;#32771;&amp;#22270;&amp;#29255;"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;
 &lt;p&gt;
注意:邮箱地址,填自己的开发者账号,常用名,随便填一个记住就行。然后选择存储到磁盘。继续就行。&lt;/p&gt;
 &lt;p&gt;
  &lt;a href="http://blog.tingyun.com/dynamic/transitionResourcePath?key=image/forumImage20160511110546038.png&amp;filename=1.55.png" rel="image_group" target="_blank" title=""&gt;   &lt;img alt="1.55.png" src="http://blog.tingyun.com/dynamic/transitionResourcePath?key=image/forumImage20160511110546038.png&amp;filename=1.55.png" title="&amp;#21548;&amp;#20113;Blog&amp;#21442;&amp;#32771;&amp;#22270;&amp;#29255;"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;
 &lt;p&gt;
保存位置在 tingyun（指定自己的文件夹，这里我选择的是我的文件夹）,点击存储&lt;/p&gt;
 &lt;p&gt;
然后点击完成后我们会在 tingyun 里看到一个CertificateSigningRequest.certSigningRequest的请求文件，也就是我们说的CSR文件。在我们生成CSR文件的同时，会在钥匙串访问中生成一对秘钥，名称为刚才我们填写的常用名&lt;/p&gt;
 &lt;p&gt;
2.配置AppID  &lt;br /&gt;
&lt;/p&gt;
 &lt;p&gt;
到苹果开发者网站https://developer.apple.com&lt;/p&gt;
 &lt;p&gt;
点击Account &lt;/p&gt;
 &lt;p&gt;
  &lt;a href="http://blog.tingyun.com/dynamic/transitionResourcePath?key=image/forumImage20160511110641214.png&amp;filename=1.56.png" rel="image_group" target="_blank" title=""&gt;   &lt;img alt="1.56.png" src="http://blog.tingyun.com/dynamic/transitionResourcePath?key=image/forumImage20160511110641214.png&amp;filename=1.56.png" title="&amp;#21548;&amp;#20113;Blog&amp;#21442;&amp;#32771;&amp;#22270;&amp;#29255;"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;
 &lt;p&gt;
选择 Certificates,identifiers&amp;amp;Profiles &lt;/p&gt;
 &lt;p&gt;
  &lt;a href="http://blog.tingyun.com/dynamic/transitionResourcePath?key=image/forumImage20160511110703158.png&amp;filename=1.78.png" rel="image_group" target="_blank" title=""&gt;   &lt;img alt="1.78.png" src="http://blog.tingyun.com/dynamic/transitionResourcePath?key=image/forumImage20160511110703158.png&amp;filename=1.78.png" title="&amp;#21548;&amp;#20113;Blog&amp;#21442;&amp;#32771;&amp;#22270;&amp;#29255;"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;
 &lt;p&gt;
选择 Identifiers -&amp;gt;App IDs 点击上方的+号创建一个 App ID.&lt;/p&gt;
 &lt;p&gt;
  &lt;a href="http://blog.tingyun.com/dynamic/transitionResourcePath?key=image/forumImage20160511110723649.png&amp;filename=1.888.png" rel="image_group" target="_blank" title=""&gt;   &lt;img alt="1.888.png" src="http://blog.tingyun.com/dynamic/transitionResourcePath?key=image/forumImage20160511110723649.png&amp;filename=1.888.png" title="&amp;#21548;&amp;#20113;Blog&amp;#21442;&amp;#32771;&amp;#22270;&amp;#29255;"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;
 &lt;p&gt;
Name: 填写 App 的名字就行&lt;/p&gt;
 &lt;p&gt;
App ID Suffix 选择不用通配符的及 Explicit App ID&lt;/p&gt;
 &lt;p&gt;
Bundle ID:填写自己应用的 Bundle ID 一定要和自己应用的一致.&lt;/p&gt;
 &lt;p&gt;
  &lt;a href="http://blog.tingyun.com/dynamic/transitionResourcePath?key=image/forumImage20160511110753676.png&amp;filename=1.555.png" rel="image_group" target="_blank" title=""&gt;   &lt;img alt="1.555.png" src="http://blog.tingyun.com/dynamic/transitionResourcePath?key=image/forumImage20160511110753676.png&amp;filename=1.555.png" title="&amp;#21548;&amp;#20113;Blog&amp;#21442;&amp;#32771;&amp;#22270;&amp;#29255;"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;
 &lt;p&gt;
在下面的 App Services 中选择自己需要的服务&lt;/p&gt;
 &lt;p&gt;
我们需要推送服务,所以在Push Notifications上打勾&lt;/p&gt;
 &lt;p&gt;
然后点击continue&lt;/p&gt;
 &lt;p&gt;
  &lt;a href="http://blog.tingyun.com/dynamic/transitionResourcePath?key=image/forumImage20160511110813852.png&amp;filename=1.666.png" rel="image_group" target="_blank" title=""&gt;   &lt;img alt="1.666.png" src="http://blog.tingyun.com/dynamic/transitionResourcePath?key=image/forumImage20160511110813852.png&amp;filename=1.666.png" title="&amp;#21548;&amp;#20113;Blog&amp;#21442;&amp;#32771;&amp;#22270;&amp;#29255;"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;
 &lt;p&gt;
3.创建证书&lt;/p&gt;
 &lt;p&gt;
证书需要创建两种，一种是开发的、一种是发布的，开发的是做测试用的。&lt;/p&gt;
 &lt;p&gt;
选择Development 点击右上角的+号,创建证书,我们首先创建开发证书&lt;/p&gt;
 &lt;p&gt;
  &lt;a href="http://blog.tingyun.com/dynamic/transitionResourcePath?key=image/forumImage20160511110833611.png&amp;filename=1.99.png" rel="image_group" target="_blank" title=""&gt;   &lt;img alt="1.99.png" height="123" src="http://blog.tingyun.com/dynamic/transitionResourcePath?key=image/forumImage20160511110833611.png&amp;filename=1.99.png" title="&amp;#21548;&amp;#20113;Blog&amp;#21442;&amp;#32771;&amp;#22270;&amp;#29255;" width="555"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;
 &lt;p&gt;
选择Apple Push Notification service SSL (Sandbox),创建推送服务证书点击下一步&lt;/p&gt;
 &lt;p&gt;
  &lt;a href="http://blog.tingyun.com/dynamic/transitionResourcePath?key=image/forumImage20160511110852080.png&amp;filename=1.00.png" rel="image_group" target="_blank" title=""&gt;   &lt;img alt="1.00.png" height="262" src="http://blog.tingyun.com/dynamic/transitionResourcePath?key=image/forumImage20160511110852080.png&amp;filename=1.00.png" title="&amp;#21548;&amp;#20113;Blog&amp;#21442;&amp;#32771;&amp;#22270;&amp;#29255;" width="557"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;
 &lt;p&gt;
这儿的 App ID 选择我们刚才创建的 App ID&lt;/p&gt;
 &lt;p&gt;
然后点击下一步,下一步&lt;/p&gt;
 &lt;p&gt;
  &lt;a href="http://blog.tingyun.com/dynamic/transitionResourcePath?key=image/forumImage20160511110906544.png&amp;filename=0.09.png" rel="image_group" target="_blank" title=""&gt;   &lt;img alt="0.09.png" height="194" src="http://blog.tingyun.com/dynamic/transitionResourcePath?key=image/forumImage20160511110906544.png&amp;filename=0.09.png" title="&amp;#21548;&amp;#20113;Blog&amp;#21442;&amp;#32771;&amp;#22270;&amp;#29255;" width="556"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;
 &lt;p&gt;
这儿点击 Choose File,选择我们刚才创建的 CSR 文件.&lt;/p&gt;
 &lt;p&gt;
然后点击生成(Generate)&lt;/p&gt;
 &lt;p&gt;
最后点击下载,下载证书。将下载的证书，放到指定位置。&lt;/p&gt;
 &lt;p&gt;
  &lt;a href="http://blog.tingyun.com/dynamic/transitionResourcePath?key=image/forumImage20160511110929879.png&amp;filename=0.08.png" rel="image_group" target="_blank" title=""&gt;   &lt;img alt="0.08.png" height="400" src="http://blog.tingyun.com/dynamic/transitionResourcePath?key=image/forumImage20160511110929879.png&amp;filename=0.08.png" title="&amp;#21548;&amp;#20113;Blog&amp;#21442;&amp;#32771;&amp;#22270;&amp;#29255;" width="568"&gt;&lt;/img&gt;&lt;/a&gt; &lt;/p&gt;
 &lt;p&gt;
发布证书的创建和开发证书一样,选择Production-&amp;gt;Apple Push Notification service SSL (Production)后面和开发证书一样&lt;/p&gt;
 &lt;p&gt;
  &lt;a href="http://blog.tingyun.com/dynamic/transitionResourcePath?key=image/forumImage20160511110947229.png&amp;filename=0.07.png" rel="image_group" target="_blank" title=""&gt;   &lt;img alt="0.07.png" height="182" src="http://blog.tingyun.com/dynamic/transitionResourcePath?key=image/forumImage20160511110947229.png&amp;filename=0.07.png" title="&amp;#21548;&amp;#20113;Blog&amp;#21442;&amp;#32771;&amp;#22270;&amp;#29255;" width="586"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;
 &lt;p&gt;
  &lt;a href="http://blog.tingyun.com/dynamic/transitionResourcePath?key=image/forumImage20160511111121989.png&amp;filename=0.9888.png" rel="image_group" target="_blank" title=""&gt;   &lt;img alt="0.9888.png" src="http://blog.tingyun.com/dynamic/transitionResourcePath?key=image/forumImage20160511111121989.png&amp;filename=0.9888.png" title="&amp;#21548;&amp;#20113;Blog&amp;#21442;&amp;#32771;&amp;#22270;&amp;#29255;"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;
 &lt;p&gt;
4.添加 Devices：&lt;/p&gt;
 &lt;p&gt;
首先选中你要添加哪种设备，然后在左上角点击“+”号。&lt;/p&gt;
 &lt;p&gt;
  &lt;a href="http://blog.tingyun.com/dynamic/transitionResourcePath?key=image/forumImage20160511111201022.png&amp;filename=0.006.png" rel="image_group" target="_blank" title=""&gt;   &lt;img alt="0.006.png" src="http://blog.tingyun.com/dynamic/transitionResourcePath?key=image/forumImage20160511111201022.png&amp;filename=0.006.png" title="&amp;#21548;&amp;#20113;Blog&amp;#21442;&amp;#32771;&amp;#22270;&amp;#29255;"&gt;&lt;/img&gt;&lt;/a&gt; &lt;/p&gt;
 &lt;p&gt;
Name 填写一个设备名字。&lt;/p&gt;
 &lt;p&gt;
UDID 填写自己需要加入测试的设备的 UDID。&lt;/p&gt;
 &lt;p&gt;
然后点击下一步&lt;/p&gt;
 &lt;p&gt;
  &lt;a href="http://blog.tingyun.com/dynamic/transitionResourcePath?key=image/forumImage20160511111230260.png&amp;filename=0.005.png" rel="image_group" target="_blank" title=""&gt;   &lt;img alt="0.005.png" height="832" src="http://blog.tingyun.com/dynamic/transitionResourcePath?key=image/forumImage20160511111230260.png&amp;filename=0.005.png" title="&amp;#21548;&amp;#20113;Blog&amp;#21442;&amp;#32771;&amp;#22270;&amp;#29255;" width="577"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;
 &lt;p&gt;
然后点击 Register 即可&lt;/p&gt;
 &lt;p&gt;
  &lt;a href="http://blog.tingyun.com/dynamic/transitionResourcePath?key=image/forumImage20160511111303570.png&amp;filename=0.001.png" rel="image_group" target="_blank" title=""&gt;   &lt;img alt="0.001.png" height="783" src="http://blog.tingyun.com/dynamic/transitionResourcePath?key=image/forumImage20160511111303570.png&amp;filename=0.001.png" title="&amp;#21548;&amp;#20113;Blog&amp;#21442;&amp;#32771;&amp;#22270;&amp;#29255;" width="557"&gt;&lt;/img&gt;&lt;/a&gt; &lt;/p&gt;
 &lt;p&gt;
点击Done。&lt;/p&gt;
 &lt;p&gt;
  &lt;a href="http://blog.tingyun.com/dynamic/transitionResourcePath?key=image/forumImage20160511111340607.png&amp;filename=0.002.png" rel="image_group" target="_blank" title=""&gt;   &lt;img alt="0.002.png" src="http://blog.tingyun.com/dynamic/transitionResourcePath?key=image/forumImage20160511111340607.png&amp;filename=0.002.png" title="&amp;#21548;&amp;#20113;Blog&amp;#21442;&amp;#32771;&amp;#22270;&amp;#29255;"&gt;&lt;/img&gt;&lt;/a&gt; &lt;/p&gt;
 &lt;p&gt;
  &lt;a href="http://blog.tingyun.com/dynamic/transitionResourcePath?key=image/forumImage20160511111404730.png&amp;filename=000.png" rel="image_group" target="_blank" title=""&gt;   &lt;img alt="000.png" src="http://blog.tingyun.com/dynamic/transitionResourcePath?key=image/forumImage20160511111404730.png&amp;filename=000.png" title="&amp;#21548;&amp;#20113;Blog&amp;#21442;&amp;#32771;&amp;#22270;&amp;#29255;"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;
 &lt;p&gt;
5.查找设备的 UDID：&lt;/p&gt;
 &lt;p&gt;
用自己的 iOS 设备连接到电脑上，打开 iTunes。&lt;/p&gt;
 &lt;p&gt;
在设备摘要处可以看见一个序列号，点击序列号就会变成 UDID。&lt;/p&gt;
 &lt;p&gt;
  &lt;a href="http://blog.tingyun.com/dynamic/transitionResourcePath?key=image/forumImage20160511111435241.png&amp;filename=0091.png" rel="image_group" target="_blank" title=""&gt;   &lt;img alt="0091.png" src="http://blog.tingyun.com/dynamic/transitionResourcePath?key=image/forumImage20160511111435241.png&amp;filename=0091.png" title="&amp;#21548;&amp;#20113;Blog&amp;#21442;&amp;#32771;&amp;#22270;&amp;#29255;"&gt;&lt;/img&gt;&lt;/a&gt; &lt;/p&gt;
 &lt;p&gt;
6.生成配置文件&lt;/p&gt;
 &lt;p&gt;
配置文件也有两种，一种是开发的，一种是发布的，开发的使我们做测试需要的，发布的是我们在 Appstore 上发布时需要的，我们都需要生成。&lt;/p&gt;
 &lt;p&gt;
我们先生成开发配置文件,选择Provisioning Profiles-&amp;gt;Development点击右上角的+号。&lt;/p&gt;
 &lt;p&gt;
  &lt;a href="http://blog.tingyun.com/dynamic/transitionResourcePath?key=image/forumImage20160511111458753.png&amp;filename=0092.png" rel="image_group" target="_blank" title=""&gt;   &lt;img alt="0092.png" src="http://blog.tingyun.com/dynamic/transitionResourcePath?key=image/forumImage20160511111458753.png&amp;filename=0092.png" title="&amp;#21548;&amp;#20113;Blog&amp;#21442;&amp;#32771;&amp;#22270;&amp;#29255;"&gt;&lt;/img&gt;&lt;/a&gt; &lt;/p&gt;
 &lt;p&gt;
选择iOS App Development 点击下一步&lt;/p&gt;
 &lt;p&gt;
  &lt;a href="http://blog.tingyun.com/dynamic/transitionResourcePath?key=image/forumImage20160511111527684.png&amp;filename=0093.png" rel="image_group" target="_blank" title=""&gt;   &lt;img alt="0093.png" src="http://blog.tingyun.com/dynamic/transitionResourcePath?key=image/forumImage20160511111527684.png&amp;filename=0093.png" title="&amp;#21548;&amp;#20113;Blog&amp;#21442;&amp;#32771;&amp;#22270;&amp;#29255;"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;
 &lt;p&gt;
这儿的 App ID 仍然选择我们刚才创建的 App ID&lt;/p&gt;
 &lt;p&gt;
  &lt;a href="http://blog.tingyun.com/dynamic/transitionResourcePath?key=image/forumImage20160511111551686.png&amp;filename=0094.png" rel="image_group" target="_blank" title=""&gt;   &lt;img alt="0094.png" src="http://blog.tingyun.com/dynamic/transitionResourcePath?key=image/forumImage20160511111551686.png&amp;filename=0094.png" title="&amp;#21548;&amp;#20113;Blog&amp;#21442;&amp;#32771;&amp;#22270;&amp;#29255;"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;
 &lt;p&gt;
  &lt;a href="http://blog.tingyun.com/dynamic/transitionResourcePath?key=image/forumImage20160511111611829.png&amp;filename=0095.png" rel="image_group" target="_blank" title=""&gt;   &lt;img alt="0095.png" src="http://blog.tingyun.com/dynamic/transitionResourcePath?key=image/forumImage20160511111611829.png&amp;filename=0095.png" title="&amp;#21548;&amp;#20113;Blog&amp;#21442;&amp;#32771;&amp;#22270;&amp;#29255;"&gt;&lt;/img&gt;&lt;/a&gt; &lt;/p&gt;
 &lt;p&gt;
这儿选择我们开发者的证书,如果不知道是哪个选择全部即可  &lt;br /&gt;
&lt;/p&gt;
 &lt;p&gt;
  &lt;a href="http://blog.tingyun.com/dynamic/transitionResourcePath?key=image/forumImage20160511111637112.png&amp;filename=0096.png" rel="image_group" target="_blank" title=""&gt;   &lt;img alt="0096.png" height="586" src="http://blog.tingyun.com/dynamic/transitionResourcePath?key=image/forumImage20160511111637112.png&amp;filename=0096.png" title="&amp;#21548;&amp;#20113;Blog&amp;#21442;&amp;#32771;&amp;#22270;&amp;#29255;" width="573"&gt;&lt;/img&gt;&lt;/a&gt; &lt;/p&gt;
 &lt;p&gt;
这儿选择我们的测试设备,如果没有则在前面的Devices里面添加即可  &lt;br /&gt;
&lt;/p&gt;
 &lt;p&gt;
  &lt;a href="http://blog.tingyun.com/dynamic/transitionResourcePath?key=image/forumImage20160511111709881.png&amp;filename=0097.png" rel="image_group" target="_blank" title=""&gt;   &lt;img alt="0097.png" height="633" src="http://blog.tingyun.com/dynamic/transitionResourcePath?key=image/forumImage20160511111709881.png&amp;filename=0097.png" title="&amp;#21548;&amp;#20113;Blog&amp;#21442;&amp;#32771;&amp;#22270;&amp;#29255;" width="561"&gt;&lt;/img&gt;&lt;/a&gt; &lt;/p&gt;
 &lt;p&gt;
  &lt;a href="http://blog.tingyun.com/dynamic/transitionResourcePath?key=image/forumImage20160511111732656.png&amp;filename=0098.png" rel="image_group" target="_blank" title=""&gt;   &lt;img alt="0098.png" height="646" src="http://blog.tingyun.com/dynamic/transitionResourcePath?key=image/forumImage20160511111732656.png&amp;filename=0098.png" title="&amp;#21548;&amp;#20113;Blog&amp;#21442;&amp;#32771;&amp;#22270;&amp;#29255;" width="565"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;
 &lt;p&gt;
随便取个名字即可,然后下载下来  &lt;br /&gt;
&lt;/p&gt;
 &lt;p&gt;
  &lt;a href="http://blog.tingyun.com/dynamic/transitionResourcePath?key=image/forumImage20160511111817402.png&amp;filename=1123.png" rel="image_group" target="_blank" title=""&gt;   &lt;img alt="1123.png" src="http://blog.tingyun.com/dynamic/transitionResourcePath?key=image/forumImage20160511111817402.png&amp;filename=1123.png" title="&amp;#21548;&amp;#20113;Blog&amp;#21442;&amp;#32771;&amp;#22270;&amp;#29255;"&gt;&lt;/img&gt;&lt;/a&gt; &lt;/p&gt;
 &lt;p&gt;
发布配置文件和开发配置文件一样创建,选择Distribution-&amp;gt;Ad Hoc即可，后面与发布配置文件一样。  &lt;br /&gt;
&lt;/p&gt;
 &lt;p&gt;
  &lt;a href="http://blog.tingyun.com/dynamic/transitionResourcePath?key=image/forumImage20160511111914255.png&amp;filename=1125.png" rel="image_group" target="_blank" title=""&gt;   &lt;img alt="1125.png" height="387" src="http://blog.tingyun.com/dynamic/transitionResourcePath?key=image/forumImage20160511111914255.png&amp;filename=1125.png" title="&amp;#21548;&amp;#20113;Blog&amp;#21442;&amp;#32771;&amp;#22270;&amp;#29255;" width="524"&gt;&lt;/img&gt;&lt;/a&gt; &lt;/p&gt;
 &lt;p&gt;
  &lt;a href="http://blog.tingyun.com/dynamic/transitionResourcePath?key=image/forumImage20160511111956889.png&amp;filename=1126.png" rel="image_group" target="_blank" title=""&gt;   &lt;img alt="1126.png" height="567" src="http://blog.tingyun.com/dynamic/transitionResourcePath?key=image/forumImage20160511111956889.png&amp;filename=1126.png" title="&amp;#21548;&amp;#20113;Blog&amp;#21442;&amp;#32771;&amp;#22270;&amp;#29255;" width="519"&gt;&lt;/img&gt;&lt;/a&gt; &lt;/p&gt;
 &lt;p&gt;
  &lt;a href="http://blog.tingyun.com/dynamic/transitionResourcePath?key=image/forumImage20160511112034232.png&amp;filename=11250.png" rel="image_group" target="_blank" title=""&gt;   &lt;img alt="11250.png" height="512" src="http://blog.tingyun.com/dynamic/transitionResourcePath?key=image/forumImage20160511112034232.png&amp;filename=11250.png" title="&amp;#21548;&amp;#20113;Blog&amp;#21442;&amp;#32771;&amp;#22270;&amp;#29255;" width="505"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;
 &lt;p&gt;
证书配置完成,打开我们创建的应用项目&lt;/p&gt;
 &lt;p&gt;
打开AppDelegate.m 文件,在- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions 方法中添加下面代码,注册消息推送&lt;/p&gt;
 &lt;pre&gt;- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    // Override point for customization after application launch.
        /** 消息推送注册 */
    if ([UIDevice currentDevice].systemVersion.floatValue &amp;gt;= 8.0) {
        
        UIUserNotificationSettings *settings = [UIUserNotificationSettings settingsForTypes:UIUserNotificationTypeBadge | UIUserNotificationTypeSound | UIUserNotificationTypeAlert categories:nil];
        [application registerUserNotificationSettings:settings];
        [application registerForRemoteNotifications];
    }else {
        [application registerForRemoteNotificationTypes:(UIRemoteNotificationTypeBadge | UIRemoteNotificationTypeSound | UIRemoteNotificationTypeAlert)];
    }
    
    return YES;
}
下面方法是返回 ANPs 苹果推送服务器生成的唯一标识
/** 接收服务器传回的设备唯一标识 token */
-(void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken{
    
    // 第一次运行获取到DeviceToken时间会比较长！
    // 将deviceToken转换成字符串，以便后续使用
    NSString *token = [deviceToken description];
    NSLog(@&amp;quot;description %@&amp;quot;, token);
}

下面方法是当有消息推送回来时,接收推送消息	
/** 设备接收到来自苹果推送服务器的消息时触发的,用来显示推送消息 */

-(void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo{

    NSLog(@&amp;quot;userInfo == %@&amp;quot;,userInfo);
}

上面方法是当注册推送服务失败时,接收错误信息
/** 注册推送服务失败 */
-(void)application:(UIApplication *)application didFailToRegisterForRemoteNotificationsWithError:(NSError *)error{
    NSLog(@&amp;quot;注册失败 %@&amp;quot;,error);
}&lt;/pre&gt;
 &lt;p&gt;
服务器端(Java服务器)&lt;/p&gt;
 &lt;p&gt;
服务器端我们需要,一个后缀为. p12的证书,以及需要的 jar 包&lt;/p&gt;
 &lt;p&gt;
服务器端的证书生成方式:&lt;/p&gt;
 &lt;p&gt;
打开我们前面下载的证书,在钥匙串中找到它&lt;/p&gt;
 &lt;p&gt;
  &lt;a href="http://blog.tingyun.com/dynamic/transitionResourcePath?key=image/forumImage20160511112206166.png&amp;filename=0.98888.png" rel="image_group" target="_blank" title=""&gt;   &lt;img alt="0.98888.png" src="http://blog.tingyun.com/dynamic/transitionResourcePath?key=image/forumImage20160511112206166.png&amp;filename=0.98888.png" title="&amp;#21548;&amp;#20113;Blog&amp;#21442;&amp;#32771;&amp;#22270;&amp;#29255;"&gt;&lt;/img&gt;&lt;/a&gt; &lt;/p&gt;
 &lt;p&gt;
点击鼠标右键选择导出&lt;/p&gt;
 &lt;p&gt;
  &lt;a href="http://blog.tingyun.com/dynamic/transitionResourcePath?key=image/forumImage20160511112222101.png&amp;filename=0.998.png" rel="image_group" target="_blank" title=""&gt;   &lt;img alt="0.998.png" height="120" src="http://blog.tingyun.com/dynamic/transitionResourcePath?key=image/forumImage20160511112222101.png&amp;filename=0.998.png" title="&amp;#21548;&amp;#20113;Blog&amp;#21442;&amp;#32771;&amp;#22270;&amp;#29255;" width="530"&gt;&lt;/img&gt;&lt;/a&gt; &lt;/p&gt;
 &lt;p&gt;
导出后缀为.p12的文件保存到自己的电脑上,需要输入一个密码,在 Java 服务器端要用到  &lt;br /&gt;
&lt;/p&gt;
 &lt;p&gt;
  &lt;a href="http://blog.tingyun.com/dynamic/transitionResourcePath?key=image/forumImage20160511112257281.png&amp;filename=0.9889.png" rel="image_group" target="_blank" title=""&gt;   &lt;img alt="0.9889.png" height="300" src="http://blog.tingyun.com/dynamic/transitionResourcePath?key=image/forumImage20160511112257281.png&amp;filename=0.9889.png" title="&amp;#21548;&amp;#20113;Blog&amp;#21442;&amp;#32771;&amp;#22270;&amp;#29255;" width="526"&gt;&lt;/img&gt;&lt;/a&gt; &lt;/p&gt;
 &lt;p&gt;
Java服务器端需要的 Jar 包&lt;/p&gt;
 &lt;p&gt;
  &lt;a href="http://blog.tingyun.com/dynamic/transitionResourcePath?key=image/forumImage20160511112326820.png&amp;filename=0.87.png" rel="image_group" target="_blank" title=""&gt;   &lt;img alt="0.87.png" src="http://blog.tingyun.com/dynamic/transitionResourcePath?key=image/forumImage20160511112326820.png&amp;filename=0.87.png" title="&amp;#21548;&amp;#20113;Blog&amp;#21442;&amp;#32771;&amp;#22270;&amp;#29255;"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;
 &lt;p&gt;
Java 服务器端代码：&lt;/p&gt;
 &lt;pre&gt;import javapns.back.PushNotificationManager;
import javapns.back.SSLConnectionHelper;
import javapns.data.Device;
import javapns.data.PayLoad;
public class pushService {
	public static void main(String[] args) {
		   
		   
		  try {
		           String deviceToken = &amp;quot;eab6df47eb4f81e0aaa93bb208cffd7dc3884fd346ea0743fcf93288018cfcb6&amp;quot;;
		           //被推送的iphone应用程序标示符      
		           PayLoad payLoad = new PayLoad();
		           payLoad.addAlert(&amp;quot;测试我的push消息&amp;quot;);
		           payLoad.addBadge(1);
		           payLoad.addSound(&amp;quot;default&amp;quot;);
		                    
		           PushNotificationManager pushManager = PushNotificationManager.getInstance();
		           pushManager.addDevice(&amp;quot;iphone&amp;quot;, deviceToken);
		           
		           		           //测试推送服务器地址：gateway.sandbox.push.apple.com /2195 
		      	   //产品推送服务器地址：gateway.push.apple.com / 2195 
		           String host=&amp;quot;gateway.sandbox.push.apple.com&amp;quot;;  //测试用的苹果推送服务器
		           int port = 2195;
		           String certificatePath = &amp;quot;/Users/hsw/Desktop/PushTest/PushTest.p12&amp;quot;; //刚才在mac系统下导出的证书
		           
		           String certificatePassword= &amp;quot;123456&amp;quot;;
		          
		           pushManager.initializeConnection(host, port, certificatePath,certificatePassword, SSLConnectionHelper.KEYSTORE_TYPE_PKCS12);
		                     
		           //Send Push
		           Device client = pushManager.getDevice(&amp;quot;iphone&amp;quot;);
		           pushManager.sendNotification(client, payLoad); //推送消息
		           pushManager.stopConnection();
		           pushManager.removeDevice(&amp;quot;iphone&amp;quot;);
		          }
		          catch (Exception e) {
		           e.printStackTrace();
		           System.out.println(&amp;quot;push faild!&amp;quot;);
		            return;
		          }
		          System.out.println(&amp;quot;push succeed!&amp;quot;);
		         }

}&lt;/pre&gt;

 &lt;div&gt;
    作者：happyshaotang2 发表于2016/5/12 9:05:15   &lt;a href="http://blog.csdn.net/happyshaotang2/article/details/51381154"&gt;原文链接&lt;/a&gt;
&lt;/div&gt;
 &lt;div&gt;
    阅读：593 评论：0   &lt;a href="http://blog.csdn.net/happyshaotang2/article/details/51381154#comments" target="_blank"&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 />
      <guid isPermaLink="true">https://itindex.net/detail/55566-ios-%E6%8E%A8%E9%80%81-%E5%8E%9F%E7%90%86</guid>
      <pubDate>Thu, 12 May 2016 17:05:15 CST</pubDate>
    </item>
    <item>
      <title>iOS内存泄漏自动检测工具PLeakSniffer</title>
      <link>https://itindex.net/detail/55978-ios-%E5%86%85%E5%AD%98%E6%B3%84%E6%BC%8F-%E5%B7%A5%E5%85%B7</link>
      <description>&lt;div&gt;
  &lt;p&gt;新款Objective-C内存泄漏自动检测工具PLeakSniffer，   &lt;a href="https://github.com/music4kid/PLeakSniffer" target="_blank"&gt;GitHub地址&lt;/a&gt;。&lt;/p&gt;
  &lt;p&gt;   &lt;strong&gt;背景&lt;/strong&gt;&lt;/p&gt;
  &lt;p&gt;前些天读到WeRead团队分享的一款内存泄漏检测工具   &lt;a href="http://wereadteam.github.io/2016/02/22/MLeaksFinder/" target="_blank"&gt;MLeaksFinder&lt;/a&gt;，恍惚想起早些时候自己也有过编写这样一个小工具的想法，不知道由于什么原因把这事给忘记了。在仔细读过MLeaksFinder源码，了解实现思路之后，发现和自己最初的想法并不相同，终于在上个周末战胜拖延症将之前的想法付诸于代码，也就诞生了这款功能类似的内存泄漏检测工具PLeakSniffer。建议读者先详细阅读下MLeaksFinder这篇博客。&lt;/p&gt;
  &lt;p&gt;   &lt;strong&gt;为什么要再造轮子&lt;/strong&gt;&lt;/p&gt;
  &lt;p&gt;我在公司的项目里实际试用了MLeaksFinder，还查处了2处泄漏??。根据MLeaksFinder代码文件中日期推测，这个项目至少已开始半年有余，并在微信读书上得到了实践验证，在功能性和稳定性上都应该有不错的表现。&lt;/p&gt;
  &lt;p&gt;在编写完PLeakSniffer之后，查出了与MLeaksFinder相同的内存泄漏，思路迥异的代码抵达了相同的终点，写代码的乐趣莫过于此。新的思路或许还能抛砖引玉，如果激发更多的创意，也算是对iOS开发社区的一点小贡献。&lt;/p&gt;
  &lt;p&gt;MLeaksFinder现阶段能查处UIViewController和UIView的泄漏，我早先的想法还能递归的查出UIViewController之下所有Property的泄漏，并在PLeakSniffer及公司项目中得到了初步的验证，这算是对MLeaksFinder功能的一个小补充。&lt;/p&gt;
  &lt;p&gt;   &lt;strong&gt;这类工具的意义&lt;/strong&gt;&lt;/p&gt;
  &lt;p&gt;在我们讨论这类工具的意义之前，我们先得明确一点：&lt;/p&gt;
  &lt;p&gt;如果不使用Instrument当中的Leak检测工具，并没有什么轻易的100%精准的内存泄漏检测方式。&lt;/p&gt;
  &lt;p&gt;但这类工具还是有其存在价值的，内存泄漏的危害不用赘述，如果有一款工具能在80%的场景下检测出可能的内存泄漏，而且这种检测并不会带来任何副作用（不影响生产环境代码），为什么不使用它呢。&lt;/p&gt;
  &lt;p&gt;大部分人都低估了他们写代码时导致意外内存泄漏的可能性。Retain Cycle，Block强引用，NSTimer释放不当，这些常见的错误还是很容易出现在我们的代码里，Instrument每使用一次要费些精力，适合做定期的大排查。平常时候就更适合用MLeaksFinder，PLeakSniffer这类工具来做实时监控，提供免费建议。&lt;/p&gt;
  &lt;p&gt;   &lt;strong&gt;PLeakSniffer实现思路&lt;/strong&gt;&lt;/p&gt;
  &lt;p&gt;我们绝大部分时候都是在编写UIViewController，UIViewController就像一个根节点，持有并管理着很多的子节点对象，这些子节点的生命周期都依赖于Controller，Controller释放的时候，他们也随之释放。用一张图简单的描述他们的关系：&lt;/p&gt;
  &lt;p&gt;   &lt;img alt="sniffer1.jpg" src="http://cc.cocimg.com/api/uploads/20160706/1467783747818769.jpg" title="1467783747818769.jpg"&gt;&lt;/img&gt;&lt;/p&gt;
  &lt;p&gt;根据各个应用使用的设计模式不同（MVC，MVP，MVVM等），Controller所持有的Property也不相同。这里我们使用MVP作为例子，Controller所包含的对象就包括各种View对象，和Presenter，Model对象。当然每个对象又有可能持有更多的子对象。&lt;/p&gt;
  &lt;p&gt;PLeakSniffer基于这样一个假设：&lt;/p&gt;
  &lt;p&gt;如果Controller被释放了，但其曾经持有过的子对象如果还存在，那么这些子对象就是泄漏的可疑目标。&lt;/p&gt;
  &lt;p&gt;当然这个假设并不是一个100%适用的真理，不同工程师编写代码的方式风格差别很大，有些会把某些UIViewController做成单例（个人觉得这不是个好主意。。），有些会把某些View缓存起来（即使Controller已被释放），还会有其他考虑不到的场景。但在80%以上的场景，我们在Controller结束生命周期之后会将其持有的资源一并释放。这时候PLeakSniffer可以发挥用处，给你一些免费的泄漏建议。&lt;/p&gt;
  &lt;p&gt;   &lt;strong&gt;那么怎么在Controller被释放之后，知道其持有的对象没有被释放呢？&lt;/strong&gt;&lt;/p&gt;
  &lt;p&gt;一个小技巧可以达成这个目标：子对象（比如view）建立一个对controller的weak引用，如果Controller被释放，这个weak引用也随之置为nil。那怎么知道子对象没有被释放呢？用一个单例对象每个一小段时间发出一个ping通知去ping这个子对象，如果子对象还活着就会一个pong通知。所以结论就是：如果子对象的controller已不存在，但还能响应这个ping通知，那么这个对象就是可疑的泄漏对象。完整的结构可以用下图表示：&lt;/p&gt;
  &lt;p&gt;   &lt;img alt="sniffer2.jpg" src="http://cc.cocimg.com/api/uploads/20160706/1467783782450752.jpg" title="1467783782450752.jpg"&gt;&lt;/img&gt;&lt;/p&gt;
  &lt;p&gt;通知移除需要一个时机，这里我们使用Associated Object机制给每一个子对象再生成一个Proxy对象，在Proxy对象的dealloc里面移除通知。&lt;/p&gt;
  &lt;p&gt;当然什么时候去判断一个对象的生命周期开始，什么时候判断为结束，需要一个精挑细选的机制。View，Controller，Property各不相同。&lt;/p&gt;
  &lt;p&gt;PLeakSniffer采取保守的策略，通过Objective C的runtime机制，递归的将一个Controller所有强引用的property找出，并安装proxy监听Ping通知。在我的测试下，基本上能将property泄漏的场景找出。&lt;/p&gt;
  &lt;p&gt;PLeakSniffer的使用方式很简答，通过Pod安装后，通过以下代码激活即可。&lt;/p&gt;
  &lt;div&gt;
   &lt;div&gt;
    &lt;table border="0" cellpadding="0" cellspacing="0"&gt;     &lt;tr&gt;
      &lt;td&gt;
       &lt;div&gt;1&lt;/div&gt;
       &lt;div&gt;2&lt;/div&gt;
       &lt;div&gt;3&lt;/div&gt;
       &lt;div&gt;4&lt;/div&gt;
&lt;/td&gt;
      &lt;td&gt;
       &lt;div&gt;
        &lt;div&gt;         &lt;code&gt;#if MY_DEBUG_ENV&lt;/code&gt;&lt;/div&gt;
        &lt;div&gt;         &lt;code&gt;[[PLeakSniffer sharedInstance] installLeakSniffer];&lt;/code&gt;&lt;/div&gt;
        &lt;div&gt;
         &lt;code&gt;[[PLeakSniffer sharedInstance] addIgnoreList:@[@&lt;/code&gt;         &lt;code&gt;&amp;quot;MySingletonController&amp;quot;&lt;/code&gt;         &lt;code&gt;]];&lt;/code&gt;
&lt;/div&gt;
        &lt;div&gt;         &lt;code&gt;#endif&lt;/code&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/td&gt;
&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;
  &lt;p&gt;addIgnoreList可以添加一些特殊的忽略名单，比如单例这种无法正确预测泄漏的对象。切记用Debug的宏将上述代码包住，不要把这些检测泄漏的代码带进线上环境。&lt;/p&gt;
  &lt;p&gt;如果检测到可疑泄漏，PLeakSniffer会在控制台打印一条日志：&lt;/p&gt;
  &lt;p&gt;   &lt;strong&gt;Controller泄漏：Detect Possible Controller Leak: %@&lt;/strong&gt;&lt;/p&gt;
  &lt;p&gt;   &lt;strong&gt;其他对象泄漏：Detect Possible Leak: %@&lt;/strong&gt;&lt;/p&gt;
  &lt;p&gt;更多的细节请查阅代码：   &lt;a href="https://github.com/music4kid/PLeakSniffer" target="_blank"&gt;GitHub地址&lt;/a&gt;。&lt;/p&gt;
&lt;/div&gt;
          
           &lt;br /&gt; &lt;br /&gt;
          
             &lt;a href="http://square10.iteye.com/blog/2325642#comments"&gt;已有   &lt;strong&gt;0&lt;/strong&gt; 人发表留言，猛击-&amp;gt;&amp;gt;  &lt;strong&gt;这里&lt;/strong&gt;&amp;lt;&amp;lt;-参与讨论&lt;/a&gt;
          
           &lt;br /&gt; &lt;br /&gt; &lt;br /&gt;
ITeye推荐
 &lt;br /&gt;
 &lt;ul&gt;  &lt;li&gt;   &lt;a href="http://www.iteye.com/clicks/433" target="_blank"&gt;—软件人才免语言低担保 赴美带薪读研！— &lt;/a&gt;&lt;/li&gt;&lt;/ul&gt;
 &lt;br /&gt; &lt;br /&gt; &lt;br /&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 />
      <guid isPermaLink="true">https://itindex.net/detail/55978-ios-%E5%86%85%E5%AD%98%E6%B3%84%E6%BC%8F-%E5%B7%A5%E5%85%B7</guid>
      <pubDate>Tue, 20 Sep 2016 14:39:29 CST</pubDate>
    </item>
    <item>
      <title>微软为Win10开发Android和iOS快速移植工具</title>
      <link>https://itindex.net/detail/53373-%E5%BE%AE%E8%BD%AF-win10-%E5%BC%80%E5%8F%91</link>
      <description>&lt;p&gt;　　在今天凌晨的Build  2015开发者大会上，微软宣布所有Android和iOS应用，都可以通过简单的修改代码，直接生成适用于Win10的应用。也就是说，开发者们不需要学习更多内容，就可以将自己的Android或iOS应用在Win10系统中运行。&lt;/p&gt; &lt;p&gt;　　微软在会上公布了Windows Store获得通用应用的四种方法：&lt;/p&gt; &lt;p&gt;　　第一，在线获取，用Web网页留下的链接，以webapp的形式轻松开始实现。&lt;/p&gt; &lt;p&gt;　　第二，.NET与Win32程序终于登陆Windows  Store了，现场演示Adobe的Photoshop。开发者们现在可以将当前的桌面应用带到Windows  Store上了。打通桌面程序后，Windows商店应用稀少的问题有望得到彻底解决。&lt;/p&gt; &lt;p&gt;　　第三，Android、Java、C++!大家将能够重用几乎所有Android手机应用上的Java和C++代码了！然后将之用于打造Windows  10应用。为了实现这一点，Windows Phones将包含一个“Android子系统”。&lt;/p&gt; &lt;p&gt;　　第四、Object C语言编译的iOS应用也可以转化成Windows 10应用！这意味着Xcode开发出的工程移植起来也很方便。&lt;/p&gt; &lt;p&gt;　　微软手机上目前应用太少，导致用户也少，微软此举是为了吸引开发者移植应用，降低移植应用的成本，从而增加Windows 10的应用量，以提升用户体验。&lt;/p&gt; &lt;p&gt;　　但分析师指出，在软件移植中会出现一种“最小公分母现象”（比喻大众化的产品最为平庸），那些最希望采用低成本手段，将软件移植到其他平台上的人，也是最不愿意在每一个平台上，将用户体验做到最精致的人。因此，微软的兼容和移植政策，无法吸引到安卓和iOS平台中最优秀的软件。&lt;/p&gt; &lt;p&gt;　　对于Android和iOS开发者来说，如果微软的工具能完美转化应用到Windows10平台，那么移植过去成本不高也是可以接受的。但是，软件开发商将一个应用移植到另一个平台，并非用一个工具就能解决，需要大量的开发测试和后期维护，对于开发商来说成本提高很多，但因为目前Windows10手机用户非常小，因此可能无法收回成本，因此指望这个工具让软件开发商大量移植应用可能并不实际。如果只是粗糙地重新编译后移植，应用里肯定会有各种各样的BUG和问题，导致用户使用体验下降，这反而会导致Windows10手机用户地降低。&lt;/p&gt; &lt;p&gt;　　因此可见，微软推动开发商移植Android和iOS应用到Windows10，对于目前的移动格局可能影响不大，马太效应会导致强者愈强、弱者愈弱，移动平台的格局已经被Android和iOS瓜分，微软想在分一杯羹的机会非常渺茫。&lt;/p&gt; &lt;p&gt;  &lt;a href="http://www.williamlong.info/archives/4220.html" target="_blank"&gt;评论《微软为Win10开发Android和iOS快速移植工具》的内容...&lt;/a&gt;&lt;/p&gt; &lt;h3&gt;相关文章:&lt;/h3&gt; &lt;ul&gt;  &lt;li&gt;   &lt;a href="http://www.williamlong.info/archives/4178.html"&gt;盗版Windows升级到Windows 10后仍然属于盗版 &lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;   &lt;a href="http://www.williamlong.info/archives/4177.html"&gt;微软联手360、腾讯免费升级Windows 10&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;   &lt;a href="http://www.williamlong.info/archives/4165.html"&gt;Windows网站架构服务器的优劣&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;   &lt;a href="http://www.williamlong.info/archives/4110.html"&gt;Windows Server操作系统安装教程&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;   &lt;a href="http://www.williamlong.info/archives/3998.html"&gt;微软发布Windows 10操作系统&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt; &lt;br /&gt;微博： &lt;a href="http://weibo.com/williamlong"&gt;新浪微博&lt;/a&gt; - 微信公众号：williamlonginfo  &lt;br /&gt;月光博客投稿信箱：williamlong.info(at)gmail.com &lt;br /&gt;Created by William Long www.williamlong.info &lt;br /&gt; &lt;img alt="&amp;#26376;&amp;#20809;&amp;#21338;&amp;#23458;" src="http://www.williamlong.info/images/qrcode.jpg"&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>互联网络</category>
      <guid isPermaLink="true">https://itindex.net/detail/53373-%E5%BE%AE%E8%BD%AF-win10-%E5%BC%80%E5%8F%91</guid>
      <pubDate>Thu, 30 Apr 2015 00:16:42 CST</pubDate>
    </item>
  </channel>
</rss>

