iOS徹底自學手冊——[三]Objective-C語言速成,利用Objective-C建立本身的對象

1.前言

上一篇已經介紹了App Delegate、View Controller的基本概念,除此以外,分別利用storyboard和純代碼建立了第一個Xcode的工程,並對不一樣方式搭建項目進行了比較。這一篇文章,我準備爲你們介紹一下奇葩的Objective-C語言的語法和使用。這篇文章主要講Objective-C的理論概念。ios

2.學習目標

2.1 Objective-C語言面向對象的特性

與C++類比學習,聲明定義一個MyObject類,並建立這個類的實例。ajax

2.2 Objective-C語言常見語法概念概覽

介紹類,方法,分類,擴展,協議,消息機制,動態特性selector,block,ARC等概念編程

本文關於Objective-C整理總結的內容比較多,不須要徹底掌握,混個眼熟就成。 網絡

3.奇葩的Objective-C語言

1)對於已經有開發經驗的開發者而言,初次接觸Objective-C的時候以爲根本不能接受,甚至有種畏懼,oc奇葩的語法簡直顛覆了本身的世界觀,由於出現了莫名其妙的符號,如, + - [ ]。可是,不要怕,咱們慢慢來了解這究竟是毛線。從名字便可看出,Objective-C是一個面向對象的編程語言,也就是它具備繼承、封裝、多態的語言特性。閉包

2)那對於沒有任何編程語言的童鞋,如何理解面向對象的編程呢?框架

對於沒有任何編程經驗的童鞋,我想這樣解釋,要弄清楚兩個問題:i)編程到底在作什麼?ii)什麼是面向對象的編程?異步

i)通俗的語言講,編程就是在和計算機說話,用計算機明白的語言跟他交流,讓他幫你作事。(就好像你跟外國人交流同樣,你想跟人家交朋友,總得用人家懂得語言吧~)編程語言

ii)「面向對象編程」,首先只是一個名詞,實質是一種抽象的概念,主要是將一類待處理的數據及數據對應的操做集合化,寫(封裝)在一塊兒。好比,一我的的屬性有,身高、體重、年齡、性別,人能夠作的事情有吃飯、睡覺、打豆豆~全部具備這些特徵的均可以稱之爲人這一類。這即是面向對象的編程。(若是看到這裏還不明白,那仍是要多寫程序,慢慢接受了新的概念有了固化的思惟模式,就能理解並應用了函數

學習的時候能夠多種語言比較學習。我選擇用C++和Objective-C進行類比,來聲明定義一個MyObject類,比較異同點,瞭解各自面向對象編程的方法。(爲什麼選擇C++?由於,1.C++有指針的概念;2.C++是面向對象的語言。另,指針就是內存的地址。)學習

3.1 先看一下C++中聲明定義類是如何編寫的

3.1.1 C++程序源碼

#include <iostream>
using namespace std;
class MyObject {
public:
    MyObject();
    ~MyObject();
    void display();
    void setValue(int x);
    int getIndex();
    int getKey();
    static void classMethod();
private:
    int index;
    int key;
};

MyObject::MyObject() {
    cout<<"construct"<<endl;
}

MyObject::~MyObject() {
    cout<<"destruct"<<endl;
}

void MyObject::setValue(int x) {
    this->index = x;
    this->key = 1<<(x+2);
}

void MyObject::display() {
    cout<<"index == "<<this->index<<endl;
    cout<<"key == "<<this->key<<endl;
}

int MyObject::getKey() {
    return this->key;
}

int MyObject::getIndex() {
    return this->index;
}

void MyObject::classMethod() {
    cout<<"this is static (or class) method"<<endl;
}

int main(int argc, const char * argv[]) {
    // insert code here...
    std::cout << "Hello, World!\n";
    
    MyObject *object = new MyObject();
    MyObject::classMethod();
    object->setValue(3);
    object->display();
    int c = object->getIndex() + object->getKey();
    object->MyObject::~MyObject();
    printf("c = %d\n", c);
    
    MyObject object2;
    object2.setValue(2);
    object2.display();
    
    return 0;
}

3.1.2 控制檯打印信息

Hello, World!

construct

this is static (or class) method

index == 3

key == 32

destruct

c = 35

construct

index == 2

key == 16

destruct

Program ended with exit code: 0

3.1.3 代碼解釋:

以上代碼利用C++聲明定義了MyObject類

1)定義了公有方法方法

  • 構造函數MyObject()
  • 析構函數~MyObject()
  • 打印index和key的方法display()
  • 設置私有變量方法setValue(int x)
  • 得到index值方法getIndex()
  • 得到key方法getKey()
  • 類方法classMethod()

