OC 在子类中调用父类方法

需求

在控制器A中实现了webview的一些协议方法,控制器Aa继承了控制器A,并且也需要重写一些webview的协议方法,做一些特殊的事情,为了不影响原有的逻辑,需要判断,如果父类实现了这些方法,就需要调用父类的方法实现。通过[super method]可以做到调用,但要先判断父类中是否有实现,然后再调用,如果子类中重写了很多方法,这就有大量的重复代码。有没有优雅的实现呢?

OC中调用一个方法或者函数的几种方式

1.方法直接调用

[a method]
当这是一个私有方法,在类的实现外就没法用这种方式调用

2. performSelector

[self performSelector:<#(SEL)#> withObject:<#(id)#>]
有参数限制,最多两个。
因为编译器不知道怎么管理它的内存,会报编译警告。

3.NSInvocation

// 1. 根据方法创建签名对象sig
    NSMethodSignature *sig = [self.upDownScrollDelegate methodSignatureForSelector:@selector(tableView:updownScroll:)];
    
    // 2. 根据签名对象创建调用对象invocation
    NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:sig];
    
    // 3. 设置调用对象的相关信息
    invocation.target = self.upDownScrollDelegate;
    invocation.selector = @selector(tableView:updownScroll:);
 
    SITableView *tempSelf = self;
    // 参数必须从第2个索引开始,因为前两个已经被target和selector使用
    [invocation setArgument:&tempSelf atIndex:2];
    [invocation setArgument:&isUp atIndex:3];
    
    // 4. 调用方法
    [invocation invoke];

无参数限制

4.直接调用objc_msgSend

定义

#if !OBJC_OLD_DISPATCH_PROTOTYPES
OBJC_EXPORT void objc_msgSend(void /* id self, SEL op, ... */ )
#else
OBJC_EXPORT id objc_msgSend(id self, SEL op, ...)
#end

具体调用

#import <objc/runtime.h>
#import <objc/message.h>
((id (*)(id, SEL, id, id))objc_msgSend)(self, sel, webView, navigation); 

在Target -> BuildSetting ->Enable Strict Checking of objc_msgSend Calls 设为NO后,就不要强转函数指针了

objc_msgSend(self, sel, webView, navigation);

PS:实际上这也是平常所写的OC方法调用编译后的实现,

5.直接调用IMP

定义

/// A pointer to the function of a method implementation.  指向一个方法实现的指针
typedef id (*IMP)(id, SEL, ...); 

使用如下:

//直接调用,比较复杂
 IMP imp = [self  instanceMethodForSelector:sel];
 ((id(*)(id, SEL, id, id))imp)(self, sel, webView, navigation)

无参数限制。
需要强转函数指针,不好理解与使用。
比较偏底层,在项目中实际使用不多

objc_msgSend发送消息,在运行时找到方法对应的实现(IMP),然后调用实现(IMP)。

在子类中调用父类方法

在OC中,调用一个方法的逻辑是

  • 1.先在当前类对象的方法缓存中查找对应的方法
  • 2.没找到则在当前类对象的方法列表查找
  • 3.没找到则在父类中重复以上两个动作
  • 4.还没找到则走增加动态解析、备用消息接受者、完整转发
  • 5.还没则抛出异常

怎么在子类中调用父类方法呢,从OC方法调用逻辑中,发现有两个思路,直接调用super,找到父类方法的IMP直接调用。那么上面所说的在OC中调用方法的几种方式还能用吗,先看个例子。

判断父类有没有实现某个方法?

@interface NSObject <NSObject> 
......
+ (BOOL)instancesRespondToSelector:(SEL)aSelector;
+ (BOOL)conformsToProtocol:(Protocol *)protocol;
- (IMP)methodForSelector:(SEL)aSelector;
+ (IMP)instanceMethodForSelector:(SEL)aSelector;
....
@end

根据NSObject提供的方法,有两种简单的方式

 //1. 是否为YES
 BOOL res = [[super class] instancesRespondToSelector:sel];
 //2. 找到方法的实现,不为NULL
 IMP imp1 =  [super methodForSelector:sel];

两种方式都达不到预期,因为实际响应的都是子类。为什么呢,因为super它实际上只是一个“编译器标示符”,它负责告诉编译器,当调用方法时,去调用父类的方法,而不是本类中的方法,但消息的实际响应者还是自己。(自己都没有单独实现这些方法,是继承来的,所以直接调用self和super的方法实现是一样;同样调用者也不会发生改变,不可能产生一个新的对象。)

正确的调用方式应该是

 //1. 是否为YES
BOOL res = [[[self class] superclass] instancesRespondToSelector:sel];
/// [super class] 返回当前实例的类型,[[self class] superclass]、[self superclass]返回当前实例父类的类型
//BOOL res = [[self superclass] instancesRespondToSelector:sel];
 //2. 找到方法的实现,不为NULL
