MyException - 我的异常网
当前位置:我的异常网» Iphone » ReactiveCocoa应用篇(2)

ReactiveCocoa应用篇(2)

www.MyException.Cn  网友分享于:2013-07-22  浏览:0次
ReactiveCocoa应用篇(二)

上一篇介绍了ReactiveCocoa的常用类,已经基本满足项目中的简单应用要求,但是针对复杂的功能还需要其它的类来协同处理。ReactiveCocoa提供了强大的流程处理功能来解决复杂的问题,包括事件点击、代理、通知、事件同步和异步等等,可以简化代码体量,实现 高聚合、低耦合 的编程思想。下面ReactiveCocoa的更强大的功能:

一. RACTuple、RACSequence

RACTuple: 元组类,类似NSArray,在解构对象中经常使用

RACSequence: 集合类,使用它来快速遍历数组和字典

    // 1.遍历数组
    NSArray *numbers = @[@1,@2,@3,@4];
    
    // 这里其实是三步
    // 第一步: 把数组转换成集合RACSequence numbers.rac_sequence
    // 第二步: 把集合RACSequence转换RACSignal信号类,numbers.rac_sequence.signal
    // 第三步: 订阅信号,激活信号,会自动把集合中的所有值,遍历出来。
    [numbers.rac_sequence.signal subscribeNext:^(id x) {
    
        NSLog(@"%@",x);
    }];
    // 2.遍历字典,遍历出来的键值对会包装成RACTuple(元组对象)
    NSDictionary *dict = @{@"name":@"xmg",@"age":@18};
    [dict.rac_sequence.signal subscribeNext:^(RACTuple *x) {
    
        // 解包元组,会把元组的值,按顺序给参数里面的变量赋值
        RACTupleUnpack(NSString *key,NSString *value) = x;
    
        // 相当于以下写法
        // NSString *key = x[0];
        // NSString *value = x[1];
    
        NSLog(@"%@ %@",key,value);
    
    }];

二. RACScheduler、RACUnit、RACEvent

  • RACScheduler: RAC中的队列,用GCD封装的

  • RACUnit: 表⽰stream不包含有意义的值,也就是看到这个,可以直接理解为nil

  • RACEvent: 把数据包装成信号事件(signal event)。它主要通过RACSignal的-materialize来使用,然并卵

三. 事件监听

  • 代替代理: rac_signalForSelector

之前需要遵守代理协议、赋值delegate、实现代理方法等都不需要,只用rac_signalForSelector就可以实现

    [[redV rac_signalForSelector:@selector(btnClick:)] subscribeNext:^(id x) {
        NSLog(@"点击红色按钮");
    }];
  • 代替KVO:rac_valuesAndChangesForKeyPath
    [[redV rac_valuesAndChangesForKeyPath:@"center" options:NSKeyValueObservingOptionNew observer:nil] subscribeNext:^(id x) {
        NSLog(@"%@",x);
    }];
  • 监听事件:rac_signalForControlEvents
    [[self.btn rac_signalForControlEvents:UIControlEventTouchUpInside] subscribeNext:^(id x) {
    
        NSLog(@"按钮被点击了");
    }];
  • 代替通知:rac_addObserverForName
    [[[NSNotificationCenter defaultCenter] rac_addObserverForName:UIKeyboardWillShowNotification object:nil] subscribeNext:^(id x) {
        NSLog(@"键盘弹出");
    }];
  • 监听文本框文字改变:rac_textSignal
    [_textField.rac_textSignal subscribeNext:^(id x) {
        NSLog(@"文字改变了%@",x);
    }];
  • 同步信号:rac_liftSelector:withSignalsFromArray:Signals
    - (void)viewDidLoad
    {
        RACSignal *request1 = [RACSignal createSignal:^RACDisposable *(id subscriber) {
    
        // 发送请求1
        [subscriber sendNext:@"发送请求1"];
            return nil;
        }];
    
        RACSignal *request2 = [RACSignal createSignal:^RACDisposable *(id subscriber) {
            // 发送请求2
            [subscriber sendNext:@"发送请求2"];
            return nil;
        }];
    
        // 使用注意:几个信号,参数一的方法就几个参数,每个参数对应信号发出的数据。
        [self rac_liftSelector:@selector(updateUIWithR1:r2:) withSignalsFromArray:@[request1,request2]];
    }
    
    // 更新UI
    - (void)updateUIWithR1:(id)data r2:(id)data1
    {
        NSLog(@"更新UI%@ %@",data,data1);
    }

