iOS - Block

来源:https://www.cnblogs.com/chenjiangxiaoyu/archive/2018/01/11/7729273.html
-Advertisement-
Play Games

1. Block 1.1 什麼是Block 之前都是對block的簡單實用,這裡重新瞭解下。 代碼塊Block是蘋果在iOS4開始引入的對C語言的擴展,實現匿名函數的特性,Block是一種特殊的數據類型,其可以正常定義變數、作為參數、作為返回值,特殊的,block還可以保存一段代碼,在需要的時候調用 ...


1. Block

1.1 什麼是Block

  之前都是對block的簡單實用,這裡重新瞭解下。

  代碼塊Block是蘋果在iOS4開始引入的對C語言的擴展,實現匿名函數的特性,Block是一種特殊的數據類型,其可以正常定義變數、作為參數、作為返回值,特殊的,block還可以保存一段代碼,在需要的時候調用,目前Block廣泛的應用iOS開發中,常用於GCD、動畫、排序及各類回調。

  註:Block的聲明與賦值只是保存了一段代碼段,必須調用才能執行內部的代碼。  

1.2 Block簡單的使用

Block的聲明:

Block變數的聲明格式為: 返回值類型(^Block名字)(參數列表);

// 聲明一個無返回值,參數為兩個字元串對象,叫做aBlock的Block
void(^aBlock)(NSString *x, NSString *y);

// 形參變數名稱可以省略,只留有變數類型即可
void(^aBlock)(NSString *, NSString *);

 Block的賦值:

Block變數的賦值格式為: Block變數 = ^(參數列表){函數體};

aBlock = ^(NSString *x, NSString *y){
    NSLog(@"%@ love %@", x, y);
};

Block聲明並賦值:

int(^myBlock)(int) = ^(int num){
    return num * 7;
};

// 如果沒有參數列表,在賦值時參數列表可以省略
void(^aVoidBlock)() = ^{
    NSLog(@"I am a aVoidBlock");
};

Block 變數的調用;

// 調用後控制台輸出"Li Lei love Han Meimei"
aBlock(@"Li Lei",@"Han Meimei");

// 調用後控制台輸出"result = 63"
NSLog(@"result = %d", myBlock(9));

// 調用後控制台輸出"I am a aVoidBlock"
aVoidBlock();

2. Block 數據結構

2.1 Block 數據結構簡單認識

block的數據結構定義如下:

對應的結構體定義如下:

struct Block_descriptor {
    unsigned long int reserved;
    unsigned long int size;
    void (*copy)(void *dst, void *src);
    void (*dispose)(void *);
};
struct Block_layout {
    void *isa;
    int flags;
    int reserved;
    void (*invoke)(void *, ...);
    struct Block_descriptor *descriptor;
    /* Imported variables. */
};

 通過上面我們可以知道,一個block實例實際上由6部分構成:

  1. isa 指針,所有對象都有該指針,用於實現對象相關的功能。
  2. flags,用於按bit位表示一些block的附加信息,本文後面介紹 block copy 的實現代碼可以看到對該變數的使用
  3. reserved 保留變數
  4. invoke 函數指針,指向具體的block 實現的函數調用地址
  5. descriptor 表示該block的附加描述信息,主要是size大小,以及 copy 和 dispose 函數的指針。
  6. variables , capture 過來的變數,block能夠訪問它外部的局部變數,就是因為將這些變數(或變數的地址)複製到了結構體中。

在 OC 語言中,一共有 3 種類型的 block:

  1. _NSConcreteGlobalBlock 全局的靜態 block,不會訪問任何外部變數。
  2. _NSConcreteStackBlock 保存在棧中的 block,當函數返回時會被銷毀
  3. _NSConcreteMallocBlock 保存在堆中的 block,當引用計數為 0 時會被銷毀。

遇到一個Block,我們怎麼確定這個Block的存儲位置呢?

a。Block不訪問外界變數(包括棧中和堆中的變數)

