ZCash零知识证明

交易过程完全匿名是数字货币ZCash最大的亮点,正是这一点使得ZCash自提出以来便备受关注。ZCash匿名交易的实现依赖于一种叫做“零知识证明”的密码学手段。本文将通过打比方的手法,用通俗的语言,解释清楚ZCash的交易原理,以及零知识证明是如何运用到ZCash交易过程中的。
本文的嘉宾是数字货币界最著名CP:Alice和Bob。
一、从比特币说起
直接讲解ZCash的交易过程可能会比较抽象。为了有助于理解,我们不妨先分析比特币,作为铺垫。
我们先来打个比方说明比特币的转账原理。
演示场景:Alice转1个比特币给Bob。
转账前,Alice要事先准备1个比特币。为了方便理解,我们把Alice准备转出的这1个比特币看成一张面额为1个比特币的“支票”,如图1。


图1
从这张“支票”中我们可以获取到如下信息:

  1. Alice确实拥有1个BTC。
  2. Alice使用私钥对这张支票签名,证明Alice拥有对这笔资产转账的权力。
    支票的面额和转账权都已经明确,Alice就可以给Bob转账了。转账的原理很简单,就是给Bob新建一张一样的“支票”,证明Bob拥有了1个比特币。同时撕掉Alice手中的“支票”,通过这“破旧”并“立新”的方式,实现资产所有权的转移。如图2。


图2
以上逻辑其实不难理解,因为这和日常生活中的银行转账是一个道理。通过银行转账,我们在交易时不必对实物货币进行转移,而是以银行记账的方式,实现“资产所有权”的转移。比特币交易的过程实质上就是一个“资产所有权”的转移过程,转入比特币的那一方“新建”一份资产所有权,而转出方需要“销毁”原先的资产所有权,被销毁的那张“支票”永远不会再出现。
二、ZCash的转账原理
与比特币一样,ZCash的交易过程也是 “资产所有权”的转移。继续沿用前文“支票”的比方。
演示场景:Alice转1个ZEC给Bob。
转账前,Alice创建一张面额为1个ZEC的“支票”,如图3。


图3
能从该凭证中获取的信息:

  1. Alice确实拥有1个ZEC。
  2. Alice使用私钥对这张支票签名,证明Alice拥有对这笔资产转账的权力。
  3. 这张“凭证”上多了一串随机数,用符号 r 表示。这串随机数的作用好比“支票代号”,用来唯一识别该支票。Alice的“支票代号”为r1。
    明确以上信息,Alice就可以进行ZEC转账了。
    第一步:比特币一样,要先为Bob新建一张“支票”。Bob的支票代号(r2)与Alice的支
    票代号(r1)不相同,如图4。


图4
第二步:新的“资产所有权”生成的同时,必须要想办法销毁原来的“资产所有权”。即必须想办法让Alice手中的“支票”失效。与比特币简单粗暴的“直接撕毁”不同,ZCash采用“备注作废”的手段,达到同样的效果。怎么理解呢?就是在不对原先“支票”作任何处理的前提下,新建一个作废文件列表,录入需要作废的“发票代号”。如图5,


图5
从上图可以看出,原先的Alice持有的支票仍旧存在,并没有消失,只是这张支票已经被记入“作废列表”。在确定资产所有权时要同时读取两个列表的信息,能确定Bob拥有资产所有权的判断方法是:作废列表中不存在Bob所持“支票”的代号。
可是为什么要这样设计呢?其实这样设计的目的是为了在交易过程中运用 “零知识证明”。
三、零知识证明
什么是零知识证明?
零知识证明 (被称为“zk-SNARK”)是实现Zcash的匿名特性的核心技术。“零知识证明”的定义是:证明者能够在不向验证者提供任何有用的信息的情况下,使验证者相信某个论断是正确的。举个简单的例子:
A要向B证明自己拥有某个房间的钥匙,假设该房间只能用钥匙打开锁,而其他任何方法都打不开。这时有2个方法:
(一)A把钥匙出示给B,B用这把钥匙打开该房间的锁,从而证明A拥有该房间的正确的钥匙。
(二)B确定该房间内有某一物体,A用自己拥有的钥匙打开该房间的门,然后把物体拿出来出示给B,从而证明自己确实拥有该房间的钥匙 。
后面这个方法属于零知识证明。好处在于在整个证明的过程中,B始终不能看到钥匙的样子,从而避免了钥匙的泄露。
那么零知识证明怎么运用到ZCash交易过程中呢?
我们再回顾比特币和ZCash的例子。
Alice要向Bob转一个单位的数字货币(BTC/ZEC),即Alice要向Bob转移一个单位的资产所有权。这时有以下两个方法:
(一)比特币中的做法:Alice拥有一张1BTC的支票,要转账给Bob时,先给Bob新建一张1BTC的支票,同时当着Bob的面将自己原先的支票撕毁。
(二)ZCash中的做法:Alice拥有一张1ZEC的支票,要转账给Bob时,先给Bob新建一张1ZEC的支票,然后在一张约定有效的作废列表中,记录下Alice的发票的代号,证明Alice的支票已经失效。
ZCash的方法属于零知识证明。整个交易过程中,Bob并没有见过Alice的支票,但是还是实现了资产所有权的转移。在ZCash的整个交易系统中,Alice和Bob的交易还有其他见证者,即负责记录交易信息的矿工。同样道理,矿工也不必看到Alice的支票,只要能确定代号为r1的支票已经作废了就行。
四、ZCash完整的匿名交易系统
有了上述铺垫,就可以进一步解释ZCash的匿名交易过程了。
还是那个例子:Alice转1 个ZEC给Bob。这个例子中有涉及到的角色有转账双方Alice和Bob,以及记账者(矿工)。
首先是Alice和Bob都有了一张支票,如图6。


图6
这两张“支票”都是有效的。Alice的支票开始就存在于整个ZCash网络,Bob的支票在生成后也会被广播到全网。
为了隐藏交易者信息,要对两张支票进行加密处理。在全网中存在的“支票”其实是这样子的,如图7。

信息都是被加密的,可以通过拥有者的私钥解密

图7
同时,因为资产只能有一份,所有矿工手里还有一个作废列表。Alice要同时广播自己的“发票代号”,录入作废列表中。发票代号也是加密的。所以矿工们能看到的信息其实是这样的。其中Alice的支票是原先存在的,Alice的支票代号r1和Bob的支票是在交易过程中被Alice广播的。如图8。


图8
矿工们能获取的信息相当有限,但是这并不影响对矿工对交易有效性的判断。
判断的逻辑相当简单:矿工拿到Alice给的支票代号r1,去作废列表中检索,假如作废列表中已经存在r1,则证明r1所对应的的支票早已失效;若作废列表中并不存在r1,则证明r1对应的支票仍旧有效,此时矿工把r1录入作废列表中,把新生成的支票录入支票列表中。所以记账的过程就是对原有支票登记失效,并存入现有支票的过程。
在这个过程中,我们不难发现,每笔交易矿工能接收到的东西只有一个发票代号,和一张新的发票,而且这两样东西都是被加密的。所以矿工并不知道转账双方是谁,也不知道转账金额是多少。
五、数据库
其实有心人可以发现,按照上文的思路,能写一个用于ZCash匿名交易的数据库。笔者后续的文章中会另起一文专门写数据库的构建。
本文为雷盈特邀作者Carlos原创文章,雷盈(公众号:RadarWinChina)是上海区块链标杆企业,为区块链全产业链提供技术与咨询服务

10/17/2017 09:42 上午 posted in  BlockChain

关于Xcode “Build Setting”中的Architectures详解

##基本概念

  • ARM架构,是一种低成本、高性能、低耗电处理器架构,目前广泛的在移动通信领域中使用。

  • ARM处理器指令集
    苹果A7处理器支持两个不同的指令集:32位ARM指令集(armv6|armv7|armv7s)和64位ARM指令集(arm64)。
    i386|x86_64 是Mac处理器的指令集,i386是针对intel通用微处理器32架构的。x86_64是针对x86架构的64位处理器。当使用iOS模拟器的时候会遇到i386|x86_64,iOS模拟器没有arm指令集。

  • 目前iOS移动设备指令集
    ARMv8/ARM64: iPhone 6(Plus), iPhone 5s, iPad Air(2), Retina iPad Mini(2,3)
    ARMv7s: iPhone 5, iPhone 5c, iPad 4
    ARMv7: iPhone 3GS, iPhone 4, iPhone 4S, iPod 3G/4G/5G, iPad, iPad 2, iPad 3, iPad Mini
    ARMv6: iPhone, iPhone 3G, iPod 1G/2G
    “Build Setting” 中Architecture详解

  • Architectures
    指定工程支持的指令集的集合,如果设置多个architecture,则生成的二进制数据包会包含多个指令集代码,体积会变大。

  • Valid Architectures
    有效的指令集集合,Architectures与Valid Architectures 的交集来确定最终的数据包包含的指令集代码。

  • Build Active Architecture Only
    指定是否只对当前连接设备所支持的指令集编译,默认Debug的时候设置为YES,Release的时候设置为NO。设置为YES是只编译当前的architecture版本,生成的包只包含当前连接设备的指令集代码。设置为NO,则生成的包包含所有的指令集代码(上面的Valid Architectures跟Architectures的交集)。因此为了调试速度更快,则Debug应该设置为YES。
    特殊:设置此值为YES,如果连接的设备是arm64的( iPhone 5s,iPhone6(plus)等),则Valid Architecture 中必须包含arm64, 否则编译会报错(报错的内容在下面常见问题中)。
    总结一下,按照新版Xcode6.1.1的默认设置就没有任何问题,之前的Xcode版本开发的项目也建议升级到Xcode6.1.1,同样按照如下配置:

  1. Architectures 默认设置为 Standard architectures(armv7,arm64) 。(之前的Xcode版本中默认设置还包括armv7s,现在去掉了,估计是因为armv7s相较armv7优化不大,能向下兼容,而设置后还会增大二进制包的大小)
  2. Valid Architectures 默认设置为 armv7 armv7s arm64。
  3. Build Active Architecture Only:默认Debug的时候设置为YES,Release的时候设置为NO。
    ##App适配64位
  • 为什么要适配64位?
    1: 苹果官方要求: 2015年2月1日起,提交到AppStore的新应用必须支持64bit,6月1日起,更新的app也必须支持64bit。详见官方文档。
    2: 为了提升性能:目前A7使用的是ARM V8架构,除了使用64位的地址总线和64位的寄存器以外,还增加了寄存器的数量,目前A7中的整数和浮点数寄存器是A6的两倍。寄存器的增加提高了程序的运行速度。详见官方文档。再者,64位系统为了兼容32位的程序,64位的iOS系统中带有两套Framework,一套32位的,一套64位的。当64位的iOS系统运行原来的32位程序时,系统会调用32位的Framework作为底层支撑,当系统运行64位程序时,系统会调用64位的Framework作为底层支撑,所以如果app不做64位适配的话,系统会调用32位Framework,消耗内存增多,运行速度会变慢。
  • 如何适配64位?
    1:Xcode版本号>5.0.1。(建议升级到最新版本)
    2:minimum deployment target > 5.1.1。
    3:按照上述Xcode6的默认配置进行配置,其他Xcode版本最好也进行如此配置。
    4:运行测试代码,解决编译warnings and errors,对照本文档或者官方文档 64-Bit Transition Guide for Cocoa Touch对相应地方做出修改。(编译器不能告诉我们一切)
    5:在真实的64-bit机器上测试。
    6:使用Instruments查看内存使用问题。

此贴只进行简要介绍,具体详见iOS工程如何支持64-bit或者苹果官方文档
##关于Architecture的常见问题

  1. 编译报错 No architectures to compile for (ONLY_ACTIVE_ARCH=YES, active arch=x86_64, VALID_ARCHS=i386).

问题起因:active architecture(当前连接设备的指令集)为64位指令集,但是valid architecture只包含32位指令集
解决方法:valid architecture增加arm64 (常见的一些帖子的解决方案是把Build Active Architecture Only设置为NO,这是个简单粗暴的解决办法,在Debug过程中也会生成包含所有指令集的代码,更何况现在官方强制必须支持64位,故不建议采纳)
2. No architectures to compile for (ONLY_ACTIVE_ARCH=YES, active arch=i386, VALID_ARCHS=x86_64).

问题起因:跟上个问题同理,只不过连接的设备指令集是32位指令集,但是valid architecture只包含64位指令集
解决办法:valid architecture增加armv7
3. App在打包submit到Appstore中,提示需要支持64bit,或者在App提交完成后Apple发Missing 64-bit support 内容的邮件给开发者。

解决办法: App按照上述适配64位的步骤进行适配。
4. 导入静态库后编译报错为

问题起因:1:可能是静态库中不包含这个类。
2:静态库工程可能没有链接到应用。
3:可能是因为静态库(.a)为真机版本,不包含模拟器版本(i386)。
解决办法:1:查看静态库里面是否存在这个类。
2:Build Phases中没有添加Link Binary With Libraries 中添加此静态库。
3:把调试目标换成真机 或者 导入一个模拟器版本跟真机版本合并的版本
同理如果导入的库是模拟器版本而用真机调试也会报错,所以导入一个通用的版本是目前来讲最好的办法。
5. 导入静态库后编译报错为

问题起因:1:可能原因同上,因为静态库不包含报错的类
2:可能原因同上,因为静态库程可能没有链接到应用。
3:可能是因静态库不支持64位
解决办法:排除前两个可能原因后,来看第三个起因的解决办法:查看静态库的提供方是否有更新支持64位,如果最新的静态库依然不支持64位的话,则需要获取其源码重新打包静态库,valid architecture中添加arm64。
详细的解决办法参考stackoverflow。如果不能获得源代码的话,鉴于目前苹果官方需要支持64位,所以只能换别的静态库了,否则联系开发者进行静态库更新。
##参考文档
1: 苹果官方文档对于”Build Setting”的介绍
2: iOS开发~制作同时支持armv7,armv7s,arm64,i386,x86_64的静态库.a
3: iOS工程如何支持64-bit
4: Xcode的Architecture和Valid Architecture的区别
5: 64-bit Transition Guide For Cocoa Touch

09/27/2017 13:16 下午 posted in  Xcode

使用CIColorCube快速製作濾鏡

转载:http://huangtw-blog.logdown.com/posts/176980-ios-quickly-made-using-a-cicolorcube-filter

如果想要再iOS的App內製作照片濾鏡,可以使用Core Image Framework內一系列的CIFilter來實作。CIFilter提供了可以調整亮度、對比等等各種特效的濾鏡。不過如果能直接用Photoshop等影像處理軟體,直接調整這些色彩參數,可以大幅加快開發速度。

本文將介紹透過CIFilter中的CIColorCube來套用Color Lookup Table,快速開發色彩濾鏡。

本文的程式原始碼可在以下網址取得
https://github.com/huangtw/ColorLUT

##Color Lookup Table

在影像處理的領域中,當我們想要調整一個影像的色彩時,時常會用到Color Lookup Table(簡稱ColorLUT)的技術。

舉個簡單的例子,如果我們想要讓影像中每個像素的R值變成原本的0.3倍,最基本的作法就是把每一個像素的R值乘以0.3,假設影像的大小為1024x768,那麼總共要786432次浮點數乘法。

如果我們一開始先建一張表,把所有色彩值經過處理(R值變為0.3倍)之後的結果記錄起來,然後把每個像素的色彩值拿去查表,得到處理之後的色彩值,那麼我們只要做786432查表動作,會比浮點運算快上許多。實際上大部分色彩調整的演算法都比這個例子複雜許多,因此更能凸顯出查表法的高效率。

不過如果要把所有色彩的處理結果都存起來,那這張表勢必會相當肥大。以RGB 24 bits為例,每個像素佔3 byte,而總共會有16777216(256x256x256)種色彩,總共佔48MB(256x256x256x3 bytes),看來並不小。因此實務上我們並不會記下所有的色彩,而是只記下部分的色彩,其他不在表內的色彩則用內插法取得處理後的結果。

因為每個像素的色彩都是由RGB三種顏色組成,因此我們會以三維陣列的方式來儲存這張表。如果把三維陣列中的每一個像素想像成三度空間中的一個點,而R、G、B分別代表X、Y、Z的座標,則陣列中的所有像素可以構成一個正立方體。以RGB 24bits為例,由於R、G、B的值為0~255,因此正方體的長寬高為255x255x255。當我們要查某個像素經過處理之後的色彩,只要將該像素處理前的RGB值當做X、Y、Z座標,位於那個位置上的像素則為處理後的顏色。

如前面提到的,為了節省大小,我們不可能提供所有的點,因此我們會使用內差法來推算。以4x4x4的ColorLUT為例,X、Y、Z三個方向都只有在0、85、170、255這些位置有資料,如果座標不在這些點上,則必須跟週圍的點做內差來得到色彩的近似值,由於是在三維空間內,因此可以透過三線性內差(Trilinear Interpolation)來計算。

##CIColorCube

在iOS App內可以透過Core Image Framework內的CIColorCube實作Color Lookup Table。使用CIColorCube時必須設定:

  • inputCubeDimension: 也就是正立方體每邊包含幾個點。以4x4x4的ColorLUT為例,則inputCubeDimension=4
  • inputCubeData: NSData物件,儲存每個點的RGBA資訊。每個點的R、G、B、A值皆為float(0~1.0),點的儲存順序為:從Z=0的平面開始,從Y=0的列開始,儲存X=0~X=255的點,後面接著Y=1的列 ,接著再儲存Z=1的平面,以此類推。以4x4x4的ColorLUT為例,順序為(0,0,0)、(85,0,0)、(170,0,0)、(255,0,0)、(0,85,0)、(85,85,0)...(0,255,255)、(85,255,255)、(170,255,255)、(255,255,255)

那我們怎麼產生inputCubeData內的資料呢?我們可以先將每個點原本對應的顏色先存在一張圖片內,然後透過Photohop之類的影像處理軟體,套用各種色彩特效到這張圖片,之後App讀取調整過後的圖片,根據每個像素的位置,將色彩資訊填進inputCubeData,之後套用CIColorCube的影像就會跟直接透過Photoshop調整出來的色彩有一樣的效果。

這樣講實在有點抽象,我們以4x4x4的ColorLUT為例,經過Photoshop調整色彩前如下:

可以看到這是一張8x8的圖片,分別包含了4個 4x4的正方形,以右上角正方形為例,這代表Z=0的平面,而X軸由左至右,Y軸為由上至下,左上角第一個像素代表位於(0,0,0)的點,第二個像素代表位於(85,0,0)的點,以此類推,由於這些像素代表的是未處理前的顏色,因此第一個像素的RGB值為0,0,0,第二的像素的RGB值為85,0,0
接著我們將這張圖經過Photoshop降低色彩飽和度,結果如下:

這張圖內的每個像素,代表每個座標點處理之後的色彩。也就是說,如果我們想知道一個RGB值為85,0,0的像素降低飽和度之後的顏色,可從正立方體中位於(85,0,0)的點得知,也就是這張圖中左上角第二個像素的顏色,RGB值為68,16,16。
因此我們只要將第二張圖片中每個像素的RGB值換算成浮點數之後,依序填入NSData中,產生的CIColorCube就可以做出跟Photoshop中一樣的色彩濾鏡。

##CIFilter+ColorLUT

由於使用CIColorCube製作ColorLUT過程有點繁瑣,因此我透過Objective-C的Category機制替CIFiliter擴充一個class method來快速產生ColorLUT

CIFilter+ColorLUT.h

#import <CoreImage/CoreImage.h>

@interface CIFilter (ColorLUT)
+ (CIFilter *)colorCubeWithColrLUTImageNamed:(NSString *)imageName dimension:(NSInteger)n;
@end

透過+ (CIFilter *)colorCubeWithColrLUTImageNamed:(NSString *)imageName dimension:(NSInteger)n這個class method,我們只要提供colorLUT的檔名imageName,以及立方體的邊長n,它就會自動讀取圖檔,將圖檔內的每一個像素填入NSData中,回傳一個由此ColorLUT產生的CIColorCube

CIFilter+ColorLUT.m*

@implementation CIFilter (ColorLUT)

+ (CIFilter *)colorCubeWithColrLUTImageNamed:(NSString *)imageName dimension:(NSInteger)n
{
    UIImage *image = [UIImage imageNamed:imageName];

    int width = CGImageGetWidth(image.CGImage);
    int height = CGImageGetHeight(image.CGImage);
    int rowNum = height / n;
    int columnNum = width / n;

    //檢查圖片大小,必須是n個n*n的正方形,所以長跟寬都必須是n的倍數
    if ((width % n != 0) || (height % n != 0) || (rowNum * columnNum != n))
    {
        NSLog(@"Invalid colorLUT");
        return nil;
    }

    //要讀取每個pixel的RGBA值,必須先把UIImage轉成bitmap  
    unsigned char *bitmap = [self createRGBABitmapFromImage:image.CGImage];
    
    if (bitmap == NULL)
    {
        return nil;
    }

    //將每個pixel的RGBA值填入data中
    //圖片由n個正方形構成,每個正方形代表不同Z值的XY平面
    //正方形內每個pixel的X值有左至右遞增,Y值由上至下遞增
    //而每個正方形所代表的Z值由左至右、由上而下遞增
    int size = n * n * n * sizeof(float) * 4;
    float *data = malloc(size);
    int bitmapOffest = 0;
    int z = 0;
    for (int row = 0; row <  rowNum; row++)
    {
        for (int y = 0; y < n; y++)
        {
            int tmp = z;
            for (int col = 0; col < columnNum; col++)
            {
                for (int x = 0; x < n; x++) {
                    float r = (unsigned int)bitmap[bitmapOffest];
                    float g = (unsigned int)bitmap[bitmapOffest + 1];
                    float b = (unsigned int)bitmap[bitmapOffest + 2];
                    float a = (unsigned int)bitmap[bitmapOffest + 3];
                    
                    int dataOffset = (z*n*n + y*n + x) * 4;

                    data[dataOffset] = r / 255.0;
                    data[dataOffset + 1] = g / 255.0;
                    data[dataOffset + 2] = b / 255.0;
                    data[dataOffset + 3] = a / 255.0;

                    bitmapOffest += 4;
                }
                z++;
            }
            z = tmp;
        }
        z += columnNum;
    }

    free(bitmap);

    //產生一個CIColorCube,並且將data包裝成NSData設進CIColorCube中
    CIFilter *filter = [CIFilter filterWithName:@"CIColorCube"];
    [filter setValue:[NSData dataWithBytesNoCopy:data length:size freeWhenDone:YES] forKey:@"inputCubeData"];
    [filter setValue:[NSNumber numberWithInteger:n] forKey:@"inputCubeDimension"];

    return filter;
}