四. 常见宏

  • RAC: 于给某个对象的某个属性绑定
    // 只要文本框文字改变,就会修改label的文字
    RAC(self.labelView,text) = _textField.rac_textSignal;
  • RACObserve: 监听某个对象的某个属性,返回的是信号
    [RACObserve(self.view, center) subscribeNext:^(id x) {
    
        NSLog(@"%@",x);
    }];
  • @weakify(Obj)和@strongify(Obj):处理闭包强引用

  • RACTupleRACTupleUnpack: 元组的构造与解构

    // 把参数中的数据包装成元组
    RACTuple *tuple = RACTuplePack(@10,@20);
    
    // 把参数中的数据包装成元组
    RACTuple *tuple = RACTuplePack(@"xmg",@20);
    
    // 解包元组,会把元组的值,按顺序给参数里面的变量赋值
    // name = @"xmg" age = @20
    RACTupleUnpack(NSString *name,NSNumber *age) = tuple;

五. 常用操作方法之映射

1. flattenMap

flattenMap作用:把源信号的内容映射成一个新的信号,信号可以是任意类型

  • 使用步骤
    1.传入一个block,block类型是返回值RACStream,参数value
    2.参数value就是源信号的内容,拿到源信号的内容做处理
    3.包装成RACReturnSignal信号,返回出去。
  • 底层实现
    0.flattenMap内部调用bind方法实现的,flattenMap中block的返回值,会作为bind中bindBlock的返回值。
    1.当订阅绑定信号,就会生成bindBlock。
    2.当源信号发送内容,就会调用bindBlock(value, *stop)
    3.调用bindBlock,内部就会调用flattenMap的block,flattenMap的block作用:就是把处理好的数据包装成信号。
    4.返回的信号最终会作为bindBlock中的返回信号,当做bindBlock的返回信号。
    5.订阅bindBlock的返回信号,就会拿到绑定信号的订阅者,把处理完成的信号内容发送出来。
  • 代码实现
    // 创建信号
    RACSubject *subject = [RACSubject subject];
    
    // 绑定信号
    RACSignal *bindSignal = [subject flattenMap:^RACStream *(id value) {
        // block:只要源信号发送内容就会调用
        // value:就是源信号发送内容
    
        value = [NSString stringWithFormat:@"%@",value];
        NSLog(@"%@",value);
        // block:只要源信号发送内容就会调用
        // value:就是源信号发送内容
        return [RACReturnSignal return:value];
    }];
    
    // flattenMap中返回的是什么信号,订阅的就是什么信号
    // 订阅信号
    [bindSignal subscribeNext:^(id x) {
    
        NSLog(@"----%@",x);
    }];
    
    // 发送数据
    [subject sendNext:@"abcd"];

2. Map