Block既不在棧又不在堆中,在代碼段中,ARC和MRC都是如此,此時為全局塊。

b。Block訪問外界變數

MRC 環境下:訪問外界變數的Block預設存儲在棧中。

ARC 環境下:訪問外界變數的Block預設存儲在堆中(實際是放在棧區,然後ARC情況下自動又拷貝到堆區),自動釋放。

2.2 NSConcreteGlobalBlock 類型的 block 的實現

我們可以新建一個block1.c文件:

#include <stdio.h>
int main()
{
    ^{ printf("Hello, World!\n"); } ();
    return 0;
}

 在終端輸入 clang -rewrite-objc block1.c ,就可以在目錄中看到 clang 輸出了一個 block1.cpp 的文件,這個文件就是 block 在 C 語言的實現:

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) {
    printf("Hello, World!\n");
}
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()
{
    (void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA) ();
    return 0;
}
  1.   一個block實際就是一個對象,它主要由一個 isa 和一個 impl 和一個 descriptor 組成。
  2. 這裡我們看到 isa 指向的還是 _NSConcreteStackBlock,但在 LLVM 的實現中,開啟 ARC 時,block 應該是 _NSConcreteGlobalBlock 類型。感覺是當一個 block 被聲明的時候,它都是一個 _NSConcreteStackBlock類的對象。
  3. impl 是實際的函數指針,本例中,它指向 _main_block_func_0。這裡的 impl 相當於之前提到的 invoke 變數,只是 clang 編譯器對變數的命名不一樣。
  4. descriptor 是用於描述當前這個 block 的附加信息的,包括結構體的大小,需要 捕獲 和 處理 的變數列表等。結構體大小需要保存是因為,每個 block 因為會 捕獲 一些變數,這些變數會加到 __main_block_impl_0 這個結構體中,讓其體積變大。後面會看到相關代碼。

2.3 NSConcreteStackBlock 類型的 block 的實現

我們另外新建一個名為 block2.c 的文件,輸入一下內容:

#include <stdio.h>
int main() {
    int a = 100;
    void (^block2)(void) = ^{
        printf("%d\n", a);
    };
    block2();
    return 0;
}

 再次使用 clang 工具,轉換後的關鍵代碼如下:

struct __main_block_impl_0 {
    struct __block_impl impl;
    struct __main_block_desc_0* Desc;
    int a;
    __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _a, int flags=0) : a(_a) {
        impl.isa = &_NSConcreteStackBlock;
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
    }
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
    int a = __cself->a; // bound by copy
    printf("%d\n", a);
}
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 a = 100;
    void (*block2)(void) = (void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, a);
    ((void (*)(__block_impl *))((__block_impl *)block2)->FuncPtr)((__block_impl *)block2);
    return 0;
}

  在本例中,我們可以看到:

  1. 本例中,isa 指向 _NSConcreteStackBlock,說明這是一個分配在棧上的實例。
  2. main_block_impl_0 中增加了一個變數a,在block中引用的變數a實際上是在申明block時,被覆制到 main_block_impl_0 結構體中的那個變數a。y因為這樣,我們就能理解,在block內部修改變數a的內容,不會影響外部的實際變數a。
  3. main_block_impl_0 中由於增加了一個變數a,所以結構體的大小變了,該結構體大小被寫在了 main_block_desc_0 中。

我們修改上面的代碼,在變數前面增加 __block 關鍵字:

#include <stdio.h>
int main()
{
    __block int i = 1024;
    void (^block1)(void) = ^{
        printf("%d\n", i);
        i = 1023;
    };
    block1();
    return 0;
}

  生成的關鍵代碼如下,可以看到,差異很大:

struct __Block_byref_i_0 {
    void *__isa;
    __Block_byref_i_0 *__forwarding;
    int __flags;
    int __size;
    int i;
};
struct __main_block_impl_0 {
    struct __block_impl impl;
    struct __main_block_desc_0* Desc;
    __Block_byref_i_0 *i; // by ref
    __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_i_0 *_i, int flags=0) : i(_i->__forwarding) {
        impl.isa = &_NSConcreteStackBlock;
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
    }
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
    __Block_byref_i_0 *i = __cself->i; // bound by ref
    printf("%d\n", (i->__forwarding->i));
    (i->__forwarding->i) = 1023;
}
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->i, (void*)src->i, 8/*BLOCK_FIELD_IS_BYREF*/);}
static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->i, 8/*BLOCK_FIELD_IS_BYREF*/);}
static struct __main_block_desc_0 {
    size_t reserved;
    size_t Block_size;
    void (*copy)(struct __main_block_impl_0*, struct __main_block_impl_0*);
    void (*dispose)(struct __main_block_impl_0*);
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0), __main_block_copy_0, __main_block_dispose_0};
int main()
{
    __attribute__((__blocks__(byref))) __Block_byref_i_0 i = {(void*)0,(__Block_byref_i_0 *)&i, 0, sizeof(__Block_byref_i_0), 1024};
    void (*block1)(void) = (void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_i_0 *)&i, 570425344);
    ((void (*)(__block_impl *))((__block_impl *)block1)->FuncPtr)((__block_impl *)block1);
    return 0;
}

  從代碼中我們可以看到:

  1. 源碼中增加一個名為 __block_byref_i_0 的結構體,用來保存我們要 捕獲 並且修改的變數 i。
  2. main_block_impl_0 引用的是 Block_byref_i_0 的結構體指針,這樣就可以達到修改外部變數的作用。
  3. __Block_byref_i_0 結構體中帶有 isa,說明它也是一個對象。
  4. 我們需要負責 Block_byref_i_0 結構體相關的記憶體管理,所以 main_block_desc_0 中增加了 copy 和 dispose 函數指針,對於在調用前後修改響應變數的引用計數。

為什麼使用__block 修飾的外部變數的值就可以被block修改呢?

我們發現一個局部變數加上 __block 修飾符後竟然跟block一樣變成了一個__Block_byref_i_0結構體類型的自動變數實例。此時我們在block內部訪問 i 變數則需要通過一個叫 __forwarding 的成員變數來間接訪問 i 變數。

__block 變數和 __forwarding

在copy操作之後,既然__block變數也被copy到堆上去了,那麼訪問該變數是訪問棧上還是堆上的呢?

通過__forwarding, 無論是在block中還是 block外訪問__block變數, 也不管該變數在棧上或堆上, 都能順利地訪問同一個__block變數。

2.3 NSConcreteMallocBlock 類型的 block 的實現

NSConcreteMallocBlock 類型的 block 通常不會在源碼中直接出現,因為預設它是當一個 block 被 copy 的時候,才會將這個 block 賦值到堆中。以下是一個 block 被copy 時的示例代碼,可以看到,在第8步,目標的 block 類型被修改為 _NSConcreteMallocBlock。

static void *_Block_copy_internal(const void *arg, const int flags) {
    struct Block_layout *aBlock;
    const bool wantsOne = (WANTS_ONE & flags) == WANTS_ONE;
    // 1
    if (!arg) return NULL;
    // 2
    aBlock = (struct Block_layout *)arg;
    // 3
    if (aBlock->flags & BLOCK_NEEDS_FREE) {
        // latches on high
        latching_incr_int(&aBlock->flags);
        return aBlock;
    }
    // 4
    else if (aBlock->flags & BLOCK_IS_GLOBAL) {
        return aBlock;
    }
    // 5
    struct Block_layout *result = malloc(aBlock->descriptor->size);
    if (!result) return (void *)0;
    // 6
    memmove(result, aBlock, aBlock->descriptor->size); // bitcopy first
    // 7
    result->flags &= ~(BLOCK_REFCOUNT_MASK);    // XXX not needed
    result->flags |= BLOCK_NEEDS_FREE | 1;
    // 8
    result->isa = _NSConcreteMallocBlock;
    // 9
    if (result->flags & BLOCK_HAS_COPY_DISPOSE) {
        (*aBlock->descriptor->copy)(result, aBlock); // do fixup
    }
    return result;
}

