线程无关方法

1
2
3
- (id)performSelector:(SEL)aSelector;  
- (id)performSelector:(SEL)aSelector withObject:(id)object;
- (id)performSelector:(SEL)aSelector withObject:(id)object1 withObject:(id)object2;

这三个方法,均为同步执行,与线程无关,主线程和子线程中均可调用。等同于直接调用该方法。在需要动态的去调用方法的时候去使用。
例如:[self performSelector:@selector(test2)];与[self test2];执行效果上完全相同。

Delayed perform

1
2
- (void)performSelector:(SEL)aSelector withObject:(id)anArgument afterDelay:(NSTimeInterval)delay inModes:(NSArray *)modes;
- (void)performSelector:(SEL)aSelector withObject:(id)anArgument afterDelay:(NSTimeInterval)delay;

1、这两个方法为异步执行
2、这两个方法只能在主线程中执行,其它线程不执行
3、即使delay传参为0,也不会立即执行,而是在next runloop执行
4、可用于当点击UI中一个按钮会触发一个消耗系统性能的事件,在事件执行期间按钮会一直处于高亮状态,此时可以调用该方法去异步的处理该事件,就能避免上面的问题。

在方法未到执行时间之前,取消方法为

1
2
+ (void)cancelPreviousPerformRequestsWithTarget:(id)aTarget selector:(SEL)aSelector object:(id)anArgument;
+ (void)cancelPreviousPerformRequestsWithTarget:(id)aTarget;

on mainthread

1
2
- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(id)arg waitUntilDone:(BOOL)wait modes:(NSArray *)array;
- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(id)arg waitUntilDone:(BOOL)wait;

1、这两个方法,在主线程和子线程中均可执行,均会在主线程中调aSelector方法
2、如果设置wait为NO:等待当前线程执行完以后,主线程才会执行aSelector方法;
3、设置为YES:不等待当前线程执行完,就在主线程上执行aSelector方法。
4、第二个方法使用默认的模式(NSDefaultRunLoopMode)。
有些人博客上的说法很多都是错误的,~~ 比如wait为YES是等待后执行、如果当前线程为主线程就马上执行 ~~。

参数

直接使用perform方法参数要求是对象

perform方法要求传入参数必须是对象,如果方法本身的参数是int,直接传NSNumber会导致得到的int参数不正确

传多个参数的解决方法

方法一

1
2
3
4
5
6
7
8
9
10
11
12
13
- (void) anotherMethod {
id target = self;
SEL selector = @selector(methodWithFirst:second:third:);
// 首先第一个参数和第二个参数是self和selector
// 剩下的参数可以是对象或者基本数据类型,根据方法需要填写参数类型
typedef void (*MethodType)(id, SEL, id, id, id);
// 这个方法实际上返回的是IMP类型,为了方便调用,强转为MethodType
MethodType methodToCall = (MethodType)[target methodForSelector:selector];
methodToCall(target, selector, @(1), @(2), @(3));
}
- (void)methodWithFirst:(id) object second:(id) secondObject third:(id) thirdObject {
// Code here
}

方法二

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
SEL selector = @selector(methodWithFirst:andSecond:andThird:);
NSMethodSignature *signature = [self methodSignatureForSelector: selector];
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature: signature];

[invocation setSelector: selector];

[invocation setTarget: self];
//前两个参数为self与selector,其它参数要从2开始
[invocation setArgument: &first atIndex: 2];

[invocation setArgument: &second atIndex: 3];

[invocation setArgument: &third atIndex: 4];

[invocation invoke];

[invocation getReturnValue: &result];

NSLog(@"NSInvocation : %@", result);

使用category

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
49
50
51
52
53
@interface NSObject (extend)

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

@end
@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:(void *)&object atIndex:++i];
}
[invocation invoke];

if ([signature methodReturnLength]) {
id data;
[invocation getReturnValue:&data];
return data;
}
return nil;
}
- (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;
}
@end

调用

1
2
3
4
//第一个方法使用数组,所以不能传nil,必须传对象
[self performSelector:@selector(methodWithFirst:second:third:) withObjects:[NSArray arrayWithObjects:@"1", @"2", @"3", nil]];
//第二个方法采用多个参数,可以传nil,必须传对象
[self performSelector:@selector(methodWithFirst:second:third:) withParameters:(void *)(@1),nil,@3];

返回值

在intel处理器中,obj_msgSend将方法的返回值存储在eax寄存器中。但是当方法返回结构体比如NSRect时,方法编译后为objc_msgSend_stret,这个方法将传递一个额外的参数:一个指向返回值内存的指针
苹果的官方文档说

For methods that return anything other than an object, use NSInvocation.

performSelector编译后使用objc_msgSend。如果返回值为结构体,本该使用objc_msgSend_stret却使用了objc_msgSend,就会导致崩溃。解释的不是很细致,请看原文

Apple aren’t kidding when they mention this. This is because performSelector: uses objc_msgSend. If you use objc_msgSend to call a method that requires objc_msgSend_stret, the function will think the first parameter is the secret struct pointer, when in fact it’s the self pointer. When the function returns, it corrupts the memory pointed to by self by overwriting it with the return value. Next time you try to use the object, you get weird and wonderful crashes.
Only use performSelector: when the selector returns an object. If the selector returns a struct, then you risk corrupting memory, even if you don’t use the return value. If the method doesn’t return an object, then use NSInvocation instead, because it is capable of determining the correct message dispatch function to use.

只有方法返回值为对象或者无返回值是才使用performSelector:方法,其它情况使用NSInvocation代替。
下面这个category检查了返回值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@interface NSObject(SafePerformSelector) 
-(id) performSelectorSafely:(SEL)aSelector;
@end
@implementation NSObject(SafePerformSelector)
-(id) performSelectorSafely:(SEL)aSelector;
{
NSParameterAssert(aSelector != NULL);
NSParameterAssert([self respondsToSelector:aSelector]);
NSMethodSignature* methodSig = [self methodSignatureForSelector:aSelector];
if(methodSig == nil) return nil;
const char* retType = [methodSig methodReturnType];
if(strcmp(retType, @encode(id)) == 0 || strcmp(retType, @encode(void)) == 0){
return [self performSelector:aSelector];
} else {
NSLog(@"-[%@ performSelector:@selector(%@)] shouldn't be used. The selector doesn't return an object or void", [self className], NSStringFromSelector(aSelector));
return nil;
}
}
@end