+ (unsigned char *)createRGBABitmapFromImage:(CGImageRef)image
{
    CGContextRef context = NULL;
    CGColorSpaceRef colorSpace;
    unsigned char *bitmap;
    int bitmapSize;
    int bytesPerRow;
    
    size_t width = CGImageGetWidth(image);
    size_t height = CGImageGetHeight(image);
    
    bytesPerRow   = (width * 4);
    bitmapSize     = (bytesPerRow * height);
    
    bitmap = malloc( bitmapSize );
    if (bitmap == NULL)
    {
        return NULL;
    }
    
    colorSpace = CGColorSpaceCreateDeviceRGB();
    if (colorSpace == NULL)
    {
        free(bitmap);
        return NULL;
    }
    
    context = CGBitmapContextCreate (bitmap,
                                     width,
                                     height,
                                     8,
                                     bytesPerRow,
                                     colorSpace,
                                     kCGImageAlphaPremultipliedLast);
    
    CGColorSpaceRelease( colorSpace );
    
    if (context == NULL)
    {
        free (bitmap);
    }
    
    CGContextDrawImage(context, CGRectMake(0, 0, width, height), image);  
    CGContextRelease(context);
    
    return bitmap;
}

@end

ColorLUTViewController.m

#import "ColorLUTViewController.h"
#import "CIFilter+ColorLUT.h"

@interface ColorLUTViewController ()
@property (nonatomic, weak) IBOutlet UIImageView *imageView1;
@property (nonatomic, weak) IBOutlet UIImageView *imageView2;
@end

@implementation ColorLUTViewController

- (void)viewDidLoad
{
    [super viewDidLoad];

    UIImage *image = [UIImage imageNamed:@"sunflower.jpg"];

    //使用colorLUTProcessed.png產生一個CIColorCube
    CIFilter *colorCube = [CIFilter colorCubeWithColrLUTImageNamed:@"colorLUTProcessed.png" dimension:64];

    //設定要處理的圖片
    CIImage *inputImage = [[CIImage alloc] initWithImage: image];
    [colorCube setValue:inputImage forKey:@"inputImage"];

    //取得處理後的圖片,不過此時還沒真的處理,先取得outputImage稍後使用
    CIImage *outputImage = [colorCube outputImage];

    CIContext *context = [CIContext contextWithOptions:[NSDictionary dictionaryWithObject:(__bridge id)(CGColorSpaceCreateDeviceRGB()) forKey:kCIContextWorkingColorSpace]];

    //此時才真的產生處理之後的圖片
    UIImage *newImage = [UIImage imageWithCGImage:[context createCGImage:outputImage fromRect:outputImage.extent]];

    [self.imageView1 setImage:image];
    [self.imageView2 setImage:newImage];
}

@end

左邊為Photoshop調整之前的圖片,右邊為Photoshopt調整顏色之後的照片
接著我們將一樣的色彩濾鏡套用到64x64x64的colorLUT圖

左邊為套用Photoshop色彩濾鏡之前的colorLUT,右邊為套用色彩濾鏡之後的colorLUT

這是透過CIColorCube處理的結果。可以看出效果跟Photoshop處理的幾乎一模一樣
透過這種方式來開發濾鏡,可以使用美術人員熟悉的Photoshop等軟體調整濾鏡的色彩,在直接套到colorLUT圖之後即可供程式使用,非常方便

Subclassing CIFilter: Recipes for Custom Effects

08/01/2017 09:18 上午 posted in  Filter

iOS开发之VPN协议(理论)

作者:dengshuai_super
出处:
http://blog.csdn.net/dengshuai_super/article/details/51872474

1 概述

虚拟专用网(英语:Virtual Private Network,简称VPN),是一种常用于连接中、大型企业或团体与团体间的私人网络的通讯方法。虚拟私人网络的讯息透过公用的网络架构(例如:互联网)来传送内联网的网络讯息。它利用已加密的通道协议(Tunneling Protocol)来达到保密、发送端认证、消息准确性等私人消息安全效果。这种技术可以用不安全的网络(例如:互联网)来发送可靠、安全的消息。需要注意的是,加密消息与否是可以控制的。没有加密的虚拟专用网消息依然有被窃取的危险。

以日常生活的例子来比喻,虚拟专用网就像:甲公司某部门的A想寄信去乙公司某部门的B。A已知B的地址及部门,但公司与公司之间的信不能注明部门名称。于是,A请自己的秘书把指定B所属部门的信(A可以选择是否以密码与B通信)放在寄去乙公司地址的大信封中。当乙公司的秘书收到从甲公司寄到乙公司的信件后,该秘书便会把放在该大信封内的指定部门信件以公司内部邮件方式寄给B。同样地,B会以同样的方式回信给A。

在以上例子中,A及B是身处不同公司(内部网路)的计算机(或相关机器),通过一般邮寄方式(公用网络)寄信给对方,再由对方的秘书(例如:支持虚拟专用网的路由器或防火墙)以公司内部信件(内部网络)的方式寄至对方本人。请注意,在虚拟专用网中,因应网络架构,秘书及收信人可以是同一人。许多现在的操作系统,例如Windows及Linux等因其所用传输协议,已有能力不用通过其它网络设备便能达到虚拟专用网连接。--维基百科

iPhone上设置–>通用–>VPN–>添加VPN配置–>类型中,有四种协议:IKEV2,IPSec,L2TP和PPTP。

2.1 PPTP:

PPTP(Point to Point Tunneling Protocol),即点对点隧道协议。该协议是在PPP协议(点对点协议(Point to Point Protocol))的基础上开发的一种新的增强型安全协议,支持多协议虚拟专用网(VPN),可以通过密码验证协议(PAP)、可扩展认证协议(EAP)等方法增强安全性。可以使远程用户通过拨入ISP、通过直接连接Internet或其他网络安全地访问企业网。—百度百科

点对点隧道协议(英语:Point to Point Tunneling Protocol,缩写为PPTP)是实现虚拟专用网(VPN)的方式之一。PPTP使用传输控制协议(TCP)创建控制通道来发送控制命令,以及利用通用路由封装(GRE)通道来封装点对点协议(PPP)数据包以发送数据。这个协议最早由微软等厂商主导开发,但因为它的加密方式容易被破解,微软已经不再建议使用这个协议。—维基百科

PPTP的协议规范本身并未描述加密或身份验证的部分,它依靠点对点协议(PPP)来实现这些安全性功能。因为PPTP协议自带在微软视窗系统家族的各个产品中,在微软点对点协议(PPP)协议堆栈中,提供了各种标准的身份验证与加密机制来支持PPTP 。 在微软视窗系统中,它可以搭配PAP、CHAP、MS-CHAP v1/v2或EAP-TLS来进行身份验证。通常也可以搭配微软点对点加密(MPPE)或IPSec的加密机制来提高安全性。—维基百科

2.2 L2TP

第二层隧道协议(英语:Layer Two Tunneling Protocol,缩写为L2TP)是一种虚拟隧道协议,通常用于虚拟专用网。L2TP协议自身不提供加密与可靠性验证的功能,可以和安全协议搭配使用,从而实现数据的加密传输。经常与L2TP协议搭配的加密协议是IPsec,当这两个协议搭配使用时,通常合称L2TP/IPsec。

L2TP支持包括IP、ATM、帧中继、X.25在内的多种网络。在IP网络中,L2TP协议使用注册端口UDP 1701。[1]因此,在某种意义上,尽管L2TP协议的确是一个数据链路层协议,但在IP网络中,它又的确是一个会话层协议。—维基百科

L2TP是一种工业标准的Internet隧道协议,功能大致和PPTP协议类似,比如同样可以对网络数据流进行加密。不过也有不同之处,比如PPTP要求网络为IP网络,L2TP要求面向数据包的点对点连接;PPTP使用单一隧道,L2TP使用多隧道;L2TP提供包头压缩、隧道验证,而PPTP不支持。—百度百科

在VPN连接中要设置L2TP连接,方法同PPTPVPN设置,同样是在VPN连接属性窗口的“网络”选项卡中,将VPN类型设置为“L2TP IPSec VPN”即可。
第二层隧道协议(L2TP)是用来整合多协议拨号服务至现有的因特网服务提供商点。PPP 定义了多协议跨越第二层点对点链接的一个封装机制。特别地,用户通过使用众多技术之一(如:拨号 POTS、ISDN、ADSL 等)获得第二层连接到网络访问服务器(NAS),然后在此连接上运行 PPP。在这样的配置中,第二层终端点和 PPP 会话终点处于相同的物理设备中(如:NAS)。
L2TP 扩展了 PPP 模型,允许第二层和 PPP 终点处于不同的由包交换网络相互连接的设备来。通过 L2TP,用户在第二层连接到一个访问集中器(如:调制解调器池、ADSL DSLAM 等),然后这个集中器将单独得的 PPP 帧隧道到 NAS。这样,可以把 PPP 包的实际处理过程与 L2 连接的终点分离开来。—百度百科

L2TP协议结构:

L2TP直观示意图:

2.2.1 与PPTP比较:

PPTP和L2TP都使用PPP协议对数据进行封装,然后添加附加包头用于数据在互联网络上的传输。尽
管两个协议非常相似,但是仍存在以下几方面的不同:

1.PPTP要求互联网络为IP网络。L2TP只要求隧道媒介提供面向数据包的点对点的连接。L2TP可以在IP(使用UDP),帧中继永久虚拟电路(PVCs),X.25虚拟电路(VCs)或ATM VCs网络上使用。
2.PPTP只能在两端点间建立单一隧道。L2TP支持在两端点间使用多隧道。使用L2TP,用户可以针对不同的服务质量创建不同的隧道。
3.L2TP可以提供包头压缩。当压缩包头时,系统开销(overhead)占用4个字节,而PPTP协议下要占用6个字节。
4.L2TP可以提供隧道验证,而PPTP则不支持隧道验证。但是当L2TP或PPTP与IPSEC共同使用时,可以由IPSEC提供隧道验证,不需要在第2层协议上验证隧道
5.L2TP访问集中器(L2TP Access Concentrator,LAC)是一种附属在网络上的具有PPP端系统和L2Tpv2协议处理能力的设备,它一般就是一个网络接入服务器软件,在远程客户端完成网络接入服务的功能。
6.L2TP网络服务器(L2TP Network Server,LNS)是用于处理L2TP协议服务器端的软件。

L2TP支持的协议:
IP协议、IPX协议和NetBEUI协议

2.3 IPsec

互联网安全协议(英语:Internet Protocol Security,缩写为IPsec),是一个协议组合,透过对IP协议的分组进行加密和认证来保护IP协议的网络传输协议族(一些相互关联的协议的集合)。

IPsec由两大部分组成:(1)创建安全分组流的密钥交换协议;(2)保护分组流的协议。前者为因特网密钥交换(IKE)协议。后者包括加密分组流的封装安全载荷协议(ESP协议)或认证头协议(AH协议)协议,用于保证数据的机密性、来源可靠性(认证)、无连接的完整性并提供抗重播服务。

IPsec协议工作在OSI模型的第三层,使其在单独使用时适于保护基于TCP或UDP的协议(如安全套接子层(SSL)就不能保护UDP层的通信流)。这就意味着,与传输层或更高层的协议相比,IPsec协议必须处理可靠性和分片的问题,这同时也增加了它的复杂性和处理开销。相对而言,SSL/TLS依靠更高层的TCP(OSI的第四层)来管理可靠性和分片。

2.3.1 IPsec做什么?

IPsec通过使一个系统提供在IP层的安全服务来选择所需的安全协议,确定算法(多个)到用于服务(s)和到位的任何加密密钥,以提供所请求的服务所需的。IPsec的可用于保护一对主机之间的一个或多个的“路径”,一对之间的安全网关,或一个安全网关和一个主机之间。(该术语“安全网关”用于整个的IPsec文件指的是实现IPsec协议的中间系统。对于例如,路由器或实施的IPsec防火墙是一个安全网关。)

一组安全服务的IPsec可以提供包括访问控制,无连接的完整性,数据源认证,拒绝播放包的(部分序列完整性的一种形式),保密性(加密)和有限的流量保密。因为在IP层提供这些服务,它们可通过任何更高层协议,例如,TCP,UDP,可使用的ICMP,BGP等

IPsec DOI也支持IP压缩协商[ SMPT98 ],由部分动机观察当使用加密功能,从而在IPsec的,它较低的协议阻止有效压缩层。----维基百科

IPsec VPN
指采用IPSec协议来实现远程接入的一种VPN技术,IPSec全称为Internet Protocol Security,是由Internet Engineering Task Force (IETF) 定义的安全标准框架,用以提供公用和专用网络的端对端加密和验证服务。

2.3.2 导入协议原因

导入IPSEC协议,原因有2个,一个是原来的TCP/IP体系中间,没有包括基于安全的设计,任何人,只要能够搭入线路,即可分析所有的通讯数据。IPSEC引进了完整的安全机制,包括加密、认证和数据防篡改功能。
另外一个原因,是因为Internet迅速发展,接入越来越方便,很多客户希望能够利用这种上网的带宽,实现异地网络的的互连通。
IPSEC协议通过包封装技术,能够利用Internet可路由的地址,封装内部网络的IP地址,实现异地网络的互通。

2.3.3 包封装协议

设想现实一种通讯方式。假定发信和收信需要有身份证(成年人才有),儿童没有身份证,不能发信收信。有2个儿童,小张和小李,他们的老爸是老张和老李。现在小张和小李要写信互通,怎么办?
一种合理的实现方式是:小张写好一封信,封皮写上 “小张–>小李”, 然后给他爸爸,老张写一个信封,写上“老张–>老李”,把前面的那封信套在里面,发给老李,老李收到信以后,打开,发现这封信是给儿子的,就转给小李了。小李回信也一样,通过他父亲的名义发回给小张。
这种通讯实现方式要依赖以下几个因素:

  • 老李和老张可以收信发信
  • 小张发信,把信件交给老张。
  • 老张收到儿子的来信以后,能够正确的处理(写好另外一个信封),并且重新包装过的信封能够正确送出去。
  • 另外一端,老李收到信拆开以后,能够正确地交给小李。
  • 反过来的流程一样。
    把信封的收发人改成Internet上的IP地址,把信件的内容改成IP的数据,这个模型就是IPsec的包封装模型。小张小李就是内部私网的IP主机,他们的老爸就是VPN网关,本来不能通讯的两个异地的局域网,通过出口处的IP地址封装,就可以实现局域网对局域网的通讯。
    引进这种包封装协议,实在是有点不得已。理想的组网方式,当然是全路由方式。(这里注意理想的组网方式虽然是全路由方式,但VPN的应用也不会因全网而没有用武之地,因为VPN的根本还是为了方便外网访问内网)任意节点之间可达(就像理想的现实通讯方式是任何人之间都可以直接写信互通一样)。
    Internet协议最初设计的时候,IP地址是32位,当时是很足够了,没有人能够预料到将来Internet能够发展到现在的规模(相同的例子发生在电信短消息上面,由于160字节的限制,很大地制约了短消息的发展)。按照2的32次方计算,理论上最多能够容纳40亿个左右IP地址。这些IP地址的利用是很不充分的,另外大约有70%左右的IP地址被美国分配掉了(谁让人家发明并且管理Internet呢?)所以对于中国来说,可供分配的IP地址资源非常有限。
    既然IP地址有限,又要实现异地lan-lan通讯,包封包,自然是最好的方式了。

2.3.4 安全协议

依然参照上述的通讯模型。
假定老张给老李的信件要通过邮政系统传递,而中间途径有很多好事之徒,很想偷看小张和小李(小张小李作生意,通的是买卖信息)通讯,或者破坏其好事。
解决这个问题,就要引进安全措施。安全可以让小李和小张自己来完成,文字用暗号来表示,也可以让他们的老爸代劳完成,写好信,交给老爸,告诉他传出去之前重新用暗号写一下。
IPSEC协议的加密技术和这个方式是一样的,既然能够把数据封装,自然也可以把数据变换,只要到达目的地的时候,能够把数据恢复成原来的样子就可以了。这个加密工作在Internet出口的VPN网关上完成。

数据认证
还是以上述通讯模型为例,仅仅有加密是不够的。
把数据加密,对应这个模型中间,是把信件的文字用暗号表示。
好事之徒无法破解信件,但是可以伪造一封信,或者胡乱把信件改一通。这样,信件到达目的地以后,内容就面目全非了,而且收信一方不知道这封信是被修改过的。
为了防止这种结果,就要引入数据防篡改机制。万一数据被非法修改,能够很快识别出来。这在现实通讯中间可以采用类似这样的算法,计算信件特征(比如统计这封信件的笔划、有多少字),然后把这些特征用暗号标识在信件后面。收信人会检验这个信件特征,由于信件改变,特征也会变。所以,如果修改人没有暗号,改了以后,数据特征值就不匹配了。收信人可以看出来。
身份认证
还是假定小张小李通讯模型。
由于老张和老李不在一个地方,他们互相不能见面,为了保证他们儿子通讯的安全。老张和老李必须要相互确认对方是否可信。这就是身份认证问题。
假定老李老张以前见过面,他们事先就约定了通讯暗号,比如1234567890对应abcdefghij, 那么写个255,对应就是一个bee。
常见的VPN身份认证可以包括预共享密钥,通讯双方实现约定加密解密的密码,直接通讯就可以了。能够通讯就是朋友,不能通讯就是坏人,区分很简单。
其他复杂的身份认证机制包括证书(电子证书比如x509之类的),比较罗里啰嗦,这里就不具体展开了,怕有兄弟看了打瞌睡。如果需要,可以找我要更具体的技术白皮书以及相关的身份认证文档。
如果有身份认证机制,密钥的经常更换就成为了可能。

2.4 IKEv2

因特网密钥交换(英语:Internet Key Exchange,简称IKE或IKEv2)是一种网络协议,归属于IPsec协议族之下,用以创建安全联结(Security association,SA)[1]。它创建在奥克利协议(Oakley protocol)与ISAKMP协议的基础之上[2]。使用X.509安全认证。

V2和V1对比:

在 IKEv1 里,一般都需要 9 个包交换,除了 EZVPN VPN 预共享密钥 6 个包除外。而在 IKEv2 里,最少 4 个包就可以完成 VPN 建立了,因此就产生一对的 IPSEC SA 了。由此可以看出,IKEv2 更精简了。但是事实并不是这样啵,只是最小的情况下而已,但是如果要加其它的认证什么的话,包的数量就会增加的。看环境而定。

2.5 iOS VPN

2.5.1 VPN概述

使用成熟的行业标准虚拟专用网络 (VPN) 协议,可在 iOS 和 OS X 中安全地访问专用公司网络。ios 和 OS X 原生支持 IKEv2、Cisco IPSec、IPSec 上的 L2TP 和 PPTP。如果您所在的组织支持上述某个协议,则无需额外的网络配置或第三方应用即可将 Apple 设备连接至虚拟专用网络。

iOS 和 OS X 支持主流 VPN 提供商的 SSL VPN。与 iOS 和 OS X 支持的其他 VPN 协议一样,SSL VPN 既可在 Apple 设备上手动配置,也可以使用配置描述文件或 MDM 解决方案进行配置。

iOS 和 OS X 也支持 IPv6、代理服务器和隧道分离等技术,可在连接到组织网络时提供灵活的 VPN 体验。iOS 和 OS X 还兼容多种认证方式,包括密码、双因子令牌、数字证书以及用于 OS X 的 Kerberos。对于使用基于证书进行认证的环境,iOS 和 OS X 提供了“请求 VPN 域”功能来简化连接过程,该功能会在需要时发起 VPN 会话来连接到特定域。有关更多信息,请参阅本章的“请求 VPN 域”概览小节。

通过 iOS 7 或更高版本以及 OS X Yosemite 或更高版本,可将每个应用配置为独立于其他应用使用 VPN 连接。这样可以确保公司数据始终通过 VPN 连接传输,而其他数据(例如员工从 App Store 获得的个人应用)则不然。有关更多信息,请参阅本参考的“为 App 单独设置 VPN”小节。

iOS 还提供“始终打开 VPN”,这要求 iOS 设备在连接到任何其他网络服务前连接到经过批准的 VPN。您可以在被监督的设备上为蜂窝移动网络和无线局域网连接配置“始终打开 VPN”。若要实施此功能,您的 VPN 提供商必须支持“始终打开 VPN”。有关信息,请参阅本参考的“始终打开 VPN”概览小节。

2.5.2 支持的协议和认证方式

iOS 和 OS X 支持以下协议和认证方式:

IKEv2:支持 IPv4 和 IPv6 以及:

认证方式:共享密钥、证书、EAP–TLS 和 EAP–MSCHAPv2

Suite B 密码系统:ECDSA 证书、使用 GCM 的 ESP 加密以及针对 Diffie–Hellman 群组的 ECP 群组

附加功能:MOBIKE、IKE 碎片、服务器重定向、分离隧道

IPSec 上的 L2TP:使用 MS–CHAP v2 密码、双因子令牌、证书进行用户认证,使用共享密钥或证书进行机器认证

SSL VPN:使用第三方 VPN 客户端通过密码、双因子令牌和证书进行用户认证

Cisco IPSec:使用密码、双因子令牌进行用户认证,使用共享密钥和证书进行机器认证

PPTP:使用 MS–CHAP v2 密码、证书和双因子令牌进行用户认证

OS X 还可以通过共享密钥或证书(通过“IPSec 上的 L2TP”和“PPTP”)使用 Kerberos 机器认证。

2.5.3 SSL VPN 客户端

SSL VPN 客户端

某些 SSL VPN 提供商创建了专门的应用,用来帮助配置 iOS 设备,以便与他们的解决方案结合使用。要配置设备以使用特定的解决方案,可从 App Store 安装供应商的配套应用,也可以提供包含必要设置的配置描述文件。

iOS 支持以下 SSL VPN 解决方案(可在 App Store 上获取):

Aruba Networks SSL VPN:Aruba Networks Mobility Controller。若要配置,请安装 Aruba Networks VIA 应用。

如需获取联系信息,请访问 Aruba Networks 网站。

Check Point Mobile SSL VPN:包含完全第 3 层 VPN 隧道的 Check Point Security Gateway。安装 Check Point Mobile 应用。

Cisco AnyConnect SSL VPN:运行建议的软件版本 8.2.5 或更高版本的 Cisco Adaptive Security Appliance (ASA)。安装 Cisco AnyConnect 应用。

F5 SSL VPN:F5 BIG–IP Edge Gateway、Access Policy Manager 和 FirePass SSL VPN 解决方案。安装 F5 BIG–IP Edge Client 应用。

有关 F5 技术简介的更多信息,请参阅保护 iPhone 访问公司 Web 应用程序时的安全。

OpenVPN SSL VPN:OpenVPN Access Server、Private Tunnel 和 OpenVPN Community。若要配置,请安装 OpenVPN Connect 应用。

Palo Alto Networks GlobalProtect SSL VPN:Palo Alto Networks 的 GlobalProtect 网关。安装 iOS 版 GlobalProtect 应用。

SonicWALL SSL VPN:10.5.4 或更高版本的 SonicWALL Aventall E–Class Secure Remote Access 设备、5.5 或更高版本的 SonicWALL SRA 设备,以及 SonicWALL Next–Generation Firewall 设备(包括运行 SonicOS 5.8.1.0 或更高版本的 TZ、NSA 和 E–Class NSA)。安装 SonicWALL Mobile Connect 应用。

有关更多信息,请访问 SonicWALL 网站。

AirWatch SSL VPN:有关信息,请访问 AirWatch 网站。

Pulse Secure SSL VPN:包含 Pulse Secure IVE package 7.0 或更高版本的 Pulse Secure SA Series SSL VPN Gateway 6.4 或更高版本。

有关更多信息,请参阅 Pulse Secure 网站上的 Junos Pulse。

Mobile Iron SSL VPN:有关信息,请访问 Mobile Iron 网站。

NetMotion SSL VPN:有关信息,请访问 NetMotion 网站。

2.5.4 VPN 设置指南

2.5.4.1 代理设置

在进行所有这些配置时,可以指定 VPN 代理:

为所有连接配置单个代理:使用手动设置,提供地址、端口和认证(如有必要)。

使用 PAC 或 WPAD 为设备提供自动代理配置文件:使用自动设置。对于 PACS,请指定 PACS 或 JavaScript 文件的 URL。对于 WPAD,iOS 会请求 DHCP 和 DNS 以进行适当的设置。

要使用 VPN 代理配置,VPN 应提供以下方面:

默认解析器和默认路由:VPN 代理可供系统上的所有 Web 请求使用。

分离隧道:只有连接到与 VPN 的 DNS 搜索域相匹配的主机时才使用 VPN 代理。

2.5.4.2 证书

设置和安装证书时:

服务器身份证书的 SubjectAltName 栏必须包含服务器的 DNS 名称或 IP 地址。设备使用此信息来验证证书是否属于服务器。为了获得更高的灵活性,可以使用通配符来指定 SubjectAltName,以达到按名称段匹配的目的,如 vpn.*.mycompany.com。如果未指定 SubjectAltName,则可以将 DNS 名称放在“通用名称”栏中。

为服务器证书签名的证书颁发机构 (CA) 的证书需要安装在设备上。如果该证书不是根证书,请安装信任链的剩余部分以便证书得到信任。如果使用客户端证书,请确保为客户端证书签名的可信 CA 证书已安装在 VPN 服务器上。使用基于证书的认证时,请确保服务器已设置为根据客户端证书中的栏位来识别用户的群组。

【重要事项】证书和 CA 必须有效(例如,可信且未过期)。不支持通过服务器发送整个证书信任链。

2.5.4.3 IKEv2 设置

以下是受支持的 IKEv2 功能及相应的配置描述文件键。

认证方式
IKEv2 支持以下认证方式。使用 AuthenticationMethod 键指定认证级别:

共享密钥:您创建的预置键。

证书:对于证书认证,LocalIdentifier 和 RemoteIdentifier 键常用于识别 iOS IKEv2 客户端和 IKEv2 服务器。

LocalIdentifier 键通常应匹配用户/设备证书的身份(SubjectAltName 或 Subject CommonName),因为服务器实施策略可能要求其匹配以验证客户端的身份。

RemoteIdentifier 键应匹配服务器证书的身份(SubjectAltName 或 Subject CommonName)。

【注】如果 RemoteIdentifier 与服务器证书的身份不匹配,可以使用 ServerCertificateCommonName 键来指定服务器证书的身份。

您还可以使用 ServerCertificateIssuerCommonName 键来指定服务器 CA 的通用名称。指定此键将触发向服务器发送 IKEv2 CERTREQ,这是某些实施策略的要求。

EAP:EAP–MSCHAPv2、EAP–TLS 和 EAP–PEAP

您必须使用 ExtendedAuthEnabled 键来启用 EAP:

指定 EAP–MSCHAPv2 的 AuthName 和 AuthPassword

指定 EAP–TLS 的用户/设备证书。EAP–TLS 需要 ServerCertificateIssuerCommonName 键

指定 EAP–PEAP 的 AuthName 和 AuthPassword 以及用户/设备证书

对于服务器认证,您也可以使用 AuthenticationMethod 键来指定 IKEv2 的认证级别。

IKE 和 Child 提议
适用于 iOS 的 IKEv2 支持一个 IKE 提议和一个 Child 提议。每个提议允许指明一个加密算法、一个完整性算法以及一个 Diffie–Hellman 群组。服务器配置必须允许客户端 IKE 和 Child 提议。

IKE 和 Child 提议还允许指明客户端发起的 IKE 和 Child 密钥更新。此外,服务器发起的密钥更新独立于客户端发起的密钥更新,而且可以在您的服务器上配置。对于“始终打开 VPN”,建议停用服务器发起的密钥更新。

失效对等体检测
适用于 iOS 的 IKEv2 支持失效对等体检测。服务器失效对等体检测独立于客户端失效对等体检测,并且可以在您的服务器上配置。对于“始终打开 VPN”,建议停用服务器失效对等体检测。

分离隧道
适用于 iOS 的 IKEv2 支持分离隧道。默认情况下,服务器通过 IKEv2 流量选择器将分离隧道路由发送到客户端。如果服务器实施策略需要 INTERNAL_IP4_SUBNET 和 INTERNAL_IP6_SUBNET 属性来将路由发送到客户端,您可以使用 UseConfigurationAttributeInternalIPSubnet 键。

【注】IKEv2 不支持从服务器分离 DNS。但是,iOS VPN 有效负载支持允许分离 DNS 的 DNS 字典。

MOBIKE、服务器重定向和 PFS
适用于 iOS 的 IKEv2 支持 MOBIKE、服务器重定向和完全正向保密 (PFS),所有这些都有默认值。要获得这些选项的完整功能,需要服务器支持和配置。

MOBIKE:默认启用。使用 DisableMOBIKE 键来停用它。

服务器重定向:默认启用。使用 DisableRedirect 键来停用它。

PFS(需要 Child Diffie–Hellman 群组):默认停用。使用 EnablePFS 键来启用它。

NAT Keepalive 负载转移
适用于 iOS 的 IKEv2 支持针对“始终打开 VPN”连接的 NAT Keepalive 负载转移,且默认启用。它可以在设备处于睡眠状态时将发送 NAT Keepalive 到硬件的负载转移。NATKeepAliveInterval 键用来控制 Keepalive 负载转移的频率,而 NATKeepAliveOffloadEnable 键用来启用和停用此功能。此功能使设备在睡眠期间仍保持“始终打开 IKEv2”连接活跃。

2.5.4.4 Cisco IPSec 设置

参考本节来配置 Cisco VPN 服务器,以便与 iOS 设备配合使用。虽然 iOS 7.2 或更高版本支持 Cisco ASA 5500 Security Appliance 和 PIX 防火墙,但建议使用 iOS 8.0 或更高版本。iOS 还支持 IOS v12.4(15)T 或更高版本的 Cisco IOS VPN 路由器。VPN 3000 系列集中器不支持 iOS VPN 功能。

认证方式
iOS 支持以下认证方式:

预共享密钥 IPSec 认证,通过 xauth 进行用户认证。

利用客户端和服务器证书进行 IPSec 认证,可选择通过 xauth 进行用户认证。

混合认证,服务器提供证书,客户端提供预共享密钥,进行 IPSec 认证。要求用户认证(通过 xauth 提供),包括以下认证方式:

用户名和密码

RSA SecurID

CRYPTOCard

认证群组
Cisco Unity 协议根据一组常见参数,使用认证群组来分组用户。应创建一个针对 iOS 用户的认证群组。对于预共享密钥认证和混合认证,群组名称必须在设备上配置,并且使用群组的共享密钥(预共享密钥)作为群组密码。

使用证书认证时,没有共享密钥。用户的群组根据证书中的栏位来确定。Cisco 服务器设置可用于将证书中的栏位对应到用户群组。

在 ISAKMP 优先级列表中,RSA–Sig 必须拥有最高优先级。

IPSec 设置和描述
您可以指定这些设置以定义 IPSec 的实施方式:

模式:隧道模式。

IKE 交换模式:“积极模式”(适用于预共享密钥认证和混合认证)或“主模式”(适用于证书认证)。

加密算法:3DES、AES–128 或 AES256。

认证算法:HMAC–MD5 或 HMAC–SHA1。

Diffie–Hellman 群组:预共享密钥认证和混合认证需要群组 2,证书认证需要配合 3DES 和 AES–128 使用群组 2,以及配合 AES–256 使用群组 2 或群组 5。

完全正向保密 (PFS):对于 IKE 阶段 2,如果使用 PFS,则 Diffie–Hellman 群组必须与用于 IKE 阶段 1 的群组相同。

模式配置:必须启用。

失效对等体检测:推荐。

标准 NAT 遍历:受支持并且可以启用(不支持 IPSec over TCP)。

负载均衡:受支持且可以启用。

阶段 1 的密钥更新:当前不受支持。建议将服务器上的密钥更新时间设定为一小时。

ASA 地址掩码:确保所有设备地址池掩码都未设定或都设定为 255.255.255.255。例如:

asa(config-webvpn)# ip local pool vpn_users 10.0.0.1-10.0.0.254 mask 255.255.255.255。

如果使用建议的地址掩码,则可能会忽略 VPN 配置所采用的某些路由。若要避免发生这种情况,请确保路由表包含所有必要的路由,并且验证子网地址可以访问,然后再进行部署。

应用程序版本:客户端软件版本会被发送到服务器,使服务器能够根据设备的软件版本接受或拒绝连接。

横幅:横幅(如果是在服务器上配置的)会显示在设备上,用户必须接受它,否则会断开连接。

分离隧道:支持。

分离 DNS:支持。

默认域:支持。

2.5.5 为 App 单独设置 VPN

在 iOS 和 OS X 中,VPN 连接可以基于每个应用建立,从而可以更精确地控制哪些数据可以通过 VPN 进行传输。而采用设备级 VPN 时,任何客户端进程均有可能通过隧道提供的路由传输流量。这种在应用级别分离通信的功能可将个人数据从组织数据分离。因此,“为 App 单独设置 VPN”可为内部使用的应用提供安全网络,同时保护个人设备活动的隐私。

通过“为 App 单独设置 VPN”,可使每个处于移动设备管理 (MDM) 范围内的应用使用安全隧道与专用网络通信,同时禁止未处于管理范围内的应用使用专用网络。对于处于管理范围内的每个应用,可以配置不同的 VPN 连接以进一步保护数据安全。例如,销售报价应用可使用完全不同于应付帐款应用的数据中心。

要使用“为 App 单独设置 VPN”,应用必须由 MDM 管理并使用标准联网 API。为任意 VPN 连接启用“为 App 单独设置 VPN”后,您需要将该连接与使用该连接的应用进行关联,以确保这些应用的网络流量的安全性。该过程使用配置描述文件中的“为 App 单独设置 VPN”映射有效负载来完成。

在 iOS 9 或更高版本中,“为 App 单独设置 VPN”可以配置为与 iOS 上内建的 VPN 客户端配合使用,支持 IPSec (IKEv1) 和 IKEv2 VPN 客户端。

IKEv2 受 IPSec 客户端支持。有关“为 App 单独设置 VPN”支持的信息,请联系第三方 SSL 或 VPN 供应商。

2.5.6 请求VPN域

2.5.6.1 概述

“请求 VPN 域”可使 Apple 设备在需要时自动建立连接。它要求基于证书的认证,且不论使用何种协议均适用。

“请求 VPN 域”通过配置描述文件的 VPN 有效负载中的 OnDemandRules 键配置。系统分两个阶段应用规则:

网络检测阶段:定义当设备的主要网络连接发生更改时适用的 VPN 要求。

连接计算阶段:定义根据需要向域名发起连接请求时的 VPN 要求。

规则可以用来执行以下操作:

识别因 Apple 设备连接至内部网络而不需要 VPN 的情况

识别因使用未知无线局域网络而需要对所有网络活动采取 VPN 的情况

在针对指定域名的 DNS 请求失败后要求使用 VPN

2.5.6.2 阶段

“请求 VPN 域”通过两个阶段连接到网络。

网络检测阶段
当设备的主要网络接口发生变化时,例如当 Apple 设备切换至不同的无线局域网络或者从无线局域网切换至 iOS 上的蜂窝移动网络或 OS X 上的以太网时,将对“请求 VPN 域”规则进行计算。如果主要接口为虚拟接口(例如,VPN 接口),将忽略“请求 VPN 域”规则。

只有当每个组(字典)中的匹配规则全部匹配时,才会执行关联的操作。如果任意一项规则不匹配,将对列表中的下一个字典进行计算,直至抵达 OnDemandRules 列表末尾。

最后一个字典应定义默认配置,即没有匹配的规则,只有操作。这会找出没有与之前的规则匹配的所有连接。

连接计算阶段
VPN 可基于对某些域的连接请求按需触发,而不是基于网络接口单方面断开或连接。

2.5.6.3 规则和操作

规则帮助定义与“请求 VPN 域”相关联的网络类型。操作帮助定义在匹配规则为真时,所执行的操作。

请求匹配规则
为所有 SSL、IKEv2 和 Cisco IPSec 客户端指定以下一个或多个匹配规则:

InterfaceTypeMatch:可选。“蜂窝移动网络(对于 iOS)或以太网(对于 OS X)”或“无线局域网”的字符串值。如果指定此规则,则当主要接口硬件为指定的类型时,即与此规则匹配。

SSIDMatch:可选。要基于当前网络匹配的 SSID 列表。如果网络不是无线局域网络,或者其 SSID 未显示在列表中,则匹配失败。如果省略此键及其列表,将忽略 SSID。

DNSDomainMatch:可选。搜索域字符串列表。如果为当前主要网络配置的 DNS 搜索域包括在此列表中,将与此属性匹配。支持通配符前缀 ();例如 .example.com 将与 anything.example.com 匹配。

DNSServerAddressMatch:可选。DNS 服务器地址字符串列表。如果当前为主要接口配置的所有 DNS 服务器地址都位于列表中,则将与此属性匹配。支持通配符 ();例如 1.2.3. 将匹配前缀为 1.2.3. 的任何 DNS 服务器。

URLStringProbe:可选。要探查其可访问性的服务器。不支持重定向。URL 应当为可信的 HTTPS 服务器。设备会发送 GET 请求来验证是否可以访问该服务器。

Action
此必选键定义当指定的所有匹配规则都计算为真时的 VPN 行为。Action 键的值包括:

Connect:在下一次尝试建立网络连接时无条件启动 VPN 连接。

Disconnect:断开 VPN 连接,不按需触发任何新的连接。

Ignore:保留任何现有的 VPN 连接,但不按需触发任何新的连接。

EvaluateConnection:对每次连接尝试计算 ActionParameters。使用此值时,需要通过键 ActionParameters(见下文)指定计算规则。

Allow:对于运行 iOS 6 或更低版本的 iOS 设备,请参阅本参考的“向后兼容性”小节。

ActionParameters
这是一组字典,包含了以下描述的键,按照键的显示顺序计算。当 Action 为 EvaluateConnection 时,此为必选键。

Domains:必选。定义此计算规则适用的域的字符串列表。支持通配符前缀,例如 *.example.com。

DomainAction:必选。定义 Domains 的 VPN 行为。DomainAction 键的值包括:

ConnectIfNeeded:如果对 Domains 的 DNS 解析失败,将触发 VPN。例如,DNS 服务器指示其不能解析域名、DNS 响应遭到重定向或者连接失败或超时。

NeverConnect:不对 Domains 触发 VPN。

当 DomainAction 为 ConnectIfNeeded 时,还可以在连接计算字典中指定以下键:

RequiredDNSServers:可选。要用于解析 Domains 的 DNS 服务器的 IP 地址列表。这些服务器不必是设备当前网络配置的一部分。如果无法访问这些 DNS 服务器,则会触发 VPN。对于一致的连接,请配置内部 DNS 服务器或可信的外部 DNS 服务器。

RequiredURLStringProbe:可选。要使用 GET 请求探查的 HTTP 或 HTTPS(首选)URL。如果对此服务器的 DNS 解析成功,则探查也一定会成功。如果探查失败,将触发 VPN。

2.5.6.4 向后兼容性

在 iOS 7 之前,域触发规则从域的列表中进行配置:

OnDemandMatchDomainAlways

OnDemandMatchDomainOnRetry

OnDemandMatchDomainNever

iOS 7 或更高版本仍然支持 OnRetry 和 Never 情形,但更倾向于 EvaluateConnection 操作。

要创建同时适用于 iOS 7 和更早版本的描述文件,除了使用 OnDemandMatchDomain 列表外,还要使用新的 EvaluateConnection 键。

指定 Allow 操作的旧配置描述文件仍适用于 iOS 7 或更高版本,但 OnDemandMatchDomainsAlways 域除外。

2.5.7 始终打开VPN

2.5.6.1 概述

“始终打开 VPN”可让您的组织将所有 IP 流量通过隧道返回到组织,从而让组织完全控制设备流量。默认隧道协议 IKEv2 通过数据加密保护流量传输安全。您的组织现在可以监控和过滤经由设备的流量、保护网络内的数据安全并限制设备访问互联网。

“始终打开 VPN”激活需要设备监督。“始终打开 VPN”描述文件安装到设备上后,“始终打开 VPN”将自动激活,无需任何用户交互,并且会保持激活状态(包括在重新启动过程中),除非卸载“始终打开 VPN”描述文件。

设备上的“始终打开 VPN”处于激活状态时,VPN 隧道的初启和清除与接口 IP 状态有关。当接口可访问 IP 网络后,则会尝试建立隧道。当接口 IP 状态不可用时,隧道会清除。

“始终打开 VPN”也支持每个接口隧道。对于 iOS 设备,每一个活跃的 IP 接口都有一个隧道(蜂窝移动网络接口和无线局域网接口各有一个隧道)。只要 VPN 隧道可用,所有的 IP 流量均会经过隧道。流量包括所有 IP 路由流量和所有 IP 范围流量(来自于第一方应用如 FaceTime 和“信息”的流量)。如果隧道不可用,则会丢弃所有 IP 流量。

设备的所有隧道流量会经过 VPN 服务器。您可以先应用可选过滤和监视处理,然后将流量转移到组织网络或互联网中的目的位置。同样地,可以将设备流量发送到组织的 VPN 服务器,而过滤和监视进程可能会在转移到设备前进行应用。

2.5.6.2 配置

iOS 设备以单用户模式运行。设备身份和用户身份联系在一起。当 iOS 设备建立到 IKEv2 服务器的 IKEv2 隧道时,该服务器会将 iOS 设备识别为单个对等实体。通常,在一台 iOS 设备和一台 VPN 服务器之间会有一个隧道。由于“始终打开 VPN”采用的是每个接口隧道,因此单个 iOS 设备和 IKEv2 服务器之间可同时建立多个隧道。

“始终打开 VPN”配置支持以下两种配置:

仅支持蜂窝移动网络的设备
如果您的组织选择在仅支持蜂窝移动网络的 iOS 设备上部署“始终打开 VPN”(此时无线局域网接口永久性退出或不激活),则会在每台设备和 IKEv2 服务器之间通过蜂窝移动网络 IP 接口建立一个 IKEv2 隧道。这与传统 VPN 模型中类似。iOS 设备充当一个 IKEv2 客户端,拥有一个身份(例如,一个客户端证书或一个用户和密码),可使用 IKEv2 服务器建立 IKEv2 隧道。

蜂窝移动网络设备和无线局域网设备
如果您的组织在同时包含蜂窝移动网络和无线局域网接口的 iOS 设备上部署“始终打开 VPN”,设备会同时建立两个 IKEv2 隧道。使用可通过蜂窝移动网络和无线局域网两者连接的设备有以下两个场景:

蜂窝移动网络隧道和无线局域网隧道在不同的 IKEv2 服务器上终止。

凭借“始终打开 VPN”每个接口隧道配置键,组织可以对设备进行配置,以建立到一台 IKEv2 服务器的蜂窝移动网络隧道和到第二台 IKEv2 服务器的无线局域网隧道。此模型有一个好处,因为隧道在不同的服务器上终止,设备可以对两个隧道使用相同的客户端身份(即客户端证书或用户/密码)。借助不同的服务器,组织也可在每个接口类型流量(蜂窝移动网络流量对无线局域网流量)的隔离和控制方面拥有更大的灵活性。不足之处在于组织必须拥有客户端认证策略相同的两台不同 IKEv2 服务器。

蜂窝移动网络隧道和无线局域网隧道在相同的 IKEv2 服务器上终止。

凭借“始终打开 VPN”每个接口隧道配置,组织还可以对设备进行配置,以建立到同一台 IKEv2 服务器的蜂窝移动网络隧道和无线局域网隧道。

每台设备有一个客户端身份:如果 IKEv2 服务器支持每个客户端有多个隧道,那么组织就可为蜂窝移动网络隧道和无线局域网隧道配置相同的客户端身份(即一个客户端证书或一个用户/密码对)。好处是可以避免为每台设备配置其他的客户端身份,以减轻服务器上的额外配置/资源负担。不足之处在于因为设备不断进出网络,会建立新的隧道,而旧的隧道会逐渐失效。基于服务器的实施策略,服务器可能无法高效、准确地清理失效的隧道。组织必须实施策略来清理服务器上的失效隧道。

每台设备有两个客户端身份:组织可以配置两个客户端身份(即两个客户端证书或两对用户/密码),一个用于蜂窝移动网络隧道,一个用于无线局域网隧道。IKEv2 服务器会看到建立了自己隧道的两个不同客户端。此模型的好处是,由于许多服务器通过其客户端身份区分隧道且仅允许一个客户端有一个隧道,所以它适用于大多数服务器实现策略。不足之处在于,此模型需要请求两次服务器上的客户端身份、配置和资源管理。

2.5.6.3 配置描述文件

“始终打开 VPN”配置描述文件可以通过使用一种 Apple 配置描述文件编辑器(如“描述文件管理器”或 Apple Configurator 2)来手动组成,或使用 MDM 解决方案来创建。有关更多信息,请参阅“描述文件管理器帮助”或“Apple Configurator 2 帮助”,或联系您的首选 MDM 供应商。

用户交互键
若要防止用户停用“始终打开 VPN”功能,可禁止删除“始终打开 VPN”描述文件,方法是将顶层的描述文件键“PayloadRemovalDisallowed”设为“true”。

若要防止用户安装其他配置描述文件来更改“始终打开 VPN”功能的行为,可禁止安装 UI 描述文件。方法是,将“com.apple.applicationaccess”有效负载下方的“allowUIConfigurationProfileInstallation”键设为“false”。组织可使用相同有效负载下方的其他支持键来实施其他限制。

证书有效负载
证书颁发机构 (CA) 是服务器证书的颁发者。

服务器 CA 证书:如果 IKEv2 隧道的认证方式使用证书,则 IKEv2 服务器会将其服务器证书发送到 iOS 设备,以验证服务器身份。为了让 iOS 设备验证服务器证书,需要服务器的 CA 证书,该证书可能已经安装。若未安装,您的组织可以为服务器 CA 证书创建证书有效负载来包括该证书。

客户端 CA 证书:如果 IKEv2 隧道的认证方式使用证书或 EAP–TLS,则 iOS 设备会将其客户端证书发送到 IKEv2 服务器,以验证客户端身份。客户端可能有一个或两个客户端证书,这取决于部署模型。组织需要为客户端证书创建证书有效负载,才能包括客户端证书。为了让 IKEv2 服务器验证客户端身份,IKEv2 服务器必须已安装客户端的 CA 证书。

“始终打开 VPN”IKEv2 证书支持:目前“始终打开 VPN”IKEv2 仅支持 RSA 证书。

2.5.6.4 有效负载

以下适用于“始终打开 VPN”有效负载:

只能在被监督的 iOS 设备上安装。

配置描述文件只能包含一个“始终打开 VPN”有效负载。

一次只能在 iOS 设备上安装一个“始终打开 VPN”配置描述文件。

在 iOS 中自动连接
“始终打开 VPN”提供可选“UIToggleEnabled”键,以便在 VPN 设置中启用自动连接开关。如果此键未在描述文件中指定或被设为 0,“始终打开 VPN”会尝试触发一个或两个 VPN 隧道。如果此键被设为 1,则 VPN“设置”面板中会显示开关,这样用户就可以打开或关闭 VPN。如果用户关闭 VPN 隧道,系统将不会建立隧道,设备会丢弃所有的 IP 流量。这在无法访问 IP 且用户仍想拨打电话时很有用。用户可关闭 VPN 隧道以避免意外触发 VPN 隧道。

每个接口隧道配置列表
“TunnelConfigurations”列表中至少需要一个隧道配置。该配置可应用于蜂窝移动网络接口(针对仅支持蜂窝移动网络的设备),或应用于蜂窝移动网络和无线局域网接口。但是,可以包括两个隧道配置(一个用于蜂窝移动网络接口,一个用于无线局域网接口)。

强制流量例外
“始终打开 VPN”仅支持强制自动登录(自动登录到受支持的强制网络,该网络带有预分配的凭证,例如来自 SIM 的凭证)。

“始终打开 VPN”也支持对强制处理的控制,支持以下内容:

AllowCaptiveWebSheet:允许内建的强制 WebSheet(网络表单)应用中的流量从隧道外通过的键。WebSheet(网络表单)应用是一个浏览器,可在无其他第三方强制应用存在的情况下,处理强制登录。在使用此键时,组织应注意其存在的安全风险,因为 WebSheet(网络表单)是功能浏览器,能够渲染来自于响应的强制服务器的任何内容。允许 WebSheet(网络表单)应用流量会使设备受到不良行为或恶意强制服务器的攻击。

AllowAllCaptiveNetworkPlugins:允许所有满足条件的第三方强制应用的流量从隧道外通过的键。此键优先于 AllowedCaptiveNetworkPlugins 字典。

AllowedCaptiveNetworkPlugins:满足条件的第三方强制应用的捆绑包 ID 列表。允许来自第三方强制应用的流量从隧道外通过。如果还配置了 AllowAllCaptiveNetworkPlugins 键,则此列表不适用。

服务例外
“始终打开 VPN”默认所有 IP 流量通过隧道,包括所有本地流量和蜂窝移动网络运营商服务的流量。因此,默认的“始终打开 VPN”行为不支持任何本地 IP 服务或 IP 运营商服务。“始终打开 VPN”的“服务例外”可让您的组织更改对服务流量的默认处理方法:允许流量从隧道外通过或丢弃流量。当前支持的服务例外为“语音信箱”和“AirPrint”,其允许的操作是允许(允许数据从隧道外通过)或丢弃(不论隧道设置,直接丢弃)。

有关“始终打开 VPN”IKEv2 协议键和属性的更多信息,请参阅 Developer Library(开发者资源库)网站上的《Configuration Profile Key Reference》(配置描述文件键参考)。

2.6 配置VPN三个类:NEVPNProtocol,NEVPNManager,NEVPNConnection

2.6.1 NEVPNProtocol

该NEVPNProtocol类是一个抽象基类,一个子类为每种类型支持的隧道协议。

这个类的实例是线程安全的。

通用隧道协议属性:

VAR serverAddress :String?
隧道服务器的地址

VAR 用户名:String?
隧道协议认证证书的用户名组成部分。

VAR passwordReference :Data?
持久性钥匙扣参阅包含隧道协议认证证书的密码组成一个钥匙串项目。

VAR 的IdentityReference :Data?
持久性钥匙扣参阅包含证书和隧道协议认证证书的私钥组件的钥匙串项。

VAR identityData :Data?
证书和隧道协议认证证书的私钥部件,PKCS12格式编码。

VAR identityDataPassword :String?
密码被用于解密PKCS12数据中所设定的identityData属性。

VAR disconnectOnSleep :BOOL
当设备休眠表示如果VPN的标志应断开。

VAR proxySettings :NEProxySettings?
一个NEProxySettings对象含有待用于HTTP代理设置和HTTPS它们通过VPN路由连接。

2.6.2 NEVPNManager

NEVPNManager用于创建和管理VPN的配置和控制所得VPN隧道连接。

每个应用程序被允许建立一个单一的VPN配置。该NEVPNManager类有一个类方法(NEVPNManager),提供访问单个NEVPNManager实例。这个单一实例对应一个VPN配置,显示在iOS和网络首选项设置应用的VPN设置面板窗格在OS X系统预置的应用程序

由创建的VPN配置NEVPNManager实例被归类为个人VPN配置。在iOS和Mac OS X的,非个人VPN配置优先于个人的VPN配置。如果两个个人VPN配置和一个非个人的VPN配置同时连接,并且两个VPN隧道被配置为充当网络流量需要到达Internet缺省路由,则非个人VPN隧道实际上将默认路由到因特网,只要它是连接。

在使用NEVPNManager类要求com.apple.developer.networking.vpn.api权利。您可以通过启用“个人VPN”能力在Xcode您的应用程序得到这个权利为您的应用程序。

所管理的VPN配置NEVPNManager被存储在由网络扩展的框架管理的网络扩展偏好。VPN配置必须明确装入从网络扩展喜好内存可以使用它之前,并配置所做的任何更改,必须采取系统的影响之前明确地保存到网络扩展的偏好。

这个类的实例是线程安全的。

管理VPN配置:

class func shared()
访问的单个实例NEVPNManager。

func loadFromPreferences(completionHandler: (NSError?) -> Void)
从网络加载扩展喜好VP​​N配置。

func saveToPreferences(completionHandler: ((NSError?) -> Void)? = nil)
保存在网络扩展喜好VP​​N配置。

func removeFromPreferences(completionHandler: ((NSError?) -> Void)? = nil)
从网络扩展喜好删除VPN配置。

设置VPN配置参数:
var onDemandRules: [NEOnDemandRule]?
连接的有序列表按需规则

var isOnDemandEnabled: Bool
用它来打开按需连接功能的布尔值。

var localizedDescription: String?
包含VPN配置的显示名称的字符串。

VAR isEnabled :BOOL
一个布尔值,用来切换VPN配置的启用状态。

VAR protocolConfiguration :NEVPNProtocol?
一个NEVPNProtocol包含VPN隧道协议的配置设置对象。

控制VPN连接:
var connection: NEVPNConnection
一个NEVPNConnection用于控制由VPN配置中指定的VPN隧道对象。
通知:
static let NEVPNConfigurationChange: NSNotification.Name
发布存储在网络扩展偏好的变化VPN配置之后。
实例方法:
FUNC setAuthorization (AuthorizationRef)

2.6.3 NEVPNConnection

概述:NEVPNConnection对象不会直接实例化。相反,每个NEVPNManager对象具有相关联的NEVPNConnection对象作为只读属性。

该NEVPNConnection类提供了启动和编程停止VPN方法。该VPN可以启动和停止的另一种方式是通过VPN点播。见onDemandRules物业NEVPNManager和NEOnDemandRule。
这个类的实例是线程安全的。

控制VPN连接:
FUNC startVPNTunnel ()
开始连接该VPN的过程

func startVPNTunnel(options: [String : NSObject]? = [:])
开始连接该VPN的过程

FUNC stopVPNTunnel ()
开始断开的VPN的过程。

获取有关的VPN连接:

VAR 状态:NEVPNStatus
VPN连接的当前状态

VAR connectedDate :Date?
的日期和时间时,连接状态变更为NEVPNStatusConnected。

常量:
NEVPNStatus
VPN状态码

通知:
static let NEVPNStatusDidChange: NSNotification.Name
发布时的VPN连接的状态发生变化。

实例属性:
var manager: NEVPNManager (Beta)

参考:
https://help.apple.com/deployment/ios/#/ior69b9b7600
https://developer.apple.com/reference/networkextension/
https://zh.wikipedia.org/wiki/Vpn

07/13/2017 09:00 上午 posted in  VPN

VPN的分类方式

VPN的分类方式比较混乱。不同的生产厂家在销售它们的VPN产品时使用了不同的分类方式,它们主要是产品的角度来划分的。不同的ISP在开展VPN业务时也推出了不同的分类方式,他们主要是从业务开展的角度来划分的。而用户往往也有自己的划分方法,主要是根据自己的需求来进行的。下面简单介绍从不同的角度对VPN的分类。
##一、按接入方式划分
这是用户和运营商最关心的VPN划分方式。一般情况下,用户可能是专线上(因特)网的,也可能是拨号上网的,这要根据拥护的具体情况而定。建立在IP网上的VPN也就对应的有两种接入方式:专线接入方式和拨号接入方式。
(1)专线VPN:它是为已经通过专线接入ISP边缘路由器的用户提供的VPN解决方案。这是一种“永远在线”的VPN,可以节省传统的长途专线费用。
(2)拨号VPN(又称VPDN):它是向利用拨号PSTN或ISDN接入ISP的用户提供的VPN业务。这是一种“按需连接”的VPN,可以节省用户的长途电话费用。需要指出的是,因为用户一般是漫游用户,是“按需连接的,因此VPDN通常需要做身份认证(比如利用CHAP和RADIUS)
##二、按协议实现类型划分
这是VPN厂商和ISP最为关心的划分方式。根据分层模型,VPN可以在第二层建立,也可以在第三层建立(甚至有人把在更高层的一些安全协议也归入VPN协议。)
(1)第二层隧道协议:这包括点到点隧道协议(PPTP)、第二层转发协议(L2F),第二层隧道协议(L2TP)、多协议标记交换(MPLS)等。
(2)第三层隧道协议:这包括通用路由封装协议(GRE)、IP安全(IPSec),这是目前最流行的两种三层协议。
第二层和第三层隧道协议的区别主要在于用户数据在网络协议栈的第几层被封装,其中GRE、IPSec和MPLS主要用于实现专线VPN业务,L2TP主要用于实现拨号VPN业务(但也可以用于实现专线VPN业务),当然这些协议之间本身不是冲突的,而是可以结合使用的。
##三、按VPN的发起方式划分
这是客户和IPS最为关心的VPN分类。VPN业务可以是客户独立自主实现的,也可以是由ISP提供的。
(1)发起(也称基于客户的):VPN服务提供的其始点和终止点是面向客户的,其内部技术构成、实施和管理对VPN客户可见。需要客户和隧道服务器(或网关)方安装隧道软件。客户方的软件发起隧道,在公司隧道服务器处终止隧道。此时ISP不需要做支持建立隧道的任何工作。经过对用户身份符(ID)和口令的验证,客户方和隧道服务器极易建立隧道。双方也可以用加密的方式通信。隧道一经建立,用户就会感觉到ISP不在参与通信。
(2)服务器发起(也称客户透明方式或基于网络的):在公司中心部门或ISP处(POP、Point of presence)安装VPN软件,客户无须安装任何特殊软件。主要为ISP提供全面管理的VPN服务,服务提供的起始点和终止点是ISP的POP,其内部构成、实施和管理对VPN客户完全透明。
在上面介绍的隧道协议中,目前MPLS只能用于服务器发起的VPN方式。
##四、按VPN的服务类型划分
根据服务类型,VPN业务大致分为三类:接入VPN(Access VPN)、内联网VPN(Intranet VPN)和外联网VPN(Extranet VPN)。通常情况下内联网VPN是专线VPN。
(1)接入VPN:这是企业员工或企业的小分支机构通过公网远程访问企业内部网络的VPN方式。远程用户一般是一台计算机,而不是网络,因此组成的VPN是一种主机到网络的拓扑模型。需要指出的是接入VPN不同于前面的拨号VPN,这是一个容易发生混淆的地方,因为远程接入可以是专线方式接入的,也可以是拨号方式接入的。
(2)内联网VPN:这是企业的总部与分支机构之间通过公网构筑的虚拟网,这是一种网络到网络以对等的方式连接起来所组成的VPN。
(3)外联网VPN:这是企业在发生收购、兼并或企业间建立战略联盟后,使不同企业间通过公网来构筑的虚拟网。这是一种网络到网络以不对等的方式连接起来所组成的VPN(主要在安全策略上有所不同)。
##五、按承载主体划分
营运VPN业务的企业;既可以自行建设他们的VPN网络,也可以把此业务外包给VPN商。这是客户和ISP最关心的问题。
(1)自建VPN:这是一种客户发起的VPN。企业在驻地安装VPN的客户端软件,在企业网边缘安装VPN网关软件,完全独立于营运商建设自己的VPN网络,运营商不需要做任何对VPN的支持工作。企业自建VPN的好处是它可以直接控制VPN网络,与运营商独立,并且VPN接入设备也是独立的。但缺点是VPN技术非常复杂,这样组建的VPN成本很高,QoS也很难保证。
(2)外包VPN:企业把VPN服务外包给运营商,运营商根据企业的要求规划、设计、实施和运维客户的VPN业务。企业可以因此降低组建和运维VPN的费用,而运营商也可以因此开拓新的IP业务增值服务市场,获得更高的收益,并提高客户的保持力和忠诚度。笔者将目前的外包VPN划分为两种:基于网络的VPN和基于CE(用户边缘设备)的管理型VPN(Managed VPN)。基于网络的VPN通常在运营商网络的呈现点(POP)安装电信级VPN交换设备。基于CE的管理型VPN业务是一种受信的第三方负责设计企业所希望的VPN解决方案,并代表企业进行管理,所使用的安全网关(防火墙、路由器等)位于用户一侧。
##六、按VPN业务层次模型划分
这是根据ISP向用户提供的VPN服务工作在第几层来划分的(注意不是根据隧道协议工作在哪一层划分的)。
(1)拨号VPN业务(VPDN):这是第一种划分方式中的VPDN(事实上是按接入方式划分的,因为很难明确VPDN究竟属于哪一层)。
(2)虚拟租用线(VLL):这是对传统的租用线业务的仿真,用IP网络对租用线进行模拟,而从两端的用户看来这样一条虚拟租用线等价于过去的租用线。
(3)虚拟专用路由网(VPRN)业务:这是对第三层IP路由网络的一种仿真。可以把VPRN理解成第三层VPN技术。
(4)虚拟专用局域网段(VPLS):这是在IP广域网上仿真LAN的技术。可以把VPLS理解成一种第二层VPN技术。

##分类方式 类型名称 说明/举例
接入方式 拨号VPN(VPDN) 为利用拨号公用交换电话网(PSTN)或综合业务数字网(ISDN)接入ISP的用户提供的VPN业务
专线VPN 为已经通过专线接入ISP边缘路由器的用户提供的VPN业务
协议层 应用层 S/MIME、Kerberose、IPSec(ISAKMP)
传输层 SSL/TLS、SOCKS
IP层 用户数据在协议栈的第三层被封装,如IPSec(AH和ESP)
第二层隧道 用户数据在协议栈的第二层被封装,如L2TP、PPTP、L2F和MPLS
隧道的
发起方式 客户发起 基于客户的VPN。隧道的起始点和终止点是面向客户的,其内部技术构成、实施和管理都由VPN客户负责
服务器(网络)发起 ISP提供并管理的VPN服务,服务提供的起始点和终止点是ISP的呈现点(POP),其内部构成、实施和管理都由ISP负责
业务类型 接入VPN 企业员工或企业的小分支机构通过公网远程拨号等方式构筑的VPN
内联网VPN 企业总部与分支机构LAN之间通过公网构筑的VPN
外联网VPN 企业发生收购、兼并或企业间建立战略联盟后,不同企业间通过公网构筑的VPN
承建和
运维主体 企业自建 基于客户的VPN。隧道的起始点和终止点是面向客户的,其内部技术构成、实施和管理都由VPN客户负责
外包 基于网络 ISP提供并管理的VPN服务,服务提供的起始点和终止点是ISP的呈现点(POP),其内部构成、实施和管理都由ISP负责
托管方式 VPN设备位于用户一侧。运营商负责安装、配置和监视、维护设备的运转情况
服务在网络中的层次 VPDN 为利用拨号公用交换电话网(PSTN)或综合业务数字网(ISDN)接入ISP的用户提供的VPN业务
VLL 对传统的租用线业务的仿真
VRPN 是对第三层IP路由网络的一种仿真
VPLS 是在IP广域网上仿真LAN的技术

07/13/2017 08:45 上午 posted in  VPN

ShadowSocks的翻墙原理

所周知,天朝局域网通过 GFW 隔离了我们与外界的交流,当然,这个隔离并非完全隔离,而是选择性的,天朝不希望你上的网站就直接阻断。每一个网络请求都是有数据特征的,不同的协议具备不同的特征,比如 HTTP/HTTPS 这类请求,会很明确地告诉 GFW 它们要请求哪个域名;再比如 TCP 请求,它只会告诉 GFW 它们要请求哪个 IP。

