iOS多线程的初步研究--dispatch队列

GCD编程的核心就是dispatch队列,dispatch block的执行最终都会放进某个队列中去进行,它类似NSOperationQueue但更复杂也更强大,并且可以嵌套使用。所以说,结合block实现的GCD,把函数闭包(Closure)的特性发挥得淋漓尽致。

dispatch队列的生成可以有这几种方式:

  1. dispatch_queue_t queue = dispatch_queue_create(“com.dispatch.serial”, DISPATCH_QUEUE_SERIAL);

    //生成一个串行队列,队列中的block按照先进先出(FIFO)的顺序去执行,实际上为单线程执行。第一个参数是队列的名称,在调试程序时会非常有用,所有尽量不要重名了。

  2. dispatch_queue_t queue = dispatch_queue_create(“com.dispatch.concurrent”, DISPATCH_QUEUE_CONCURRENT); //生成一个并发执行队列,block被分发到多个线程去执行
  1. dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    //获得程序进程缺省产生的并发队列,可设定优先级来选择高、中、低三个优先级队列。由于是系统默认生成的,所以无法调用dispatch_resume()和dispatch_suspend()来控制执行继续或中断。需要注意的是,三个队列不代表三个线程,可能会有更多的线程。并发队列可以根据实际情况来自动产生合理的线程数,也可理解为dispatch队列实现了一个线程池的管理,对于程序逻辑是透明的。

官网文档解释说共有三个并发队列,但实际还有一个更低优先级的队列,设置优先级为DISPATCH_QUEUE_PRIORITY_BACKGROUND。Xcode调试时可以观察到正在使用的各个dispatch队列。

  1. dispatch_queue_t queue = dispatch_get_main_queue();
    //获得主线程的dispatch队列,实际是一个串行队列。同样无法控制主线程dispatch队列的执行继续或中断。

接下来我们可以使用dispatch_async或dispatch_sync函数来加载需要运行的block。

1
2
3
4
5
6
7
8
9
10
11
12
13
dispatch_async(queue, ^{
  //block具体代码
});
//异步执行block,函数立即返回
dispatch_sync(queue, ^{
  //block具体代码
});
//同步执行block,函数不返回,一直等到block执行完毕。编译器会根据实际情况优化代码,所以有时候你会发现block其实还在当前线程上执行,并没用产生新线程。

实际编程经验告诉我们,尽可能避免使用dispatch_sync,嵌套使用时还容易引起程序死锁。

如果queue1是一个串行队列的话,这段代码立即产生死锁:

1
2
3
4
5
6
7
8
9
10
11
dispatch_sync(queue1, ^{
dispatch_sync(queue1, ^{
    ......
  });
  ......
 });

不妨思考下,为什么下面代码也肯定死锁:

1
2
3
4
5
dispatch_sync(dispatch_get_main_queue(), ^{
  ......
});

那实际运用中,一般可以用dispatch这样来写,常见的网络请求数据多线程执行模型:

1
2
3
4
5
6
7
8
9
10
11
12
13
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
  //子线程中开始网络请求数据
  //更新数据模型
  dispatch_sync(dispatch_get_main_queue(), ^{
    //在主线程中更新UI代码
  });
});

程序的后台运行和UI更新代码紧凑,代码逻辑一目了然。

dispatch队列是线程安全的,可以利用串行队列实现锁的功能。比如多线程写同一数据库,需要保持写入的顺序和每次写入的完整性,简单地利用串行队列即可实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
dispatch_queue_t queue1 = dispatch_queue_create("com.dispatch.writedb", DISPATCH_QUEUE_SERIAL);
- (void)writeDB:(NSData *)data
{
  dispatch_async(queue1, ^{
    //write database
  });
}

下一次调用writeDB:必须等到上次调用完成后才能进行,保证writeDB:方法是线程安全的。

dispatch队列还实现其它一些常用函数,包括:

void dispatch_apply(size_t iterations, dispatch_queue_t queue, void (^block)(size_t));

//重复执行block,需要注意的是这个方法是同步返回,也就是说等到所有block执行完毕才返回,如需异步返回则嵌套在dispatch_async中来使用。多个block的运行是否并发或串行执行也依赖queue的是否并发或串行。

void dispatch_barrier_async(dispatch_queue_t queue, dispatch_block_t block);

//这个函数可以设置同步执行的block,它会等到在它加入队列之前的block执行完毕后,才开始执行。在它之后加入队列的block,则等到这个block执行完毕后才开始执行。

