performSelector系列方法的研究

- (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/wei371522/article/details/81216853

扩展1:performSelector系之延迟执行
- (void)performSelector:(SEL)aSelector withObject:(nullable id)anArgument afterDelay:(NSTimeInterval)delay inModes:(NSArray<NSRunLoopMode> *)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<NSRunLoopMode> *)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<NSString *> *)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<NSString *> *)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/u011132324/article/details/104831041?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

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
- (void)close { // Empty queues. [self emptyQueues]; [partialReadBuffer release]; partialReadBuffer = nil; [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(disconnect) object:nil]; // Close streams. if (theReadStream != NULL) { CFReadStreamSetClient(theReadStream, kCFStreamEventNone, NULL, NULL); CFReadStreamUnscheduleFromRunLoop (theReadStream, theRunLoop, kCFRunLoopDefaultMode); CFReadStreamClose (theReadStream); CFRelease (theReadStream); theReadStream = NULL; } if (theWriteStream != NULL) { CFWriteStreamSetClient(theWriteStream, kCFStreamEventNone, NULL, NULL); CFWriteStreamUnscheduleFromRunLoop (theWriteStream, theRunLoop, kCFRunLoopDefaultMode); CFWriteStreamClose (theWriteStream); CFRelease (theWriteStream); theWriteStream = NULL; } // Close sockets. if (theSocket != NULL) { CFSocketInvalidate (theSocket); CFRelease (theSocket); theSocket = NULL; } if (theSocket6 != NULL) { CFSocketInvalidate (theSocket6); CFRelease (theSocket6); theSocket6 = NULL; } if (theSource != NULL) { CFRunLoopRemoveSource (theRunLoop, theSource, kCFRunLoopDefaultMode); CFRelease (theSource); theSource = NULL; } if (theSource6 != NULL) { CFRunLoopRemoveSource (theRunLoop, theSource6, kCFRunLoopDefaultMode); CFRelease (theSource6); theSource6 = NULL; } theRunLoop = NULL; // If the client has passed the connect/accept method, then the connection has at least begun. // Notify delegate that it is now ending. if (theFlags & kDidPassConnectMethod) { // Delay notification to give him freedom to release without returning here and core-dumping. if ([theDelegate respondsToSelector: @selector(onSocketDidDisconnect:)]) { //[theDelegate performSelector:@selector(onSocketDidDisconnect:) withObject:self afterDelay:0]; [theDelegate onSocketDidDisconnect:self]; } } // Clear flags. theFlags = 0x00; }
06-13

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

程序员的修养

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值