Object-C高級編程Blocks

  • 什麼是Blocks
  • 代碼塊高亮
  • 圖片鏈接和圖片上傳
  • LaTex數學公式
  • UML序列圖和流程圖
  • 離線寫博客
  • 導入導出Markdown文件
  • 豐富的快捷鍵

什麼是Blocks

可以用一句話來表示Blocks的擴充功能:帶有局部變量的匿名函數。

Blocks語法

Blocks完整表達式語法:
^返回值類型(參數列表) {
}
如果無返回值類型或者無參數可以縮寫:
^ {
}

Block類型變量

先看C語言中函數func地址賦值給函數指針類型變量funcptr:

int func(int count) {
    return count + 1;
}
int (*funcptr)(int) = &func;

在Block語法下,可將Block賦值給聲明爲Block類型的變量:

int (^funcptr)(int) = ^int(int count) {};

Block類型變量用途:

  • 自動變量
  • 函數參數
  • 靜態變量
  • 靜態全局變量
  • 全局變量

Block作爲函數參數

void func(int (^blk) (int)) {
}

Block作爲返回類型

int (^ func() (int)) {
    return ^(int count) {return count + 1;};
}

用typedef可以簡寫

typedef int (^ blk_t) (int);

通過typedef可聲明”blk_t”類型變量;void func(int (^blk) (int)) {};改寫爲

void func(blk_t blk) {};
int (^ func() (int)) {};

改寫爲

blk_t func() {};

截獲自動變量

- (void)func {
    int val = 10;
    void (^blk)() = ^{NSLog(@"%d", val);};
    val = 2;
    blk();
}

函數輸出爲10,而是執行Block語法的自動變量的瞬間值;

__block說明符

自動變量值截獲只能保存執行block語法執行的瞬間值。保存後就不能修改該值。弱想在上面的block語法中實現block內賦值,需要用到__block說明符

__block int val = 10;
void(^blk)() = ^{val = 2; NSLog(@"%d", val);};

截獲的自動變量

Block的實現

Block實質

Block是“帶有自動變量的匿名函數”,但Block究竟是什麼呢?通過clang(LLVM編譯器)具有轉換爲我們可讀源代碼的功能。通過“-rewrite-objc”選項就能將含有Block語法的源代碼變換爲C++源代碼
clang -rewrite-objc 源代碼名稱

int main() {
    void (^blk)(void) = ^{printf("Block\n");};
    blk();
    return 0;
}

通過clang可變換爲以下形式:

struct __block_impl {
    void *isa;
    int Flags;
    int Reserved;
    void *FuncPtr;
};