把源信号的值映射成一个新的值,返回一个对象

  • 使用步骤
    1.传入一个block,类型是返回对象,参数是value
    2.value就是源信号的内容,直接拿到源信号的内容做处理
    3.把处理好的内容,直接返回就好了,不用包装成信号,返回的值,就是映射的值。
  • 底层实现
    1.Map底层其实是调用flatternMap,Map中block中的返回的值会作为flatternMap中block中的值。
    2.当订阅绑定信号,就会生成bindBlock。
    3.当源信号发送内容,就会调用bindBlock(value, *stop)
    4.调用bindBlock,内部就会调用flattenMap的block
    5.flattenMap的block内部会调用Map中的block,把Map中的block返回的内容包装成返回的信号。
    6.返回的信号最终会作为bindBlock中的返回信号,当做bindBlock的返回信号。
    7.订阅bindBlock的返回信号,就会拿到绑定信号的订阅者,把处理完成的信号内容发送
  • 代码实现
    RACSubject *subject = [RACSubject subject];
    
    RACSignal *bindSignal = [subject map:^id(id value) {
        // 返回的类型,就是你需要映射的值
        return [NSString stringWithFormat:@"%@",value];
    }];
    
    [bindSignal subscribeNext:^(id x) {
    
        NSLog(@"%@",x);
    }];
    
    [subject sendNext:@"123"];
    [subject sendNext:@"456"]

六. 操作方法之组合

1. concat

按一定顺序拼接信号,当多个信号发出的时候,有顺序的接收信号

  • 底层实现
    1.当拼接信号被订阅,就会调用拼接信号的didSubscribe
    2.didSubscribe中,会先订阅第一个源信号(signalA)
    3.会执行第一个源信号(signalA)的didSubscribe
    4.第一个源信号(signalA)didSubscribe中发送值,就会调用第一个源信号(signalA)订阅者的nextBlock,
    通过拼接信号的订阅者把值发送出来.
    5.第一个源信号(signalA)didSubscribe中发送完成,就会调用第一个源信号(signalA)订阅者的completedBlock,
    订阅第二个源信号(signalB)这时候才激活(signalB)。
    6.订阅第二个源信号(signalB),执行第二个源信号(signalB)的didSubscribe
    7.第二个源信号(signalA)didSubscribe中发送值,就会通过拼接信号的订阅者把值发送出来
  • 代码实现
    RACSignal *signalA = [RACSignal createSignal:^RACDisposable *(id subscriber) {
    
        // 发送信号
        [subscriber sendNext:@"123"];
    
        [subscriber sendCompleted];
        return nil;
    }];
    
    RACSignal *signalB = [RACSignal createSignal:^RACDisposable *(id subscriber) {
    
        // 发送信号
        [subscriber sendNext:@"abc"];
    
        return nil;
    }];
    
    // concat:按顺序去连接
    // 注意:concat,第一个信号必须要调用sendCompleted
    // 创建组合信号
    
    RACSignal *concatSignal = [signalA concat:signalB];
    
    // 订阅组合信号
    
    [concatSignal subscribeNext:^(id x) {
    
        // 既能拿到A信号的值,又能拿到B信号的值
        NSLog(@"%@",x);
    }];

2. then

用于连接两个信号,当第一个信号完成,才会连接then返回的信号。注意使用then,之前信号的值会被忽略掉

  • 底层实现
    1. 先过滤掉之前的信号发出的值。
    2. 使用concat连接then返回的信号
  • 代码实现
    RACSignal *signalA = [RACSignal createSignal:^RACDisposable *(id subscriber) {
    
        // 发送信号
        [subscriber sendNext:@"abc"];
    
        [subscriber sendCompleted];
        return nil;
    }];
    
    RACSignal *signalB = [RACSignal createSignal:^RACDisposable *(id subscriber) {
    
        // 发送信号
        [subscriber sendNext:@"123"];
    
        return nil;
    }];
    
    // 创建组合信号
    // then:忽悠掉第一个信号所有值
    RACSignal *thenSignal = [signalA then:^RACSignal *{
        // 返回信号就是需要组合的信号
        return signalB;
    }];
    
    // 订阅信号
    
    [thenSignal subscribeNext:^(id x) {
        NSLog(@"%@",x);
    }];

3. merge

