ReactiveCocoa 響應式函數編程

来源:https://www.cnblogs.com/xujinzhong/archive/2018/02/05/8416511.html
-Advertisement-
Play Games

簡介 ReactiveCocoa(簡稱為RAC),RAC具有函數響應式編程特性,由Matt Diephouse開源的一個應用於iOS和OS X的新框架。 為什麼使用RAC? 因為RAC具有高聚合低耦合的思想所以使用RAC會讓代碼更簡潔,邏輯更清晰。 如何在項目中添加RAC? 方法1.可以使用Coco ...


簡介


ReactiveCocoa(簡稱為RAC),RAC具有函數響應式編程特性,由Matt Diephouse開源的一個應用於iOS和OS X的新框架。

為什麼使用RAC?


因為RAC具有高聚合低耦合的思想所以使用RAC會讓代碼更簡潔,邏輯更清晰。

如何在項目中添加RAC?


  • 方法1.可以使用Cocoapods導入RAC
    在Podfile中添加如下內容

pod 'ReactiveObjC'

  • 其他方法看最下方官方鏈接

工作原理


  工作原理

常見類解釋


1. Stream - 信號流值 - RACStream類
表示一個基本單元可以為任意值,其值會隨著事件的變化而變化,可以在其上進行一些複雜的操作運算(map,filter,skip,take等.)此類不會被經常使用, 多情況下表現為signal和sequences(RACSignal 和RACSequence繼承於RACStream類)

[[RACObserve(self, reactiveString)
    filter:^BOOL(NSString *value) {
        return [value hasPrefix:@"A"];
}]
subscribeNext:^(NSString *value) {
        NSLog(@"%@",value);
}];

2. Signals - 信號 - RACSignal類

    RACSignal能力

 

什麼是Signals?


  Signals

有訂閱者監聽時信號才會發信息, Signals會向那個訂閱者發送0或多個載有數值的”next”事件,後面跟著一個”complete”事件或一個”error”事件。
Signals會發送三種不同信號給Subscriber

  • next:是可以為nil的新值, RACStream方法只能在這個值上進行操作運算。
  • error:表示在Signals完成之前發生了錯誤,值不會在RACStream類中存儲。
  • completed:表示Signals成功的完成,值不會在RACStream類中存儲。
  訂閱者監聽
__block int aNumber = 0;
// Signal that will have the side effect of incrementing `aNumber` block
// variable for each subscription before sending it.
RACSignal *aSignal = [RACSignal createSignal:^ RACDisposable * (id<RACSubscriber> subscriber) {
    aNumber++;
    [subscriber sendNext:@(aNumber)];
    [subscriber sendCompleted];
    return nil;
}];
        
// This will print "subscriber one: 1"
[aSignal subscribeNext:^(id x) {
    NSLog(@"subscriber one: %@", x);
}];
        
// This will print "subscriber two: 2"
[aSignal subscribeNext:^(id x) {
    NSLog(@"subscriber two: %@", x);
}];

如果需要對信號進行過濾,轉換,分解和合併那些值的話則不同的訂閱者可能需要使用信號通過不同方式發送的值。


  信號處理
RACSignal *usernameIsValidSignal = RACObserve(self.viewModel, usernameValid);
RAC(self.Button, alpha) = [usernameIsValidSignal
    map:^(NSNumber *valid) {
        return valid. boolValue?@1:@0.5;
}];

3. Subscriber - 訂閱者 - RACSubscriber協議
表示能夠接收信號的對象,訂閱信號才會激活信號,實現RACSubscriber協議的對象都可以為訂閱者。
可以通過- (RACDisposable *)subscribeNext:(void (^)(id x))nextBlock 方法創建Subscriber。

RACSignal *repeatSignal = [[RACSignal interval:1 onScheduler:[RACScheduler mainThreadScheduler]] repeat];
[repeatSignal subscribeNext: ^(NSDate* time){
      NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
      [formatter setDateFormat:@"HH:mm:ss"];
      NSLog(@"%@",[formatter stringFromDate:time]);
}];

4. Subjects - 手動控制信號 - RACSubject
表示可以手動控制信號,
處理流程:創建信號-訂閱信號-發送信號

