iOS底層面試總結(jié)


我們經(jīng)常會看一些面試題,但是好多面試題我們都是知其然不知其所以然,你如果認(rèn)真的看了我上面總結(jié)的幾十篇文章,那么你也會知其所以然。

OC對象本質(zhì)

1、一個(gè)NSObject對象占用多少內(nèi)存?

系統(tǒng)分配了16個(gè)字節(jié)給NSObject對象(通過malloc_size函數(shù)獲得),但NSObject對象內(nèi)部只使用了8個(gè)字節(jié)的空間(64bit環(huán)境下,可以通過class_getInstanceSize函數(shù)獲得)

2、對象的isa指針指向哪里?

  • instance對象的isa指向class對象
  • class對象的isa指向meta-class對象
  • meta-class對象的isa指向基類的meta-class對象

3、OC的類信息存放在哪里?

  • 對象方法、屬性、成員變量、協(xié)議信息,存放在class對象中
  • 類方法,存放在meta-class對象中
  • 成員變量的具體值,存放在instance對象

KVO

1、iOS用什么方式實(shí)現(xiàn)對一個(gè)對象的KVO?(KVO的本質(zhì)是什么?)

  • 利用RuntimeAPI動(dòng)態(tài)生成一個(gè)子類,并且讓instance對象的isa指向這個(gè)全新的子類
  • 當(dāng)修改instance對象的屬性時(shí),會調(diào)用Foundation的_NSSetXXXValueAndNotify函數(shù)
    • 1、調(diào)用willChangeValueForKey方法
    • 2、調(diào)用setAge方法
    • 3、調(diào)用didChangeValueForKey方法
    • 4、didChangeValueForKey方法內(nèi)部調(diào)用oberser的observeValueForKeyPath:ofObject:change:context:方法

2、如何手動(dòng)觸發(fā)KVO?

手動(dòng)調(diào)用willChangeValueForKey:和didChangeValueForKey:

3、直接修改成員變量會觸發(fā)KVO么?

不會觸發(fā)KVO

KVC

1、通過KVC修改屬性會觸發(fā)KVO么?

會觸發(fā)KVO,因?yàn)镵VC是調(diào)用set方法,KVO就是監(jiān)聽set方法

2、KVC的賦值和取值過程是怎樣的?原理是什么?

KVO的setValue:forKey原理

  • 1、按照setKey,_setKey的順序查找成員方法,如果找到方法,傳遞參數(shù),調(diào)用方法
  • 2、如果沒有找到,查看accessInstanceVariablesDirectly的返回值(accessInstanceVariablesDirectly的返回值默認(rèn)是YES),
    • 返回值為YES,按照_Key,_isKey,Key,isKey的順序查找成員變量, 如果找到,直接賦值,如果沒有找到,調(diào)用setValue:forUndefinedKey:,拋出異常
    • 返回NO,直接調(diào)用setValue:forUndefinedKey:,拋出異常

KVO的ValueforKey原理

  • 1、按照getKey,key,isKey,_key的順序查找成員方法,如果找到直接調(diào)用取值
  • 2、如果沒有找到,查看accessInstanceVariablesDirectly的返回值
    • 返回值為YES,按照_Key,_isKey,Key,isKey的順序查找成員變量,如果找到,直接取值,如果沒有找到,調(diào)用setValue:forUndefinedKey:,拋出異常
    • 返回NO,直接調(diào)用setValue:forUndefinedKey:,拋出異常

Category

1、Category的實(shí)現(xiàn)原理

  • Category編譯之后的底層結(jié)構(gòu)是struct category_t,里面存儲著分類的對象方法、類方法、屬性、協(xié)議信息
  • 在程序運(yùn)行的時(shí)候,runtime會將Category的數(shù)據(jù),合并到類信息中(類對象、元類對象中)

2、Category和Class Extension的區(qū)別是什么?

  • Class Extension在編譯的時(shí)候,它的數(shù)據(jù)就已經(jīng)包含在類信息中
  • Category是在運(yùn)行時(shí),才會將數(shù)據(jù)合并到類信息中

