关于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,同样按照如下配置:
- Architectures 默认设置为 Standard architectures(armv7,arm64) 。(之前的Xcode版本中默认设置还包括armv7s,现在去掉了,估计是因为armv7s相较armv7优化不大,能向下兼容,而设置后还会增大二进制包的大小)
- Valid Architectures 默认设置为 armv7 armv7s arm64。
- 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的常见问题
- 编译报错 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
使用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圖之後即可供程式使用,非常方便
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)
从网络加载扩展喜好VPN配置。
func saveToPreferences(completionHandler: ((NSError?) -> Void)? = nil)
保存在网络扩展喜好VPN配置。
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
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大致类似:
- PC客户端(即你的电脑)发出请求基于Socks5协议跟SS-Local端进行通讯,由于这个SS-Local一般是本机或路由器等局域网的其他机器,不经过GFW,所以解决GFW通过特征分析进行干扰的问题。
- SS-Local和SS-Server两端通过多种可选的加密方法进行通讯,经过GFW的时候因为是常规的TCP包,没有明显特征码GFW也无法对通讯数据进行解密,因此通讯放行。
- SS-Server将收到的加密数据进行解密,还原初始请求,再发送到用户需要访问的服务网站,获取响应原路再返回SS-04,返回途中依然使用了加密,使得流量是普通TCP包,并成功穿过GFW防火墙。
因此,Shadowsocks的优点在于它通过流量混淆隐秘解决了GFW通过分析流量特征从而干扰的问题,这是它优于SSH和VPN翻墙的地方(但VPN更注重加密安全性)。缺点也依然明显,需要一点点技术和资源(墙外VPS服务器)来搭建Shadowsocks服务,好在已经有人搭建相应的服务出售翻墙服务了。
iOS多线程全套:线程生命周期,多线程的四种解决方案,线程安全问题,GCD的使用,NSOperation的使用
本文主要是分享iOS多线程的相关内容,为了更系统的讲解,将分为以下7个方面来展开描述。
- 多线程的基本概念
- 线程的状态与生命周期
- 多线程的四种解决方案:pthread,NSThread,GCD,NSOperation
- 线程安全问题
- NSThread的使用
- GCD的理解与使用
- 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实现多线程的步骤如下:
- 创建任务:先将需要执行的操作封装到NSOperation对象中。
- 创建队列:创建NSOperationQueue。
- 将任务加入到队列中:将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)}
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
Copyright © 2015 Powered by MWeb, 豫ICP备09002885号-5