`
zjjzmw1
  • 浏览: 1352370 次
  • 性别: Icon_minigender_1
  • 来自: 开封
社区版块
存档分类
最新评论

TCP与UDP区别和oc中消息传递机制-附:对performSelector方法的扩充

    博客分类:
  • iOS
阅读更多
TCP---传输控制协议,提供的是面向连接、可靠的字节流服务。当客户和服务器彼此交换数据前,必须先在双方之间建立一个TCP连接,之后才能传输数据。TCP提供超时重发,丢弃重复数据,检验数据,流量控制等功能,保证数据能从一端传到另一端。
UDP---用户数据报协议,是一个简单的面向数据报的运输层协议。UDP不提供可靠性,它只是把应用程序传给IP层的数据报发送出去,但是并不能保证它们能到达目的地。由于UDP在传输数据报前不用在客户和服务器之间建立一个连接,且没有超时重发等机制,故而传输速度很快
Overview
TCP (Transmission Control Protocol) is the most commonly used protocol on the Internet. The reason for this is because TCP offers error correction. When the TCP protocol is used there is a "guaranteed delivery." This is due largely in part to a method called "flow control." Flow control determines when data needs to be re-sent, and stops the flow of data until previous packets are successfully transferred. This works because if a packet of data is sent, a collision may occur. When this happens, the client re-requests the packet from the server until the whole packet is complete and is identical to its original.

UDP (User Datagram Protocol) is anther commonly used protocol on the Internet. However, UDP is never used to send important data such as webpages, database information, etc; UDP is commonly used for streaming audio and video. Streaming media such as Windows Media audio files (.WMA) , Real Player (.RM), and others use UDP because it offers speed! The reason UDP is faster than TCP is because there is no form of flow control or error correction. The data sent over the Internet is affected by collisions, and errors will be present. Remember that UDP is only concerned with speed. This is the main reason why streaming media is not high quality.

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指令来生成:
SEL selector = @selector(message);
@selector是在编译期计算的,所以并不是函数调用。更进一步的测试表明,它在Mac OS X 10.6和iOS下都是一个C风格的字符串(char*):
NSLog (@"%s", (char *)selector);
你会发现结果是“message”这个消息名。

下面就写个测试类:

@interface Test : NSObject
@end

@implementation Test

- (NSString *)intToString:(NSInteger)number {
return [NSString stringWithFormat:@"%d", number];
}

- (NSString *)doubleToString:(double *)number {
return [NSString stringWithFormat:@"%f", *number];
}

- (NSString *)pointToString:(CGPoint)point {
return [NSString stringWithFormat:@"{%f, %f}", point.x, point.y];
}

- (NSString *)intsToString:(NSInteger)number1 second:(NSInteger)number2 third:(NSInteger)number3 {
return [NSString stringWithFormat:@"%d, %d, %d", number1, number2, number3];
}

- (NSString *)doublesToString:(double)number1 second:(double)number2 third:(double)number3 {
return [NSString stringWithFormat:@"%f, %f, %f", number1, number2, number3];
}

- (NSString *)combineString:(NSString *)string1 withSecond:string2 withThird:string3 {
return [NSString stringWithFormat:@"%@, %@, %@", string1, string2, string3];
}

@end
再来测试下objc_msgSend:

#import <objc/message.h>
//要使用objc_msgSend的话,就要引入这个头文件

Test *test = [[Test alloc] init];
CGPoint point = {123, 456};
NSLog(@"%@", objc_msgSend(test, @selector(pointToString:), point));
[test release];
结果是“{123.000000, 456.000000}”。而且与之前猜想的一样,下面这样调用也是可以的:
NSLog(@"%@", objc_msgSend(test, (SEL)"pointToString:", point));
看到这里你应该发现了,这种实现方式只能确定消息名和参数数目,而参数类型和返回类型就给抹杀了。所以编译器只能在编译期警告你参数类型不对,而无法阻止你传递类型错误的参数。

接下来再看看NSObject协议提供的一些传递消息的方法:
- (id)performSelector:(SEL)aSelector
- (id)performSelector:(SEL)aSelector withObject:(id)anObject
- (id)performSelector:(SEL)aSelector withObject:(id)anObject withObject:(id)anotherObject
也没有觉得很无语?为什么参数必须是对象?为什么最多只支持2个参数?

好在selector本身也不在乎参数类型,所以传个不是对象的玩意也行:
NSLog(@"%@", [test performSelector:@selector(intToString:) withObject:(id)123]);
可是double和struct就不能这样传递了,因为它们占的字节数和指针不一样。如果非要用performSelector的话,就只能修改参数类型为指针了:
- (NSString *)doubleToString:(double *)number {
return [NSString stringWithFormat:@"%f", *number];
}

double number = 123.456;
NSLog(@"%@", [test performSelector:@selector(doubleToString:) withObject:(id)(&number)]);
参数类型算是搞定了,可是要支持多个参数,还得费番气力。理想状态下,我们应该可以实现这2个方法:
@interface NSObject (extend)

- (id)performSelector:(SEL)aSelector withObjects:(NSArray *)objects;
- (id)performSelector:(SEL)aSelector withParameters:(void *)firstParameter, ...;

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

- (id)performSelector:(SEL)aSelector withObjects:(NSArray *)objects {
NSMethodSignature *signature = [self methodSignatureForSelector:aSelector];
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature];
[invocation setTarget:self];
[invocation setSelector:aSelector];

NSUInteger i = 1;
for (id object in objects) {
[invocation setArgument:&object atIndex:++i];
}
[invocation invoke];

if ([signature methodReturnLength]) {
id data;
[invocation getReturnValue:&data];
return data;
}
return nil;
}

@end


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个方法:
- (id)performSelector:(SEL)aSelector withParameters:(void *)firstParameter, ... {
NSMethodSignature *signature = [self methodSignatureForSelector:aSelector];
NSUInteger length = [signature numberOfArguments];
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature];
[invocation setTarget:self];
[invocation setSelector:aSelector];

[invocation setArgument:&firstParameter atIndex:2];
va_list arg_ptr;
va_start(arg_ptr, firstParameter);
for (NSUInteger i = 3; i < length; ++i) {
void *parameter = va_arg(arg_ptr, void *);
[invocation setArgument:&parameter atIndex:i];
}
va_end(arg_ptr);

[invocation invoke];

if ([signature methodReturnLength]) {
id data;
[invocation getReturnValue:&data];
return data;
}
return nil;
}

NSLog(@"%@", [test performSelector:@selector(combineString:withSecond:withThird:) withParameters:@"1", @"2", @"3"]);

NSInteger number1 = 1, number2 = 2, number3 = 3;
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的参数的&去掉,然后直接传指针进去:
NSInteger number1 = 1, number2 = 2, number3 = 3;
NSLog(@"%@", [test performSelector:@selector(intsToString:second:third:) withParameters:&number1, &number2, &number3]);

double number4 = 1.0, number5 = 2.0, number6 = 3.0;
NSLog(@"%@", [test performSelector:@selector(doublesToString:second:third:) withParameters:&number4, &number5, &number6]);
[test release];
至于NSObject类添加的performSelector:withObject:afterDelay:等方法,也可以用这种方式来支持多个参数。

接下来再说说刚才略过的_cmd,它还可以用来实现递归调用。下面就以斐波那契数列为例:
- (NSInteger)fibonacci:(NSInteger)n {
if (n > 2) {
return [self fibonacci:n - 1] + [self fibonacci:n - 2];
}
return n > 0 ? 1 : 0;
}
改成用_cmd实现就变成了这样:
return (NSInteger)[self performSelector:_cmd withObject:(id)(n - 1)] + (NSInteger)[self performSelector:_cmd withObject:(id)(n - 2)];
或者直接用objc_msgSend:
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,因此只需稍作修改就行了:
- (NSInteger)fibonacci:(NSInteger)n {
static IMP func;
if (!func) {
func = [self methodForSelector:_cmd];
}

if (n > 2) {
return (NSInteger)func(self, _cmd, n - 1) + (NSInteger)func(self, _cmd, n - 2);
}
return n > 0 ? 1 : 0;
}
现在运行时间比刚才减少了1/4,还算不错。

顺便再展现一下Objective-C强大的动态性,给Test类添加一个sum:and:方法:
NSInteger sum(id self, SEL _cmd, NSInteger number1, NSInteger number2) {
return number1 + number2;
}

class_addMethod([Test class], @selector(sum:and:), (IMP)sum, "i@:ii");
NSLog(@"%d", [test sum:1 and:2]);
class_addMethod的最后那个参数是函数的返回值和参数类型,详细内容可以参考Type Encodings文档。



分享到:
评论

相关推荐

    OC-performSelector

    OC-performSelector

    UIWindow-TouchIndicatorExtension:用于指示用户在屏幕上的触摸的 UIWindow 扩展

    用法将文件UIWindow+TouchIndicatorExtension.m添加到您的项目中,并在应用程序委托的 -applicationDidBecomeActive: 方法末尾调用以下代码以激活触摸指示器。 UIWindow *keyWindow = [[UIApplication ...

    leetcode题库-iOS-Interview-Questions:收集整理iOS笔试面试题

    http和socket通信的区别,tcp和udp的区别,session和cookie的区别 block实现原理 响应链 frame 和 bounds 写一个宏MIN,这个宏输入两个参数并返回较小的一个 #define MIN(a,b) ((a)&gt;(b)?(b):(a)) 什么是KeyPath #...

    ios demo,performSelector和respondsToSelector

    ios demo,performSelector和respondsToSelector,动态添加方法,多参数调用

    swift performSelector

    swift并没有提供performSelector ,我伪代码写了一个扩展类。使用时请小心。详见我博文说明。http://blog.csdn.net/fengsh998/article/details/35842441

    InvocationDemo

    NSInvocation的作用和performSelector:withObject:的作用是一样的:用于iOS编程中调用某个对象的消息。 performSelector:withObject:调用一些参数较少的消息是比较方便的,但是对于参数个数大于2的消息,使用...

    IOS多线程编程NSThread的使用方法

    IOS多线程编程NSThread的使用方法 NSThread是多线程的一种,有两种方法创建子线程 (1)优点:NSThread 比GCD、NSOperation都轻量级 (2)缺点:需要自己管理线程的生命周期,线程同步。线程同步对数据的加锁会有...

    ios-超简单一个方法实现播放动画.zip

    - (void)playAnimateWithName:(NSString *)name framesNumber:(int)frames {... [self.imageView performSelector:@selector(setAnimationImages:) withObject:nil afterDelay:self.imageView.animationDuration]; }

    对比分析iOS延迟执行的4种方式

    1.performSelector(NSObject)方法 2.NSTimer方法 3.GCD方法 4.sleep(NSThread)方法 一、performSelector方法: 代码如下:[self performSelector:@selector(delayMethod) withObject:nil afterDelay:1.0f]; 1....

    iOS延迟执行方法详解

    近日学习了延迟执行的几种方法,分享一下。 1.performSelector(NSObject)方法  2.NSTimer方法  3.GCD方法  4.sleep(NSThread)方法 延迟执行代码: - (void)delayDo : (id)sender { NSLog(@do:%@,sender); } 1....

    ios开发小技巧

    读者可能知道一个简易的方法,那就是在程序的viewDidLoad中加入 [[UIApplication sharedApplication]setStatusBarHidden:YES animated:NO]; 33. 更改AlertView背景 UIAlertView *theAlert = [[[UIAlertViewalloc] ...

    iOS中延时执行的几种方式比较及汇总

    本文列举了四种延时执行某函数的方法及其一些区别。假如延时1秒时间执行下面的方法。 - (void)delayMethod { NSLog(@execute); } 1.performSelector方法 这是iOS中常用的一种延迟执行方法.  //不带参数 [self ...

    objective-c小技巧

    1. 使用@property和@synthesize声明一个成员变量,给其赋值是时要在前面加上"self.",以便调用成员变量的setmember方法。 直接调用成员变量并且给其赋值:member=[NSString stringWithFormat:@””];将不执行...

    iOS长按按钮

    [self performSelector:@selector(lazyButtontouchDown) withObject:nil afterDelay:self.minimumPressDuration]; } -(void)lazyButtontouchDown { } //当离开按钮的时候取消所调用的方法 - (void)...

    ios开发记录

    //在oc中,空对象调用方法或属性不会引起程序报错或崩溃,但是也不会有任何事件发生 // NSString *str = nil; // [str length]; //判断两个字符串是否相等,不能使用==,使用等号是判断两个对象是否是一个对象,...

    iOS中NSInvocation的基本用法教程

    大家应该都有所了解,在 iOS中可以直接调用某个对象的消息方式有两种:一种是performSelector:withObject;再一种就是NSInvocation。 第一种方式比较简单,能完成简单的调用。但是对于&gt;2个的参数或者有返回值的处理...

    蓝牙自动开启

    [self performSelector:@selector(toggle:) withObject:btCont afterDelay:1.0f] ; #endif } #if TARGET_IPHONE_SIMULATOR #else - (void)toggle:(id)btCont { BOOL currentState = [btCont enabled] ; [btCont ...

    DelayTest.zip

    实现延时方法执行,可用的方法有定时器、GCD、NSThread、performSelector方法

    IOS开发中延迟执行和取消

    在 Objective-C 中延迟执行还是很常见的需求,通常有如下几种方式可供选择: performSelector: 想要延迟调用某个方法: [self performSelector:@selector(delay) withObject:nil afterDelay:3.0]; 取消延迟的方法...

    objective c 消息 objc_msgSend

    objective c 消息 objc_msgSend 及 performSelector 的使用例子。

Global site tag (gtag.js) - Google Analytics