你好,歡迎來到IOS教程網

 Ios教程網 >> IOS編程開發 >> IOS開發綜合 >> [編寫高質量iOS代碼的52個有效方法](一)Objective-C基礎

[編寫高質量iOS代碼的52個有效方法](一)Objective-C基礎

編輯:IOS開發綜合

先睹為快

1.了解Objective-C語言的起源
2.在類的頭文件中盡量少引入其他頭文件
3.多用字面量語法,少用與之等價的方法
4.多用類型常量,少用#define預處理器指令
5.用枚舉表示狀態、選項、狀態碼

 

第1條:了解Objective-C語言的起源

Objective-C與C++,Java等面向對象語言的區別在於Objective-C使用“消息結構”,而不是“函數調用”。Objective-C語言是由消息型語言鼻祖Smalltalk演化而來。

消息與函數調用之間的語法區別:

// 消息(Objective-C)
Object *obj = [Object new];
[obj performWith:parameter1 and:parameter2];

// 函數調用(C++)
Object *obj = new Object;
obj->perform(parameter1,parameter2);

關鍵區別在於:使用消息結構的語言,其運行時所應執行的代碼由運行環境來決定;而使用函數調用的語言,則由編譯器決定。Objective-C的重要工作都由運行時組件而非編譯器完成。

Objective-C是C的超集,所以C語言中的所有功能在編寫Objective-C代碼時依然適用。理解C語言的內存模型尤為重要,這有助於理解Objective-C的內存模型及其引用計數機制的工作原理。Objective-C語言中的指針是用來指示對象的。想要聲明一個變量,另其指代某個對象:

NSString *someString = @"the string";
NSString *anotherString = someString;

兩個變量都是指向NSString的指針。所有Objective-C語言的對象都必須這樣聲明,因為對象所占內存總是分配在堆中,而不是棧中。不在再棧上分配Objective-C對象。而兩個變量所占內存都分配在棧上,且兩塊內存裡的值一樣,都是NSString對象的內存地址。

這裡寫圖片描述<喎?/kf/ware/vc/" target="_blank" class="keylink">vcD4NCjxwPjxjb2RlIGNsYXNzPQ=="hljs javascript">分配在堆中的內存必須直接管理,而分配在棧中用於保存變量的內存則會在其棧幀彈出時自動清理。

在Objective-C代碼中,有時也會遇到定義裡不含*的變量,它們可能會使用棧空間,例如:

CGRect frame;

CGRect是C結構體,整個系統框架都在使用這種結構體。如果改用Objective-C對象來做,需要額外開銷,如分配及釋放堆內存等。如果只需要保存int、float、double、char等非對象類型,通常使用CGRect這種結構體就可以了。

第2條:在類的頭文件中盡量少引入其他頭文件

與C和C++一樣,Objective-C也使用頭文件與實現文件來區隔代碼。

創建一個EOCPerson類,並在類中用到另一個類EOCEmployer類的實例

// EOCPerson.h
#import 

@class EOCEmployer;

@interface EOCPerson : NSObject
@property(nonatomic, copy) NSString *firstName;
@property(nonatomic, copy) NSString *lastName;
@property(nonatomic, strong) EOCEmployer *employer;
@end

// EOCPerson.m
#import "EOCPerson.h"
#import "EOCEmployer.h"

@implementation EOCPerson
// 實現方法
@end

在頭文件中用@class EOCEmployer;語句告訴編譯器需要用到這個類,不需要知道該類的全部細節,再在需要知道其所有接口細節的實現文件中用#import "EOCEmployer.h"引入EOCEmployer類。這種做法叫做向前聲明。如果直接在頭文件中引入EOCEmployer.h,則會一並引入EOCEmployer.h中的所有內容,此過程持續下去,則要引入許多根本用不到的內容,增加編譯時間,還可能造成循環引用。

但有些時候無法使用向前聲明,例如自定義的類繼承自某個超類或遵從某個協議。

// EOCRectangle.h
#import "EOCShape.h"
#import "EOCDrawable.h"

@interface EOCRectangle : EOCShape 
@property(nonatomic, assign) float width;
@property(nonatomic, assign) float height;
@end

