TCP---传输控制协议,提供的是面向连接、可靠的字节流服务。当客户和服务器彼此交换数据前,必须先在双方之间建立一个TCP连接,之后才能传输数据。TCP提供超时重发,丢弃重复数据,检验数据,流量控制等功能,保证数据能从一端传到另一端。
  UDP---用户数据报协议,是一个简单的面向数据报的运输层协议。UDP不提供可靠性,它只是把应用程序传给IP层的数据报发送出去,但是并不能保证它们能到达目的地。由于UDP在传输数据报前不用在客户和服务器之间建立一个连接,且没有超时重发等机制,故而传输速度很快
  oc中消息传递机制-附:对performSelector方法的扩充
  各种语言都有些传递函数的方法:C语言中可以使用函数指针,C++中有函数引用、仿函数和lambda,Objective-C里也有选择器(selector)和block。
  不过由于iOS SDK中的大部分API都是selector的方式,所以本文就重点讲述selector了。
  Objective-C和我接触过的其他面向对象的语言不同,它强调消息传递,而非方法调用。因此你可以对一个对象传递任何消息,而不需要在编译期声名这些消息的处理方法。
  很显然,既然编译期并不能确定方法的地址,那么运行期就需要自行定位了。而Objective-C runtime就是通过“id objc_msgSend(id theReceiver, SEL theSelector, ...)”这个函数来调用方法的。其中theReceiver是调用对象,theSelector则是消息名,省略号就是C语言的不定参数了。
  这里的消息名是SEL类型,它被定义为struct objc_selector *。不过文档中并没有透露objc_selector是什么东西,但提供了@selector指令来生成:  

1. SEL selector = @selector(message);

复制代码

  @selector是在编译期计算的,所以并不是函数调用。更进一步的测试表明,它在Mac OS X 10.6和iOS下都是一个C风格的字符串(char*):   

  1. NSLog (@"%s", (char *)selector);

复制代码

  你会发现结果是“message”这个消息名。
  下面就写个测试类:  

1.  @interface Test : NSObject
2.   @END
3.   @implementation Test
4.   - (NSString *)intToString:(NSInteger)number {
5.   return [NSString stringWithFormat:@"%d", number];
6.   }
7.   - (NSString *)doubleToString:(double *)number {
8.   return [NSString stringWithFormat:@"%f", *number];
9.   }
10.   - (NSString *)pointToString:(CGPoint)point {
11.   return [NSString stringWithFormat:@"{%f, %f}", point.x, point.y];
12.   }
13.   - (NSString *)intsToString:(NSInteger)number1 second:(NSInteger)number2 third:(NSInteger)number3 {
14.   return [NSString stringWithFormat:@"%d, %d, %d", number1, number2, number3];
15.   }
16.   - (NSString *)doublesToString:(double)number1 second:(double)number2 third:(double)number3 {
17.   return [NSString stringWithFormat:@"%f, %f, %f", number1, number2, number3];
18.   }
19.   - (NSString *)combineString:(NSString *)string1 withSecond:string2 withThird:string3 {
20.   return [NSString stringWithFormat:@"%@, %@, %@", string1, string2, string3];
21.   }
22.   @end

复制代码

  再来测试下objc_msgSend:  

1. #import
2.   //要使用objc_msgSend的话,就要引入这个头文件
3.   Test *test = [[Test alloc] init];
4.   CGPoint point = {123, 456};
5.   NSLog(@"%@", objc_msgSend(test, @selector(pointToString:), point));
6.   [test release];
7.   结果是“{123.000000, 456.000000}”。而且与之前猜想的一样,下面这样调用也是可以的:
8.   NSLog(@"%@", objc_msgSend(test, (SEL)"pointToString:", point));

复制代码

  看到这里你应该发现了,这种实现方式只能确定消息名和参数数目,而参数类型和返回类型就给抹杀了。所以编译器只能在编译期警告你参数类型不对,而无法阻止你传递类型错误的参数。
  接下来再看看NSObject协议提供的一些传递消息的方法:

