RAC


RAC中類的繼承關系

1.RACDynamicSignal:RACSignal:RACStream
2.RACSequence:RACStream
3.RACCompoundDisposable : RACDisposable
4.RACSubscriptionScheduler : RACScheduler
5.RACQueueScheduler : RACScheduler


RAC優勢(提高開發效率)

1.把target-Action/代理/通知/KVO統一封裝成信號,即信號就可以實現這些功能。
2.監聽屬性的代碼少
3.代碼直觀,代碼復雜度減小m3u8
4.通過block降低代碼間的耦合


訂閱和訂閱者

  • subscribeNext:訂閱
    subscribeNext:^(id x) { }
  • subscriber:訂閱者,例如訂閱者調用sendNext方法。
    [subscriber sendNext:responseObject];

25個事例+1個小項目幫助你解開RAC的面紗


1.監聽UITextField內容的輸入信號

#import "ViewController.h"
#import <ReactiveCocoa/ReactiveCocoa.h>
@interface ViewController ()
@property (weak, nonatomic) IBOutlet UITextField *usernameField;

@end

@implementation ViewController
// rac給每一個UI控件都增加了分類,在分類里面給UI控件擴充了新的方法。利用rac編程時用到的就是這個方法
- (void)viewDidLoad {
    [super viewDidLoad];
    
    [self Username];// 監聽UITextField內容的輸入信號

}
-(void)Username{

    /*  1+2可以寫成一行代碼
     [self.usernameField.rac_textSignal subscribeNext:^(id x) {/
     NSLog(@"%@",x);
     }];
     */
    
    // 文本信號:監聽UITextField內容的輸入
    RACSignal *textSignal = self.usernameField.rac_textSignal;// 1
   // 訂閱者block
    [textSignal subscribeNext:^(id x) {// 2
        NSLog(@"%@",x);
        
    }];
    
}

@end

截圖

101.1.gif

2.監聽按鈕的點擊信號(信號理解為事件)

#import "ViewController.h"
#import <ReactiveCocoa/ReactiveCocoa.h>
@interface ViewController ()
@property (weak, nonatomic) IBOutlet UIButton *login;

@end

@implementation ViewController
// rac給每一個UI控件都增加了分類,在分類里面給UI控件擴充了新的方法。利用rac編程時用到的就是這個方法
- (void)viewDidLoad {
    [super viewDidLoad];
    
 
    [self  Login];// 監聽按鈕的點擊信號(信號理解為事件)
}

 -(void)Login{
    /* 1+2可以寫成一行代碼
    [[self.login rac_signalForControlEvents:UIControlEventTouchUpInside]subscribeNext:
    ^(id x) {
        NSLog(@"%@",x);
    }];
    */
  RACSignal *clickSignal = [self.login rac_signalForControlEvents:UIControlEventTouchUpInside];// 1
    // 訂閱者block
    [clickSignal subscribeNext:^(id x) { // 2
        NSLog(@"%@",x);
    }];
}

@end

截圖

101.2.gif

3.取消按鈕的點擊信號

#import "ViewController.h"
#import <ReactiveCocoa/ReactiveCocoa.h>
@interface ViewController ()

@property (weak, nonatomic) IBOutlet UIButton *login;
@property(nonatomic,strong)RACDisposable *btnDisposable;
@end

@implementation ViewController
// rac給每一個UI控件都增加了分類,在分類里面給UI控件擴充了新的方法。利用rac編程時用到的就是這個方法
- (void)viewDidLoad {
    [super viewDidLoad];
    
    [self Dispose];// 取消按鈕的點擊信號。先點擊屏幕,然后再點擊按鈕,發現沒有打印。因為點擊信號已經取消了,點擊多少次都不會打印。
}

-(void)Dispose{
    
   self.btnDisposable = [[self.login  rac_signalForControlEvents:UIControlEventTouchUpInside] subscribeNext:^(id x) {
        NSLog(@"%@",x);
    }];
    
}

-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    [self.btnDisposable dispose];
}
@end

截圖

101.3.gif

4.信號的map方法

#import "ViewController.h"
#import <ReactiveCocoa/ReactiveCocoa.h>
@interface ViewController ()
@property (weak, nonatomic) IBOutlet UITextField *usernameField;

@end

@implementation ViewController
// rac給每一個UI控件都增加了分類,在分類里面給UI控件擴充了新的方法。利用rac編程時用到的就是這個方法
- (void)viewDidLoad {
    [super viewDidLoad];
    [self Map];// 信號的map方法
}

-(void)Map{
    // 文本信號:監聽UITextField內容的輸入
    RACSignal *textSignal = self.usernameField.rac_textSignal;
    // 調用map方法:更改訂閱者block輸出的參數的類型
    textSignal = [textSignal map:^id(id value){
    return [NSString stringWithFormat:@"username = %@",value];
    }];
    // 訂閱者block
    [textSignal subscribeNext:^(id x) {
        NSLog(@"%@",x);
    }];
}
@end

截圖

101.4.gif

5.信號的filter方法

#import "ViewController.h"
#import <ReactiveCocoa/ReactiveCocoa.h>
@interface ViewController ()
@property (weak, nonatomic) IBOutlet UITextField *usernameField;

@end

