iOS-有效編寫高質量Objective-C方法-三

歡迎你們關注個人公衆號,我會按期分享一些我在項目中遇到問題的解決辦法和一些iOS實用的技巧,現階段主要是整理出一些基礎的知識記錄下來
編程

文章也會同步更新到個人博客:
ppsheep.com安全

本篇文章,主要是對OC中的一些原理的講解,可能會有一些枯燥,可是真正當你理解時,會有一種豁然開朗的感受。這裏會涉及到 對象,屬性,消息以及運行期的一些介紹,只有咱們真正理解了這些原理以後,咱們的開發水平纔會進一步提高,而不是止步於view的簡單編寫,view的編寫想要寫得好,也須要了解這一些原理。框架

理解屬性(property)這一律念

在開始講屬性以前,咱們先來理解一下幾個概念:函數

  • 對象:對象是"基本的構造單元",在OC中,咱們常用對象來儲存和傳遞數據
  • 消息傳遞:在對象之間傳遞數據並執行任務的過程就叫作消息傳遞
  • OC Runtime:當應用程序運行起來,爲其提供相關支持的代碼叫作"運行環境(Runtime)",它提供了一些使得對象之間可以傳遞數據的函數,而且包含了建立類實例須要的全部邏輯

上面三個概念,在OC編程中尤爲重要,雖然如今可能你沒有很深入的理解,可是隨着學習深刻,你確定可以體會到。性能

屬性

"屬性(property)"相信你們都很熟悉,是OC中用來存儲對象的數據的實例變量。實例變量通常是經過"存取方法"來訪問,"設置方法"來設置實例變量。在以前咱們已經講過,對於實例變量,若是是自己訪問,那麼讀取最好是直接讀取(採用下劃線方式直接訪問),而設置最好使用屬性來設置。具體的,能夠參見上一篇(iOS-有效編寫高質量Objective-C方法-二)。學習

對於一些簡單的概念性的東西我就不講了,給出結論就行:atom

  • 不必定要在接口中或者說是聲明文件中定義好全部的實例變量,能夠在實現文件中定義,以保護與類實現相關的內部信息
  • 屬性按照標準的命名方式,在編譯器編譯期間會自動加上存取方法

接下來,咱們來講幾個關鍵字:spa

@synthesize線程

咱們能夠在代碼中經過這個關鍵字來指定咱們想要的實例變量代理

例如:在頭文件中

@interface : NSObject

@property(nonatomic, copy) NSString *name;

@end複製代碼

這個屬性,在咱們運行期環境下,生成的實例變量爲_name,可是咱們在.m中並不想使用這個名稱,那麼咱們在實現文件裏就能夠這樣寫:

@implementation

@synthesize name =  _myName

@end複製代碼

那麼咱們在.m實現文件中,均可以直接使用_myName來操做屬性name

不過爲了書寫的規範,和團隊之間協做,我仍是建議按照規範的OC代碼風格來編寫代碼,團隊成員之間,一看就可以看清楚代碼

@dynamic

這個關鍵字 是用來阻止編譯器自動合成存取方法,不過這個關鍵字我都用的不多,上面的關鍵字一樣的,也使用較少。

這個關鍵字的意思是:阻止編譯器合成屬性所對應的實例變量,也不要合成實例變量的存取方法

這裏講一下實例變量就是帶下劃線的_name而屬性是經過property聲明的name

這兩個須要區分開來

屬性特質

nonatomic

所謂的屬性特質,就是指咱們在申明屬性的時候,property括號中跟的一些關鍵字

@property(nonatomic, readwrite, copy);複製代碼

其中的nonatomic,readwrite,copy這些都是屬性特質,咱們先來講nonatomic

這個關鍵字叫作屬性的原子性,通俗來講,這個關鍵字主要是來控制屬性的同步鎖
同步鎖:不一樣的線程在讀取屬性的時候,若是屬性是經過atomic來聲明的,那麼這兩個線程老是可以讀到屬性的有效值(注意這裏是有效的屬性值,並無說是正確的屬性值),若是屬性是經過nonatomic聲明的,那麼不一樣的線程讀取屬性值時,若是有線程正在修改該屬性的值,另外的線程正在讀取屬性值,那麼就可能將還未修改完成的屬性值讀取出來(這裏是還沒有修改完成的屬性值,有可能讀出一個徹底沒有任何意義的屬性值)

那麼又要來講一說,爲何咱們老是看到在編寫iOS程序時,屬性老是使用nonatomic來聲明的呢,這是由於在iOS中使用同步鎖的開銷太大,這會帶來性能問題。在通常狀況下,咱們並不要求屬性必須具備原子性,由於這個原子性並非說就是"線程安全了",若是咱們須要實現線程安全,那麼還須要使用更爲底層的同步鎖定機制才行,即使是使用atomic來聲明,不一樣的線程仍是可能讀取到不一樣的屬性值,只是說這個屬性值是有效的,有意義的。