1. - (id)performSelector:(SEL)aSelector
2.   - (id)performSelector:(SEL)aSelector withObject:(id)anObject
3.   - (id)performSelector:(SEL)aSelector withObject:(id)anObject withObject:(id)anotherObject

复制代码

  也没有觉得很无语?为什么参数必须是对象?为什么最多只支持2个参数?
  好在selector本身也不在乎参数类型,所以传个不是对象的玩意也行:  

1.  NSLog(@"%@", [test performSelector:@selector(intToString:) withObject:(id)123]);

复制代码

  可是double和struct就不能这样传递了,因为它们占的字节数和指针不一样。如果非要用performSelector的话,就只能修改参数类型为指针了:

1. - (NSString *)doubleToString:(double *)number {
2.   return [NSString stringWithFormat:@"%f", *number];
3.   }
4.   double number = 123.456;
5.   NSLog(@"%@", [test performSelector:@selector(doubleToString:) withObject:(id)(&number)]);

复制代码

  参数类型算是搞定了,可是要支持多个参数,还得费番气力。理想状态下,我们应该可以实现这2个方法:  

1.  @interface NSObject (extend)
2.   - (id)performSelector:(SEL)aSelector withObjects:(NSArray *)objects;
3.   - (id)performSelector:(SEL)aSelector withParameters:(void *)firstParameter, ...;
4.   @end

复制代码

  先看看前者,NSArray要求所有的元素都必须是对象,并且不能为nil,所以适用的范围仍然有限。不过你可别小看它,因为你会发现根本没法用objc_msgSend来实现,因为你在写代码时没法预知参数个数。
  这时候就轮到NSInvocation登场了:   

1. @implementation NSObject (extend)
2.   - (id)performSelector:(SEL)aSelector withObjects:(NSArray *)objects {
3.   NSMethodSignature *signature = [self methodSignatureForSelector:aSelector];
4.   NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature];
5.   [invocation setTarget:self];
6.   [invocation setSelector:aSelector];
7.   NSUInteger i = 1;
8.   for (id object in objects) {
9.   [invocation setArgument:&object atIndex:++i];
10.   }
11.   [invocation invoke];
12.   if ([signature methodReturnLength]) {
13.   id data;
14.   [invocation getReturnValue:&data];
15.   return data;
16.   }
17.   return nil;
18.   }
19.   @end
20.   NSLog(@"%@", [test performSelector:@selector(combineString:withSecond:withThird:) withObjects:[NSArray arrayWithObjects:@"1", @"2", @"3", nil]]);

复制代码

  这里有3点要注意的:
  因为方法调用有self(调用对象)和_cmd(选择器)这2个隐含参数,因此设置参数时,索引应该从2开始。
  因为参数是对象,所以必须传递指针,即&object。
  methodReturnLength为0时,表明返回类型是void,因此不需要获取返回值。返回值是对象的情况下,不需要我们来创建buffer。但如果是C风格的字符串、数组等类型,就需要自行malloc,并释放内存了。
  再来实现第2个方法:

1. - (id)performSelector:(SEL)aSelector withParameters:(void *)firstParameter, ... {
2.   NSMethodSignature *signature = [self methodSignatureForSelector:aSelector];
3.   NSUInteger length = [signature numberOfArguments];
4.   NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature];
5.   [invocation setTarget:self];
6.   [invocation setSelector:aSelector];
7.   [invocation setArgument:&firstParameter atIndex:2];
8.   va_list arg_ptr;
9.   va_start(arg_ptr, firstParameter);
10.   for (NSUInteger i = 3; i < length; ++i) {
11.   void *parameter = va_arg(arg_ptr, void *);
12.   [invocation setArgument:¶meter atIndex:i];
13.   }
14.   va_end(arg_ptr);
15.   [invocation invoke];
16.   if ([signature methodReturnLength]) {
17.   id data;
18.   [invocation getReturnValue:&data];
19.   return data;
20.   }
21.   return nil;
22.   }
23.   NSLog(@"%@", [test performSelector:@selector(combineString:withSecond:withThird:) withParameters:@"1", @"2", @"3"]);
24.   NSInteger number1 = 1, number2 = 2, number3 = 3;
25.   NSLog(@"%@", [test performSelector:@selector(intsToString:second:third:) withParameters:number1, number2, number3]);

