我只是一個搬運工,僅僅爲了加深記憶,感謝做者分享,文章大部分來源:進擊的蝸牛君html
alloc負責爲對象的全部成員變量分配內存空間,而且爲各個成員變量重置爲默認值,如int型爲0,BOOL型爲NO,指針型變量爲nil。僅僅分配空間還不夠,還須要init來對對象執行初始化操做,纔可使用它。若是隻調用alloc不調用init,也能運行,但可能會出現未知結果。數組
所作的事情和alloc差很少,也是分配內存和初始化。安全
new只能使用默認的init初始化,而alloc可使用其餘初始化方法,由於顯示調用總比隱式調用好,因此每每使用alloc/init來初始化。bash
NSString *A = @"hello";
NSString *B = @"hello";
NSString *C = [NSString stringWithFormat:@"hello"];
NSString *D = [NSString stringWithFormat:@"hello"];
複製代碼
@「hello」
位於常量池,可重複使用,其中A和B指向的都是同一分內存地址。而[NSString stringWithFormat:@"hello"]
是運行時建立出來的,保存在運行時內存(即堆內存),其中C和D指向的內存地址不一樣,與A、B也不相同。數據結構
具體能夠分爲四類:線程安全、讀寫權限、內存管理和指定讀寫方法。app
若是不寫該類修飾符,默認就是atomic。二者的最大區別決定編譯器生成的getter/setter方法是否屬於原子操做,若是本身寫了getter/setter方法,此時用什麼都同樣。對於atomic來講,getter/setter增長了鎖來確保數據操做的完整性,不受其餘線程影響。例如線程A的getter方法運行到一半,線程B調用setter方法,name線程A仍是能獲得一個完整的Value。而對nonatomic來講,多個線程能同時訪問操做,就沒法保證是不是完成的Value,還會發生髒數據。可是nonatomic更快,開發中每每在可控狀況下安全換效率。ide
注意:atomic並不能徹底保證線程安全,只能保證數據操做的線程安全,例如線程A使用getter方法,同時線程B、C使用setter方法,那最後線程A取到的值有3中可能:原始值、B set的值或C set的值;又例如線程A使用getter方法,線程B同時調用release方法,因爲release方法並無加鎖,全部有可能致使cash。模塊化
readonly只讀屬性,只會生成getter方法,不會生成setter方法。readwrite讀寫屬性,會生成getter/setter方法,默認是該修飾符。函數
strong強引用,適用於對象,引用計數+1, 對象默認是該修飾符。weak弱引用,爲這種屬性設置新值時,設置方法既不釋放舊值,也不保留新值,不會使引用計數加1。當所指對象被銷燬時,指針會自動被置爲nil,防止野指針。佈局
assgin適用於基礎數據類型,如NSIntger,CGFloat,int等,只進行簡單賦值,基礎數據類型默認是該修飾符。若是用此修飾符修飾對象,對象被銷燬時,並不會置空,會形成野指針。copy是爲了解決上下文的異常依賴,實際賦值類型不可變對象時,淺拷貝;可變對象時,深拷貝。淺拷貝:指針拷貝。深拷貝:指針與指向內存地址的值拷貝。
給getter/setter方法期別名,能夠不一致,而且能夠與其餘屬性的getter/setter重名。例如Person類中定義以下:
@property (nonatomic, copy, setter=setNewName:, getter=oldName) NSString *name;
@property (nonatomic, copy) NSString *oldName;
複製代碼
那麼此時p1.oldName
始終是_name
的值,而若是聲明的順序交換,此時p1.oldName
就是_oldName
的值了,若是想獲得_name
的值,使用p1.name
便可,可是此時不能使用-setName:
。因此別名都是有意思且不重複的,避免一些想不到問題。
strong是淺拷貝,僅拷貝指針並增長引用計數;而copy在對於實際賦值對象是可變對象時,是深拷貝。不可變對象使用copy修飾,如NSString,NSArray,NSSet等;可變對象使用strong修飾,如NSMutableString、NSMutableArray,NSMutableSet等,這是爲何呢?因爲父類屬性能夠指向子類對象,試想這樣一個例子:
@interface: Person :NSObject
@property (nonatomic, strong) NSString *name;
@end
NSMutableString *mutableName = [NSMutableString stringWithFormat:@"hello"];
p.name = mutableName;
[mutableName appendString:@" world"];
複製代碼
因爲Person.name
使用的strong修飾,它對於賦值對象進行的淺拷貝,那麼Person.name
此時實際指向與mutableName
指向的同一塊內存區,若是mutableName
的內容修改,,此時Person.name
也會修改,這並非咱們所想要的,因此咱們使用copy來修飾,這樣即便賦值對象時一個可變對象,也會在setter方法中copy一份不可變對象再賦值。而對於可變對象的屬性來講,若是使用copy修飾,從上面可知會獲得一個不可變對象再賦值,就會拋出異常,因此咱們使用strong。
assgin用於基礎類型,能夠修飾對象,但這個對象再銷燬後,這個指針並不會置空,會形成野指針錯誤。weak用於對象,沒法修飾基礎類型,而且在對象銷燬後,指針會自動置爲nil,不會引發野指針崩潰。
完成屬性定義後,編譯器會自動編寫訪問這些屬性所需的方法,此過程叫作「自動合成」(autosynthesis)。須要強調的是,這個過程由編譯時期在編譯期執行,因此編譯器裏看不到這些合成方法(synthesized method)的源代碼。除了生成方法代碼getter、setter以外,編譯器還要自動向類中添加適當類型的實例變量,而且在屬性名前面加下劃線,以此做爲實例變量的名字。也能夠在類的實現代碼裏經過@synthesize語法來指定實例變量的名字。
在protocol中使用property只會生成setter和getter方法聲明,咱們使用屬性的目的,是但願遵照協議的對象能實現該屬性。category使用@propety也是隻會生成setter和getter方法的聲明,若是咱們真的須要給category增長屬性,須要藉助於Runtime的關聯對象。
深拷貝是對內容拷貝,即複製一份原來的內容放在其餘內存下,新對象指針指向該內存區域,與原來的對象沒有關係。
淺拷貝是指對指針的拷貝,即建立一個新指針也指向原對象的內存空間,至關於給原來的對象索引數+1。
對於可變對象來講,Copy與MutableCopy都是深拷貝;對於不可變對象來講,Copy是淺拷貝,MutableCopy是深拷貝:Copy返回的都是不可變對象,MutableCopy返回的都是可變對象。
NSArray *arr1 = @[];
NSArray *arr2 = arr1;
NSArray *arr3 = [arr1 copy];
NSArray *arr4 = [arr1 mutableCopy];
NSMutableArray *arr5 = [NSMutableArray array];
NSMutableArray *arr6 = arr5;
NSMutableArray *arr7 = [arr5 copy];
NSMutableArray *arr8 = [arr5 mutableCopy];
NSLog(@"%p %x", arr1, &arr1);
NSLog(@"%p %x", arr2, &arr2);
NSLog(@"%p %x", arr3, &arr3);
NSLog(@"%p %x", arr4, &arr4);
NSLog(@"%p %x", arr5, &arr5);
NSLog(@"%p %x", arr6, &arr6);
NSLog(@"%p %x", arr7, &arr7);
NSLog(@"%p %x", arr8, &arr8);
複製代碼
打印結果
0x604000001970 e7ba22d8
0x604000001970 e7ba22c8
0x604000001970 e7ba22c0
0x604000458360 e7ba22b8
0x6040004581e0 e7ba22b0
0x6040004581e0 e7ba22a8
0x604000001970 e7ba22a0
0x604000458270 e7ba2298
複製代碼
在Runtime中,爲了管理全部對象的引用計數和weak指針,建立了一個全局的SideTables,實際是一個hash表,裏面都是SideTable結構體,而且對象的內存地址做爲Key,SideTable部分定義以下
struct SideTable {
//保證原子操做的自旋鎖
spinlock_t slock;
//保存引用計數的hash表
RefcountMap refcnts;
//用於維護weak指針的結構體
weak_table_t weak_table;
....
};
複製代碼
其中用於維護weak指針的結構體weak_table_t
是一個全局表,其部分定義以下
struct weak_table_t {
//保存全部弱引用表的入口,包含全部對象的弱引用表
weak_entry_t *weak_entries;
//存儲空間
size_t num_entries;
//參與判斷引用計數輔助量
uintptr_t mask;
//hash key 最大偏移值
uintptr_t max_hash_displacement;
};
複製代碼
其中全部的weak指針正是在weak_entry_t
中,其部分定義以下
struct weak_entry_t {
//被指對象的地址。前面循環遍歷查找的時候就是判斷目標地址是否和他相等。
DisguisedPtr<objc_object> referent;
union {
struct {
//可變數組,裏面保存着全部指向這個對象的弱引用的地址。當這個對象被釋放的時候,referrers裏的全部指針都會被設置成nil。
weak_referrer_t *referrers;
uintptr_t out_of_line_ness : 2;
uintptr_t num_refs : PTR_MINUS_2;
uintptr_t mask;
uintptr_t max_hash_displacement;
};
struct {
// out_of_line_ness field is low bits of inline_referrers[1]
weak_referrer_t inline_referrers[WEAK_INLINE_COUNT];
};
};
...
};
複製代碼
weak因爲不增長引用計數,因此不能在SideTable中與引用計數表放在一塊兒,Runtime單獨使用了一個hash表weak_table_t
來管理weak,其中底層結構體weak_entry_t
以weak指向的對象內存地址爲key,value是一個存儲該對象全部weak指針的數組。當這個對象dealloc時,查出對應的SideTable,搜索key對應的指針數組,而且遍歷數組將全部weak對象置爲nil,並清除記錄。
//建立weak對象
id __weak obj1 = obj;
//Runtime會調用以下方法初始化
id objc_initWeak(id *location, id newObj)
{
//若是對象實例爲nil,當前weak對象直接置空
if (!newObj) {
*location = nil;
return nil;
}
return storeWeak<DontHaveOld, DoHaveNew, DoCrashIfDeallocating>
(location, (objc_object*)newObj);
}
//更新指針指向
static id storeWeak(id *location, objc_object *newObj)
{
assert(haveOld || haveNew);
if (!haveNew) assert(newObj == nil);
Class previouslyInitializedClass = nil;
id oldObj;
SideTable *oldTable;
SideTable *newTable;
//查詢當前weak指針原指向的oldSideTable與當前newObj的newSideTable
retry:
if (haveOld) {
oldObj = *location;
oldTable = &SideTables()[oldObj];
} else {
oldTable = nil;
}
if (haveNew) {
newTable = &SideTables()[newObj];
} else {
newTable = nil;
}
.....
//解除weak指針在舊對象中註冊
if (haveOld) {
weak_unregister_no_lock(&oldTable->weak_table, oldObj, location);
}
//添加weak到新對象的註冊
if (haveNew) {
newObj = (objc_object *)
//這個地方仍然須要newObj來覈對內存地址來找到weak_entry_t,從而刪除
weak_register_no_lock(&newTable->weak_table, (id)newObj, location,
crashIfDeallocating);
if (newObj && !newObj->isTaggedPointer()) {
newObj->setWeaklyReferenced_nolock();
}
*location = (id)newObj;
} else {}
SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable);
return (id)newObj;
}
複製代碼
KVC容許以字符串形式簡介操做對象的屬性,全稱爲Key Value Coding, 健值編碼。
-(void) setValue:(nullable id)value forKey:(NSString *)key;
複製代碼
-set<Key>:
代碼經過setter方法賦值,若是-set<Key>:
沒有找到,還會去查找_set<Key>:
方法。+(BOOL)accessInstanceVariableDirectly
方法,若是你重寫了該方法並使其返回NO,則KVC下一步會執行setValue:forUndefineKey:
,默認拋出異常。_<key>
、 _<isKey>
、 <key>
、<isKey>
的成員變量。setValue:forUnderfinekKey
方法,默認拋出異常。- (nullable id)valueForKey:(NSString *)key;
複製代碼
-get<key>
, -<key>
,is<key>
代碼經過getter方法獲取值。countOf<key>
,objectIn<Key>AtIndex:
和-<key>AtIndexes
方法和另外兩個中的一個被找到,返回一個響應全部NSArray方法的代理集合,簡單來講就是能夠當NSArray用。-countOf<key>
, -enumeratorOf<key>
和-memberOf<key>:
方法,若是三個都能找到,返回一個全部NSSet方法的代理集合,簡單來講就是能夠當NSSet使用。_<key>
, _<isKey>
, <key>
, <isKey>
的成員變量,返回該成員變量的值。valueForUndefineKey:
方法,默認拋出異常。- (nullable id)valueForKeyPath:(NSString *)keyPath;
- (void)setValue:(nullable id)value forKeyPath:(NSString *)keyPath;
複製代碼
KVC不只能夠操做對象屬性,還能夠操做對象的」屬性鏈「。如Person中有一個類型爲Date的birthday屬性,而Date中又有year,month,day等屬性,那麼Person能夠直接經過birthday.year這種 Key Path來操做birthday的year屬性。
從上述機制能夠看出,在沒有setter方法時,會檢查(BOOL)accessInstanceVariableDirectly
來決定是否搜索類似成員變量,所以只須要重寫該方法並返回NO便可。
開發中可能有些須要設定對象屬性不能夠設定某些值,此時就須要檢驗Value的可用性,經過以下方法
- (BOOL)validateValue:(inout id __nullable * __nonnull)ioValue forKey:(NSString *)inKey error:(out NSError **)outError;
複製代碼
這個方法的默認實現是去探索裏面是否有這樣的方法-(BOOL)validate<key>:error:
,若是有這個方法,就調用這個方法來返回,沒有的話就直接返回YES。注意:在KVC設值的時候,並不會主調用該方法去校驗,須要開發者手動調用校驗,意味着即便實現此方法,也能夠賦值成功
- (nullable id)valueForUndefinedKey:(NSString *)key;
- (void)setValue:(nullable id)value forUndefinedKey:(NSString *)key;
複製代碼
沒有找到相關的Key,就會拋出NSUndefinedKeyException
異常,使用KVC時通常須要重寫這兩個方法。
- (void)setNilValueForKey:(NSString *)key;
複製代碼
當給基礎類型的對象屬性設置爲nil時,會拋出NSValidArgumentException
異常,通常也須要重寫。
-(void)setValuesForKeysWithDictionary:
字典轉model。valueForKey:
將會被傳遞給容器中的每個對象,而不是容器自己進行操做,由此咱們能夠有效的提取容器中每一個對象的指定屬性值集合。@avg
,@count
,@max
,@min
,@sum
。KVO提供了一種機制,(基於NSKeyValueObservin協議,全部的Object都是實現了此協議)能夠供觀察者監聽對象屬性的變化並接受通知,全稱爲Key Value Observing,即健值監聽。
經常使用API
- (void)addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(nullable void *)context;
- (void)removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath context:(nullable void *)context;
- (void)removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath;
複製代碼
觀察者重寫
- (void)observeValueForKeyPath:(nullable NSString *)keyPath ofObject:(nullable id)object change:(nullable NSDictionary<NSKeyValueChangeKey, id> *)change context:(nullable void *)context;
複製代碼
當觀察者對對象A註冊一個監聽時,系統此時會動態建立一個名爲NSKVONotifying_A的新類,該類繼承自對象A本來的類,並重寫被觀察者的setter方法,在原方法的調用先後通知觀察者值的改變。而後將對象A的isa指針(isa指針告訴Runtime這個對象的類是什麼)指向NSKVONotifying_A這個新類,那麼對象A就變成了新建立類的實例,不只如此,Apple還重寫了-class
方法來隱藏該新類,讓人們覺得註冊先後對象A的類並無改變,但實際上若是手動新建一個NSKVONotifying_A類,在觀察者運行到註冊時,便會引發重複類崩潰。
從上述實現原理來看,很明顯能夠知道,若是沒有經過setter賦值,直接賦值該成員變量是不會觸發KVO機制的,可是KVC除外,這也側面證實了KVC和KVO是有內在聯繫的。
分類用於對已有的類添加新方法,並不須要建立新的子類,不須要訪問原有類的源代碼,能夠將類定義模塊化地分佈到多個分類中。
分類的實現是基於Runtime動態的將分類中方法添加到類中,Runtime中經過class_addIvar()
方法添加成員變量,但蘋果對該方法只能在構造一個類的過程當中用,不容許對一個已有的類動態的添加成員變量。爲何蘋果不容許?這是由於對象在運行期已經給成員變量都分配了內存,若是動態添加屬性,不只須要破壞內部佈局,並且已經建立的類的實例也符合當前類的定義,這簡直是災難性的。可是方法保存在類的可變區域中,修改是不會影響類的內存佈局的因此沒問題。
在分類中聲明屬性能夠編譯經過,可是使用該屬性,會報找不到getter/setter方法,這是因爲即便聲明屬性,也不會生成成員變量
,天然也沒有必要實現getter/setter方法,那麼咱們就須要經過Runtime的關聯對象來爲屬性實現getter/setter方法。例如對Person的一個分類增長SPeciaName
屬性,實現代碼以下
#import "Person+Test.h"
#import <objc/runtime.h>
// 定義關聯的key
static const char* specialNameKey = "specialName";
@implementation Person (Test)
- (void)setSpecialName:(NSString *)specialName {
// 第一個參數:給哪一個對象添加關聯
// 第二個參數:關聯的key,經過這個key獲取
// 第三個參數:關聯的value
// 第四個參數:關聯的策略
objc_setAssociatedObject(self, specialNameKey, specialName, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
- (NSString *)specialName {
// 根據關聯的key,獲取關聯的值。
return objc_getAssociatedObject(self, specialNameKey);
}
@end
複製代碼
其中關聯對象也是存在一個hash表中,經過內次尋址。當對象銷燬時,會找到對應存儲的關聯對象作清理工做。
擴展與分類類似,至關於匿名分類,但分類一般有.h和.m文件,而擴展經常使用於臨時對某個類的接口進行擴展,通常聲明私有屬性,私有方法,私有成員變量。
Block是對C語言的擴展,用來實現匿名函數的特性,Block本質也是OC對象,它內部也有isa指針,是封裝了函數調用以及函數調用環境的OC對象。
block對應的結構體以下
struct Block_descriptor {
unsigned long int reserved;
unsigned long int size;
void (*copy)(void *dst, void *src);
void (*dispose)(void *);
};
struct Block_layout {
//全部對象都有該指針,用於實現對象相關的功能
void *isa;
//用於按 bit 位表示一些 block 的附加信息
int flags;
//保留變量
int reserved;
//函數指針,指向具體的 block 實現的函數調用地址
void (*invoke)(void *, ...);
//表示該 block 的附加描述信息,主要是 size 大小,以及 copy 和 dispose 函數的指針
struct Block_descriptor *descriptor;
/* Imported variables. */
//捕獲的變量,block 可以訪問它外部的局部變量,就是由於將這些變量(或變量的地址)複製到告終構體中
};
複製代碼
在Objective-C語言中,一共有3種類型的block:_NSConcreteGlobalBlock
全局的靜態block,不會訪問任何外部變量。_NSConcreteStackBlock
保存在棧中的block,當函數返回時會被銷燬。_NSConcreteMallocBlock
保存在堆中的block,當引用計數爲0時會被銷燬。
事件響應,數據傳遞,鏈式語法。
1. 爲何不能直接修改局部變量?
這是由於block會從新生成一份變量,因此局部變量修改不會影響block的變量,並且編譯器加了限制,block中的變量也不容許修改。
2. 爲何能修改全局變量和靜態變量
全局變量所佔用的內存只有一份,供全部函數共同調用,block能夠直接使用,而不須要深拷貝或者使用變量指針。靜態變量實際與_block修飾相似,block是直接使用的指向靜態變量的指針,並未從新深考貝。
3. 如何修改局部變量
將局部變量使用_block修飾,告訴編譯器這個局部變量是能夠修改的,那麼block不會再生成一份,而是複製使用該局部變量的指針。
4. 爲何要在block中使用strongWeak
咱們爲了防止循環引用使用了weakSelf
, 可是某些狀況在block執行過程當中,會出現self忽然釋放的狀況,致使運行不正確,因此咱們使用strongSelf
來增長強引用,保證後續代碼能夠正常運行。那麼豈不是會致使循環引用? 確實會,可是隻是在block代碼塊的做用域裏,一旦執行結束,strongSelf就會釋放,這個臨時的循環引用就會自動打破。
5. block用copy仍是strong修飾
MRC下使用copy,ARC下均可以。MRC下block建立時,若是block中使用了成員變量,齊類型是_NSConcreteStackBlock
,它的內存是放在棧區,做用域僅僅是在初始化的區域內,一旦外部使用,就可能形成崩潰,因此通常使用copy
來將blcok拷貝到堆內存,此時類型爲_NSConcreteMallocBlock
,使得block能夠在聲明域外使用。ARC下只有_NSConcreteGlobleBlock
和_NSConcreteMallocBlock
類型,若是block中使用了成員變量,其類型是_NSConcreteMallocBlock
,因此不管是strong
仍是copy
均可以。
6. 如何不使用_block修改局部變量?
雖然編譯器作了限制,可是咱們仍然能夠在block中經過指針修改,如
int a = 1;
void (^test)() = ^ {
//經過使用指針繞過了編譯器限制,可是因爲block中是外面局部變量的拷貝,因此即便修改了,外面局部變量也不會變,實際做用不大。
int *p = &a;
*p = 2;
NSLog(@"%d", a);
};
test();
NSLog(@"%d", a);
複製代碼
全部對象在runtime層都是以struct展現的,NSObject就是一個包含了isa指針的結構體,以下
/// An opaque type that represents an Objective-C class.
typedef struct objc_class *Class;
/// Represents an instance of a class.
struct objc_object {
Class _Nonnull isa OBJC_ISA_AVAILABILITY;
};
複製代碼
而Class也是個包含了isa的結構體,以下
struct objc_class {
Class _Nonnull isa OBJC_ISA_AVAILABILITY;
#if !__OBJC2__
Class _Nullable super_class OBJC2_UNAVAILABLE;
const char * _Nonnull name OBJC2_UNAVAILABLE;
long version OBJC2_UNAVAILABLE;
long info OBJC2_UNAVAILABLE;
long instance_size OBJC2_UNAVAILABLE;
struct objc_ivar_list * _Nullable ivars OBJC2_UNAVAILABLE;
struct objc_method_list * _Nullable * _Nullable methodLists OBJC2_UNAVAILABLE;
struct objc_cache * _Nonnull cache OBJC2_UNAVAILABLE;
struct objc_protocol_list * _Nullable protocols OBJC2_UNAVAILABLE;
#endif
} OBJC2_UNAVAILABLE;
複製代碼
objc_object
中的isa指針告訴了runtime本身指向了什麼類,objc_class
中的isa指向父類,最終根元類的isa會指向本身,造成閉環。Objective-C 2.0中並未具體暴露實現,但咱們能夠看到Objective—C 2.0中的大概實現,包含了父類,成員變量,方法表等。
ARC(Automatic Reference Counting)自動引用計數,是蘋果在WWDC 2011大會上提出的內存管理計數,常應用於iOS和MacOS。GC(Garbage Collection)垃圾回收機制,因爲Java的流行而廣爲人知,簡單說就是系統按期查找不用的對象,並釋放器內存。
不是的,雖然大部分使用ARC的內存管理都作的很好,可是若是使用不當,仍然會形成內存泄漏,例如循環引用:OC與Core Foundation類進行橋接的時候,管理不當也會內存泄漏:指針未清空,形成野指針等。
分爲五個區:棧區,堆區,全局區,常量區,代碼區。程序啓動後,全局區,常量區和代碼區是已經固定的,不會改變。
存一些局部變量,函數跳轉地址,現場保護等,該區由系統處理,無需咱們干預。大量的局部變量深遞歸,函數循環均可能耗盡棧內存而形成程序崩潰。
即運行時內存,咱們建立對象就是在這裏,須要開發者來管理。
用於存放全局變量和靜態變量,初始化的放在一塊區域,未初始化的放在相鄰的一塊區域。
存放常量,如字符串常量,const常量。
存放代碼。
static修飾的變量存儲在靜態區,在編譯時就分配好了內存,會一直存在app內存中直到中止運行。該靜態區只會初始化一次,在內存中只有一份,而且限制了它只能在聲明的做用域中使用,例如單利。注:static也能夠在.h文件中聲明,可是因爲頭文件能夠被其餘文件任意引用使用,此時限制做用域沒有任何意義,違背了它的初衷,並且重複聲明也會報錯。
const經常使用於聲明常量,只讀不可寫,該常量存儲在常量區,編譯時就分配了相關內存,也會一直存在app內存直到中止運行,示例代碼以下:
int const *p // *p只讀 ;p變量
int * const p // *p變量 ; p只讀
const int * const p //p和*p都只讀
int const * const p //p和*p都只讀
複製代碼
extern用於聲明外部全局變量/常量,告訴編譯器須要找對應的全局變量,須要在.m中實現,以下寫法是錯誤的
//Person.h
extern NSString *const Test = @"test";
複製代碼
正確的使用方法是
//Person.h
extern NSString *const Test;
//Person.m
NSString *const Test = @"test";
複製代碼
它經常使用於讓當前類可使用其餘類的全局變量/常量,也常常用於統一管理全局變量/常量,更規範整潔,而且在打包時配合const使用,能夠避免其餘人修改。extern能夠在多處聲明,可是實現只能是一份,不然會報重複定義。
預處理是C語言的一部分,在編譯以前,編譯器會對這些預處理進行處理,這些預處理的結果與源程序一塊兒編譯,
@class
僅僅是告訴編譯器有這個類,至於類裏有什麼信息,這裏是不須要知道的,沒法使用該類的實例變量,屬性和方法,其編譯效率較#import
更高,由於#import
須要把引用類的全部文件都走一遍,而@class
不用。#import
還會形成遞歸引用,若是A、B兩類只相互引用,不會報錯,可是若是任意一方聲明瞭對方的實例,就會報錯。
因爲OC中並不能隱藏系統方法,例如咱們在實現單例時,將來避免其餘人對單例類new、allco、copy、以及mutableCopy,保證整個系統中只有一個單例實例,咱們能夠在頭文件中聲明不可用的方法,以下:
//更簡潔
+(instancetype) alloc NS_UNAVAILABLE;
+(instancetype) new NS_UNAVAILABLE;
-(instancetype) copy NS_UNAVAILABLE;
-(instancetype) mutableCopy NS_UNAVAILABLE;
//能自定義提示語
+(instancetype) alloc __attribute__((unavailable("alloc not available, call sharedInstance instead")));
+(instancetype) new __attribute__((unavailable("call sharedInstance instead")));
-(instancetype) copy __attribute__((unavailable("call sharedInstance instead")));
-(instancetype) mutableCopy __attribute__((unavailable("call sharedInstance instead")));
複製代碼
NULL是C語言的用法,此時調用函數或者訪問成員變量,會報錯。能夠用來賦值基本數據類型來表示空。nil和Nil是OC語法,調用函數或者訪問成員變量不會報錯,nil是對object對象置爲空,Nil是對Class類型的指針置空。NSNull是一個類,因爲nil比較特殊,在Array和Dictionary中被用於標記結束,因此不能存放nil,咱們能夠經過NSNull來表示數據爲空。可是向NSNull發送消息會報錯。
NSDictionary(字典)是使用哈希表Hash table(也叫散列表)來實現的。哈希表是根據(key)而直接訪問在內存存儲位置的數據結構,也就是說,它經過計算一個關於鍵(key)值的函數,將所需查詢的數據映射到表中一個位置來訪問記錄,這加快了查找速度。這個映射函數稱作散列函數,存放記錄的數組稱作哈希表。也就是說哈希表的本質是一個數組,數組中每個元素實際上是一個NSDictionary鍵值對。
庫是共享程序代碼的方式,通常分爲靜態庫和動態庫。
靜態庫:連接時完整地拷貝至可執行文件中,被屢次使用就有多份冗餘拷貝。文件後綴通常爲.a, 開發者本身創建的.framework能夠直接使用。詳見《iOS中.a與.framework庫的區別》
@synthesize語義是若是你沒有手動是實現setter/getter方法,那麼編譯器會自動加上這個兩個方法。能夠用來改變實例變量的名稱,如@synthesize firstName = _myFirstName
。
@dynamic是告訴編譯器不須要它自動生成,有用戶本身生成(固然對於readonly的屬性只提供getter便可)。假如一個屬性被聲明爲@dynamic var ,而後你沒有提供@setter方法和@getter方法,編譯的時候沒問題,可是當程序運行到instance.var = someVar, 因爲缺setter方法會致使程序崩潰;或者當運行到sameVar = var時,因爲缺乏getter方法一樣會致使崩潰。編譯時沒問題,運行時才執行相應的方法,這就是所謂的動態綁定。
咱們要搞清楚一個問題,什麼狀況下不會autosynthesis(自行合成)?
訪問了野指針,好比對一個已經釋放的對象執行了release、訪問已經釋放對象的成員變量或者發消息。