- (id)performSelector:(SEL)aSelector;
在开发中,我们想立即执行某个方法时,可以调用NSObject的performSelector:方法实现,该方法是不传参方法,如果想传参,可以使用下面方法
- (id)performSelector:(SEL)aSelector withObject:(id)object; - (id)performSelector:(SEL)aSelector withObject:(id)object1 withObject:(id)object2;
我们都了解performSelector:是在当前的runloop中执行,并且runloop的mode为NSDefaultRunloopMode,而且该方法是在运行时才执行消息的发送,所以具有动态性,但是该方法也有缺点就是有可能造成内存泄漏,我们看一下下面的例子
警告
编译器报错,因为performSelector在运行时确定消息发送,在编译期不做校验,编译器会假设该方法返回值是一个对象,并且不会对返回值进行内存管理(retain/release)。
- 调用者不负责performSelector方法返回的对象,但是调用alloc、new、copy、mutableCopy等方法簇中的方法时,会依赖代码的结构使用不通的Selector的类型,由于不能在编译期确定返回对象的所有权,所以编译器会生成警告,警告会产生内存泄漏,下面代码就是一个例子, 当调用构造累方法时系统会开辟一块内存空间,但是不会对该内存进行管理,即引用计数不会加减1,即内存泄漏了。

- 给该方法传递SEL形式的方法,由于SEL值不定,performSelector的动态性,所以也会有警告