GFW 封锁包含多种方式,最容易操作也是最基础的方式便是域名黑白名单,在黑名单内的域名不让通过,IP 黑白名单也是这个道理。如果你有一台国外服务器不在 GFW 的黑名单内,天朝局域网的机器就可以跟这一台机器通讯。那么一个翻墙的方案就出来了:境内设备与境外机器通讯,境内想看什么网页,就告诉境外的机器,让境外机器代理抓取,然后送回来,我们要做的就是保证境内设备与境外设备通讯时不被 GFW 怀疑和窃听。

ssh tunnel 是比较具有代表性的防窃听通讯隧道,通过 ssh 与境外服务器建立一条加密通道,此时的通讯 GFW 会将其视作普通的连接。由于大家都这么玩,GFW 着急了,于是它通过各种流量特征分析,渐渐的能够识别哪些连接是 ssh 隧道,并尝试性的对隧道做干扰,结果还是玩不过 GFW,众多隧道纷纷不通。

Shadowsocks 及其部署
如果你理解了上面那道隐形的墙的原理,那 Shadowsocks 的原理就可以用一句简单的描述来理解了:它发出的 TCP 包,没有明显包特征,GFW 分析不出来,当作普通流量放过了。

具体而言,Shadowsocks 将原来 ssh 创建的 Socks5 协议拆开成 Server 端和 Client 端,两个端分别安装在境外服务器和境内设备上。

Clowwindy分享并开源了他提出的Shadowsocks翻墙解决方案。它的翻墙原理是什么?有什么优点和缺点?

简单理解的话,Shadowsocks是将以前通过SSH创建的Socks5协议拆开成Server端和client端,下面这个原理图能简单介绍其翻墙原理,基本上和利用SSH tunnel大致类似:

  1. PC客户端(即你的电脑)发出请求基于Socks5协议跟SS-Local端进行通讯,由于这个SS-Local一般是本机或路由器等局域网的其他机器,不经过GFW,所以解决GFW通过特征分析进行干扰的问题。
  2. SS-Local和SS-Server两端通过多种可选的加密方法进行通讯,经过GFW的时候因为是常规的TCP包,没有明显特征码GFW也无法对通讯数据进行解密,因此通讯放行。
  3. SS-Server将收到的加密数据进行解密,还原初始请求,再发送到用户需要访问的服务网站,获取响应原路再返回SS-04,返回途中依然使用了加密,使得流量是普通TCP包,并成功穿过GFW防火墙。

因此,Shadowsocks的优点在于它通过流量混淆隐秘解决了GFW通过分析流量特征从而干扰的问题,这是它优于SSH和VPN翻墙的地方(但VPN更注重加密安全性)。缺点也依然明显,需要一点点技术和资源(墙外VPS服务器)来搭建Shadowsocks服务,好在已经有人搭建相应的服务出售翻墙服务了。

07/12/2017 13:59 下午 posted in  VPN

iOS多线程全套:线程生命周期,多线程的四种解决方案,线程安全问题,GCD的使用,NSOperation的使用

本文主要是分享iOS多线程的相关内容,为了更系统的讲解,将分为以下7个方面来展开描述。

  1. 多线程的基本概念
  2. 线程的状态与生命周期
  3. 多线程的四种解决方案:pthread,NSThread,GCD,NSOperation
  4. 线程安全问题
  5. NSThread的使用
  6. GCD的理解与使用
  7. NSOperation的理解与使用
    Demo在这里:
    WHMultiThreadDemo

Demo的运行gif图如下:

##一、多线程的基本概念

进程:可以理解成一个运行中的应用程序,是系统进行资源分配和调度的基本单位,是操作系统结构的基础,主要管理资源。
线程:是进程的基本执行单元,一个进程对应多个线程。
主线程:处理UI,所有更新UI的操作都必须在主线程上执行。不要把耗时操作放在主线程,会卡界面。
多线程:在同一时刻,一个CPU只能处理1条线程,但CPU可以在多条线程之间快速的切换,只要切换的足够快,就造成了多线程一同执行的假象。
线程就像火车的一节车厢,进程则是火车。车厢(线程)离开火车(进程)是无法跑动的,而火车(进程)至少有一节车厢(主线程)。多线程可以看做多个车厢,它的出现是为了提高效率。
多线程是通过提高资源使用率来提高系统总体的效率。
我们运用多线程的目的是:将耗时的操作放在后台执行!
##二、线程的状态与生命周期

下图是线程状态示意图,从图中可以看出线程的生命周期是:新建 - 就绪 - 运行 - 阻塞 - 死亡


3873004-a64a254331bb442a.png

下面分别阐述线程生命周期中的每一步

  • 新建:实例化线程对象
  • 就绪:向线程对象发送start消息,线程对象被加入可调度线程池等待CPU调度。
  • 运行:CPU 负责调度可调度线程池中线程的执行。线程执行完成之前,状态可能会在就绪和运行之间来回切换。就绪和运行之间的状态变化由CPU负责,程序员不能干预。
  • 阻塞:当满足某个预定条件时,可以使用休眠或锁,阻塞线程执行。sleepForTimeInterval(休眠指定时长),sleepUntilDate(休眠到指定日期),@synchronized(self):(互斥锁)。
  • 死亡:正常死亡,线程执行完毕。非正常死亡,当满足某个条件后,在线程内部中止执行/在主线程中止线程对象
  • 还有线程的exit和cancel
  • [NSThread exit]:一旦强行终止线程,后续的所有代码都不会被执行。
  • [thread cancel]取消:并不会直接取消线程,只是给线程对象添加 isCancelled 标记。
    ##三、多线程的四种解决方案

多线程的四种解决方案分别是:pthread,NSThread,GCD, NSOperation。
下图是对这四种方案进行了解读和对比。


3873004-d66485790959dcf5.png

##四、线程安全问题

当多个线程访问同一块资源时,很容易引发数据错乱和数据安全问题。就好比几个人在同一时修改同一个表格,造成数据的错乱。

解决多线程安全问题的方法

方法一:互斥锁(同步锁)

@synchronized(锁对象) {
    // 需要锁定的代码
}

判断的时候锁对象要存在,如果代码中只有一个地方需要加锁,大多都使用self作为锁对象,这样可以避免单独再创建一个锁对象。

加了互斥做的代码,当新线程访问时,如果发现其他线程正在执行锁定的代码,新线程就会进入休眠。

方法二:自旋锁
加了自旋锁,当新线程访问代码时,如果发现有其他线程正在锁定代码,新线程会用死循环的方式,一直等待锁定的代码执行完成。相当于不停尝试执行代码,比较消耗性能。

属性修饰atomic本身就有一把自旋锁。

下面说一下属性修饰nonatomic 和 atomic

nonatomic 非原子属性,同一时间可以有很多线程读和写
atomic 原子属性(线程安全),保证同一时间只有一个线程能够写入(但是同一个时间多个线程都可以取值),atomic 本身就有一把锁(自旋锁)
atomic:线程安全,需要消耗大量的资源
nonatomic:非线程安全,不过效率更高,一般使用nonatomic
##五、NSThread的使用

No.1:NSThread创建线程

NSThread有三种创建方式:

init方式
detachNewThreadSelector创建好之后自动启动
performSelectorInBackground创建好之后也是直接启动

/** 方法一,需要start */
NSThread *thread1 = [[NSThread alloc] initWithTarget:self selector:@selector(doSomething1:) object:@"NSThread1"];
// 线程加入线程池等待CPU调度,时间很快,几乎是立刻执行
[thread1 start];
 
/** 方法二,创建好之后自动启动 */
[NSThread detachNewThreadSelector:@selector(doSomething2:) toTarget:self withObject:@"NSThread2"];
 
/** 方法三,隐式创建,直接启动 */
[self performSelectorInBackground:@selector(doSomething3:) withObject:@"NSThread3"];
 
- (void)doSomething1:(NSObject *)object {
    // 传递过来的参数
    NSLog(@"%@",object);
    NSLog(@"doSomething1:%@",[NSThread currentThread]);
}
 
- (void)doSomething2:(NSObject *)object {
    NSLog(@"%@",object);
    NSLog(@"doSomething2:%@",[NSThread currentThread]);
}
 
- (void)doSomething3:(NSObject *)object {
    NSLog(@"%@",object);
    NSLog(@"doSomething3:%@",[NSThread currentThread]);
}

No.2:NSThread的类方法

返回当前线程

// 当前线程
[NSThread currentThread];
NSLog(@"%@",[NSThread currentThread]);

// 如果number=1,则表示在主线程,否则是子线程
打印结果:{number = 1, name = main}
阻塞休眠

//休眠多久
[NSThread sleepForTimeInterval:2];
//休眠到指定时间
[NSThread sleepUntilDate:[NSDate date]];

类方法补充

//退出线程
[NSThread exit];
//判断当前线程是否为主线程
[NSThread isMainThread];
//判断当前线程是否是多线程
[NSThread isMultiThreaded];
//主线程的对象
NSThread *mainThread = [NSThread mainThread];

No.3:NSThread的一些属性

//线程是否在执行
thread.isExecuting;
//线程是否被取消
thread.isCancelled;
//线程是否完成
thread.isFinished;
//是否是主线程
thread.isMainThread;
//线程的优先级,取值范围0.0到1.0,默认优先级0.5,1.0表示最高优先级,优先级高,CPU调度的频率高
 thread.threadPriority;

Demo:WHMultiThreadDemo

##六、GCD的理解与使用

No.1:GCD的特点

  • GCD会自动利用更多的CPU内核

  • GCD自动管理线程的生命周期(创建线程,调度任务,销毁线程等)

  • 程序员只需要告诉 GCD 想要如何执行什么任务,不需要编写任何线程管理代码
    No.2:GCD的基本概念

  • 任务(block):任务就是将要在线程中执行的代码,将这段代码用block封装好,然后将这个任务添加到指定的执行方式(同步执行和异步执行),等待CPU从队列中取出任务放到对应的线程中执行。

  • 同步(sync):一个接着一个,前一个没有执行完,后面不能执行,不开线程。

  • 异步(async):开启多个新线程,任务同一时间可以一起执行。异步是多线程的代名词

  • 队列:装载线程任务的队形结构。(系统以先进先出的方式调度队列中的任务执行)。在GCD中有两种队列:串行队列和并发队列。

  • 并发队列:线程可以同时一起进行执行。实际上是CPU在多条线程之间快速的切换。(并发功能只有在异步(dispatch_async)函数下才有效)

  • 串行队列:线程只能依次有序的执行。

  • GCD总结:将任务(要在线程中执行的操作block)添加到队列(自己创建或使用全局并发队列),并且指定执行任务的方式(异步dispatch_async,同步dispatch_sync)
    No.3:队列的创建方法

使用dispatch_queue_create来创建队列对象,传入两个参数,第一个参数表示队列的唯一标识符,可为空。第二个参数用来表示串行队列(DISPATCH_QUEUE_SERIAL)或并发队列(DISPATCH_QUEUE_CONCURRENT)。

// 串行队列
dispatch_queue_t queue = dispatch_queue_create("test", DISPATCH_QUEUE_SERIAL);
// 并发队列
dispatch_queue_t queue1 = dispatch_queue_create("test", DISPATCH_QUEUE_CONCURRENT);

GCD的队列还有另外两种:
主队列:主队列负责在主线程上调度任务,如果在主线程上已经有任务正在执行,主队列会等到主线程空闲后再调度任务。通常是返回主线程更新UI的时候使用。

dispatch_get_main_queue()
  dispatch_async(dispatch_get_global_queue(0, 0), ^{
      // 耗时操作放在这里
      dispatch_async(dispatch_get_main_queue(), ^{
          // 回到主线程进行UI操作
      });
  });

全局并发队列:全局并发队列是就是一个并发队列,是为了让我们更方便的使用多线程。dispatch_get_global_queue(0, 0)

//全局并发队列
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
//全局并发队列的优先级
#define DISPATCH_QUEUE_PRIORITY_HIGH 2 // 高优先级
#define DISPATCH_QUEUE_PRIORITY_DEFAULT 0 // 默认(中)优先级
#define DISPATCH_QUEUE_PRIORITY_LOW (-2) // 低优先级
#define DISPATCH_QUEUE_PRIORITY_BACKGROUND INT16_MIN // 后台优先级
//iOS8开始使用服务质量,现在获取全局并发队列时,可以直接传0
dispatch_get_global_queue(0, 0);

No.4:同步/异步/任务、创建方式

同步(sync)使用dispatch_sync来表示。
异步(async)使用dispatch_async。

任务就是将要在线程中执行的代码,将这段代码用block封装好。

代码如下:

    // 同步执行任务
    dispatch_sync(dispatch_get_global_queue(0, 0), ^{
        // 任务放在这个block里
        NSLog(@"我是同步执行的任务");
 
    });
    // 异步执行任务
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        // 任务放在这个block里
        NSLog(@"我是异步执行的任务");
 
    });

No.5:GCD的使用

由于有多种队列(串行/并发/主队列)和两种执行方式(同步/异步),所以他们之间可以有多种组合方式。

  • 串行同步
  • 串行异步
  • 并发同步
  • 并发异步
  • 主队列同步
  • 主队列异步
  • 串行同步
    执行完一个任务,再执行下一个任务。不开启新线程。
/** 串行同步 */
- (void)syncSerial {
 
    NSLog(@"\n\n**************串行同步***************\n\n");
 
    // 串行队列
    dispatch_queue_t queue = dispatch_queue_create("test", DISPATCH_QUEUE_SERIAL);
 
    // 同步执行
    dispatch_sync(queue, ^{
        for (int i = 0; i < 3; i++) {
            NSLog(@"串行同步1   %@",[NSThread currentThread]);
        }
    });
    dispatch_sync(queue, ^{
        for (int i = 0; i < 3; i++) {
            NSLog(@"串行同步2   %@",[NSThread currentThread]);
        }
    });
    dispatch_sync(queue, ^{
        for (int i = 0; i < 3; i++) {
            NSLog(@"串行同步3   %@",[NSThread currentThread]);
        }
    });
}

输入结果为顺序执行,都在主线程:

串行同步1   {number = 1, name = main}
串行同步1   {number = 1, name = main}
串行同步1   {number = 1, name = main}
串行同步2   {number = 1, name = main}
串行同步2   {number = 1, name = main}
串行同步2   {number = 1, name = main}
串行同步3   {number = 1, name = main}
串行同步3   {number = 1, name = main}
串行同步3   {number = 1, name = main}

串行异步
开启新线程,但因为任务是串行的,所以还是按顺序执行任务。

/** 串行异步 */
- (void)asyncSerial {
 
    NSLog(@"\n\n**************串行异步***************\n\n");
 
    // 串行队列
    dispatch_queue_t queue = dispatch_queue_create("test", DISPATCH_QUEUE_SERIAL);
 
    // 同步执行
    dispatch_async(queue, ^{
        for (int i = 0; i < 3; i++) {
            NSLog(@"串行异步1   %@",[NSThread currentThread]);
        }
    });
    dispatch_async(queue, ^{
        for (int i = 0; i < 3; i++) {
            NSLog(@"串行异步2   %@",[NSThread currentThread]);
        }
    });
    dispatch_async(queue, ^{
        for (int i = 0; i < 3; i++) {
            NSLog(@"串行异步3   %@",[NSThread currentThread]);
        }
    });
}

输入结果为顺序执行,有不同线程:

串行异步1   {number = 3, name = (null)}
串行异步1   {number = 3, name = (null)}
串行异步1   {number = 3, name = (null)}
串行异步2   {number = 3, name = (null)}
串行异步2   {number = 3, name = (null)}
串行异步2   {number = 3, name = (null)}
串行异步3   {number = 3, name = (null)}
串行异步3   {number = 3, name = (null)}
串行异步3   {number = 3, name = (null)}

并发同步
因为是同步的,所以执行完一个任务,再执行下一个任务。不会开启新线程。

/** 并发同步 */
- (void)syncConcurrent {
3
    NSLog(@"\n\n**************并发同步***************\n\n");
3
    // 并发队列
    dispatch_queue_t queue = dispatch_queue_create("test", DISPATCH_QUEUE_CONCURRENT);
3
    // 同步执行
    dispatch_sync(queue, ^{
        for (int i = 0; i < 3; i++) {
            NSLog(@"并发同步1   %@",[NSThread currentThread]);
        }
    });
    dispatch_sync(queue, ^{
        for (int i = 0; i < 3; i++) {
            NSLog(@"并发同步2   %@",[NSThread currentThread]);
        }
    });
    dispatch_sync(queue, ^{
        for (int i = 0; i < 3; i++) {
            NSLog(@"并发同步3   %@",[NSThread currentThread]);
        }
    });
}

输入结果为顺序执行,都在主线程:

并发同步1   {number = 1, name = main}
并发同步1   {number = 1, name = main}
并发同步1   {number = 1, name = main}
并发同步2   {number = 1, name = main}
并发同步2   {number = 1, name = main}
并发同步2   {number = 1, name = main}
并发同步3   {number = 1, name = main}
并发同步3   {number = 1, name = main}
并发同步3   {number = 1, name = main}

并发异步
任务交替执行,开启多线程。

/** 并发异步 */
- (void)asyncConcurrent {
 
    NSLog(@"\n\n**************并发异步***************\n\n");
 
    // 并发队列
    dispatch_queue_t queue = dispatch_queue_create("test", DISPATCH_QUEUE_CONCURRENT);
 
    // 同步执行
    dispatch_async(queue, ^{
        for (int i = 0; i < 3; i++) {
            NSLog(@"并发异步1   %@",[NSThread currentThread]);
        }
    });
    dispatch_async(queue, ^{
        for (int i = 0; i < 3; i++) {
            NSLog(@"并发异步2   %@",[NSThread currentThread]);
        }
    });
    dispatch_async(queue, ^{
        for (int i = 0; i < 3; i++) {
            NSLog(@"并发异步3   %@",[NSThread currentThread]);
        }
    });
}

输入结果为无序执行,有多条线程:

并发异步1   {number = 3, name = (null)}
并发异步2   {number = 4, name = (null)}
并发异步3   {number = 5, name = (null)}
并发异步1   {number = 3, name = (null)}
并发异步2   {number = 4, name = (null)}
并发异步3   {number = 5, name = (null)}
并发异步1   {number = 3, name = (null)}
并发异步2   {number = 4, name = (null)}
并发异步3   {number = 5, name = (null)}

主队列同步
如果在主线程中运用这种方式,则会发生死锁,程序崩溃。

/** 主队列同步 */
- (void)syncMain {
 
    NSLog(@"\n\n**************主队列同步,放到主线程会死锁***************\n\n");
 
    // 主队列
    dispatch_queue_t queue = dispatch_get_main_queue();
 
    dispatch_sync(queue, ^{
        for (int i = 0; i < 3; i++) {
            NSLog(@"主队列同步1   %@",[NSThread currentThread]);
        }
    });
    dispatch_sync(queue, ^{
        for (int i = 0; i < 3; i++) {
            NSLog(@"主队列同步2   %@",[NSThread currentThread]);
        }
    });
    dispatch_sync(queue, ^{
        for (int i = 0; i < 3; i++) {
            NSLog(@"主队列同步3   %@",[NSThread currentThread]);
        }
    });
}

主队列同步造成死锁的原因:

如果在主线程中运用主队列同步,也就是把任务放到了主线程的队列中。
而同步对于任务是立刻执行的,那么当把第一个任务放进主队列时,它就会立马执行。
可是主线程现在正在处理syncMain方法,任务需要等syncMain执行完才能执行。
syncMain执行到第一个任务的时候,又要等第一个任务执行完才能往下执行第二个和第三个任务。
这样syncMain方法和第一个任务就开始了互相等待,形成了死锁。
主队列异步
在主线程中任务按顺序执行。

/** 主队列异步 */
- (void)asyncMain {
 
    NSLog(@"\n\n**************主队列异步***************\n\n");
 
    // 主队列
    dispatch_queue_t queue = dispatch_get_main_queue();
 
    dispatch_sync(queue, ^{
        for (int i = 0; i < 3; i++) {
            NSLog(@"主队列异步1   %@",[NSThread currentThread]);
        }
    });
    dispatch_sync(queue, ^{
        for (int i = 0; i < 3; i++) {
            NSLog(@"主队列异步2   %@",[NSThread currentThread]);
        }
    });
    dispatch_sync(queue, ^{
        for (int i = 0; i < 3; i++) {
            NSLog(@"主队列异步3   %@",[NSThread currentThread]);
        }
    });
}

输入结果为在主线程中按顺序执行:

主队列异步1   {number = 1, name = main}
主队列异步1   {number = 1, name = main}
主队列异步1   {number = 1, name = main}
主队列异步2   {number = 1, name = main}
主队列异步2   {number = 1, name = main}
主队列异步2   {number = 1, name = main}
主队列异步3   {number = 1, name = main}
主队列异步3   {number = 1, name = main}
主队列异步3   {number = 1, name = main}

GCD线程之间的通讯
开发中需要在主线程上进行UI的相关操作,通常会把一些耗时的操作放在其他线程,比如说图片文件下载等耗时操作。

当完成了耗时操作之后,需要回到主线程进行UI的处理,这里就用到了线程之间的通讯。

- (IBAction)communicationBetweenThread:(id)sender {
 
    // 异步
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        // 耗时操作放在这里,例如下载图片。(运用线程休眠两秒来模拟耗时操作)
        [NSThread sleepForTimeInterval:2];
        NSString *picURLStr = @"http://www.bangmangxuan.net/uploads/allimg/160320/74-160320130500.jpg";
        NSURL *picURL = [NSURL URLWithString:picURLStr];
        NSData *picData = [NSData dataWithContentsOfURL:picURL];
        UIImage *image = [UIImage imageWithData:picData];
 
        // 回到主线程处理UI
        dispatch_async(dispatch_get_main_queue(), ^{
            // 在主线程上添加图片
            self.imageView.image = image;
        });
    });
}