@implementation ViewController
// rac給每一個UI控件都增加了分類,在分類里面給UI控件擴充了新的方法。利用rac編程時用到的就是這個方法
- (void)viewDidLoad {
    [super viewDidLoad];
    [self filterText];// 信號的filter方法
}
-(void)filterText{
    // 文本信號:監聽UITextField內容的輸入
    RACSignal *textSignal = self.usernameField.rac_textSignal;
    textSignal = [textSignal filter:^BOOL(NSString *value) {// 將(id value)改為(NSString *value),因為我們已經確定了輸入的內容是字符串(即使輸入的是數字也行)
        NSLog(@"filter %@",value);
        // 當輸入的內容大于5時。才會調用訂閱者block
        return value.length > 5;
    }];
    // 訂閱者block
    [textSignal subscribeNext:^(id x) {
        NSLog(@"輸出%@",x);// 只有當輸入的個數大于5時才打印這行內容
    }];
    
}
@end

截圖

101.5.gif

6.組合信號combineLatest:reduce:

#import "ViewController.h"
#import <ReactiveCocoa/ReactiveCocoa.h>
@interface ViewController ()
@property (weak, nonatomic) IBOutlet UITextField *usernameField;
@property (weak, nonatomic) IBOutlet UIButton *login;
@property (weak, nonatomic) IBOutlet UITextField *pwd;

@property(nonatomic,strong)RACDisposable *btnDisposable;
@end

@implementation ViewController
// rac給每一個UI控件都增加了分類,在分類里面給UI控件擴充了新的方法。利用rac編程時用到的就是這個方法
- (void)viewDidLoad {
    [super viewDidLoad];

    [self combine]; // 組合信號combineLatest:reduce:
    
}

-(void)combine{
    NSArray *signals = @[self.usernameField.rac_textSignal,self.pwd.rac_textSignal];
    
    // 組合信號
    RACSignal *combineSignal = [RACSignal combineLatest:signals reduce:^id(NSString *username,NSString *password){
        // 無論用戶名和密碼有沒有值,都會執行訂閱block,只是返回的值有0和1,讓登錄按鈕變為點擊狀態的話,x必須為1.
        return @(username.length > 0 && password.length >0);
        
    }];
    // 訂閱者block
    [combineSignal subscribeNext:^(id x) {
        NSLog(@"%@",x);
        // x為1時,登錄按鈕才可以點擊
        self.login.enabled = [x boolValue];
    }];
}
@end

截圖

101.6.gif

7.創建一個用于封裝自定義的網絡請求的信號

#import "ViewController.h"
#import <ReactiveCocoa/ReactiveCocoa.h>
@interface ViewController ()
@end

@implementation ViewController
// rac給每一個UI控件都增加了分類,在分類里面給UI控件擴充了新的方法。利用rac編程時用到的就是這個方法
- (void)viewDidLoad {
    [super viewDidLoad];

    [self net];// 創建信號,用于封裝自定義的網絡請求
    
}

-(void)net{
    // 創建信號,用于封裝自定義的網絡請求。即:把網絡請求的代碼封裝在createSignal的代碼塊中
    RACSignal *ipSignal = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
        // 1.創建請求對象
        NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:@"http://pv.sohu.com/cityjson"]];
        // 2.創建下載任務
        NSURLSessionDataTask *dataTask = [[NSURLSession sharedSession] dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
            
            // data不是UTF-8編碼,而是GBK編碼
            if (error == nil) {// 沒錯誤就執行
                //將data對象轉換成字符串
                NSStringEncoding enc = CFStringConvertEncodingToNSStringEncoding(kCFStringEncodingGB_18030_2000);
                NSString *responseStr = [[NSString alloc] initWithData:data encoding:enc];

                // 調用sendNext,底層就會調用subscribeNext.只有調用了subscribeNext,才會調用訂閱者block,才會執行block中的內容,從而打印responseStr中的內容,然后接著會打印x中的內容。沒有這句代碼,什么也不會打印
                [subscriber sendNext:responseStr];//A:調用subscribeNext的block
                [subscriber sendNext:@"CoderZb"];// B:調用subscribeNext的block
                
                // 注意:調用sendCompleted或者sendError,程序會終止向下執行,所以當C出現,D不執行。反之亦然。
                [subscriber sendCompleted];// C:調用sendCompleted的block
                // 模擬錯誤信息
                 NSError *ErrorInfo = [NSError errorWithDomain:@"服務未啟動" code:2016 userInfo:nil];                [subscriber sendError:ErrorInfo];//D:調用error的block
            }else{
                NSLog(@"+++++%@",error);
            }
        }];
        // 3.執行下載任務
        [dataTask resume];
        return nil;
        
    }];
    
        // 訂閱者block
        [ipSignal subscribeNext:^(id x) {// sendNext:調用的。
             NSLog(@"網絡請求成功,內容為%@",x);// 打印responseStr中的內容。驗證:將responseStr替換為@"CoderZb"
        } error:^(NSError *errorInfo){
            NSLog(@"%@",errorInfo);
        }
        completed:^{// sendCompleted調用的。
            NSLog(@"終止了訂閱");
        }];
}

@end

截圖

101.7.gif

8.信號嵌套

#import "ViewController.h"
#import <ReactiveCocoa/ReactiveCocoa.h>
@interface ViewController ()

@property (weak, nonatomic) IBOutlet UIButton *ipAddressBtn;

@property (weak, nonatomic) IBOutlet UILabel *ipLabel;
@end