把多个信号合并为一个信号,任何一个信号有新值的时候就会调用

  • 底层实现
    1.合并信号被订阅的时候,就会遍历所有信号,并且发出这些信号。
    2.每发出一个信号,这个信号就会被订阅
    3.也就是合并信号一被订阅,就会订阅里面所有的信号。
    4.只要有一个信号被发出就会被监听。
  • 代码实现
    // 任意一个信号请求完成都会订阅到
    
    RACSubject *signalA = [RACSubject subject];
    
    RACSubject *signalB = [RACSubject subject];
    
    // 组合信号
    RACSignal *mergeSignal = [signalA merge:signalB];
    
    // 订阅信号
    
    [mergeSignal subscribeNext:^(id x) {
        // 任意一个信号发送内容都会来这个block
        NSLog(@"%@",x);
    }];
    
    // 发送数据
    [signalA sendNext:@"abc"];
    [signalB sendNext:@"123"];

4. zipWith

把两个信号压缩成一个信号,只有当两个信号同时发出信号内容时,并且把两个信号的内容合并成一个元组,才会触发压缩流的next事件

  • 底层实现
    1. 定义压缩信号,内部就会自动订阅signalA,signalB
    2. 每当signalA或者signalB发出信号,就会判断signalA,signalB有没有发出个信号,有就会把最近发出的信号都包装成元组发出。
  • 代码实现
    RACSubject *signalA = [RACSubject subject];
    
    RACSubject *signalB = [RACSubject subject];
    
    // 压缩成一个信号
    // zipWith:当一个界面多个请求的时候,要等所有请求完成才能更新UI
    // zipWith:等所有信号都发送内容的时候才会调用
    
    RACSignal *zipSignal = [signalA zipWith:signalB];
    
    // 订阅信号
    [zipSignal subscribeNext:^(id x) {
        NSLog(@"%@",x);
    }];
    
    // 发送信号
    // 注意: 打印顺序是由压缩的信号顺序决定的,不是由发送信号的顺序决定的。
    [signalB sendNext:@"123"];
    [signalA sendNext:@"abc"];

5.reduce

用于信号发出的内容是元组,把信号发出元组的值聚合成一个值

  • 底层实现
    订阅聚合信号,每次有内容发出,就会执行reduceblcok,把信号内容转换成reduceblcok返回的值。
  • 代码实现
    // 聚合
    // 常见的用法,(先组合在聚合)。combineLatest:(id)signals reduce:(id (^)())reduceBlock
    // reduce中的block简介:
    // reduceblcok中的参数,有多少信号组合,reduceblcok就有多少参数,每个参数就是之前信号发出的内容
    // reduceblcok的返回值:聚合信号之后的内容。
    
    RACSignal *reduceSiganl = [RACSignal combineLatest:@[_accountFiled.rac_textSignal,_pwdField.rac_textSignal] reduce:^id(NSString *account,NSString *pwd){
        // block:只要源信号发送内容就会调用,组合成新一个值
        NSLog(@"%@ %@",account,pwd);
        // 聚合的值就是组合信号的内容
    
        return @(account.length && pwd.length);
    }];
    
    // 订阅组合信号
    // [reduceSiganl subscribeNext:^(id x) {
    //
    // _loginBtn.enabled = [x boolValue];
    // }];
    
    RAC(_loginBtn,enabled) = reduceSiganl;

6. combine

把两个信号组合成一个信号,跟zip一样,没什么区别

  • 底层实现
1.当组合信号被订阅,内部会自动订阅signalA,signalB,必须两个信号都发出内容,才会被触发。
2.并且把两个信号组合成元组发出。
  • 代码实现
    RACSignal *signalA = [RACSignal createSignal:^RACDisposable *(id subscriber) {
    
        [subscriber sendNext:@1];
    
        return nil;
    }];
    
    RACSignal *signalB = [RACSignal createSignal:^RACDisposable *(id subscriber) {
    
        [subscriber sendNext:@2];
    
        return nil;
    }];
    
    // 把两个信号组合成一个信号,跟zip一样,没什么区别
    RACSignal *combineSignal = [signalA combineLatestWith:signalB];
    
        [combineSignal subscribeNext:^(id x) {
    
        NSLog(@"%@",x);
    }];