3、load、initialize方法的區(qū)別什么?

  • 1.調(diào)用方式

    • 1> load是根據(jù)函數(shù)地址直接調(diào)用
    • 2> initialize是通過objc_msgSend調(diào)用
  • 2.調(diào)用時(shí)刻

    • 1> load是runtime加載類、分類的時(shí)候調(diào)用(只會調(diào)用1次 )
    • 2> initialize是類第一次接收到消息的時(shí)候調(diào)用,每一個(gè)類只會initialize一次(父類的initialize方法可能會被調(diào)用多次)

4、load、initialize的調(diào)用順序

1.load

  • 1> 先調(diào)用類的load
    • a) 先編譯的類,優(yōu)先調(diào)用load
    • b) 調(diào)用子類的load之前,會先調(diào)用父類的load
  • 2> 再調(diào)用分類的load
    • a) 先編譯的分類,優(yōu)先調(diào)用load

2.initialize

  • 1> 先初始化父類
  • 2> 再初始化子類(可能最終調(diào)用的是父類的initialize方法)

5、如何實(shí)現(xiàn)給分類“添加成員變量”?

默認(rèn)情況下,因?yàn)榉诸惖讓咏Y(jié)構(gòu)的限制,不能添加成員變量到分類中。但可以通過關(guān)聯(lián)對象來間接實(shí)現(xiàn)

關(guān)聯(lián)對象提供了以下API
添加關(guān)聯(lián)對象
void objc_setAssociatedObject(id object, const void * key,
                                id value, objc_AssociationPolicy policy)

獲得關(guān)聯(lián)對象
id objc_getAssociatedObject(id object, const void * key)

移除所有的關(guān)聯(lián)對象
void objc_removeAssociatedObjects(id object)

Block

1、block的原理是怎樣的?本質(zhì)是什么?

  • block本質(zhì)上也是一個(gè)OC對象,它內(nèi)部也有個(gè)isa指針
  • block是封裝了函數(shù)調(diào)用以及函數(shù)調(diào)用環(huán)境的OC對象

2、block的(capture)

為了保證block內(nèi)部能夠正常訪問外部的變量,block有個(gè)變量捕獲機(jī)制

3、Block類型有哪幾種 block有3種類型,可以通過調(diào)用class方法或者isa指針查看具體類型,最終都是繼承自NSBlock類型

  • 1、NSGlobalBlock ( _NSConcreteGlobalBlock
  • 2、NSStackBlock ( _NSConcreteStackBlock )
  • 3、NSMallocBlock ( _NSConcreteMallocBlock )

4、block的copy

在ARC環(huán)境下,編譯器會根據(jù)情況自動(dòng)將棧上的block復(fù)制到堆上,比如以下情況

  • 1、block作為函數(shù)返回值時(shí)
  • 2、將block賦值給__strong指針時(shí)
  • 3、block作為Cocoa API中方法名含有usingBlock的方法參數(shù)時(shí)
  • 4、block作為GCD API的方法參數(shù)時(shí)
MRC下block屬性的建議寫法
@property (copy, nonatomic) void (^block)(void);

ARC下block屬性的建議寫法
@property (strong, nonatomic) void (^block)(void);
@property (copy, nonatomic) void (^block)(void);

5、__block修飾符

  • __block可以用于解決block內(nèi)部無法修改auto變量值的問題

  • __block不能修飾全局變量、靜態(tài)變量(static)

  • 編譯器會將__block變量包裝成一個(gè)對象

  • 當(dāng)__block變量在棧上時(shí),不會對指向的對象產(chǎn)生強(qiáng)引用

  • 當(dāng)__block變量被copy到堆時(shí)

    • 會調(diào)用__block變量內(nèi)部的copy函數(shù)
    • copy函數(shù)內(nèi)部會調(diào)用_Block_object_assign函數(shù)
    • _Block_object_assign函數(shù)會根據(jù)所指向?qū)ο蟮男揎椃╛_strong、__weak、__unsafe_unretained)做出相應(yīng)的操作,形成強(qiáng)引用(retain)或者弱引用(注意:這里僅限于ARC時(shí)會retain,MRC時(shí)不會retain)
  • 如果__block變量從堆上移除

    • 會調(diào)用__block變量內(nèi)部的dispose函數(shù)
    • dispose函數(shù)內(nèi)部會調(diào)用_Block_object_dispose函數(shù)
    • _Block_object_dispose函數(shù)會自動(dòng)釋放指向的對象(release)