2)定義了私有成員變量

  • index和key

3.1.4 特別注意

  • 構造函數是建立實例時調用的,在這個方法中能夠爲實例進行初始化操做;
  • 析構函數是銷燬實例對象是被系統調用的,這裏能夠將對象中的指針對象都delete掉;
  • 不論是否使用new關鍵字建立對象,系統都會自動調用構造函數和析構函數,構造函數實質是爲對象在內存分配一個空間,存儲這個對象的數據,而析構函數則是將這個對象佔用的內存釋放掉
  • 使用new關鍵字建立類的實例時,系統在堆內存爲該實例動態分配了一塊內存區域(實際上等同於調用的C語言的malloc方法爲變量動態分配內存),調用對象的方法(或成員變量)使用「->」符號;
  • 不使用new關鍵字建立類的實例時,系統在棧內存爲該實例分配一塊內存區域,調用對象的方法和變量須使用「.」符號。
  • 類方法是靜態方法,是在類裝載的時候裝載的。(可是要特別注意,類的靜態變量是該類的對象所共有的,便是全部對象共享變量。因此建議儘可能少用靜態變量。儘可能在靜態方法中使用內部變量。)

 

3.2 再看一下Objective-C是如何定義一個MyObject類的

3.2.1 建立新項目

選擇OS X中的Command Line Tool,建立好工程後,按住command+N鍵建立Cocoa Touch Class,建立一個名爲MyObject的NSObject的子類。

這樣就建立了OS X的命令行程序,以用於簡單演示如何聲明定義Objective-C的類

3.2.2 程序源碼

1)main.m文件:

#import <Foundation/Foundation.h>

#import "MyObject.h"


int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // insert code here...
        NSLog(@"Hello, World!");
        MyObject *object = [[MyObject alloc] initWithIndex:3];
        [object display];
//        object = nil;
    }
    return 0;
}

 2) MyObject.h

#import <Foundation/Foundation.h>

@interface MyObject : NSObject
- (instancetype)initWithIndex:(NSInteger)index;
- (void)display;
+ (void)classMethod;
@end

3) .m文件

#import "MyObject.h"

@interface MyObject()
@property (assign, nonatomic) NSInteger index;
@property (assign, nonatomic) NSInteger key;

@end

@implementation MyObject
@synthesize index = _index;
@synthesize key = _key;

- (instancetype)initWithIndex:(NSInteger)index {
    self = [super init];
    if (self) {
        NSLog(@"init");
        self.index = index;
    }
    return self;
}

- (void)setIndex:(NSInteger)index {
    _index = index;
    self.key = 1<<(index+2);
}

- (NSInteger)index {
    return _index;
}

- (NSInteger)key {
    return _key;
}

- (void)display {
    NSLog(@"index == %ld \n key == %ld", (long)_index, (long)_key);
}

+ (void)classMethod {
    NSLog(@"this is static (or class) method");
}

- (void)dealloc {
    NSLog(@"dealloc");
}
@end

 

3.2.3 控制檯打印結果

2016-01-10 17:55:50.401 CompileOC[20792:1052939] Hello, World!

2016-01-10 17:55:50.402 CompileOC[20792:1052939] init

2016-01-10 17:55:50.402 CompileOC[20792:1052939] index == 3 

 key == 32

2016-01-10 17:55:50.402 CompileOC[20792:1052939] dealloc

Program ended with exit code: 0

3.2.4 代碼解釋

1) 在main.m文件中建立了MyObject的實例並調用display方法。

2)在頭文件中聲明瞭三個公有方法,

- (instancetype)initWithIndex:(NSInteger)index;

- (void)display;

+ (void)classMethod;