@implementation ViewController
// rac給每一個UI控件都增加了分類,在分類里面給UI控件擴充了新的方法。利用rac編程時用到的就是這個方法
- (void)viewDidLoad {
    [super viewDidLoad];

    [self flatterMap];//信號嵌套

}

-(void)flatterMap{
    // 按鈕信號
    RACSignal *btnSignal = [self.ipAddressBtn rac_signalForControlEvents:UIControlEventTouchUpInside];
    [[btnSignal flattenMap:^RACStream *(id value) {
        return [self ipSignal];// 又嵌套了一個封裝網絡請求的信號
    }] subscribeNext:^(id x) {
          NSLog(@"%@",x);
        self.ipLabel.text = x;
    }];
}

-(RACSignal *)ipSignal{
    // 創建信號,用于封裝自定義的網絡請求。即:把網絡請求的代碼封裝在createSignal的代碼塊中
   return [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
        // 1.創建請求對象
        NSURLRequest *  request = [NSURLRequest requestWithURL:[NSURL URLWithString:@"http://pv.sohu.com/cityjson"]];
        // 2.創建下載任務
        NSURLSessionDataTask *dataTask = [[NSURLSession sharedSession] dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
//            NSLog(@"---%@",[NSThread currentThread]); // 通過打印可知,block代碼塊所在的線程為子線程,所以應將x,y處的代碼寫在主線程中,否則ipLabel上顯示不出來內容.因為顯示UI要在主線程(主隊列)執行
            // data不是UTF-8編碼,而是GBK編碼
            if (error == nil) {// 沒錯誤就執行
                //將data對象轉換成字符串
                NSStringEncoding enc = CFStringConvertEncodingToNSStringEncoding(kCFStringEncodingGB_18030_2000);
                NSString *responseStr = [[NSString alloc] initWithData:data encoding:enc];
                
               [[NSOperationQueue mainQueue] addOperationWithBlock:^{// 主隊列即主線程。驗證:打印下面的注釋
//                   NSLog(@"+++%@",[NSThread currentThread]);
                 [subscriber sendNext:responseStr];//x:調用subscribeNext的block
               }];
                
                
            }else{
                [[NSOperationQueue mainQueue] addOperationWithBlock:^{
                  NSLog(@"+++++%@",error);// y
                }];
                
            }
        }];
        // 3.執行下載任務
        [dataTask resume];
       return [RACDisposable disposableWithBlock:^{
           // 取消網絡請求
           [dataTask cancel];
       }];
    }];
}
@end

截圖

101.8.gif

9.信號注入

#import "ViewController.h"
#import <ReactiveCocoa/ReactiveCocoa.h>
@interface ViewController ()
@property (weak, nonatomic) IBOutlet UIButton *ipAddressBtn;

@property (weak, nonatomic) IBOutlet UILabel *ipLabel;

// 可視化創建UIActivityIndicatorView控件并勾選面板右側的Hides When Stopped,即靜態(停止)的時候隱藏該控件。
@property (weak, nonatomic) IBOutlet UIActivityIndicatorView *Indicator;

@end

@implementation ViewController
// rac給每一個UI控件都增加了分類,在分類里面給UI控件擴充了新的方法。利用rac編程時用到的就是這個方法
- (void)viewDidLoad {
    [super viewDidLoad];
    
    [self doNext];// 信號注入:doNext在網絡請求之前可以提前做一些操作,例如提前開始Indicator的動畫
}

-(void)doNext{

    RACSignal *btnSignal = [self.ipAddressBtn rac_signalForControlEvents:UIControlEventTouchUpInside ];
    [btnSignal doNext:^(id x) {
        // 開始Indicator的動畫,此時Indicator會顯示,因為在可視化面板右側勾選了Hides When Stopped
                [self.Indicator startAnimating];
    }];
    [[btnSignal flattenMap:^RACStream *(id value) {
        // 開始Indicator的動畫,此時Indicator會顯示,因為在可視化面板右側勾選了Hides When Stopped
                [self.Indicator startAnimating];
                // 執行網絡請求的信號
                return [self ipSignal];// 當有ruturn操作時,后面的內容不能再用btnSignal對象調用,否則最終的結果不是自己想要的。
    }] subscribeNext:^(id x) {
        self.ipLabel.text = x;
                // 停止Indicator的動畫.此時Indicator會隱藏,因為在可視化面板右側勾選了Hides When Stopped
                [self.Indicator stopAnimating];
    }];
    
}

-(void)flatterMap{
    RACSignal *btnSignal = [self.ipAddressBtn rac_signalForControlEvents:UIControlEventTouchUpInside];
    [[btnSignal flattenMap:^RACStream *(id value) {
        return [self ipSignal];
    }] subscribeNext:^(id x) {
          NSLog(@"%@",x);
        self.ipLabel.text = x;
    }];
}

