- (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: NSInvalidArgumentExceptionformat: @"%@ 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: NSGenericExceptionformat: @"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)aSelectorwithObject: (id)argumentafterDelay: (NSTimeInterval)seconds
{//获取当前runloopNSRunLoop     *loop = [NSRunLoop currentRunLoop];//创建GSTimedPerformer,并加入到当前runloop中_timedPerformersGSTimedPerformer  *item;item = [[GSTimedPerformer alloc] initWithSelector: aSelectortarget: selfargument: argumentdelay: 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
{
@publicSEL     selector;id     target;id       argument;NSTimer    *timer;
}
- (void) fire;
- (id) initWithSelector: (SEL)aSelectortarget: (id)targetargument: (id)argumentdelay: (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)aSelectortarget: (id)aTargetargument: (id)anArgumentdelay: (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,没有returnif ([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: aSelectortarget: selfargument: anObjectorder: 0modes: anArray];}}else//两个线程不一致{GSPerformHolder   *h;NSConditionLock   *l = nil;//如果线程结束了,那么不会执行selectorif ([aThread isFinished] == YES){[NSException raise: NSInternalInconsistencyExceptionformat: @"perform [%@-%@] attempted on finished thread (%@)",NSStringFromClass([self class]),NSStringFromSelector(aSelector),aThread];}//如果线程未结束,执行条件满足,创建条件锁,将所有信息封装为GSPerformHolderif (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:aFlagmodes: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

performSelector系列方法的研究相关推荐

  1. C#多线程学习(四) 多线程的自动管理(线程池) (转载系列)——继续搜索引擎研究...

    在多线程的程序中,经常会出现两种情况: 一种情况:   应用程序中,线程把大部分的时间花费在等待状态,等待某个事件发生,然后才能给予响应                   这一般使用ThreadPo ...

  2. C#多线程学习(五) 多线程的自动管理(定时器) (转载系列)——继续搜索引擎研究...

    Timer类:设置一个定时器,定时执行用户指定的函数.               定时器启动后,系统将自动建立一个新的线程,执行用户指定的函数. 初始化一个Timer对象: Timer timer ...

  3. C#多线程学习(三) 生产者和消费者 (转载系列)——继续搜索引擎研究

    前面说过,每个线程都有自己的资源,但是代码区是共享的,即每个线程都可以执行相同的函数.这可能带来的问题就是几个线程同时执行一个函数,导致数据的混乱,产生不可预料的结果,因此我们必须避免这种情况的发生. ...

  4. 计算机视听觉机理和方法,重大研究计划“视听觉信息的认知计算” - 基金申请 - 小木虫 - 学术 科研 互动社区...

    重大研究计划"视听觉信息的认知计算" 2008年度项目指南 与人类视听觉感知密切相关的图像.语音和文本(语言)信息在社会.经济和国家安全等领域中扮演着重要角色,并在今后一段时间内仍 ...

  5. SAP QM 稳定性研究功能研习系列1 - 稳定性研究总流程

    SAP QM 稳定性研究功能研习系列1 - 稳定性研究总流程 如下图是SAP系统里标准的Stability Study Processing. 2,QM01创建一个QS类型(StabilStudy w ...

  6. 调用父级方法_通信:找到任意组件实例的findComponents系列方法,5个终极方案

    已经介绍了两种组件间通信的方法:provide / inject 和 dispatch / broadcast.它们有各自的使用场景和局限,比如前者多用于子组件获取父组件的状态,后者常用于父子组件间通 ...

  7. 提高计算机系统性能的方法或者技术,互联网内容审计系统性能优化方法的研究-计算机科学与技术专业论文.docx...

    互联网内容审计系统性能优化方法的研究-计算机科学与技术专业论文 摘要近年来,互联网络以其快速的信息传递和广泛的资源共享深入到了人类 摘要 近年来,互联网络以其快速的信息传递和广泛的资源共享深入到了人类 ...

  8. 设计有三个窗口的框架结构网页_技术周刊丨钢框架结构直接分析设计与传统设计方法对比研究——恒荷载作用结果对比...

    ▲ 点击上方蓝字,关注SAUSAGE非线性!作者:侯晓武 丨 职位:技术经理 仁荷大学(韩)建筑工学博士 曾任建筑软件(MIDAS Gen/Building)技术负责人 拥有11年建筑软件技术支持经验 ...

  9. 计算机模拟病例考试试题,计算机模拟病例考试评分方法的研究概述

    计算机模拟病例考试评分方法的研究概述 计算机模拟病例考试是测量学生解决临床问题的能力,尤其是临床决策思维和处理病人问题能力的重要方法 (本文共3页) 阅读全文>> 计算机辅助物理教学可以丰 ...

最新文章

  1. QT绘制带有负条的条形图。
  2. Java面试题中高级,javaif循环语句
  3. 2021-06-01 深入分析锁升级流程的基础
  4. 关于el-form中的rules未生效问题的解决方法
  5. GIT代码管理: git remote add
  6. 手机 物理分辨率 逻辑分辨率
  7. 商务办公软件应用与实践【5】
  8. c语言命令行贪吃蛇,C语言实现贪吃蛇游戏(命令行)
  9. CUDA学习笔记(四)GPU架构
  10. C#调用存储过程带输出参数或返回值分类(转)
  11. ps使用,绘制外观图
  12. windows7更换系统时间服务器,win7如何修改系统时间
  13. 现代浏览器观察者 Observer API 指南
  14. Chrome和edge报STATUS_STACK_BUFFER_OVERRUN错误的处理办法
  15. 算法 |【实验5.3】:一元三次方程的根-连续区间的二分搜索求近似解
  16. 8月9日华为发布了其自研的鸿蒙操作系统,华为正式发布自研操作系统鸿蒙
  17. opporeno3详细参数_opporeno3pro参数配置详情-opporeno3pro手机性能评测
  18. cpu性能测试软件 国际象棋,CPU性能评测软件
  19. 50个Pandas的奇淫技巧:向量化字符串,玩转文本处理
  20. 杨婷:腾讯云在线教育解决方案分享

热门文章

  1. 知道RAD Studio Sydney(Delphi 10.4.2)这些,少走弯路
  2. 回归算法 经典案例 波士顿房价预测
  3. PADS VX2.8 PCB封装的提取方法
  4. 修改Transmission登陆密码
  5. 基于php办公用品网上商城的设计与实现(含源文件)
  6. Android学习路线(十三)Activity生命周期——暂停和恢复(Pausing and Resuming )一个Activity
  7. 黑龙江认识电子计算机ppt,[IT认证]认识计算机配件.ppt
  8. css:块元素、行内元素、行内块元素以及三种元素之间的转换
  9. MacBook Pro 15 2016款在三星T5移动固态硬盘上安装win10
  10. ‘冰封’合约背后的老牌劲敌 | 链安团队漏洞分析连载第二期 —— 拒绝服务漏洞