Objective-C 類與對象

類方法

OC 中類的方法只有類的靜態(tài)方法和類的實例方法

@interface Controller : NSObject

+ (void)thisIsAStaticMethod;    // 靜態(tài)方法

– (void)thisIsAnInstanceMethod; // 實例方法

@end

OC 中的方法只要聲明在 @interface 里,就可以認(rèn)為是對外公開的,公有的。

聲明和實現(xiàn)都寫在 @implementation 里的方法,就是類私有的,類的外部是看不到的。

可以使用 Category 來實現(xiàn)私有方法:

// AClass.h
@interface AClass : NSObject

-(void)sayHello;

@end

// AClass.m
@interface AClass (private)

-(void)privateSayHello;

@end

@implementation AClass

-(void)sayHello {
    [self privateSayHello];
}

-(void)privateSayHello {
    NSLog(@"偷偷的 Say Hello");
}

使用這種方法時,外部就不能直接調(diào)用到 privateSayHello 方法。

注意在上面的代碼里面,當(dāng)我們想通過 Category 來進(jìn)行方法隱藏的時候,我們可以把實現(xiàn)放在主 implementation 里。當(dāng)我們想擴(kuò)展別的不能獲取到源代碼的類,或者想把不同 Category 的實現(xiàn)分開,可以新建 <ClassName>+CategoryName.m 文件,在里面進(jìn)行實現(xiàn):

#import "SystemClass+CategoryName.h"

@implementation SystemClass ( CategoryName )
// method definitions
@end

也可以使用 Extension 來實現(xiàn)私有方法:

// AClass.h 與上面相同

// AClass.m
@interface AClass()

-(void)privateSayHello;

@end

@implementation AClass

-(void)sayHello {
    [self privateSayHello];
}

-(void)privateSayHello {
    NSLog(@"Private Hello");
}

@end

與使用 Category 類似,由于聲明隱藏在 .m 中,調(diào)用者無法看到其聲明,也就無法調(diào)用 privateSayHello 這個方法,在ARC下會引發(fā)編譯錯誤。

和使用 Category 相比,使用 Extension 有以下兩個好處:

  1. Extension 聲明的方法必須在類的主 @implementation 區(qū)間內(nèi)實現(xiàn),可以避免使用有名 Category 帶來的多個不必要的 implementation 段。
  2. 如果 Extension 中聲明的方法沒有實現(xiàn),編譯器會給出 Warning,使用 Category 則不會。

類變量

蘋果推薦在現(xiàn)代 Objective-C 中使用 @property 來實現(xiàn)成員變量:

@interface AClass : NSObject

@property (nonatomic, copy) NSString *name;

@end

使用@property聲明的變量可以使用實例名.變量名來獲取和修改。

@property可以看做是一種語法糖,在 MRC 下,使用 @property 可以看成實現(xiàn)了下面的代碼:

// AClass.h
@interface AClass : NSObject{
@public
    NSString *_name;
}

-(NSString*) name;
-(void) setName:(NSString*)newName;
@end

// AClass.m
@implementation AClass

-(NSString*) name{
    return _name;
}

-(void) setName:(NSString *)name{
    if (_name != name) {
        [_name release];
        _name = [name copy];
    }
}
@end

也就是說,@property 會自動生成 getter 和 setter, 同時進(jìn)行自動內(nèi)存管理。
@property 的說明可以有以下幾種:

  • readwrite 是可讀可寫特性;需要生成 getter 方法和 setter 方法
  • readonly 是只讀特性,只會生成 getter 方法 不會生成 setter 方法,不希望屬性在類外改變時使用
  • assign 是賦值特性,setter 方法將傳入?yún)?shù)賦值給實例變量;僅設(shè)置變量時;
  • retain 表示持有特性,setter 方法將傳入?yún)?shù)先保留,再賦值,傳入?yún)?shù)的 retain count 會+1;
  • copy 表示拷貝特性,setter 方法將傳入對象復(fù)制一份;需要完全一份新的變量時。
  • nonatomic 和 atomic ,決定編譯器生成的 setter getter是否是原子操作。 atomic 表示使用原子操作,可以在一定程度上保證線程安全。一般推薦使用 nonatomic ,因為 nonatomic 編譯出的代碼更快