// 1.創建信號
RACSubject *subject = [RACSubject subject];
// 2.訂閱信號 First
[subject subscribeNext:^(id x) {
    // block調用時刻:當信號發出新值,就會調用.
      NSLog(@"FirstSubscribeNext%@",x);
}];
// 2.訂閱信號 Second
[subject subscribeNext:^(id x) {
      // block調用時刻:當信號發出新值,就會調用.
      NSLog(@"SecondSubscribeNext%@",x);
}];
// 3.發送信號
[subject sendNext:@"1"];
[subject sendNext:@"2"];

也是RAC代碼與非RAC代碼的Bridge 所以非常有用,此類繼承於RACSignal類。

5. ReplaySubject - 手動回放控制信號 - RACReplaySubject
表示可以手動控制信號,底層實現和RACSubject不一樣,它會先把值保存起來,然後遍歷剛剛保存的所有訂閱者,一個一個調用訂閱者的nextBlock然後調用subscribeNext訂閱信號,遍歷保存的所有值,一個一個調用訂閱者的nextBlock。
可以有以下兩種處理流程:

處理流程 1:創建信號-訂閱信號-發送信號(和Subjects一樣)
處理流程 2:創建信號-發送信號-訂閱信號

// 1.創建信號
RACReplaySubject *replaySubject = [RACReplaySubject subject];
// 2.發送信號
[replaySubject sendNext:@"1"];
[replaySubject sendNext:@"2"];
// 3.訂閱信號 First
[replaySubject subscribeNext:^(id x) {
      NSLog(@"FirstSubscribeNext%@",x);
}];
// 3.訂閱信號 Second
[replaySubject subscribeNext:^(id x) {
      NSLog(@"SecondSubscribeNext%@",x);
}];

6. Command- 命令信號 - RACCommand
表示訂閱響應Action信號,通常由UI來出發,比如一個Button當控制項被觸發時會被自動禁用掉。

UIButton *reactiveBtn = [[UIButton alloc] init];
[reactiveBtn setTitle:@"點我" forState:UIControlStateNormal];
    reactiveBtn.rac_command = [[RACCommand alloc] initWithSignalBlock:^RACSignal *(UIButton *input) {
    NSLog(@"點擊了我:%@",input.currentTitle);
    //返回一個空的信號量
    return [RACSignal empty];
}];

7. Sequences- 集合 - RACSequence
表示一個不可變的序列值且不能包含空值,使用-rac_sequence.signal來獲取Signal。

RACSignal *signal = [@"A B C D E F G H I" componentsSeparatedByString:@" "].rac_sequence.signal;
// Outputs
[signal subscribeNext:^(NSString *x) {
    NSLog(@"%@", x);
}];

8. Disposables- 清理訂閱 - RACDisposable
表示用於取消信號的訂閱,當一個signal被subscriber後,當執行sendComplete或sendError時subscriber會被移除,或者手動調用[disposable dispose]進行移除操作。
當subscriber被移除後,所有該subscriber相關的工作都會被停止或取消,如http請求,資源也會被釋放。

9. Scheduler- 計劃 - RACScheduler
表示一個信號隊列,是信號執行任務時所在的隊列或者信號執行完成後將結果放到隊列里執行,它支持取消對列里的執行並總是串列執行。

RAC常用巨集


RACObserve(TARGET, KEYPATH)
表現形式:RACObserve(self, stringProperty)
KVO的簡化版本 相當於對TARGET中KEYPATH的值設置監聽,返回一個RACSignal

RAC(TARGET, ...)
表現形式:RAC(self, stringProperty) = TextField.rac_textSignal
第一個是需要設置屬性值的對象,第二個是屬性名
RAC巨集允許直接把信號的輸出應用到對象的屬性上
每次信號產生一個next事件,傳遞過來的值都會應用到該屬性上

RACChannelTo(TARGET, ...)
RACChannelTo 用於雙向綁定
RACChannelTo(self, stringProperty)=RACChannelTo(self.label, text) ;

RAC結構圖


  RAC結構圖

RAC基礎使用


創建一個TextField名為usernameTextField 設置監聽TextField

[self.usernameTextField.rac_textSignal 
    subscribeNext:^(id x){
    NSLog(@"%@", x);
}];

filter:如果想添加一個條件 只輸出x的長度大於3的,可以使用filter操作來實現這個目的

[self.usernameTextField.rac_textSignal
    filter:^BOOL(NSString* text){
    return text.length > 3;
}];
filter

