IOS 之runLoop

先不扯什么概念茬贵,因為自己之前對概念理解不深刻,只有在項目中真正用到了才能真正體會移袍。

使用場景一:NSTimer 倒計時

  • 倒計時有兩種創(chuàng)建形式
  • 第一種 需要手動將定時器添加到NSRunLoop
[NSThread detachNewThreadSelector:@selector(startSteamTimer) toTarget:self withObject:nil];
- (NSTimer *) startSteamTimer{
return  [NSTimer timerWithTimeInterval:2.0 target:self selector:@selector(run) userInfo:nil repeats:YES];
}
//這樣的run方法永遠(yuǎn)不會調(diào)用必須 加入下面的代碼
[[NSRunLoop currentRunLoop] addTimer:_streamTimer forMode:NSDefaultRunLoopMode];
//若在非主線程 需要自己啟動RunLoop [[NSRunLoop currentRunLoop]  run]
  • 第二種 創(chuàng)建NSTimer 自動添加NSRunLoop
[NSTimer scheduledTimerWithTimeInterval:1.0f target:self selector:
                    @selector(_steamTimerAction) userInfo:nil repeats:YES];

使用場景二:用FTP 協(xié)議上傳和下載文件時 用到了NSRunLoop


 CFReadStreamRef readStreamRef = CFReadStreamCreateWithFTPURL(NULL, ( __bridge CFURLRef) url);
        CFReadStreamSetProperty(readStreamRef,
                                kCFStreamPropertyFTPAttemptPersistentConnection,
                                kCFBooleanFalse);
        
        CFReadStreamSetProperty(readStreamRef, kCFStreamPropertyShouldCloseNativeSocket, kCFBooleanTrue);
        CFReadStreamSetProperty(readStreamRef, kCFStreamPropertyFTPUsePassiveMode, kCFBooleanTrue);
        CFReadStreamSetProperty(readStreamRef, kCFStreamPropertyFTPFetchResourceInfo, kCFBooleanTrue);
        CFReadStreamSetProperty(readStreamRef, kCFStreamPropertyFTPUserName, (__bridge CFStringRef) self.ftpUsername);
        CFReadStreamSetProperty(readStreamRef, kCFStreamPropertyFTPPassword, (__bridge CFStringRef) self.ftpPassword);
        //
        CFReadStreamSetProperty(readStreamRef, kCFStreamPropertyFTPProxy, kCFBooleanTrue);
        //
        
        self.dataStream = ( __bridge_transfer NSInputStream *) readStreamRef;
        self.dataStream.delegate = self;
        if (self.dataStream == nil) {
            
            [self.delegate ftpError:self withErrorCode:FTPClientCantReadStream];
            
        }
        
//這里是重點(diǎn)
        [self performSelector:@selector(scheduleInCurrentThread:)
                     onThread:[[self class] networkThread]
                   withObject:self.dataStream
                waitUntilDone:YES];
       
//        [self.dataStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];

        [self.dataStream open];
#pragma thread management
+ (NSThread *)networkThread {
    static NSThread *networkThread = nil;
    static dispatch_once_t oncePredicate;

    dispatch_once(&oncePredicate, ^{
        networkThread =
        [[NSThread alloc] initWithTarget:self
                                selector:@selector(networkThreadMain:)
                                  object:nil];
        [networkThread start];
    });

    return networkThread;
}

+ (void)networkThreadMain:(id)unused {
    do {
        @autoreleasepool {
            [[NSRunLoop currentRunLoop] run];
        }
    } while (YES);
}

- (void)scheduleInCurrentThread:(NSStream*)aStream
{
    [aStream scheduleInRunLoop:[NSRunLoop currentRunLoop]
                           forMode:NSRunLoopCommonModes];
}

看了下方法的含義:Unless the client is polling the stream, it is responsible for ensuring that the stream is scheduled on at least one run loop and that at least one of the run loops on which the stream is scheduled is being run

確保流至少在一個運(yùn)行循環(huán)上調(diào)度解藻,并且流調(diào)度所在的至少一個運(yùn)行循環(huán)正在運(yùn)行

在流對象放入run loop且有流事件(有可讀數(shù)據(jù))發(fā)生時,流對象會向代理對象發(fā)送stream:handleEvent:消息葡盗。在打開流之前螟左,我們需要調(diào)用流對象的scheduleInRunLoop:forMode:方法,這樣做可以避免在沒有數(shù)據(jù)可讀時阻塞代理對象的操作觅够。我們需要確保的是流對象被放入正確的run loop中胶背,即放入流事件發(fā)生的那個線程的run loop中。

那為什么要RunLoop