-(RACSignal *)ipSignal{
    // 創建信號,用于封裝自定義的網絡請求。即:把網絡請求的代碼封裝在createSignal的代碼塊中
   return [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
        // 1.創建請求對象
        NSURLRequest *  request = [NSURLRequest requestWithURL:[NSURL URLWithString:@"http://pv.sohu.com/cityjson"]];
        // 2.創建下載任務
        NSURLSessionDataTask *dataTask = [[NSURLSession sharedSession] dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
//            NSLog(@"---%@",[NSThread currentThread]); // 通過打印可知,block代碼塊所在的線程為子線程,所以應將x,y處的代碼寫在主線程中,否則ipLabel上顯示不出來內容.因為顯示UI要在主線程(主隊列)執行
            // data不是UTF-8編碼,而是GBK編碼
            if (error == nil) {// 沒錯誤就執行
                //將data對象轉換成字符串
                NSStringEncoding enc = CFStringConvertEncodingToNSStringEncoding(kCFStringEncodingGB_18030_2000);
                NSString *responseStr = [[NSString alloc] initWithData:data encoding:enc];
                
               [[NSOperationQueue mainQueue] addOperationWithBlock:^{// 主隊列即主線程。驗證:打印下面的注釋
//                   NSLog(@"+++%@",[NSThread currentThread]);
                 [subscriber sendNext:responseStr];//x:調用subscribeNext的block
               }];
                
                
            }else{
                [[NSOperationQueue mainQueue] addOperationWithBlock:^{
                  NSLog(@"+++++%@",error);// y
                }];
                
            }
        }];
        // 3.執行下載任務
        [dataTask resume];
       return [RACDisposable disposableWithBlock:^{
           // 取消網絡請求
           [dataTask cancel];
       }];
    }];
}

@end

截圖

101.9.gif

10.信號延遲

#import "ViewController.h"
#import <ReactiveCocoa/ReactiveCocoa.h>
@interface ViewController ()

@property (weak, nonatomic) IBOutlet UIButton *ipAddressBtn;
@end

@implementation ViewController
// rac給每一個UI控件都增加了分類,在分類里面給UI控件擴充了新的方法。利用rac編程時用到的就是這個方法
- (void)viewDidLoad {
    [super viewDidLoad];
    [self throttle];// 信號延遲:訂閱block中的內容延時輸出
}

-(void)throttle{
  
    
     RACSignal *btnSignal = [self.ipAddressBtn rac_signalForControlEvents:UIControlEventTouchUpInside ];
    // 延遲5秒輸出block中的內容
    [[btnSignal throttle:5] subscribeNext:^(id x) {
        NSLog(@"%@",x);
    }];
    
}
@end

截圖

101.10.gif

11.信號串聯

#import "ViewController.h"
#import <ReactiveCocoa/ReactiveCocoa.h>
@interface ViewController ()

@end

@implementation ViewController
// rac給每一個UI控件都增加了分類,在分類里面給UI控件擴充了新的方法。利用rac編程時用到的就是這個方法
- (void)viewDidLoad {
    [super viewDidLoad];

    [self concat];// 信號串聯(一條線程),任務依次執行.需要執行sendCompleted操作
}

-(void)concat{
    //調用sendCompleted,表示信號的完成。只有信號完成了,才能執行串聯操作。就比如串行隊列,只有前一個任務執行完成了,后面的任務才能執行。如何判斷任務完成了,類比Rac中的sendCompleted操作。
    // 起床信號
    RACSignal *getup =[RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
        [subscriber sendNext:@"正在穿衣服"];
        [subscriber sendNext:@"起床"];
        [subscriber sendCompleted];// 信號串聯,必須在兩個信號之間執行sendCompleted操作
        return nil;
    }];
    
    // 吃飯信號
    RACSignal *eat  =[RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
        [subscriber sendNext:@"正在吃飯"];
        [subscriber sendNext:@"吃完飯了"];
        return nil;
    }];
    
    // 信號串聯
    [[getup concat:eat] subscribeNext:^(id x) {
        NSLog(@"%@",x);
    }];
    
}

@end

截圖

101.11.gif

12.信號并聯

#import "ViewController.h"
#import <ReactiveCocoa/ReactiveCocoa.h>
@interface ViewController ()

@end

@implementation ViewController
// rac給每一個UI控件都增加了分類,在分類里面給UI控件擴充了新的方法。利用rac編程時用到的就是這個方法
- (void)viewDidLoad {
    [super viewDidLoad];
    [self merge];// 信號并聯(多條線程),任務同時執行.不用執行sendCompleted操作
}

-(void)merge{
    
    // 起床信號
    RACSignal *getup =[RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
        [subscriber sendNext:@"正在穿衣服"];
        [subscriber sendNext:@"起床"];
        return nil;
    }];
    
    // 吃飯信號
    RACSignal *eat  =[RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
        [subscriber sendNext:@"正在吃飯"];
        [subscriber sendNext:@"吃完飯了"];
        return nil;
    }];
    
    // 信號并聯
    [[getup merge:eat] subscribeNext:^(id x) {
        NSLog(@"%@",x);
    }];

}

@end

截圖

101.12.gif

13.信號忽略

#import "ViewController.h"
#import <ReactiveCocoa/ReactiveCocoa.h>
@interface ViewController ()
@end

@implementation ViewController
// rac給每一個UI控件都增加了分類,在分類里面給UI控件擴充了新的方法。利用rac編程時用到的就是這個方法
- (void)viewDidLoad {
    [super viewDidLoad];

    [self then];// 信號忽略(一條線程),任務依次執行.忽略的是then:前面的信號,執行then后面的信號,所以then前面的信號不執行,then后面的信號依次執行.需要執行sendCompleted操作
}