void dispatch_barrier_sync(dispatch_queue_t queue, dispatch_block_t block);

//同上,除了它是同步返回函数

void dispatch_after(dispatch_time_t when, dispatch_queue_t queue, dispatch_block_t block);

//延迟执行block

最后再来看看dispatch队列的一个很有特色的函数:

void dispatch_set_target_queue(dispatch_object_t object, dispatch_queue_t queue);

它会把需要执行的任务对象指定到不同的队列中去处理,这个任务对象可以是dispatch队列,也可以是dispatch源(以后博文会介绍)。而且这个过程可以是动态的,可以实现队列的动态调度管理等等。比如说有两个队列dispatchA和dispatchB,这时把dispatchA指派到dispatchB:

dispatch_set_target_queue(dispatchA, dispatchB);

那么dispatchA上还未运行的block会在dispatchB上运行。这时如果暂停dispatchA运行:

dispatch_suspend(dispatchA);

则只会暂停dispatchA上原来的block的执行,dispatchB的block则不受影响。而如果暂停dispatchB的运行,则会暂停dispatchA的运行。

这里只简单举个例子,说明dispatch队列运行的灵活性,在实际应用中你会逐步发掘出它的潜力。

dispatch队列不支持cancel(取消),没有实现dispatch_cancel()函数,不像NSOperationQueue,不得不说这是个小小的缺憾。

wireshark 常用过滤表达式

针对wireshark最常用的自然是针对IP地址的过滤。其中有几种情况:

  1. 对源地址为192.168.0.1的包的过滤,即抓取源地址满足要求的包。
    • 表达式为:ip.src == 192.168.0.1
  2. 对目的地址为192.168.0.1的包的过滤,即抓取目的地址满足要求的包。
    • 表达式为:ip.dst == 192.168.0.1
  3. 对源或者目的地址为192.168.0.1的包的过滤,即抓取满足源或者目的地址的ip地址是192.168.0.1的包。
    • 表达式为:ip.addr == 192.168.0.1,或者 ip.src == 192.168.0.1 or ip.dst == 192.168.0.1
  4. 要排除以上的数据包,我们只需要将其用括号囊括,然后使用 “!” 即可。
    • 表达式为:!(表达式)

针对协议的过滤

  1. 仅仅需要捕获某种协议的数据包,表达式很简单仅仅需要把协议的名字输入即可。
    • 表达式为:http
  2. 需要捕获多种协议的数据包,也只需对协议进行逻辑组合即可.
    • 表达式为:http or telnet (多种协议加上逻辑符号的组合即可)
  3. 排除某种协议的数据包
    • 表达式为:not arp !tcp

针对端口的过滤(视协议而定)

  1. 捕获某一端口的数据包
    • 表达式为:tcp.port == 80
  2. 捕获多端口的数据包,可以使用and来连接,下面是捕获高端口的表达式
    • 表达式为:udp.port >= 2048

针对长度和内容的过滤

  1. 针对长度的过虑(这里的长度指定的是数据段的长度)
    • 表达式为:udp.length < 30 http.content_length <=20   
  2. 针对数据包内容的过滤
    • 表达式为:http.request.uri matches “vipscu” (匹配http请求中含有vipscu字段的请求信息)     

通过以上的最基本的功能的学习,如果随意发挥,可以灵活应用,就基本上算是入门了。以下是比较复杂的实例(来自wireshark图解教程):

tcp dst port 3128

ip src host 10.1.1.1

显示来源IP地址为10.1.1.1的封包。

host 10.1.2.3

显示目的或来源IP地址为10.1.2.3的封包。

src portrange 2000-2500

显示来源为UDP或TCP,并且端口号在2000至2500范围内的封包。

not imcp

显示除了icmp以外的所有封包。(icmp通常被ping工具使用)

src host 10.7.2.12 and not dst net 10.200.0.0/16

显示来源IP地址为10.7.2.12,但目的地不是10.200.0.0/16的封包。

(src host 10.4.1.12 or src net 10.6.0.0/16) and tcp dst portrange 200-10000 and dst net 10.0.0.0/8

显示来源IP为10.4.1.12或者来源网络为10.6.0.0/16,目的地TCP端口号在200至10000之间,并且目的位于网络10.0.0.0/8内的所有封包。

block的使用

语法介绍

