文档章节

NSThread & Run Loop

小春0618
 小春0618
发布于 2015/01/27 09:07
字数 4700
阅读 35
收藏 0

前言

多线程的价值无需赘述,对于App性能和用户体验都有着至关重要的意义,在iOS开发中,Apple提供了不同的技术支持多线程编程,除了跨平台的 pthread之外,还提供了NSThread、NSOperationQueue、GCD等多线程技术,从本篇Blog开始介绍这几种多线程技术的细 节。

对于pthread这种跨平台的多线程技术,这本Programming with POSIX Threads做了详细介绍,不再提及。

NSThread

使用NSThead创建线程有很多方法:

  • +detachNewThreadSelector:toTarget:withObject:类方法直接生成一个子线程

[NSThread detachNewThreadSelector:@selector(threadRoutine:) toTarget:self withObject:nil];

  • 创建一个NSThread类实例,然后调用start方法。

NSThread* aThread = [[NSThread alloc] initWithTarget:self selector:@selector(threadRoutine:) object:nil];[aThread start];

  • 调用NSObject的+performSelectorInBackground:withObject:方法生成子线程。

[myObj performSelectorInBackground:@selector(threadRoutine:) withObject:nil];

  • 创建一个NSThread子类,然后调用子类实例的start方法,。

创建线程也是有开销的,iOS下主要成本包括构造内核数据结构(大约1KB)、栈空间(子线程512KB、主线程1MB,不过可以使用方法-setStackSize:自己设置,注意必须是4K的倍数,而且最小是16K),创建线程大约需要90毫秒的创建时间。

第二种和第四种方法创建的线程有个好处是拥有线程的对象,因此可以使用performSelector:onThread:withObject:waitUntilDone:在该线程上执行方法,这是一种非常方便的线程间通讯的方法(相对于设置麻烦的NSPort用于通讯),所要执行的方法可以直接添加到目标线程的Runloop中执行。Apple建议使用这个接口运行的方法不要是耗时或者频繁的操作,以免子线程的负载过重。

第三种方法其实与第一种方法是一样的,都会直接生成一个子线程。

上面四种方法生成的子线程都是detached状态,即主线程结束时这些线程都会被直接杀死;如果要生成joinable状态的子线程,只能使用pthread接口啦。

如果需要,可以设置线程的优先级(-setThreadPriority:);如果要在线程中保存一些状态信息,还可以使用到-threadDictionary得到一个NSMutableDictionary,以key-value的方式保存信息用于线程内读写。

NSThread的入口方法

要写一个有效的子线程入口方法需要注意很多问题,示例代码:

- (void)threadRoutine
{    
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];    
    BOOL moreWorkToDo = YES;    
    BOOL exitNow = NO;    
    NSRunLoop* runLoop = [NSRunLoop currentRunLoop];    
    NSMutableDictionary* threadDict = [[NSThread currentThread] threadDictionary];    
    [threadDict setValue:[NSNumber numberWithBool:exitNow] forKey:@"ThreadShouldExitNow"]; 
     //添加事件源    
     [self myInstallCustomInputSource];    
     while (moreWorkToDo && !exitNow)    {        
         //执行线程真正的工作方法,如果完成了可以设置moreWorkToDo为False        
         [runLoop runUntilDate:[NSDate date]];       
         exitNow = [[threadDict valueForKey:@"ThreadShouldExitNow"] boolValue];    
     }    
     [pool release];
}

  • 必须创建一个NSAutoreleasePool,因为子线程不会自动创建。同时要注意这个pool因为是最外层pool,如果线程中要进行长时 间的操作生成大量autoreleased的对象,则只有在该子线程退出时才会回收,因此如果线程中会大量创建autoreleased对象,那么需要创 建额外的NSAutoreleasePool,可以在NSRunloop每次迭代时创建和销毁一个NSAutoreleasePool。

  • 如果你的子线程会抛出异常,最好在子线程中设置一个异常处理函数,因为如果子线程无法处理抛出的异常,会导致程序直接Crash关闭。

  • (可选)设置Run Loop,如果子线程只是做个一次性的操作,那么无需设置Run Loop;如果子线程进入一个循环需要不断处理一些事件,那么设置一个Run Loop是最好的处理方式,如果需要Timer,那么Run Loop就是必须的。

  • 如果需要在子线程运行的时候让子线程结束操作,子线程每次Run Loop迭代中检查相应的标志位来判断是否还需要继续执行,可以使用threadDictionary以及设置Input Source的方式来通知这个子线程。那么什么是Run Loop呢?这是涉及NSThread及线程相关的编程时无法回避的一个问题。