3).m文件中

  • 建立了一個MyObject類的擴展,在擴展中聲明瞭兩個NSInteger的屬性,屬性的特性爲assign和nonatomic
  • 重寫了父類中的實例(減號)init方法,併爲index屬性賦值
  • 實現了index屬性的setter方法
  • 實現了index和key屬性的getter方法
  • 建立了打印index和key的方法

    - (void)display

  • 建立了類方法

    + (void)classMethod;

  • 重寫了父類的dealloc方法

     

4)與C++代碼作對比

  • 在C++中能夠建立沒有父類的類,可是在Objective-C中建立的類都繼承自NSObject或其子類。不選擇父類的話是不能建立Objective-C的類的。
  • 在C++中調用方法或變量用「->」或「.」符號,而在Objective-C中調用方法是[],並且順序不是從左至右,而是從內到外。
  • 在C++中只有當new來建立對象時,纔會動態分配內存;
  • 而在Objective-C中建立的全部類的實例對象都是動態分配的,從建立Objective-C實例的方式
    類名 *實例名 = [[類名 alloc] init]; 
    也能夠看出,Objective-C建立實例都是動態分配內存,實際上也是等同於調用的C語言的malloc方法爲變量動態分配內存
  • 即在Objective-C中全部的對象都是指針。
  • 在C++中聲明公有方法用public關鍵字代表, 私有方法用private代表。而在Objective-C中聲明公有方法只要在頭文件的

    @interface

    @end
    之間就能夠了,而私有方法不須要在頭文件中聲明。

  • 補充,在Objective-C中可使用@private @public @protected @package 編譯器指令來設置實例變量的訪問限制。
    實例變量的聲明方式爲,

    @implementation {
            @private
                  int index;
                  int key;
    }
    @end
      

  • 注意實例變量和類的屬性並非一回事,屬性和實例變量的本質區別是,屬性沒法直接訪問對象的內部狀態,可是能夠利用setter和getter方法進行訪問,而在setter和getter方法中還能夠包含其餘邏輯,好比,本文章中

    - (void)setIndex:(NSInteger)index
    方法,不單爲實例變量
    _index賦值,還用setter的簡潔方式

    self.key = 1<<(index+2);
    爲實例變量_key賦值。

  • 那_index和_key實例變量是誰聲明的呢?這是Xcode編譯器本身生成的:

    @synthesize index = _index;

    @synthesize key = _key;
    上面的代碼使用了 @synthesize 編譯指令,讓編譯器分別爲屬性index和key自動建立實例變量_index和_key。即,自動爲屬性建立對應實例變量的方法是 
    @synthesize 屬性名 = 實例變量名;
    事實上,當沒有實現setter方法時,@synthesize指令不寫,編譯器也能夠爲屬性自動建立實例變量,編譯器自動爲屬性建立的變量名默認爲_屬性名,在調試的時候能夠設置變量觀察。

  • 注意本程序是在Xcode7中建立運行的,Xcode5以後建立工程時就默認ARC模式了,如今基本項目都是自動引用計數的模式編寫的。老項目中的使用手動管理的須要作以下的設置:
    在Targets的Build Phases選項下Compile Sources下選擇要不使用ARC編譯的文件,雙擊它,輸入-fno-objc-arc便可
  • 在項目開發中,我認爲屬性遠比實例變量好用,大能夠只聲明屬性,而不聲明實例變量。

4.Objective-C語言常見語法概念

4.1 基本語法概念

4.1.1類元素

1)實例變量

聲明方法:

@implementation {
       //聲明實例變量
}
@end

實例變量是是類封裝的數據

2)屬性

@property (特性) 類型 屬性名;

屬性是Objective-C提供的訪問對象共享狀態的機制,編譯器能夠自動生成訪問實例變量的方法。Xcode自動建立的實例變量默認變量名爲「_屬性名」

3)類的聲明(接口)

@interface MyObject : NSObject

@end

通用的以下:

@interface 類名 : 父類名

@end

說明:

  • @interface 和 @end 指令之間的區域只能聲明屬性或方法,不能有具體的實現過程。
  • 若是在頭文件中的接口聲明的屬性或方法,其實就是公有的屬性和方法,能夠直接訪問。

4)類的實現

@implementation {

//實例變量的聲明

}

//屬性和方法的定義

@end

說明:

  • 方法的實現過程必須寫在 @implementation 和 @end 之間

