1. Objective-C簡介
Objective-C語言簡介
- Objective-C在C語言基礎上做了面向對象擴展。
- 1983年由Brad Cox和Tom Love發明,后成為NeXT的主力語言,后被蘋果收購,成為蘋果開發平臺的主力語言。
- 與Cocoa和Cocoa Touch框架高度集成,支持開發Mac OS X、iOS應用。
- 在蘋果開發平臺上,通過LLVM編譯器架構,支持與Swift語言雙向互操作。
在iOS開發平臺上支持的語言有Swift、Objective-C、C/C++,主要使用前兩者。
?
如何掌握高級編程語言
底層思維:向下,如何把握機器底層從微觀理解對象構造
- 語言轉換
- 編譯轉換
- 內存模型
- 運行時機制
抽象思維:向上,如何將我們的周圍世界抽象為程序代碼
- 面向對象
- 組件封裝
- 設計模式
- 架構模式
「時空人」三位一體分析法
- 對時間分析——發生在什么時候?
- Compile-time VS Run-time
- 對空間分析——變量放在哪里?
- Stack VS Heap
- 人物分析——代碼哪里來的?
- Programmer VS Compiler/Runtime/Framework
兩種開發方式
- Clang或GCC命令行
clang -fobjc-arc HelloWorld.m -o HelloWorld
- 或
gcc -fobjc-arc HelloWorld.m -o HelloWorld
,推薦用clang-fobjc-arc
:支持ARC(Automatic Reference Counting)- 適合調試、研究、微觀探查
-
-o
:輸出文件名 - 更多可以參考
-help
//引入頭文件,可以避免多次引入重復頭文件,推薦使用。
#import <Foundation/Foundation.h>
int main(int argc, const char * argv[]) {
@autoreleasepool {
//類似于prinft,但多了日期時間等信息。
NSLog(@"Hello World~");
}
return 0;
}
- Objective-C的字符串前要加
@
- Xcode項目
- 構建正規工程項目
- 使用大型框架,追求設計質量與代碼組織
Objective-C編譯過程
- GCC: GCC Front End -> GCC Optimizer -> GCC Code Generator
- LLVM-GCC: GCC Front End -> LLVM Optimizer -> LLVM Code Generator
- LLVM-Clang: Clang Front End -> LLVM Optimizer -> LLVM Code Generator
- Objective-C直接生成機器碼
- LLVM:Low Level Virtual Machine
- LLVM-Clang:目前iOS上一般使用的方式,針對iOS優化更好。
學習資源
課程總結
- 了解Objective-C語言和編譯架構
- 了解Objective-C開發工具
- 了解機器思維和抽象思維
- 駕馭工具,而不是被工具駕馭
- 掌握英文學習資源很重要
2. 類與對象
類型系統
- 引用類型 Reference Type
- 類 Class
- 指針 Pointer
- 塊 Block
- 值類型 Value Type
- 基礎數值類型
- 結構 Struct
- 枚舉 Enum
- 類型裝飾
- 協議 Protocol
- 類別 Category
- 擴展 Extension
- C語言中的數據類型均可用在Objective-C中
類 VS 結構
- 類型與實例
- 類與對象
- 結構與值
- 類——引用類型
- 位于棧上的指針(引用)
- 位于堆上的實體對象
- 結構——值類型
- 實例直接位于棧中
- 空間分析
- 運行時內存圖——「胸中有溝壑」
類定義
RPoint.h
@interface RPoint: NSObject
@property int x; //屬性,狀態,這里默認初始化為0
@property int y;
- (void) print; //方法,行為
@end
-
- (void)print
:-
表示實例方法,+
表示類方法,其后的括號內為返回值類型。
RPoint.m
#import <Foundation/Foundation.h>
#import "RPoint.h"
@implementation RPoint
- (void) print {
NSLog(@"[%d, %d]", self.x, self.y);
}
@end
SPoint.h
typedef struct {
int x;
int y;
}SPoint;
對象的空間分析
棧上存儲指針(引用),堆上存儲真正的對象,
值的空間分析
實例(值)內存直接存儲在??臻g
棧 VS 堆
棧:存儲值類型
- 無ARC負擔,由系統自動管理,以執行函數為單位
- 空間大小編譯時確定(參數+局部變量)
- 函數執行時,系統自動分配一個Stack
- 函數執行結束,系統立即自動回收Stack
- 函數之間通過拷貝值傳遞
- 具有局部性,大小有限額,超出會Stack Overflow
堆:存儲引用類型對象
- 分配由程序員手動請求(創建對象時)
- 釋放由運行時ARC機制自動釋放(確定時)
- 函數之間通過拷貝引用(指針)傳遞
- 具有全局性,總體無大小限制(受制于系統內存整體大?。?/li>
main.m
#import <Foundation/Foundation.h>
#import "RPoint.h"
#import "SPoint.h"
void process(RPoint* rp3, SPoint sp3);
int main(int argc, const char * argv[]) {
@autoreleasepool {
//Objective-C中所有的對象均以指針的形式存在,要加 * 號
//中括號為發送消息(方法調用)
//創建對象,alloc為向系統請求內存分配
RPoint* rp1 = [[RPoint alloc] init];
rp1.x = 10;
rp1.y = 20;
[rp1 print]; //[10, 20]
//
SPoint sp1;
sp1.x = 10;
sp2.y = 20;
NSLog(@"------拷貝------")
RPoint rp2 = rp1;
rp2.x++;
rp2.y++;
//引用傳遞
[rp1 print]; //[11, 21]
[rp2 print]; //[11, 21]
SPoint sp2 = sp1;
sp2.x++;
sp2.y++;
//值傳遞
NSLog(@"[%d, %d]", sp1.x, sp1.y); //[10, 20]
NSLog(@"[%d, %d]", sp2.x, sp2.y); //[11, 21]
NSLog(@"------傳參------");
process(rp1, sp1);
[rp1 print]; //[12, 22]
NSLog(@"[%d, %d]", sp1.x, sp1.y); //[10, 20]
}
return 0;
}
//函數開始執行時自動創建一個新棧,與main函數的棧不同,結束時會自動銷毀
void process(RPoint* rp3, SPoint sp3) {
rp3.x++;
rp3.y++;
sp3.x++;
sp3.y++;
[rp3 print]; //[12, 22]
NSLog(@"[%d, %d]", sp3.x, sp3.y); //[11, 21]
//函數執行結束后rp3和sp3將被銷毀
//但rp3僅是指針,銷毀后指針所指向的對象并不受影響。
}
?
3. 數據成員:屬性與實例變量
類型成員 Type Member
數據成員 Data Member 描述對象狀態
- 實例變量 Instance Variable
- 屬性 Property
函數成員 Function Member 描述對象行為
- 方法 Method
- 初始化器 Init
- 析構器 Dealloc
認識屬性
屬性表達實例狀態,描述類型對外接口。相比直接訪問實例變量,屬性可以做更多控制。
默認情況下,編譯器會為屬性定義propertyName自動合成:
- 一個getter訪問器方法:propertyName
- 一個setter訪問器方法:setPropertyName
- 一個實例變量:_propertyName
可自定義訪問器方法,也可更改訪問器方法名、或實例變量名。
可以使用靜態全局變量(C語言)+ 類方法,模擬類型屬性。
屬性聲明:
@property NSString* firstName;
//--上述代碼將生成:--
NSString* _firstName;
- (NSString*) firstName {/**code**/}
- (void) setFirstName: (NSString*)newValue {/**code**/}
//----
//更改訪問器方法名
@property (getter = GivenName, setter = setGivenName:) NSString* lastName;
//更改實例變量名
@synthesize firstName = givenName;
調用方法:
//訪問器方法
[employee setFirstName: @"Tom"];//set
[employee firstName]; //get
//屬性表達式,推薦。
employee.firstName = @"Tom"; //set
NSLog(@"First Name: %@", employee.firstName);//get
模擬靜態(類)屬性
在.m文件中定義一個靜態變量
//靜態變量
static int _max = 100;
@implementation Employee {
//...
}
在.h文件中定義方法
@interface Employee: NSObject
//...
+ (int) max;
+ (void) setMax: (int)newValue;
//...
@end
在.m文件中實現方法
@implementation Employee {
//...
//為靜態變量提供訪問器方法
+ (int) max {
return _max;
}
+ (void) setMax: (int)newValue {
_max = newValue;
}
//...
}
調用方法:
//Employee為類型,不是對象
[Employee setMax: 300];
Employee.max = 400;
NSLog(@"class variable is %d.", Employee.max);
實例變量
可以定義實例變量,而不定義屬性。只有實例變量,沒有類變量。
如果同時自定義了getter和setter訪問器方法,或者針對只讀屬性定義了getter訪問其方法,編譯器將不再合成實例變量。
在類外一律使用屬性來訪問,類內大多也通過self使用屬性訪問。只有一下情況使用實例變量來訪問:
- 初始化器 init
- 析構器 dealloc
- 自定義訪問器方法
- 實例變量只能在類內訪問,類外不可訪問。
- 引用類型的屬性使用實例變量訪問時可能會?有內存管理的問題。
實例變量的生存周期
實例變量的存儲:跟隨對象實例存儲在堆上。
值類型實例變量直「內嵌」在對象實例中。跟隨對象實例內存釋放而被釋放。
引用類型實例變量通過指針「引用」堆上的引用類型實例,ARC針對引用進行計數管理,自動釋放引用計數為0的對象。
屬性的描述特性
屬性描述特性(Attribute)可以指定屬性不同環境下的不同功能。
- 讀寫特性
- 讀寫屬性 readwrite (默認)
- 只讀屬性 readonly
- 多線程特性
- 原子性 atomic (默認)
- 非原子性 nonatomic
- 內存管理特性
- ARC環境
- 強引用 strong (默認)
- 弱引用 weak 阻止循環引用
- 拷貝屬性 copy 為屬性賦值時創建獨立拷貝
- 其他情況
- retain
- assign
- unsafe_unretained
@property (readonly, nonatomic) NSString* firstName;
- 避免循環引用:使用weak屬性
4. 函數成員:方法
函數成員 Function Member 描述對象行為
- 方法 Method
- 初始化器 Init
- 析構器 Dealloc
認識方法 Method
函數:代碼段上的可執行指令序列
- 全局函數(C語言函數)
- 成員函數(Objective-C方法)
方法是類的成員函數,表達實例行為或類型行為。
所有方法默認為公有方法。沒有private或protected方法。
動態消息分發:方法調用通過運行時動態消息分發實現,在對象上調用方法又稱「向對象發送消息」。
實例方法或類型方法
實例方法——表達實例行為,可以訪問
- 實例成員(實例屬性、實例變量、實例方法)
- 類型方法、靜態變量
類方法——表達類型行為,訪問權限:
- 可以訪問:類型方法、靜態變量
- 不能訪問:實例成員(實力屬性、實例變量、實例方法)
了解編譯器背后對實例方法和類方法的不同處理:self指針
//實例方法
- (void) print {
NSLog(@"[%d, %d]", self.x, self.y);
}
//編譯器編譯后:
void print(BLNPoint* self) {
NSLog(@"[%d, %d]", self.x, self.y);
}
//調用時:
[p1 print];
//編譯器編譯后:
print(p1);
//類型方法
+ (BLNPoint*) getOriginPoint {
//...
//在類型方法中,self 等同于類型,與實例方法中的self不同。
[self process];
//等同于
[BLNPoint process];
[self print];//錯誤
}
//編譯器編譯后:
BLNPoint* getOriginPoint() {
//...
}
//調用時:
BLNPoint* origin = [BLNPoint getOriginPoint];
//編譯器編譯后:
BLNPoint* origin = getOriginPoint();
方法參數
- 如果參數類型為值類型,則為傳值方式;如果參數類型為引用類型,則為傳指針方式。
- 方法可以沒有參數,也可以沒有返回值。
- 如果方法有參數,方法名約定包含第一個參數名,第二個參數開始需要顯示提供外部參數名。
- 調用時,第一個參數名忽略,但后面的參數名必須顯式標明。
- (BOOL) isEqualToPoint: (BLNPoint*) point;
- (void) moveToX: (int)x toY: (int)y;
//調用
[p1 isEqualToPoint: p2];
[p1 moveToX: 100 toY: 200];
-
id
可以表示所有的類型
id obj = [[BLNPoint alloc] init];
[obj moveToX: 50 toY: 60];
[obj print];
[obj setX: 70];
obj.x = 70;//obj聲明為id時不可以這樣用
- 理解動態方法調用機制——消息分發表
5. 初始化器與析構器
認識初始化器與析構器
初始化器用于初始化對象實例或者類型,是一個特殊的函數。
- 對象初始化器:- (id) init 可以重載多個
- 類型初始化器:+ (void) initialize 只能有一個
析構器用于釋放對象擁有的資源,無返回值的函數。
- 對象析構器 - (void) dealloc 只能有一個
- 沒有類型析構器
- (id) init;
- (id) initWithName: (NSString *)name;
- (id) initWithName: (NSString *)name WithPages: (int)pages;
- (void) dealloc;
+ (void) initialize;
對象初始化器
初始化對象實例時,init通常和alloc搭配使用。
alloc所做的事情——NSObject已實現:
- 在堆上分配合適大小的內存。
- 將屬性或者實例變量的內存置0。
init所做的事情——可以自定義:
- 調用父類初始化器 [super init] (前置調用)。
- 初始化當前對象實例變量(注意使用實例變量,不要使用屬性)。
new 相當于調用alloc/init的無參數版本。
- (id) init {
self = [super init];
if (self) {
//...
}
return self;
}
- (id) initWithName: (NSString *)name WithPages: (int)pages {
self = [super init];
if (self) {
//初始化實例對象,使用實例變量,不要使用屬性self.name
_name = [name copy];
_pages = pages;
}
return self;
}
//調用:
Book* book = [Book new];
//相當于
Book* book = [[Book alloc] init];
類初始化器
類初始化器initialize負責類型級別的初始化。
initialize在每個類使用之前被系統自動調用,且每個進程周期中,只被調用一次。
子類的initialize會自動調用父類的initialize(前置調用)。
+ (void) initialize {
if (self == [Book class]) {
//...
}
}
對象析構器
對象析構器dealloc負責釋放對象擁有的動態資源:
- 自動實現:1. ARC將對象屬性引用計數減持
- 手動實現:2. 釋放不受ARC管理的動態內存,如malloc分配的內存
- 手動實現:3. 關閉非內存資源,如文件句柄、網絡端口...
dealloc由ARC根據對象引用計數規則,在釋放對象內存前自動調用,無法手工調用。
子類的dealloc會自動調用父類的dealloc(后置調用)。
6. 繼承與多態
認識面向對象
封裝 Encapsulation
隱藏對象內部實現細節,對外僅提供公共接口訪問。
繼承 Inheritance
一個類型在另外類型基礎上進行的擴展實現。
多態 Polymorphism
不同類型針對同一行為接口的不同實現方式。
認識繼承 Inheritance
繼承:每一個類只能有一個基類,子類自動繼承基類的:
- 實例變量
- 屬性
- 實例方法
- 類方法
了解所有類的根類:NSObject
繼承的兩層含義:
- 成員復用:子類復用基類成員
- 類型抽象:將子類當做父類來使用(IS-A關系準則Circle is a Shape)
Objective-C只支持單繼承,一個類只能有一個基類
如果希望實例變量在類外可以訪問,可以放在接口文件中,必須放在大括號內并加上@public,一般情況下不推薦
@interface Shape: NSObject {
@public int _data;
}
//調用時
shape->_data++;
認識運行時多態 Polymorphism
多態:子類在父類統一行為接口下,表現不同的實現方式。
對比重寫與重載
- 子類重寫父類同名同參數方法:子類只可以重寫override父類方法
- 方法名相同、參數不同:Objective-C不支持方法的重載。
在子類的代碼中,可以使用super來調用基類的實現。
- self具有多態性,可以指向不同子類
- super,沒有多態性,僅指向當前父類
- 屬性的setter和getter方法也可以被重寫
理解self的多態性:
//Shape類
- (void) draw {
NSLog(@"Shape object draw");
}
- (void) move {
NSLog(@"Shape object move");
//這種情況下:self指代rectangle
//所以會調用rectangle的draw方法
[self draw];
}
//------------------------------
//Rectangle類繼承自Shape
- (void) draw {
NSLog(@"Rectangle object draw");
}
/**
//可以理解為Rectangle有一個隱藏的move方法
//和Shape類中的一模一樣
- (void) move {
NSLog(@"Shape object move");
//這種情況下:self指代rectangle
//所以會調用rectangle的draw方法
[self draw];
}
**/
//----------------------------
//調用時
//Shape是聲明類型,Rectangle是實際類型
//所以這兩行的執行結果是相同的
Shape* rectangle = [[Rectangle alloc] init];
//Rectangle* rectangle = [[Rectangle alloc] init];
[rectangle draw];//Rectangle object draw
//這里會輸出:
//Shape object move
//Rectangle object draw
[rectangle move];
繼承中的init和dealloc
初始化器 init
- 子類自動繼承基類的初始化器
- 子類也可以重寫基類初始化器,此時子類初始化器必須首先調用基類的一個初始化器(手工調用)。
析構器 dealloc
- 子類可以選擇繼承基類析構器,或者重寫基類析構器。
- 子類析構器執行完畢后,會自動調用基類析構器(后置調用,且在開啟ARC后不支持手工調用)
- 子類析構器自動具有多態性
Tips: 盡量避免在父類init和dealloc中調用子類重寫的方法。
- (id) init {
self = [super init];
if (self) {
//...
}
return self;
}