Run Loop

Run Loop本身并不具备并发执行的功能,但是和多线程开发息息相关,而且概念令人迷惑,相关的介绍资料也很少,它的主要的特性如下:

  • 每个线程都有一个Run Loop,主线程的Run Loop会在App运行时自动运行,子线程中需要手动运行。

  • 每个Run Loop都会以一个模式mode来运行,可以使用NSRunLoop的- (BOOL)runMode:(NSString *)mode beforeDate:(NSDate *)limitDate 方法运行在某个特定模式mode。

  • Run Loop的处理两大类事件源:Timer Source和Input Source(包括performSelector***方法簇、Port或者自定义Input Source),每个事件源都会绑定在Run Loop的某个特定模式mode上,而且只有RunLoop在这个模式运行的时候才会触发该Timer和Input Source。

  • 如果没有任何事件源添加到Run Loop上,Run Loop就会立刻exit。

Run Loop接口

要操作Run Loop,Foundation层和Core Foundation层都有对应的接口可以操作Run Loop。

Foundation层对应的是NSRunLoop:

Core Foundation层对应的是CFRunLoopRef:

两组接口差不多,不过功能上还是有许多区别的,例如CF层可以添加自定义Input Source事件源(CFRunLoopSourceRef)和Run Loop观察者Observer(CFRunLoopObserverRef),很多类似功能的接口特性也是不一样的。

Run Loop运行

Run Loop如何运行呢?在上一节NSThread的入口函数中使用了一种NSRunLoop的使用场景,再看一例:

- (void)main
{    
    @autoreleasepool {        
        NSLog(@"starting thread.......");        
        NSTimer *timer = [NSTimer timerWithTimeInterval:2 target:self selector:@selector(doTimerTask) userInfo:nil repeats:YES];        
        [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];               [timer release];        
        while (! self.isCancelled) {            
            [self doOtherTask];            
            BOOL ret = [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];            
            NSLog(@"after runloop counting.........: %d", ret);        
        }        
        NSLog(@"finishing thread.........");    
    }
}

- (void)doTimerTask
{    
    NSLog(@"do timer task");
}

- (void)doOtherTask
{    
    NSLog(@"do other task");
}

我们看到入口方法里创建了一个NSTimer,并且以NSDefaultRunLoopMode模式加入到当前子线程的NSRunLoop中。进入循环后肯定会执行-doOtherTask方式法一次,然后再以NSDefaultRunLoopMode模式运行NSRunLoop,如果一次Timer事件触发处理后,这个Run Loop会返回吗?答案是不会,Why?

NSRunLoop的底层是由CFRunLoopRef实现的,你可以想象成一个循环或者类似Linux下select或者epoll,当没有事件 触发时,你调用的Run Loop运行方法不会立刻返回,它会持续监听其他事件源,如果需要Run Loop会让子线程进入sleep等待状态而不是空转,只有当Timer Source或者Input Source事件发生时,子线程才会被唤醒,然后处理触发的事件,然而由于Timer source比较特殊,Timer Source事件发生处理后,Run Loop运行方法- (BOOL)runMode:(NSString *)mode beforeDate:(NSDate *)limitDate;也不会返回;而其他非Timer事件的触发处理会让这个Run Loop退出并返回YES。当Run Loop运行在一个特定模式时,如果该模式下没有事件源,运行Run Loop会立刻返回NO。

NSRunLoop的运行接口:

//运行 NSRunLoop,运行模式为默认的NSDefaultRunLoopMode模式,没有超时限制
- (void)run;

//运行 NSRunLoop: 参数为运行模式、时间期限,返回值为YES表示是处理事件后返回的,NO表示是超时或者停止运行导致返回的
- (BOOL)runMode:(NSString *)mode beforeDate:(NSDate *)limitDate;

//运行 NSRunLoop: 参数为运时间期限,运行模式为默认的NSDefaultRunLoopMode模式 
-(void)runUntilDate:(NSDate *)limitDate;

CFRunLoopRef的运行接口:

//运行 
CFRunLoopRefvoid CFRunLoopRun();