七、操作方法之过滤

1. filter

使用它可以获取满足条件的信号

  • 代码实现
    // 只有当我们文本框的内容长度大于5,才想要获取文本框的内容
    
    [[_textField.rac_textSignal filter:^BOOL(id value) {
        // value: 源信号的内容
        return [value length] > 5;
        // 返回值,就是过滤条件,只有满足这个条件,才能能获取到内容
    }] subscribeNext:^(id x) {
        NSLog(@"%@",x);
    }];

2.ignore

忽略某些值的信号

  • 代码实现
    // ignore:忽略一些值
    // ignoreValues:忽略所有的值
    
    // 1.创建信号
    RACSubject *subject = [RACSubject subject];
    // 2.忽略一些
    RACSignal *ignoreSignal = [subject ignoreValues];
    // 3.订阅信号
    [ignoreSignal subscribeNext:^(id x) {
    
        NSLog(@"%@",x);
    }];
    
    // 4.发送数据
    [subject sendNext:@"2"];
    [subject sendNext:@"456"];
    [subject sendNext:@"789"];

3.distinctUntilChanged

当上一次的值和当前的值有明显的变化就会发出信号,否则会被忽略掉

  • 代码实现
    // distinctUntilChanged:如果当前的值跟上一个值相同,就不会被订阅到
    
    RACSubject *subject = [RACSubject subject];
    
    [[subject distinctUntilChanged] subscribeNext:^(id x) {
        NSLog(@"%@",x);
    }];
    
    [subject sendNext:@1];
    [subject sendNext:@2];
    [subject sendNext:@2];
    [subject sendNext:@3];

4.take

从开始一共取N次的信号

  • 代码实现
    // 1.创建信号
    RACSubject *subject = [RACSubject subject];
    
    // 2、处理信号,订阅信号
    [[subject take:2] subscribeNext:^(id x) {
        NSLog(@"%@",x);
    }];
    
    // 3.发送信号
    [subject sendNext:@1];
    [subject sendNext:@2];
    [subject sendNext:@3];

5. takeLast

取最后N次的信号,前提条件,订阅者必须调用完成,因为只有完成,就知道总共有多少信号

  • 代码实现
    // 1.创建信号
    RACSubject *signal = [RACSubject subject];
    
    // 2、处理信号,订阅信号
    [[signal takeLast:1] subscribeNext:^(id x) {
        NSLog(@"%@",x);
    }];
    
    // 3.发送信号
    [signal sendNext:@1];
    [signal sendNext:@2];
    [signal sendNext:@3];
    
    [signal sendCompleted];

6.takeUntil

获取信号直到执行完这个信号,当发送信号为空或结束时,后面有发送的信号,也不会执行

  • 代码实现
    RACSubject *subject = [RACSubject subject];
    RACSubject *signal = [RACSubject subject];
    
    [[subject takeUntil:signal] subscribeNext:^(id x) {
        NSLog(@"%@",x);
    }];
    
    [subject sendNext:@1];
    [subject sendNext:@"abc"];
    [signal sendError:nil];
    [signal sendNext:@2];
    [signal sendNext:@3];

7.skip

跳过几个信号,不接受

  • 代码实现
    // skip;跳跃几个值
    RACSubject *subject = [RACSubject subject];
    
    [[subject skip:2] subscribeNext:^(id x) {
        NSLog(@"%@",x);
    }];
    
    [subject sendNext:@1];
    [subject sendNext:@2];
    [subject sendNext:@3];