如果自定義的類繼承於某個超類,則必須引入定義那個超類的頭文件,如果要遵從某個協議,盡量把該類遵循某協議的聲明移到分類(什麼是分類?)中。如果不行的話,就把協議單獨放在一個頭文件中,然後將其引入。

第3條:多用字面量語法,少用與之等價的方法

Foundation框架中的NSString、NSNumber、NSArray、NSDictionary這4個類的實例的聲明,即可以用常見的alloc及init方法,也可以直接用字面量語法來聲明:

// 將整數、浮點數封入到Objective-C對象中,可以用字面量,也可以調用NSNumber中的方法。
// 字面量
NSNumber *intNumber = @1;
// 等價方法
NSNumber *intNumber = [NSNumber numberWithInt:1];
// 字面量
NSNumber *doubleNumber = @3.14159
// 等價方法
NSNumber *doubleNumber = [NSNumber numberWithDouble:3.14159];

// 字面量語法也適用於表達式
int x = 5;
float y = 6.32f;
NSNumber *expressionNumber = @(x * y);
// 使用方法和字面量語法創建數組
NSArray *animals = [NSArray arrayWithObjects:@"cat",@"dog",@"mouse",nil];
NSArray *animals = @[@"cat",@"dog",@"mouse"];

// 使用方法和字面量語法獲取下標對應的對象
NSString *dog = [animals objectAtIndex:1];
NSString *dog = animals[1];

使用字面量的好處:假如創建一個含有3個對象的數組,第二個對象為nil,其他兩個對象都有效,如果用字面量語法創建,則在運行時會拋出異常,可以更快找到錯誤。而如果用arrayWithObjects:方法,則不會拋出異常,但創建的數組會只包含第一個對象,因為該方法會依次處理各個參數,直到發現nil為止。這樣會導致錯誤不容易被發現。

// 使用方法和字面量語法創建字典
NSDictionary *personData = [NSDictionary dictionaryWithObjectAndKeys:@"Matt",@"firstName",@"Galloway",@"lastName",[NSNumber numberWithInt:28],@"age",nil];
NSDictionary *personData = @{@"firstName":@"Matt",@"lastName":@"Galloway",@"age":@28};

// 使用方法和字面量語法獲取下標對應的對象
NSString *lastName = [personData objectForKey:@"lastName"];
NSString *lastName = personData[@"lastName"];

用字面量語法創建字典也有類似優點,有助於查錯。

使用字面量語法創建出來的字符串、數組、字典對象都是不可變的。若想要可變版本的對象,需要復制一份。

NSMutableArray *mutable = [@[@1,@2,@3] mutableCopy];

這麼做會多調用一個方法,而且還需要再創建一個對象,但使用字面量語法利大於弊。

第4條:多用類型常量,少用#define預處理器指令

編寫代碼時經常要定義常量,例如,要寫一個UI視圖類,此視圖顯示出來之後就播放動畫,然後消失。如果想將播放動畫的時間提取為常量,通常會這麼寫:

#define ANIMATION_DURATION 0.3

但是這樣定義出來的常量沒有類型信息,且預處理過程會把碰到的ANIMATION_DURATION一律替換成0.3,假設此指令聲明在某個頭文件中,那麼所有引入這個頭文件的代碼都會進行替換。更好的方式是定義一個類型為NSTimeInterval的常量

// EOCAnimatedView.h
#import 

@interface EOCAnimatedView : UIView
@end

// EOCAnimatedView.m
#import "EOCAnimatedView.h"

// 聲明定義常量
static const NSTimeInterval EOCAnimationDuration = 0.3;

@implementation EOCAnimatedView
@end

變量一定要同時用static於const來聲明。const修飾符可以保護變量不被修改。static修飾符則意味著僅在定義此變量的編譯單元中可見。如果不加static,在另一個編譯單元也聲明了同名變量就會報錯。這樣創建的常量是不公開的。

如果需要對外公開某個常量,就需要常量放在全局符號表中,以便可以在定義該常量的編譯單元之外使用:

// EOCAnimatedView.h
#import 

// 聲明常量
extern const NSTimeInterval EOCAnimationDuration;