-(void)then{
    // 起床信號
    RACSignal *getup =[RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
        [subscriber sendNext:@"正在穿衣服"];
        [subscriber sendNext:@"起床"];
        [subscriber sendCompleted];
        return nil;
    }];
    
    // 吃飯信號
    RACSignal *eat  =[RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
        [subscriber sendNext:@"正在吃飯"];
        [subscriber sendNext:@"吃完飯了"];
        return nil;
    }];
    
    // 信號忽略.忽略了getup信號
    [[getup then:^RACSignal *{
        return eat;
    }] subscribeNext:^(id x) {
        NSLog(@"%@",x);
    }];

}

@end

截圖

101.13.gif

14.信號延遲

#import "ViewController.h"
#import <ReactiveCocoa/ReactiveCocoa.h>
@interface ViewController ()

@property (weak, nonatomic) IBOutlet UIButton *ipAddressBtn;

@end

@implementation ViewController
// rac給每一個UI控件都增加了分類,在分類里面給UI控件擴充了新的方法。利用rac編程時用到的就是這個方法
- (void)viewDidLoad {
    [super viewDidLoad];

    [self delay];// 信號延遲:等同throttle
}

-(void)delay{
    RACSignal *btnSignal = [self.ipAddressBtn rac_signalForControlEvents:UIControlEventTouchUpInside ];
    // 點擊按鈕,延遲5秒執行
    [[btnSignal delay:5] subscribeNext:^(id x) {
        NSLog(@"%@",x);
    }];

    
}
@end

截圖

101.14.gif

15.信號超時

#import "ViewController.h"
#import <ReactiveCocoa/ReactiveCocoa.h>
@interface ViewController ()

@end

@implementation ViewController
// rac給每一個UI控件都增加了分類,在分類里面給UI控件擴充了新的方法。利用rac編程時用到的就是這個方法
- (void)viewDidLoad {
    [super viewDidLoad];

    [self timeout];//信號超時.用于網絡請求的信號

}

-(void)timeout{
    // 過了7秒就超時,就不再執行訂閱者block.因為Signal方法中線程休眠了5秒,5秒<7秒,所以沒有超過超時的時間,所以會執行訂閱者block.如果線程休眠10秒,那么就不會執行訂閱者block
    [[[self Signal] timeout:7 onScheduler:[RACScheduler mainThreadScheduler]] subscribeNext:^(id x) {
        NSLog(@"%@",x);
    }];
}

-(RACSignal *)Signal{
    // 創建信號,用于封裝自定義的網絡請求。即:把網絡請求的代碼封裝在createSignal的代碼塊中
    return [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
        // 1.創建請求對象
        NSURLRequest *  request = [NSURLRequest requestWithURL:[NSURL URLWithString:@"http://pv.sohu.com/cityjson"]];
        // 2.創建下載任務
        NSURLSessionDataTask *dataTask = [[NSURLSession sharedSession] dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
            //            NSLog(@"---%@",[NSThread currentThread]); // 通過打印可知,block代碼塊所在的線程為子線程,所以應將x,y處的代碼寫在主線程中,否則ipLabel上顯示不出來內容.因為顯示UI要在主線程(主隊列)執行
            // data不是UTF-8編碼,而是GBK編碼
            if (error == nil) {// 沒錯誤就執行
                //將data對象轉換成字符串
                NSStringEncoding enc = CFStringConvertEncodingToNSStringEncoding(kCFStringEncodingGB_18030_2000);
                NSString *responseStr = [[NSString alloc] initWithData:data encoding:enc];
                #warning 線程休眠5秒
                [NSThread sleepForTimeInterval:10];
                [[NSOperationQueue mainQueue] addOperationWithBlock:^{// 主隊列即主線程。驗證:打印下面的注釋
                    // NSLog(@"+++%@",[NSThread currentThread]);
                    [subscriber sendNext:responseStr];//x:調用subscribeNext的block
                }];
                
                
            }else{
                [[NSOperationQueue mainQueue] addOperationWithBlock:^{
                    NSLog(@"+++++%@",error);// y
                }];
                
            }
        }];
        // 3.執行下載任務
        [dataTask resume];
        return [RACDisposable disposableWithBlock:^{
            // 取消網絡請求
            [dataTask cancel];
        }];
    }];
}
@end

截圖

101.15.gif

16.監聽選中的圖片,監聽選中圖片的手勢


#import "ViewController.h"
#import <ReactiveCocoa/ReactiveCocoa.h>
@interface ViewController ()
@property (weak, nonatomic) IBOutlet UIImageView *imageView;
@property(nonatomic,strong)RACDisposable *btnDisposable;
@end

@implementation ViewController

-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{

    static dispatch_once_t onceToken;
    // 一次性代碼,保證只執行一次.這樣第二次點擊屏幕時候不會執行這段代碼,但是3處的代碼會執行
    dispatch_once(&onceToken,^{        // 功能1:監聽選中的圖片
        UIImagePickerController *pickVC = [[UIImagePickerController alloc] init];
        pickVC.sourceType = UIImagePickerControllerSourceTypePhotoLibrary;
        // 通過信號獲取用戶選中的圖片
        [pickVC.rac_imageSelectedSignal subscribeNext:^(NSDictionary *Info) {
            //        NSLog(@"%@",x);
            // key根據上面的打印內容粘貼
            UIImage *selectImage = Info[@"UIImagePickerControllerOriginalImage"];
            self.imageView.image = selectImage;
            // 選中完圖片,直接到返回之前的控制器
            [self dismissViewControllerAnimated:YES completion:nil];
        }];
        [self presentViewController:pickVC animated:YES completion:nil];

    });
   
    // 功能2:監聽點擊圖片的手勢
    self.imageView.userInteractionEnabled = YES;
    UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc]init];
    [self.imageView addGestureRecognizer:tap];
    [tap.rac_gestureSignal subscribeNext:^(id x) {
        NSLog(@"圖片被點擊");
    }];
}
@end