八、操作方法之秩序

  • 执行Next之前,会先执行这个Block

  • 执行sendCompleted之前,会先执行这个Block

    RACSignal *signal = [RACSignal createSignal:^RACDisposable *(id subscriber) {
        [subscriber sendNext:@123];
        [subscriber sendCompleted];
        return nil;
    }];
    
    // 执行[subscriber sendNext:@1];之前会调用这个Block
    [[[signal doNext:^(id x) {
        NSLog(@"doNext");
    }] doCompleted:^{
        // 执行[subscriber sendCompleted];之前会调用这个Block
        NSLog(@"doCompleted");
    }] subscribeNext:^(id x) {
        NSLog(@"---%@",x);
    }];

九、操作方法之线程

  • deliverOn

内容传递切换到制定线程中,副作用在原来线程中,把在创建信号时block中的代码称之为副作用

具体应用

    _executing = [[[[[immediateExecuting
        deliverOn:RACScheduler.mainThreadScheduler]
        // This is useful before the first value arrives on the main thread.
        startWith:@NO]
        distinctUntilChanged]
        replayLast]
        setNameWithFormat:@"%@ -executing", self];
  • subscribeOn

内容传递和副作用都会切换到制定线程中

具体应用

    RACMulticastConnection *connection = [[signal
        subscribeOn:RACScheduler.mainThreadScheduler]
        multicast:[RACReplaySubject subject]];

十、操作方法之时间

1.timeout

超时,可以让一个信号在一定的时间后,自动报错

    RACSignal *signalA = [[RACSignal createSignal:^RACDisposable *(id subscriber) {
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            [subscriber sendNext:@123];
        });
        return nil;
    }] timeout:1 onScheduler:[RACScheduler currentScheduler]];
    
    [signalA subscribeNext:^(id x) {
    
        NSLog(@"%@",x);
    } error:^(NSError *error) {
        // 1秒后会自动调用
        NSLog(@"%@",error);
    }];

2.interval

定时:每隔一段时间发出信号

    RACSignal *signal = [[RACSignal createSignal:^RACDisposable *(id subscriber) {
    
        [subscriber sendNext:@123];
        return nil;
        //delay 延迟发送next。
    }] delay:2.0];
    
    [signal subscribeNext:^(id x) {
        NSLog(@"%@",x);
    }];

3. delay

延迟发送next

    [[RACSignal interval:1.0 onScheduler:[RACScheduler currentScheduler]] subscribeNext:^(id x) {
        NSLog(@"123");
    }];

十一、操作方法之重复

1. retry

只要失败,就会重新执行创建信号中的block,直到成功.

    __block int i = 0;
    RACSignal *signal = [RACSignal createSignal:^RACDisposable *(id subscriber) {
    
        if (i == 5) {
            [subscriber sendNext:@123];
        } else{
            NSLog(@"error");
            [subscriber sendError:nil];
        }
    
        i++;
    
        return nil;
    }];
    
    [[signal retry] subscribeNext:^(id x) {
        NSLog(@"%@",x);
    } error:^(NSError *error) {
        NSLog(@"%@",error);
    }];

2. replay

当一个信号被多次订阅,反复播放内容

    RACSignal *signal = [RACSignal createSignal:^RACDisposable *(id subscriber) {
    
        [subscriber sendNext:@123];
        [subscriber sendNext:@456];
        return nil;
    }];
    
    [[signal replay] subscribeNext:^(id x) {
        NSLog(@"%@",x);
    }];
    
    [[signal replay] subscribeNext:^(id x) {
        NSLog(@"%@",x);
    }];

3. throttle

当某个信号发送比较频繁时,可以使用节流,在某一段时间不发送信号内容,过了一段时间获取信号的最新内容发出

    RACSubject *signal = [RACSubject subject];
    _signal = signal;
    // 节流,在一定时间(1秒)内,不接收任何信号内容,过了这个时间(1秒)获取最后发送的信号内容发出。
    // [[signal throttle:2.0] subscribeNext:^(id x) {
    // NSLog(@"%@",x);
    // }];
    
    [[signal throttle:1.0 valuesPassingTest:^BOOL(id next) {
    
        return YES;
    }] subscribeNext:^(id x) {
        NSLog(@"%@",x);
    }];
    
    - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
    {
        int i = 0;
        [_signal sendNext:@(i)];
        i++;
    }