map:把text轉換成length進行輸出,使用map可以對信號進行轉換,一個源信號轉換成另外一個新的信號輸出

[[[self.usernameTextField.rac_textSignal
map:^id(NSString*text){
  return @(text.length);
}]
filter:^BOOL(NSNumber*length){
  return[length integerValue] > 3;
}]
subscribeNext:^(id x){
  NSLog(@"%@", x);
}];
map

信號可聚合也可以分割

聚合: 多個信號可以聚合成一個新的信號,這個可以是任何類型的信號

RACSignal *signal =
[RACSignal combineLatest:@[validUsernameSignal, validPasswordSignal]
                    reduce:^id(NSNumber*usernameValid, NSNumber *passwordValid){
                      return @([usernameValid boolValue]&&[passwordValid boolValue]);
                    }];

分割:一個信號可以有很多subscriber,也就是作為很多後續步驟的源

RACSignal *signal = self.usernameTextField.rac_textSignal;
    [signal subscribeNext:^(id x) {
        NSLog(@"1111");
    }];
    [signal subscribeNext:^(id x) {
        NSLog(@"2222");
    }];
}

RAC設置Button的ControlEvents

[[self.signInButton
     rac_signalForControlEvents:UIControlEventTouchUpInside]
     subscribeNext:^(id x) {
     NSLog(@"button click");
}];
  rac_signalForControlEvents
登陸功能舉例說明
需要實現登陸功能需要點擊登陸button
- (RACSignal *)signInSignal {
    return [RACSignal createSignal:^RACDisposable *(id subscriber){
     [self.signInService 
     signInWithUsername:self.usernameTextField.text
               password:self.passwordTextField.text
               complete:^(BOOL success){
                    [subscriber sendNext:@(success)];
                    [subscriber sendCompleted];
       }];
     return nil;
    }];
}
[[[[self.signInButton
     rac_signalForControlEvents:UIControlEventTouchUpInside]
     doNext:^(id x){
       self.signInButton.enabled =NO;
       self.signInFailureText.hidden =YES;
     }]

    flattenMap:^id(id x){        
        return[self signInSignal];
    }]

    subscribeNext:^(NSNumber*signedIn){
    self.signInButton.enabled =YES;
    BOOL success =[signedIn boolValue];
    self.signInFailureText.hidden = success;
    if(success){
        [self performSegueWithIdentifier:@"signInSuccess" sender:self];
    }
}];

flattenMap:[self signInSignal]返回的也是signal,所以是信號中的信號,使用這個操作把按鈕點擊事件轉換為登錄信號,同時還從內部信號發送事件到外部信號。

 

doNext:為一個附加操作,在一個next事件發生時執行的邏輯,而該邏輯並不改變事件本身。

  流程

RAC高級使用


error 和 completed,節流,線程,延伸,其他

記憶體管理

ReactiveCocoa設計的一個目標就是支持匿名生成管道這種編程風格。到目前為止,在你所寫的所有響應式代碼中,這應該是很直觀的。
為了支持這種模型,ReactiveCocoa自己持有全局的所有信號。如果一個signal有一個或多個訂閱者,那這個signal就是活躍的。如果所有的訂閱者都被移除了,那這個信號就能被銷毀了。

如何取消訂閱一個signal?
在一個completed或者error事件之後,訂閱會自動移除。你還可以通過RACDisposable 手動移除訂閱。

RACSignal的訂閱方法都會返回一個RACDisposable實例,它能讓你通過dispose方法手動移除訂閱。這個方法並不常用到,但是還是有必要知道可以這樣做。

RACSignal *backgroundColorSignal =
  [self.searchText.rac_textSignal 
      map:^id(NSString *text) { 
          return [self isValidSearchText:text] ? 
              [UIColor whiteColor] : [UIColor yellowColor]; 
  }]; 

RACDisposable *subscription = 
[backgroundColorSignal 
    subscribeNext:^(UIColor *color) {
        self.searchText.backgroundColor = color; 
}]; 

[subscription dispose];​

避免迴圈引用
在ReactiveCocoa中提供了避免迴圈引用的方法
@weakify巨集讓你創建一個弱引用的影子對象(如果你需要多個弱引用,你可以傳入多個變數),
@strongify讓你創建一個對之前傳入@weakify對象的強引用。