上面的代码是在新开的线程中进行图片的下载,下载完成之后回到主线程显示图片。

GCD栅栏
当任务需要异步进行,但是这些任务需要分成两组来执行,第一组完成之后才能进行第二组的操作。这时候就用了到GCD的栅栏方法dispatch_barrier_async。

- (IBAction)barrierGCD:(id)sender {
 
    // 并发队列
    dispatch_queue_t queue = dispatch_queue_create("test", DISPATCH_QUEUE_CONCURRENT);
 
    // 异步执行
    dispatch_async(queue, ^{
        for (int i = 0; i < 3; i++) {
            NSLog(@"栅栏:并发异步1   %@",[NSThread currentThread]);
        }
    });
    dispatch_async(queue, ^{
        for (int i = 0; i < 3; i++) {
            NSLog(@"栅栏:并发异步2   %@",[NSThread currentThread]);
        }
    });
 
    dispatch_barrier_async(queue, ^{
        NSLog(@"------------barrier------------%@", [NSThread currentThread]);
        NSLog(@"******* 并发异步执行,但是34一定在12后面 *********");
    });
 
    dispatch_async(queue, ^{
        for (int i = 0; i < 3; i++) {
            NSLog(@"栅栏:并发异步3   %@",[NSThread currentThread]);
        }
    });
    dispatch_async(queue, ^{
        for (int i = 0; i < 3; i++) {
            NSLog(@"栅栏:并发异步4   %@",[NSThread currentThread]);
        }
    });
}

上面代码的打印结果如下,开启了多条线程,所有任务都是并发异步进行。但是第一组完成之后,才会进行第二组的操作。

栅栏:并发异步1   {number = 3, name = (null)}
栅栏:并发异步2   {number = 6, name = (null)}
栅栏:并发异步1   {number = 3, name = (null)}
栅栏:并发异步2   {number = 6, name = (null)}
栅栏:并发异步1   {number = 3, name = (null)}
栅栏:并发异步2   {number = 6, name = (null)}
 ------------barrier------------{number = 6, name = (null)}
******* 并发异步执行,但是34一定在12后面 *********
栅栏:并发异步4   {number = 3, name = (null)}
栅栏:并发异步3   {number = 6, name = (null)}
栅栏:并发异步4   {number = 3, name = (null)}
栅栏:并发异步3   {number = 6, name = (null)}
栅栏:并发异步4   {number = 3, name = (null)}
栅栏:并发异步3   {number = 6, name = (null)}

GCD延时执行
当需要等待一会再执行一段代码时,就可以用到这个方法了:dispatch_after。

dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
    // 5秒后异步执行
    NSLog(@"我已经等待了5秒!");
});

GCD实现代码只执行一次
使用dispatch_once能保证某段代码在程序运行过程中只被执行1次。可以用来设计单例。

static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
    NSLog(@"程序运行过程中我只执行了一次!");
});

GCD快速迭代
GCD有一个快速迭代的方法dispatch_apply,dispatch_apply可以同时遍历多个数字。

- (IBAction)applyGCD:(id)sender {
 
    NSLog(@"\n\n************** GCD快速迭代 ***************\n\n");
 
    // 并发队列
    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
 
    // dispatch_apply几乎同时遍历多个数字
    dispatch_apply(7, queue, ^(size_t index) {
        NSLog(@"dispatch_apply:%zd======%@",index, [NSThread currentThread]);
    });
}

打印结果如下:

dispatch_apply:0======{number = 1, name = main}
dispatch_apply:1======{number = 1, name = main}
dispatch_apply:2======{number = 1, name = main}
dispatch_apply:3======{number = 1, name = main}
dispatch_apply:4======{number = 1, name = main}
dispatch_apply:5======{number = 1, name = main}
dispatch_apply:6======{number = 1, name = main}

GCD队列组
异步执行几个耗时操作,当这几个操作都完成之后再回到主线程进行操作,就可以用到队列组了。

队列组有下面几个特点:

所有的任务会并发的执行(不按序)。
所有的异步函数都添加到队列中,然后再纳入队列组的监听范围。
使用dispatch_group_notify函数,来监听上面的任务是否完成,如果完成, 就会调用这个方法。
队列组示例代码:

- (void)testGroup {
    dispatch_group_t group =  dispatch_group_create();
 
    dispatch_group_async(group, dispatch_get_global_queue(0, 0), ^{
        NSLog(@"队列组:有一个耗时操作完成!");
    });
 
    dispatch_group_async(group, dispatch_get_global_queue(0, 0), ^{
        NSLog(@"队列组:有一个耗时操作完成!");
    });
 
    dispatch_group_notify(group, dispatch_get_main_queue(), ^{
        NSLog(@"队列组:前面的耗时操作都完成了,回到主线程进行相关操作");
    });
}

打印结果如下:

队列组:有一个耗时操作完成!
队列组:有一个耗时操作完成!
队列组:前面的耗时操作都完成了,回到主线程进行相关操作

至此,GCD的相关内容叙述完毕。下面让我们继续学习NSOperation。

##七、NSOperation的理解与使用

No.1:NSOperation简介

NSOperation是基于GCD之上的更高一层封装,NSOperation需要配合NSOperationQueue来实现多线程。

NSOperation实现多线程的步骤如下:

  1. 创建任务:先将需要执行的操作封装到NSOperation对象中。
  2. 创建队列:创建NSOperationQueue。
  3. 将任务加入到队列中:将NSOperation对象添加到NSOperationQueue中。
    需要注意的是,NSOperation是个抽象类,实际运用时中需要使用它的子类,有三种方式:

使用子类NSInvocationOperation
使用子类NSBlockOperation
定义继承自NSOperation的子类,通过实现内部相应的方法来封装任务。
No.2:NSOperation的三种创建方式

NSInvocationOperation的使用
创建NSInvocationOperation对象并关联方法,之后start。

- (void)testNSInvocationOperation {
    // 创建NSInvocationOperation
    NSInvocationOperation *invocationOperation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(invocationOperation) object:nil];
    // 开始执行操作
    [invocationOperation start];
}
 
- (void)invocationOperation {
    NSLog(@"NSInvocationOperation包含的任务,没有加入队列========%@", [NSThread currentThread]);
}

打印结果如下,得到结论:程序在主线程执行,没有开启新线程。

这是因为NSOperation多线程的使用需要配合队列NSOperationQueue,后面会讲到NSOperationQueue的使用。

NSInvocationOperation包含的任务,没有加入队列========{number = 1, name = main}

NSBlockOperation的使用
把任务放到NSBlockOperation的block中,然后start。

- (void)testNSBlockOperation {
    // 把任务放到block中
    NSBlockOperation *blockOperation = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"NSBlockOperation包含的任务,没有加入队列========%@", [NSThread currentThread]);
    }];
 
    [blockOperation start];
}

执行结果如下,可以看出:主线程执行,没有开启新线程。

同样的,NSBlockOperation可以配合队列NSOperationQueue来实现多线程。

NSBlockOperation包含的任务,没有加入队列========{number = 1, name = main}

但是NSBlockOperation有一个方法addExecutionBlock:,通过这个方法可以让NSBlockOperation实现多线程。

- (void)testNSBlockOperationExecution {
    NSBlockOperation *blockOperation = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"NSBlockOperation运用addExecutionBlock主任务========%@", [NSThread currentThread]);
    }];
 
    [blockOperation addExecutionBlock:^{
        NSLog(@"NSBlockOperation运用addExecutionBlock方法添加任务1========%@", [NSThread currentThread]);
    }];
    [blockOperation addExecutionBlock:^{
        NSLog(@"NSBlockOperation运用addExecutionBlock方法添加任务2========%@", [NSThread currentThread]);
    }];
    [blockOperation addExecutionBlock:^{
        NSLog(@"NSBlockOperation运用addExecutionBlock方法添加任务3========%@", [NSThread currentThread]);
    }];
    [blockOperation start];
}

执行结果如下,可以看出,NSBlockOperation创建时block中的任务是在主线程执行,而运用addExecutionBlock加入的任务是在子线程执行的。

NSBlockOperation运用addExecutionBlock========{number = 1, name = main}
addExecutionBlock方法添加任务1========{number = 3, name = (null)}
addExecutionBlock方法添加任务3========{number = 5, name = (null)}
addExecutionBlock方法添加任务2========{number = 4, name = (null)}

运用继承自NSOperation的子类
首先我们定义一个继承自NSOperation的类,然后重写它的main方法,之后就可以使用这个子类来进行相关的操作了。

/*******************"WHOperation.h"*************************/
 
#import @interface WHOperation : NSOperation
 
@end
 
 
/*******************"WHOperation.m"*************************/
 
#import "WHOperation.h"
 
@implementation WHOperation
 
- (void)main {
    for (int i = 0; i < 3; i++) {
        NSLog(@"NSOperation的子类WHOperation======%@",[NSThread currentThread]);
    }
}

@end
 
/*****************回到主控制器使用WHOperation**********************/
 
- (void)testWHOperation {
    WHOperation *operation = [[WHOperation alloc] init];
    [operation start];
}

运行结果如下,依然是在主线程执行。

SOperation的子类WHOperation======{number = 1, name = main}
NSOperation的子类WHOperation======{number = 1, name = main}
NSOperation的子类WHOperation======{number = 1, name = main}

所以,NSOperation是需要配合队列NSOperationQueue来实现多线程的。下面就来说一下队列NSOperationQueue。

No.3:队列NSOperationQueue

NSOperationQueue只有两种队列:主队列、其他队列。其他队列包含了串行和并发。

主队列的创建如下,主队列上的任务是在主线程执行的。

1
NSOperationQueue *mainQueue = [NSOperationQueue mainQueue];
其他队列(非主队列)的创建如下,加入到‘非队列’中的任务默认就是并发,开启多线程。

1
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
注意:

非主队列(其他队列)可以实现串行或并行。
队列NSOperationQueue有一个参数叫做最大并发数:maxConcurrentOperationCount。
maxConcurrentOperationCount默认为-1,直接并发执行,所以加入到‘非队列’中的任务默认就是并发,开启多线程。
当maxConcurrentOperationCount为1时,则表示不开线程,也就是串行。
当maxConcurrentOperationCount大于1时,进行并发执行。
系统对最大并发数有一个限制,所以即使程序员把maxConcurrentOperationCount设置的很大,系统也会自动调整。所以把最大并发数设置的很大是没有意义的。
No.4:NSOperation + NSOperationQueue

把任务加入队列,这才是NSOperation的常规使用方式。

addOperation添加任务到队列
先创建好任务,然后运用- (void)addOperation:(NSOperation *)op 方法来吧任务添加到队列中,示例代码如下:

- (void)testOperationQueue {
    // 创建队列,默认并发
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
 
    // 创建操作,NSInvocationOperation
    NSInvocationOperation *invocationOperation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(invocationOperationAddOperation) object:nil];
    // 创建操作,NSBlockOperation
    NSBlockOperation *blockOperation = [NSBlockOperation blockOperationWithBlock:^{
        for (int i = 0; i < 3; i++) {
            NSLog(@"addOperation把任务添加到队列======%@", [NSThread currentThread]);
        }
    }];
 
    [queue addOperation:invocationOperation];
    [queue addOperation:blockOperation];
}
 
- (void)invocationOperationAddOperation {
    NSLog(@"invocationOperation===aaddOperation把任务添加到队列====%@", [NSThread currentThread]);
}

运行结果如下,可以看出,任务都是在子线程执行的,开启了新线程!

invocationOperation===addOperation把任务添加到队列===={number = 4, name = (null)}
addOperation把任务添加到队列======{number = 3, name = (null)}
addOperation把任务添加到队列======{number = 3, name = (null)}
addOperation把任务添加到队列======{number = 3, name = (null)}

addOperationWithBlock添加任务到队列
这是一个更方便的把任务添加到队列的方法,直接把任务写在block中,添加到任务中。

- (void)testAddOperationWithBlock {
    // 创建队列,默认并发
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
 
    // 添加操作到队列
    [queue addOperationWithBlock:^{
        for (int i = 0; i < 3; i++) {
            NSLog(@"addOperationWithBlock把任务添加到队列======%@", [NSThread currentThread]);
        }
    }];
}

运行结果如下,任务确实是在子线程中执行。

addOperationWithBlock把任务添加到队列======{number = 3, name = (null)}
addOperationWithBlock把任务添加到队列======{number = 3, name = (null)}
addOperationWithBlock把任务添加到队列======{number = 3, name = (null)}

运用最大并发数实现串行
上面已经说过,可以运用队列的属性maxConcurrentOperationCount(最大并发数)来实现串行,值需要把它设置为1就可以了,下面我们通过代码验证一下。

- (void)testMaxConcurrentOperationCount {
    // 创建队列,默认并发
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
 
    // 最大并发数为1,串行
    queue.maxConcurrentOperationCount = 1;
 
    // 最大并发数为2,并发
//    queue.maxConcurrentOperationCount = 2;
 
 
    // 添加操作到队列
    [queue addOperationWithBlock:^{
        for (int i = 0; i < 3; i++) {
            NSLog(@"addOperationWithBlock把任务添加到队列1======%@", [NSThread currentThread]);
        }
    }];
    // 添加操作到队列
    [queue addOperationWithBlock:^{
        for (int i = 0; i < 3; i++) {
            NSLog(@"addOperationWithBlock把任务添加到队列2======%@", [NSThread currentThread]);
        }
    }];
 
    // 添加操作到队列
    [queue addOperationWithBlock:^{
        for (int i = 0; i < 3; i++) {
            NSLog(@"addOperationWithBlock把任务添加到队列3======%@", [NSThread currentThread]);
        }
    }];
}

运行结果如下,当最大并发数为1的时候,虽然开启了线程,但是任务是顺序执行的,所以实现了串行。

你可以尝试把上面的最大并发数变为2,会发现任务就变成了并发执行。

addOperationWithBlock把任务添加到队列1======{number = 3, name = (null)}
addOperationWithBlock把任务添加到队列1======{number = 3, name = (null)}
addOperationWithBlock把任务添加到队列1======{number = 3, name = (null)}
addOperationWithBlock把任务添加到队列2======{number = 3, name = (null)}
addOperationWithBlock把任务添加到队列2======{number = 3, name = (null)}
addOperationWithBlock把任务添加到队列2======{number = 3, name = (null)}
addOperationWithBlock把任务添加到队列3======{number = 3, name = (null)}
addOperationWithBlock把任务添加到队列3======{number = 3, name = (null)}
addOperationWithBlock把任务添加到队列3======{number = 3, name = (null)}

No.5:NSOperation的其他操作

取消队列NSOperationQueue的所有操作,NSOperationQueue对象方法

- (void)cancelAllOperations

取消NSOperation的某个操作,NSOperation对象方法

- (void)cancel

使队列暂停或继续

// 暂停队列
[queue setSuspended:YES];

判断队列是否暂停

- (BOOL)isSuspended

暂停和取消不是立刻取消当前操作,而是等当前的操作执行完之后不再进行新的操作。

No.6:NSOperation的操作依赖

NSOperation有一个非常好用的方法,就是操作依赖。可以从字面意思理解:某一个操作(operation2)依赖于另一个操作(operation1),只有当operation1执行完毕,才能执行operation2,这时,就是操作依赖大显身手的时候了。

- (void)testAddDependency {
 
    // 并发队列
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
 
    // 操作1
    NSBlockOperation *operation1 = [NSBlockOperation blockOperationWithBlock:^{
        for (int i = 0; i < 3; i++) {
            NSLog(@"operation1======%@", [NSThread  currentThread]);
        }
    }];
 
    // 操作2
    NSBlockOperation *operation2 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"****operation2依赖于operation1,只有当operation1执行完毕,operation2才会执行****");
        for (int i = 0; i < 3; i++) {
            NSLog(@"operation2======%@", [NSThread  currentThread]);
        }
    }];
 
    // 使操作2依赖于操作1
    [operation2 addDependency:operation1];
    // 把操作加入队列
    [queue addOperation:operation1];
    [queue addOperation:operation2];
}

运行结果如下,操作2总是在操作1之后执行,成功验证了上面的说法。

operation1======{number = 3, name = (null)}
operation1======{number = 3, name = (null)}
operation1======{number = 3, name = (null)}
****operation2依赖于operation1,只有当operation1执行完毕,operation2才会执行****
operation2======{number = 4, name = (null)}
operation2======{number = 4, name = (null)}
operation2======{number = 4, name = (null)}

转载于:http://www.jianshu.com/p/7649fad15cdb

07/10/2017 08:46 上午 posted in  Thread

xcode4的环境变量,Build Settings参数,workspace及联编设置

##一、xcode4中的环境变量

$(BUILT_PRODUCTS_DIR)

build成功后的,最终产品路径--可以在Build Settings参数的Per-configuration Build Products Path项里设置

$(TARGET_NAME)

目标工程名称

$(SRCROOT)

工程文件(比如Nuno.xcodeproj)的路径

$(CURRENT_PROJECT_VERSION)

当前工程版本号

其他:

当编译静态库,设备选模拟器(iPhone 5.0 Simulator),未设置任何Build Settings参数时,默认的基础路径:

/Users/xxx/Library/Developer/Xcode/DerivedData/xxxWorkspace-caepeadwrerdcrftijaolkkagbjf

下面用$()代替上面一长串东东

$(SYMROOT) = $()/Build/Products

$(BUILD_DIR) = $()/Build/Products

$(BUILD_ROOT) = $()/Build/Products

这三个变量中的$()不会随着Build Settings参数的设置而改变

相反,以下可以通过设置而改变

$(CONFIGURATION_BUILD_DIR) = $()/Build/Products/Debug-iphonesimulator

$(BUILT_PRODUCTS_DIR) = $()/Build/Products/Debug-iphonesimulator

$(CONFIGURATION_TEMP_DIR) = $()/Build/Intermediates/UtilLib.build/Debug-iphonesimulator

$(TARGET_BUILD_DIR) = $()/Build/Products/Debug-iphonesimulator

$(SDK_NAME) = iphonesimulator5.0

$(PLATFORM_NAME) = iphonesimulator

$(CONFIGURATION) = Debug

$(TARGET_NAME) = UtilLib

$(EXECUTABLE_NAME) = libUtilLib.a 可执行文件名

${IPHONEOS_DEPLOYMENT_TARGET} 5.0

$(ACTION) = build

$(CURRENTCONFIG_SIMULATOR_DIR) 当前模拟器路径 

$(CURRENTCONFIG_DEVICE_DIR) 当前设备路径 


$(BUILD_DIR)/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME =

$()/Build/Products/Debug-iphonesimulator

$(PROJECT_TEMP_DIR)/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) = $()/Build/Intermediates/UtilLib.build/Debug-iphonesimulator

自定义变量

${CONFIGURATION}-iphoneos 表示:Debug-iphoneos

${CONFIGURATION}-iphonesimulator 表示:Debug-iphonesimulator

$(CURRENTCONFIG_DEVICE_DIR) = ${SYMROOT}/${CONFIGURATION}-iphoneos

$(CURRENTCONFIG_SIMULATOR_DIR) = ${SYMROOT}/${CONFIGURATION}-iphonesimulator

自定义一个设备无关的路径(用来存放各种架构arm6/arm7/i386输出的产品)

$(CREATING_UNIVERSAL_DIR) = ${SYMROOT}/${CONFIGURATION}-universal

自定义变量代表的值

$(CURRENTCONFIG_DEVICE_DIR) = $()/Build/Products/Debug-iphoneos

$(CURRENTCONFIG_SIMULATOR_DIR) = $()/Build/Products/Debug-iphonesimulator

$(CREATING_UNIVERSAL_DIR) = $()/Build/Products/Debug-universal

iphoneos5.0下的编译脚本:

xcodebuild -project "UtilLib.xcodeproj" -configuration "Debug" -target "UtilLib" -sdk "iphoneos5.0" -arch "armv6 armv7" build RUN_CLANG_STATIC_ANALYZER=NO  $(BUILD_DIR)="${BUILD_DIR}" BUILD_ROOT="${BUILD_ROOT}"

iphonesimulator5.0下的编译脚本:

xcodebuild -project "UtilLib.xcodeproj" -configuration "Debug" -target "UtilLib" -sdk "iphonesimulator5.0" -arch "i386" build RUN_CLANG_STATIC_ANALYZER=NO $(BUILD_DIR)="${BUILD_DIR}"  BUILD_ROOT="${BUILD_ROOT}"

加上下面一句表示输出到文件:

> "${BUILD_ROOT}.build_output"

lipo脚本工具:合并iPhone模拟器和真机的静态类库,生成通用库