//运行 CFRunLoopRef: 参数为运行模式、时间和是否在处理Input Source后退出标志,返回值是exit原因
SInt32 CFRunLoopRunInMode (mode, seconds, returnAfterSourceHandled);

//停止运行 
CFRunLoopRefvoid CFRunLoopStop( CFRunLoopRef rl );

//唤醒 
CFRunLoopRefvoid CFRunLoopWakeUp ( CFRunLoopRef rl );

详细讲解下NSRunLoop的三个运行接口:

  • - (void)run; 无条件运行

不建议使用,因为这个接口会导致Run Loop永久性的运行在NSDefaultRunLoopMode模式,即使使用CFRunLoopStop(runloopRef);也无法停止Run Loop的运行,那么这个子线程就无法停止,只能永久运行下去。

  • - (void)runUntilDate:(NSDate *)limitDate; 有一个超时时间限制

比上面的接口好点,有个超时时间,可以控制每次Run Loop的运行时间,也是运行在NSDefaultRunLoopMode模式。这个方法运行Run Loop一段时间会退出给你检查运行条件的机会,如果需要可以再次运行Run Loop。注意CFRunLoopStop(runloopRef);也无法停止Run Loop的运行,因此最好自己设置一个合理的Run Loop运行时间。示例:

while (!Done){    
    [[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:10]];     NSLog(@"exiting runloop.........:");
}

  • - (BOOL)runMode:(NSString *)mode beforeDate:(NSDate *)limitDate; 有一个超时时间限制,而且设置运行模式

这个接口在非Timer事件触发、显式的用CFRunLoopStop停止Run Loop、到达limitDate后会退出返回。如果仅是Timer事件触发并不会让Run Loop退出返回;如果是PerfromSelector***事件或者其他Input Source事件触发处理后,Run Loop会退出返回YES。示例:

while (!Done){    
    BOOL ret = [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode                                        beforeDate:[NSDate distantFuture]];    
    NSLog(@"exiting runloop.........: %d", ret);
}

那么如何知道一个Run Loop是因为什么原因exit退出的呢?NSRunLoop中没有接口可以知道,而需要通过Core Foundation的接口来运行CFRunLoopRef,NSRunLoop其实就是CFRunLoopRef的二次封装。使用CFRunLoop的 接口(C的接口)来运行Run Loop,有两个接口:

  • void CFRunLoopRun(void);

运行在默认的kCFRunLoopDefaultMode模式下,直到使用CFRunLoopStop接口停止这个Run Loop,或者Run Loop的所有事件源都被删除。

  • SInt32 CFRunLoopRunInMode(CFStringRef mode, CFTimeInterval seconds, Boolean returnAfterSourceHandled);

第一个参数是指RunLoop运行的模式(例如kCFRunLoopDefaultMode或者kCFRunLoopCommonModes),第二个参数是运行时间,第三个参数是是否在处理事件后让Run Loop退出返回。 示例:

while (!self.isCancelled){    
    [self doOtherTask];    
    SInt32 result = CFRunLoopRunInMode(kCFRunLoopDefaultMode, 2, YES);    
    if (result == kCFRunLoopRunStopped)    {        
        [self cancel];    
    }    
    NSLog(@"exit run loop.........: %ld", result);
}

如果Run Loop退出返回后,返回值是SInt32类型(signed long),表明Run Loop返回的原因,目前有四种:

enum {    
    kCFRunLoopRunFinished = 1, 
    //Run Loop结束,没有Timer或者其他Input Source    
    kCFRunLoopRunStopped = 2, 
    //Run Loop被停止,使用CFRunLoopStop停止Run Loop    
    kCFRunLoopRunTimedOut = 3, 
    //Run Loop超时    
    kCFRunLoopRunHandledSource = 4 
    ////Run Loop处理完事件,注意Timer事件的触发是不会让Run Loop退出返回的,即使CFRunLoopRunInMode的第三个参数是YES也不行
};

注意:Run Loop是可以嵌套调用的(就像NSAutoreleasePool),例如一个Run Loop运行过程中一个事件触发后,那么在触发方法里可以再运行当前子线程的Run Loop,然后由这个Run Loop等待其他事件触发。不过这种嵌套Run Loop调用方式我用的比较少。

以上Run Loop运行方法参考本文最后的Sample Code自行尝试。

