1. 程式人生 > >為什麼說 Objective-C 沒有私有方法和私有變數

為什麼說 Objective-C 沒有私有方法和私有變數

首先,我們先來看一下私有的定義:私有是指只能夠在本類內部使用或訪問,但是不能在類的外部被訪問。

看起來好像沒有什麼問題,不過以下的幾種方式的確打破了上面的規則。

訪問私有方法

在 Objective-C 中,物件呼叫方法是以傳送訊息的形式實現的。所有方法的呼叫最終都會轉化為傳送訊息的形式,原型如下:

id objc_msgSend(id self, SEL op, ...)

我們定義如下兩個類,實現子類訪問父類的 "私有方法"。

#import <objc/message.h>
// MMFather.h
@interface MMFather : NSObject 
@end
// MMFather.m @implementation MMFather (void)run:(NSString *)param { NSLog(@"Father run with %@", param); } @end // MMSon.h @interface MMSon : MMFather @end // MMSon.m @implementation MMSon @end// main.m int main(int argc, const char * argv[]) { @autoreleasepool { MMSon *son = [MMSon new]; ​ // 方法一
#pragma clang diagnostic push #pragma clang diagnostic ignored "-Wundeclared-selector" objc_msgSend(son, @selector(run:), @"iPhone"); #pragma clang diagnostic pop// 方法二 #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wundeclared-selector" if ([son respondsToSelector:@selector
(run:)]) { [son performSelector:@selector(run:) withObject:@"iPhone"]; } #pragma clang diagnostic pop ​ } return 0; } ≈

我們聲明瞭兩個類,一個 MMFather 類,一個繼承自 MMFather 的 MMSon 類。在 MMFather.m 中實現 run: 方法,但是並沒有在 MMFather.h 中公開。如果我們直接使用 Son 例項呼叫 run 方法編譯器會報錯。

但是我們使用上述兩種方法都能成功實現對父類私有方法的訪問。"-Wundeclared-selector" 是為了消除編譯器找不到方法的警告,該方式只能在確認某個方式實現的前提下使用,否者會在執行時奔潰。

Objective-C 為什麼能夠實現訪問「私有方法」呢?其實這跟 Objective-C 語言的動態特性有密切的關係,物件在執行的時候才會去查詢方法。Objective-C 物件有一個 isa 指標指向其父類,在向該例項傳送訊息的時候,若它自己不能識別回到父類中去查詢該訊息。

如下如所示:


objective-c_message.png

具體有關 Objective-C 物件模型的知識可以參看這篇:Objective-C物件模型及應用

訪問私有變數

私有變數的定義類似私有方法,但是在 Objective-C 中,仍然可以通過 runtime 來實現對私有變數的訪問。

在 MMSon.m 中加入一個私有方法,在 main.m 中訪問:

 @implementation MMFather {
    NSString *_name;
 }

 // main.m
 {
    MMSon *son = [[MMSon alloc] initWithName:@"小白"];
    Ivar nameIvar = class_getInstanceVariable([son class], "_name");
    NSString *name = object_getIvar(son, nameIvar);
    NSLog(@"son name: %@", name);
 }

控制檯成功的打印出了:小白
綜上所述:Objective-C 的確是沒有絕對的私有方法和私有變數的,也這是因為 OC 的這種特性,可以讓開發者做更多 trick 的事。