`lipo -create -output "${CREATING_UNIVERSAL_DIR}/${EXECUTABLE_NAME}" "${CURRENTCONFIG_DEVICE_DIR}/${EXECUTABLE_NAME}"         "${CURRENTCONFIG_SIMULATOR_DIR}/${EXECUTABLE_NAME}"

意思是:把"${CURRENTCONFIG_DEVICE_DIR}目录下的.a文件,和${CURRENTCONFIG_SIMULATOR_DIR}目录下的.a文件合并,

在${CREATING_UNIVERSAL_DIR}目录下,生成两个设备都通用的静态库,

例如:lipo -create -output xy.a x.a y.a

##二、xcode4中build Settings常见参数解析

1.Installation Directory:安装路径
静态库编译时,在Build Settings中Installation Directory设置“$(BUILT_PRODUCTS_DIR)”

Skip Install设为YES
Installation Directory默认为/usr/local/lib
因为Build Location默认时,.a文件会放在很长(比如:/Users/xxx/Library/Developer/Xcode/DerivedData/xxxProgram

dalrvzehhtesxdfqhxixzafvddwe/Build/Products/Debug-iPhoneos)的路径下,或是我们target指定的路径
Skip Install如果是NO,可能会被安装到默认路径/usr/local/lib

2.Public Headers Folder Path:对外公开头文件路径
设为“include”(具体的头文件路径为:$(BUILT_PRODUCTS_DIR)/include/xx.h)
在最终文件.a同级目录下生成一个include目录
默认:/usr/local/include
Public Headers Folder Path这个路径就是使用这lib的某工程需要依赖的外部头文件.导入这路径后,#include/import "xx.h"才能看到

3.User Header Search Paths:依赖的外部头文件搜索路径
设置为“$(BUILT_PRODUCTS_DIR)/include”
和2中路径对应

4.Per-configuration Build Products Path:最终文件路径
比如设为“../app”,就会在工程文件.xcodeproj上一层目录下的app目录里,创建最终文件
默认为$(BUILD_DIR)/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)
等于$(BUILT_PRODUCTS_DIR)

5.Per-configuration Intermediate Build Files Path:临时中间文件路径
默认为:$(PROJECT_TEMP_DIR)/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)

6.Code Signing Identity:真机调试的证书选择
选一个和Bundle identifier相对应的证书
Library Search Paths:库搜索路径
Architectures:架构,设为 armv6 或 armv7
Valid Architectures:应用框架,可以设为 armv6、 armv7 或i386
Product Name:工程文件名,默认为$(TARGET_NAME)
Info.plist File:info文件路径
Build Variants:默认为normal
Other Linker Flags:其他链接标签
设为“-ObjC”

当导入的静态库使用了类别,需要设为-ObjC
iOS Deployment Target:ios部署对象
比如可以选择设为,ios3到ios5的一种版本
Prefix Header:预编头文件(比如:UtilLib/UtilLib-Prefix.pch)
Precompile Prefix Header:设为“Yes”,表示允许加入预编译头

##三、workspace(工作区)

作用:管理多个工程(project),多工程联编

四、workspace多工程联编设置

一、

1.新建一个静态库工程,比如UtilLib,并生成UtilLib.h和UtilLib.m文件

2.选中需要公开的头文件,

把右侧栏的Target Membership中设置为public

或则,选中工程目录target的Build Phases标签的copy headers项,在public中添加要公开的头文件

3.Architectures设为:armv6 armv7

4.Valid Architectures设为:armv6 armv7 i386

5.Build Products Path设为:$(SRCROOT)/../build

6.Per-configuration Build Products Path设为:

$(SRCROOT)/../build/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)

7.Per-configuration Intermediate Build Files Path设为:

$(SRCROOT)/../build/$(TARGET_NAME).build/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)

8.设置安装路径:Installation Directory项

9.设置对外公开的头文件路径:Public Headers Folder Path项

10.为静态库添加依赖的shell脚本

选中工程目录target的Build Phases标签,点击由下角的Add Build Phase按钮

在弹出的菜单里选择Add run script项,然后页面中会多出一个Run Script项
在黑框里填写"$SRCROOT/mergeArmSymbols.sh"

建立对此脚本的依赖(编译静态库的后会运行此脚本)

如果编译时设备选的是iphone simulator:

则此脚本会在对应iphone device的产品目录Debug-iphoneos中,生成对device有用的.a静态库,

相反,如果设备选的是iphone device:

则此脚本会在对应iphone simulator的产品目录Debug-iphoneos中,生成对simulator有用的.a静态库

最后,此脚本调用lipo工具,把本工程生成静态库与此脚本生成的静态库合并,生成simulator和device都通用的.a文件

11.具体bash shell脚本如下:

mergeArmSymbols.sh
# Version 2.0 (updated for Xcode 4, with some fixes)

# Author: Adam Martin - http://twitter.com/redglassesapps
# Based on: original script from Eonil (main changes: Eonil's script WILL NOT WORK in Xcode GUI - it WILL CRASH YOUR COMPUTER)
#
# More info: see this Stack Overflow question: http://stackoverflow.com/questions/3520977/build-fat-static-library-device-simulator-using-xcode-and-sdk-4

#################[ Tests: helps workaround any future bugs in Xcode ]########
#
DEBUG_THIS_SCRIPT="true"

if [ $DEBUG_THIS_SCRIPT = "true" ]
then
echo "########### TESTS #############"
echo "Use the following variables when debugging this script; note that they may change on recursions"
echo "BUILD_DIR = $BUILD_DIR"
echo "BUILD_ROOT = $BUILD_ROOT"
echo "CONFIGURATION_BUILD_DIR = $CONFIGURATION_BUILD_DIR"
echo "BUILT_PRODUCTS_DIR = $BUILT_PRODUCTS_DIR"
echo "CONFIGURATION_TEMP_DIR = $CONFIGURATION_TEMP_DIR"
echo "TARGET_BUILD_DIR = $TARGET_BUILD_DIR"
echo "SDK_NAME = $SDK_NAME"
echo "PLATFORM_NAME = $PLATFORM_NAME"
echo "CONFIGURATION = $CONFIGURATION"
echo "TARGET_NAME = $TARGET_NAME"
echo "ARCH_TO_BUILD = $ARCH_TO_BUILD"
echo "ARCH_TO_BUILD = $ARCH_TO_BUILD"
echo "ACTION = $ACTION"
echo "SYMROOT = $SYMROOT"
echo "EXECUTABLE_NAME = $EXECUTABLE_NAME"
echo "CURRENTCONFIG_SIMULATOR_DIR = $CURRENTCONFIG_SIMULATOR_DIR"
echo "CURRENTCONFIG_DEVICE_DIR = $CURRENTCONFIG_DEVICE_DIR"

echo "#############Other###########"
echo "BUILD_DIR/CONFIGURATION/EFFECTIVE_PLATFORM_NAME = $BUILD_DIR/$CONFIGURATION$EFFECTIVE_PLATFORM_NAME"

echo "PROJECT_TEMP_DIR/CONFIGURATION/EFFECTIVE_PLATFORM_NAME = $PROJECT_TEMP_DIR/$CONFIGURATION$EFFECTIVE_PLATFORM_NAME"

fi

#####################[ part 1 ]##################
# First, work out the BASESDK version number
#    (incidental: searching for substrings in sh is a nightmare! Sob)

SDK_VERSION=$(echo ${SDK_NAME} | grep -o '.\{3\}$')

# Next, work out if we're in SIM or DEVICE

if [ ${PLATFORM_NAME} = "iphonesimulator" ]
then
OTHER_SDK_TO_BUILD=iphoneos${SDK_VERSION}
ARCH_TO_BUILD="armv6 armv7"
else
OTHER_SDK_TO_BUILD=iphonesimulator${SDK_VERSION}
ARCH_TO_BUILD="i386"
fi

echo "XCode has selected SDK: ${PLATFORM_NAME} with version: ${SDK_VERSION} (although back-targetting: ${IPHONEOS_DEPLOYMENT_TARGET})"
echo "...therefore, OTHER_SDK_TO_BUILD = ${OTHER_SDK_TO_BUILD}"
#
#####################[ end of part 1 ]##################

#####################[ part 2 ]##################
#
# IF this is the original invocation, invoke whatever other builds are required
#
# Xcode is already building ONE target... build ONLY the missing platforms/configurations.

if [ "true" == ${ALREADYINVOKED:-false} ]
then
echo "RECURSION: Not the root invocation, don't recurse"
else
# Prevent recursion
export ALREADYINVOKED="true"

echo "RECURSION: I am the root... recursing all missing build targets..."
echo "RECURSION: ...about to invoke: xcodebuild -configuration \"${CONFIGURATION}\" -target \"${TARGET_NAME}\" -sdk \"${OTHER_SDK_TO_BUILD}\" -arch \"${ARCH_TO_BUILD}\" ${ACTION} RUN_CLANG_STATIC_ANALYZER=NO"
xcodebuild -project "${TARGET_NAME}.xcodeproj" -configuration "${CONFIGURATION}" -target "${TARGET_NAME}" -sdk "${OTHER_SDK_TO_BUILD}" -arch "${ARCH_TO_BUILD}" ${ACTION} RUN_CLANG_STATIC_ANALYZER=NO BUILD_DIR="${BUILD_DIR}" BUILD_ROOT="${BUILD_ROOT}" > "${BUILD_ROOT}.build_output"
ACTION="build"

# Merge all platform binaries as a fat binary for each configurations.

# Calculate where the (multiple) built files are coming from:
CURRENTCONFIG_DEVICE_DIR=${SRCROOT}/../build/${CONFIGURATION}-iphoneos
CURRENTCONFIG_SIMULATOR_DIR=${SRCROOT}/../build/${CONFIGURATION}-iphonesimulator

echo "Taking device build from: ${CURRENTCONFIG_DEVICE_DIR}"
echo "Taking simulator build from: ${CURRENTCONFIG_SIMULATOR_DIR}"

CREATING_UNIVERSAL_DIR=${SRCROOT}/../build/${CONFIGURATION}-universal
echo "...outputing a universal arm6/arm7/i386 build to: ${CREATING_UNIVERSAL_DIR}"

# ... remove the products of previous runs of this script
#      NB: this directory is only created by this script - it should be safe to delete

rm -rf "${CREATING_UNIVERSAL_DIR}"
mkdir "${CREATING_UNIVERSAL_DIR}"

#
echo "lipo: for current configuration (${CONFIGURATION}) creating output file: ${CREATING_UNIVERSAL_DIR}/${EXECUTABLE_NAME}"
lipo -create -output "${CREATING_UNIVERSAL_DIR}/${EXECUTABLE_NAME}" "${CURRENTCONFIG_DEVICE_DIR}/${EXECUTABLE_NAME}" "${CURRENTCONFIG_SIMULATOR_DIR}/${EXECUTABLE_NAME}"

#######custom########
#copy universal lib to ../libs
libsDir=../libs
includeDir=../include

rm -rf "${libsDir}"
mkdir -p "${libsDir}"

echo "cp -R ${CREATING_UNIVERSAL_DIR}/${EXECUTABLE_NAME} ${libsDir}"

cp -R "${CREATING_UNIVERSAL_DIR}/${EXECUTABLE_NAME}" "${libsDir}"

echo "cp -R ${CURRENTCONFIG_DEVICE_DIR}/include ${includeDir}"

cp -R "${CURRENTCONFIG_DEVICE_DIR}/include" "${includeDir}"

fi
05/18/2017 13:53 下午 posted in  Xcode

Xcode环境变量

官网链接:打开

Variable                                  Example
PATH                                      "/Developer/Platforms/iPhoneOS.platform/Developer/usr/bin:/Developer/usr/bin:/usr/bin:/bin:/usr/sbin:/sbin"
LANG                                      en_US.US-ASCII
IPHONEOS_DEPLOYMENT_TARGET                4.1
ACTION                                    build
AD_HOC_CODE_SIGNING_ALLOWED               NO
ALTERNATE_GROUP                           staff
ALTERNATE_MODE                            u+w,go-w,a+rX
ALTERNATE_OWNER                           username
ALWAYS_SEARCH_USER_PATHS                  YES
APPLE_INTERNAL_DEVELOPER_DIR              /AppleInternal/Developer
APPLE_INTERNAL_DIR                        /AppleInternal
APPLE_INTERNAL_DOCUMENTATION_DIR          /AppleInternal/Documentation
APPLE_INTERNAL_LIBRARY_DIR                /AppleInternal/Library
APPLE_INTERNAL_TOOLS                      /AppleInternal/Developer/Tools
APPLY_RULES_IN_COPY_FILES                 NO
ARCHS                                     "armv6 armv7"
ARCHS_STANDARD_32_64_BIT                  "armv6 armv7"
ARCHS_STANDARD_32_BIT                     "armv6 armv7"
ARCHS_UNIVERSAL_IPHONE_OS                 armv7
AVAILABLE_PLATFORMS                       "iphonesimulator macosx iphoneos"
BUILD_COMPONENTS                          "headers build"
BUILD_DIR                                 "/Users/username/Library/Developer/Xcode/DerivedData/project-dxdgjvgsvvbhowgjqouevhmvgxgf/ArchiveIntermediates/Project Distribution/BuildProductsPath"
BUILD_ROOT                                "/Users/username/Library/Developer/Xcode/DerivedData/project-dxdgjvgsvvbhowgjqouevhmvgxgf/ArchiveIntermediates/Project Distribution/BuildProductsPath"
BUILD_STYLE
BUILD_VARIANTS                             normal
BUILT_PRODUCTS_DIR                         "/Users/username/Library/Developer/Xcode/DerivedData/project-dxdgjvgsvvbhowgjqouevhmvgxgf/ArchiveIntermediates/Project Distribution/BuildProductsPath/Distribution-iphoneos"
CACHE_ROOT                                 /var/folders/2x/rvb2r9s16mq6r318zxvn0lk80000gn/C/com.apple.Xcode.501
CCHROOT                                    /var/folders/2x/rvb2r9s16mq6r318zxvn0lk80000gn/C/com.apple.Xcode.501
CHMOD                                      /bin/chmod
CHOWN                                      /usr/sbin/chown
CLASS_FILE_DIR                             "/Users/username/Library/Developer/Xcode/DerivedData/project-dxdgjvgsvvbhowgjqouevhmvgxgf/ArchiveIntermediates/Project Distribution/IntermediateBuildFilesPath/project.build/Distribution-iphoneos/Project.build/JavaClasses"
CLEAN_PRECOMPS                             YES
CLONE_HEADERS                              NO
CODESIGNING_FOLDER_PATH                    "/Users/username/Library/Developer/Xcode/DerivedData/project-dxdgjvgsvvbhowgjqouevhmvgxgf/ArchiveIntermediates/Project Distribution/InstallationBuildProductsLocation/Applications/project.app"
CODE_SIGNING_ALLOWED                       YES
CODE_SIGNING_REQUIRED                      YES
CODE_SIGN_CONTEXT_CLASS                    XCiPhoneOSCodeSignContext
CODE_SIGN_IDENTITY                         "iPhone Distribution"
COMBINE_HIDPI_IMAGES                       NO
COMPOSITE_SDK_DIRS                         /var/folders/2x/rvb2r9s16mq6r318zxvn0lk80000gn/C/com.apple.Xcode.501/CompositeSDKs
COMPRESS_PNG_FILES                         YES
CONFIGURATION                              Distribution
CONFIGURATION_BUILD_DIR                    "/Users/username/Library/Developer/Xcode/DerivedData/project-dxdgjvgsvvbhowgjqouevhmvgxgf/ArchiveIntermediates/Project Distribution/BuildProductsPath/Distribution-iphoneos"
CONFIGURATION_TEMP_DIR                     "/Users/username/Library/Developer/Xcode/DerivedData/project-dxdgjvgsvvbhowgjqouevhmvgxgf/ArchiveIntermediates/Project Distribution/IntermediateBuildFilesPath/project.build/Distribution-iphoneos"
CONTENTS_FOLDER_PATH                       project.app/Contents
COPYING_PRESERVES_HFS_DATA                 NO
COPY_PHASE_STRIP                           YES
COPY_RESOURCES_FROM_STATIC_FRAMEWORKS      YES
CP                                         /bin/cp
CURRENT_ARCH                               armv7
CURRENT_VARIANT                            normal
DEAD_CODE_STRIPPING                        YES
DEBUGGING_SYMBOLS                          YES
DEBUG_INFORMATION_FORMAT                   dwarf-with-dsym
DEPLOYMENT_LOCATION                        YES
DEPLOYMENT_POSTPROCESSING                  YES
DERIVED_FILES_DIR                          "/Users/username/Library/Developer/Xcode/DerivedData/project-dxdgjvgsvvbhowgjqouevhmvgxgf/ArchiveIntermediates/Project Distribution/IntermediateBuildFilesPath/project.build/Distribution-iphoneos/Project.build/DerivedSources"
DERIVED_FILE_DIR                           "/Users/username/Library/Developer/Xcode/DerivedData/project-dxdgjvgsvvbhowgjqouevhmvgxgf/ArchiveIntermediates/Project Distribution/IntermediateBuildFilesPath/project.build/Distribution-iphoneos/Project.build/DerivedSources"
DERIVED_SOURCES_DIR                        "/Users/username/Library/Developer/Xcode/DerivedData/project-dxdgjvgsvvbhowgjqouevhmvgxgf/ArchiveIntermediates/Project Distribution/IntermediateBuildFilesPath/project.build/Distribution-iphoneos/Project.build/DerivedSources"
DEVELOPER_APPLICATIONS_DIR                 /Developer/Applications
DEVELOPER_BIN_DIR                          /Developer/usr/bin
DEVELOPER_DIR                              /Developer
DEVELOPER_FRAMEWORKS_DIR                   /Developer/Library/Frameworks
DEVELOPER_FRAMEWORKS_DIR_QUOTED            "\"/Developer/Library/Frameworks\""
DEVELOPER_LIBRARY_DIR                      /Developer/Library
DEVELOPER_SDK_DIR                          /Developer/SDKs
DEVELOPER_TOOLS_DIR                        /Developer/Tools
DEVELOPER_USR_DIR                          /Developer/usr
DEVELOPMENT_LANGUAGE                       English
DOCUMENTATION_FOLDER_PATH                  project.app/English.lproj/Documentation
DO_HEADER_SCANNING_IN_JAM                  NO
DSTROOT                                    "/Users/username/Library/Developer/Xcode/DerivedData/project-dxdgjvgsvvbhowgjqouevhmvgxgf/ArchiveIntermediates/Project Distribution/InstallationBuildProductsLocation"
DWARF_DSYM_FILE_NAME                       project.app.dSYM
DWARF_DSYM_FILE_SHOULD_ACCOMPANY_PRODUCT   NO
DWARF_DSYM_FOLDER_PATH                    "/Users/username/Library/Developer/Xcode/DerivedData/project-dxdgjvgsvvbhowgjqouevhmvgxgf/ArchiveIntermediates/Project Distribution/BuildProductsPath/Distribution-iphoneos"
EFFECTIVE_PLATFORM_NAME                    -iphoneos
EMBEDDED_PROFILE_NAME                      embedded.mobileprovision
ENABLE_HEADER_DEPENDENCIES                 YES
ENABLE_OPENMP_SUPPORT                      NO
ENTITLEMENTS_ALLOWED                       YES
ENTITLEMENTS_REQUIRED                      YES
EXCLUDED_INSTALLSRC_SUBDIRECTORY_PATTERNS  ".svn CVS"
EXECUTABLES_FOLDER_PATH                    project.app/Executables
EXECUTABLE_FOLDER_PATH                     project.app
EXECUTABLE_NAME                            project
EXECUTABLE_PATH                            project.app/project
FILE_LIST                                  "/Users/username/Library/Developer/Xcode/DerivedData/project-dxdgjvgsvvbhowgjqouevhmvgxgf/ArchiveIntermediates/Project Distribution/IntermediateBuildFilesPath/project.build/Distribution-iphoneos/Project.build/Objects/LinkFileList"
FIXED_FILES_DIR                            "/Users/username/Library/Developer/Xcode/DerivedData/project-dxdgjvgsvvbhowgjqouevhmvgxgf/ArchiveIntermediates/Project Distribution/IntermediateBuildFilesPath/project.build/Distribution-iphoneos/Project.build/FixedFiles"
FRAMEWORKS_FOLDER_PATH                     project.app/Frameworks
FRAMEWORK_FLAG_PREFIX                      -framework
FRAMEWORK_SEARCH_PATHS                     "\"/Users/username/Library/Developer/Xcode/DerivedData/project-dxdgjvgsvvbhowgjqouevhmvgxgf/ArchiveIntermediates/Project Distribution/BuildProductsPath/Distribution-iphoneos\" "
FRAMEWORK_VERSION                          A
FULL_PRODUCT_NAME                          project.app
GCC3_VERSION                               3.3
GCC_C_LANGUAGE_STANDARD                    gnu99
GCC_INLINES_ARE_PRIVATE_EXTERN             YES
GCC_PFE_FILE_C_DIALECTS                    "c objective-c c++ objective-c++"
GCC_PRECOMPILE_PREFIX_HEADER               YES
GCC_PREFIX_HEADER                          project/Prefix.pch
GCC_PREPROCESSOR_DEFINITIONS               "NDEBUG DISTRIBUTION_BUILD=1 KK_TARGET=0x000F0"
GCC_SYMBOLS_PRIVATE_EXTERN                 YES
GCC_THUMB_SUPPORT                          YES
GCC_TREAT_WARNINGS_AS_ERRORS               NO
GCC_VERSION                                com.apple.compilers.llvm.clang.1_0
GCC_VERSION_IDENTIFIER                     com_apple_compilers_llvm_clang_1_0
GCC_WARN_ABOUT_RETURN_TYPE                 YES
GCC_WARN_UNUSED_FUNCTION                   YES
GCC_WARN_UNUSED_VARIABLE                   YES
GENERATE_MASTER_OBJECT_FILE                NO
GENERATE_PKGINFO_FILE                      YES
GENERATE_PROFILING_CODE                    NO
GID                                        20
GROUP                                      staff
INPUT_FILE_BASE             Default
INPUT_FILE_DIR              "/Volumes/Development/Project Game/Project-v1/images"
INPUT_FILE_NAME             Default.png
INPUT_FILE_PATH             "/Volumes/Development/Project Game/Project-v1/images/Default.png"
SCRIPT_INPUT_FILE           "/Volumes/Development/Project Game/Project-v1/images/Default.png"
SCRIPT_OUTPUT_FILE_0        "/Users/username/Library/Developer/Xcode/DerivedData/project-dxdgjvgsvvbhowgjqouevhmvgxgf/ArchiveIntermediates/Project Distribution/IntermediateBuildFilesPath/project.build/Distribution-iphoneos/Project.build/DerivedSources/Default.png"

EXCLUDED_RECURSIVE_SEARCH_PATH_SUBDIRECTORIES              "*.nib *.lproj *.framework *.gch (*) CVS .svn .git *.xcodeproj *.xcode *.pbproj *.pbxproj"
HEADERMAP_INCLUDES_FLAT_ENTRIES_FOR_TARGET_BEING_BUILT     YES
HEADERMAP_INCLUDES_FRAMEWORK_ENTRIES_FOR_ALL_PRODUCT_TYPES YES
HEADERMAP_INCLUDES_NONPUBLIC_NONPRIVATE_HEADERS            YES
HEADERMAP_INCLUDES_PROJECT_HEADERS                         YES
HEADER_SEARCH_PATHS                  "\"/Users/username/Library/Developer/Xcode/DerivedData/project-dxdgjvgsvvbhowgjqouevhmvgxgf/ArchiveIntermediates/Project Distribution/BuildProductsPath/Distribution-iphoneos/include\" "
ICONV                                /usr/bin/iconv
INFOPLIST_EXPAND_BUILD_SETTINGS      YES
INFOPLIST_FILE                       project/Resources/Info.plist
INFOPLIST_OUTPUT_FORMAT              binary
INFOPLIST_PATH                       project.app/Info.plist
INFOPLIST_PREPROCESS                 NO
INFOSTRINGS_PATH                     project.app/English.lproj/InfoPlist.strings
INPUT_FILE_REGION_PATH_COMPONENT
INPUT_FILE_SUFFIX                    .png
INSTALL_DIR                          "/Users/username/Library/Developer/Xcode/DerivedData/project-dxdgjvgsvvbhowgjqouevhmvgxgf/ArchiveIntermediates/Project Distribution/InstallationBuildProductsLocation/Applications"
INSTALL_GROUP                        staff
INSTALL_MODE_FLAG                    u+w,go-w,a+rX
INSTALL_OWNER                        username
INSTALL_PATH                         /Applications
INSTALL_ROOT                         "/Users/username/Library/Developer/Xcode/DerivedData/project-dxdgjvgsvvbhowgjqouevhmvgxgf/ArchiveIntermediates/Project Distribution/InstallationBuildProductsLocation"
JAVAC_DEFAULT_FLAGS                  "-J-Xms64m -J-XX:NewSize=4M -J-Dfile.encoding=UTF8"
JAVA_APP_STUB                        /System/Library/Frameworks/JavaVM.framework/Resources/MacOS/JavaApplicationStub
JAVA_ARCHIVE_CLASSES                 YES
JAVA_ARCHIVE_TYPE                    JAR
JAVA_COMPILER                        /usr/bin/javac
JAVA_FOLDER_PATH                     project.app/Java
JAVA_FRAMEWORK_RESOURCES_DIRS        Resources
JAVA_JAR_FLAGS                       cv
JAVA_SOURCE_SUBDIR                   .
JAVA_USE_DEPENDENCIES                YES
JAVA_ZIP_FLAGS                       -urg
JIKES_DEFAULT_FLAGS                  "+E +OLDCSO"
KEEP_PRIVATE_EXTERNS                 NO
LD_GENERATE_MAP_FILE                 NO
LD_MAP_FILE_PATH                     "/Users/username/Library/Developer/Xcode/DerivedData/project-dxdgjvgsvvbhowgjqouevhmvgxgf/ArchiveIntermediates/Project Distribution/IntermediateBuildFilesPath/project.build/Distribution-iphoneos/Project.build/project-LinkMap-normal-armv7.txt"
LD_NO_PIE                            NO
LD_OPENMP_FLAGS                      -fopenmp
LEGACY_DEVELOPER_DIR                 /Developer/Library/Xcode/PrivatePlugIns/Xcode3Core.ideplugin/Contents/SharedSupport/Developer
LEX                                  /Developer/usr/bin/lex
LIBRARY_FLAG_NOSPACE                 YES
LIBRARY_FLAG_PREFIX                  -l
LIBRARY_SEARCH_PATHS                 "\"/Users/username/Library/Developer/Xcode/DerivedData/project-dxdgjvgsvvbhowgjqouevhmvgxgf/ArchiveIntermediates/Project Distribution/BuildProductsPath/Distribution-iphoneos\"  \"/Volumes/Development/Project Game/Project-v1/FlurryLib\""
LINKER_DISPLAYS_MANGLED_NAMES        NO
LINK_FILE_LIST_normal_armv6          "/Users/username/Library/Developer/Xcode/DerivedData/project-dxdgjvgsvvbhowgjqouevhmvgxgf/ArchiveIntermediates/Project Distribution/IntermediateBuildFilesPath/project.build/Distribution-iphoneos/Project.build/Objects-normal/armv6/project.LinkFileList"
LINK_FILE_LIST_normal_armv7          "/Users/username/Library/Developer/Xcode/DerivedData/project-dxdgjvgsvvbhowgjqouevhmvgxgf/ArchiveIntermediates/Project Distribution/IntermediateBuildFilesPath/project.build/Distribution-iphoneos/Project.build/Objects-normal/armv7/project.LinkFileList"
LINK_WITH_STANDARD_LIBRARIES         YES
LOCALIZED_RESOURCES_FOLDER_PATH      project.app/English.lproj
LOCAL_ADMIN_APPS_DIR                 /Applications/Utilities
LOCAL_APPS_DIR                       /Applications
LOCAL_DEVELOPER_DIR                  /Library/Developer
LOCAL_LIBRARY_DIR                    /Library
MACH_O_TYPE                          mh_execute
MAC_OS_X_PRODUCT_BUILD_VERSION       11A511
MAC_OS_X_VERSION_ACTUAL              1070
MAC_OS_X_VERSION_MAJOR               1070
MAC_OS_X_VERSION_MINOR               0700
NATIVE_ARCH                          armv6
NATIVE_ARCH_32_BIT                   i386
NATIVE_ARCH_64_BIT                   x86_64
NATIVE_ARCH_ACTUAL                   x86_64
NO_COMMON                            YES
OBJECT_FILE_DIR                      "/Users/username/Library/Developer/Xcode/DerivedData/project-dxdgjvgsvvbhowgjqouevhmvgxgf/ArchiveIntermediates/Project Distribution/IntermediateBuildFilesPath/project.build/Distribution-iphoneos/Project.build/Objects"
OBJECT_FILE_DIR_normal               "/Users/username/Library/Developer/Xcode/DerivedData/project-dxdgjvgsvvbhowgjqouevhmvgxgf/ArchiveIntermediates/Project Distribution/IntermediateBuildFilesPath/project.build/Distribution-iphoneos/Project.build/Objects-normal"
OBJROOT                              "/Users/username/Library/Developer/Xcode/DerivedData/project-dxdgjvgsvvbhowgjqouevhmvgxgf/ArchiveIntermediates/Project Distribution/IntermediateBuildFilesPath"
ONLY_ACTIVE_ARCH                     NO
OPTIMIZATION_LEVEL                   0
OS                                   MACOS
OSAC                                 /usr/bin/osacompile
OTHER_CFLAGS                         -DNS_BLOCK_ASSERTIONS=1
OTHER_CPLUSPLUSFLAGS                 -DNS_BLOCK_ASSERTIONS=1
OTHER_INPUT_FILE_FLAGS
OTHER_LDFLAGS                        -lz
PACKAGE_TYPE                         com.apple.package-type.wrapper.application
PASCAL_STRINGS                       YES
PATH_PREFIXES_EXCLUDED_FROM_HEADER_DEPENDENCIES  "/usr/include /usr/local/include /System/Library/Frameworks /System/Library/PrivateFrameworks /Developer/Headers /Developer/SDKs /Developer/Platforms"
PBDEVELOPMENTPLIST_PATH              project.app/pbdevelopment.plist
PFE_FILE_C_DIALECTS                  "c objective-c c++ objective-c++"
PKGINFO_FILE_PATH                    "/Users/username/Library/Developer/Xcode/DerivedData/project-dxdgjvgsvvbhowgjqouevhmvgxgf/ArchiveIntermediates/Project Distribution/IntermediateBuildFilesPath/project.build/Distribution-iphoneos/Project.build/PkgInfo"
PKGINFO_PATH                         project.app/PkgInfo
PLATFORM_DEVELOPER_APPLICATIONS_DIR  /Developer/Platforms/iPhoneOS.platform/Developer/Applications
PLATFORM_DEVELOPER_BIN_DIR           /Developer/Platforms/iPhoneOS.platform/Developer/usr/bin
PLATFORM_DEVELOPER_LIBRARY_DIR       /Developer/Library/Xcode/PrivatePlugIns/Xcode3Core.ideplugin/Contents/SharedSupport/Developer/Library
PLATFORM_DEVELOPER_SDK_DIR           /Developer/Platforms/iPhoneOS.platform/Developer/SDKs
PLATFORM_DEVELOPER_TOOLS_DIR         /Developer/Platforms/iPhoneOS.platform/Developer/Tools
PLATFORM_DEVELOPER_USR_DIR           /Developer/Platforms/iPhoneOS.platform/Developer/usr
PLATFORM_DIR                         /Developer/Platforms/iPhoneOS.platform
PLATFORM_NAME                        iphoneos
PLATFORM_PREFERRED_ARCH              i386
PLATFORM_PRODUCT_BUILD_VERSION       8H7
PLIST_FILE_OUTPUT_FORMAT             binary
PLUGINS_FOLDER_PATH                  project.app/PlugIns
PRECOMPS_INCLUDE_HEADERS_FROM_BUILT_PRODUCTS_DIR                    YES
PRECOMP_DESTINATION_DIR              "/Users/username/Library/Developer/Xcode/DerivedData/project-dxdgjvgsvvbhowgjqouevhmvgxgf/ArchiveIntermediates/Project Distribution/IntermediateBuildFilesPath/project.build/Distribution-iphoneos/Project.build/PrefixHeaders"
PRESERVE_DEAD_CODE_INITS_AND_TERMS   NO
PRIVATE_HEADERS_FOLDER_PATH          project.app/PrivateHeaders
PRODUCT_NAME                         project
PRODUCT_SETTINGS_PATH                "/Volumes/Development/Project Game/Project-v1/project/Resources/Info.plist"
PRODUCT_TYPE                         com.apple.product-type.application
PROFILING_CODE                       NO
PROJECT                              project
PROJECT_DERIVED_FILE_DIR             "/Users/username/Library/Developer/Xcode/DerivedData/project-dxdgjvgsvvbhowgjqouevhmvgxgf/ArchiveIntermediates/Project Distribution/IntermediateBuildFilesPath/project.build/DerivedSources"
PROJECT_DIR                          "/Volumes/Development/Project Game/Project-v1"
PROJECT_FILE_PATH                    "/Volumes/Development/Project Game/Project-v1/project.xcodeproj"
PROJECT_NAME                         project
PROJECT_TEMP_DIR                     "/Users/username/Library/Developer/Xcode/DerivedData/project-dxdgjvgsvvbhowgjqouevhmvgxgf/ArchiveIntermediates/Project Distribution/IntermediateBuildFilesPath/project.build"
PROVISIONING_PROFILE_REQUIRED        YES
PUBLIC_HEADERS_FOLDER_PATH           project.app/Headers
RECURSIVE_SEARCH_PATHS_FOLLOW_SYMLINKS   YES
REMOVE_CVS_FROM_RESOURCES            YES
REMOVE_GIT_FROM_RESOURCES            YES
REMOVE_SVN_FROM_RESOURCES            YES
RESOURCE_RULES_REQUIRED              YES
REZ_COLLECTOR_DIR                    "/Users/username/Library/Developer/Xcode/DerivedData/project-dxdgjvgsvvbhowgjqouevhmvgxgf/ArchiveIntermediates/Project Distribution/IntermediateBuildFilesPath/project.build/Distribution-iphoneos/Project.build/ResourceManagerResources"
REZ_OBJECTS_DIR                      "/Users/username/Library/Developer/Xcode/DerivedData/project-dxdgjvgsvvbhowgjqouevhmvgxgf/ArchiveIntermediates/Project Distribution/IntermediateBuildFilesPath/project.build/Distribution-iphoneos/Project.build/ResourceManagerResources/Objects"
REZ_SEARCH_PATHS                     "\"/Users/username/Library/Developer/Xcode/DerivedData/project-dxdgjvgsvvbhowgjqouevhmvgxgf/ArchiveIntermediates/Project Distribution/BuildProductsPath/Distribution-iphoneos\" "
RUN_CLANG_STATIC_ANALYZER            NO
SCAN_ALL_SOURCE_FILES_FOR_INCLUDES   NO
SCRIPTS_FOLDER_PATH                  project.app/Scripts
SCRIPT_INPUT_FILE                    "/Volumes/Development/Project Game/Project-v1/fonts/helvetica-black-hd.png"
SCRIPT_OUTPUT_FILE_0                 "/Users/username/Library/Developer/Xcode/DerivedData/project-dxdgjvgsvvbhowgjqouevhmvgxgf/ArchiveIntermediates/Project Distribution/IntermediateBuildFilesPath/project.build/Distribution-iphoneos/Project.build/DerivedSources/helvetica-black-hd.png"
SCRIPT_OUTPUT_FILE_COUNT             1
SDKROOT                              /Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS4.3.sdk
SDK_DIR                              /Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS4.3.sdk
SDK_NAME                             iphoneos4.3
SDK_PRODUCT_BUILD_VERSION            8H7
SED                                  /usr/bin/sed
SEPARATE_STRIP                       NO
SEPARATE_SYMBOL_EDIT                 NO
SET_DIR_MODE_OWNER_GROUP            YES
SET_FILE_MODE_OWNER_GROUP           NO
SHALLOW_BUNDLE                      YES
SHARED_DERIVED_FILE_DIR             "/Users/username/Library/Developer/Xcode/DerivedData/project-dxdgjvgsvvbhowgjqouevhmvgxgf/ArchiveIntermediates/Project Distribution/BuildProductsPath/Distribution-iphoneos/DerivedSources"
SHARED_FRAMEWORKS_FOLDER_PATH       project.app/SharedFrameworks
SHARED_PRECOMPS_DIR                 /Users/username/Library/Developer/Xcode/DerivedData/project-dxdgjvgsvvbhowgjqouevhmvgxgf/Build/PrecompiledHeaders
SHARED_SUPPORT_FOLDER_PATH          project.app/SharedSupport
SKIP_INSTALL                        NO
SOURCE_ROOT                         "/Volumes/Development/Project Game/Project-v1"
SRCROOT                             "/Volumes/Development/Project Game/Project-v1"
STRINGS_FILE_OUTPUT_ENCODING        binary
STRIP_INSTALLED_PRODUCT             YES
STRIP_STYLE                         all
SUPPORTED_DEVICE_FAMILIES           1,2
SUPPORTED_PLATFORMS                 "iphonesimulator iphoneos"
SYMROOT                             "/Users/username/Library/Developer/Xcode/DerivedData/project-dxdgjvgsvvbhowgjqouevhmvgxgf/ArchiveIntermediates/Project Distribution/BuildProductsPath"
SYSTEM_ADMIN_APPS_DIR               /Applications/Utilities
SYSTEM_APPS_DIR                     /Applications
SYSTEM_CORE_SERVICES_DIR            /System/Library/CoreServices
SYSTEM_DEMOS_DIR                    /Applications/Extras
SYSTEM_DEVELOPER_APPS_DIR           /Developer/Applications
SYSTEM_DEVELOPER_BIN_DIR            /Developer/usr/bin
SYSTEM_DEVELOPER_DEMOS_DIR          "/Developer/Applications/Utilities/Built Examples"
SYSTEM_DEVELOPER_DIR                /Developer
SYSTEM_DEVELOPER_DOC_DIR            "/Developer/ADC Reference Library"
SYSTEM_DEVELOPER_GRAPHICS_TOOLS_DIR "/Developer/Applications/Graphics Tools"
SYSTEM_DEVELOPER_JAVA_TOOLS_DIR     "/Developer/Applications/Java Tools"
SYSTEM_DEVELOPER_PERFORMANCE_TOOLS_DIR   "/Developer/Applications/Performance Tools"
SYSTEM_DEVELOPER_RELEASENOTES_DIR   "/Developer/ADC Reference Library/releasenotes"
SYSTEM_DEVELOPER_TOOLS              /Developer/Tools
SYSTEM_DEVELOPER_TOOLS_DOC_DIR      "/Developer/ADC Reference Library/documentation/DeveloperTools"
SYSTEM_DEVELOPER_TOOLS_RELEASENOTES_DIR   "/Developer/ADC Reference Library/releasenotes/DeveloperTools"
SYSTEM_DEVELOPER_USR_DIR            /Developer/usr
SYSTEM_DEVELOPER_UTILITIES_DIR      /Developer/Applications/Utilities
SYSTEM_DOCUMENTATION_DIR            /Library/Documentation
SYSTEM_LIBRARY_DIR                  /System/Library
TARGETED_DEVICE_FAMILY              1
TARGETNAME                          Project
TARGET_BUILD_DIR                    "/Users/username/Library/Developer/Xcode/DerivedData/project-dxdgjvgsvvbhowgjqouevhmvgxgf/ArchiveIntermediates/Project Distribution/InstallationBuildProductsLocation/Applications"
TARGET_NAME                         Project
TARGET_TEMP_DIR                     "/Users/username/Library/Developer/Xcode/DerivedData/project-dxdgjvgsvvbhowgjqouevhmvgxgf/ArchiveIntermediates/Project Distribution/IntermediateBuildFilesPath/project.build/Distribution-iphoneos/Project.build"
TEMP_DIR                            "/Users/username/Library/Developer/Xcode/DerivedData/project-dxdgjvgsvvbhowgjqouevhmvgxgf/ArchiveIntermediates/Project Distribution/IntermediateBuildFilesPath/project.build/Distribution-iphoneos/Project.build"
TEMP_FILES_DIR                      "/Users/username/Library/Developer/Xcode/DerivedData/project-dxdgjvgsvvbhowgjqouevhmvgxgf/ArchiveIntermediates/Project Distribution/IntermediateBuildFilesPath/project.build/Distribution-iphoneos/Project.build"
TEMP_FILE_DIR                       "/Users/username/Library/Developer/Xcode/DerivedData/project-dxdgjvgsvvbhowgjqouevhmvgxgf/ArchiveIntermediates/Project Distribution/IntermediateBuildFilesPath/project.build/Distribution-iphoneos/Project.build"
TEMP_ROOT                           "/Users/username/Library/Developer/Xcode/DerivedData/project-dxdgjvgsvvbhowgjqouevhmvgxgf/ArchiveIntermediates/Project Distribution/IntermediateBuildFilesPath"
TEST_AFTER_BUILD                    NO
UID                                 501
UNLOCALIZED_RESOURCES_FOLDER_PATH   project.app    UNSTRIPPED_PRODUCT           NO
USER                         username
USER_APPS_DIR                /Users/username/Applications
USER_HEADER_SEARCH_PATHS     project/libs
USER_LIBRARY_DIR             /Users/username/Library
USE_DYNAMIC_NO_PIC           YES
USE_HEADERMAP                YES
USE_HEADER_SYMLINKS          NO
VALIDATE_PRODUCT             YES
VALID_ARCHS                  "armv6 armv7"
VERBOSE_PBXCP                NO
VERSIONPLIST_PATH            project.app/version.plist
VERSION_INFO_BUILDER         username
VERSION_INFO_FILE            project_vers.c
VERSION_INFO_STRING          "\"@(#)PROGRAM:project  PROJECT:project-\""
WRAPPER_EXTENSION            app
WRAPPER_NAME                 project.app
WRAPPER_SUFFIX               .app
XCODE_APP_SUPPORT_DIR        /Developer/Library/Xcode
XCODE_PRODUCT_BUILD_VERSION  4B110
XCODE_VERSION_ACTUAL         0410
XCODE_VERSION_MAJOR          0400
XCODE_VERSION_MINOR          0410
YACC                        /Developer/usr/bin/yacc
05/18/2017 13:51 下午 posted in  Xcode

打包合并通用Framework(模拟器.真机)的脚本

在项目的Build Phases中,新建一个Run Script,输入下面内容:

if [ "${ACTION}" = "build" ]
then
INSTALL_DIR=${SRCROOT}/Products/${PROJECT_NAME}.framework

DEVICE_DIR=${BUILD_ROOT}/${CONFIGURATION}-iphoneos/${PROJECT_NAME}.framework

SIMULATOR_DIR=${BUILD_ROOT}/${CONFIGURATION}-iphonesimulator/${PROJECT_NAME}.framework


if [ -d "${INSTALL_DIR}" ]
then
rm -rf "${INSTALL_DIR}"
fi

mkdir -p "${INSTALL_DIR}"

cp -R "${DEVICE_DIR}/" "${INSTALL_DIR}/"
#ditto "${DEVICE_DIR}/Headers" "${INSTALL_DIR}/Headers"

lipo -create "${DEVICE_DIR}/${PROJECT_NAME}" "${SIMULATOR_DIR}/${PROJECT_NAME}" -output "${INSTALL_DIR}/${PROJECT_NAME}"

#open "${DEVICE_DIR}"
#open "${SRCROOT}/Products"
fi
05/18/2017 13:47 下午 posted in  Xcode