Run Loop的运行模式Mode

iOS下Run Loop的主要运行模式mode有:

1) NSDefaultRunLoopMode: 默认的运行模式,除了NSConnection对象的事件。

2) NSRunLoopCommonModes: 是一组常用的模式集合,将一个input source关联到这个模式集合上,等于将input source关联到这个模式集合中的所有模式上。在iOS系统中NSRunLoopCommonMode包含NSDefaultRunLoopMode、 NSTaskDeathCheckMode、UITrackingRunLoopMode,我有个timer要关联到这些模式上,一个个注册很麻烦,我可 以用CFRunLoopAddCommonMode([[NSRunLoop currentRunLoop] getCFRunLoop],(__bridge CFStringRef) NSEventTrackingRunLoopMode)将 NSEventTrackingRunLoopMode或者其他模式添加到这个NSRunLoopCommonModes模式中,然后只需要将Timer 关联到NSRunLoopCommonModes,即可以实现Run Loop运行在这个模式集合中任何一个模式时,这个Timer都可以被触发。默认情况下NSRunLoopCommonModes包含了 NSDefaultRunLoopMode和UITrackingRunLoopMode。注意:让Run Loop运行在NSRunLoopCommonModes下是没有意义的,因为一个时刻Run Loop只能运行在一个特定模式下,而不可能是个模式集合。

3) UITrackingRunLoopMode: 用于跟踪触摸事件触发的模式(例如UIScrollView上下滚动),主线程当触摸事件触发时会设置为这个模式,可以用来在控件事件触发过程中设置Timer。

4) GSEventReceiveRunLoopMode: 用于接受系统事件,属于内部的Run Loop模式。

5) 自定义Mode:可以设置自定义的运行模式Mode,你也可以用CFRunLoopAddCommonMode添加到NSRunLoopCommonModes中。

Run Loop运行时只能以一种固定的模式运行,只会监控这个模式下添加的Timer Source和Input Source,如果这个模式下没有相应的事件源,Run Loop的运行也会立刻返回的。注意Run Loop不能在运行在NSRunLoopCommonModes模式,因为NSRunLoopCommonModes其实是个模式集合,而不是一个具体的 模式,我可以在添加事件源的时候使用NSRunLoopCommonModes,只要Run Loop运行在NSRunLoopCommonModes中任何一个模式,这个事件源都可以被触发。

Run Loop的事件源

归根结底,Run Loop就是个处理事件的Loop,可以添加Timer和其他Input Source等各种事件源,如果事件源没有发生时,Run Loop就可能让线程进入asleep状态,而事件源发生时就会唤醒休眠的(asleep)的子线程来处理事件。Run Loop的事件源事件源分两类:Timer Source和Input Source(包括-performSelector:***API调用簇,Port Input Source、自定义Input Source)。

从上图可以看出Run Loop就是处理事件的一个循环,不同的是Timer Source事件处理后不会使Run Loop结束,而Input Source事件处理后会让Run Loop退出。因此你需要自己的一个Loop去不断运行Run Loop来处理事件,就像本文开头的示例那样。

细分下Run Loop的事件源:

1) Timer Souce就是创建Timer添加到Run Loop中,没啥好说的,Cocoa或者Core Foundation都有相应接口实现。需要注意的是scheduledTimerWith****开头生成的Timer会自动帮你以默认NSDefaultRunLoopMode模式加载到当前的Run Loop中,而其他接口生成的Timer则需要你手动使用-addTimer:forMode添 加到Run Loop中。需要额外注意的是Timer的触发不会让Run Loop返回。(Timer sources deliver events to their handler routines but do not cause the run loop to exit.) 具体实验可以看下面的Sample Code。

2) Input Source中的-performSelector:***API调用簇方法,有以下这些接口:

performSelectorOnMainThread:withObject:waitUntilDone:
performSelectorOnMainThread:withObject:waitUntilDone:modes:

performSelector:onThread:withObject:waitUntilDone:performSelector:onThread:withObject:waitUntilDone:modes:

performSelector:withObject:afterDelay:
performSelector:withObject:afterDelay:inModes:

cancelPreviousPerformRequestsWithTarget:
cancelPreviousPerformRequestsWithTarget:selector:object:

这些API最后两个是取消当前线程中调用,其他API是在主线程或者当前线程下的Run Loop中执行指定的@selector。