代码块本质上是和其他变量类似。不同的是,代码块存储的数据是一个函数体。使用代码块是,你可以像调用其他标准函数一样,传入参数数,并得到返回值。
脱字符(^)是块的语法标记。按照我们熟悉的参数语法规约所定义的返回值以及块的主体(也就是可以执行的代码)。下图是如何把块变量赋值给一个变量的语法讲解:

按照调用函数的方式调用块对象变量就可以了:

1
int result = myBlock(4); // result28

1、参数是NSString*的代码块

1
2
3
4
5
6
void (^printBlock)(NSString *x);
printBlock = ^(NSString* str)
{
NSLog(@"print:%@", str);
};
printBlock(@"hello world!");

运行结果是:print:hello world!

2、代码用在字符串数组排序

1
2
3
4
5
6
7
NSArray *stringArray = [NSArray arrayWithObjects:@"abc 1", @"abc 21", @"abc 12",@"abc 13",@"abc 05",nil];
NSComparator sortBlock = ^(id string1, id string2)
{
return [string1 compare:string2];
};
NSArray *sortArray = [stringArray sortedArrayUsingComparator:sortBlock];
NSLog(@"sortArray:%@", sortArray);

运行结果:
sortArray:(
“abc 05”,
“abc 1”,
“abc 12”,
“abc 13”,
“abc 21”
)

3、代码块的递归调用

代码块想要递归调用,代码块变量必须是全局变量或者是静态变量,这样在程序启动的时候代码块变量就初始化了,可以递归调用

1
2
3
4
5
6
7
8
static void (^ const blocks)(int) = ^(int i)
{
if (i > 0) {
NSLog(@"num:%d", i);
blocks(i - 1);
}
};
blocks(3);

运行打印结果:

num:3

num:2

num:1

4、在代码块中使用局部变量和全局变量

在代码块中可以使用和改变全局变量

1
2
3
4
5
6
7
8
9
10
11
12
13
14
int global = 1000;
int main(int argc, const char * argv[])
{
@autoreleasepool {
void(^block)(void) = ^(void)
{
global++;
NSLog(@"global:%d", global);
};
block();
NSLog(@"global:%d", global);
}
return 0;
}

运行打印结果:

global:1001

global:1001

而局部变量可以使用,但是不能改变。

1
2
3
4
5
6
7
8
int local = 500;
void(^block)(void) = ^(void)
{
// local++;
NSLog(@"local:%d", local);
};
block();
NSLog(@"local:%d", local);

在代码块中改变局部变量编译不通过。怎么在代码块中改变局部变量呢?在局部变量前面加上关键字:__block

1
2
3
4
5
6
7
8
__block int local = 500;
void(^block)(void) = ^(void)
{
local++;
NSLog(@"local:%d", local);
};
block();
NSLog(@"local:%d", local);

运行结果:

local:501

local:501

转载自:http://blog.csdn.net/totogo2010/article/details/7839061

KVC结合NSCoding实例代码

item.h

1
2
3
4
5
6
7
8
9
10
11
#import <Foundation/Foundation.h>
@interface Item : NSObject
@property (nonatomic, strong) NSString *identifier;
@property (nonatomic, strong) NSString *title;
@property (nonatomic, strong) NSString *notes;
- (id)initWithTitle:(NSString *)title notes:(NSString *)notes;
@end

item.m

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
#import "Item.h"
@implementation Item
- (NSArray *)keysForEncoding;
{
return [NSArray arrayWithObjects:@"title", @"notes", @"identifier", nil];
}
- (id)initWithTitle:(NSString *)title notes:(NSString *)notes
{
self = [super init];
if (self)
{
self.title = title;
self.notes = notes;
// create a unique identifier for this object, helps with state restoration
NSUUID *uuid = [[NSUUID alloc] init];
self.identifier = [uuid UUIDString];
}
return self;
}
// we are being created based on what was archived earlier
- (id)initWithCoder:(NSCoder *)aDecoder
{
self = [super init];
if (self)
{
for (NSString *key in self.keysForEncoding)
{
[self setValue:[aDecoder decodeObjectForKey:key] forKey:key];
}
}
return self;
}
// we are asked to be archived, encode all our data
- (void)encodeWithCoder:(NSCoder *)aCoder
{
for (NSString *key in self.keysForEncoding)
{
[aCoder encodeObject:[self valueForKey:key] forKey:key];
}
}
@end

巧妙的利用KVC辅助实现对象的序列化