因此咱們在開發iOS程序時,仍是使用nonaomic來聲明,可是在macOS程序開發中,卻不會遇到這種性能瓶頸,性能配置不同嘛

readwrite/readonly

這個屬性特質,咱們根據字面意思就能看出來,就是聲明屬性權限的,這個也沒什麼好說的了。

strong/copy/assign/weak

這個多是咱們平時用的最多的,也是思考最多的,其實平時咱們怎麼用,都是知道的,可是爲何這麼用呢?

assign:"設置方法"只會針對"純量類型"進行賦值,例如CGFloat、NSInteger這種

strong:此特質象徵了一種擁有關係,在"設置方法"中,這種屬性是先保留新值,而且釋放舊值,而後將新值設置上去

copy:這種方法和strong類型有點類似,可是它並非保留新值,而是直接就想新值拷貝,當屬性爲NSString類型時,咱們常用這種,那麼爲何咱們在NSString常用拷貝呢,覺得咱們在設置時,可能會傳進來一個NSMutableString對象,這個對象是NSString的子類,是能夠賦值給NSString的,若是咱們不使用拷貝,那麼當外部改變NSMultableString值時,咱們的屬性值也會直接被修改掉,因此這時,咱們就須要拷貝一份不可變的

weak:這個是一種弱引用,爲這種方法設置時,既不會保留新值,也不會釋放舊值,當屬性所指的對象銷燬時,屬性值也會被清空,在ViewController中定義view時咱們常常會使用到weak,可是咱們常常仍是將view聲明爲strong,固然這使用起來不會有很大的影響,可是咱們的應用在運行過程當中,就會產生不少的沒用view屬性值沒有被釋放掉,佔用無效內存。因此建議你們在使用view時,仍是聲明爲weak

@property(nonatomic,weak) UILable *lable;

//初始化lable時

UILable * lable = [[UILable alloc] init];
[self.view addSubview: lable];
self.lable = lable;複製代碼

方法名

在咱們定義的屬性爲Boolean值時,咱們的習慣是獲取方法,通常是"is"
開頭,那麼咱們就能夠在聲明時,這樣書寫

@property(nonatomic,getter=isOn) Bool on;複製代碼

屬性的獲取方法,就成了isOn;

咱們在屬性中定義了屬性特質,那麼咱們在書寫賦值時,就應該嚴格按照屬性特質賦值,例如,咱們有一個初始方法,須要對咱們的屬性NSString name賦值

- (instancetype)initWithName:(NSString *)name{
    if(self = [super init]){
    //此處就應該使用copy來對name賦值
        _name = [name copy];
    }
}複製代碼

以"類族模式"隱藏實現細節

類族是一種隱藏抽象基類背後實現細節的頗有用的模式。。那麼什麼叫作類族呢?

舉個例子:

在UIKit中有一個名叫UIButton的類,若是想要建立按鈕,咱們能夠調用一個類方法

+ (UIButton *)buttonWithType:(UIButtonType)type;複製代碼

該方法返回的對象,取決於傳入按鈕的類型,然而,無論傳入的是什麼類型,返回的類都是繼承自同一個基類:UIButton。 這樣,全部繼承自UIButton的類組成了一個類族。

在系統框架中,使用到了不少的類族。。

那麼爲何要這樣作呢?
UIButton這個例子,在實現時,使用者無需關心建立出來的按鈕是屬於哪個類,也不用考慮按鈕的繪製細節,我只須要知道,我怎麼建立按鈕,如何設置標題。如何增長點擊操做等。

建立類族

咱們如今來假設一個處理僱員的類,每一個僱員都擁有本身的名字和薪水這兩個屬性,管理者能夠命令器執行平常工做。可是,各類僱員的工做內容卻不一樣,經理在帶領僱員作項目的時候,無需關心每一個人怎樣完成本身的工做,只須要指示其開工便可。

首先咱們定義一個基類:

typedef NS_ENUM(NSUinteger, PPSEmployeeType){
    PPSEmployeeTypeDeveloper,
    PPSEmployeeTypeDesigner,
    PPSEmployeeTypeFinance,
}

@interface PPSEmployee    :    NSObject

@property (nonatomic, copy) NSStirng *name;
@property (nonamotic, assign) NSUInteger salary;

//建立方法
+ (PPSEmployee *)employeeWithType:(PPSEmployeeType) type;

//指示開工
- (void)doADaysWork;

@end複製代碼
@implementation PPSEmployee

+ (PPSEmployee *)employeeWithType:(PPSEmployeeType) type{
    switch (type){
        case PPSEmployeeTypeDeveloper:
            return [PPSEmployeeDeveloper new];
            break;
        case PPSEmployeeTypeDesigner:
            return [PPSEmployeeDesigner new];
            break;
        case PPSEmployeeTypeFinance:
            return [PPSEmployeeFinance new];
            break;
    }
}