6、循環(huán)引用

  • 用__weak、__unsafe_unretained解決
__unsafe_unretained typeof(self) weakSelf = self;
self.block = ^{
 print(@"%p", weakSelf);
}
__weak typeof(self) weakSelf = self;
self.block = ^{
 print(@"%p", weakSelf);
}
  • 用__block解決(必須要調(diào)用block)
__block id weakSelf = self;
self.block = ^{
weakSelf = nil;
}
self.block();

RunTime

1、講一下 OC 的消息機(jī)制

  • OC中的方法調(diào)用其實(shí)都是轉(zhuǎn)成了objc_msgSend函數(shù)的調(diào)用,給receiver(方法調(diào)用者)發(fā)送了一條消息(selector方法名)
  • objc_msgSend底層有3大階段:消息發(fā)送(當(dāng)前類、父類中查找)、動(dòng)態(tài)方法解析、消息轉(zhuǎn)發(fā)

2、消息轉(zhuǎn)發(fā)機(jī)制流程

  • 1、消息發(fā)送
  • 2、動(dòng)態(tài)方法解析
  • 3、消息轉(zhuǎn)發(fā)

消息發(fā)送階段

消息發(fā)送流程是我們平時(shí)最經(jīng)常使用的流程,其他的像動(dòng)態(tài)方法解析和消息轉(zhuǎn)發(fā)其實(shí)是補(bǔ)救措施。具體流程如下

  • 1、首先判斷消息接受者receiver是否為nil,如果為nil直接退出消息發(fā)送
  • 2、如果存在消息接受者receiverClass,首先在消息接受者receiverClass的cache中查找方法,如果找到方法,直接調(diào)用。如果找不到,往下進(jìn)行
  • 3、沒有在消息接受者receiverClass的cache中找到方法,則從receiverClass的class_rw_t中查找方法,如果找到方法,執(zhí)行方法,并把該方法緩存到receiverClass的cache中;如果沒有找到,往下進(jìn)行
  • 4、沒有在receiverClass中找到方法,則通過superClass指針找到superClass,也是現(xiàn)在緩存中查找,如果找到,執(zhí)行方法,并把該方法緩存到receiverClass的cache中;如果沒有找到,往下進(jìn)行
  • 5、沒有在消息接受者superClass的cache中找到方法,則從superClass的class_rw_t中查找方法,如果找到方法,執(zhí)行方法,并把該方法緩存到receiverClass的cache中;如果沒有找到,重復(fù)4、5步驟。如果找不到了superClass了,往下進(jìn)行
  • 6、如果在最底層的superClass也找不到該方法,則要轉(zhuǎn)到動(dòng)態(tài)方法解析

動(dòng)態(tài)方法解析

開發(fā)者可以實(shí)現(xiàn)以下方法,來動(dòng)態(tài)添加方法實(shí)現(xiàn)

  • +resolveInstanceMethod:
  • +resolveClassMethod: 動(dòng)態(tài)解析過后,會重新走“消息發(fā)送”的流程,從receiverClass的cache中查找方法這一步開始執(zhí)行

消息轉(zhuǎn)發(fā)

如果方法一個(gè)方法在消息發(fā)送階段沒有找到相關(guān)方法,也沒有進(jìn)行動(dòng)態(tài)方法解析,這個(gè)時(shí)候就會走到消息轉(zhuǎn)發(fā)階段了。

  • 調(diào)用forwardingTargetForSelector,返回值不為nil時(shí),會調(diào)用objc_msgSend(返回值, SEL)
  • 調(diào)用methodSignatureForSelector,返回值不為nil,調(diào)用forwardInvocation:方法;返回值為nil時(shí),調(diào)用doesNotRecognizeSelector:方法
  • 開發(fā)者可以在forwardInvocation:方法中自定義任何邏輯
  • 以上方法都有對象方法、類方法2個(gè)版本(前面可以是加號+,也可以是減號-)