@interface EOCAnimatedView : UIView
@end

// EOCAnimatedView.m
#import "EOCAnimatedView.h"

// 定義常量
const NSTimeInterval EOCAnimationDuration = 0.3;

@implementation EOCAnimatedView
@end

此類常量必須要定義,而且只能定義一次。因為要放到全局符號表裡,所以命名常量時需謹慎,避免名稱沖突。

第5條:用枚舉表示狀態、選項、狀態碼

枚舉是一種常量命名方式。某個對象所經歷的各種狀態就可以定義為一個簡單的枚舉集:

// 套接字連接狀態
enum EOCConnectionState{
    EOCConnectionStateDisconnected,
    EOCConnectionStateConnecting,
    EOCConnectionStateConnected,
};

編譯器會為每個枚舉值分配一個獨有的編號,從0開始,每個枚舉遞增1。實現枚舉所用的數據類型取決於編譯器,不過其二進制位(bit)的個數必須能完全表示下枚舉編號才行。

C++11標准擴充了枚舉的特性,Objective-C也能得益於C++11標准。其中一項改動是:可以指明用何種底層數據類型來保存枚舉類型的變量。這樣做的好處是,可以向前聲明枚舉變量了。

// 指定底層數據類型
enum EOCConnectionState : NSInteger {/* . . . */};

// 向前聲明枚舉變量
enum EOCConnectionState : NSInteger;

// 手動指定枚舉成員的值,接下來的枚舉值都會在上一個的基礎上自動遞增1
enum EOCConnectionState{
    EOCConnectionStateDisconnected = 1,
    EOCConnectionStateConnecting,
    EOCConnectionStateConnected,
};

還有一種情況應該使用枚舉類型,那就是定義選項的時候。若這些選項可以彼此組合,則更應如此:

// 設備支持方向
enum EOCPermittedDirection{
    EOCPermittedDirectionUp = 1 << 0,    // 0001 上
    EOCPermittedDirectionDown = 1 << 1,  // 0010 下
    EOCPermittedDirectionLeft = 1 << 2,  // 0100 左
    EOCPermittedDirectionRight = 1 << 3, // 1000 右
};

// direction枚舉值為0101,表示支持上和左兩個方向
enum EOCPermittedDirection direction = EOCPermittedDirectionUp|EOCPermittedDirectionLeft;

使用宏創建枚舉類型(NS_ENUM與NS_OPTIONS都是Foundation框架中定義的輔助宏)

// 普通枚舉類型
typedef NS_ENUM(NSUInteger, EOCConnectionState){
    EOCConnectionStateDisconnected = 1,
    EOCConnectionStateConnecting,
    EOCConnectionStateConnected,
};

// 選項枚舉類型
typedef NS_OPTIONS(NSUInteger, EOCPermittedDirection){
    EOCPermittedDirectionUp = 1 << 0,
    EOCPermittedDirectionDown = 1 << 1,
    EOCPermittedDirectionLeft = 1 << 2,
    EOCPermittedDirectionRight = 1 << 3,
};

在switch語句中使用枚舉:

typedef NS_ENUM(NSUInteger, EOCConnectionState){
    EOCConnectionStateDisconnected,
    EOCConnectionStateConnecting,
    EOCConnectionStateConnected,
};

EOCConnectionState state = EOCConnectionStateConnected;
switch (state) {
    case EOCConnectionStateDisconnected:
        // Handle disconnected state
        break;
    case EOCConnectionStateConnecting:
        // Handle connecting state
        break;
    case EOCConnectionStateConnected:
        // Handle connected state
        break;
}

在switch語句中使用枚舉時,最好不要有default分支,如果枚舉中加入了一個新狀態,編譯器會發出警告信息提示有狀態未在switch語句中處理,假如寫上了default分支,那麼就會導致編譯器不會發出警告信息。通常要確保switch語句能正確處理所有枚舉值。

  1. 上一頁:
  2. 下一頁:
蘋果刷機越獄教程| IOS教程問題解答| IOS技巧綜合| IOS7技巧| IOS8教程
Copyright © Ios教程網 All Rights Reserved