PerformSelector May Cause a Leak

在开发过程中,遇到这样一个编译器警告:


Warning

图片可能看不清,源代码如下:

    [self performSelector:NSSelectorFromString(item[@"kAction"])];

这行代码会引出一个Warning:performSelector may cause a leak because its selector is unknown
其实解决办法,百度一下就有很多,也确实能通过一些奇技淫巧来解决这个问题。问题的根本原因解释却很少,最后还是找了一篇stackoverflow上的的文章做了学习。
以下内容参考stackoverflow原文,并加上了一些自己的理解:

警告原因

这个警告会出现在ARC中,与内存管理有关系。runtime需要知道运行这行代码后,方法的返回值是什么,是void无返回值还是NSString*返回字符串等,情况很多。通常来说,ARC会通过方法声明来获取到这些信息,然后去进行相应内存管理。
ARC在处理方法的返回值时有以下四种情况:(需要学习MRC的一些相关知识)

  • 1 忽略无返回值或返回值不是一个指针对象的情况。
  • 2 不retain返回值(不做引用计数处理),如果没有对象引用该返回值,返回值release(以NS_RETURNS_NOT_RETAINED做标识)。
  • 3 retain返回值(引用计数+1),用于init、coFR5py家族方法或者标记有NS_RETURNS_RETAINED的方法)
  • 4 标记为autorelease,放入一个autoreleasepool中,并且假设对象在某个范围内都不会释放(方法以NS_RETURNS_ATUORELEASED做标识,自动释放池释放后,进行autorelease操作)

以上四种情况翻译的不太好,这块内容也不是非常理解。看了objc arc的简单探索学习了下,再回头解释几个概念。

Method family

An Objective-C method may fall into a method family, which is a conventional set of behaviors ascribed to it by the Cocoa conventions.

指的是命名上表示一类型的方法,比如- init和- initWithMark:都属于init的family,同样类似的还有copy,mutableCopy,new等。

ns_returned__

在ARC中用来标记方法的返回值内存管理类型,有以下三种:

#define NS_RETURNS_RETAINED __attribute__((ns_returns_retained))  
#define NS_RETURNS_NOT_RETAINED __attribute__((ns_returns_not_retained))  
#define NS_RETURNS_INNER_POINTER __attribute__((objc_returns_inner_pointer)) 

一般不需要我们自己对方法进行内存管理的定义,以上定义对应的使用情况也在前文做了列举。
根据这个标记,其实在编译器眼里,我们声明的方法都是长这样的:

@interface Sark : NSObject  
- (instancetype)doSomeThing NS_RETURNS_NOT_RETAINED; // 1  
- (instancetype)initWithMark:(NSString *)mark NS_RETURNS_RETAINED; // 2  
@end 

这也就是为什么在ARC中用new开头定义属性是不行的,编译器会报错:

@property (nonatomic, strong) NSString *newName;

因为在编译器眼里,new开头的方法作为new家族的方法,内存管理需要区分对待,如此声明编译器并不能懂。

对于NS_RETURNS_INNER_POINTER:

对于NS_RETURNS_INNER_POINTER这个标识,主要使用在返回的是一个对象的内部C指针的情况,

NSString的方法:

  • (__strong const char *)UTF8String NS_RETURNS_INNER_POINTER;

中途学习结束,再回头研究一下警告原因:

那行报警告的代码,编译器并不能判断我们调用方法的返回类型,ARC也不能够准确的进行上述四种内存管理情况的判断,所以默认会按照情况2进行非retain/release处理,但是如果我们调用的方法是init家族或者copy家族这种返回一个新对象的方法时,系统仍然按照非retain/release处理,那么虽然该方法的调用结果是开辟了一块内存空间,但是我们已经无法再去释放这块空间了,因为没有任何指针指向这块空间。

另外对于无返回值(void)的,或者返回值不是一个指针对象的方法,尽管我们可以忽略编译器的警告,但是仍然有些危险。
比如当用这行代码调用一个无返回值的方法时,并且使用一个指针试图这个返回值的时候会发生崩溃,如下:

    NSNumber *aNumber = [self performSelector:NSSelectorFromString(@"someVoidMethod") withObject:nil];
    NSLog(@"%@",aNumber);

- (CGFloat )someFloatMethod{
    return 5.f;
}

综上,编译器报警告“May Cause a Leak”是对的。

解决办法

方法一:

if (!_controller) { return; }
SEL selector = NSSelectorFromString(@"someMethod");
IMP imp = [_controller methodForSelector:selector];
void (*func)(id, SEL) = (void *)imp;
func(_controller, selector);

或者简洁一点但是稍显复杂:

SEL selector = NSSelectorFromString(@"someMethod");
((void (*)(id, SEL))[_controller methodForSelector:selector])(_controller, selector);

简单来说就是自己完善一个方法的完整信息,包括方法名,方法的实现和方法的返回值。以便让编译器知道我们自己清楚正在使用什么样的方法来规避警告。

方法二:
如果你清楚你在做什么,可以直接使用#pragma clang来忽视编译器的警告:

#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
    [self performSelector:NSSelectorFromString(item[@"kAction"])];
#pragma clang diagnostic pop

为了后续使用方便,也可以定义一个宏函数:

#define SuppressPerformSelectorLeakWarning(Stuff) \
    do { \
        _Pragma("clang diagnostic push") \
        _Pragma("clang diagnostic ignored \"-Warc-performSelector-leaks\"") \
        Stuff; \
        _Pragma("clang diagnostic pop") \
    } while (0)

用的时候:

SuppressPerformSelectorLeakWarning(
    [self performSelector:NSSelectorFromString(item[@"kAction"])]
);

需要返回值则如下:

id result;
SuppressPerformSelectorLeakWarning(
    result =  [self performSelector:NSSelectorFromString(item[@"kAction"])]
);

方法三:
投机取巧的方法,当方法无返回值的时候并且你并不在意方法在下一个Runloop中执行(runloop这块我也不是很懂),可以采用下面这一种方法:

[self performSelector:NSSelectorFromString(item[@"kAction"]) withObject:nil  afterDelay:0];

个人感觉是一种类似旁门左道的方法,不是很推荐使用。

总得来说,遇到这个警告,如果你知道内存关系,那么其实无视警告也可以。但是作为严谨的程序员,还是要采用一种方法去解决它,解决时候也是一样的道理,清楚内存关系。
方法一相对来说比较麻烦,但是手动操作一遍可以理顺这个方法的返回值,SEL,IMP等信息,也不失为一次思想上的避险。
方法二、三纯粹是为了消除警告,前提是确保无视之后不会引起内存问题。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,482评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,377评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,762评论 0 342
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,273评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,289评论 5 373
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,046评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,351评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,988评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,476评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,948评论 2 324
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,064评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,712评论 4 323
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,261评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,264评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,486评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,511评论 2 354
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,802评论 2 345

推荐阅读更多精彩内容