@weakify(self) 
[[self.searchText.rac_textSignal 
map:^id(NSString *text) { 
    return [self isValidSearchText:text] ? 
        [UIColor whiteColor] : [UIColor yellowColor]; 
}] 
subscribeNext:^(UIColor *color) { 
    @strongify(self) 
    self.searchText.backgroundColor = color; 
}];​

signal能發送3種不同類型的事件
Next
Completed
Error

當應用獲取訪問社交媒體賬號的許可權時,用戶會看見一個彈框。這是一個非同步操作,因此把這封裝進一個signal是很好的選擇

-(RACSignal *)requestAccessToTwitterSignal {
// 1 - define an error 
NSError *accessError = [NSError errorWithDomain:RWTwitterInstantDomain 
                                           code:RWTwitterInstantErrorAccessDenied 
                                       userInfo:nil];
                                   
// 2 - create the signal 
@weakify(self) 
return [RACSignal createSignal:^RACDisposable *(id subscriber) { 
    // 3 - request access to twitter 
    @strongify(self) 
    [self.accountStore requestAccessToAccountsWithType:self.twitterAccountType 
           options:nil 
        completion:^(BOOL granted, NSError *error) {
        // 4 - handle the response 
        if (!granted) { 
           [subscriber sendError:accessError]; 
        } else { 
            [subscriber sendNext:nil]; 
            [subscriber sendCompleted]; 
        } 
    }]; 
return nil; 
}]; 
}​

then:then方法會等待completed事件的發送,然後再訂閱由then block返回的signal。這樣就高效地把控制權從一個signal傳遞給下一個。

[[[[self requestAccessToTwitterSignal] 
then:^RACSignal *{ 
    @strongify(self) 
    return self.searchText.rac_textSignal; 
}] 
filter:^BOOL(NSString *text) { 
    @strongify(self) 
    return [self isValidSearchText:text]; 
}] 
subscribeNext:^(id x) { 
    NSLog(@"%@", x); 
} error:^(NSError *error) { 
    NSLog(@"An error occurred: %@", error); 
}];​
  then

實時搜索內容方法

  • 創建請求鏈接方法

 

-(SLRequest *)requestforTwitterSearchWithText:(NSString *)text { 
NSURL *url = [NSURL URLWithString:@"https://api.twitter.com/1.1/search/tweets.json"]; 
NSDictionary *params = @{@"q" : text}; 
SLRequest *request = [SLRequest   requestForServiceType:SLServiceTypeTwitter 
                                    requestMethod:SLRequestMethodGET 
                                              URL:url 
                                       parameters:params]; 
return request; 
}​
  • 創建請求signal
-(RACSignal *)signalForSearchWithText:(NSString *)text { 
// 1 - define the errors 
NSError *noAccountsError = [NSError errorWithDomain:RWTwitterInstantDomain 
                                               code:RWTwitterInstantErrorNoTwitterAccounts 
                                           userInfo:nil]; 
NSError *invalidResponseError = [NSError errorWithDomain:RWTwitterInstantDomain 
                                                    code:RWTwitterInstantErrorInvalidResponse 
                                                    userInfo:nil]; 
                                                    
// 2 - create the signal block 
@weakify(self) 
return [RACSignal createSignal:^RACDisposable *(id subscriber) { 
    @strongify(self); 
    
    // 3 - create the request 
    SLRequest *request = [self requestforTwitterSearchWithText:text]; 
    
    // 4 - supply a twitter account 
    NSArray *twitterAccounts = [self.accountStore accountsWithAccountType:self.twitterAccountType];       
    if (twitterAccounts.count == 0) { 
        [subscriber sendError:noAccountsError]; 
    } else { 
        [request setAccount:[twitterAccounts lastObject]]; 
        
    // 5 - perform the request 
    [request performRequestWithHandler: ^(NSData *responseData, 
            NSHTTPURLResponse *urlResponse, NSError *error) { 
        if (urlResponse.statusCode == 200) { 
        
            // 6 - on success, parse the response 
            NSDictionary *timelineData = [NSJSONSerialization JSONObjectWithData:responseData 
                                            options:NSJSONReadingAllowFragments 
                                              error:nil]; 
            [subscriber sendNext:timelineData]; 
            [subscriber sendCompleted]; 
        } else { 
            // 7 - send an error on failure 
            [subscriber sendError:invalidResponseError]; 
        } 
    }]; 
} 
return nil; 
}];
}
  • 使用flattenMap來把每個next事件映射到一個新的signal