字面意思運(yùn)行的循環(huán)蔚约,就像操作系統(tǒng)一樣奄妨,手機(jī)一有電話就有反應(yīng),系統(tǒng)里面在循環(huán)“跑圈”苹祟,也借助do-while 死循環(huán)理解

  • RunLoop 實際上是一個對象砸抛,這個對象在循環(huán)中用來處理程序運(yùn)行過程中出現(xiàn)的各種事件(比如說觸摸事件、UI刷新事件树枫、定時器事件直焙、Selector事件),從而保持程序的持續(xù)運(yùn)行砂轻。
  • RunLoop 在沒有事件處理的時候奔誓,會使線程進(jìn)入睡眠模式,從而節(jié)省 CPU 資源搔涝,提高程序性能厨喂。

RunLoop 和線程

RunLoop 和線程是息息相關(guān)的,我們知道線程的作用是用來執(zhí)行特定的一個或多個任務(wù)庄呈,在默認(rèn)情況下蜕煌,線程執(zhí)行完之后就會退出,就不能再執(zhí)行任務(wù)了诬留。這時我們就需要采用一種方式來讓線程能夠不斷地處理任務(wù)斜纪,并不退出。所以文兑,我們就有了 RunLoop盒刚。

一條線程對應(yīng)一個RunLoop對象绿贞,每條線程都有唯一一個與之對應(yīng)的 RunLoop 對象。
RunLoop 并不保證線程安全籍铁。我們只能在當(dāng)前線程內(nèi)部操作當(dāng)前線程的 RunLoop 對象靠柑,而不能在當(dāng)前線程內(nèi)部去操作其他線程的 RunLoop 對象方法。
RunLoop 對象在第一次獲取 RunLoop 時創(chuàng)建吓懈,銷毀則是在線程結(jié)束的時候。
主線程的 RunLoop 對象系統(tǒng)自動幫助我們創(chuàng)建好了(原理如 1.3 所示)耻警,而子線程的 RunLoop對象需要我們主動創(chuàng)建和維護(hù)。

官方模型


runloop.png

通過 Input sources(輸入源)和 Timer sources(定時源)兩種來源等待接受事件甘穿;然后對接受到的事件通知線程進(jìn)行處理腮恩,并在沒有事件的時候讓線程進(jìn)行休息

RunLoop 相關(guān)類

下面我們來了解一下Core Foundation框架下關(guān)于 RunLoop 的 5 個類,只有弄懂這幾個類的含義温兼,我們才能深入了解 RunLoop 的運(yùn)行機(jī)制募判。

CFRunLoopRef:代表 RunLoop 的對象
CFRunLoopModeRef:代表 RunLoop 的運(yùn)行模式
CFRunLoopSourceRef:就是 RunLoop 模型圖中提到的輸入源 / 事件源
CFRunLoopTimerRef:就是 RunLoop 模型圖中提到的定時源
CFRunLoopObserverRef:觀察者,能夠監(jiān)聽 RunLoop 的狀態(tài)改變

一個RunLoop對象(CFRunLoopRef)中包含若干個運(yùn)行模式(CFRunLoopModeRef)届垫。而每一個運(yùn)行模式下又包含若干個輸入源(CFRunLoopSourceRef)装处、定時源(CFRunLoopTimerRef)误债、觀察者(CFRunLoopObserverRef)妄迁。

  • 每次 RunLoop 啟動時登淘,只能指定其中一個運(yùn)行模式(CFRunLoopModeRef),這個運(yùn)行模式(CFRunLoopModeRef)被稱作當(dāng)前運(yùn)行模式(CurrentMode)形帮。
  • 如果需要切換運(yùn)行模式(CFRunLoopModeRef)周叮,只能退出當(dāng)前 Loop仿耽,再重新指定一個運(yùn)行模式(CFRunLoopModeRef)進(jìn)入。
  • 這樣做主要是為了分隔開不同組的輸入源(CFRunLoopSourceRef)项贺、定時源(CFRunLoopTimerRef)、觀察者(CFRunLoopObserverRef)棕叫,讓其互不影響

CFRunLoopRef 類 代表 RunLoop 的對象

獲取方式

  • Core Foundation

CFRunLoopGetCurrent(); // 獲得當(dāng)前線程的 RunLoop 對象
CFRunLoopGetMain(); // 獲得主線程的 RunLoop 對象

當(dāng)然,在Foundation 框架下獲取 RunLoop 對象類的方法如下:

  • Foundation

[NSRunLoop currentRunLoop]; // 獲得當(dāng)前線程的 RunLoop 對象
[NSRunLoop mainRunLoop]; // 獲得主線程的 RunLoop 對象