struct __main_block_impl_0 {
    struct __block_impl impl;
    struct __main_block_desc_0 *Desc;
    //構造函數
    __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags = 0) {
        impl.isa = &_NSConcreteStackBlock;
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
    }
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
    print("Block\n");
}
static struct __main_block_desc_0 {
    unsigned long reserved;
    unsigned long Block_size;
`} __main_block_desc_0_Data {
    0,
    sizeof(struct __main_block_impl_0)
}
int main() {
    void (*block)(void) = (void * (void))&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA);
    ((void (*) struct __block_impl *)) ((struct __block_impl *)blk) ->FuncPtr)((struct __block_impl *)blk);
    return 0;
}

先將源代碼分成幾部分逐步理解,首先看源代碼中的Block語法:

^{printf("Block\n");};

可以看到變換後的源代碼

static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
    printf("Block\n");
}

通過Block使用的匿名函數實際上被作爲簡單的c語言來處理。另外,根據Block語法所屬的函數名(此處爲main)和該Block語法在函數出現的順序值(此處爲0)來給經clang變換的函數命名。
該函數的參數__cself,爲指向Block值得變量。我們先看看該參數的聲明。

struct __main_block_impl_0 *__cself

與C++的this和OC的self相同,參數__cself是__main_block_impl_0結構體的指針。
該結構體聲明如下:

struct __main_block_impl_0 {
    struct __block_impl impl;
    struct __main_block_impl_desc_0* Desc;
}

由於轉換後的源代碼中,也一併寫入了其構造函數,所以看起來複雜,如果去除該構造函數,__main_block_impl_0結構體將變得非常簡單:

struct __block_impl {
    void *isa;
    int Flags;
    int Reserved;
    void *FuncPtr;
};

第二個成員變量爲Desc指針,__main_block_impl_desc_0結構體聲明:

struct __main_blok_impl_desc_0 {
    unsigned long reserved;
    unsigned long Block_size;
};

其成員名稱爲今後版本升級所需要的區域和Block大小。
初始化含有這些結構體的__main_block_impl_0結構體的構造函數。

__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) {
    impl.isa = NSConcreteStackBlock;
    impl.Flags = flag;
    impl.FuncPtr = fp;
    Desc = desc;
}

NSConcreteStackBlock表明該Block在棧上,我們先看看該構造函數的調用。

void(*blk)(void) = (void(*)(void)&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA);

因爲轉換較多,看起來不是很清楚,去掉轉換部分如下:

struct __main_block_impl_0 tmp = __main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA);
struct __main_block_impl_0 *blk = &tmp;

這樣就容易理解了。該源代碼將__main_block_impl_0結構體類型的自動變量,即棧上生成的__main_block_impl_0結構體實例的指針,賦值給__main_block_impl_0結構體指針類型的變量blk,以下爲對應的源代碼。

void (^blk)(void) = ^{print("Block\n");};

棧上生成的__main_block_impl_0結構體實例指針賦值給__main_block_impl_0結構體指針類型的變量blk。該源碼的Block就是__main_block_impl_0結構體類型的自動變量。
下面看看__main_block_impl_0結構體實例構造參數。

__main_block_impl_0(__main_block_func_0, &__main_block_desc_0_Data);

第一個參數是C語言函數指針。爲Block匿名函數在C語言中的體現。第二個參數爲靜態全局變量初始化的__main_bloock_desc_0結構體實例指針。以下爲初始化代碼。

static struct __main_block_desc_0 __main_block_desc_0_DATA = {
    0,
    sizeof(struct __main_block_impl_0)
};

由此可知,該源代碼使用Block,即__main_block_impl_0結構體的實例大小,進行初始化。
下面看看棧上的__main_block_impl_0結構體(即Block)是如何根據這些參數進行初始化的,如果展開__main_block_impl_0結構體的__block_impl結構體,可記爲如下形式:

struct __main_block_impl_0 {
    void *isa;
    int Flags;
    int Reserved;
    void *FuncPtr;
    struct __main_block_desc_0* Desc;
}

該結構體根據構造函數像下面這樣進行初始化。

isa = &_NSConcreteStackBlock;
Flags = 0;
Reserved = 0;
FuncPtr = __main_block_func_0;
Desc = &__main_block_desc_0_DATA;

再來看看使用該Block部分。

blk();

這部分變換爲以下源碼:

((void (*))(struct __block_impl *))((struct __block _block_impl *)blk)->FuncPtr)((struct __block_impl *)blk);

去掉轉換部分。

(*blk->impl.FuncPtr)(blk);

這就是簡單的函數指針調用函數,由Block語法轉換的__main_block_func_0函數的指針被賦值給成員變量FuncPtr中,另外也說明了__main_block_func_0函數參數__cself指向Block值

截獲自動變量

- (void)func {
    int val = 10;
    void (^blk)() = ^{NSLog(@"%d", val);};
    val = 2;
    blk();
}

代碼通過clang進行轉換。

struct __main_block_impl_0 {
    struct __block_impl impl;
    struct __main_block_desc_0 *Desc;
    int val;

    __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _val, int flags=0) : val(_val) {
        impl.isa = &_NSConcreteStackBlock;
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
    }
};
static void __main_func_0(struct __main_block_impl_0 *__cself) {
    int val = __cself->val;
    printf(val);
}
static struct __main_block_desc_0 {
    unsigned long reserved;
    unsigned long Block_size;
} __main_block_desc_0_Data = {
    0,
    sizeof(struct __main_block_impl_0)
};
int main() {
    int val = 10;
    void(*blk)(void) = &__main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA, val);
    return 0;
}

Block語法表達式中使用的自動變量被作爲成員變量追加到__main_block_impl_0結構體中。

struct __main_block_impl_0 {
    struct __block_impl impl;
    struct __main_block_desc_0 *Desc;
    int val;
    }

Block語法表達式中沒有使用的自動變量不會被追加,Block的自動變量截獲只針對Block中使用的自動變量。下面看看初始化該結構體的構造函數的差異。

__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _val, int flags=0) : val(_val) {}

使用執行Block語法時的自動變量val和初始化__main_block_impl_0結構體實例。

    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = 0;
    impl.FuncPtr = __main_block_func_0;
    Desc = &__main_block_desc_0_DATA;
    val = 10;

由此可知,在__main_block_impl_0結構體實例(Block)中,自動變量被截獲。下面再來看Block匿名函數實現

^{printf(val);}

該源代碼轉化爲以下函數:

static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
    int val = __cself->val;
    printf(val);
}

在轉換後的源碼中,截獲的__main_block_impl_0結構體實例的成員變量上的自動變量,這些變量在Block語法表達式之前被聲明定義。因此,原來的源代碼表達式無需改動便可以使用截獲的自動變量執行。

__block說明符

我們再來回顧截取自動變量的例子

^{printf(val);}

該源碼轉換結果如下:

static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
    int val = __cself->val;
    printf(val);
}

Block中使用的被截獲的自動變量就如“帶有自動變量的匿名函數”,在Block結構體實例中重寫該自動變量也不會改變原先截獲的自動變量。自動變量val雖然被捕獲進來了,但是是用 __cself->val來訪問的。Block僅僅捕獲了val的值,並沒有捕獲val的內存地址。所以在__main_block_func_0這個函數中即使我們重寫這個自動變量val的值,依舊沒法去改變Block外面自動變量val的值。
OC可能是基於這一點,在編譯的層面就防止開發者可能犯的錯誤,因爲自動變量沒法在Block中改變外部變量的值,所以編譯過程中就報編譯錯誤

int val = 0;
void (^blk)(void) = ^{val = 1;};
#import <Foundation/Foundation.h>

int global_i = 1;

static int static_global_j = 2;

int main(int argc, const char * argv[]) {

    static int static_k = 3;
    int val = 4;

    void (^myBlock)(void) = ^{
        global_i ++;
        static_global_j ++;
        static_k ++;
        NSLog(@"Block中 global_i = %d,static_global_j = %d,static_k = %d,val = %d",global_i,static_global_j,static_k,val);
    };

    global_i ++;
    static_global_j ++;
    static_k ++;
    val ++;
    NSLog(@"Block外 global_i = %d,static_global_j = %d,static_k = %d,val = %d",global_i,static_global_j,static_k,val);

    myBlock();

    return 0;
}

運行結果

Block 外  global_i = 2,static_global_j = 3,static_k = 4,val = 5
Block 中  global_i = 3,static_global_j = 4,static_k = 5,val = 4

這裏就有2點需要弄清楚了
1.爲什麼在Block裏面不加__bolck不允許更改變量?
2.爲什麼自動變量的值沒有增加,而其他幾個變量的值是增加的?自動變量是什麼狀態下被block捕獲進去的?
爲了弄清楚這2點,我們用clang轉換一下源碼出來分析分析。

int global_i = 1;

static int static_global_j = 2;

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  int *static_k;
  int val;
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int *_static_k, int _val, int flags=0) : static_k(_static_k), val(_val) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  int *static_k = __cself->static_k; // bound by copy
  int val = __cself->val; // bound by copy

        global_i ++;
        static_global_j ++;
        (*static_k) ++;
        NSLog((NSString *)&__NSConstantStringImpl__var_folders_45_k1d9q7c52vz50wz1683_hk9r0000gn_T_main_6fe658_mi_0,global_i,static_global_j,(*static_k),val);
    }

static struct __main_block_desc_0 {
  size_t reserved;
  size_t Block_size;
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};


int main(int argc, const char * argv[]) {

    static int static_k = 3;
    int val = 4;

    void (*myBlock)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, &static_k, val));

    global_i ++;
    static_global_j ++;
    static_k ++;
    val ++;
    NSLog((NSString *)&__NSConstantStringImpl__var_folders_45_k1d9q7c52vz50wz1683_hk9r0000gn_T_main_6fe658_mi_1,global_i,static_global_j,static_k,val);

    ((void (*)(__block_impl *))((__block_impl *)myBlock)->FuncPtr)((__block_impl *)myBlock);

    return 0;
}

首先全局變量global_i和靜態全局變量static_global_j的值增加,以及它們被Block捕獲進去,這一點很好理解,因爲是全局的,作用域很廣,所以Block捕獲了它們進去之後,在Block裏面進行++操作,Block結束之後,它們的值依舊可以得以保存下來。

接下來仔細看看自動變量和靜態變量的問題。
在__main_block_impl_0中,可以看到靜態變量static_k和自動變量val,被Block從外面捕獲進來,成爲__main_block_impl_0這個結構體的成員變量了。
接着看構造函數,

__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int *_static_k, int _val, int flags=0) : static_k(_static_k), val(_val)

這個構造函數中,自動變量和靜態變量被捕獲爲成員變量追加到了構造函數中。main裏面的myBlock閉包中的__main_block_impl_0結構體,初始化如下

void (*myBlock)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, &static_k, val));
impl.isa = &_NSConcreteStackBlock;
impl.Flags = 0;
impl.FuncPtr = __main_block_impl_0; 
Desc = &__main_block_desc_0_DATA;
*_static_k = 4;
val = 4;

再研究一下源碼,我們注意到__main_block_func_0這個函數的實現

static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  int *static_k = __cself->static_k; // bound by copy
  int val = __cself->val; // bound by copy
      global_i ++;
      static_global_j ++;
      (*static_k) ++;
      NSLog((NSString *)&__NSConstantStringImpl__var_folders_45_k1d9q7c52vz50wz1683_hk9r0000gn_T_main_6fe658_mi_0,global_i,static_global_j,(*static_k),val);
    }

回到上面的例子上面來,4種變量裏面只有靜態變量,靜態全局變量,全局變量這3種是可以在Block裏面被改變值的。仔細觀看源碼,我們能看出這3個變量可以改變值的原因。
1、靜態全局變量,全局變量由於作用域的原因,於是可以直接在Block裏面被改變。他們也都存儲在全局區。
2、靜態變量傳遞給Block是內存地址值,所以能在Block裏面直接改變值。
根據官方文檔我們可以瞭解到,蘋果要求我們在自動變量前加入 __block關鍵字(__block storage-class-specifier存儲域類說明符),就可以在Block裏面改變外部自動變量的值了。
總結一下在Block中改變變量值有2種方式,一是傳遞內存地址指針到Block中,二是改變存儲區方式(__block)。靜態變量的這種方法似乎也適用於自動變量的訪問。但我們爲什麼沒有這麼做呢?
實際上在由Block語法生成的值Block上,可以存儲超過其變量作用域的被截獲對象的自動變量。變量作用域結束的同時,原來·的自動變量被廢棄,因此靜態變量的地址將不能訪問。
下面我們來實際用__block說明符,來變更值

__block int val = 10;
void (^blk)(void) = ^{val = 1;};

該代碼變換後如下:

struct __Blcok_byref_val_0 {
    void *isa;
    __Blcok_byref_val_0 *__forwarding;
    int __flags;
    int __size;
    intval;
};
struct __main_block_impl_0 {
    struct __block_impl impl;
    struct __block_impl_desc_0 *Desc;
    __Block_byref_val_0 *val;
    __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_val_0 *_val, int flags=0): val(_val->__forwarding) {
        impl.isa = &_NSConcreteStackBlock;
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
    }->__forwarding->val
};
static void __main_blok_func_0(struct __main_block_impl_0 *__cself) {
    __Blcok_byref_val_0 *val = __cself->val;
    (val->__forwarding->val) = 1;
}
static void __main_block_copy_0 {

}
  • 加粗 Ctrl + B
  • 斜體 Ctrl + I
  • 引用 Ctrl + Q
  • 插入鏈接 Ctrl + L
  • 插入代碼 Ctrl + K
  • 插入圖片 Ctrl + G
  • 提升標題 Ctrl + H
  • 有序列表 Ctrl + O
  • 無序列表 Ctrl + U
  • 橫線 Ctrl + R
  • 撤銷 Ctrl + Z
  • 重做 Ctrl + Y

Markdown及擴展

Markdown 是一種輕量級標記語言,它允許人們使用易讀易寫的純文本格式編寫文檔,然後轉換成格式豐富的HTML頁面。 —— [ 維基百科 ]

使用簡單的符號標識不同的標題,將某些文字標記爲粗體或者斜體,創建一個鏈接等,詳細語法參考幫助?。

本編輯器支持 Markdown Extra ,  擴展了很多好用的功能。具體請參考Github.

表格

Markdown Extra 表格語法:

項目 價格
Computer $1600
Phone $12
Pipe $1

可以使用冒號來定義對齊方式:

項目 價格 數量
Computer 1600 元 5
Phone 12 元 12
Pipe 1 元 234

定義列表

Markdown Extra 定義列表語法:
項目1
項目2
定義 A
定義 B
項目3
定義 C

定義 D

定義D內容

代碼塊

代碼塊語法遵循標準markdown代碼,例如:

@requires_authorization
def somefunc(param1='', param2=0):
    '''A docstring'''
    if param1 > param2: # interesting
        print 'Greater'
    return (param2 - param1 + 1) or None
class SomeClass:
    pass
>>> message = '''interpreter
... prompt'''


###腳註
生成一個腳註[^footnote].
  [^footnote]: 這裏是 **腳註** 的 *內容*.

### 目錄
用 `[TOC]`來生成目錄:

[TOC]

### 數學公式
使用MathJax渲染*LaTex* 數學公式,詳見[math.stackexchange.com][1].

 - 行內公式,數學公式爲:$\Gamma(n) = (n-1)!\quad\forall n\in\mathbb N$。
 - 塊級公式:

$$	x = \dfrac{-b \pm \sqrt{b^2 - 4ac}}{2a} $$

更多LaTex語法請參考 [這兒][3].

### UML 圖:

可以渲染序列圖:

```sequence
張三->李四: 嘿,小四兒, 寫博客了沒?
Note right of 李四: 李四愣了一下,說:
李四-->張三: 忙得吐血,哪有時間寫。




<div class="se-preview-section-delimiter"></div>

或者流程圖:

Created with Raphaël 2.1.2開始我的操作確認?結束yesno
  • 關於 序列圖 語法,參考 這兒,
  • 關於 流程圖 語法,參考 這兒.

離線寫博客

即使用戶在沒有網絡的情況下,也可以通過本編輯器離線寫博客(直接在曾經使用過的瀏覽器中輸入write.blog.csdn.net/mdeditor即可。Markdown編輯器使用瀏覽器離線存儲將內容保存在本地。

用戶寫博客的過程中,內容實時保存在瀏覽器緩存中,在用戶關閉瀏覽器或者其它異常情況下,內容不會丟失。用戶再次打開瀏覽器時,會顯示上次用戶正在編輯的沒有發表的內容。

博客發表後,本地緩存將被刪除。 

用戶可以選擇 把正在寫的博客保存到服務器草稿箱,即使換瀏覽器或者清除緩存,內容也不會丟失。

注意:雖然瀏覽器存儲大部分時候都比較可靠,但爲了您的數據安全,在聯網後,請務必及時發表或者保存到服務器草稿箱

瀏覽器兼容

  1. 目前,本編輯器對Chrome瀏覽器支持最爲完整。建議大家使用較新版本的Chrome。
  2. IE9以下不支持
  3. IE9,10,11存在以下問題
    1. 不支持離線功能
    2. IE9不支持文件導入導出
    3. IE10不支持拖拽文件導入

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章