截圖

101.16.gif

17.監聽鍵盤的彈出

#import "ViewController.h"
#import <ReactiveCocoa/ReactiveCocoa.h>
@interface ViewController ()
@property (weak, nonatomic) IBOutlet UITextField *usernameField;
@end

@implementation ViewController
// rac給每一個UI控件都增加了分類,在分類里面給UI控件擴充了新的方法。利用rac編程時用到的就是這個方法
- (void)viewDidLoad {
    [super viewDidLoad];
    
    
    // 功能3:監聽鍵盤的彈出
    NSNotificationCenter *notification = [NSNotificationCenter defaultCenter];
    [[notification rac_addObserverForName:UIKeyboardDidShowNotification object:nil] subscribeNext:^(id x) {
        NSLog(@"鍵盤彈出,鍵盤信息如下----%@",x);
    }];

}

@end

截圖

101.17.gif

18.監聽屬性值的改變

#import "ViewController.h"
#import <ReactiveCocoa/ReactiveCocoa.h>
#import "Person.h"
@interface ViewController ()
@property(nonatomic,strong)Person *person;
@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    [self kvo];// kvo監聽屬性的改變
    
   }

-(void)kvo{
    self.person = [[Person alloc] init];
    // kvo監聽屬性的改變,一旦屬性發生改變就會調用訂閱block.所以點擊一下屏幕,就會調用訂閱block一次。
    [RACObserve(self.person, name) subscribeNext:^(id x) {
        NSLog(@"%@",x);
    }];
    

}
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    self.person.name = @"CoderZb";// 修改name屬性
}
@end

截圖

101.18.gif

19.綁定屬性

#import "ViewController.h"
#import <ReactiveCocoa/ReactiveCocoa.h>
@interface ViewController ()

@property (weak, nonatomic) IBOutlet UILabel *ipLabel;

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    [self Bundle];// 綁定屬性.將修改的內容顯示到屬性上
}

-(void)Bundle{
    // 綁定ipLabel對象的text屬性,將[self ipSignal]返回的結果賦值給text屬性。相當于給系統的屬性賦值。
    RAC(self.ipLabel,text) = [self ipSignal];
}
-(RACSignal *)ipSignal{
    // 創建信號,用于封裝自定義的網絡請求。即:把網絡請求的代碼封裝在createSignal的代碼塊中
    return [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
        // 1.創建請求對象
        NSURLRequest *  request = [NSURLRequest requestWithURL:[NSURL URLWithString:@"http://pv.sohu.com/cityjson"]];
        // 2.創建下載任務
        NSURLSessionDataTask *dataTask = [[NSURLSession sharedSession] dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
            //            NSLog(@"---%@",[NSThread currentThread]); // 通過打印可知,block代碼塊所在的線程為子線程,所以應將x,y處的代碼寫在主線程中,否則ipLabel上顯示不出來內容.因為顯示UI要在主線程(主隊列)執行
            // data不是UTF-8編碼,而是GBK編碼
            if (error == nil) {// 沒錯誤就執行
                //將data對象轉換成字符串
                NSStringEncoding enc = CFStringConvertEncodingToNSStringEncoding(kCFStringEncodingGB_18030_2000);
                NSString *responseStr = [[NSString alloc] initWithData:data encoding:enc];
                
                #warning 線程休眠5秒
                [NSThread sleepForTimeInterval:5];
                [[NSOperationQueue mainQueue] addOperationWithBlock:^{// 主隊列即主線程。驗證:打印下面的注釋
                    //                   NSLog(@"+++%@",[NSThread currentThread]);
                    [subscriber sendNext:responseStr];//x:調用subscribeNext的block
                }];
                
                
            }else{
                [[NSOperationQueue mainQueue] addOperationWithBlock:^{
                    NSLog(@"+++++%@",error);// y
                }];
                
            }
        }];
        // 3.執行下載任務
        [dataTask resume];
        return [RACDisposable disposableWithBlock:^{
            // 取消網絡請求
            [dataTask cancel];
        }];
    }];
}

@end

截圖

101.19.gif

20.監聽UITextField文本信號的改變

#import "ViewController.h"
#import <ReactiveCocoa/ReactiveCocoa.h>
@interface ViewController ()
@property (weak, nonatomic) IBOutlet UITextField *usernameField;
@property (weak, nonatomic) IBOutlet UILabel *ipLabel;

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
   
    [self Change]; // 監聽UITextField文本信號的改變,只要文本發生改變,就會將改變的內容顯示在UILabel上
    
}

-(void)Change{
    // 監聽UITextField文本信號的改變,只要文本發生改變,就會將改變的內容添加到UILabel上
    RAC(self.ipLabel,text) = self.usernameField.rac_textSignal;
}
@end

截圖

101.20.gif