解决
解决方式一:换思路避免使用该方法
1、官方推荐的NSInvocation
SEL sel = NSSelectorFromString(@"new"); NSMethodSignature *method = [[self class] instanceMethodSignatureForSelector:sel]; NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:method]; [invocation setSelector:sel]; [invocation setTarget:self]; [invocation invoke];
2、使用block重新构建代码
3、使用UIApplicationn的sendAction方法
[UIApplication.sharedApplication sendAction:NSSelectorFromString(@"new") to:self from:nil forEvent:nil];
解决方式二:使用CFBridgingRelease解决内存泄漏
调用alloc、new、copy、mutableCopy等方法簇中的方法时,系统开辟内存,无人负责管理,我们只需将返回的对象引用计数减1即可,也就是CGBridgingRelease
id obj = CFBridgingRelease(((void *(*)(id, SEL))[self methodForSelector:NSSelectorFromString(@"new")])(self, NSSelectorFromString(@"new")));
扒扒实现
我们先看一下performSlector的底层实现,里面调用了objc_msgSend实现消息的传递,但是依旧没有获取到有警告的任何信息,于是分析libs-base/blob/master/Source/NSObject.m的实现,虽然实现细节多了一些,但是依旧没有获取到有警告的信息,有可能苹果不想我们使用该方法进行对象的创建吧~,如果大家有想法可以留言探讨
+ (id)performSelector:(SEL)sel { if (!sel) [self doesNotRecognizeSelector:sel]; return ((id(*)(id, SEL))objc_msgSend)((id)self, sel); } // Replaced by CF (throws an NSException) + (void)doesNotRecognizeSelector:(SEL)sel { _objc_fatal("+[%s %s]: unrecognized selector sent to instance %p", class_getName(self), sel_getName(sel), self); } //libs-base/blob/master/Source/NSObject.m的实现 - (id) performSelector: (SEL)aSelector { IMP msg; if (aSelector == 0) [NSException raise: NSInvalidArgumentException format: @"%@ null selector given", NSStringFromSelector(_cmd)]; /* The Apple runtime API would do: * msg = class_getMethodImplementation(object_getClass(self), aSelector); * but this cannot ask self for information about any method reached by * forwarding, so the returned forwarding function would ge a generic one * rather than one aware of hardware issues with returning structures * and floating points. We therefore prefer the GNU API which is able to * use forwarding callbacks to get better type information. */ msg = objc_msg_lookup(self, aSelector); if (!msg) { [NSException raise: NSGenericException format: @"invalid selector '%s' passed to %s", sel_getName(aSelector), sel_getName(_cmd)]; return nil; } return (*msg)(self, aSelector); }
还有一个文章可以有空看看:https://blog.csdn.net/wei/article/details/
扩展1:performSelector系之延迟执行
- (void)performSelector:(SEL)aSelector withObject:(nullable id)anArgument afterDelay:(NSTimeInterval)delay inModes:(NSArray
*)modes; - (void)performSelector:(SEL)aSelector withObject:(nullable id)anArgument afterDelay:(NSTimeInterval)delay; + (void)cancelPreviousPerformRequestsWithTarget:(id)aTarget selector:(SEL)aSelector object:(nullable id)anArgument; + (void)cancelPreviousPerformRequestsWithTarget:(id)aTarget;
通过分析源码可知performSelector:withObject: afterDelay:方法在当前线程的runloop中的default mode中等待timer倒计时结束,执行timer的fire方法,在fire方法中执行selector指定的方法,不会出现内存泄漏问题,但是如果selector方法没有执行,就有可能出现内存泄漏。
例如:从VC1控制器push到VC2控制器,VC2中有下面的一个方法[self performSelector:@selector(printInfo) withObject:nil afterDelay:100];此时从VC2控制器pop到VC1控制器会出现了内存泄露。
原因:执行延迟方法时,ARC会将当前VC的引用计数加1,方法结束后将VC的引用计数减1,如果该方法没执行就pop到之前的VC,那么系统释放当前VC,但runloop还引用着这个VC,它的引用计数没有减少到0,没有执行用dealloc方法,也就是内存泄露了。
解决:调用:cancelPreviousPerformRequestsWithTarget: selector:object:及时取消延迟,cancelPreviousPerfprmRequestsWithTarget:selector:objrct:的实现中释放了资源,感兴趣的可以扒拉一下源码看一下
//libs-base/blob/master/Source中的实现 /* Sets given message to be sent to this instance after given delay, * in any run loop mode. See [NSRunLoop]*/ - (void) performSelector: (SEL)aSelector withObject: (id)argument afterDelay: (NSTimeInterval)seconds { //获取当前runloop NSRunLoop *loop = [NSRunLoop currentRunLoop]; //创建GSTimedPerformer,并加入到当前runloop中_timedPerformers GSTimedPerformer *item; item = [[GSTimedPerformer alloc] initWithSelector: aSelector target: self argument: argument delay: seconds]; [[loop _timedPerformers] addObject: item]; RELEASE(item); //GSTimedPerformer中的timer添加到当前runloop [loop addTimer: item->timer forMode: NSDefaultRunLoopMode]; } //其中GSTimedPerformer在NSRunLoop.m文件里面,我们只摘抄了关键方法 /* The GSTimedPerformer class is used to hold information about * messages which are due to be sent to objects at a particular time.*/ @interface GSTimedPerformer: NSObject { @public SEL selector; id target; id argument; NSTimer *timer; } - (void) fire; - (id) initWithSelector: (SEL)aSelector target: (id)target argument: (id)argument delay: (NSTimeInterval)delay; - (void) invalidate; @end @implementation GSTimedPerformer - (void) dealloc { [self finalize]; TEST_RELEASE(timer); RELEASE(target); RELEASE(argument); [super dealloc]; } - (void) fire { DESTROY(timer); [target performSelector: selector withObject: argument]; [[[NSRunLoop currentRunLoop] _timedPerformers] removeObjectIdenticalTo: self]; } - (void) finalize { [self invalidate]; } - (id) initWithSelector: (SEL)aSelector target: (id)aTarget argument: (id)anArgument delay: (NSTimeInterval)delay { self = [super init]; if (self != nil) { selector = aSelector; target = RETAIN(aTarget); argument = RETAIN(anArgument); timer = [[NSTimer allocWithZone: NSDefaultMallocZone()] initWithFireDate: nil interval: delay target: self selector: @selector(fire) userInfo: nil repeats: NO]; } return self; } - (void) invalidate { if (timer != nil) { [timer invalidate]; DESTROY(timer); } } @end + (void)cancelPreviousPerformRequestsWithTarget:(id)targe selector:(SEL)aSelector object:(id)arg { NSMutableArray *perf = [[NSRunLoop currentRunLoop] _timedPerformers]; unsigned count = [perf count]; if (count > 0) { GSTimedPerformer *array[count]; IF_NO_GC(RETAIN(target)); IF_NO_GC(RETAIN(arg)); [perf getObjects: array]; while (count-- > 0) { // 遍历查找 GSTimedPerformer *p = array[count]; if (p->target == target && sel_isEqual(p->selector, aSelector) && (p->argument == arg || [p->argument isEqual:arg])) { // target\sel\argument均一致 [p invalidate]; [perf removeObjectAtIndex: count]; } } RELEASE(arg); RELEASE(target); } }
扩展2:performSelector系之模式
- (void)performSelector:(SEL)aSelector target:(id)target argument:(nullable id)arg order:(NSUInteger)order modes:(NSArray
*)modes; - (void)cancelPerformSelector:(SEL)aSelector target:(id)target argument:(nullable id)arg; - (void)cancelPerformSelectorsWithTarget:(id)target;
官方:该方法在下一个runloop中执行,计时器触发时,如果当前模式在指定的modes里面,那么线程从runloop中获取消息并且执行selector,如果当前runloop的mode不在指定的modes里面,计时器将等待直到等到runloop的mode是modes里面指定的mode在执行。
底层实现:
扩展3:performSelector系之线程
//在主线程中执行 - (void)performSelectorOnMainThread:(SEL)aSelector withObject:(nullable id)arg waitUntilDone:(BOOL)wait modes:(nullable NSArray
*)array; // equivalent to the first method with kCFRunLoopCommonModes - (void)performSelectorOnMainThread:(SEL)aSelector withObject:(nullable id)arg waitUntilDone:(BOOL)wait; //在指定线程中执行 - (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(nullable id)arg waitUntilDone:(BOOL)wait modes:(nullable NSArray
*)array; // equivalent to the first method with kCFRunLoopCommonModes - (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(nullable id)arg waitUntilDone:(BOOL)wait; //在系统创建子线程,在该线程中执行 - (void)performSelectorInBackground:(SEL)aSelector withObject:(nullable id)arg;
指定任意线程方法的实现
下面我们介绍第三个方法- (void) performSelector: (SEL)aSelector onThread: (NSThread*)aThread withObject: (id)anObject waitUntilDone: (BOOL)aFlag modes: (NSArray*)anArray的实现,其他方法都是调用这个方法来实现的
- (void)performSelector:(SEL)aSelector onThread:(NSThread*)aThread withObject:(id)anObject waitUntilDone:(BOOL)aFlag modes:(NSArray*)anArray { GSRunLoopThreadInfo *info; NSThread *t; //判段是否有model,没有return if ([anArray count] == 0) { return; } //获取当前线程,与参数aThread比较 t = GSCurrentThread(); if (aThread == nil) { aThread = t; } //获取aThread的runloop信息 info = GSRunLoopInfoForThread(aThread); //两个线程一致 if (t == aThread) { //如果当前runloop不可用或等待条件不满足,说明需要等待, if (aFlag == YES || info->loop == nil) { [self performSelector: aSelector withObject: anObject]; }else { //当前runloop可用,直接执行 [info->loop performSelector: aSelector target: self argument: anObject order: 0 modes: anArray]; } } else//两个线程不一致 { GSPerformHolder *h; NSConditionLock *l = nil; //如果线程结束了,那么不会执行selector if ([aThread isFinished] == YES) { [NSException raise: NSInternalInconsistencyException format: @"perform [%@-%@] attempted on finished thread (%@)", NSStringFromClass([self class]), NSStringFromSelector(aSelector), aThread]; } //如果线程未结束,执行条件满足,创建条件锁,将所有信息封装为GSPerformHolder if (aFlag == YES) { l = [[NSConditionLock alloc] init]; } h = [GSPerformHolder newForReceiver: self argument: anObject selector: aSelector modes: anArray lock: l]; //由GSRunLoopThreadInfo对象统一管理,在满足条件时执行 [info addPerformer: h]; if (l != nil) { [l lockWhenCondition: 1]; [l unlock]; RELEASE(l); if ([h isInvalidated] == NO) { /* If we have an exception passed back from the remote thread,re-raise it.*/ if (nil != h->exception) { NSException *e = AUTORELEASE(RETAIN(h->exception)); RELEASE(h); [e raise]; } } } RELEASE(h); } } //GSPerformHolder对象的创建方法,将参数封装成PerformHolder对象 + (GSPerformHolder*) newForReceiver: (id)r argument: (id)a selector: (SEL)s modes: (NSArray*)m lock: (NSConditionLock*)l { GSPerformHolder *h; h = (GSPerformHolder*)NSAllocateObject(self, 0, NSDefaultMallocZone()); h->receiver = RETAIN(r); h->argument = RETAIN(a); h->selector = s; h->modes = RETAIN(m); h->lock = l; return h; } //GSRunLoopThreadInfo对象的addPerformer方法的实现如下 -(void)addPerformer:(id)performer { BOOL signalled = NO; [lock lock]; NSTimeInterval start = 0.0; //如管道已满,则写入可能会失败。在这种情况下,我们需要暂时释放锁以允许其他人,线程使用管道中的数据。 线程可能及其运行循环可能会在此期间停止...因此我们需要检查outputFd仍然有效。 while (outputFd >= 0 && NO == (signalled = (write(outputFd, "0", 1) == 1) ? YES : NO)) { NSTimeInterval now = [NSDate timeIntervalSinceReferenceDate]; if (0.0 == start) { start = now; } else if (now - start >= 1.0) { NSLog(@"Unable to signal %@ within a second; blocked?", self); break; } [lock unlock]; [lock lock]; } //成功,将performer添加到GSRunLoopThreadInfo数组中,之后在调用GSRunLoopThreadInfo对象的fire方法时调用 if (signalled) [performers addObject: performer]; [lock unlock]; //失败,释放performer,删除资源 if (!signalled) [performer invalidate]; }
- (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(nullable id)arg waitUntilDone:(BOOL)wait { [self performSelector:aSelector onThread:aThread withObject:anObject waitUntilDone:aFlag modes:commonModes()] }
一般使用这个方法将消息传递到应用程序的其他线程,此方法不能取消,如果想取消使用performSelector:withObject:afterDelay: 或 performSelector:withObject:afterDelay:inModes:方法
主线程调用
//指定主线程 - (void)performSelectorOnMainThread:(SEL)aSelector withObject:(id)anObject waitUntilDone:(BOOL)aFlag modes:(NSArray*)anArray { if (defaultThread == nil) defaultThread = [NSThread mainThread]; [self performSelector:aSelector onThread:defaultThread withObject:anObject waitUntilDone:aFlag modes:anArray]; } //指定主线程和mode - (void)performSelectorOnMainThread:(SEL)aSelector withObject:(id)anObject waitUntilDone:(BOOL)aFlag { [self performSelectorOnMainThread:aSelector withObject:anObject waitUntilDone:aFlag modes:commonModes()]; }
在任意一条线程调用
- (void)performSelectorInBackground:(SEL)aSelector withObject:(id)anObject { [NSThread detachNewThreadSelector:aSelector toTarget:self withObject:anObject]; }
一篇相关文章: – https://blog.csdn.net/u0/article/details/?utm_medium=distribute.pc_relevant_t0.none-task-blog-OPENSEARCH-1.edu_weight&depth_1-utm_source=distribute.pc_relevant_t0.none-task-blog-OPENSEARCH-1.edu_weight
https://blog.chenyalun.com/2018/09/30/PerformSelector%E5%8E%9F%E7%90%86/#performSelector-target-argument-order-modes
发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/228900.html原文链接:https://javaforall.net