3. 變數的複製

對於 block 外的變數引用,block預設是將其複製到其數據結構中來實現訪問的,也就是說block的自動變數只針對block內部使用的自動變數,不使用則不截獲,因為截獲的自動變數會存儲於block的結構體內部,會導致block體積變大,預設情況下 block 只能訪問不能修改局部變數的值,如下圖所示:

對於 __block 修飾的外部變數引用,block 是複製其引用地址來實現訪問的,block可以修改__block 修飾的外部變數的值,如下圖所示:

 

4. ARC 對 block 類型的影響

在 ARC 開啟的情況下,將只會有 NSConcreteGlobalBlock 和 NSConcreteMallocBlock 類型的 block。

原本的 NSConcreteStackBlock 的 block 會被 NSConcreteMallocBlock 類型的 block替代。證明方式是以下代碼再 XCode 中,會輸出 <__NSMallocBlock__: 0x100109960>。

在蘋果的官方文檔中也提到,當把棧中的block返回時,不需要調用 copy 方法了。

#import <Foundation/Foundation.h>
int main(int argc, const char * argv[])
{
    @autoreleasepool {
        int i = 1024;
        void (^block1)(void) = ^{
            printf("%d\n", i);
        };
        block1();
        NSLog(@"%@", block1);
    }
    return 0;
}

 ARC下,訪問外界變數的 Block 為什麼要從棧區拷貝到堆區呢?

棧上的Block,如果其所屬的變數作用域結束,該Block就被廢棄,如同一般的自動變數。當然,Block中的__block變數也同時被廢棄:


為瞭解決棧塊在其變數作用域結束之後被廢棄(釋放)的問題,我們需要把Block複製到堆中,延長其生命周期。開啟ARC時,大多數情況下編譯器會恰當地進行判斷是否有需要將Block從棧複製到堆,如果有,自動生成將Block從棧上複製到堆上的代碼。Block的複製操作執行的是copy實例方法。Block只要調用了copy方法,棧塊就會變成堆塊。

如下圖:

5. 鏈式語法的實現

  類似於第三方自動佈局 Masonry 的代碼:

[view1 mas_makeConstraints:^(MASConstraintMaker *make) {
    make.top.equalTo(superview.mas_top).with.offset(padding.top);
    make.left.equalTo(superview.mas_left).with.offset(padding.left);
    make.bottom.equalTo(superview.mas_bottom).with.offset(-padding.bottom);
    make.right.equalTo(superview.mas_right).with.offset(-padding.right);
}];

5.1 如何實現

我們舉個例子,假如對於一個已有類的實例 classInstance,現在要用句點 . 和小括弧 () 的方式連續調用它的"方法" method1,method2,method3,如下圖所示:

從圖中我們可以知道,要實現鏈式語法,主要包含 點語法、小括弧調用、連續訪問 三部分:

  • 點語法:在OC中,對於點語法的使用,最常見於屬性的訪問,比如對在方法內部調用 self.xxx,在類的實例中用 classInstance.xxx;
  • 小括弧調用:OC中一般用中括弧 [] 來實現方法的調用,而對於 Block 的調用則還是保留使用小括弧 ( ) 的方式,因此我們可以考慮用 Block來實現鏈式語法中的 ();
  • 如何實現連續訪問?:Block可以理解為帶有自動變數的匿名函數或函數指針,它也是有返回值的。我們可以把上述類實例每次方法的調用(實質為 Block 的調用)的返回值都設為當前類實例本身,即 classInstance.method1() 返回了當前 classInstance ,此時才能在其後面繼續執行 .method2() 的調用,以此類推。

總結一句話:我們可以定義類的一些只讀 Block 類型的屬性,並把這些 Block 的返回值類型設置為當前類本身,然後實現這些 Block 屬性的 getter 方法。

下麵是一個Demo,鏈式計算器的例子,可以連續地調用計算器的加減乘除進行計算:

@interface Cacluator : NSObject

@property (assign, nonatomic) NSInteger result;

// 下麵分別定義加減乘除四個只讀block類型的屬性
// 設置為只讀是為了限制只需要實現 getter方法
// 這裡每個 Block 類型的屬性攜帶一個 NSInteger 類型的參數,返回參數是當前類型
@property (copy, nonatomic, readonly) Cacluator *(^add)(NSInteger number);
@property (copy, nonatomic, readonly) Cacluator *(^minus)(NSInteger number);
@property (copy, nonatomic, readonly) Cacluator *(^multiply)(NSInteger number);
@property (copy, nonatomic, readonly) Cacluator *(^divide)(NSInteger number);

@end


@implementation Cacluator

// 此處為 add 屬性的 getter方法實現
// 前面聲明 add 屬性的類型為 block 類型,所以此處 getter 返回一個 block
// 對於返回的 block,返回值類型為 Calculator,所以返回self

-(Cacluator *(^)(NSInteger))add{
    return ^id(NSInteger num){
        self.result += num;
        return self;
    };
}

-(Cacluator *(^)(NSInteger))minus{
    return ^id(NSInteger num){
        self.result -= num;
        return self;
    };
}

-(Cacluator *(^)(NSInteger))multiply{
    return ^id(NSInteger num){
        self.result *= num;
        return self;
    };
}

-(Cacluator *(^)(NSInteger))divide{
    return ^id(NSInteger num){
        NSAssert(num != 0, @"除數不能為0");
        self.result /= num;
        return self;
    };
}

@end

 測試代碼:

Calculator *calc = [[Calculator alloc] init]; // 初始化一個計算器類實例

calc.add(8).minus(4).multiply(6).divide(3); // 鏈式調用

NSLog(@"%d", (int)calc.result); // 輸出 8

 分析:

上面 calc.add 訪問 calc 的 add 屬性會調用 [calc add] 方法,此方法會返回一個Block如下:

^id(NSInteger num){
      self.result += num;
      return self;  
};

在這個Block中,前面已聲明其返回值類型為:Caculator,所以在其裡面返回了 self,這樣當調用該 Block 時,會返回 self (實例本身),流程如下:

1.calc.add 獲得一個 Block
2.calc.add(8) Block 的執行,並返回了 self (即實例 calc)
3.於是在 calc.add(8) 後面可繼續訪問 calc 的其他屬性,一路點下去

 5.2 更簡潔的實現

上面是通過先聲明一系列的Block屬性, 再去實現Block屬性的getter 方法來實現鏈式調用,感覺還是有點麻煩,我們去看看是否有更簡潔的實現方式:

點語法的本質:

  • 在OC中,點語法實際上只是一種替換手段,對於屬性的getter方法,class.xxx 的寫法最終會被編譯器替換成 [class xxx];對於setter 方法,即把 class.xxx 寫在等號左邊,class.xxx = value 會被轉換成 [class setXxx:value],本質都是方法調用
  • 即使再class中並沒有顯式聲明 xxx 屬性,在編譯代碼時,代碼中如果有 class.xxx 的寫法也會被替換成 [class xxx],所以只要在class中有一個聲明為 xxx 的方法,即可在代碼中其它地方寫 class.xxx

所以,解決方案是:

  在定義類的頭文件的@interface中,直接聲明某一方法名為xxx,該方法的返回值是一個Block,而此block的返回值設為該類本身。

@interface Calculator : NSObject

@property (nonatomic, assign) NSInteger result; // 保存計算結果

// 上面的屬性聲明其實是可以省略的,只要聲明下麵方法即可;
// 在 Objective-C 中,點語法只是一種替換手段,class.xxx 的寫法(寫在等號左邊除外)最終會被編譯器替換成 [class xxx],本質上是方法調用;