CFRunLoopModeRef. 運(yùn)行模式

  • 1疗认、kCFRunLoopDefaultMode:App的默認(rèn)運(yùn)行模式伏钠,通常主線程是在這個運(yùn)行模式下運(yùn)行

  • 2、UITrackingRunLoopMode:跟蹤用戶交互事件(用于 ScrollView 追蹤觸摸滑動缎浇,保證界面滑動時不受其他Mode影響)

  • 3赴肚、UIInitializationRunLoopMode:在剛啟動App時第進(jìn)入的第一個 Mode,啟動完成后就不再使用

  • 4尊蚁、GSEventReceiveRunLoopMode:接受系統(tǒng)內(nèi)部事件横朋,通常用不到

  • 5、kCFRunLoopCommonModes:偽模式琴锭,不是一種真正的運(yùn)行模式(后邊會用到)

其中kCFRunLoopDefaultMode决帖、UITrackingRunLoopMode、kCFRunLoopCommonModes是我們開發(fā)中需要用到的模式

CFRunLoopTimerRef

CFRunLoopTimerRef是定時源(RunLoop模型圖中提到過)地回,理解為基于時間的觸發(fā)器刻像,基本上就是NSTimer(哈哈,這個理解就簡單了吧)
下面我們來演示下CFRunLoopModeRef和CFRunLoopTimerRef結(jié)合的使用用法细睡,從而加深理解

UITextView *tv = [[UITextView alloc] initWithFrame:CGRectMake(0, 0, self.view.frame.size.width/2, self.view.frame.size.height/2)];

    tv.text =@"放很多字出現(xiàn)滾動條";

    tv.backgroundColor = UIColor.redColor;
    [self.view addSubview:tv];
    NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:2 target:self selector:@selector(run) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:_streamTimer forMode:NSDefaultRunLoopMode];

然后運(yùn)行溜徙,這個時候我們發(fā)現(xiàn)如何我們不拖動UITextView的滾動條犀填,定時器會穩(wěn)定的每隔2秒調(diào)用run方法打印

但拖動的時候嗓违,我們發(fā)現(xiàn)沒有打印

這是因為:

  • 當(dāng)我們不做任何操作的時候,RunLoop處于NSDefaultRunLoopMode下

  • 而當(dāng)我們拖動UITextView 的時候比庄,RunLoop就結(jié)束NSDefaultRunLoopMode乏盐,切換到了UITrackingRunLoopMode模式下,這種模式下沒有添加NSTimer,所有我們定時器不工作了
    但當(dāng)我送松開滾動條神凑,RunLoop就結(jié)束UITrackingRunLoopMode模式何吝,又切換回NSDefaultRunLoopMode模式,所以NSTimer就又開始正常工作了

  • 你可以試著將上述代碼中的[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];語句換為[[NSRunLoop currentRunLoop] addTimer:timer forMode:UITrackingRunLoopMode];瓣喊,也就是將定時器添加到當(dāng)前RunLoop的UITrackingRunLoopMode下黔酥,你就會發(fā)現(xiàn)定時器只會在拖動Text View的模式下工作,而不做操作的時候定時器就不工作棵帽。
    那難道我們就不能在這兩種模式下讓NSTimer都能正常工作嗎渣玲?

  • 當(dāng)然可以,這就用到了我們之前說過的偽模式(kCFRunLoopCommonModes)逾苫,這其實不是一種真實的模式枚钓,而是一種標(biāo)記模式,意思就是可以在打上Common Modes標(biāo)記的模式下運(yùn)行

CFRunLoopSourceRef

CFRunLoopSourceRef是事件源(RunLoop模型圖中提到過),CFRunLoopSourceRef有兩種分類方法指煎。

  • 第一種按照官方文檔來分類(就像RunLoop模型圖中那樣):

Port-Based Sources(基于端口)
Custom Input Sources(自定義)
Cocoa Perform Selector Sources

  • 第二種按照函數(shù)調(diào)用棧來分類:

Source0 :非基于Port
Source1:基于Port,通過內(nèi)核和其他線程通信威始,接收像街、分發(fā)系統(tǒng)事件

這兩種分類方式其實沒有區(qū)別,只不過第一種是通過官方理論來分類脓斩,第二種是在實際應(yīng)用中通過調(diào)用函數(shù)來分類畴栖。

備注:斷點(diǎn)可以看到函數(shù)調(diào)用棧“Source0”

CFRunLoopObserverRef

CFRunLoopObserverRef是觀察者燎猛,用來監(jiān)聽RunLoop的狀態(tài)改變
CFRunLoopObserverRef可以監(jiān)聽的狀態(tài)改變有以下幾種:

typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
    kCFRunLoopEntry = (1UL << 0),               // 即將進(jìn)入Loop:1
    kCFRunLoopBeforeTimers = (1UL << 1),        // 即將處理Timer:2    
    kCFRunLoopBeforeSources = (1UL << 2),       // 即將處理Source:4
    kCFRunLoopBeforeWaiting = (1UL << 5),       // 即將進(jìn)入休眠:32
    kCFRunLoopAfterWaiting = (1UL << 6),        // 即將從休眠中喚醒:64
    kCFRunLoopExit = (1UL << 7),                // 即將從Loop中退出:128
    kCFRunLoopAllActivities = 0x0FFFFFFFU       // 監(jiān)聽全部狀態(tài)改變  
};
  • 1照皆、在ViewController.m中添加如下代碼
 // 創(chuàng)建觀察者
    CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler(CFAllocatorGetDefault(), kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
        NSLog(@"監(jiān)聽到RunLoop發(fā)生改變---%zd",activity);
    });

    // 添加觀察者到當(dāng)前RunLoop中
    CFRunLoopAddObserver(CFRunLoopGetCurrent(), observer, kCFRunLoopDefaultMode);

    // 釋放observer膜毁,最后添加完需要釋放掉
    CFRelease(observer);

可以看到RunLoop的狀態(tài)在不斷的改變,最終變成了狀態(tài) 32爽茴,也就是即將進(jìn)入睡眠狀態(tài)室奏,說明RunLoop之后就會進(jìn)入睡眠狀態(tài)

RunLoop原理

好了,五個類都講解完了胧沫,下邊開始放大招了绒怨。這下我們就可以來理解RunLoop的運(yùn)行邏輯了。


RunLoop運(yùn)行邏輯圖.png

這張圖對于我們的理解RunLoop來說太有幫助了犬金,下邊我們可以理解RunLoop邏輯
每次在開啟RunLoop的時候,所在線程所在線程的RunLoop會自動處理之前未處理的事件晚顷,并且通知相關(guān)的觀察者

具體的順序如下:

  • 1该默、通知觀察者RunLoop已經(jīng)啟動
  • 2、通知觀察者即將要開始的定時器
  • 3栓袖、通知觀察者任何即將啟動的非基于端口的源
  • 4啟動任何準(zhǔn)備好的非基于端口的源
  • 5裹刮、如果基于端口的源準(zhǔn)備好并處于等待狀態(tài),立即啟動必指;并進(jìn)入步驟9
  • 6塔橡、通知觀察者線程進(jìn)入休眠狀態(tài)
  • 7、將線程置于休眠知道任一下面的事件發(fā)生:

某一事件到達(dá)基于端口的源
定時器啟動
RunLoop設(shè)置的時間已經(jīng)超時
RunLoop被顯示喚醒

  • 8葛家、通知觀察者線程將被喚醒
  • 9癞谒、處理未處理的事件

如果用戶定義的定時器啟動,處理定時器事件并重啟RunLoop弹砚。進(jìn)入步驟2
如果輸入源啟動桌吃,傳遞相應(yīng)的消息
如果RunLoop被顯示喚醒而且時間還沒超時,重啟RunLoop茅诱。進(jìn)入步驟2

  • 10瑟俭、通知觀察者RunLoop結(jié)束。

runLoop一般的使用

我們在開發(fā)應(yīng)用程序的過程中摆寄,如果后臺操作特別頻繁坯门,經(jīng)常會在子線程做一些好事的操作(下載文件田盈、后臺播放音樂等)缴阎,我們最好能讓這條線程永遠(yuǎn)常駐內(nèi)存
那么怎么做呢简软?其實開頭案例我有使用
添加一條常駐內(nèi)存的子線程,在該線程的RunLoop下添加一Sources,開啟RunLoop
具體實現(xiàn)過程如下:

    _threadP = [[NSThread alloc] initWithTarget:self selector:@selector(run1) object:nil];
    [_threadP start];
}


- (void)run1 {
    
    NSLog(@"runn1");
    
    [[NSRunLoop currentRunLoop] addPort:[NSPort port] forMode:NSDefaultRunLoopMode];
   
    [[NSRunLoop currentRunLoop] run];
    
    NSLog(@"runloop沒啟動成功");
    NSLog(@"---run:%@",[NSThread currentThread]);
    
}

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    [self performSelector:@selector(run2222) onThread:self.threadP withObject:nil waitUntilDone:NO];
    NSLog(@"runw222");
    
}
- (void)run2222 {
    NSLog(@"我在這個線程想干啥就可以干啥");
    NSLog(@"---run:%@",[NSThread currentThread]);
}

