第一天視頻課程:
Objective-C 語言簡介
Objective-C 語言是一門在C語言基礎上做了面向對象擴展的編程語言,1983年由Brad Cox 和Tom Love發明,是目前蘋果開發平臺的主力語言,與Cocoa 和Cocoa Touch框架高度集成,支持開發Mac OS X、iOS應用。在蘋果開發平臺上通過LLVM(LowLevelVirtualMachine)編譯器架構支持與Swift語言雙向互操作。
iOS開發平臺
Cocoa框架部分包括了系統內核(Core OS)、內核服務(Core Service)、媒體(Media)、觸摸(Cocoa Touch)這幾個內庫。通過LLVM編譯框架和Objective-C運行時編譯和運行。目前主要有Objective-C、Swift和C/C++這幾種語言來進行編碼,常用的開發工具為Xcode。
掌握高級編程語言的思維方式
底層思維:從微觀、機器的層面理解語言的構造、編譯轉換、內存模型和運行時機制。
抽象思維:將我們周圍世界抽象為程序代碼,即面向對象的思維方式,組件封裝、設計模式、架構模式。
“時空人”三位一體分析方法
時間分析,發生在什么時候?編譯時還是運行時。
空間分析,變量放在那里?堆空間還是??臻g。
人物分析,代碼哪里來的?程序員還是編譯器、運行時、框架。
Objective-C 語言的兩種開發方式
Clang 或 GCC命令行:適合調試、研究、微觀探查。
Xcode項目:適合構建正規工程項目,使用大型框架,追求設計質量與代碼組織。
Objective-C 語言代碼學習
#import 導入頭文件(類似C語言的#include),可以避免相同頭文件的重復導入,推薦使用#import代替#include。
@autoreleasepool 支持ARC(Automatic Reference Counting)的一個池,用來表明啟用了內存自動回收機制。
NSlog(@"Hello,world!"); NSlog類似C語言里的printf用來打印字符串,OC語言里的字符串前需要加@符號(@"Hello,world!")。
?頭文件的擴展名是.h,主程序文件的擴展名是.m
編譯命名行:clang -fobjc-arc HelloWorld.m -o HelloWorld其中-fobjc-arc為ARC內存管理的開關命名,其中-o HelloWorld,-o 為output即輸出的意思,一起為將輸出的文件命名為HelloWorld。clang也可以換成gcc即用gcc編譯器編譯。(推薦用clang)
命名行:clang -help 用來顯示clang的設置幫助文檔,可以用來了解clang的設置。
命名行:./HelloWorld 執行生成的HelloWorld文件。
ObjC編譯過程
目前主流為LLVM-Clang的編譯過程,由OC、C\C++代碼通過Clang前端再通過LLVM優化和LLVM代碼生成器生成出X86-64機器碼。
學習資源
蘋果官方文檔:https://developer.apple.com/library/
programming with Objective-C: https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/ProgrammingWithObjectiveC/
iOS專區:https://developer.apple.com/library/ios/
蘋果開發者大會WWDC:https://developer.apple.com/videos/wwdc/2014/
??????????????????????WWDC:https://developer.apple.com/videos/wwdc/2015/
第二天視頻課程:
類型系統
引用類型 reference type:包括class、pointer、block。
值類型 value type:包括基礎數值類型、結構struct、枚舉enum。
類型裝飾:包括協議protocol、類別category、擴展extension
類 class VS. 結構 struct
類型與實例:類的實例是對象,結構的實例是值。
類是一個引用類型,是位于棧上的指針指向了一個位于堆上的實體對象。
結構是一個值類型,它直接位于棧中。
例子1:聲明一個類(建立一個對外接口)
@interface RPoint:NSObject? //聲明一個類,類名RPoint,繼承自類NSObject。:表示繼承。
@property int x; //聲明一個屬性 int X
@property int y;
-(void)print; //聲明一個實例方法 print
@end
在OC中創建類時要先在頭文件中(.h)創建一個對外借口,@interface來表示對外接口的起始,用@end來對應結束。
@property 在類中定義屬性的關鍵字,用來表示這個類的狀態。
-(void)print 是在類中定義方法的語句,其中-表示是一個實例方法,方法的返回值是void,方法的名字是print.類中的方法來表示這個類的行為。
例子2:實現一個類
#import<Foundation/Foundation.h> //導入Foundation.h頭文件
#import"rpoint.h" //導入rpoint.h頭文件,即上面我們創建的類聲明文件。
@implementation RPoint? //定義一個類名為RPoint?的類
-(void)print{
NSLog(@"[%d,%d]", self.x, self.y);? //print方法的實現
}
@end
OC的類需要在主文件中(.m)實現,@implementation 來表示實現的起始,用@end來對應結束。
NSLog(@"[%d,%d]", self.x, self.y);? NSLog表示打印一個字符串,self表示當前的事例,整句語句表示將當前事例的屬性x和y打印出來。
例子3:
#import<Foundation/Foundation.h>
#import"rpoint.h" //導入類所在的文件
#import"spoint.h"? //導入結構體所在的文件
void process(RPoint* rp3, SPoint sp3); //函數process的聲明
int main(int argc, const char * argv[]){ //入口函數
??? @autoreleasepool{
????? RPoint* rp1=[[RPoint alloc]init]; //生成一個RPoint類的對象rp1
????? rp1.x=10;
????? rp1.y=20;
????? [rp1 print]; //顯示結果為10, 20
????? SPoint sp1; //生成一個SPoint結構體實例。
????? 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
??? }
??? return0;
}
void process(RPoint* rp3, SPoint sp3){
? rp3.x++;
? rp3.y++;
? sp3.x++;
? sp3.y++;
? [rp3 print];
? NSLog(@"[%d,%d]",sp3.x, sp3.y);
}
RPoint* rp1=[[RPoint alloc] init]; //在內存??臻g創建了一個名字為rp1的RPpoint類實例對象。[]為調用符號,調用也可以稱為發送消息。alloc的用處是手動在堆空間申請內存空間,init為初始化所分配的空間。
堆(heap):堆空間用于存儲引用類型對象,由程序員手動申請內存空間,釋放由運行時ARC機制自動釋放,函數之間通過拷貝引用(指針)傳遞。堆空間具有全局性,總體大小受制于系統內存整體大小。
由于rp1是一個類的實例對象,所以無論是復制還是傳參,都是由一個棧中的指針指向堆中的實體對象,復制副本和傳參參數的改變都能直接導致rp1指向的實際對象發生改變。
SPoint sp1; //在內存的堆空間創建了一個名字為sp1的結構體。
棧(stack):??臻g用于存儲值類型,無ARC負擔,由系統自動管理,以執行函數為單位。棧的空間大小在編譯時確定(根據參數+局部變量來計算),在函數執行時由系統自動分配一個棧,函數執行結束系統立即自動收回該棧空間,函數之間通過拷貝值傳遞。棧空間具有局部性,大小有限額(編譯工具可以設定棧的大小,一般為1M),超出會棧溢出(stack overflow)。
由于sp1是一個結構值,它存儲于棧中,無論復制還是傳參,都將在棧中復制一個sp1的副本,sp1的原始值保持不變。
第三天視頻課程:
OC類的類型成員
OC類所包含的類型成員(Type Member)主要分為兩大類,一類是數據成員(data member)用來描述對象的狀態,還有一類是函數成員(function member)用來描述對象的行為。其中數據成員又有實例變量(instance variable)和屬性(property)兩種。函數成員分為方法(method)、初始化器(init)和析構器(dealloc)三種。
認識屬性
屬性表達實例狀態,描述類型對外接口。相比直接訪問實例變量,屬性可以做更多控制。
默認情況下,編譯器會為屬性定義propertyName自動合成一個getter訪問器方法:propertyName、一個setter訪問器方法:setPropertyName還有一個實例變量_propertyName。
舉例:
在OC類中聲明一個屬性:@property NSString* firstName; //聲明一個類所包含的屬性。
此時編譯器會自動生成類似如下代碼,但是并不顯示出來:
-(NSString*)firstName{? //生成一個getter函數
???return _firstName;
}
-(void)setFirstName:(NSString *)newValue{? //生成一個setter函數
??_firstName=newValue;
}
NSString* _firstName; //創建一個以 _(下劃線)+屬性名的實例變量
由于編譯器自動生成了類似如上代碼,我們可以用編譯器生成的getter方法和setter方法來訪問或修改該屬性的內容。
例如:
Employee* employee=[[Employee alloc] init];
[employee setFirstName: @"Tom"]; //將employee類的FirstName屬性設置為“Tom”,setFirstName這個setter方法是編譯器自動生成的。
NSLog(@"First Name: %@", [employee firstName]); //打印出employee類的FirstName屬性,此處[employee firstName]是調用了編譯器自動生成的getter方法firstName。
除了以上的方法來訪問getter和setter方法外,可以用類名+.(點表達式)+屬性名來訪問屬性的getter和setter方法。
例如:
employee.lastName=@"Chen"; //訪問了lastName屬性的setter表達式,等價于[employee setFirstName: @"Chen"];
NSLog(@"First Name: %@", employee.lastName); //訪問了lastName屬性的getter表達式,等價于NSLog(@"First Name: %@", [employee firstName]);
兩種方法在本質上沒有什么區別,推薦用點表達式,使用比較方便。
可自定義訪問器方法,也可更改訪問器方法名或實例變量名。
例如:
@property (readonly) NSString* fullName; //在頭文件中定義了一個只讀屬性fullName
-(NSString *)fullName{
? return [NSString stringWithFormat:@"%@ %@", self.firstName, self.lastName];
} //在主文件中自定義了fullName屬性的getter訪問器方法。stringWithFormat函數是用來鏈接兩個字符串類實例。
@property (getter=GivenName, setter=GivenName:) NSString* firstName; //聲明firstName屬性時將該屬性的getter、setter訪問器的名字都設置為GivenName。
@synthesize firstName=givenName; //在主文件中將firstName屬性由系統自動生成的_firstName實例變量改名為givenName。
可以使用靜態全局變量(C語言)+類方法,模擬類型屬性。
static int _max=100; //在主文件中定義一個靜態變量_max
+(int)max; //在頭文件中聲明一個類方法(getter訪問器) +(int)max
+(void)setMax:(int)newValue; //在頭文件中聲明一個類方法(setter訪問器)? +(void)setMax
+(int)max{
return _max;? //在主文件中實現類方法+(int)max
}
+(void)setMax:(int)newValue{
????? _max=newValue;? //在主文件中實現類方法+(void)setMax
}
完成以上步驟后,可以實現訪問類型屬性。
[Employee setMax:400]; 等同于Employee.max=400; 模擬出來的類屬性。
實例變量
可以定義實例變量,而不定義屬性(這樣外部不能訪問到實例變量),只有實例變量,沒有類變量(使用靜態全局變量+類方法可以模擬出類變量的效果)。
如果同時自定義了getter和setter訪問器方法,或者針對只讀屬性定義了getter訪問器方法,編譯器將不再合成實例變量。
引用類型的實例變量在類外一律使用屬性來訪問,類內大多也通過self使用屬性訪問。只有以下情就卡了況使用實例變量來訪問:1、初始化器 init? 2、析構器 dealloc 3、自定義訪問器方法。?
實例變量的生存周期
實例變量的儲存:跟隨對象實例存儲在堆上。
值類型實例變量直接“內嵌”在對象實例中。跟隨對象實例內存釋放而被釋放。
引用類型實例變量通過指針“引用”堆上的引用類型實例,ARC針對引用進行計數管理,自動釋放引用計數為0的對象。
屬性的描述特性(Attribute)
屬性的描述特性可以指定屬性不同環境下的不同功能。
讀寫特性,默認情況屬性都是可讀寫(readwrite)的,也可以設置為只讀(readonly)屬性。
例如:
@property (readonly) NSString* fullName; //在頭文件中定義了一個只讀屬性fullName
多線程特性,默認情況下是屬性是原子性(atomic)的,表示多線程時原子性屬性不能被線程搶走,要么沒有運行,開始運行了必須運行到結束。也可以設置為非原子性(nonatomic).
例如:
@property (nonatomic) NSString* fullName; //在頭文件中定義了一個非原子性屬性fullName
內存管理特性
引用屬性默認為強(strong)引用屬性,弱(weak)引用阻止循環引用。當兩個實例對象屬性相互強引用時會形成循環引用,這時ARC內存管理將視這兩個互引用的屬性都占用,無法將這兩個實例的引用屬性釋放。
為了避免上述情況發生把其中一個對象屬性設置為弱引用,這樣就不會出現不能釋放的問題了。
拷貝(copy)屬性創建獨立拷貝,當引用屬性不想被外界直接引用時,可以用拷貝屬性,讓外界引用一個拷貝副本,來確保原始數據不會被外界修改。
第四天視頻課程:
認識方法 Method
代碼段上的可執行指令序列就是函數,函數有全局函數(C語言函數),和成員函數(OC中也叫方法)。
方法是類的成員函數,表達實例行為或類型行為。
舉例:
以下聲明了4個方法
-(void) print; //-表示是實例方法,返回值是void, 方法名是print, 無參數
-(BOOL) isEqualToPoint: (BLNPoint*) point; //返回值是BOOL, 參數名是point, 參數類型是BLNPoint*
-(void) moveToX: (int)x toY: (int)y;? //有兩個(int)類型的參數x和y
+(BLNPoint*) getOriginPoint; //+表示getOriginPoint是一個類方法
所有方法默認為公有方法。沒有private或protected方法(可以在接口文件中不要聲明方法,而在實現文件中實現方法來做到類似private方法)。
動態消息發布:方法調用通過運行時動態消息分發實現,在對象上調用方法又稱“向對象發送消息”。
例子:
[p1 print]; //實例p1調用了print方法,也可以說向對象p1發送了一個print消息
[p1 moveToX:100 toY:200]; //向對象p1發送了一個moveToX消息,其中還包含了兩個參數,100和200
BLNPoint* origin=[BLNPoint getOriginPoint]; //向BLNPoint類發送了一個getOriginPoint消息,返回值賦值給BLNPoint的類型實例origin
實例方法或類型方法
實例方法用來表達實例的行為所以只能通過實例對象來調用,實例方法在內部實現時可以訪問實例成員包括實例屬性、實例變量和實例方法。也可以訪問類型方法和靜態變量。
類方法用來表達類的行為只能通過類來調用,類型方法在實現時可以訪問類型方法和靜態變量,不能訪問實例成員包括實例屬性、實例變量和實例方法。
編譯器背后對實例方法和類方法的不同處理:self指針
例子:
實例方法實現
-(void) print{
????? NSLog(@"[%d, %d]", self.x, self.y);
}
上面的實例方法實現中self實際上是一個隱藏的指針參數,用來傳遞當前實例地址,編譯器編譯后用C語言的表達方式可以寫成:
void print(BLNPoint* self){
????? NSLog(@"[%d, %d]", self.x, self.y);
}
調用該方法時:[p1 print]; //print(p1);
類方法實現
+(BLNPoint*) getOriginPoint{
????? BLNPoint* origin=[[BLNPoint alloc] init];
????? origin.x=0;
????? origin.y=0;
????? return origin;
}
上述的類方法實現編譯后用C語言表示為
BLNPoint* getOriginPoint(){
????? BLNPoint* origin=[[BLNPoint alloc] init];
????? origin.x=0;
????? origin.y=0;
????? return origin;
}
類方法里面不能用self關鍵字來訪問實例變量,但是依然可以使用self關鍵字,這時self關鍵字用來表示當前類。在類方法實現里面可以用[self process]來調用當前類中的process方法,也等同于[BLNPoint process]。
方法參數
如果參數類型是值類型,為傳值方式,如果參數類型為引用類型,則為傳指針方式。
方法可以沒有參數,也可以沒有返回值。
如果方法有參數,方法名約定包含第一個參數,從第二個參數開始需要顯示提供外部參數名。
-(void) moveToX: (int)x toY: (int)y;?//toY是第二個參數的外部參數名,x是第一個內部參數名,y是第二個內部參數名。
調用時,第一個參數名忽略,但后面的參數名必須顯示標明。如: [p1 moveToX:100 toY:200];
動態方法調用機制--消息分發表
在OC里所有的對象類型都可以聲明為id類型
例子:
id obj=[[BLNPoint alloc] init];
[obj moveToX:50 toY:60];
[obj print];
這里類型為id的對象obj可以調用類型為BLNPoint的對象方法,是由于OC的動態調用機制造成的。
OC中調用所有方法都會通過消息分發表,上圖灰色的部分其中有一個指針,指向class再指向method list。這樣做可以增加靈活性,支持在運行時向方法表添加新的方法。缺點是每次調用方法都要多次尋址有性能損失。
第五天視頻課程:
初始化器與析構器
初始化器和析構器是類型的特殊函數成員,初始化器用于初始化對象實例或者類型。
對象初始化器:-(id)init 可以重載多個。
例子:
在頭文件中可以聲明多個有不同參數列表的-(id)init函數。
-(id)init;
-(id)initWithName:(NSString *)name;
-(id)initWithName:(NSString *)name WithPages:(int)pages;
-(id)initWithName:(NSString *)name WithPages:(int)pages WithCategory:(NSString *)category;
在主文件中實現聲明的init方法
-(id)init{
????? self = [super init];
????? if(self){? //如果父類初始化失敗self指針將等于Null
??????? NSLog(@"Book Object init“); //在調用了父類的init方法后可以添加自己所需的內容。
????? }
????? return self;
}
-(id)initWithName:(NSString *)name WithPages:(int)pages{
????? return [self initWithName:name WithPages:pages WithCategory:@"General"];
}
-(id)initWithName:(NSString *)name WithPages:(int)pages WithCategory:(NSString *)category{ //由于該初始化器的參數是最多的,作為主初始化器,其他參數少的初始化器可以直接調用主初始化器。
????? self = [super init];
????? if(self){
????? _name = [name copy]; //在初始化器中使用實例變量而不要使用實例屬性。
????? _pages = pages;
????? _category = [category copy];
????? }
????? return self:
}
初始化對象實例時,init通常和alloc搭配使用。
Book *b1 = [[Book alloc]init]; //alloc是一個從NSObject繼承過來的類方法。Tips: 按住commond健再點擊關鍵詞可以查看其類庫。
Book *b1 = [[Book alloc]init]; 也可以拆分為:
Book *b1 = [Book alloc];
b1 =[b1 init]; //這里等號左邊的b1是不可以省略的,因為[b1 init]有一個返回值,是返回一個地址,OC在這里返回的地址有可能和上一行Book *b1 = [Book alloc];其中的b1地址不一樣。
alloc所做的事情--NSObject已實現:1、在對上分配合適大小的內存。 2、將屬性或者實例變量的內存置0。
init所做的事情--可以自定義:1、調用父類初始化器[super init](前置調用)。 2、初始化當前對象實例變量。
Book *b1= [Book new]; //new相當于調用 alloc/init的無參數版本,不能傳遞參數。
類初始化器
類型初始化器:+(void)initialize 只能有一個,負責類型級別初始化,初始化類里面的靜態變量。
initialize在每個類使用之前被系統自動調用,且每個進程周期中只被調用一次。
子類的initalize會自動調用父類的initialize(前置調用)。
+(void)initialize{
????? if(self ==[Book class]){ //判斷該類是否是Book類
??????????? NSLog(@"Book Class initialize");
????? }
}
對象析構器
對象析構器 -(void)dealloc 只能有一個,用于釋放對象(沒有類型析構器)擁有的動態資源,無返回值。
-(void)dealloc{
自動調用:1.ARC將對象屬性引用計數減持(-1)
手動實現:2.釋放不受ARC管理的動態內存,如malloc分配的內存,關閉非內存資源,如文件句柄、網絡端口
自動調用:3.父類dealloc
}
dealloc由ARC根據對象引用計數規則,在釋放對象內存前自動調用,無法手工調用。
子類的dealloc會自動調用父類的dealloc(后置調用)。
第六天視頻課程:
認識面向對象
封裝 encapsulation:隱藏對象內部實現細節,對外部僅提供公共接口訪問。
繼承 inheritance:一個類型在另外類型基礎上進行的擴展實現。每一個類只能有一個基類,子類自動繼承基類的:實例變量、屬性、實例方法、類方法。NSObject類是所有類的根類,所有類向上追溯最上面的類都是NSObject類。繼承有兩層含義:1、成員復用,子類復用基類的成員。所有的成員都會被繼承,就算是私有成員也被繼承,只是成員訪問不到。2、類型抽象,將子類當作父類來使用。
例子:
建立一個類Shape
@interface Shape : NSObject{ //Shape類繼承于NSObject Tips:按住option鍵點擊關鍵字可以看到相關的參考文檔。
????? @public int _data; //定義了一個公開的實例變量
}
????? @property int no;
????? -(void)draw;
????? +(void)process;
@end
建立一個Shape的子類Circle
@interface Circle:Shape //Circle類繼承了Shape類
@property int radius; //Circle類自己的實例屬性
@end
由于Circle繼承了Shape類,所以Shape類的實例變量、屬性、實例方法、類方法Circle類都可以使用。
Circle* circle=[[Circle alloc]init];
circle.no=200;
circle->_data++; //在訪問父類實例變量時用->來訪問
[circle draw];
[Circle process]; //Circle類也可以調用父類的類方法。
第七天視頻課程:
多態 polymorphism:不同類型針對同一行為接口的不同實現方式。
對比重寫(override)與重載:子類重寫父類同名同參數方法,子類只可以重寫父類方法。方法名相同、參數不同,OC不支持方法的重載(重載指的是方法名相同,參數不同。用不同的參數傳遞給相同名字的方法,達到不同的運行效果)。
在子類的代碼中,可以使用super類調用基類的實現,self具有多態性,可以指向不同子類,super沒有多態性,僅指向當前父類。
例子:
建立一個Shape的子類Rectangle
@interface Rectangle:Shape
@property int width;
@property int length;
@end
基類Shape中有一個實例方法move:
-(void)move{
????? NSLog(@"Shape object move");
????? [self draw];
}
針對Rectangle類增加了兩個屬性(width和length)重寫(override)繼承過來的方法
@implementation Rectangle
-(id)init{
????? self = [super init];? //重寫時子類初始化器中必須首先調用基類的初始化器
????? if(self){
??????????? _length = 10;
??????????? _width = 20;
????? }
????? return self;
}
-(void)draw{
????? NSLog(@"Rectangle object draw: length=%d, width=%d", self.length, self.width);
}
-(void)print{
????? NSLog(@"Rectangle Instance variable %d", _data);
}
+(void)process{
????? NSLog(@"Rectangle class process");
}
-(void)dealloc{
?????? NSLog(@"Rectangle dealloc");
}
當實例對象調用時,Rectangle實例將調用重寫的方法。Rectangle實例調用-(void)move時將調用父類Shape類的實例方法-(void)move,此方法中調用的draw方法[self draw],此時self有多態性,將調用Rectangle實例中的draw方法。
對于父類中的屬性也可以在子類中重寫。
父類Shape中的屬性:@property int no;
在子類Rectangle中重寫:
-(int)no{
????? return super.no;? //super表示調用父類的方法
}
-(int)setNo:(int)no{
????? super.no = no;
}
屬性的重寫本質上就是getter和setter兩個訪問器的重寫。
繼承中的init和dealloc
初始化器 init:子類自動繼承基類的初始化器,子類也可以重寫初始化器,重寫時子類初始化器中必須首先調用基類的初始化器(手工調用)再添加其他內容。
析構器 dealloc:子類可以選擇繼續繼承基類析構器,或者重寫基類析構器,子類析構器執行完畢后,會自動調用基類析構器(后置調用,且不支持手工調用)。子類析構器自動具有多態性。
Tips:盡量避免在父類init和dealloc中調用子類重寫的方法。