IMP imp1 =  [[[self class] superclass] instanceMethodForSelector:sel];

在子类中调用父类方法

先不说编译器能不能编译通过,用[super performSelector][invocation invokeWithTarget:super];的这种思路,但从上面的实践中就知道这条路行不通。实际上,编译器会编译失败,Use of undeclared identifier 'super'

1.直接方法调用

- (void)webView:(WKWebView *)webView didFailProvisionalNavigation:(null_unspecified WKNavigation *)navigation withError:(NSError *)error {

    if ([[self superclass] instancesRespondToSelector:_cmd]) {
        [super webView:webView didFailProvisionalNavigation:navigation withError:error];
    }
}

- (void)webView:(WKWebView *)webView didFinishNavigation:(null_unspecified WKNavigation *)navigation {
    if ([[self superclass] instancesRespondToSelector:_cmd]) {
        [super webView:webView didFinishNavigation:navigation];
    }
    
}

如果一个页面中有太多这样的代码,看着会比较塞心,不够优雅。

2.objc_msgSend

- (void)webView:(WKWebView *)webView didFailProvisionalNavigation:(null_unspecified WKNavigation *)navigation withError:(NSError *)error {

    if ([[self superclass] instancesRespondToSelector:_cmd]) {
        struct objc_super superReceiver = {
            self,
            [self superclass]
        };
        objc_msgSendSuper(&superReceiver, _cmd, webView, navigation, error);
    }
}

- (void)webView:(WKWebView *)webView didFinishNavigation:(null_unspecified WKNavigation *)navigation {
    if ([[self superclass] instancesRespondToSelector:_cmd]) {
        struct objc_super superReceiver = {
            self,
            [self superclass]
        };
        objc_msgSendSuper(&superReceiver, _cmd, webView, navigation);
    }
    
}

objc_msgSendSuper是objc_msgSend调用父类方法时的实现,即[super method]编译后就成了objc_msgSendSuper

PS:这是Target -> BuildSetting ->Enable Strict Checking of objc_msgSend Calls 设为NO后的调用

3.IMP

- (void)webView:(WKWebView *)webView didFailProvisionalNavigation:(null_unspecified WKNavigation *)navigation withError:(NSError *)error {

    IMP imp = [[[self class]superclass] instanceMethodForSelector:_cmd];
    if (imp) {
        imp(self, _cmd, webView, navigation, error);
    }
}

- (void)webView:(WKWebView *)webView didFinishNavigation:(null_unspecified WKNavigation *)navigation {
    IMP imp = [[[self class]superclass] instanceMethodForSelector:_cmd];
    if (imp) {
        imp(self, _cmd, webView, navigation);
    }
}

发现后两者的方法实现里,大部分代码是相同的,仅有的不同是方法参数的实现。很容易想到宏,它支持可变参数,并且可向内传递。具体实现如下

#define ZLLCallSuper(self, _cmd, format, ...) \
if ([[self superclass] instancesRespondToSelector:_cmd]) {\
struct objc_super superReceiver = {\
    self,\
    [self superclass]\
};\
objc_msgSendSuper(&superReceiver, _cmd, __VA_ARGS__);\
}

- (void)webView:(WKWebView *)webView didFailProvisionalNavigation:(null_unspecified WKNavigation *)navigation withError:(NSError *)error {

    ZLLCallSuper(self, _cmd, webView, navigation, error)
}

- (void)webView:(WKWebView *)webView didFinishNavigation:(null_unspecified WKNavigation *)navigation {
   ZLLCallSuper(self, _cmd, webView, navigation)
}

PS:这是Target -> BuildSetting ->Enable Strict Checking of objc_msgSend Calls 设为NO后的调用

但宏不支持类型检测,从内心深处是拒绝的,但如果直接定义可变参数的OC方法,没办法(或者说我还不知道)传递可变参数。
这种方式要改Xcode的配置,不然强制指针转换时宏替换编译不通过,最终的方式还是有瑕疵,还得改。

2019.3.13更新

默认情况下,系统的IMP定义,直接调用,会有编译错误Too many arguments to function call, expected 0, have n

typedef id (*IMP)(id, SEL, ...); 

我们重新定义IMP,对系统的强转,然后直接调用

typedef void(*ZLLIMP) (id, SEL, ...);
ZLLIMP imp = (ZLLIMP)[[[self class] superclass] instanceMethodForSelector:_cmd];
if (imp) {
      imp(self, _cmd, webView, navigation, error);
}

结合上面的宏,就可以比较优雅的实现在子类中调用父类方法了,但还是要有宏,还是有点不完美,继续探索。

PS:为什么直接用IMP会有编译错误,而我们自己定义的没有呢。从Xcode6之后,苹果不希望我们直接调用这些方法,取消了参数提示功能。可以通过Target -> BuildSetting ->Enable Strict Checking of objc_msgSend Calls 设为NO后的修改,打开参数提示功能

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

推荐阅读更多精彩内容