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
  • 示例項目結構 在 Visual Studio 中創建一個 WinForms 應用程式後,項目結構如下所示: MyWinFormsApp/ │ ├───Properties/ │ └───Settings.settings │ ├───bin/ │ ├───Debug/ │ └───Release/ ...
  • [STAThread] 特性用於需要與 COM 組件交互的應用程式,尤其是依賴單線程模型(如 Windows Forms 應用程式)的組件。在 STA 模式下,線程擁有自己的消息迴圈,這對於處理用戶界面和某些 COM 組件是必要的。 [STAThread] static void Main(stri ...
  • 在WinForm中使用全局異常捕獲處理 在WinForm應用程式中,全局異常捕獲是確保程式穩定性的關鍵。通過在Program類的Main方法中設置全局異常處理,可以有效地捕獲並處理未預見的異常,從而避免程式崩潰。 註冊全局異常事件 [STAThread] static void Main() { / ...
  • 前言 給大家推薦一款開源的 Winform 控制項庫,可以幫助我們開發更加美觀、漂亮的 WinForm 界面。 項目介紹 SunnyUI.NET 是一個基於 .NET Framework 4.0+、.NET 6、.NET 7 和 .NET 8 的 WinForm 開源控制項庫,同時也提供了工具類庫、擴展 ...
  • 說明 該文章是屬於OverallAuth2.0系列文章,每周更新一篇該系列文章(從0到1完成系統開發)。 該系統文章,我會儘量說的非常詳細,做到不管新手、老手都能看懂。 說明:OverallAuth2.0 是一個簡單、易懂、功能強大的許可權+可視化流程管理系統。 有興趣的朋友,請關註我吧(*^▽^*) ...
  • 一、下載安裝 1.下載git 必須先下載並安裝git,再TortoiseGit下載安裝 git安裝參考教程:https://blog.csdn.net/mukes/article/details/115693833 2.TortoiseGit下載與安裝 TortoiseGit,Git客戶端,32/6 ...
  • 前言 在項目開發過程中,理解數據結構和演算法如同掌握蓋房子的秘訣。演算法不僅能幫助我們編寫高效、優質的代碼,還能解決項目中遇到的各種難題。 給大家推薦一個支持C#的開源免費、新手友好的數據結構與演算法入門教程:Hello演算法。 項目介紹 《Hello Algo》是一本開源免費、新手友好的數據結構與演算法入門 ...
  • 1.生成單個Proto.bat內容 @rem Copyright 2016, Google Inc. @rem All rights reserved. @rem @rem Redistribution and use in source and binary forms, with or with ...
  • 一:背景 1. 講故事 前段時間有位朋友找到我,說他的窗體程式在客戶這邊出現了卡死,讓我幫忙看下怎麼回事?dump也生成了,既然有dump了那就上 windbg 分析吧。 二:WinDbg 分析 1. 為什麼會卡死 窗體程式的卡死,入口門檻很低,後續往下分析就不一定了,不管怎麼說先用 !clrsta ...
  • 前言 人工智慧時代,人臉識別技術已成為安全驗證、身份識別和用戶交互的關鍵工具。 給大家推薦一款.NET 開源提供了強大的人臉識別 API,工具不僅易於集成,還具備高效處理能力。 本文將介紹一款如何利用這些API,為我們的項目添加智能識別的亮點。 項目介紹 GitHub 上擁有 1.2k 星標的 C# ...