3、什么是Runtime?平時(shí)項(xiàng)目中有用過么?

  • OC是一門動(dòng)態(tài)性比較強(qiáng)的編程語言,允許很多操作推遲到程序運(yùn)行時(shí)再進(jìn)行
  • OC的動(dòng)態(tài)性就是由Runtime來支撐和實(shí)現(xiàn)的,Runtime是一套C語言的API,封裝了很多動(dòng)態(tài)性相關(guān)的函數(shù)
  • 平時(shí)編寫的OC代碼,底層都是轉(zhuǎn)換成了Runtime API進(jìn)行調(diào)用

具體應(yīng)用

  • 利用關(guān)聯(lián)對象(AssociatedObject)給分類添加屬性
  • 遍歷類的所有成員變量(修改textfield的占位文字顏色、字典轉(zhuǎn)模型、自動(dòng)歸檔解檔)
  • 交換方法實(shí)現(xiàn)(交換系統(tǒng)的方法)
  • 利用消息轉(zhuǎn)發(fā)機(jī)制解決方法找不到的異常問題

4、super的本質(zhì)

  • super調(diào)用,底層會轉(zhuǎn)換為objc_msgSendSuper2函數(shù)的調(diào)用,接收2個(gè)參數(shù)
    • struct objc_super2
    • SEL
  • receiver是消息接收者
  • current_class是receiver的Class對象

RunLoop

1、講講 RunLoop,項(xiàng)目中有用到嗎?

  • 定時(shí)器切換的時(shí)候,為了保證定時(shí)器的準(zhǔn)確性,需要添加runLoop 2、在聊天界面,我們需要持續(xù)的把聊天信息存到數(shù)據(jù)庫中,這個(gè)時(shí)候需要開啟一個(gè)保活線程,在這個(gè)線程中處理

2、runloop內(nèi)部實(shí)現(xiàn)邏輯

每次運(yùn)行RunLoop,線程的RunLoop會自動(dòng)處理之前未處理的消息,并通知相關(guān)的觀察者。具體順序

  • 1、通知觀察者(observers)RunLoop即將啟動(dòng)
  • 2、通知觀察者(observers)任何即將要開始的定時(shí)器
  • 3、通知觀察者(observers)即將處理source0事件
  • 4、處理source0
  • 5、如果有source1,跳到第9步
  • 6、通知觀察者(observers)線程即將進(jìn)入休眠
  • 7、將線程置于休眠知道任一下面的事件發(fā)生
    • 1、source0事件觸發(fā)
    • 2、定時(shí)器啟動(dòng)
    • 3、外部手動(dòng)喚醒
  • 8、通知觀察者(observers)線程即將喚醒
  • 9、處理喚醒時(shí)收到的時(shí)間,之后跳回2
    • 1、如果用戶定義的定時(shí)器啟動(dòng),處理定時(shí)器事件
    • 2、如果source0啟動(dòng),傳遞相應(yīng)的消息
  • 10、通知觀察者RunLoop結(jié)束

3、RunLoop與線程

  • 每條線程都有唯一的一個(gè)與之對應(yīng)的RunLoop對象
  • RunLoop保存在一個(gè)全局的Dictionary里,線程作為key,RunLoop作為value
  • 線程剛創(chuàng)建時(shí)并沒有RunLoop對象,RunLoop會在第一次獲取它時(shí)創(chuàng)建
  • RunLoop會在線程結(jié)束時(shí)銷毀
  • 主線程的RunLoop已經(jīng)自動(dòng)獲?。▌?chuàng)建),子線程默認(rèn)沒有開啟RunLoop

4、timer 與 runloop 的關(guān)系?

  • 一個(gè)RunLoop包含若干個(gè)Mode,每個(gè)Mode又包含若干個(gè)Source0/Source1/Timer/Observer
  • RunLoop啟動(dòng)時(shí)只能選擇其中一個(gè)Mode,作為currentMode
  • 如果需要切換Mode,只能退出當(dāng)前Loop,再重新選擇一個(gè)Mode進(jìn)入
  • 不同組的Source0/Source1/Timer/Observer能分隔開來,互不影響
  • 如果Mode里沒有任何Source0/Source1/Timer/Observer,RunLoop會立馬退出