复制代码

  和前面的实现差不多,不过由于参数长度是未知的,所以用到了[signature numberOfArguments]。当然也可以把SEL转成字符串(可用NSStringFromSelector()),然后查找:的数量。
  处理可变参数时用到了va_start、va_arg和va_end,熟悉C语言的一看就明白了。
  不过由于不知道参数的类型,所以只能设为void *。而这个程序也报出了警告,说void *和NSInteger类型不兼容。而如果把参数换成double,那就直接报错了。遗憾的是我也不知道怎么判别一个void *指针究竟是指向C数据类型,还是指向一个Objective-C对象,所以最好是封装成Objective-C对象。如果只需要兼容C类型的话,倒是可以将setArgument的参数的&去掉,然后直接传指针进去:

1. NSInteger number1 = 1, number2 = 2, number3 = 3;
2.   NSLog(@"%@", [test performSelector:@selector(intsToString:second:third:) withParameters:&number1, &number2, &number3]);
3.   double number4 = 1.0, number5 = 2.0, number6 = 3.0;
4.   NSLog(@"%@", [test performSelector:@selector(doublesToString:second:third:) withParameters:&number4, &number5, &number6]);
5.   [test release];

复制代码

  至于NSObject类添加的performSelector:withObject:afterDelay:等方法,也可以用这种方式来支持多个参数。
  接下来再说说刚才略过的_cmd,它还可以用来实现递归调用。下面就以斐波那契数列为例:  

1. - (NSInteger)fibonacci:(NSInteger)n {
2.   if (n > 2) {
3.   return [self fibonacci:n - 1] + [self fibonacci:n - 2];
4.   }
5.   return n > 0 ? 1 : 0;
6.   }

复制代码

  改成用_cmd实现就变成了这样:  

1. return (NSInteger)[self performSelector:_cmd withObject:(id)(n - 1)] + (NSInteger)[self performSelector:_cmd withObject:(id)(n - 2)];

复制代码

  或者直接用objc_msgSend:

1. return (NSInteger)objc_msgSend(self, _cmd, n - 1) + (NSInteger)objc_msgSend(self, _cmd, n - 2);

复制代码

  但是每次都通过objc_msgSend来调用显得很费劲,有没有办法直接进行方法调用呢?答案是有的,这就需要用到IMP了。IMP的定义为“id (*IMP) (id, SEL, …)”,也就是一个指向方法的函数指针。
  NSObject提供methodForSelector:方法来获取IMP,因此只需稍作修改就行了:  

1. - (NSInteger)fibonacci:(NSInteger)n {
2.   static IMP func;
3.   if (!func) {
4.   func = [self methodForSelector:_cmd];
5.   }
6.   if (n > 2) {
7.   return (NSInteger)func(self, _cmd, n - 1) + (NSInteger)func(self, _cmd, n - 2);
8.   }
9.   return n > 0 ? 1 : 0;
10.   }

复制代码

  现在运行时间比刚才减少了1/4,还算不错。
  顺便再展现一下Objective-C强大的动态性,给Test类添加一个sum:and:方法:  

1. NSInteger sum(id self, SEL _cmd, NSInteger number1, NSInteger number2) {
2.   return number1 + number2;
3.   }
4.   class_addMethod([Test class], @selector(sum:and:), (IMP)sum, "i@:ii");
5.   NSLog(@"%d", [test sum:1 and:2]);

复制代码

  class_addMethod的最后那个参数是函数的返回值和参数类型,详细内容可以参考Type Encodings文档。