未完待续...

文章评论

那些性感的让人尖叫的程序员
那些性感的让人尖叫的程序员
什么才是优秀的用户界面设计
什么才是优秀的用户界面设计
程序员眼里IE浏览器是什么样的
程序员眼里IE浏览器是什么样的
60个开发者不容错过的免费资源库
60个开发者不容错过的免费资源库
十大编程算法助程序员走上高手之路
十大编程算法助程序员走上高手之路
一个程序员的时间管理
一个程序员的时间管理
老程序员的下场
老程序员的下场
中美印日四国程序员比较
中美印日四国程序员比较
写给自己也写给你 自己到底该何去何从
写给自己也写给你 自己到底该何去何从
每天工作4小时的程序员
每天工作4小时的程序员
程序员最害怕的5件事 你中招了吗?
程序员最害怕的5件事 你中招了吗?
程序员必看的十大电影
程序员必看的十大电影
10个调试和排错的小建议
10个调试和排错的小建议
Web开发者需具备的8个好习惯
Web开发者需具备的8个好习惯
Google伦敦新总部 犹如星级庄园
Google伦敦新总部 犹如星级庄园
聊聊HTTPS和SSL/TLS协议
聊聊HTTPS和SSL/TLS协议
那些争议最大的编程观点
那些争议最大的编程观点
我是如何打败拖延症的
我是如何打败拖延症的
老美怎么看待阿里赴美上市
老美怎么看待阿里赴美上市
程序员和编码员之间的区别
程序员和编码员之间的区别
代码女神横空出世
代码女神横空出世
2013年中国软件开发者薪资调查报告
2013年中国软件开发者薪资调查报告
当下全球最炙手可热的八位少年创业者
当下全球最炙手可热的八位少年创业者
漫画:程序员的工作
漫画:程序员的工作
如何区分一个程序员是“老手“还是“新手“?
如何区分一个程序员是“老手“还是“新手“?
程序员的鄙视链
程序员的鄙视链
10个帮程序员减压放松的网站
10个帮程序员减压放松的网站
程序猿的崛起——Growth Hacker
程序猿的崛起——Growth Hacker
看13位CEO、创始人和高管如何提高工作效率
看13位CEO、创始人和高管如何提高工作效率
“肮脏的”IT工作排行榜
“肮脏的”IT工作排行榜
5款最佳正则表达式编辑调试器
5款最佳正则表达式编辑调试器
 程序员的样子
程序员的样子
为啥Android手机总会越用越慢?
为啥Android手机总会越用越慢?
程序员都该阅读的书
程序员都该阅读的书
亲爱的项目经理,我恨你
亲爱的项目经理,我恨你
要嫁就嫁程序猿—钱多话少死的早
要嫁就嫁程序猿—钱多话少死的早
Java 与 .NET 的平台发展之争
Java 与 .NET 的平台发展之争
Web开发人员为什么越来越懒了?
Web开发人员为什么越来越懒了?
团队中“技术大拿”并非越多越好
团队中“技术大拿”并非越多越好
不懂技术不要对懂技术的人说这很容易实现
不懂技术不要对懂技术的人说这很容易实现
编程语言是女人
编程语言是女人
为什么程序员都是夜猫子
为什么程序员都是夜猫子
初级 vs 高级开发者 哪个性价比更高?
初级 vs 高级开发者 哪个性价比更高?
如何成为一名黑客
如何成为一名黑客
旅行,写作,编程
旅行,写作,编程
科技史上最臭名昭著的13大罪犯
科技史上最臭名昭著的13大罪犯
我的丈夫是个程序员
我的丈夫是个程序员
鲜为人知的编程真相
鲜为人知的编程真相
程序员应该关注的一些事儿
程序员应该关注的一些事儿
软件开发程序错误异常ExceptionCopyright © 2009-2015 MyException 版权所有