OC基礎總結
重新回過頭看這些基礎知識,對許多知識點都有新的認識,擁有堅實的基礎才能更快的成長。
#improt
OC程序的源文件的后綴名是.m m代表message表示消息機制。main 仍然是OC程序的入口和出口,main函數有一個int類型的返回值,代表程序的結束狀態。
#import
預處理指令,是#inlcude
指令的增強版,作用是將文件的內容在預編譯的時候拷貝到寫指令的地方。 #import
做了優化,同一個文件無論#import多少次,都只會包含一次。
簡要原理:#import指令在包含文件的時候,底層會先判斷這個文件是否被包含,如果包含過就會略過。
因此#import
#include
主要區別在于使用#include
需要處理重復引用,而#import
能防止同一個文件被多次包含,不需要處理重復引用。
框架
- 一個功能集蘋果或者第三方事先將一個協成員在開發程序的時候經常要用到的功能事先寫好。把這些功能封裝在一個類或者函數中,這些函數和類的集合就叫做框架。
- Foundation框架
Foundation:基礎框架,這個框架中提供了一些最基礎的功能,輸入和輸出,一些數據類型。
如何使用面向對象來設計程序
面向對象后期維護和修改十分方便。當我們遇到一個需求的時候,不要親自去實現。
- 先看看有沒有現成的人是專門做這件事情的,框架,如果有直接使用。
- 如果沒有就自己造一個擁有這樣功能的對象,并且創造出來的這個對象可以多次被使用。
類和對象
- 對象 - 具體
對象是現實生活中一個具體存在,看得見,摸得著,拿過來就可以直接使用 - 類 - 統稱
物以類聚,人以群分。
類時對一群具有相同特征或者行為的事物的一個統稱,抽象的,不能直接使用,如果非要使用類的話,只能去類中找到類的具體存在,也就是對象,然后使用。
類和對象的關系
類是模板,類的對象是根據這個模板創建出來的,類模板中有什么,對象中就有什么,絕不可能多,也絕不可能少。
如何設計一個類
設計類的三要素
- 類的名字。
- 這類事物具有的相同的特征,這類事物用手什么。
- 這類事物的能干什么。
類加載
- 在創建對象的時候,肯定是需要訪問類的。
- 聲明一個類的指針變量也會訪問類的。
在程序運行期間,當某個類第一次被訪問到的時候,會將這個類存儲到內存中的代碼段區域,這個過程叫做類加載。
只有類在第一次被訪問的時候,才會做類加載,并且一旦類被加載到代碼段以后,直到程序結束的時候才會被釋放。
對象在內存中究竟是如何存儲的。
例:Person *p1 = [Person new];
- Person *p1; 會在棧內存中申請一塊空間,在棧內存中聲明1個Person類型的指針變量p1。p1是一個指針變量,那么只能存儲地址。
- [person new];真正在內存中創建對象的其實是這句代碼。
- new方法在堆內存中創建一塊合適大小的空間,然后在空間中根據類的模板創建對象。
類模板中定義了什么屬性,就把這些屬性依次聲明在對象之中。
對象中還有另外一個屬性,叫做isa ,是一個指針,指向對象所屬的類在代碼段中的地址。 - 初始化對象的屬性,給對象的屬性賦默認值。
如果屬性的類型是基本數據類型,那么就賦值為0
如果屬性的類型是c語言的指針類型,那么就賦值為NULL
如果屬性的類型為OC語言的類指針類型,那么就賦值為nil - 注意
1). 對象中只有屬性沒有方法,屬性包括自己類的屬性,外加一個isa指針指向代碼段中的類。
2). 如何訪問對象的屬性,指針名->屬性名
根據指針,找到指針指向的對象,在找到對象中的屬性來訪問。
3). 如何調用方法。[指針名 方法名];
先根據指針名找到對象,對象發現要調用方法,在根據對象的isa指針找到類。然后調用類里的方法。
4). 為什么不把方法存儲到對象之中。
因為每一個對象的方法的代碼實現都是一模一樣的,沒有必要為每一個對象都保存一個方法,這樣的話就太浪費空間了,既然都一樣,那么就只保存一份在代碼段中。
5). 對象屬性是有默認值的。
nil與NULL的區別
NULL 可以作為指針變量的值,如果一個指針變量的值是NULL值代表這個指針不指向內存中的任何一塊空間,其實等價于0。NULL其實是一個宏,就是0。
nil 只能作為指針變量的值,代表這個指針變量不指向內存中任何空間。nil其實也等價于0,也是一個宏,就是0。
所以NULL和nil其實是一樣的,雖然使用NULL的地方可以使用nil,但是不建議隨意使用。C指針用NULL OC的類指針用nil。
Person *p1 = nil;
表示p1指針不指向任何對象。
如果一個指針的值為nil代表這個指針不指向任何對象,此時如果通過p1指針去訪問p1指針指向的對象的屬性,運行就會報錯。如果通過p1指針去調用對象的方法,運行不會報錯,但是方法不會執行。
多個指針指向同一個對象
同類型的指針變量之間是可以相互賦值的。p1,p2指向同一個對象,無論誰修改對象的屬性都會修改。因為他們指向同一塊內存空間。
對象和方法
對象可以作為方法的參數也可以作為方法的返回值。
類的本質是我們自定義的一種數據類型,并且對象在內存中的大小是由我們自己決定的,數據類型是在內存中開辟空間的一個模板
當對象作為方法的參數傳遞的時候,是地址傳遞。所以,在方法內部通過形參去修改形參指向的對象的時候,會影響實參變量指向的對象的值。對象作為方法的返回值,返回的是對象的地址
對象作為類的屬性。
屬性的本質是變量,在創建對象的時候,對象當中的屬性是按照類模板中的規定逐個創建出來的。類模板中屬性是什么類型,那么對象中的屬性就是什么類型。
如果對象的屬性是另外一個類的對象,這個屬性僅僅是一個指針變量而已,并沒有對象產生。這個時候還要為這個屬性賦值一個對象的地址,才可以正常使用。
類模板中屬性是什么類型,對象當中的屬性就是什么類型。
類方法的聲明和調用
類方法的調用不依賴對象,如果要調用類方法不需要去創建對象。而是直接使用類名就可以調用類方法。
類方法和對象方法的調用過程。
類方法節約空間且效率高,因為調用類方法不需要創建對象。但是在類方法中不能直接訪問屬性。因為屬性只有在對象創建的時候才會創建在對象之中,而類方法在執行的時候有可能還沒有類對象,所以不能訪問屬性。但是我們可以在類方法中創建類對象。
同理,在類方法中也不能通過self直接調用當前類的其他的對象方法,因為對象方法只能通過對象來調用,在對象方法中可以直接調用類方法。
因此如果方法不需要直接訪問屬性,也不需要直接調用其他的對象方法,那么我們就可以直接將這個方法定義為類方法。
繼承
- 子類從父類繼承,就意味著子類擁有了父類的所有成員,包括屬性和方法。
- 繼承是類在繼承,而不是對象在繼承,子類對象中擁有父類對象中的同樣的成員。
如果不是所有的子類都擁有的方法,那么這個方法就不應該定義在父類之中,因為一旦定義在父類之中,那么所有的子類都擁有該方法
繼承的特點
- 一個類只能有一個父類,不能有多個父類。
- 傳遞性,A繼承B,B繼承C 那么A就同時擁有B和C的成員。
NSObject
NSObject類是所有類的父類,NSObject類中包含了創建對象的方法,所以我們自己創建的類必須直接或者間接的繼承自NSObject。
NSObject中有一個isa指針的屬性,所以每一個子類對象中都有一個叫做isa的指針。
Super關鍵字
- 子類中已經有父類的屬性,相當于子類中已經定義過父類的屬性,因此子類當中不能存在和父類同名的屬性,否則會出現沖突。
- 可以使用super關鍵字調用當前對象從父類繼承過來的對象方法。可以使用self 也可以使用 super
- 類方法也可以被子類繼承。子類可以直接調用父類的類方法。
- super只能用來調用父類的對象方法或者類方法,不能用來訪問屬性。
- 子類從父類繼承,相當于子類模板中擁有了父類模板中的所有成員。
- 創建一個子類對象,仍然是根據子類模板來創建對象,只不過子類模板中擁有父類的屬性和方法,也有子類自己的屬性和方法。
- 父類的方法用super 可讀性更高,我們很快就能知道這個方法是父類方法。
訪問修飾符:
用來修飾屬性,可以限定對象的屬性在那一段范圍之中訪問。
@private : 私有,被其修飾的屬性只能在本類的內部訪問。
@protected: 受保護的 被其修飾的屬性只能在本類以及本類的子類中訪問。只能在本類和子類的方法實現中訪問。
@package: 被其修飾的屬性,可以在當前框架中訪問。
@public: 公共的,被其修飾的屬性可以在任意地方訪問。
如果不為屬性指定訪問修飾符 默認:protected
子類仍然可以繼承父類的私有屬性。就算父類的屬性是private,只不過在子類當中無法直接訪問從父類繼承過來的私有屬性,可以通過set get方法來訪問。
訪問修飾符的作用域
從寫訪問修飾符的地方開始往下,直到遇到另外一個訪問修飾符的或者結束大括弧為止,中間的所有的屬性都應用這個訪問修飾符。
使用建議
@public 無論什么情況下都不要使用,屬性不要直接暴漏給外界。
@private 如果屬性只想在本類中使用,不想再子類中使用。
@protected 如果你希望屬性只在本類和本類的子類中使用。
訪問修飾符只能用來修飾屬性,不能用來修飾方法。
里氏替換原則
子類可以替換父類的位置,并且程序的功能不受影響。
即一個父類指針指向一個子類對象,可正常調用子類的方法和屬性。
LSP 里氏替換原則:
一個指針中不僅可以存儲本類對象的地址,還可以存儲子類對象的地址
如果一個指針的類型是NSObject類型的,那么這個指針中可以存儲任意的OC對象的地址。
如果一個數組的元素的類型是一個OC指針類型的,那么這個數組中不僅可以存儲本類對象還可以存儲子類對象。
如果一個數組元素是NSObject指針類型,那就意味著任意類型的對象都可以存在數組中。
如果一個方法的參數是一個對象,我們可以傳本類對象也可以傳子類對象
當一個父類指針指向1個子類對象的時候,通過父類指針就只能去調用子類對象中的父類成員。
方法重寫
當子類擁有父類的行為,但是子類的行為與父類不同。這個時候就可以通過重寫父類的方法來實現。
當一個父類指針指向一個子類對象的時候,通過這個父類指針調用的方法,如果子類對象中重寫了這個方法,調用的就是子類重寫的方法。
多態
指的是同一個行為,對于不同的事物具有完全不同的表現形式。同一個行為具備多種形態。子類重寫父類的方法就是多態。
description 方法
description方法是定義在NSObject之中的。我們通過重寫description方法來修改NSLog的輸出形式。NSLog的底層就是description方法。
結構體與類的異同點
相同點:
都可以將多個數據封裝為一個數據
不同點:
- 結構體只能封裝數據,而類不僅可以封裝數據還可以封裝行為。
- 結構體變量分配在??臻g (局部)
- 對象變量分配在堆空間
- 棧的特點:空間相對較小,但是存儲在棧中的數據訪問的效率更高一些
- 堆的特點:空間相對較大,但數據訪問的效率相對要低。
如果表示的實體沒有行為,只有屬性。
那么如果屬性比較少,只有幾個,那么這個時候就定義為結構體,分配在棧,提高效率。如果屬性比較多,不要定義成結構體,因為這樣結構體變量會在棧中占據比較大的空間,導致訪問效率降低。
類的本質
類是以Class對象存儲在代碼段中的
內存中的五大區域
棧 存儲局部變量
堆 允許程序員自己申請的空間,需要程序員自己控制
BSS段 存儲沒有初始化的全局變量和靜態變量
數據段 用來存儲已經初始化的全局變量,靜態變量還有常量
代碼段 用來存儲程序的代碼。
類加載:當類第一次被訪問的時候,這個類就會被加載到代碼段存儲起來。
類什么時候加載到代碼段
類第一次被訪問的時候,類就會被加載到代碼段存儲,即類加載時。類以什么樣的形式存儲在代碼段。
任何存儲在內存中的數據都有一個數據類型,任何在內存中申請的空間也有自己的類型。那么在代碼段存儲類的那塊空間是什么類型的?
在代碼段中存儲類的步驟
1). 先在代碼段中創建一個Class對象,Class是Foundation框架中的一個類,這個Class對象就是用來存儲類的信息的。
2). 將類的信息存儲在這個Class對象中
所以類是以Class對象的形式存儲在代碼段的,存儲類的這個Class對象也叫作類對象,所以存儲類的類對象也有一個isa指針,指向存儲父類對象。類一旦被加載到代碼段之后,什么時候會被釋放。
類一旦被加載到代碼段之后是不會被回收的,除非程序結束。如何拿到存儲在代碼段中的類對象?
1). 調用類的類方法class就可以得到存儲類的類對象的地址。
2). 調用對象的對象方法 class就可以得到存儲這個對象所屬的類的class對象的地址。
3). 對象中的isa指針的值其實就是代碼段中存儲類的類對象的地址。
4). 拿到存儲類的類對象以后,完全等價于類
Class c1 = [Person class];
c1對象就是Person類,c1完全等價于Person,可以使用類對象來調用類的類方法。
注意:聲明Class指針的時候,不需要加*
因為在typedef的時候已經加了*
了如何使用類對象
1). 拿到存儲類的類對象以后, Class c1 = [Person class]; c1對象就是Person類,c1對象完全等價于Person
2). 使用類對象來調用類的類方法。
3). 可以使用類對象來調用new方法,創建存儲在類對象中的類的對象。
4). 使用類對象,只能調用類的類方法,因為類對象就等價于存在其中的類總結
1). 類是以Class對象的形式存儲在代碼段之中的。
2). 可以使用類對象來調用類的類方法。
3). 通過class方法拿到存儲類的類對象。
SEL選擇器
SEL其實是一個類,要在內存中申請空間存儲數據,SEL對象是用來存儲一個方法的。
類是以Class對象的形式存儲在代碼段之中。那么如何將方法存儲在類對象之中?
- 先創建一個SEL對象
- 將方法的信息存儲在這個SEL對象之中
- SEL對象作為Class對象的一個屬性來存儲。
- 類似一個數組的形式將所有的SEL對象存儲,即方法列表。SEL對象中存儲方法的信息。
如何拿到存儲方法的SEL對象
- 因為SEL是一個typedef類型的,在自定義的時候已經加了
*
所以我們聲明SEL指針的時候不需要加*
。 - 取到存儲方法的SEL對象 SEL sel = @selector(sayHi);
- 調用方法的本質:
[p1 sayHi];
1). 先拿到存儲sayHi方法的SEL對象,也就是拿到存儲sayHi方法的SEL數據,SEL消息。
2). 將SEL消息發送給p1對象。
3). p1對象接收到SEL消息以后,就知道要調用方法
4). 根據對象的isa指針找到存儲類的類對象。
5). 找到這個類對象以后,在這個類對象中去搜尋是否有和傳入的SEL數據相匹配的方法。如果有就執行,如果沒有再找父類,直到NSObject。
OC最重要的1個機制:消息機制,調用方法的本質其實就是為對象發送SEL消息,[p1 sayHi];
表示為p1對象發送1條sayHi消息。
手動的為對象發送SEL消息
- 先得到方法的SEL數據。
- 將這個SEL消息發送給p1對象。
通過方法- (id)performSelector:(SEL)aSelector;
手動發送消息
Person *p1 = [Person new];
SEL s1 = @selector(sayHi);
[p1 performSelector:s1]; 與 [p1 sayHi]效果是完全一樣的.
// 帶一個參數和兩個參數的方法
// 如果有多個參數可以把參數封裝在一個對象里面,傳遞對象
- (id)performSelector:(SEL)aSelector withObject:(id)object;
- (id)performSelector:(SEL)aSelector withObject:(id)object1 withObject:(id)object2;
點語法
使用點語法訪問對象的屬性。
語法:對象名.去掉下劃線的屬性名
p.name = @“jack”;
這個時候就會將@“jack”賦值給p對象的_name屬性。
原理:
本質并不是把@"jack"直接賦值給p對象的_name屬性,實質上是調用set方法。
當使用點語法賦值的時候,編譯器會將點語法轉換為調用setter方法的代碼。
當使用點語法取值的時候,編譯器會將點語法轉換為調用getter方法的代碼。
在getter setter方法中慎用點語法,可能會造成無限遞歸而程序崩潰。
主要是看情況,get方法中如果點語法是調用set方法是可以使用的。
例如懶加載中我們是可以使用點語法為賦值的,因為懶加載是get方法,而賦值是調用set方法,所部不會遞歸調用。
如果屬性沒有封裝setter getter 是無法使用點語法的。
@property
@property 自動生成getter setter 方法的聲明。
原理:由編譯器在編譯的時候自動生成。
@synthesize
自動生成getter setter 方法的實現。
@synthesize age // age一定要是前面@property聲明過的。
- 生成一個真私有屬性,屬性的類型和@synthesize對應的@property類型一致,屬性的名字和@synthesize對應的@property名字一致。
- 自動生成setter方法的實現。實現的方式,將參數直接賦值給自動生成的那個私有屬性。 self->age = age;
- 自動生成getter方法的實現。將生成的私有屬性的值返回
希望@synthesize 不要生成私有屬性,setter getter 的實現中操作我們已經寫好的屬性就可以了。
@synthesize @property名稱 = 已經存在的屬性名;
@synthesize age = _age;
- 不會再去生成私有屬性
- 直接生成setter getter的實現 setter的實現:把參數的值直接賦值給指定的屬性。getter實現:直接返回指定的屬性的值。
@property增強。
Xcode 4.4之后,只要寫一個@property 編譯器會自動生成私有屬性,并且自動生成getter setter 聲明和實現。
@property NSString *name;
- 自動的生成一個私有屬性,屬性的類型和@property類型一致,屬性的名稱和@property的名稱一致,屬性的名稱自動的加下劃線。
- 自動生成這個屬性的 setter getter方法的聲明和實現。直接將參數的值賦值給自動生成的私有屬性,直接返回生成的私有屬性的值。
@property生成set get方法和成員屬性,但是如果同時重寫了setter getter方法,那么就不會自動生成私有屬性了。需要自己寫。
父類的property一樣可以被子類繼承,但是生成的屬性是私有的,可以通過setter getter方法來訪問
動態類型和靜態類型
OC是一門弱語言,編譯器在編譯的時候,語法檢查的時候沒有那么嚴格。
強類型的語言:編譯器在編譯的時候,做語法檢查的時候,就非常嚴格,行就是行,不行就是不行。
靜態類型
指的是一個指針指向的對象是一個本類對象。
動態類型
一個指針指向的對象不是本類對象。
編譯檢查
編譯器在編譯的時候,檢查能否通過一個指針去調用指針指向的對象的方法。
判斷原則:看指針所屬的類型之中有沒有這個方法,如果有就認為可以調用,編譯通過,如果這個類中沒有,那么編譯報錯。這就叫做編譯檢查,在編譯的時候,能不能調用對象的方法主要是看指針的類型,我們可以將指針的類型做轉換,來達到騙過編譯器的目的。
運行檢查
編譯檢查只是騙過了編譯器,但是這個方法究竟能不能執行,所以在運行的時候運行檢查會去檢查對象中是否真的有這個方法,如果有就執行,如果沒有就報錯
編譯器,用編譯器將C代碼OC代碼轉化成二進制,編譯器是個軟件,蘋果自己寫的叫做LLVM,可以編譯C OC Swift語言。
我們可以通過以下方法先來先判斷以下對象中是否有這個方法,如果有再去執行,如果沒有就別去執行,避免程序在沒有方法的時候報錯。
1). 判斷對象中是否有這個方法可以執行.
- (BOOL)respondsToSelector:(SEL)aSelector; (最常用)
2). 判斷類中是否有指定的類方法.
+ (BOOL)instancesRespondToSelector:(SEL)aSelector;
3). 判斷指定的對象是否為 指定類的對象或者子類對象.
- (BOOL)isKindOfClass:(Class)aClass;
4). 判斷對象是否為指定類的對象 不包括子類.
- (BOOL)isMemberOfClass:(Class)aClass;
5). 判斷類是否為另外1個類的子類.
+ (BOOL)isSubclassOfClass:(Class)aClass;
NSObject
OC中所有類的基類,根據LSP NSObject指針就可以指向任意的OC對象,所有NSObject指針是一個萬能指針,可以指向任意的OC對象
缺點:如果要調用指向的子類對象的獨有的方法,就必須要做類型轉換。
id指針
是一個萬能指針,可以指向任意的OC對象
- id是一個typedef自定義類型
- id指針,是1個萬能指針,可以指向任意的OC對象。
1). id是1個typedef自定義類型,在定義的時候已經加了*
所以,聲明id指針的時候不需要再加*
了。
2). id指針是1個萬能指針,任意的OC對象都可以指. - NSObject和id的異同.
相同點: 萬能指針,都可以執行任意的OC對象。
不同點: 通過NSObject指針去調用對象的方法的時候,編譯器會做編譯檢查,
通過id類型的指針去調用對象的方法的時候,編譯器直接通過,無論調用什么方法。
注意: id指針只能調用對象的方法,不能使用點語法,如果使用點語法就會直接報編譯錯誤 。如果要聲明1個萬能指針 千萬不要使用NSObject 而是使用id
instancetype --- id
id和instancetype的區別.
- instancetype只能作為方法的返回值,不能在別的地方使用。
id既可以聲明指針變量,也可以作為參數,也可以作為返回值。 - instancetype是1個有類型的代表當前類的對象。
id是1個無類型的指針,僅僅是1個地址,沒有類型的指針。
new方法
創建對象,我們之前通過new方法
類名 *指針名 = [類名 new];
new實際上是1個類方法,其作用為:
- 創建對象。
- 初始化對象。
- 把對象的地址返回。
new方法的內部,其實是先調用的alloc方法,再調用的init方法。
alloc方法是1個類方法。作用: 那1個類調用這個方法就創建那個類的對象,并把對象返回,分配內存空間。
init方法是1個對象方法。作用: 初始化對象。
init方法
作用: 初始化對象,為對象的屬性賦初始值,這個init方法我們叫做構造方法。
init方法做的事情:初始化對象,并為對象的屬性賦默認值。
如果屬性的類型是基本數據類型就賦值為0,C指針賦值NULL,OC指針賦值nil。
所以,我們創建1個對象如果沒有為這個對象的屬性賦值這個對象的屬性是有默認值的。
重寫init方法的規范:
1). 必須要先調用父類的init方法,然后將方法的返回值賦值給self。
2). 調用init方法初始化對象有可能會失敗,如果初始化失敗,返回的就是nil。
3). 判斷父類是否初始化成功,判斷self的值是否為nil,如果不為nil說明初始化成功。
4). 如果初始化成功就初始化當前對象的屬性。
5). 最后 返回self的值。
自定義構造方法:
1). 自定義構造方法的返回值必須是instancetype。
2). 自定義構造方法的名稱必須以initWith開頭。
3). 方法的實現和init的要求一樣。
文中如果有不對的地方歡迎指出。我是xx_cc,一只長大很久但還沒有二夠的家伙。