[toc]
## 1、block的基本概念及使用
`Block`是一種特殊的`數據結構`,**它可以保存一段代碼**,等到需要的時候進行調用執行這段代碼,常用于GCD、動畫、排序及各類回調。
- Block變量的聲明格式為: **返回值類型(^Block名字)(參數列表);**
```
//聲明一個沒有傳參和返回值的block
void(^myBlock1)(void) ;
//聲明一個有兩個傳參沒有返回值的block 形參變量名稱可以省略,只留有變量類型即可
void(^myBlock2)(NSString *name,int age);
//聲明一個沒有傳參但有返回值的block
NSString *(^myBlock3)();
//聲明一個既有返回值也有參數的block
int(^myBlock4)(NSString *name);
```
- block的賦值: **Block變量 = ^(參數列表){函數體};**
```
//如果沒有參數可以省略寫(void)
myBlock1 = ^{
NSLog(@"hello,word");
};
myBlock2 = ^(NSString *name,int age){
NSLog(@"%@的年齡是%d",name,age);
};
//通常情況下都將返回值類型省略,因為編譯器可以從存儲代碼塊的變量中確定返回值的類型
myBlock3 = ^{
return @"小李";
};
myBlock4 = ^(NSString *name){
NSLog(@"根據查找%@的年齡是10歲",name);
return 10;
};
```
- 當然也可以直接在聲明的時候就賦值: **返回值類型(^Block名字)(參數列表) = ^(參數列表){函數體};**
```
int(^myBlock5)(NSString *address,NSString *name) = ^(NSString *address,NSString *name){
NSLog(@"根據查找家住%@的%@今年18歲了",address,name);
return 18;
};
```
- blcok的調用:**Block名字();**
```
//沒有返回值的話直接 Block名字();調用
myBlock1();
//有參數的話要傳遞相應的參數
myBlock2(@"校花",12);
//有返回值的話要對返回值進行接收
NSString *name = myBlock3();
NSLog(@"%@",name);
//既有參數又有返回值的話就需要即傳參數又接收返回值
int age = myBlock5(@"河北村",@"大梨");
NSLog(@"%d",age);
```
在實際使用Block的過程中,我們可能需要`重復`地聲明多個相同返回值相同參數列表的Block變量(blcok內部執行的代碼功能不一樣),如果總是重復地編寫一長串代碼來聲明變量會非常繁瑣, 所以我們可以使用`typedef`來定義Block類型。
```
#import "ViewController.h"
//typedef 定義Block
typedef void(^commentBlock)(NSString *name,int age);
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
commentBlock commentBlock1 = ^(NSString *name,int age){
//這里的操作是將age的name從數據庫中篩選出來
};
commentBlock commentBlock2 = ^(NSString *name,int age){
//這里的操作是將age的name添加到數據庫
};
commentBlock commentBlock3 = ^(NSString *name,int age){
//這里的操作是將age的name從數據庫中刪除
};
commentBlock1(@"ma",12);
commentBlock2(@"dong",19);
commentBlock3(@"mei",8);
}
@end
```
這樣可以減少重復代碼,避免重復用**void(^commentBlock)(NSString *name,int age)**;聲明block
## 2、block的底層結構
接下來我們來看一下block究竟是一個什么樣的結構?
通過`clang`命令將oc代碼轉換成c++代碼(如果遇到_weak的報錯是因為_weak是個運行時函數,所以我們需要在clang命令中指定運行時系統版本才能編譯):
```
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc -fobjc-arc -fobjc-runtime=ios-8.0.0 main.m -o main.cpp
```
```
-(void)viewDidLoad{
[super viewDidLoad];
int i = 1;
void(^block)(void) = ^{
NSLog(@"%d",i);
};
block();
}
```
轉換成c++代碼如下:
```
//block的真實結構體
struct __ViewController__viewDidLoad_block_impl_0 {
struct __block_impl impl;
struct __ViewController__viewDidLoad_block_desc_0* Desc;
int i;
//構造函數(相當于OC中的init方法 進行初始化操作) i(_i):將_i的值賦給i flags有默認值,可忽略
__ViewController__viewDidLoad_block_impl_0(void *fp, struct __ViewController__viewDidLoad_block_desc_0 *desc, int _i, int flags=0) : i(_i) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
//封存block代碼的函數
static void __ViewController__viewDidLoad_block_func_0(struct __ViewController__viewDidLoad_block_impl_0 *__cself) {
int i = __cself->i; // bound by copy
NSLog((NSString *)&__NSConstantStringImpl__var_folders_3g_7t9fzjm91xxgdq_ysxxghy_80000gn_T_ViewController_c252e7_mi_0,i);
}
//計算block需要多大的內存
static struct __ViewController__viewDidLoad_block_desc_0 {
size_t reserved;
size_t Block_size;
} __ViewController__viewDidLoad_block_desc_0_DATA = { 0, sizeof(struct __ViewController__viewDidLoad_block_impl_0)};
//viewDidLoad方法
static void _I_ViewController_viewDidLoad(ViewController * self, SEL _cmd) {
((void (*)(__rw_objc_super *, SEL))(void *)objc_msgSendSuper)((__rw_objc_super){(id)self, (id)class_getSuperclass(objc_getClass("ViewController"))}, sel_registerName("viewDidLoad"));
//定義的局部變量i
int i = 1;
//定義的block底部實現
void(*block)(void) = &__ViewController__viewDidLoad_block_impl_0(
__ViewController__viewDidLoad_block_func_0, &__ViewController__viewDidLoad_block_desc_0_DATA, i));
//block的調用
bloc->FuncPtr(block);
}
```
從中我們可以看出,定義的block實際上就是一直指向結構體`_ViewController_viewDidLoad_block_impl_0`的指針(將一個_ViewController_viewDidLoad_block_impl_0結構體的地址賦值給了block變量)。而這個結構體中,我們看到包含以下幾個部分:
**impl、Desc、引用的局部變量、構造方法。**
而從構造方法我們又可以看出impl中有以下幾個成員:`isa、Flags、FuncPtr`,所以綜合以上信息我們可以知道block內部有以下幾個成員:

接下來,我們依依來看block底層結構中這些結構體或者參數的作用是什么?

### **首先Desc:**
```
static struct __ViewController__viewDidLoad_block_desc_0 {
size_t reserved;
size_t Block_size;
} __ViewController__viewDidLoad_block_desc_0_DATA = { 0, sizeof(struct __ViewController__viewDidLoad_block_impl_0)};
```
desc結構體中存儲著兩個參數,`reserved`和`Block_size`,并且reserved賦值為0而Block_size則存儲著__ViewController__viewDidLoad_block_impl_0的占用空間大小。最終將desc結構體的地址傳入__ViewController__viewDidLoad_block_impl_0中賦值給Desc。**所以Desc的作用是記錄Block結構體的內存大小。**
### 接下來,我們來看,int i:
i也就是我們定義的局部變量,因為在block塊中使用到i局部變量,所以在block聲明的時候這里才會將i作為參數傳入,**也就說block會捕獲i**。如果沒有在block中使用age,這里將只會傳入impl,Desc兩個參數。這里需要注意的一點是,調用block結構體的構造函數時,是將我們定義的**局部變量i的值**傳進去了的,也就是構造函數實現的時候i(_i) 這部分代碼的作用就是將_i的值傳給i。其實這也就解釋清楚為什么我們在block中無法修改i的值了,**因為block用到的i根本和我們自己定義的i不是同一個**,block內部是自己單獨創建了一個參數i,然后將我們定義的局部變量i的值賦給了自己創建的i。
### 最后我們來看一下impl這個結構體:
```
struct __block_impl {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
};
```
我們在block結構體的構造函數中也可以看出這幾個成員分別有什么作用:
- `isa`: 指針,存放結構體的內存地址
- `Flags`: 這個用不到 有默認值
- `FuncPtr`: block代碼塊地址
所以通過以上分析,我們可以得出以下幾個結論:
- 1、`block本質上也是一個OC對象`,它內部也有個isa指針,這一點我們也可以通過打印其父類是NSObject來證明;

- 2、block是封裝了函數調用以及函數調用環境的OC對象.(所謂調用環境就是比如block用到了變量i就把它也封裝進來了);
- 3、FuncPtr則存儲著viewDidLoad_block_func_0函數的地址,也就是block代碼塊的地址。所以當調用block的時候,bloc->FuncPtr(block);是直接調用的FuncPtr方法。
- 4、impl結構體中isa指針存儲著&_NSConcreteStackBlock地址,可以暫時理解為其類對象地址,block就是_NSConcreteStackBlock類型的。
- 5、Desc存儲__viewDidLoad_block_impl_0結構體所占用的內存大小,也就是`存儲著Block的內存大小`。
我們 可以用一張圖來表示各個結構體之間的關系:

再簡單化就是:(網絡圖片 比較清晰)

block底層的數據結構也可以通過一張圖來展示(variables就是結構體中所引用的變量,invoke就是上面的FuncPtr也就是block封裝代碼的函數地址,這個圖是根據上面的分析可以總結出):
>- isa指針,所有對象都有該指針,用于實現對象相關的功能。
>- flags,用于按bit位表示一些block的附加信息,block copy的實現代碼可以看到對該變量的使用。
>- reserved,保留變量。
>- invoke,函數指針,指向具體的Block實現的函數調用地址。就是FuncPtr
>- descriptor,表示該Block的附加描述信息,主要是size大小,以及copy和dispose函數的指針。
>- variables,截取過來的變量,Block能夠訪問它外部的局部變量,就是因為將這些變量(或變量的地址)復制到了結構體中。
>

## 3. block 變量捕獲
我們定義了幾個變量:全局變量name、局部變量-`auto變量`:i,obj、局部變量-`靜態變量`:height,分別在blcok內部修改和訪問↓↓

我們發現在block內部都可以訪問這些變量,但是無法修改局部變量中的auto變量,無法修改的原因我們在上面的分析中也可以看出,是因為block內部自己創建了對應的變量,外部auto變量只是將值傳遞到block內賦給block創建的內部變量。block內部存在的只是自己創建的變量并不存在block外部的auto變量,所以沒辦法修改。
**但是為什么全局變量和靜態變量就可以訪問呢?**我們將oc代碼轉換成c++代碼來看

發現,block內部雖然訪問了四個變量,但是其底層只捕獲了三個變量,并沒有捕獲全局變量name。
而且比較局部變量中的auto變量和靜態變量,發現block底層捕獲auto變量時是捕獲的其值,**而捕獲靜態變量時是捕獲的變量地址**(i是值, *height是地址),這也就是為什么我們可以在block修改靜態變量,因為block內修改的靜態變量其實和block外的靜態變量是同一個內存地址,同一個東西。
關于auto變量obj,block內部也是捕獲它的值,不要因為它有*就覺得捕獲的是地址,因為obj本身就是個對象,本身就是地址,如果block捕獲的是obj的地址的話應該是NSObject **obj 即指向指針的地址。
所以我們通過上面這個例子可以總結出:

**為什么會出現這種區別呢?**
首先,為什么捕獲局部變量而不捕獲全局變量這個問題很好理解:
**全局變量整個項目都可以訪問**,block調用的時候可以直接拿到訪問,不用擔心變量被釋放的情況;
而局部變量則不同,局部變量是有**作用域**的,如果block調用的時候block已經被釋放了,就會出現嚴重的問題,所以為了避免這個問題block需要捕獲需要的局部變量。(比如我們局部變量和block都卸載了viewDidLoad方法,但是我在touchesBegan方法中調用block,這個時候局部變量早就釋放了,所以block要捕獲局部變量)
**接下來,為什么auto變量是捕獲的值,而靜態變量是捕獲的地址呢?**
這是因為自動變量和靜態變量存儲的區域不同,兩者釋放時間也不同。
>我們在關于局部變量、全局變量的分析中講到了**自動變量是存放在棧中的**,創建與釋放是由系統設置的,隨時可能釋放掉,而**靜態變量是存儲在全局存儲區的**,生命周期和app是一樣的,不會被銷毀。所以對于隨時銷毀的自動變量肯定是把值拿進來保存了,如果保存自動變量的地址,那么等自動變量釋放后我們根據地址去尋值肯定會發生懷內存訪問的情況,而靜態變量因為項目運行中永遠不會被釋放,所以保存它的地址值就完全可以了,等需要用的時候直接根據地址去尋值,肯定可以找到的。
那么,又有一個問題了,**為什么靜態變量和全局變量同樣不會被銷毀,為什么一個被捕獲地址一個則不會被捕獲呢?**
我個人覺得是靜態變量和全局變量因為兩者訪問方式不同造成的,我們都知道全局變量整個項目都可以拿來訪問,所以某個全局變量在全局而言是唯一的(也就是全局變量不能出現同名的情況,即使類型不同也不行,否則系統不知道你具體訪問的是哪一個)而靜態變量則不是,全局存儲區可能存儲著若干個名為height的靜態變量。
所以這就導致了訪問方式的不同,比如說有個block,內部有一個靜態變量和一個全局變量,那么在調用的時候系統可以直接根據全局變量名去**全局存儲區**查找就可以找到,名稱是惟一的,所以不用捕獲任何信息即可訪問。而靜態變量而不行,全局存儲區可能存儲著若干個名為height的靜態變量,所以blcok只能根據內存地址去區分調用自己需要的那個。
>我之前有個想法:是不是因為兩者訪問范圍不同,全局變量可以全局訪問,靜態變量只能當前文件訪問。但仔細想想block即使不是在當前文件調用的,但它的具體執行代碼塊內代碼肯定是在當前文件執行的,也就是block內部訪問變量不存在跨文件訪問的情況,既然兩者都可以訪問到那么訪問范圍就不是原因了。
```
-(void)viewDidLoad{
[super viewDidLoad];
void(^block)(void) = ^{
NSLog(@"%@",self);
};
block();
void(^block2)(void) = ^{
NSLog(@"%@",self.address);
};
block2();
}
```
上面這段代碼中的block是怎么捕獲變量的呢?
我們轉換成c++代碼可以看出,我們可以看到viewdidload實際上是轉換成了 void _I_ViewController_viewDidLoad(ViewController * self, SEL _cmd)
也就是這個方法轉換為底層實現時是有兩個參數的:self和_cmd,既然self是方法的參數,那么self肯定是個局部變量,又因為這個局部變量并沒有static修飾,所以self應該會被捕獲并且是值傳遞

在訪問實例對象(self)的屬性(address),我們發現block并沒有捕獲這個具體的屬性而是捕獲的實例對象(self),這是因為通過self就可以獲取到這個實例對象的屬性,捕獲一個實例對象就夠了,而在block內部使用這個屬性的時候,也是通過實例對象來獲取的↓↓

## 4. block的類型
我們在分析block底層結構的時候,看到了isa存儲的是&_NSConcreteStackBlock地址,也就是這個block是個block類型的,那么block只有這一種類型嗎?
答案是否定的,blcok有三種類型,我們可以通過代碼來驗證:

有的同學可能將oc轉為c++↓↓發現oc代碼編譯后三個block都是StackBlock類型的,和我們剛才打印的不一樣。這是因為runtime運行時過程中進行了轉變。最終類型當然以runtime運行時類型也就是我們打印出的類型為準。

**既然存在三種不同的類型,那系統是根據什么來劃分block的類型的呢?不同類型的block分別存儲在哪呢?**

就是根據兩點:有沒有訪問auto變量、有沒有調用copy方法:

而這三種變量存放在內存中的位置也不同:__NSMallocBlock__是在平時編碼過程中最常使用到的。存放在堆中需要我們自己進行內存管理。

關于判斷類型的兩個條件,我們第一個條件也就是判斷有誤訪問auto變量這個是明白的,但是第二個就不太清楚了,**調用copy有什么用?做了哪些操作了?**
>__NSGlobalBlock __ 調用copy操作后,`什么也不做`
>__NSStackBlock __ 調用copy操作后,復制效果是:`從棧復制到堆`;副本存儲位置是堆
> __NSMallocBlock __ 調用copy操作后,復制效果是:`引用計數增加`;副本存儲位置是堆
也就是:

由于ARC環境下,系統會對一下情況下的block自動做copy處理:
```
//1.block作為函數返回值時
typedef void (^Block)(void);
Block myblock()
{
int a = 10;
//此時block類型應為__NSStackBlock__
Block block = ^{
NSLog(@"---------%d", a);
};
return block;
}
int main(int argc, const char * argv[]) {
@autoreleasepool {
Block block = myblock();
block();
// 打印block類型為 __NSMallocBlock__
NSLog(@"%@",[block class]);
}
return 0;
}
//2.將block賦值給__strong指針時,比如(arc中默認所有對象都是強指針指引)
void (^block1)(void) = ^{
NSLog(@"Hello");
};
//3.block作為Cocoa API中方法名含有usingBlock的方法參數時。例如:遍歷數組的block方法,將block作為參數的時候。
NSArray *array = @[];
[array enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
}];
//4.block作為GCD API的方法參數時 例如:GDC的一次性函數或延遲執行的函數,執行完block操作之后系統才會對block進行release操作。
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
});
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
});
```
所以我們關閉ARC才能更好的看清楚copy的作用:
>project -> Build settings -> Apple LLVM complier 3.0 - Language -> objective-C Automatic Reference Counting設置為NO
然后我們定義幾個不同類型的block,并分別調用copy方法查看結果:

發現block的copy方法確實是有這樣的作用,需要說明的一點,block3雖然多次copy后打印出來的retainCount始終是1,但其內存管理器中仍然會增加。
**既然copy方法最大的一個作用是把block從棧拷貝到堆,它這樣做的原因是什么?block在棧中和堆中有什么區別嗎?**
我們都知道棧中對象的內存空間是隨時可能被系統釋放掉的,而堆中的內存空間是由開發者維護的,如果block存在于棧中就可以出現一個問題,當我們調用block的時候,他被釋放掉了,從而出現錯誤:

我們發現,當我們調用block的時候打印出來莫名其妙的東西,這是因為test方法執行完后,棧內存中block所占用的內存已經被系統回收,因此就有可能出現亂得數據。
可能有的同學會根據上面block訪問auto變量的想法來思考,blcok不是已經捕獲了這個變量了么,其實這完全是兩碼事,block確實是把變量a捕獲到了自己內部,但是現在它自己的空間都被釋放掉了,更不用說它捕獲的變量了,肯定被釋放掉了。
所以,這種情況下,是需要將block移到堆上面的,讓開發者控制它的生命周期,這就用到了copy(arc環境下不用,因為test方法中將block賦值給了一個__strong指針,會生成copy)

既然blcok存放在堆中了,block內部有捕獲了a的值,所以就可以正常輸出了。
我們從這種情況也可以知道為什么不同環境下block的聲明屬性寫法的不同:
MRC下block屬性的建議寫法:
```
@property (copy, nonatomic) void (^block)(void);
```
ARC下block屬性的建議寫法:
```
@property (strong, nonatomic) void (^block)(void);
@property (copy, nonatomic) void (^block)(void);
```
copy屬性意味著,系統會自動對修飾的block進行一次copy操作,
所以在mrc環境下,copy屬性修飾block就不會出現上面block存在棧里,在訪問時被釋放的情況;
而在arc環境下,系統會在block被__strong指針引用時自動執行copy方法,所以就可以寫strong和copy兩種
## 5. __block
既然block內部可以修改靜態變量和全局變量的值,而無法修改自動變量的值,那么有沒有什么方式可以解決這個問題呢?
答案是肯定的,我們可以通過_ _block修飾這個自動變量,從而可以在block內部訪問并修改這個自動變量了:
__block不能修飾全局變量、靜態變量(static)

**那么,_ _block 這個修飾符做了什么操作呢?**就讓可以讓block內部可以訪問自動變量
我們通過底層代碼可以看出,__weak將int類型的數據轉換成了一個`__Block_byref_i_0`的結構體類型

而這個結構體的結構是:
```
struct __Block_byref_i_0 {
void *__isa;
__Block_byref_i_0 *__forwarding;
int __flags;
int __size;
int i;
};
```
而從賦值上看,isa為0,既然有isa指針,那么說明這個結構體也是一個對象,__forwarding存儲的是__Block_byref_i_0的地址值,flags為0,size為Block_byref_i_0的內存大小,i是真正存儲變量值的地方,其內部結構就是這樣的↓↓(關于des結構體為什么會多了一個copy函數和一個dispose函數在下面相互引用中會講到)

而當我們在block內部修改和訪問這個變量是,底層實現是這樣的:

是通過`__Block_byref_i_0`結構體的指針__forwarding讀取和修改的變量i.
為什么要通過__forwarding轉一下呢,而不是直接讀取i
這是因為當我們調用block的時候,block可能存在于棧中可能存在于堆中,
```
void (^block)(void);
void test()
{
// __NSStackBlock__
int a = 10;
block = ^{
NSLog(@"block---------%d", a);
} ;
//情況一:此時調用block還存在與棧中
block();
//此時block有兩份 一個在棧中 一個在堆中
[blcok copy];
//一次copy對應一次release
[block release];
//這個方法執行完后雖然棧中的block釋放了 但是已經拷貝到堆里一份,所以還是可以繼續調用的
}
int main(int argc, const char * argv[]) {
@autoreleasepool {
test();
//情況二:test方法執行完后 棧中的block被釋放了 堆中還有一個copy的block
block();
}
return 0;
}
```
__forwarding指向示意圖:

如果是直接通過結構體的內存地址訪問變量,因為結構體在堆中的地址和在棧中的地址肯定不一樣,情況一和情況二很明顯又是執行的同一個方法,所以就沒有辦法實現這個功能,也就是如果方法里是根據棧中的地址訪問屬性的,那么情況二就會出錯,因為這個時候這個地址已經被釋放了,如果是根據堆中的值去訪問變量的話,那么情況一又有問題了,因為這個時候堆里還沒有這個block呢。所以需要根據__forwarding指針去訪問變量,這樣的話才能確保情況一和情況二都會訪問到這個結構體。
所以我們總結一下上面的分析:
- 1.__block將int i進行包裝,包裝成一個__Block_byref_i_0結構體對象,結構體中的i是存儲i的int值的;
- 2.當我們在block內修改或訪問該對象時,是通過該對象的__forwarding去找對應的結構體再找對應的屬性值,這是因為__forwarding在不同情況下指向不同的地址,防止只根據單一的一個內存地址出現變量提前釋放無法訪問的情況。
那么我們就明白為什么可以修改__block修飾的自動變量了,因為__block修飾變量變成了一個對象,我們修改只是修改的這個對象中的一個屬性,并不是修改的這個對象:就像這樣↓↓

__block修飾下的i不再是int類型而變成一個對象(對象p),我們block內部訪問修改和訪問的是這個對象內部的int i(字符串p),所以是可以修改訪問的。只不過這個轉化為對象的內部過程封裝起來不讓開發者看到,所以就給人的感覺是可以修改auto變量也就是修改時是int i。
block外部訪問__block修飾的變量也是通過__forwarding指針找到結構體對象內部的int i,既然是訪問你的block內部的屬性i,那么就是修改后的21了↓↓

