- 什麼是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>
或者流程圖:
離線寫博客
即使用戶在沒有網絡的情況下,也可以通過本編輯器離線寫博客(直接在曾經使用過的瀏覽器中輸入write.blog.csdn.net/mdeditor即可。Markdown編輯器使用瀏覽器離線存儲將內容保存在本地。
用戶寫博客的過程中,內容實時保存在瀏覽器緩存中,在用戶關閉瀏覽器或者其它異常情況下,內容不會丟失。用戶再次打開瀏覽器時,會顯示上次用戶正在編輯的沒有發表的內容。
博客發表後,本地緩存將被刪除。
用戶可以選擇 把正在寫的博客保存到服務器草稿箱,即使換瀏覽器或者清除緩存,內容也不會丟失。
注意:雖然瀏覽器存儲大部分時候都比較可靠,但爲了您的數據安全,在聯網後,請務必及時發表或者保存到服務器草稿箱。
瀏覽器兼容
- 目前,本編輯器對Chrome瀏覽器支持最爲完整。建議大家使用較新版本的Chrome。
- IE9以下不支持
- IE9,10,11存在以下問題
- 不支持離線功能
- IE9不支持文件導入導出
- IE10不支持拖拽文件導入