深入探討NSString、NSArray和NSDictionary為什么不可以繼承

版本記錄

版本號 時間
V1.0 2017.08.31

前言

NSStringNSArrayNSDictionary是大家一定會用到的類,但是大家有沒有發現,除了系統的三個子類NSMutableArrayNSMutableStringNSMutableDictionary繼承自它們幾個,你見過自定義的類繼承它們的嗎?下面我們就深入探討這個問題。

問題提出

就我目前做的這些項目而言,說實話我沒見過誰自定義類繼承NSStringNSArrayNSDictionary,是因為完全沒有這個功能要求上的必要自定義類繼承自它們,還是因為蘋果原則上就不允許我們自定義繼承它們的類呢?

下面我們就研究一下。


問題分析

下面我們還是先看代碼,自定義一個類繼承自NSString,如下所示。

1. JJString.h
#import <Foundation/Foundation.h>

@interface JJString : NSString

@end
2. JJTestStringVC.h
#import <UIKit/UIKit.h>

@interface JJTestStringVC : UIViewController

@end
3. JJTestStringVC.m
#import "JJTestStringVC.h"
#import "JJString.h"

@interface JJTestStringVC ()

@end

@implementation JJTestStringVC

- (void)viewDidLoad
{
    [super viewDidLoad];
    
    JJString *str = [JJString stringWithString:@"ABC"];
    NSLog(@"%@", str);
}

@end

運行就發現奔潰了,輸出信息如下所示:

2017-08-31 19:06:37.081 JJOC[1374:34198] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '*** initialization method -initWithCharactersNoCopy:length:freeWhenDone: cannot be sent to an abstract object of class JJString: Create a concrete instance!'
*** First throw call stack:
(
    0   CoreFoundation                      0x000000010d8b4b0b __exceptionPreprocess + 171
    1   libobjc.A.dylib                     0x000000010cf3b141 objc_exception_throw + 48
    2   CoreFoundation                      0x000000010d91d625 +[NSException raise:format:] + 197
    3   Foundation                          0x000000010cb45730 -[NSString initWithCharactersNoCopy:length:freeWhenDone:] + 14
    4   Foundation                          0x000000010ca49869 +[NSString stringWithString:] + 45
    5   JJOC                                0x000000010c75a3fe -[JJTestStringVC viewDidLoad] + 94
    6   UIKit                               0x000000010f30f01a -[UIViewController loadViewIfRequired] + 1235
    7   UIKit                               0x000000010f34de6c -[UINavigationController _layoutViewController:] + 56
    8   UIKit                               0x000000010f34e74a -[UINavigationController _updateScrollViewFromViewController:toViewController:] + 466
    9   UIKit                               0x000000010f34e8bb -[UINavigationController _startTransition:fromViewController:toViewController:] + 127
    10  UIKit                               0x000000010f34fa03 -[UINavigationController _startDeferredTransitionIfNeeded:] + 843
    11  UIKit                               0x000000010f350b41 -[UINavigationController __viewWillLayoutSubviews] + 58
    12  UIKit                               0x000000010f54260c -[UILayoutContainerView layoutSubviews] + 231
    13  UIKit                               0x000000010f22f55b -[UIView(CALayerDelegate) layoutSublayersOfLayer:] + 1268
    14  QuartzCore                          0x000000010edae904 -[CALayer layoutSublayers] + 146
    15  QuartzCore                          0x000000010eda2526 _ZN2CA5Layer16layout_if_neededEPNS_11TransactionE + 370
    16  QuartzCore                          0x000000010eda23a0 _ZN2CA5Layer28layout_and_display_if_neededEPNS_11TransactionE + 24
    17  QuartzCore                          0x000000010ed31e92 _ZN2CA7Context18commit_transactionEPNS_11TransactionE + 294
    18  QuartzCore                          0x000000010ed5e130 _ZN2CA11Transaction6commitEv + 468
    19  QuartzCore                          0x000000010ed5eb37 _ZN2CA11Transaction17observer_callbackEP19__CFRunLoopObservermPv + 115
    20  CoreFoundation                      0x000000010d85a717 __CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__ + 23
    21  CoreFoundation                      0x000000010d85a687 __CFRunLoopDoObservers + 391
    22  CoreFoundation                      0x000000010d83f038 CFRunLoopRunSpecific + 440
    23  UIKit                               0x000000010f16608f -[UIApplication _run] + 468
    24  UIKit                               0x000000010f16c134 UIApplicationMain + 159
    25  JJOC                                0x000000010c75a37f main + 111
    26  libdyld.dylib                       0x0000000111b3a65d start + 1
    27  ???                                 0x0000000000000001 0x0 + 1
)
libc++abi.dylib: terminating with uncaught exception of type NSException