- 前言
- WebRTC知識集
- iOS 集成WebRTC各知識點小集
- iOS WebRTC集成時遇到的問題總結
- WebRTC多人音視頻聊天架構及實戰
- iOS端 使用WebRTC實現1對1音視頻實時通話
- iOS 基于WebRTC的點對點音視頻通信 總結篇
- WebRTC Native 源碼導讀 - iOS 相機采集實現分析
- OC 底層原理
- OC runtime 運行時詳解
- GCD dispatch_queue_create 創建隊列
- iOS底層 Runtime深入理解
- iOS底層 RunLoop深入理解
- iOS底層 Block的本質與使用
- iOS內存泄漏
- iOS中isKindOfClass和isMemberOfClass
- 從預編譯的角度理解Swift與Objective-C及混編機制
- 移動支付集成
- iOS 微信支付集成及二次封裝
- iOS 支付寶支付 Alipay集成及二次封裝
- iOS Paypal 貝寶支付集成及二次封裝
- iOS 微信、支付寶、銀聯、Paypal 支付組件封裝
- iOS 微信、支付寶、銀聯支付組件的進一步設計
- iOS 組件化
- iOS 組件化實施過程
- iOS 組件化的二進制化
- 使用pod package打包framework 實現組件的二進制化
- iOS 自制Framework 獲取指定bundle并讀取里面的資源
- .podSpec文件相關知識整理
- 開發并上傳靜態庫到CocoaPods
- pod引用第三方庫的幾種方式
- 如何在.podspec 文件中添加對本地庫的依賴
- lipo 命令合并真機與模擬器生成的framework
- iOS多線程
- NSOperation相關知識點
- 自定義NSOperation
- ios多個網絡請求之間的并行與串行場景的處理
- iOS動畫
- ios animation 動畫學習總結
- CABasicAnimation使用總結
- UITableView cell呈現的動效整理
- CoreAnimation動畫使用詳解
- iOS音視頻開發
- iOS 音視頻開發之AVCaptureMetadataOutput
- iOS操作本地視頻 - 獲取,壓縮,取第一幀
- 使用 GPUImage 實現一個簡單相機
- 直播App架構及思維導圖
- 如何快速的開發一個完整的iOS直播app
- iOS視頻拖動預覽及裁剪
- iOS 直播流程概述
- iOS直播:評論框與粒子系統點贊動畫
- iOS音視頻開發 - 采集
- 基于AVFoundation實現視頻錄制的兩種方式
- Swift知識集
- Swift 的枚舉、結構體和類詳解
- Swift 泛型詳解
- Swift屬性的包裝器@PropertyWrapper
- SwiftHub項目 之網絡層封裝的一點見解
- Moya+RxSwift+HandyJson 實現網絡請求及模型轉換
- Swift開發小記(含面試題)
- RxSwift 入坑手冊 - 基礎概念
- 理解 Swift 中的元類型:.Type 與 .self
- Swift HandyJSON庫中的類型相互轉換的實現
- Swift 中使用嵌套結構體定義一組相關的常量
- Swift Type-Erased(類型擦除)
- Swift中的weak和unowned關鍵字
- Swift 中的錯誤處理
- Swift中的Result 類型的簡單介紹
- Swift Combine 入門導讀
- Swift CustomStringConvertible 協議的使用
- 跨平臺
- Cordova跨平臺方案 iOS工程創建的步驟
- 使用Cordova 打包WebApp為原生應用詳解 (加殼封裝)
- RAC響應式編程
- 快速上手ReactiveCocoa之基礎篇
- RAC ReactiveCocoa 使用小集
- 優雅的 RACCommand
- 三方庫集成及使用
- 融云IM iOS sdk 集成 一篇就夠了
- iOS YYTextView使用筆記
- iOS YYLabel使用筆記
- iOS 蘋果集成登錄及蘋果圖標的制作要求
- iOS 面向切面編程 Aspects 庫的使用
- VKMsgSend庫對oc runtime的封裝
- OC Protocol協議分發器
- iOS 高德地圖實現大頭針展示,分級大頭針,自定制大頭針,在地圖上畫線,線和點共存,路線規劃(駕車路線規劃),路線導航,等一些常見的使用場景
- 工作總結
- 自定義UINavigationBar 適配iOS11, iOS15的問題
- SFSafariViewController 加載的網頁與原生oc之間的交互
- UICollectionView 設置header的二種方法
- UIPanGestureRecognizer進行視圖滑動并處理手勢沖突
- OC與Swift混編 注意事項
- UICollectionView 設置水平滑動后,調整每個Item項的排列方式
- oc 下定義字符串枚舉
- 高性能iOS應用開發中文版讀書筆記
- iOS 圖集滑動到最后時添加“顯示更多”效果的view組件 實現
- CocoaPods 重裝
- WKWebview使用二三事
- IOS電商首頁如何布局
- iOS中的投屏方案
- CGAffineTransform 介紹
- 用Block實現鏈式編程
- iOS 本地化簡明指南
- iOS 檢查及獲取相機、麥克風、相冊、位置等權限
- iOS 手勢UIGestureRecognizer詳解
- ios 編譯時報 Could not build module xxx 的解決方法嘗試
- iOS 常見編譯報錯及解決方案匯總(持續更新)
- AVMakeRectWithAspectRatioInsideRect 的使用
- graphhopper-ios 編譯過程詳解
- 算法
- iOS實現LRU緩存
- 架構
- IOS項目架構
- 其他雜項
- 推薦一個好用的Mac精品軟件下載站
- 如何能成為一位合格的職業經理人
- 零基礎怎么學習視頻剪輯?這篇初剪輯學者指南你一定不要錯過
- 免費SSL證書的制作
- 《一部手機拍全景》匯總課
- Linux下JAVA常用命令大全
- 即時通訊
- 通訊協議與即時通訊雜談
- 簡述移動端IM開發的那些坑:架構設計、通信協議和客戶端
- 基于實踐:一套百萬消息量小規模IM系統技術要點總結
- PaddleOCR 文字識別深度學習
- PaddleOCR mac 安裝指南
- PaddleOCR 標注工具PPOCRLabel的使用
- PaddleOCR 更換模型
- PaddleOCR 自制模型訓練