// add、minus、multiply、divide 四個方法都會返回一個 Block,
// 這個 Block 有一個 NSInteger 類型的參數,並且其返回值類型為當前 Calculator 類型;
// 下麵四個方法的實現與上面 Calculator.m 中的一致。
- (Calculator * (^)(NSInteger num)) add;
- (Calculator * (^)(NSInteger num)) minus;
- (Calculator * (^)(NSInteger num)) multiply;
- (Calculator * (^)(NSInteger num)) divide;

 Masonry 也是這麼做的,只聲明瞭方法,並沒有聲明相應的屬性。另外,對於Masonry鏈式語法中的 .and、.with 等寫法只是為了讓代碼讀起來更通順,實現方式為:聲明一個名為 and 和 with 的方法,在方法里返回self:

- (MASConstraint *)with {
    return self;
}

- (MASConstraint *)and {
    return self;
}

 存在的問題:

當用點語法去訪問類的某一個 Block 屬性時,Block 後面的參數 Xcode

XXXHTTPManager *http = [XXXHTTPManager manager];

// 下麵 .get(...) 裡面的參數,Xcode 並不會提示自動補全,需要手動去填寫,.success(...) .failure(...) 等也一樣,
// 這裡不能像傳統中括弧 [] 方法調用那樣,輸入方法名就可以自動提示該方法所有的參數並按回車自動補全。
http.get(@"https://kangzubin.cn", nil).success(^(NSURLSessionDataTask *task, id responseObject) {
    // Success TODO
}).failure(^(NSURLSessionDataTask *task, NSError *error) {
    // Failure TODO
}).resume();

 Xcode 中有個強大但未被充分利用的功能:Code Snippets(代碼塊)可以解決。

http://www.imlifengfeng.com/blog/?utm_medium=email&utm_source=gank.io&p=457


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 今天工作中發現一個activity的android:screenOrientation屬性設置為behind,平時經常看到的是landscape、portrait,一時沒有反應過來,故查了一下android:screenOrientation支持的屬性。 眾所周知,Android應用程式中,andr ...
  • 此處除掉標題欄,需要註意一點,AppCompactActivity是繼承自Activity。然而,AppCompactActivity據查看網上資料得知,其實它貌似沒有標題欄,上面顯示的標題欄是一個ActionBar控制項(PS:即標題欄部分是一個控制項,該控制項功能還比較強大),因此AppCompact ...
  • Android Activity,Service(含IntentService)的生命周期詳解 Android Activity,Service(含IntentService)的生命周期詳解 Actvity的生命周期流程圖: 以下是Activity生命周期的幾個過程: 1.啟動Activity:系統 ...
  • Android中startService的使用及Service生命周期 Android中startService的使用及Service生命周期 Android中有兩種主要方式使用Service,通過調用Context的startService方法或調用Context的bindService方法,本文 ...
  • 同步和非同步的概念區別: 同步,必須執行完成某個問題後才能繼續執行其他的。 非同步,我會去先執行其他問題,你執行完之後返回給我一個結果就可以。 android中為什麼要引用非同步任務呢 android啟動的,會啟動一個線程也稱為主線程,UI線程,但是我們不能把所有耗時的任務交給主線程來完成,這樣會影響用戶 ...
  • GsonFormat插件可以根據JSONObject格式的字元串,自動生成實體類參數。 要使用這個插件,首先要做的事下載它。方法如下: 方法一: 1.Android studio File->Settings..->Plugins–>Browse repositores..搜索GsonFormat  ...
  • 應用包下載地址: https://github.com/kingthy/TVRemoteIME/raw/master/released/IMEService-release.apk TVRemoteIME 電視盒子的遠程輸入法應用,可跨屏遠程輸入和跨屏遠程式控制制盒子 應用的誕生 自從家裡有電視盒子以來 ...
  • 使用之前,先簡單介紹一下這個SwipeRecyclerView,這是嚴大(嚴振傑)基於RecyclerView的進行修改和封裝的高級RecyclerView,其可以實現像QQ聊天界面的側滑刪除菜單,和一些高級的功能,我這裡也就不一一列舉出來了 想要瞭解更多的同學,請看這一篇,作者寫的http://b ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...