解決定時(shí)器在滾動(dòng)視圖上面失效問題NSTimer添加到兩種RunLoop中

[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];
[[NSRunLoop currentRunLoop] addTimer:timer forMode:UITrackingRunLoopMode];
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];

5、RunLoop有幾種狀態(tài)

kCFRunLoopEntry = (1UL << 0), // 即將進(jìn)入RunLoop 
kCFRunLoopBeforeTimers = (1UL << 1), // 即將處理Timer 
kCFRunLoopBeforeSources = (1UL << 2), // 即將處理Source 
kCFRunLoopBeforeWaiting = (1UL << 5), //即將進(jìn)入休眠 
kCFRunLoopAfterWaiting = (1UL << 6),// 剛從休眠中喚醒
 kCFRunLoopExit = (1UL << 7),// 即將退出RunLoop

**6、RunLoop的mode的作用 **

RunLoop的mode的作用 系統(tǒng)注冊了5中mode

kCFRunLoopDefaultMode //App的默認(rèn)Mode,通常主線程是在這個(gè)Mode下運(yùn)行
UITrackingRunLoopMode //界面跟蹤 Mode,用于 ScrollView 追蹤觸摸滑動(dòng),保證界面滑動(dòng)時(shí)不受其他 Mode 影響
UIInitializationRunLoopMode // 在剛啟動(dòng) App 時(shí)第進(jìn)入的第一個(gè) Mode,啟動(dòng)完成后就不再使用
GSEventReceiveRunLoopMode // 接受系統(tǒng)事件的內(nèi)部 Mode,通常用不到
kCFRunLoopCommonModes //這是一個(gè)占位用的Mode,不是一種真正的Mode

但是我們只能使用兩種mode

kCFRunLoopDefaultMode //App的默認(rèn)Mode,通常主線程是在這個(gè)Mode下運(yùn)行
UITrackingRunLoopMode //界面跟蹤 Mode,用于 ScrollView 追蹤觸摸滑動(dòng),保證界面滑動(dòng)時(shí)不受其他 Mode 影響

多線程

1、你理解的多線程?
2、iOS的多線程方案有哪幾種?你更傾向于哪一種?
3、你在項(xiàng)目中用過 GCD 嗎?
4、GCD 的隊(duì)列類型
5、說一下 OperationQueue 和 GCD 的區(qū)別,以及各自的優(yōu)勢
6、線程安全的處理手段有哪些? 使用線程鎖

  • 1、OSSpinLock
  • 2、os_unfair_lock
  • 3、pthread_mutex
  • 4、dispatch_semaphore
  • 5、dispatch_queue(DISPATCH_QUEUE_SERIAL)
  • 6、NSLock
  • 7、NSRecursiveLock
  • 8、NSCondition
  • 9、NSConditionLock
  • 10、@synchronized
  • 11、pthread_rwlock
  • 12、dispatch_barrier_async
  • 13、atomic

7、線程通訊 線程間通信的體現(xiàn)

  • 1、一個(gè)線程傳遞數(shù)據(jù)給另一個(gè)線程
  • 2、在一個(gè)線程中執(zhí)行完特定任務(wù)后,轉(zhuǎn)到另一個(gè)線程繼續(xù)執(zhí)行任務(wù)

1、NSThread 可以先將自己的當(dāng)前線程對象注冊到某個(gè)全局的對象中去,這樣相互之間就可以獲取對方的線程對象,然后就可以使用下面的方法進(jìn)行線程間的通信了,由于主線程比較特殊,所以框架直接提供了在主線程執(zhí)行的方法

- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(nullable id)arg waitUntilDone:(BOOL)wait;

- (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(nullable id)arg waitUntilDone:(BOOL)wait NS_AVAILABLE(10_5, 2_0);

2、GCD

 dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{

 });

內(nèi)存管理

1、使用CADisplayLink、NSTimer有什么注意點(diǎn)?
CADisplayLink、NSTimer會對target產(chǎn)生強(qiáng)引用,如果target又對它們產(chǎn)生強(qiáng)引用,那么就會引發(fā)循環(huán)引用

