面試系列:
-
iOS全解1:基礎/內存管理/Block/GCD(
當前位置
) - iOS全解2:Runloop
- iOS全解3:Runtime
- iOS全解4:KVC/KVO、通知/推送/信號量、Delegate/Protocol、Singleton
- iOS全解5:網絡協議 HTTP、Socket
- iOS全解6:CoreAnimation/Layer
- iOS全解7:音頻/視頻
- iOS全解8:啟動優化、性能優化、App后臺?;?、崩潰檢測
- iOS全解9:編程思想、架構、組件化、RAC
基本概念
一、OC的三大特性為:封裝
、繼承
、多態
1、封裝:
- 變量 的封裝:setter、getter 方法,self調用方便高效
- 方法 的封裝:簡潔、高效,不用關心內部過程
- SDK 的封裝:使用方便、私有隱藏,安全性高(不會泄露數據和代碼)
2、繼承
- 抽取了重復代碼:子類可以擁有父類中的所有成員變量和方法
- 建立了類與類之間的聯系
- 每個類中都有一個superclass指針指向父類
- 所有類的根類都是NSObject,便于isa指針 查詢
- 調用某個對象的方法時,優先去當前類中找,如果找不到,再去父類中找
- 子類重新實現父類的某個方法,會覆蓋父類以前的方法。
繼承的缺點:耦合性太高(類與類之間的關系過于緊密),沒有多繼承,OC里面都是單繼承,多繼承可以用protocol委托代理來模擬實現;可以通過實現多個接口完成OC的多重繼承。(實現多個協議)
3、多態
例如: -(void) animal:(id); id 就是多態,傳入時,才識別 具體類的真實形象。runtime 也是運行時,才識別具體類。
要想使用多態必須使用繼承(繼承是多態的前提)
多態:父類指針指向子類對象 Animal *aa = [Dog new]; 調用方法時會檢測對象的真實形象
好處:如果函數或方法參數中使用的是父類類型,可以傳入父類,子類對象。
局限性:父類類型的變量不能直接調用子類特有的方法,必須強制轉換為子類類型變量后,才能使用。
內存管理RAC:
ARC 相對于 GC 的優點:
ARC 工作在編譯期,在運行時沒有額外開銷。
ARC 的內存回收是平穩進行的,對象不被使用時會立即被回收。而 GC 的內存回收是一陣一陣的,回收時需要暫停程序,會有一定的卡頓。
ARC 相對于 GC 的缺點:
GC 真的是太簡單了,基本上完全不用處理內存管理問題,而 ARC 還是需要處理類似循環引用
這種內存管理問題。
property的關鍵字分三類:
1.原子性(也就線程安全)
有atomic和nonatomic, acomic就是線程安全,但是一般使用nonacomic,因為acomic的線程安全開銷太大,影響性能,即使需要保證線程安全,我們也可以通過自已的代碼控制,而不用acomic。
2.引用計數:
assign:
assign用于非指針變量,基本修飾數據類型,統一由系統的棧區進行內存管理。(int、double、float等)
weak:
對對象的弱引用,不增加引用計數,也不持有對象,當對象消失后指針自動變為nil。
copy:分為深拷貝、淺拷貝,可變復制、不可變復制
淺拷貝
:對內存地址的復制,讓目標對象指針和原對象指向同一片內存空間會增加引用計數
深拷貝
:對對象內容的復制,開辟新的內存空間
單層深復制
,也就是我們經常說的深復制,我這里說的單層深復制是對于集合類所說的(即NSArray、NSDictionary、NSSet),單層深復制指的是只復制了該集合類的最外層,里邊的元素沒有復制,(即這兩個集合類的地址不一樣,但是兩個集合里所存儲的元素的地址是一樣的)
完全復制
,指的是完全復制整個集合類,也就是說兩個集合地址不一樣,里邊所存儲的元素地址也不一樣
引用:深拷貝和淺拷貝,可變復制,不可變復制(這里解釋更詳細)http://www.lxweimin.com/p/e3da07684bf1
- 非集合類(NSString,NSNumber)
[noMutableObject copy] //淺復制 (復制不可變對象)
[noMutableObject mutableCopy] //深復制 (復制不可變對象)
[mutableObject copy] //深復制 (復制可變對象)
[mutableObject mutableCopy] //深復制 (復制可變對象)
- 集合類(NSArray,NSDictionary, NSSet)
[noMutableObject copy] //淺復制 (復制不可變對象)
[noMutableObject mutableCopy] //單層深復制 (復制不可變對象)
[mutableObject copy] //單層深復制 (復制可變對象)
[mutableObject mutableCopy] //單層深復制 (復制可變對象)
這個寫法會出什么問題:@property (nonatomic, copy) NSMutableArray *array;
問題:添加/刪除/修改 數組內的元素的時候,程序會因為找不到對應的方法而崩潰。
原因:是因為 copy 就是復制一個不可變 NSArray 的對象,不能對 NSArray 對象進行添加/修改。
然后在需要修改時,創建一個可變的副本:
NSMutableArray *mutableArray = [self.array mutableCopy];
[mutableArray addObject:newObject];
self.array = [mutableArray copy];
- 那么如何實現多層復制呢?我們以NSArray舉例說明
// 完全復制
NSArray *copyArray = [[NSArray alloc] initWithArray:array copyItems:YES];
需要特別注意的是
以上我們所說的兩種情況默認都實現了NSCopying
和NSMutableCopying
協議。
對于自定義繼承自NSObject的類
copy需要實現NSCopying協議,然后實現以下方法,否則copy會crash
-(id)copyWithZone:(NSZone *)zone {
CopyObject *copy = [[[self class] alloc] init];
copy.name = self.name;
copy.mobile = self.mobile;
copy.company = self.company;
copy.descInfo = self.descInfo;
return copy;
}
mutableCopy時,需要實現NSMutableCopying協議,否則mutableCopy會crash
-(id)mutableCopyWithZone:(NSZone *)zone {
MutableCopyObject *mutableCopy = [[[self class] alloc] init];
mutableCopy.name = self.name;
mutableCopy.mobile = self.mobile;
mutableCopy.company = self.company;
mutableCopy.descInfo = self.descInfo;
return mutableCopy;
}
strong:淺拷貝,也就是指針引用
是每對這個屬性引用一次,retainCount 就會+1,只能修飾 NSObject 對象,不能修飾基本數據類型。是 id 和 對象 的默認修飾符。
unsafe_unretained(assgin):和week 非常相似
可以同時修飾基本數據類型和 NSObject 對象 ,其實它本身是 week 的前身 , 在 iOS5 之后,基本都用 week 代替了 unsafe_unretained 。 但它們之間還是稍微有點區別的,并不是完全一樣,對上層代碼來說,能用 unsafe_unretained 的地方,都可以用 week 代替?!巴瑫r要注意一點,這個修飾符修飾的變量不屬于編譯器的內存管理對象”
二、類別Category
重寫一個類的方式用「繼承」還是「分類」,取決于具體情況:
- 假如目標類有許多的子類,我們需要拓展這個類又不希望影響到原有的代碼,繼承后比較好。
- 如果僅僅是拓展方法,分類更好.(不需要涉及到原先的代碼)
優點:
- 分類中方法的優先級比原來類中的方法高,也就是說,在分類中重寫了原來類中的方法,那么分類中的方法會覆蓋原來類中的方法。+(void)load方法是一個特例,它會在當前類執行完之后,category中的再執行。)
- 可以用runtime進行method swizzling(方法的偷梁換柱)來處理異常調用的方法。
缺點:
通過觀察頭文件我們可以發現,Cocoa框架中的許多類都是通過category來實現功能的,可能不經意間你就覆蓋了這些方法中的其一,有時候就會產生一些無法排查的異常原因。
其他問題
淺拷貝和深拷貝的區別?
淺拷貝
:只復制指向對象的指針,指針指向同一個地址,而不復制引用對象本身。
深拷貝
:復制引用對象本身。內存中存在了兩份獨立對象本身,當修改A 時,B 不變。(即:B = [A copy]; )
一、內存管理
案例:
辦公室開關燈
,有人開燈,無人關燈;進人加一,出人減一;第一個開燈的人持有此燈,第一個人能廢棄此燈,其他人只是弱引用。
內存管理原則:
1.自己生成的對象,自己持有
2.非自己生成的對象,自己也能持有
3.不再需要自己持有的對象時,釋放掉
4.非自己持有的對象,無法釋放(釋放會崩潰)
對象操作與Objective-C方法的對應
對象操作 | Objective-C方法 |
---|---|
生成并持有對象 | alloc、new、copy、mutableCopy |
持有對象 | retain (引用計數加1) |
釋放對象 | release(引用計數減1) |
廢棄對象 | dealloc(引用計數為0) |
相關釋放函數:autorelease、NSAutoreleasePool、@autoreleasepool
2、非自己生成的對象,自己也能持有
id obj = [NSMutableArray array];
[obj retain];
* 使用autorelease方法,可以取得對象的存在,但是自己不持有對象(obj不立刻釋放掉,注冊到autoreleasePool中)
- (id)object {
id obj = [[NSObject alloc] init];
[obj autorelease];
return obj;
}
ARC 環境下的使用功能規則
- 不能使用retain、release、retainCount、autorelease
- 不能使用NSAllocateObject、NSDeallocateObject
- 必須遵循內存管理命名規則(駝峰命名法)
- 不要顯式調用dealloc
- 使用 @autorelease 代替 NSAutoreleasePool
- 不能使用區域 (NSZone)
- 對象型變量不能作為C語言結構體(struct/union)的成員
- 顯式轉換 “id” 和 “void *”
會讓對象引用計數增加的操作:
1. new、alloc、retain、copy、mutableCopy
2. 添加視圖:用 addview 把一個控件添加到視圖上,這個控件的引用計數+1;
3. 添加數組:把一個對象添加到數組中,數組內部會把這個對象的引用計數+1;
4. 屬性賦值:會讓對象的引用計數+1;
5. 屬性關聯:xib中的控件,跟代碼關聯后,會讓對象的引用計數+1;
6. push這個操作會讓對象的引用計數增加。
ARC、MRC混編
- ARC環境下 使用MRC代碼,使用 -fno-objc-arc轉換
- MRC環境下 使用ARC代碼,使用 -fobjc-arc轉換
所有權修飾符
- _ _strong 修飾符
- _ _weak 修飾符
- _ _unsafe_unretained 修飾符(MRC環境下使用)
- _ _autoreleasing 修飾符 (ARC環境下使用)
1、_ _strong修飾符是id類型和對象類型的默認修飾符,一般是隱式的;
id obj = [[NSObject alloc] init];
//同上
id _ _strong obj = [[NSObject alloc] init];
2、 _ _weak 解決 循環引用的 修飾符,防止內存泄漏。在持有某對象的弱引用時,若該對象被廢棄,則弱引用自動失效,且被賦值nil,不在持有該對象(持有的對象超出作用域,會被廢棄銷毀)。
3、_ _unsafe_unretained 和 _ _weak修飾的變量一樣,因為自己生成并持有的對象不能繼續為自己所有,所以生成的對象會被立即釋放。
4、_ _autoreleasing 隱式修飾符:非自己生成的對象,自己也能持有,會注冊到自動釋放池中。
屬性聲明的屬性 與 所有權修飾符的對應關系
聲明的屬性 | 所有權修飾符 | 說明 |
---|---|---|
assign | _ _unsafe_unretained | 修飾基本數據類型,如NSInteger和CGFloat,這些數值主要存在于棧上。 |
copy | _ _strong | 每次復制會在內存中拷貝一份對象,指針指向不同的地址 |
retain | _ _strong | 用來持有對象 |
strong | _ _strong | 持有對象,引用計數會增加1。該對象只要引用計數不為0則不會被銷毀。當然強行將其設為nil可以銷毀它。 |
unsafe_unretained | _ _unsafe_unretained | 不安全持有 |
weak | _ _weak | 不擁有該對象。其修飾的對象引用計數不會增加。無需手動設置,該對象會自行在內存中銷毀。 |
釋放對象時,廢棄對象不被持有的動作:
1、objc_release (釋放)
2、因為引用計數為0,所以執行dealloc(釋放內存)
3、_objc_rootDealloc(根解除)
4、object_dispose(廢棄處理)
5、objc_destructInstance(銷毀)
6、objc_clear_deallocating(清除)
對象被廢棄時,最后調用objc_clear_deallocating的動作(相關表 SideTables
):
1、從weak表中獲取廢棄對象的記錄(地址為鍵值)。address= h(key)
2、將包含在記錄中的所有附有_ _weak修飾符變量的地址,賦值nil。
3、從weak表中刪除該記錄。
4、從引用計數表中刪除廢棄對象的記錄(地址為鍵值)。
/* MRC環境
* NSAutoreleasePool
* autorelease
*/
- (void)test2 {
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
id obj = [[NSObject alloc] init];
[obj autorelease];
[pool drain];
//同上
@autoreleasepool {
id obj = [[NSObject alloc] init];
[obj autorelease];
}
//同上 (ARC:ReferenceCountingVC2 )
@autoreleasepool {
id __autoreleaseing obj2;
obj2 = obj
}
}
下面了解幾個概念:
內存泄漏:
應當廢棄的對象在超出其生存周期后繼續存在。
野指針:
指針變量未初始化,其值是隨機的;指針釋放后未置空,指向不存在的內存。
懸垂指針:
指向曾經存在的對象,但是該對象已經不存在了。
標記指針:
Tagged Pointer
是一種特殊的“指針”,其特殊在于,其實它存儲的并不是地址,而是真實的數據和一些附加的信息。(后面做專門講解)
isa 指針:
NONPOINTER_ISA
對象的isa指針,用來表明對象所屬的類類型和一些附加信息。(nonPointer_isa 后面做專門講解)
標記指針: Tagged Pointer
tagged pointer是一種特殊的“指針”,其特殊在于,其實它存儲的并不是地址,而是真實的數據和一些附加的信息。
可以看到,利用tagged pointer后,“指針”即存儲了對象本身,也存儲了和對象相關的標記。這時的tagged pointer里面存儲的不是地址,而是一個數據集合。同時,其占用的內存空間也由16字節縮減為8字節。
(即:Tagged Pointer = 對象本身 + 對象相關標記)
我們可以在WWDC2013的《Session 404 Advanced in Objective-C》視頻中,看到蘋果對于Tagged Pointer特點的介紹:
? Tagged Pointer專門用來存儲小的對象,例如NSNumber
,NSDate
,NSString
。
? Tagged Pointer指針的值不再是地址了,而是真正的值。實際上它不再是一個對象了,它只是一個披著對象皮的普通變量而已。所以,它的內存并不存儲在堆中,也不需要malloc和free。
? 在內存讀取上有著3倍的效率,創建時比以前快10倍
例如:NSString
其輸出的class類型為 NSTaggedPointerString
。在字符串長度在9個以內時,iOS其實使用了tagged pointer做了優化的。
直到字符串長度大于9,字符串才真正成為了__NSCFString
類型。
首先,iOS需要一個標志位來判斷當前指針是真正的指針還是tagged pointer。
在runtime源碼的objc-internal.h中,有關于標志位
的定義如下:
#if __has_feature(objc_fixed_enum) || __cplusplus >= 201103L
enum objc_tag_index_t : uint16_t
#else
typedef uint16_t objc_tag_index_t;
enum
#endif
{
OBJC_TAG_NSAtom = 0,
OBJC_TAG_1 = 1,
OBJC_TAG_NSString = 2, // 標志位是2的 tagger pointer表示這是一個NSString對象。
OBJC_TAG_NSNumber = 3,
OBJC_TAG_NSIndexPath = 4,
OBJC_TAG_NSManagedObjectID = 5,
OBJC_TAG_NSDate = 6,
OBJC_TAG_RESERVED_7 = 7,
OBJC_TAG_First60BitPayload = 0,
OBJC_TAG_Last60BitPayload = 6,
OBJC_TAG_First52BitPayload = 8,
OBJC_TAG_Last52BitPayload = 263,
OBJC_TAG_RESERVED_264 = 264
};
#if __has_feature(objc_fixed_enum) && !defined(__cplusplus)
typedef enum objc_tag_index_t objc_tag_index_t;
#endif
‘地址’以
0xa
開頭,轉換為二進制是1010
, 首位1表示這是一個tagged pointer
,而010轉換為十進制是2,表示這是一個NSString類型
。‘地址’以
0xb
開頭, 轉換為二進制是1011
, 首位1表示這是一個tagged pointer
, 而011轉換為十進制是3,表示這是一個NSNumber類型
。
(即:標志位是2的tagger pointer表示這是一個NSString對象。)
由于一個tagged pointer所指向的并不是一個真正的OC對象,它其實是沒有isa屬性的。
isa 指針(NONPOINTER_ISA)
isa:
是一個指向對象所屬Class類型的指針。(nonPointer_isa)
對象的isa指針,用來表明對象所屬的類類型和一些附加信息。
如果isa指針僅表示類型的話,對內存顯然也是一個極大的浪費。于是,就像tagged pointer一樣,對于isa指針,蘋果同樣進行了優化
。isa指針表示的內容變得更為豐富,除了表明對象屬于哪個類之外,還附加了 「引用計數」extra_rc
,是否有被weak引用標志位weakly_referenced
,是否有附加對象標志位has_assoc
等信息。(rc:reference counter 引用計數)
//------- NSObject(實質) -------
@interface NSObject <NSObject> {
Class isa OBJC_ISA_AVAILABILITY;
}
//------- objc_class(繼承)-------
struct objc_class : objc_object {
// Class ISA;
Class superclass;
cache_t cache; // formerly cache pointer and vtable 以前的緩存指針和虛函數表
class_data_bits_t bits; // class_rw_t * plus custom rr/alloc flags class_rw_t *加上自定義rr/alloc標志
//。。。
}
//------- isa指針(指向)-------
struct objc_object {
private:
isa_t isa;
public:
Class ISA(); // ISA() assumes this is NOT a tagged pointer object 假設這不是一個標記的指針對象
Class getIsa(); // getIsa() allows this to be a tagged pointer object 允許這是一個標記的指針對象
//。。。
}
//------- 聯合體(定義)-------
union isa_t
{
isa_t() { } //構造函數1
isa_t(uintptr_t value) : bits(value) { } //構造函數2
Class cls; //成員1(占據64位內存空間)
uintptr_t bits; //成員2(占據64位內存空間)
#if SUPPORT_PACKED_ISA
# if __arm64__
# define ISA_MASK 0x0000000ffffffff8ULL
# define ISA_MAGIC_MASK 0x000003f000000001ULL
# define ISA_MAGIC_VALUE 0x000001a000000001ULL
struct { //成員3(占據64位內存空間:從低位到高位依次是nonpointer到extra_rc。成員后面的:表明了該成員占用幾個bit。)
uintptr_t nonpointer : 1; //(低位)注意:標志位,表明isa_t *是否是一個真正的指針?。?!
uintptr_t has_assoc : 1; // 關聯對象
uintptr_t has_cxx_dtor : 1;
uintptr_t shiftcls : 33; // MACH_VM_MAX_ADDRESS 0x1000000000
uintptr_t magic : 6;
uintptr_t weakly_referenced : 1; //弱引用
uintptr_t deallocating : 1;
uintptr_t has_sidetable_rc : 1; //引用計數 相關成員1
uintptr_t extra_rc : 19; //引用計數 相關成員2 (用19位來 記錄對象的引用次數)
# define RC_ONE (1ULL<<45)
# define RC_HALF (1ULL<<18)
};
}
引用于書籍《Objective-C高級編程》
GNUstep 是Cocoa 框架的互換框架。
CF:Core Foundation
CFRutime.c
蘋果的實現大概是采用散列表(引用計數表)來管理引用計數。
在runtime中,有四個數據結構非常重要,分別是SideTables
、SideTable
、weak_table_t
和weak_entry_t
。它們和對象的引用計數,以及weak引用相關。
(相關記錄,我的電腦文件:101-Runtime基礎)
引用計數表的關系
其實在絕大多數情況下,僅用優化的isa_t
來記錄對象的引用計數
就足夠了。只有在19位的extra_rc
盛放不了那么大的引用計數時,才會借助SideTable出馬。
SideTable:是一個 全局的引用計數表,它記錄了所有對象的引用計數。
先說一下這四個數據結構的關系。 在runtime內存空間中,SideTables 是一個長度為8個元素 的hash數組,里面存儲了SideTable
。SideTables
的hash鍵值就是一個對象obj的address。即:table1 = h(address1)
因此可以說,一個obj,對應了一個SideTable
。但是一個SideTable
,會對應多個obj。因為SideTable
的數量只有64個,所以會有很多obj共用同一個SideTable
。
SideTables
SideTable地址 | 關鍵字 |
---|---|
table1 = h ( address1 ) | address1 |
table2 = h ( address2 ) | address1 |
table3 = h ( address3 ) | address1 |
... | ... |
SideTable
obj對象地址 | 關鍵字 |
---|---|
address1 = h ( key1 ) 、RefcountMap1、weak_table1 | key1 |
address2 = h ( key2 ) 、RefcountMap2、weak_table2 | key2 |
address3 = h ( key3 )、RefcountMap3、weak_table3 | key3 |
... | ... |
而在一個SideTable中,又有3個成員,分別是:
spinlock_t slock
: 自旋鎖,用于上鎖/解鎖 SideTable。RefcountMap refcnts
:以DisguisedPtr<objc_object>為key的hash表,用來存儲OC對象的引用計數(僅在未開啟isa優化 或 在isa優化情況下isa_t的引用計數溢出時才會用到)。weak_table_t weak_table
: 存儲對象弱引用指針的hash表。是OC weak功能實現的核心數據結構。
struct
weak_table_t
{
weak_entry_t *weak_entries
; // 保存了所有指向指定對象的 weak 指針
size_tnum_entries
; // 存儲空間
uintptr_tmask
; // 參與判斷引用計數輔助量
uintptr_tmax_hash_displacement
; // hash key 最大偏移值
};
其中,refcents
是一個hash map,其key是obj的地址,而value,則是obj對象的引用計數。 即:referenceCount = h ( address )
而weak_table
則存儲了弱引用obj的指針的地址,其本質是一個以obj地址為key,弱引用obj的指針的地址作為value的hash表。hash表的節點類型是weak_entry_t
。 即:objPointer = h ( address )
二、Block
三、GCD與多線程
iOS全解1-3:鎖、GCD與多線程
iOS全解1-4:NSURLSession
參考文章,如有問題請聯系本人QQ1178690076
Object-C高級編程讀書筆記(1)——Block的基本概念
Object-C高級編程讀書筆記(2)——Block的實質
Object-C高級編程讀書筆記(3)——Block的變量截取
Object-C高級編程讀書筆記(4)——__block說明符
Object-C高級編程讀書筆記(5)——Block的對象類型截取
Objective-C runtime機制(1)——基本數據結構:objc_object & objc_class
Objective-C runtime機制(2)——消息機制
Objective-C runtime機制(3)——method swizzling
Objective-C runtime機制(4)——深入理解Category
Objective-C runtime機制(5)——iOS 內存管理
Objective-C runtime機制(6)——weak引用的底層實現原理
Objective-C runtime機制(7)——SideTables, SideTable, weak_table, weak_entry_t
Objective-C runtime機制(8)——OC對象從創建到銷毀
Objective-C runtime機制(9)——main函數前發生了什么
Objective-C runtime機制(10)——KVO的實現機制
Objective-C runtime機制(11)——結業考試