參考:C Storage Classes
iOS定義靜態變量、靜態常量、全局變量
iOS開發——OC篇&常用關鍵字的使用與區別
iOS 宏(define)與常量(const)的正確使用
一、C語言的存儲類型
在C語言中,程序內變量或函數的作用域和壽命是由其存儲類確定的。每個變量都具有生存周期,或存儲其值得上下文。方法,同變量一樣,也存在,或可見于,一個特殊的范圍里,這就決定了哪一部分能夠知道且能夠訪問它們。
C里有四種存儲類:
- auto
- register
- static
- extern
1、auto
很有可能你從來沒見過這個關鍵字。這是因為auto是默認存儲類,因此通常并不需要顯式的使用。
當運行到程序塊的時候,auto類型的變量能自動分配內存,并且在該程序塊運行完成時釋放。 訪問auto變量僅限于聲明它的block,以及任何嵌套block內。
2、register
大多數OC程序員可能也不熟悉register,因為它沒有被廣泛的使用在NS世界里。
register行為就像auto,但不同的是它們不是被分配到堆棧中,它們被存儲在一個寄存器里。
寄存器能比內存提供更快的訪問速度,但由于內存管理的復雜性,把變量放在寄存器中并不能保證程序變得更快。事實上,很可能由于在寄存器上占用了不必要的空間而最終被放緩執行。使用寄存器實際上只是一個給編譯器存儲變量的建議,實現時可以選擇是否遵從這一點。
寄存器在OC不夠普及其實挺好的:最好還是不要使用它,因為比起其他任何明顯的方式加快應用程序,它更容易引起讓人更加頭疼的結果。
3、static
作為關鍵字,static被以很多不同的,不兼容的方式使用,因此要弄清楚每一個實例到底是什么意思可能會造成混淆。
方法或函數內部的一個static變量保留其調用之前的值。
-
全局聲明的一個static變量可以被任何函數或方法調用,只要這些方法出現在跟static變量同一個文件中。這同樣適用于static方法。
靜態單例
OC中一個常見的模式是靜態單例,在這個case里,一個靜態聲明的變量被初始化,并在任何一個函數或者類方法中被返回。 dispatch once 用于保證變量初始化在一個線程安全的方式下 只 發生一次:
+ (instancetype)sharedInstance {
static id _sharedInstance = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
_sharedInstance = [[self alloc] init];
});
return _sharedInstance;
}
單例模式對于創建整個應用程序共享的對象是很有用的,諸如HTTP客戶端或一個通知管理,或創建過程很昂貴的對象,諸如格式化。
4、extern
當static使得一個特定的文件中的函數和變量全局可見,extern則使它們對所有文件可見。
一般來說,全局變量并不是一個好主意。由于沒有如何及何時改變只的任何對extern有兩個常見和實際的用途。
全局字符串常量
任何時候,如果你的應用程序要在一個公共文件頭部申明一個非自然語言的字符串常量,都應該將其聲明為外部字符串常量。尤其是在聲明諸如userInfo字典,NSNotification 名稱和 NSError 域的時候。
該模式是在公共頭文件里申明一個extern的NSSting*const,并在實現文件里定義該NSString * const:
//AppDelegate.h
extern NSString * const kAppErrorDomain;
//AppDelegate.m
NSString * const kAppErrorDomain = @"com.example.yourapp.error";
字符串的值并沒有特別需要注意的事情,只要它是唯一的。使用字符串常量建立了嚴格的約束,用該常數變量來代替字符串文本值本身。
公共方法
一些API可能會想要公開曝光一些輔助方法。由于僅提供輔助而與具體狀態無關的考慮,用方法來封裝這些行為是一個很好的方式,而且如果特別有用,還可能值得使其全局可用。
//TransactionStateMachine.h
typedef NS_ENUM(NSUInteger, TransactionState) { TransactionOpened, TransactionPending, TransactionClosed,};extern NSString * NSStringFromTransactionState(TransactionState state);
//TransactionStateMachine.m
NSString * NSStringFromTransactionState(TransactionState state) { switch (state) { case TransactionOpened: return @"Opened"; case TransactionPending: return @"Pending"; case TransactionClosed: return @"Closed"; default: return nil; }}
理解任何事情其實都是去了解上下文??赡苣切┪覀兛吹降暮苊黠@且不證自明的東西,對所有那些沒有我們的參照系的人來說是未知的。我們無法真正了解和欣賞自己和他人的觀點及信息的差異或許使我們最根本的缺點。
這就是為什么,在我們構建的邏輯0和1的宇宙中,我們如此謹慎的區分上下文,并基于這些明確的規則上構建我們的假設。C存儲類對于理解程序是如何運行時必不可少的。如果沒有他們,我們的開發將如履薄冰。因此,需要謹慎對待這些簡單的規則,才能包含新新的編寫代碼。
二、iOS的內存存儲區的劃分
- 1、棧區:棧區主要存放函數內部定義的變量、數組。函數調用時,開辟空間,函數執行完畢,回收空間,空間的開辟與回收有系統管理。
- 2、堆區:堆區最大的特點:空間的開辟與釋放由開發人員手動管理。
- 3、全局靜態區:主要存放函數外部定義的全局變量以及靜態變量,空間一旦開辟,就不會回收,直到應用程序執行結束。
- 4、常量區:存儲常量:整形常量、浮點型常量、字符串常量
- 5、代碼區:存放程序編譯之后生成的cpu指令。
三、OC中的一些關鍵字
1、static
1.1 static變量只是在編譯時候進行初始化,對于static變量,無論是定義在方法體里面還是在方法體外面其作用域都一樣。
我們經常使用的UItableViewController里面,在定義UItableView的時候,模板會經常使用以下代碼
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = @"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];
return cell;
}
在上面定義了static變量,在編譯期間會對這個變量進行初始化賦值,也就是說這個變量值要么為nil,要么在編譯期就可以確定其值,一般情況下,只能用NSSrting或者基本類型,并且這個變量只能在cellForRowAtIndexPath訪問。這個和C語言里面的static的變量屬性一樣。
1.2 static變量也可以定義在.m的方法體外。這樣所有的方法內部都可以訪問這個變量。但是在類之外是沒有辦法訪問的,也就是不能用 XXXClass.staticVar 的方式來訪問 staticVar變量。相當于static變量都是私有的。
如果.m文件和方法體里面定義了同名的static變量,那么方法體里面的實例變量和全局的static變量不會沖突,在方法體內部訪問的static變量和全局的static變量是不同的。
@implementation IFoundAppDelegate
static NSString * staticStr = @"test";
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
static NSString * staticStr = @"test2";
NSLog(@"the staticStr is %@ -- %d",staticStr,[staticStr hash]);
}
- (void)applicationWillResignActive:(UIApplication *)application
{
NSLog(@"the staticStr is %@ -- %d",staticStr,[staticStr hash]);
}
以上兩個static變量是兩個不同的變量,在didFinishLaunchingWithOptions方法內部,訪問的是方法體內部定義的staticStr變量,在applicationWillResignActive方法體里面,訪問的是全局定義的staticStr變量。可以通過日志打印其hash來進行確認兩個變量是否一樣。
2、const和define
2.1、當我們想全局共用一些數據時,可以用宏、變量、常量
宏:
#define HSCoder @"漢斯哈哈哈"
變量:
NSString *HSCoder = @"漢斯哈哈哈";
常量:
//四種寫法:
static const NSString *HSCoder = @"漢斯哈哈哈";
const NSString *HSCoder = @"漢斯哈哈哈";
NSString const *HSCoder = @"漢斯哈哈哈";
NSString * const HSCoder = @"漢斯哈哈哈";
思考:宏與常/變量的選擇?
- 宏:只是預處理器里進行文本替換,沒有類型,不做任何類型檢查,編譯器可以對相同的字符串進行優化。只保存一份到 .rodata 段。甚至有相同后綴的字符串也可以優化,你可以用GCC 編譯測試,"Hello world" 與 "world" 兩個字符串,只存儲前面一個。取的時候只需要給前面和中間的地址,如果是整形、浮點型會有多份拷貝,但這些數寫在指令中。占的只是代碼段而已,大量用宏會導致二進制文件變大。
- 變量:共享一塊內存空間,就算項目中N處用到,也不會分配N塊內存空間,可以被修改,在編譯階段會執行類型檢查。
- 常量:共享一塊內存空間,就算項目中N處用到,也不會分配N塊內存空間,可以根據const修飾的位置設定能否修改,在編譯階段會執行類型檢查。
2.2 常量區分
全局常量: 不管你定義在任何文件夾,外部都能訪問。
const NSString *HSCoder = @"漢斯哈哈哈";
局部常量:用static修飾后,不能提供外界訪問
static const NSString *HSCoder = @"漢斯哈哈哈";
ps :static經常用到.m文件中,如果是.h文件定義的全局變量,在別的文件中,引入這個頭文件,也是可以訪問這個變量的。
2.3const修飾位置不同,代表什么?
1.const NSString *HSCoder = @"漢斯哈哈哈";
"*HSCoder"不能被修改, "HSCoder"能被修改
2.NSString const *HSCoder = @"漢斯哈哈哈";
"*HSCoder"不能被修改, "HSCoder"能被修改
3.NSString * const HSCoder = @"漢斯哈哈哈";
"HSCoder"不能被修改,"*HSCoder"能被修改
結論:const右邊的總不能被修改
驗證:
所以一般我們定義一個常量又不想被修改應該這樣:
NSString * const HSCoder = @"漢斯哈哈哈";
一般項目中,定義全局常量,會寫在獨立文件中
訪問:
導入頭文件
訪問常量