5)類的方法:分爲類方法和實例方法

  • 聲明:

    [+或-](返回類型)方法名:(參數類型)參數1 參數名:(參數類型)參數2 ... 參數名:(參數類型)參數n;
  • 實現:

    [+或-](返回類型)方法名:(參數類型)參數1 參數名2:(參數類型)參數2 ... 參數名n:(參數類型)參數n {

                  //code here

                 return 返回值;

         }

  • 調用:
    [消息接收者(即類的實例對象) 方法名:1  參數名2:2 ...  參數名n:n];

說明:

  • 方法的聲明其實就是方法部分實現除了{}之外的代碼Objective-C的方法定義方式很奇葩,剛開始很差接受,這實際上是Objective-C消息機制一種體現。
  • 在其餘語言中,方法的書寫方式是函數式的,相似於數學中的函數f(x,y,z)。
  • 在Objective-C中調用方法時向消息的接收者(類的實例對象)發送一條消息,書寫形式爲[消息接收者 消息方法名:參數1 參數名2:參數2],全部調用方法都是動態的,根據發出消息的方法名來動態查找對應的指針。感興趣的話能夠參考羅朝輝大神的博客 http://blog.csdn.net/kesalin/article/details/6689226

    「不一樣的類能夠擁有相同的 selector,這個沒有問題,由於不一樣類的實例對象performSelector相同的 selector 時,會在各自的消息選標(selector)/實現地址(address) 方法鏈表中根據 selector 去查找具體的方法實現IMP, 而後用這個方法實現去執行具體的實現代碼。這是一個動態綁定的過程,在編譯的時候,咱們不知道最終會執行哪一些代碼,只有在執行的時候,經過selector去查詢,咱們才能肯定具體的執行代碼。」

  • 多參數的聲明、實現和調用。例如,我在MyObject中定義一個多參數的請求方法:

    - (void)requestDataWithIndex:(NSInteger)index forKey:(NSInteger)key completion:(void(^)(NSError *error, id response))completion {    //code here
        NSError *responseError = nil;//assume it is responsed from server
    
        NSDictionary *responseData = @{@"message": @"OK"};
    
        if (completion != nil) {
    
            if (responseError != nil) {
    
                completion(responseError, nil);
    
            } else {
    
                completion(nil, responseData);
    
            }
    
        }
    
    }

     

說明:

  • - (void)requestDataWithIndex:(NSInteger)index forKey:(NSInteger)key completion:(void(^)(NSError *error, id response))completion直接能夠複製到@interface 和 @end 之間做爲方法的聲明
  • 注意方法中第三個參數是block變量,這個後面會稍做介紹,先了解概念混個眼熟,之後慢慢細琢磨。
  • block是一塊代碼塊,實質是閉包,程序運行時,block{}內的不會當即執行,即非順序執行的,是異步執行的,因此最適合選爲作網絡異步請求的回調函數,功能相似於ajax。

 

 

6)類的擴展

其實就是寫在類.m文件中的接口,只不過與類.h中接口相比,在.m文件中聲明的實例變量、屬性、方法外部不能訪問,即實現了方法、屬性的私有化。例如MyObject.m文件中

@interface MyObject()

@property (assign, nonatomic) NSInteger index;

@property (assign, nonatomic) NSInteger key;

@end

這段代碼就是擴展,而且聲明的index和key屬性外部不能訪問,只能在類的內部訪問。

7)分類

分類的目的主要是爲了擴展類的方法,好比MyObject類我添加一個NY的分類,建立方法以下:

  • command+N 選擇iOS Source中的Objective-C File,Next
  • 填寫File(分類名),選擇category,class 填寫MyObject,而後點Next,再點Create建立
  • 頭文件:
#import "MyObject.h"

@interface MyObject (NY)

@end
  • 實現文件
#import "MyObject+NY.h"

@implementation MyObject (NY)

@end

說明:

  • 分類中只能夠聲明和定義方法,不能夠聲明類接口屬性或實例變量。由於已經引用了原來類的頭文件會報重複聲明類的語法錯誤。

/Users/.../CompileOC/CompileOC/MyObject+NY.m:10:1: Duplicate interface definition for class 'MyObject'

 

8)協議

  • 協議的聲明定義