3) Port Input Source:概念上也比较简单,可以用NSMachPort作为线程之间的通讯通道。例如在主线程创建子线程时传入一个NSPort对象,这样主线程就 可以和这个子线程通讯啦,如果要实现双向通讯,那么子线程也需要回传给主线程一个NSPort。

NSPort的子类除了NSMachPort,还可以使用NSMessagePort或者Core Foundation中的CFMessagePortRef。

注意:虽然有这么棒的方式实现线程间通讯方式,但是估计是由于危及iOS的Sandbox沙盒环境,所以这些API都是私有接口,如果你用到NSPortMessage,XCode会提示'NSPortMessage' for instance message is a forward declaration

4) 自定义Input Source:

向Run Loop添加自定义Input Source只能使用Core Foundation的接口:CFRunLoopSourceCreate创建一个source,CFRunLoopAddSource向Run Loop中添加source,CFRunLoopRemoveSource从Run Loop中删除source,CFRunLoopSourceSignal通知source,CFRunLoopWakeUp唤醒Run Loop。

Apple官方文档提供了一个自定义Input Source使用模式。

主线程持有包含子线程的Run Loop和Source的context对象,还有一个用于保存需要运行操作的数据buffer。主线程需要子线程干活时,首先将需要的操作数据添加到数 据buffer,然后通知source,唤醒子线程Run Loop(因为子线程可能正在sleep状态,CFRunLoopWakeUp唤醒Run Loop可以通知线程醒来干活),由于子线程也持有这个source和数据buffer,因此在触发唤醒时可以使用这个数据buffer的数据来执行相关操作(需要注意数据buffer访问时的同步)。

具体实现参见本文最后的Sample Code。

Run Loop的Observer

Core Foundation层的接口可以定义一个Run Loop的观察者在Run Loop进入以下某个状态时得到通知:

  • Run loop的进入

  • Run loop处理一个Timer的时刻

  • Run loop处理一个Input Source的时刻

  • Run loop进入睡眠的时刻

  • Run loop被唤醒的时刻,但在唤醒它的事件被处理之前

  • Run loop的终止

Observer的创建以及添加到Run Loop中需要使用Core Foundation的接口:

CFRunLoopObserverContext  context = {0, (__bridge void *)(self), NULL, NULL, NULL};
CFRunLoopObserverRef observer = CFRunLoopObserverCreate(kCFAllocatorDefault, kCFRunLoopBeforeTimers, YES, 0, &myRunLoopObserver, &context);
if (observer){  
    CFRunLoopAddObserver(CFRunLoopGetCurrent(), observer,                                 kCFRunLoopCommonModes);
}

首先创建Observer的context,然后调用Core Foundation方法CFRunLoopObserverCreate创建Observer,再加入到当前线程的Run Loop中,注意CFRunLoopObserverCreate方法的第二个参数是Observer观察类型,有如下几种:

        

/* Run Loop Observer Activities */
typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {    
    kCFRunLoopEntry = (1UL << 0),    
    kCFRunLoopBeforeTimers = (1UL << 1),    
    kCFRunLoopBeforeSources = (1UL << 2),    
    kCFRunLoopBeforeWaiting = (1UL << 5),    
    kCFRunLoopAfterWaiting = (1UL << 6),    
    kCFRunLoopExit = (1UL << 7),    
    kCFRunLoopAllActivities = 0x0FFFFFFFU
};

对应Run Loop的各种事件,kCFRunLoopAllActivities比较特殊,可以观察所有事件。具体样例代码请参考Sample Code。

总结

Run Loop就是一个处理事件源的循环,你可以控制这个Run Loop运行多久,如果当前没有事件发生,Run Loop会让这个线程进入睡眠状态(避免再浪费CPU时间),如果有事件发生,Run Loop就处理这个事件。Run Loop处理事件和发送给Observer通知的流程如下:

  • 1) 进入Run Loop运行,此时会通知观察者进入Run Loop;

  • 2) 如果有Timer即将触发时,通知观察者;

  • 3) 如果有非Port的Input Sourc即将e触发时,通知观察者;

  • 4)触发非Port的Input Source事件源;

  • 5)如果基于Port的Input Source事件源即将触发时,立即处理该事件,跳转到步骤9;

  • 6)通知观察者当前线程将进入休眠状态;

  • 7)将线程进入休眠状态直到有以下事件发生:基于Port的Input Source被触发、Timer被触发、Run Loop运行时间到了过期时间、Run Loop被唤醒。

  • 8) 通知观察者线程将要被唤醒。

  • 9) 处理被触发的事件:

    • 如果是用户自定义的Timer,处理Timer事件后重新启动Run Loop进入步骤2;

    • 如果线程被唤醒又没有到过期时间,则进入步骤2;

    • 如果是其他Input Source事件源有事件发生,直接处理这个事件;

  • 10)到达此步骤说明Run Loop运行时间到期,或者是非Timer的Input Source事件被处理后,Run Loop将要退出,退出前通知观察者线程已退出。