- (void)doADaysWork{
    // 子類覆寫
}

@end複製代碼

每一個實體子類都是從基類繼承而來

@interface PPSEmployeeDeveloper    :    PPSEmployee
@end複製代碼
@implementation PPSEmployeeDeveloper

- (void)doADaysWork{
    [self coding];
}

@end複製代碼

咱們上面實現的方式,是根據僱員的類別,建立僱員類的實例,這實際上是一種工廠模式。在Java中,咱們知道這種方式通常是經過抽象類來實現,可是OC中沒有抽象類這一說,因而開發者一般會在文檔中寫明使用方法。

在Cocoa中 有不少的類族 大部分的集合類型都是類族 咱們在一個對象是不是屬於某一個類時,若是咱們採用下面的方式,每每得不到咱們想要的效果:

id myArr = @[@"a",@"b"];
if([myArr class] == [NSArray class]){
    //這裏永遠不會跑到 由於 咱們知道這裏[myArr class]返回的永遠是NSArr的一個子類,NSArray只是一個基類
}複製代碼

固然,咱們要作這個判斷的時候,應該都知道是使用isKindOfClass 這個方法,這個方法實際上是用來判斷是不是同一類族,而不是某個類

在既有類中使用關聯對象存放自定義數據

有時候咱們須要在對象中存放相關的信息,這時候咱們能想到的方法就是,建立一個子類,而後咱們使用的時候 直接使用子類。 可是並非全部狀況都可以這樣作,有時候類的實例多是因爲某種機制所創建的,咱們開發者是沒有辦法建立出本身創建的子類的實例。幸虧,咱們能夠經過OC的一項強大的特性"關聯對象"來解決這個問題。

那麼什麼事關聯對象呢?

咱們能夠給某個對象關聯許多其餘對象,這些對象經過"鍵"來區分。存儲值的時候,能夠經過指明存儲策略來維護內存,存儲策略就是你存儲的是一個NSString啊,那你就該把從存儲策略改成copy,相似於這種

下面是對象關聯類型:

關聯類型 等效的@property屬性
OBJC_ASSOCIATION_ASSIGN assign
OBJC_ASSOCIATION_COPY_NONATOMIC nonatomic,copy
OBJC_ASSOCIATION_RETAIN_NONATOMIC nonatomic,retain
OBJC_ASSOCIATION_COPY copy
OBJC_ASSOCIATION_RETAIN retain

管理關聯對象的相關方法:

  • void objc _ setAssociatedObject(id object, void *key, id value, objc_AssociationPolicy policy) 使用此方法 以給定的鍵和策略爲某對象設置關聯對象
  • void objc _ getAssociatedObject(id object, void *key) 使用此方法根據給定的鍵從某對象中獲取相關的關聯對象的值
  • void objc_removeAssociatedObject(id object)使用此方法移除指定對象的所有關聯對象

使用這種方法時,咱們能夠把某對象想象成一個NSDictionary,把關聯到該對象的值理解爲字典中的條目,那麼這些關聯對象,就至關於設置字典裏的值和獲取字典裏的值了

使用舉例

在iOS中,咱們若是想要使用UIAlertView 咱們須要這樣定義

UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"你肯定嗎?" 
                                     message:@"可能沒那麼肯定吧" 
                                     delegate:self
                                     cancelButtonTitle:@"取消" 
                                     otherButtonTitles:@"繼續", nil];
 [alert show];複製代碼

而後咱們須要實現UIAlertView的代理,來進行操做的識別判斷

-(void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex{
    if (buttonIndex == 0) {
        [self doCancle];
    }else{
        [self doContinue];
    }
}複製代碼

這樣寫,如今看來是沒有什麼問題的,可是若是咱們須要在當前的一個類中,處理多個警告信息,那麼代碼將會變得複雜,咱們須要在delegate中判斷當前的UIAlertView的信息,根據不一樣的信息實行不一樣的操做。

若是 咱們能在建立UIAlertView的時候 就將這些操做作好,那麼,在delegate中咱們就不須要判斷UIAlertView了。事實上這種方法是可行的。

UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"你肯定嗎?" 
                                 message:@"可能沒那麼肯定吧" 
                                 delegate:self 
                                 cancelButtonTitle:@"取消" 
                                 otherButtonTitles:@"繼續", nil];
void (^block) (NSInteger) = ^(NSInteger buttonIndex){
    if (buttonIndex == 0) {
        NSLog(@"cancle");
    }else{
        NSLog(@"continue");
    }
};

//將block設置爲UIAlertView的關聯對象
objc_setAssociatedObject(alert, PPSMyAlertViewKey, block, OBJC_ASSOCIATION_COPY);
[alert show];複製代碼

咱們只須要在delegate中拿到block就行

void (^block)(NSInteger) =  objc_getAssociatedObject(alertView, PPSMyAlertViewKey);
 block(buttonIndex);複製代碼
相關文章
相關標籤/搜索