默認(rèn)的 @property 是 readwrite,assign,atomic。

@synthesize 和 @dynamic

@property 背后使用 synthesize 來生成 getter 和 setter,對于現(xiàn)代 OC 來說,編譯器默認(rèn)會進(jìn)行自動 synthesize,把 ivar 和屬性綁定起來:

@synthesize propertyName = _propertyName

這樣,就不需要我們寫任何代碼,就可以直接使用 getter 和 setter 了。

然而并不是所有情況下編譯器都會進(jìn)行自動 synthesize,具體由下面幾種:

  • 可讀寫(readwrite)屬性實現(xiàn)了自己的 getter 和 setter
  • 只讀(readonly)屬性實現(xiàn)了自己的 getter
  • 使用 @dynamic,顯式表示不希望編譯器生成 getter 和 setter
  • protocol 中定義的屬性,編譯器不會自動 synthesize,需要手動寫
  • 當(dāng)重載父類中的屬性時,也必須手動寫 synthesize

類的擴(kuò)展——Protocol, Category 和 Extension

Protocol

OC是單繼承的,OC中的類可以實現(xiàn)多個 protocol 來實現(xiàn)類似 C++ 中多重繼承的效果。

Protocol 類似 Java 中的 interface,定義了一個方法列表,這個方法列表中的方法可以使用 @required@optional 標(biāo)注,以表示該方法是否是客戶類必須要實現(xiàn)的方法。 一個 protocol 可以繼承其他的 protocol 。

@protocol TestProtocol<NSObject> // NSObject也是一個 Protocol,這里即繼承 NSObject 里的方法
-(void)Print;               
@end

@interface B : NSObject<TestProtocol>
-(void)Print; // 默認(rèn)方法是@required的,即必須實現(xiàn)
@end

Delegate(委托)是 Cocoa 中常見的一種設(shè)計模式,其實現(xiàn)依賴于 protocol 這個語言特性。

含有 property 的 Protocol

上面提到過,當(dāng) Protocol 中含有 property 時,編譯器是不會進(jìn)行自動 synthesize 的,需要手動處理:

@class ExampleClass;

@protocol ExampleProtocol

@required

@property (nonatomic, retain) ExampleClass *item;

@end

在實現(xiàn)這個 Protocol 的時候,要么再次聲明 property:

@interface MyObject : NSObject <ExampleProtocol>

@property (nonatomic, retain) ExampleClass *item;

@end

要么進(jìn)行手動 synthesize:

@interface MyObject : NSObject <ExampleProtocol>
@end

@implementation MyObject
@synthesize item;

@end

工程自帶的 AppDelegate 使用了前一種方法,UIApplicationDelegate protocol 當(dāng)中定義了 window 屬性:

@property (nonatomic, retain) UIWindow *window NS_AVAILABLE_IOS(5_0);

在 AppDelegate.h 中我們可以看到這個:

@interface AppDelegate : UIResponder <UIApplicationDelegate>

@property (nonatomic, strong) UIWindow *window;

@end

Category

Category 是一種很靈活的擴(kuò)展原有類的機(jī)制,使用 Category 不需要訪問原有類的代碼,也無需繼承。Category提供了一種簡單的方式,來實現(xiàn)類的相關(guān)方法的模塊化,把不同的類方法分配到不同的類文件中。

Category 常見的使用方法如下:

// SomeClass.h
@interface SomeClass : NSObject{
}
-(void) print;
@end

// SomeClass+Hello.h
#import "SomeClass.h"

@interface SomeClass (Hello)
-(void)hello;
@end

// 實現(xiàn)
#import "SomeClass+Hello.h"
@implementationSomeClass (Hello)
-(void)hello{
    NSLog (@"name:%@ ", @"Jacky");
}
@end

在使用 Category 時需要注意的一點是,如果有多個命名 Category 均實現(xiàn)了同一個方法(即出現(xiàn)了命名沖突),那么這些方法在運(yùn)行時只有一個會被調(diào)用,具體哪個會被調(diào)用是不確定的。因此在給已有的類(特別是 Cocoa 類)添加 Category 時,推薦的函數(shù)命名方法是加上前綴:

@interface NSSortDescriptor (XYZAdditions)
+ (id)xyz_sortDescriptorWithKey:(NSString *)key ascending:(BOOL)ascending;
@end

Extension

Extension 可以認(rèn)為是一種匿名的 Category, Extension 與 Category 有如下幾點顯著的區(qū)別:

  1. 使用 Extension 必須有原有類的源碼
  2. Extension 可以在類中添加新的屬性和實例變量,Category 不可以(注:在 Category 中實際上可以通過運(yùn)行時添加新的屬性,下面會講到)
  3. Extension 里添加的方法必須要有實現(xiàn)(沒有實現(xiàn)編譯器會給出警告)
    下面是一個 Extension 的例子:
@interface MyClass : NSObject  
- (float)value;  
@end  

@interface MyClass () { // 注意此處擴(kuò)展的寫法  
    float value;  
}  
- (void)setValue:(float)newValue;  
@end  

@implementation MyClass  

- (float)value {  
    return value;  
}  

- (void)setValue:(float)newValue {  
    value = newValue;  
}  
@end

Extension 很常見的用法,是用來給類添加私有的變量和方法,用于在類的內(nèi)部使用。例如在 interface 中定義為 readonly 類型的屬性,在實現(xiàn)中添加 extension,將其重新定義為 readwrite,這樣我們在類的內(nèi)部就可以直接修改它的值,然而外部依然不能調(diào)用 setter 方法來修改。示例代碼如下(來自蘋果官方文檔):

XYZPerson.h

@interface XYZPerson : NSObject
...
@property (readonly) NSString *uniqueIdentifier;

@end
XYZPerson.m

@interface XYZPerson ()
@property (readwrite) NSString *uniqueIdentifier;
@end

@implementation XYZPerson
...
@end

如何給已有的類添加屬性

首先強(qiáng)調(diào)一下上面例子中所展示的,Extension 可以給類添加屬性,編譯器會自動生成 getter,setter 和 ivar。 Category 并不支持這些。如果使用 Category 的話,類似下面這樣:

@interface XYZPerson (UDID)
@property (readwrite) NSString *uniqueIdentifier;
@end

@implementation XYZPerson (UDID)
...
@end

盡管編譯可以通過,但是當(dāng)真正使用 uniqueIdentifier 時直接會導(dǎo)致程序崩潰。

如果我們手動去 synthesize 呢?像下面這樣:

@implementation XYZPerson (UDID)
@synthesize uniqueIdentifier;
...
@end

然而這樣做的話,代碼直接報編譯錯誤了:

@synthesize not allowed in a category's implementation

看來這條路是徹底走不通了。

不過我們還有別的方法,想通過 Category 添加屬性的話,可以通過 Runtime 當(dāng)中提供的 associated object 特性。NSHipster 的 這篇文章 展示了具體的做法。

如何在類中添加全局變量

有些時候我們需要在類中添加某個在類中全局可用的變量,為了避免污染作用域,一個比較好的做法是在 .m 文件中使用 static 變量:

static NSOperationQueue * _personOperationQueue = nil;

@implementation XYZPerson
...
@end

由于 static 變量在編譯期就是確定的,因此對于 NSObject 對象來說,初始化的值只能是 nil。如何進(jìn)行類似 init 的初始化呢?可以通過重載 initialize 方法來做:

@implementation XYZPerson
- (void)initialize {
    if (!_personOperationQueue) {
        _personOperationQueue = [[NSOperationQueue alloc] init];
    }
}
@end

為什么這里要判斷是否為 nil 呢?因為 initialize 方法可能會調(diào)用多次,后面會提到。

如果是通過 Category 呢?當(dāng)然也可以通過 initialize,不過除非必須的情況下,并不推薦在 Category 當(dāng)中進(jìn)行重載。

下面介紹一個有點黑魔法的方法,除了 initilize 之外,我們還可以通過編譯器的 attribute 特性來實現(xiàn)初始化:

__attribute__((constructor))
static void initialize_Queue() {
    _personOperationQueue = [[NSOperationQueue alloc] init];
}

@implementation XYZPerson (Operation)

@end

類的導(dǎo)入