21.RAC中,解決block造成的循環引用的兩種做法

#import "ViewController.h"
#import <ReactiveCocoa/ReactiveCocoa.h>
#import "Person.h"
@interface ViewController ()
@property (weak, nonatomic) IBOutlet UITextField *usernameField;
@property (weak, nonatomic) IBOutlet UIButton *login;
@property (weak, nonatomic) IBOutlet UITextField *pwd;
@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    // 解決block造成的循環引用的兩種做法
    // 做法1:
    /*
    __weak typeof(self) weakSelf = self;
    [[RACSignal combineLatest:@[self.usernameField.rac_textSignal,self.pwd.rac_textSignal] reduce:^id(NSString *username,NSString *pwd){
        return @(username.length > 0 && pwd.length > 0);
    }] subscribeNext:^(id x) {
        weakSelf.login.enabled = [x boolValue];
    }];
     */
    // 做法2:
    
    @weakify(self)
    [[RACSignal combineLatest:@[self.usernameField.rac_textSignal,self.pwd.rac_textSignal] reduce:^id(NSString *username,NSString *pwd){
        return @(username.length > 0 && pwd.length > 0);
    }] subscribeNext:^(id x) {
        @strongify(self)
        self.login.enabled = [x boolValue];
    }];
    

}
-(void)dealloc{
    NSLog(@"%s",__func__);
}
@end

截圖

101.21.gif

22.(一)rac_command

#import "ViewController.h"
#import <ReactiveCocoa/ReactiveCocoa.h>
@interface ViewController ()
@property (weak, nonatomic) IBOutlet UIButton *ipAddressBtn;
@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
     [self btnClick];//rac_command
  
   }
-(void)btnClick{
    //  RACCommand:點擊按鈕,執行訂閱者block
    self.ipAddressBtn.rac_command = [[RACCommand alloc] initWithSignalBlock:^RACSignal *(id input) {
        return [self ipSignal];
    }];

}
-(RACSignal *)ipSignal{
    // 創建信號,用于封裝自定義的網絡請求。即:把網絡請求的代碼封裝在createSignal的代碼塊中
    return [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
        // 1.創建請求對象
        NSURLRequest *  request = [NSURLRequest requestWithURL:[NSURL URLWithString:@"http://pv.sohu.com/cityjson"]];
        // 2.創建下載任務
        NSURLSessionDataTask *dataTask = [[NSURLSession sharedSession] dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
                if (error == nil) {// 沒錯誤就執行
                //將data對象轉換成字符串
                NSStringEncoding enc = CFStringConvertEncodingToNSStringEncoding(kCFStringEncodingGB_18030_2000);
                NSString *responseStr = [[NSString alloc] initWithData:data encoding:enc];
                NSLog(@"%@",responseStr);
               }else{
                [[NSOperationQueue mainQueue] addOperationWithBlock:^{
                    NSLog(@"+++++%@",error);// y
                }];
                
            }
        }];
        // 3.執行下載任務
        [dataTask resume];
        return nil;

    }];
   }

@end

截圖

101.22.gif

23.(二)rac_command

#import "ViewController.h"
#import <ReactiveCocoa/ReactiveCocoa.h>
@interface ViewController ()
@property (weak, nonatomic) IBOutlet UITextField *usernameField;

@property (weak, nonatomic) IBOutlet UITextField *pwd;
@property (weak, nonatomic) IBOutlet UIButton *ipAddressBtn;
@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
  
     [self EnabledSingle];// rac_command
   
   }
-(void)EnabledSingle{
    // 按鈕的可用信號
    RACSignal *btnEnabledSingle = [RACSignal combineLatest:@[self.usernameField.rac_textSignal,self.pwd.rac_textSignal] reduce:^id(NSString *username,NSString *pwd){
        // 只有用戶名和密碼不為空時,ipAddressBtn按鈕才會變為可以點擊的狀態。
        return @(username.length > 0 && pwd.length > 0);
    }];

    // 按鈕點擊后觸發信號,從而會打印出ip地址。如果按鈕不可點擊,是無法觸發信號的。
    self.ipAddressBtn.rac_command = [[RACCommand alloc]initWithEnabled:btnEnabledSingle signalBlock:^RACSignal *(id input) {
        return [self ipSignal];
    }];

}

-(RACSignal *)ipSignal{
    // 創建信號,用于封裝自定義的網絡請求。即:把網絡請求的代碼封裝在createSignal的代碼塊中
    return [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
        // 1.創建請求對象
        NSURLRequest *  request = [NSURLRequest requestWithURL:[NSURL URLWithString:@"http://pv.sohu.com/cityjson"]];
        // 2.創建下載任務
        NSURLSessionDataTask *dataTask = [[NSURLSession sharedSession] dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
            //            NSLog(@"---%@",[NSThread currentThread]); // 通過打印可知,block代碼塊所在的線程為子線程,所以應將x,y處的代碼寫在主線程中,否則ipLabel上顯示不出來內容.因為顯示UI要在主線程(主隊列)執行
            // data不是UTF-8編碼,而是GBK編碼
            if (error == nil) {// 沒錯誤就執行
                //將data對象轉換成字符串
                NSStringEncoding enc = CFStringConvertEncodingToNSStringEncoding(kCFStringEncodingGB_18030_2000);
                NSString *responseStr = [[NSString alloc] initWithData:data encoding:enc];
                NSLog(@"%@",responseStr);
            }else{
                [[NSOperationQueue mainQueue] addOperationWithBlock:^{
                    NSLog(@"+++++%@",error);// y
                }];
                
            }
        }];
        // 3.執行下載任務
        [dataTask resume];
        return nil;
    }];
}