2、介紹下內(nèi)存的幾大區(qū)域

  • 代碼段:編譯之后的代碼
  • 數(shù)據(jù)段
    • 字符串常量:比如NSString *str = @"123"
    • 已初始化數(shù)據(jù):已初始化的全局變量、靜態(tài)變量等
    • 未初始化數(shù)據(jù):未初始化的全局變量、靜態(tài)變量等
  • 棧:函數(shù)調(diào)用開銷,比如局部變量。分配的內(nèi)存空間地址越來越小
  • 堆:通過alloc、malloc、calloc等動(dòng)態(tài)分配的空間,分配的內(nèi)存空間地址越來越大

3、講一下你對 iOS 內(nèi)存管理的理解 在iOS中,使用引用計(jì)數(shù)來管理OC對象的內(nèi)存

  • 一個(gè)新創(chuàng)建的OC對象引用計(jì)數(shù)默認(rèn)是1,當(dāng)引用計(jì)數(shù)減為0,OC對象就會銷毀,釋放其占用的內(nèi)存空間
  • 調(diào)用retain會讓OC對象的引用計(jì)數(shù)+1,調(diào)用release會讓OC對象的引用計(jì)數(shù)-1

內(nèi)存管理的經(jīng)驗(yàn)總結(jié)

  • 當(dāng)調(diào)用alloc、new、copy、mutableCopy方法返回了一個(gè)對象,在不需要這個(gè)對象時(shí),要調(diào)用release或者autorelease來釋放它
  • 想擁有某個(gè)對象,就讓它的引用計(jì)數(shù)+1;不想再擁有某個(gè)對象,就讓它的引用計(jì)數(shù)-1

可以通過以下私有函數(shù)來查看自動(dòng)釋放池的情況 extern void _objc_autoreleasePoolPrint(void);

4、ARC 都幫我們做了什么 LLVM + Runtime

  • LVVM生成release代碼
  • RunTime負(fù)責(zé)執(zhí)行

5、weak指針的實(shí)現(xiàn)原理 runtime維護(hù)了一個(gè)weak表,用于存儲指向某個(gè)對象的所有weak指針。weak表其實(shí)是一個(gè)hash(哈希)表,key是所指對象的地址,Value是weak指針的地址(這個(gè)地址的值是所指對象指針的地址)數(shù)組

  • 1、初始化時(shí):runtime會調(diào)用objc_initWeak函數(shù),初始化一個(gè)新的weak指針指向?qū)ο蟮牡刂?/li>
  • 2、添加引用時(shí):objc_initWeak函數(shù)會調(diào)用 storeWeak() 函數(shù), storeWeak() 的作用是更新指針指向,創(chuàng)建對應(yīng)的弱引用表
  • 3、釋放時(shí),調(diào)用clearDeallocating函數(shù)。clearDeallocating函數(shù)首先根據(jù)對象地址獲取所有weak指針地址的數(shù)組,然后遍歷這個(gè)數(shù)組把其中的數(shù)據(jù)設(shè)為nil,最后把這個(gè)entry從weak表中刪除,最后清理對象的記錄

6、autorelease對象在什么時(shí)機(jī)會被調(diào)用release

  • 1、iOS在主線程的Runloop中注冊了2個(gè)Observer
  • 2、第1個(gè)Observer監(jiān)聽了kCFRunLoopEntry事件,會調(diào)用objc_autoreleasePoolPush()
  • 3、第2個(gè)Observer監(jiān)聽了kCFRunLoopBeforeWaiting事件,會調(diào)用objc_autoreleasePoolPop()、objc_autoreleasePoolPush() 監(jiān)聽了kCFRunLoopBeforeExit事件,會調(diào)用objc_autoreleasePoolPop() autoreleased 對象是在 runloop 的即將進(jìn)入休眠時(shí)進(jìn)行釋放的

7、方法里有局部對象, 出了方法后會立即釋放嗎 在ARC情況下會立即釋放 在MRC情況下,對象是在 runloop 的即將進(jìn)入休眠時(shí)進(jìn)行釋放的

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容