從上面我們可以看見:

  • 方法NSString stringWithString:調用后,會調用NSString initWithCharactersNoCopy:length:freeWhenDone:。然后就接著調用NSException raise:format:,接著就會拋出異常。

這里需要理解一下:這里就是類簇的問題,類簇就是把一組有共同特性的子類都繼承說一個父類,當我們在需要各個子類的時候,我們就需要去操作父類就可以,運用抽象工廠模式,父類會根據你的需求返回給你相應的子類。

下面我們在看一下例子

#import "JJTestStringVC.h"
#import "JJString.h"

@interface JJTestStringVC ()

@end

@implementation JJTestStringVC

- (void)viewDidLoad
{
    [super viewDidLoad];
    
    id obj1 = [NSString alloc];
    id obj2 = [NSMutableString alloc];
    
    id obj3 = [obj1 init];
    id obj4 = [obj2 init];
    
    id obj5 = [JJString alloc];
    id obj6 = [obj5 init];
    
    NSLog(@"obj1 = %@", [obj1 class]);
    NSLog(@"obj2 = %@", [obj2 class]);
    NSLog(@"obj3 = %@", [obj3 class]);
    NSLog(@"obj4 = %@", [obj4 class]);
    NSLog(@"obj5 = %@", [obj5 class]);
    NSLog(@"obj6 = %@", [obj6 class]);
}

@end

下面看輸出結果

2017-08-31 20:34:54.585 JJOC[2358:75128] obj1 = NSPlaceholderString
2017-08-31 20:34:54.585 JJOC[2358:75128] obj2 = NSPlaceholderMutableString
2017-08-31 20:34:54.585 JJOC[2358:75128] obj3 = __NSCFConstantString
2017-08-31 20:34:54.586 JJOC[2358:75128] obj4 = __NSCFString
2017-08-31 20:34:54.586 JJOC[2358:75128] obj5 = JJString
2017-08-31 20:34:54.586 JJOC[2358:75128] obj6 = JJString

這里大家可以看到:

  • NSStringNSMutableString調用alloc的時候會生成一個對象NSPlaceholderString
  • NSString調用init的時候會生成對象__NSCFConstantString,而NSMutableString調用init的時候會生成對象__NSCFString
  • JJString調用allocinit的時候還是JJString對象。

為什么會是這個樣子,其實可以從下面幾個情況著手:

  • 這里,NSPlaceholderString是一個中間對象。后面的- init- initWithXXXXX消息都是發送給這個中間對象,再由它做工廠,生成真的對象分別是這里的NSCFConstantStringNSCFString類。

那么為什么我們自己的類調用alloc時,就不返回NSPlaceholderString這個類對象了呢?關鍵就在于NSString alloc方法的實現。NSString的alloc方法實現可以猜測一下:

@class NSPlaceholderString;  

@interface NSString:(NSObject)  

+  (id) alloc;  

@ end  

@implementation NSString  

+(id) alloc 
{  
    if ([self isEquals:[NSString class]]) {  
        return [NSPlaceholderString alloc];  
    }  
    else  
        return [super alloc];  
}  
@end  

@interface NSPlaceholderString:(NSString)  

@end 

關鍵就在于alloc的實現,可以發現,當只用NSString調用alloc的時候,由于self == [NSString class],所以這時返回的是NSPlaceholderString的類對象;而使用其他類(比如派生類)調用alloc時,返回的是super的 alloc,這里也就是[NSObject alloc],而NSObject的alloc方法返回的是調用類的類對象,所以在我們用我們自己的LBString就是LBString類的類對象了所以沒有NSString 的一些方法了。

參考文章

1. Foundation庫下的NSString,NSArray, NSDictionary……可以被繼承嗎

后記

感謝這個技術大牛給的技術指導,致敬,引用參考已經列到參考文章里面了。謝謝大家,希望對大家有所幫助。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容