導(dǎo)入類可以使用 #include , #import@class 三種方法,其區(qū)別如下:

  • #import是Objective-C導(dǎo)入頭文件的關(guān)鍵字,#include是C/C++導(dǎo)入頭文件的關(guān)鍵字
  • 使用#import頭文件會自動只導(dǎo)入一次,不會重復(fù)導(dǎo)入,相當(dāng)于#include和#pragma once;
  • @class告訴編譯器需要知道某個類的聲明,可以解決頭文件的相互包含問題;
  • @class是放在interface中的,只是在引用一個類,將這個被引用類作為一個類型使用。在實現(xiàn)文件中,如果需要引用到被引用類的實體變量或者方法時,還需要使用#import方式引入被引用類。

類的初始化

Objective-C 是建立在 Runtime 基礎(chǔ)上的語言,類也不例外。OC 中類是初始化也是動態(tài)的。在 OC 中絕大部分類都繼承自 NSObject,它有兩個非常特殊的類方法 loadinitilize,用于類的初始化

+load

+load 方法是當(dāng)類或分類被添加到 Objective-C runtime 時被調(diào)用的,實現(xiàn)這個方法可以讓我們在類加載的時候執(zhí)行一些類相關(guān)的行為。子類的 +load 方法會在它的所有父類的 +load 方法之后執(zhí)行,而分類的 +load 方法會在它的主類的 +load 方法之后執(zhí)行。但是不同的類之間的 +load 方法的調(diào)用順序是不確定的。

load 方法不會被類自動繼承, 每一個類中的 load 方法都不需要像 viewDidLoad 方法一樣調(diào)用父類的方法。子類、父類和分類中的 +load 方法的實現(xiàn)是被區(qū)別對待的。也就是說如果子類沒有實現(xiàn) +load 方法,那么當(dāng)它被加載時 runtime 是不會去調(diào)用父類的 +load 方法的。同理,當(dāng)一個類和它的分類都實現(xiàn)了 +load 方法時,兩個方法都會被調(diào)用。因此,我們常??梢岳眠@個特性做一些“邪惡”的事情,比如說方法混淆(Method Swizzling)。FDTemplateLayoutCell 中就使用了這個方法,見這里。

+initialize

+initialize 方法是在類或它的子類收到第一條消息之前被調(diào)用的,這里所指的消息包括實例方法和類方法的調(diào)用。也就是說 +initialize 方法是以懶加載的方式被調(diào)用的,如果程序一直沒有給某個類或它的子類發(fā)送消息,那么這個類的 +initialize 方法是永遠(yuǎn)不會被調(diào)用的。

+initialize 方法的調(diào)用與普通方法的調(diào)用是一樣的,走的都是發(fā)送消息的流程。換言之,如果子類沒有實現(xiàn) +initialize 方法,那么繼承自父類的實現(xiàn)會被調(diào)用;如果一個類的分類實現(xiàn)了 +initialize 方法,那么就會對這個類中的實現(xiàn)造成覆蓋。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • Swift1> Swift和OC的區(qū)別1.1> Swift沒有地址/指針的概念1.2> 泛型1.3> 類型嚴(yán)謹(jǐn) 對...
    cosWriter閱讀 11,618評論 1 32
  • 一、概述 Objective-C語言是一門動態(tài)語言,它將很多靜態(tài)語言在編譯和鏈接期所做的事推遲到運(yùn)行時處理。這種動...
    Fly晴天里Fly閱讀 1,284評論 0 6
  • 面向?qū)ο蟮娜筇匦裕悍庋b、繼承、多態(tài) OC內(nèi)存管理 _strong 引用計數(shù)器來控制對象的生命周期。 _weak...
    運(yùn)氣不夠技術(shù)湊閱讀 1,216評論 0 10
  • Objective-C在C語言的基礎(chǔ)上引入了object-oriented(面向?qū)ο?的概念。其class(類)與...
    東忙忙西茫茫閱讀 475評論 0 0
  • 2017.5.23 星期二 小雨轉(zhuǎn)晴 親子日記(30) 下班看到還不到點,就去喂了喂小白兔,這兩只小兔子見長了。哈...
    于澤媽媽閱讀 235評論 0 1

友情鏈接更多精彩內(nèi)容