[[[[[self requestAccessToTwitterSignal] 
then:^RACSignal *{ 
    @strongify(self) 
    return self.searchText.rac_textSignal; 
}] 
filter:^BOOL(NSString *text) { 
    @strongify(self) 
    return [self isValidSearchText:text]; 
}] 
flattenMap:^RACStream *(NSString *text) { 
    @strongify(self) 
    return [self signalForSearchWithText:text]; 
}] 
subscribeNext:^(id x) { 
    NSLog(@"%@", x); 
} error:^(NSError *error) { 
    NSLog(@"An error occurred: %@", error); 
}];

線程

在subscribeNext:error:中的數據沒有在主線程(Thread 1)中執行,更新UI只能在主線程中執行,所以更新UI需要轉到主線程中執行。

要怎麼更新UI呢?
通常的做法是使用操作隊列但是ReactiveCocoa有更簡單的解決辦法,在flattenMap:之後添加一個deliverOn:操作就可以轉到主線程上了。
:如果你看一下RACScheduler類,就能發現還有很多選項,比如不同的線程優先順序,或者在管道中添加延遲。

[[[[[[self requestAccessToTwitterSignal] 
then:^RACSignal *{ 
    @strongify(self) 
    return self.searchText.rac_textSignal; 
}] 
filter:^BOOL(NSString *text) { 
    @strongify(self) 
    return [self isValidSearchText:text]; 
}] 
flattenMap:^RACStream *(NSString *text) { 
    @strongify(self) 
    return [self signalForSearchWithText:text]; 
}] 
deliverOn:[RACScheduler mainThreadScheduler]] 
subscribeNext:^(id x) { 
    NSLog(@"%@", x); 
} error:^(NSError *error) { 
    NSLog(@"An error occurred: %@", error); 
}];

非同步載入圖片

-(RACSignal *)signalForLoadingImage:(NSString *)imageUrl { 
RACScheduler *scheduler = [RACScheduler 
    schedulerWithPriority:RACSchedulerPriorityBackground]; 
    
return [[RACSignal createSignal:^RACDisposable *(id subscriber) { 
    NSData *data = [NSData dataWithContentsOfURL:[NSURL URLWithString:imageUrl]]; 
    UIImage *image = [UIImage imageWithData:data]; 
    [subscriber sendNext:image]; 
    [subscriber sendCompleted]; 
    return nil; 
}] subscribeOn:scheduler]; 
}

首先獲取一個後臺scheduler,來讓signal不在主線程執行。然後,創建一個signal來下載圖片數據,當有訂閱者時創建一個UIImage。最後是subscribeOn:來確保signal在指定的scheduler上執行。

 -(UITableViewCell *)tableView:(nonnull UITableView *)tableView cellForRowAtIndexPath:(nonnull NSIndexPath *)indexPath {
UITableViewCell * cell = [tableView dequeueReusableCellWithIdentifier:@"TableViewCell"];
[[[[self signalForLoadingImage:tweet.profileImageUrl] 
takeUntil:cell.rac_prepareForReuseSignal] 
deliverOn:[RACScheduler mainThreadScheduler]] 
subscribeNext:^(UIImage *image) { 
    cell.twitterAvatarView.image = image; 
}];
return cell;
}

cell是重用的,可能有臟數據,所以上面的代碼首先重置圖片。然後創建signal來獲取圖片數據。你之前也遇到過deliverOn:這一步,它會把next事件發送到主線程,這樣subscribeNext:block就能安全執行了。

cell.rac_prepareForReuseSignal:Cell復用時的清理。
takeUntil:當給定的signal完成前一直取值

節流

每次輸入一個字,搜索都會馬上執行。如果你輸入很快(或者只是一直按著刪除鍵),這可能會造成應用在一秒內執行好幾次搜索,這很不理想。
更好的解決方法是,當搜索文本在短時間內,比如說500毫秒,不再變化時,再執行搜索。
在filter之後添加一個throttle步驟:

[[[[[[[self requestAccessToTwitterSignal] 
then:^RACSignal *{ 
    @strongify(self) 
    return self.searchText.rac_textSignal; 
}] 
filter:^BOOL(NSString *text) { 
    @strongify(self) 
    return [self isValidSearchText:text]; 
}] 
throttle:0.5] 
flattenMap:^RACStream *(NSString *text) { 
    @strongify(self) 
    return [self signalForSearchWithText:text]; 
}] 
deliverOn:[RACScheduler mainThreadScheduler]] 
subscribeNext:^(NSDictionary *jsonSearchResult) { 
    NSArray *statuses = jsonSearchResult[@"statuses"]; 
    NSArray *tweets = [statuses linq_select:^id(id tweet) { 
        return [RWTweet tweetWithStatus:tweet]; 
    }]; 
    [self.resultsViewController displayTweets:tweets]; 
} error:^(NSError *error) { 
    NSLog(@"An error occurred: %@", error); 
}];

throttle:只有當前一個next事件在指定的時間段內沒有被接收到後,throttle操作才會發送next事件。

代替代理

如果想在其他地方監聽到tableView的代理信息則需要設置如下方法

[[tableView rac_signalForSelector:@selector(tableView:didSelectRowAtIndexPath:) fromProtocol:@protocol(UITableViewDelegate) ] subscribeNext:^(RACTuple * x) {
    NSLog(@"點擊了");
}];

rac_signalForSelector: fromProtocol: 要先綁定在設置代理



作者:PHM
鏈接:https://www.jianshu.com/p/e99cb4310482
來源:簡書
著作權歸作者所有。商業轉載請聯繫作者獲得授權,非商業轉載請註明出處。

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

-Advertisement-
Play Games
更多相關文章
  • 尊重勞動成果,轉載請標明出處:http://www.cnblogs.com/tangZH/p/8419053.html 在做項目的過程中,遇到了一個奇怪的現象,我設置RelativeLayout為的寬度為wrap_content,而且RelativeLayout裡面的組件也設置了固定大小,可是Rel ...
  • 尊重勞動成果,轉載請標明出處:http://www.cnblogs.com/tangZH/p/8419010.html 我們在項目中經常需在一個listview中展示不一樣的佈局,我們可以在adapter的getView()中根據position來決定該展示哪些佈局。 我在項目中便是如此,第一個it ...
  • 前言:圖片選擇器基本上是每個App必備的東西,用公認好的第三方也可以,但是自己寫的改起來方便,用起來順手,而且這東西想想可能沒動手之前想想比較難,實際操作起來就很簡單了,這次先主要寫流程,具體優化的細節以後在寫。 難點:動手之前最困惑的問題就是怎麼獲取到手機里所有的圖片,獲取到之後,顯示出來,處理邏 ...
  • 前段時間弄了一款安卓電視盒子的遠程遙控輸入法APP:TVRemoteIME,此APP實現了遠程跨屏的輸入、遙控和應用管理功能。 最近發現盒子上要播放電影資源除了買APP會員之外,能直接免費播放電影的第三方APP越來越少了,要麼更新不及時要麼電影資源非常的少或者廣告繁多。而在電腦上要找一部電影播放還是... ...
  • LSAFHTTPClient 對所有請求進行統一管理,添加了兩個隊列:一個工作隊列,一個讀寫緩存的io隊列 LSAFHTTPClient.h 代碼如下: LSAFHTTPClient.m 代碼如下: LSBaseRequest 實現網路層的統一請求,在子類請求中調用基類的請求方法。 LSBaseRe ...
  • CGAffineTransform 概述 CGAffineTransform是一個用於處理形變的類,其可以改變控制項的平移、縮放、旋轉等,其坐標系統採用的是二維坐標系,即向右為x軸正方向,向下為y軸正方向 在UIView中有一個transform屬性便是專門用來控制形變的,其使用方法如下 樣例素材 在 ...
  • 一、Block定義 Block可以理解為一個函數指針(即它是一個指針,指向某個函數) returnType (^blockName) (parameter list) = ^ (parameter list) {代碼塊}; 說明: returnType:block的返回類型,可定義為void; bl ...
  • 數字證書: 是數字形式的標識,與我們的護照和駕駛證十分相似。它是由證書頒發機構(CA)的權威機構頒發的,由該權威機構擔保證書信息的有效性。因此數字證書只有在特定的時間段內有效。 證書中包含了證書中所標識的公鑰,也就是說證書里包含了你的公鑰,公鑰與你個人信息相匹配,證書會找到用戶對應的自己的公鑰,並確 ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...