@protocol NYProtocol <NSObject>

@optional

- (void)nyOptionalProtocalMethod;

@required

- (void)nyRequiredProtocalMethod;

@end

通用:

@protocol 協議名 <NSObject>

@optional

//可選擇實現的方法

@required

//必須實現的方法

@end

  • 類聽從協議的方法

@interface MyObject() <NYProtocol>

@property (assign, nonatomic) NSInteger index;

@property (assign, nonatomic) NSInteger key; 

@end

說明:

  • 協議的定義能夠寫寫在類的頭文件中也能夠寫在類的實現文件中,或者也能夠建立一個單獨的協議頭文件,可是必需要寫在聽從該協議的類接口以前,不然會報語法錯誤
  • @optional指令標明的方法,聽從該協議的類不需要實現,但@required指令標明的必須實現
  • 只有類的接口後面能夠跟<協議> @interface MyObject() <NYProtocol>
  • 協議方法相似於C++中的虛函數,不須要具體實現,只需聲明方法名,在子類中實現具體方法。協議方法實際是爲了實現C++多繼承的目的,子類能夠根據須要實現方法,好比,tableview的delegate協議和datasource協議,根據具體的UI須要來實現對應的協議方法。

4.2 ARC(Automatic Reference Counting)自動引用計數

ARC是Objective-C提供的內存管理方式,系統根據對象被引用的次數計次,當引用計數爲零時,自動釋放該對象所佔有的內存。而非系統自動管理內存的方式,稱爲MRR(Manual Retain Release),老的項目代碼中仍會有手動管理內存的代碼。你會看到 retain 、 release、 autorelease 、dealloc這樣的消息(方法)代碼,以用來管理內存。可是在ARC模式下並不能手動編寫這些代碼,當引用一個對象時,即這個對象引用計數加一,當這個對象中某個指針,例如,本文中的 object = nil 時,引用計數減一,引用計數爲零時,系統自動釋放對象所佔用的內存。

但在Xcode5以後,建立項目時沒法限定非ARC模式,並且「基本如今開發的項目都使用ARC」,因此,對於初學者來講,手動內存管理就暫時不須要更深刻的瞭解了。但若是,但願能實現ARC與MRR的混編譯,那麼就須要在Targets的Build Phases選項下Compile Sources下選擇要不使用ARC編譯的文件,雙擊它,輸入-fno-objc-arc便可實現ARC和MRR混編。

5.小結

本篇文章主要介紹了 Objective-C 的基本語法,但願初學者讀完以後能對 Objective-C 編程語言有一點基本的瞭解,打消一部分 Objective-C 這種陌生的編程語言的畏懼感,從而能繼續往下學。

關於學習的方法,我還想說幾句。我認爲,人接觸新鮮事物總會有種恐懼感,適當的恐懼感反而會更有意思,恐懼感太強烈會阻礙接受新的概念。接受新觀念確定是須要必定時間,由於原有的觀念已經給思惟產生一個定式,新觀念可能和舊觀念有衝突的地方,但確定二者之間也存在必定聯繫。找出二者的共同點,能快速理解新概念。因此,我以爲學習一種新事物,首先要接受,慢慢的就適應了,不急~咱還年輕~

6.博文推薦

http://blog.csdn.net/kesalin/article/details/8155245

7.結語

十分感謝萌萌噠樂樂(https://medium.com/@HiSuneBear)給我提的排版建議及文章評價,讓我有更強烈的動力把文章寫好(這篇文章寫的太長,截至2016-01-11 17:41:55 我寫了快三天。因此,最後有點累,不想寫了,可是樂樂童鞋讓個人小宇宙又從新燃了😂 決心要把這篇文章拉回正軌~~)。另外,也十分感謝 @朱不鳴 @D 兩位小童鞋對文章的批評建議~我會努力把文章寫好,但願能給更多的人幫助,謝謝!

最後,預告一下接下來的文章內容:

  • 經過介紹蘋果官方文檔來介紹Cocoa Touch的框架
  • 經過介紹如何搭建項目來介紹Xcode的基本配置
  • 經過搭建一個簡單應用來熟悉UIKit中各類視圖控制器和視圖的使用

敬請期待吧😉 ~

相關文章
相關標籤/搜索