運(yùn)行之后發(fā)現(xiàn)打印了----run1-----建炫,而未開啟RunLoop則未打印

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末肛跌,一起剝皮案震驚了整個濱河市察郁,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌稳捆,老刑警劉巖麦轰,帶你破解...
    沈念sama閱讀 206,126評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件款侵,死亡現(xiàn)場離奇詭異,居然都是意外死亡甲脏,警方通過查閱死者的電腦和手機(jī)壕鹉,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,254評論 2 382
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來负乡,“玉大人脊凰,你說我怎么就攤上這事茂腥∏惺。” “怎么了朝捆?”我有些...
    開封第一講書人閱讀 152,445評論 0 341
  • 文/不壞的土叔 我叫張陵,是天一觀的道長驯用。 經(jīng)常有香客問我儒老,道長,這世上最難降的妖魔是什么薇正? 我笑而不...
    開封第一講書人閱讀 55,185評論 1 278
  • 正文 為了忘掉前任囚衔,我火速辦了婚禮练湿,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘鞠鲜。我一直安慰自己,他們只是感情好榆苞,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,178評論 5 371
  • 文/花漫 我一把揭開白布坐漏。 她就那樣靜靜地躺著碧信,像睡著了一般。 火紅的嫁衣襯著肌膚如雪躏筏。 梳的紋絲不亂的頭發(fā)上呈枉,一...
    開封第一講書人閱讀 48,970評論 1 284
  • 那天埃碱,我揣著相機(jī)與錄音酥泞,去河邊找鬼芝囤。 笑死,一個胖子當(dāng)著我的面吹牛悯姊,可吹牛的內(nèi)容都是我干的挠轴。 我是一名探鬼主播耳幢,決...
    沈念sama閱讀 38,276評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼启上!你這毒婦竟也來了店印?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 36,927評論 0 259
  • 序言:老撾萬榮一對情侶失蹤包券,失蹤者是張志新(化名)和其女友劉穎炫贤,沒想到半個月后兰珍,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,400評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡亮元,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,883評論 2 323
  • 正文 我和宋清朗相戀三年唠摹,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片勾拉。...
    茶點(diǎn)故事閱讀 37,997評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖苛秕,靈堂內(nèi)的尸體忽然破棺而出艇劫,到底是詐尸還是另有隱情,我是刑警寧澤店煞,帶...
    沈念sama閱讀 33,646評論 4 322
  • 正文 年R本政府宣布顷蟀,位于F島的核電站,受9級特大地震影響鸣个,放射性物質(zhì)發(fā)生泄漏囤萤。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,213評論 3 307
  • 文/蒙蒙 一澄惊、第九天 我趴在偏房一處隱蔽的房頂上張望富雅。 院中可真熱鬧,春花似錦亭敢、人聲如沸图筹。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,204評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽瓜晤。三九已至,卻和暖如春驱犹,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背雄驹。 一陣腳步聲響...
    開封第一講書人閱讀 31,423評論 1 260
  • 我被黑心中介騙來泰國打工医舆, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人蔬将。 一個月前我還...
    沈念sama閱讀 45,423評論 2 352
  • 正文 我出身青樓霞怀,卻偏偏與公主長得像,于是被迫代替她去往敵國和親廉沮。 傳聞我的和親對象是個殘疾皇子胁黑,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,722評論 2 345

推薦閱讀更多精彩內(nèi)容

  • Runloop基本知識 什么是Runloop Runloop丧蘸,正如其名遥皂,loop表示某種循環(huán),和run放在一起就表...
    擱淺的青蛙閱讀 290評論 0 0
  • 最近研究其他知識的時候弟孟,發(fā)現(xiàn)有用到Runloop样悟,所以特地研究下。 什么是RunLoop RunLoop用咱們的大...
    charlotte2018閱讀 436評論 0 3
  • 目錄 -RunLoop的概念 -RunLoop邏輯與實現(xiàn) -RunLoop在iOS中運(yùn)用 -RunLoop實踐 -...
    StarkShen閱讀 6,327評論 6 23
  • 1.RunLoop的介紹: RunLoop即運(yùn)行循環(huán)(跑圈),只不過它這種循環(huán)比較高級震糖。一般的 while 循環(huán)會...
    云霄_云霄閱讀 2,525評論 0 5
  • 1.RunLoop基礎(chǔ)知識 1.1 字面意思 a 運(yùn)行循環(huán) b 跑圈 1.2 基本作用(作用重大) a 保持...
    Jack__yang閱讀 332評論 0 6