本文转载自:http://www.hrchen.com/2013/06/multi-threading-programming-of-ios-part-1/

小春0618
粉丝 8
博文 40
码字总数 10552
作品 0
海淀
程序员
私信 提问
Objective-C 后台线程 和 NSOperationQueue

后台线程 简单操作,无需创建线程时,可以使用一下函数(以UILabel 为例): [myLabel performSelector:@selector(setText:) withObject:@"hello" afterDelay:0.1f]; 代替平时的操作:[myLabe......

木木情深
2014/02/19
646
0
多线程

举例说明怎么简单的创建一个子线程。 用到的类是NSThread类,这里使用detachNewTheadSelector:toTagaet:withObject创建一个线程。 函数setupThread:(NSArray*)userInfor。通过userInfor将需要...

长平狐
2012/08/13
140
0
iOS OS X 和 iOS 中的多线程技术-3 (NSThread)

//联系人:石虎QQ:1224614774昵称:嗡嘛呢叭咪哄 一、NSThread NSThread 是 OS X 和 iOS 都提供的一个线程对象,它是线程的一个轻量级实现。在执行一些轻量级的简单任务时,NSThread 很有用,但...

石虎132
2017/11/25
0
0
iOS开发 多线程(二) NSThread的使用(转)

每个iOS应用程序都有个专门用来更新显示UI界面、处理用户的触摸事件的主线程,因此不能将其他太耗时的操作放在主线程中执行,不然会造成主线程堵塞(出现卡机现象),带来极坏的用户体验。一般...

神补刀
2015/08/18
655
0
iOS多线程编程之NSThread的使用

iOS多线程编程之NSThread的使用 1、简介: 1.1 iOS有三种多线程编程的技术,分别是: 1.、NSThread 2、Cocoa NSOperation (iOS多线程编程之NSOperation和NSOperationQueue的使用) 3、GCD 全...

malawo
2013/09/05
18
0

没有更多内容

加载失败,请刷新页面

加载更多

Blockstack-2 :Blockstack ID注册

本篇文章主要记录Blockstack ID注册的流程; 在介绍注册流程之前,先简单的介绍一下Blockstack ID; 相对于传统互联网来说,Blockstack ID更像是统一的账号系统;即一个账号即可登录和授权所...

Riverzhou
30分钟前
5
0
面试官问:平时碰到系统CPU飙高和频繁GC,你会怎么排查?

处理过线上问题的同学基本上都会遇到系统突然运行缓慢,CPU 100%,以及Full GC次数过多的问题。当然,这些问题的最终导致的直观现象就是系统运行缓慢,并且有大量的报警。本文主要针对系统运...

Java高级架构师n
55分钟前
21
0
面向对象编程

1、类和对象 类是对象的蓝图和模板,而对象是实例;即对象是具体的实例,类是一个抽象的模板 当我们把一大堆拥有共同特征的对象的静态特征(属性)和动态特征(行为)都抽取出来后,就可以定...

huijue
今天
21
0
redis异常解决 :idea启动本地redis出现 jedis.exceptions.JedisDataException: NOAUTH Authentication required

第一次安装在本地redis服务,试试跑项目,结果却出现nested exception is redis.clients.jedis.exceptions.JedisDataException: NOAUTH Authentication required错误,真是让人头疼 先检查一...

青慕
今天
35
0
Spring 之 IoC 源码分析 (基于注解方式)

一、 IoC 理论 IoC 全称为 Inversion of Control,翻译为 “控制反转”,它还有一个别名为 DI(Dependency Injection),即依赖注入。 二、IoC方式 Spring为IoC提供了2种方式,一种是基于xml...

星爵22
今天
34
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部