@end

截圖

101.23.gif

24.RACSequence

#import "ViewController.h"
#import <ReactiveCocoa/ReactiveCocoa.h>
@interface ViewController ()
@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
      [self racSequence];// RACSequence:主要用于遍歷
   }
-(void)racSequence{
    NSArray *names = @[@"Tom",@"Mary",@"Jack"];
    // 獲取RACSequence里面的信號進行遍歷
    [names.rac_sequence.signal subscribeNext:^(id x) {
        NSLog(@"%@",x);
    }];
    
}

@end

截圖

101.24.gif

25.RACSequence和filter結合使用

#import "ViewController.h"
#import <ReactiveCocoa/ReactiveCocoa.h>
@interface ViewController ()
@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
   
     [self racSequenceAndfilter];// RACSequence和filter結合使用
}

-(void)racSequenceAndfilter{
    // RACSequence與filter(過濾)的結合使用
    NSString *text = @"19293842456647";
    [[text.rac_sequence.signal filter:^BOOL(id value) {
        return [value intValue] >5;// 只打印大于5的數字
    }] subscribeNext:^(id x) {
        NSLog(@"%@",x);
    }];

}
@end

截圖

101.25.gif

登錄小項目


  • 查看隱藏的Apache的安裝目錄:/etc/apache2/
  • 做法1:dock下右鍵Finder,選擇"前往文件夾",輸入"/etc"
  • 做法2:finder下->前往->前往文件夾->輸入/etc
  • 做法3:Terminal 輸入 "open /etc"

  • apache相關指令:

  • 開啟apache服務 sudo apachectl start

  • 停止apache服務 sudo apachectl stop

  • 重啟服務 sudo apachectl restart

  • 查看版本 httpd -v

  • 注意點:

端口沖突的問題.png

Apache+Mysql+PHP的集成環境的配置(一、二、三)


(一)Apache服務器的開啟和配置、php的配置

httpd.conf配置錯誤的話,重新下載未修改的httpd.conf文件 密碼


具體步驟推薦這個參考鏈接

參考鏈接1
參考鏈接2
參考鏈接3
參考鏈接4

  • 1.在自己的用戶目錄(zhangbin)里新建一個Sites文件夾
111.1.png

  • 2.進到cd /etc/apache2/users/目錄下,sudo vim zhangbin.conf,增加內容為:
<Directory "/Users/zhangbin/Sites/">
AllowOverride All
Options Indexes MultiViews FollowSymLinks
Require all granted
</Directory>
111.2.gif

  • 3.修改zhangbin.conf這個文件的權限
sudo chmod 644 zhangbin.conf

111.3.gif

  • 4.cd到/etc/apache2/目錄,sudo vim httpd.conf 將下面五句話的注釋去掉
LoadModule authz_core_module libexec/apache2/mod_authz_core.so 
LoadModule authz_host_module libexec/apache2/mod_authz_host.so 
LoadModule userdir_module libexec/apache2/mod_userdir.so 
Include /private/etc/apache2/extra/httpd-userdir.conf
LoadModule php5_module libexec/apache2/libphp5.so
  • 5.進到/etc/apache2/extra/目錄,sudo vim httpd-userdir.conf ,將這句話的注釋去掉
Include /private/etc/apache2/users/*.conf
  • 6.sudo apachectl restart 重啟服務器
  • 7.將php后臺代碼放到Sites文件夾下
111.7.png

  • 8.瀏覽器輸入: loacal/~zhangbin/
111.8.png

拓展知識點修改apache默認頁面的內容

cd /Library/WebServer/Documents/
sudo vim index.html.en

(二)開啟MySQL服務器(必不可少),安裝Navicat數據庫管理工具


  • 1.只安裝MySQL.prefPane,用于開啟MySQL服務器


    102.1gif.gif
  • 2.終端輸入(必須)
    mysqladmin -u root password "123456"

  • 3.安裝Navicat數據庫管理工具. 目的:創建表,創建數據庫等等。


    102.2.gif
  • 4.創建數據庫,創建表

102.3.gif

(三)效果展示


注冊
101.26.gif
登錄
101.27.gif
發表日志
101.28.gif

細節問題
  • 1.開啟和關閉MySQL的影響


    101.29.gif
  • 2.1.Apache系統級的根目錄及對應網址是:
    /Library/WebServer/Documents/

         http://localhost
    
  • 2.2.用戶級的根目錄及對應網址是:
    ~/Sites

        http://localhost/~zhangbin/
    

由2.1和2.2可知關閉和開啟apache服務器對訪問網址的影響如下截圖


101.30.gif

[軟件+項目LS](https://pan.baidu.com/s/1gffo8IB 密碼 43kp )

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,247評論 6 543
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,520評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,362評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,805評論 1 317
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,541評論 6 412
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,896評論 1 328
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,887評論 3 447
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,062評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,608評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,356評論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,555評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,077評論 5 364
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,769評論 3 349
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,175評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,489評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,289評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,516評論 2 379

推薦閱讀更多精彩內容