iOS 編寫高質(zhì)量Objective-C代碼(二)—— 面向?qū)ο?/h1>

《編寫高質(zhì)量OC代碼》已順利完成一二三四五六七八篇!
附上鏈接:
iOS 編寫高質(zhì)量Objective-C代碼(一)—— 簡(jiǎn)介
iOS 編寫高質(zhì)量Objective-C代碼(二)—— 面向?qū)ο?/a>
iOS 編寫高質(zhì)量Objective-C代碼(三)—— 接口和API設(shè)計(jì)
iOS 編寫高質(zhì)量Objective-C代碼(四)—— 協(xié)議與分類
iOS 編寫高質(zhì)量Objective-C代碼(五)—— 內(nèi)存管理機(jī)制
iOS 編寫高質(zhì)量Objective-C代碼(六)—— block專欄
iOS 編寫高質(zhì)量Objective-C代碼(七)—— GCD專欄
iOS 編寫高質(zhì)量Objective-C代碼(八)—— 系統(tǒng)框架


這篇將從面向?qū)ο?/strong>的角度分析如何提高OC的代碼質(zhì)量栽连。

一、理解“ 屬性 ”這一概念

屬性(@property)是OC的一項(xiàng)特性砚殿。
@property:編譯器會(huì)自動(dòng)生成實(shí)例變量gettersetter方法粘茄。
For Example:

@property (nonatomic, strong) UIView *qiShareView;

等價(jià)于:

@synthesize qiShareView = _qiShareView;
- (UIView *)qiShareView;
- (void)setQiShareView:(UIView *)qiShareView;

如果不希望生成存取方法和實(shí)例變量侨核,那就要使用@dynamic關(guān)鍵字

@dynamic qiShareView;

屬性特質(zhì)有四類:

  1. 原子性:默認(rèn)為atomic
    • nonatomic:非原子性,讀寫時(shí)不加同步鎖
    • atomic:原子性祈惶,讀寫時(shí)加同步鎖
  2. 讀寫權(quán)限:默認(rèn)為readwrite
    • readwrite:讀寫雕旨,擁有gettersetter方法
    • readonly:只讀,僅擁有getter方法
  3. 內(nèi)存管理:
    • assign:對(duì)“純量類型”做簡(jiǎn)單賦值操作(NSInteger捧请、CGFloat等)奸腺。
    • strong:強(qiáng)擁有關(guān)系,設(shè)置方法 保留新值血久,并釋放舊值突照。
    • weak:弱擁有關(guān)系,設(shè)置方法 不保留新值氧吐,不釋放舊值讹蘑。當(dāng)指針指向的對(duì)象銷毀時(shí),指針置nil筑舅。
    • copy:拷貝擁有關(guān)系座慰,設(shè)置方法不保留新值,將其拷貝翠拣。
    • unsafe_unretained:非擁有關(guān)系版仔,目標(biāo)對(duì)象被釋放,指針不置nil误墓,這一點(diǎn)和assign一樣蛮粮。區(qū)別于weak
  4. 方法名:
    • getter=<name>:指定get方法的方法名,常用
    • setter=<name>:指定set方法的方法名谜慌,不常用
      例如:
@property (nonatomic, getter=isOn) BOOL on;

在iOS開發(fā)中然想,99.99..%的屬性都會(huì)聲明為nonatomic。
一是atomic會(huì)嚴(yán)重影響性能欣范,
二是atomic只能保證讀/寫操作的過程是可靠的变泄,并不能保證線程安全。
關(guān)于第二點(diǎn)可以參考我的博客:iOS 為什么屬性聲明為atomic依然不能保證線程安全恼琼?

二妨蛹、在對(duì)象內(nèi)部盡量直接訪問實(shí)例變量

  1. 實(shí)例變量( _屬性名 )訪問對(duì)象的場(chǎng)景:
    • initdealloc方法中,總是應(yīng)該通過訪問實(shí)例變量讀寫數(shù)據(jù)
    • 沒有重寫gettersetter方法晴竞、也沒有使用KVO監(jiān)聽
    • 好處:不走OC的方法派發(fā)機(jī)制蛙卤,直接訪問內(nèi)存讀寫,速度快颓鲜,效率高表窘。

For Example:

- (instancetype)initWithDic:(NSDictionary *)dic {
    
    self = [super init];
    
    if (self) {
        
        _qi = dic[@"qi"];
        _share = dic[@"share"];
    }
    
    return self;
}
  1. 用存取方法訪問對(duì)象的場(chǎng)景:
    • 重寫了getter/setter方法(比如:懶加載)
    • 使用了KVO監(jiān)聽值的改變

For Example:

- (UIView *)qiShareView {
  
    if (!_qiShareView) {

        _qiShareView = [UIView new];
    }

    return _qiShareView;
}

三、理解“對(duì)象等同性”

思考下面輸出什么甜滨?

NSString *aString = @"iPhone 8";
NSString *bString = [NSString stringWithFormat:@"iPhone %i", 8];
NSLog(@"%d", [aString isEqual:bString]);
NSLog(@"%d", [aString isEqualToString:bString]);
NSLog(@"%d", aString == bString);

答案是110
==操作符只是比較了兩個(gè)指針?biāo)笇?duì)象的地址是否相同乐严,而不是指針?biāo)傅膶?duì)象的值
所以最后一個(gè)為0

四、以類族模式隱藏實(shí)現(xiàn)細(xì)節(jié)

為什么下面這個(gè)例子的if永遠(yuǎn)為false衣摩?

id maybeAnArray = @[];
if ([maybeAnArray class] == [NSArray class]) {
     //Code will never be executed
}

因?yàn)?code>[maybeAnArray class] 的返回永遠(yuǎn)不會(huì)是NSArray昂验,NSArray是一個(gè)類族,返回的值一直都是NSArray的實(shí)體子類艾扮。大部分collection類都是某個(gè)類族中的抽象基類
所以上面的if想要有機(jī)會(huì)執(zhí)行的話要改成

id maybeAnArray = @[];
if ([maybeAnArray isKindOfClass [NSArray class]) {
      // Code probably be executed
}

這樣判斷的意思是既琴,maybeAnArray這個(gè)對(duì)象是否是NSArray類族中的一員
使用類族的好處:可以把實(shí)現(xiàn)細(xì)節(jié)隱藏再一套簡(jiǎn)單的公共接口后面

五、在既有類中使用關(guān)聯(lián)對(duì)象存放自定義數(shù)據(jù)

先引入runtime類庫(kù)

#import <objc/runtime.h>
objc_AssociationPolicy(對(duì)象關(guān)聯(lián)策略類型):
objc_AssociationPolicy(關(guān)聯(lián)策略類型) 等效的@property屬性
OBJC_ASSOCIATION_ASSIGN 等效于 assign
OBJC_ASSOCIATION_RETAIN_NONATOMIC 等效于 nonatomic, retain
OBJC_ASSOCIATION_COPY_NONATOMIC 等效于 nonatomic, copy
OBJC_ASSOCIATION_RETAIN 等效于 retain
OBJC_ASSOCIATION_COPY 等效于 copy
三個(gè)方法管理關(guān)聯(lián)對(duì)象:
  • objc_setAssociatedObject(設(shè)置關(guān)聯(lián)對(duì)象)
/** 
 * Sets an associated value for a given object using a given key and association policy.
 * 
 * @param object The source object for the association.
 * @param key The key for the association.
 * @param value The value to associate with the key key for object. Pass nil to clear an existing association.
 * @param policy The policy for the association. For possible values, see “Associative Object Behaviors.”
 */
OBJC_EXPORT void
objc_setAssociatedObject(id _Nonnull object, const void * _Nonnull key,
                         id _Nullable value, objc_AssociationPolicy policy)
  • objc_getAssociatedObject(獲得關(guān)聯(lián)對(duì)象)
/** 
 * Returns the value associated with a given object for a given key.
 * 
 * @param object The source object for the association.
 * @param key The key for the association.
 * 
 * @return The value associated with the key \e key for \e object.
 */
OBJC_EXPORT id _Nullable
objc_getAssociatedObject(id _Nonnull object, const void * _Nonnull key)
  • objc_removeAssociatedObjects(去除關(guān)聯(lián)對(duì)象)
/** 
 * Removes all associations for a given object.
 * 
 * @param object An object that maintains associated objects.
 * 
 * @note The main purpose of this function is to make it easy to return an object 
 *  to a "pristine state”. You should not use this function for general removal of
 *  associations from objects, since it also removes associations that other clients
 *  may have added to the object. Typically you should use \c objc_setAssociatedObject 
 *  with a nil value to clear an association.
 * 
 */
OBJC_EXPORT void
objc_removeAssociatedObjects(id _Nonnull object)

小結(jié):

  • 可以通過“關(guān)聯(lián)對(duì)象”機(jī)制可以把兩個(gè)對(duì)象聯(lián)系起來
  • 定義關(guān)聯(lián)對(duì)象可以指定內(nèi)存管理策略
  • 應(yīng)用場(chǎng)景:只有在其他做法(代理泡嘴、通知等)不可行時(shí)甫恩,才可以選擇使用關(guān)聯(lián)對(duì)象。這種做法難于找bug

六酌予、理解objc_msgSend(對(duì)象的消息傳遞機(jī)制)

首先我們要區(qū)分兩個(gè)基本概念:
1 .靜態(tài)綁定(static binding):在編譯期就能決定運(yùn)行時(shí)所應(yīng)調(diào)用的函數(shù)磺箕。代表語(yǔ)言:C、C++等
2 .動(dòng)態(tài)綁定 (dynamic binding):所要調(diào)用的函數(shù)直到運(yùn)行期才能確定抛虫。代表語(yǔ)言:OC松靡、swift等

OC是一門強(qiáng)大的動(dòng)態(tài)語(yǔ)言,它的動(dòng)態(tài)性體現(xiàn)在它強(qiáng)大的runtime機(jī)制上建椰。

解釋:在OC中雕欺,如果向某對(duì)象傳遞消息,那就會(huì)使用動(dòng)態(tài)綁定機(jī)制來決定需要調(diào)用的方法棉姐。在底層屠列,所有方法都是普通的C語(yǔ)言函數(shù),然而對(duì)象收到消息后伞矩,由運(yùn)行期決定究竟調(diào)用哪個(gè)方法脸哀,甚至可以在程序運(yùn)行時(shí)改變,這些特性使得OC成為一門強(qiáng)大的動(dòng)態(tài)語(yǔ)言扭吁。

底層實(shí)現(xiàn):基于C語(yǔ)言函數(shù)實(shí)現(xiàn)撞蜂。
  • 實(shí)現(xiàn)的基本函數(shù)是objc_msgSend,定義如下:
void objc_msgSend(id self, SEL cmd, ...) 

這是一個(gè)參數(shù)個(gè)數(shù)可變的函數(shù)侥袜,第一參數(shù)代表接受者蝌诡,第二個(gè)參數(shù)代表選擇子(OC函數(shù)名),之后的參數(shù)就是消息中傳入的參數(shù)枫吧。

  • 舉例:git提交
id return = [git commit:parameter];

上面的方法會(huì)在運(yùn)行時(shí)轉(zhuǎn)換成如下的OC函數(shù):

id return = objc_msgSend(git, @selector(commit), parameter);

objc_msgSend函數(shù)會(huì)在接收者所屬的類中搜尋其方法列表浦旱,如果能找到這個(gè)跟選擇子名稱相同的方法,就跳轉(zhuǎn)到其實(shí)現(xiàn)代碼九杂,往下執(zhí)行颁湖。若是當(dāng)前類沒找到宣蠕,那就沿著繼承體系繼續(xù)向上查找,等找到合適方法之后再跳轉(zhuǎn) 甥捺,如果最終還是找不到抢蚀,那就進(jìn)入消息轉(zhuǎn)發(fā)(下一條具體展開)的流程去進(jìn)行處理了。

可是如果每次傳遞消息都要把類中的方法遍歷一遍镰禾,這么多消息傳遞加起來肯定會(huì)很耗性能皿曲。所以以下講解OC消息傳遞的優(yōu)化方法。

OC對(duì)消息傳遞的優(yōu)化:
  • 快速映射表(緩存)優(yōu)化:
    objc_msgSend在搜索這塊是有做緩存的吴侦,每個(gè)OC的類都有一塊這樣的緩存屋休,objc_msgSend會(huì)將匹配結(jié)果緩存在快速映射表(fast map)中,這樣以來這個(gè)類一些頻繁調(diào)用的方法會(huì)出現(xiàn)在fast map 中备韧,不用再去一遍一遍的在方法列表中搜索了劫樟。
  • 尾調(diào)用優(yōu)化
    原理:這里專門總結(jié)了一篇博客。 鏈接:看這里看這里~~
    好處:最大限度的合理的分配使用的資源织堂,避免過早發(fā)生棧溢出的現(xiàn)象毅哗。

七、理解消息轉(zhuǎn)發(fā)機(jī)制

首先區(qū)分兩個(gè)基本概念:
1 .消息傳遞:對(duì)象正常解讀消息捧挺,傳遞過去(見上一條)虑绵。
2 .消息轉(zhuǎn)發(fā):對(duì)象無(wú)法解讀消息,之后進(jìn)行消息轉(zhuǎn)發(fā)闽烙。

消息轉(zhuǎn)發(fā)完整流程圖:
消息轉(zhuǎn)發(fā)完整流程

流程解釋:

  • 第一步:調(diào)用resolveInstanceMethod:征詢接受者(所屬的類)是否可以添加方法以處理未知的選擇子翅睛?(此過程稱為動(dòng)態(tài)方法解析)若有,轉(zhuǎn)發(fā)結(jié)束黑竞。若沒有捕发,走第二步。
  • 第二步:調(diào)用forwardingTargetForSelector:詢問接受者是否有其他對(duì)象能處理此消息很魂。若有扎酷,轉(zhuǎn)發(fā)結(jié)束,一切如常遏匆。若沒有法挨,走第三步。
  • 第三步:調(diào)用forwardInvocation:運(yùn)行期系統(tǒng)將消息封裝到NSInvocation對(duì)象中幅聘,再給接受者一次機(jī)會(huì)凡纳。
  • 最后:以上三步還不行,就拋出異常:unrecognized selector sent to instance xxxx

八帝蒿、用“方法調(diào)配技術(shù)”調(diào)試“黑盒方法”

方法調(diào)配(Method Swizzling):使用另一種方法實(shí)現(xiàn)來替換原有的方法實(shí)現(xiàn)荐糜。(實(shí)際應(yīng)用中,常用此技術(shù)向原有實(shí)現(xiàn)中添加新的功能。)

<objc/runtime.h>里的兩個(gè)常用的方法:

  • 獲取給定類的指定實(shí)例方法:
/** 
 * Returns a specified instance method for a given class.
 * 
 * @param cls The class you want to inspect.
 * @param name The selector of the method you want to retrieve.
 * 
 * @return The method that corresponds to the implementation of the selector specified by 
 *  \e name for the class specified by \e cls, or \c NULL if the specified class or its 
 *  superclasses do not contain an instance method with the specified selector.
 *
 * @note This function searches superclasses for implementations, whereas \c class_copyMethodList does not.
 */
OBJC_EXPORT Method _Nullable
class_getInstanceMethod(Class _Nullable cls, SEL _Nonnull name)
  • 交換兩種方法實(shí)現(xiàn)的方法:
/** 
 * Exchanges the implementations of two methods.
 * 
 * @param m1 Method to exchange with second method.
 * @param m2 Method to exchange with first method.
 * 
 * @note This is an atomic version of the following:
 *  \code 
 *  IMP imp1 = method_getImplementation(m1);
 *  IMP imp2 = method_getImplementation(m2);
 *  method_setImplementation(m1, imp2);
 *  method_setImplementation(m2, imp1);
 *  \endcode
 */
OBJC_EXPORT void
method_exchangeImplementations(Method _Nonnull m1, Method _Nonnull m2) 

利用這兩個(gè)方法就可以交換指定類中的指定方法暴氏。在實(shí)際應(yīng)用中延塑,我們會(huì)通過這種方式為既有方法添加新功能。

For Example:交換method1與method2的方法實(shí)現(xiàn)

Method method1 = class_getInstanceMethod(self, @selector(method1:));
Method method2 = class_getInstanceMethod(self, @selector(method2:));
method_exchangeImplementations(method1, method2);

九答渔、理解“類對(duì)象”的用意

Objective-C類是由Class類型來表示的关带,實(shí)質(zhì)是一個(gè)指向objc_class結(jié)構(gòu)體的指針。它的定義如下:

typedef struct objc_class *Class;

在<objc/runtime.h>中能看到他的實(shí)現(xiàn):

struct objc_class {
    Class _Nonnull isa  OBJC_ISA_AVAILABILITY;  //!< 指向metaClass(元類)的指針

#if !__OBJC2__
    Class _Nullable super_class                              OBJC2_UNAVAILABLE;  //!< 父類
    const char * _Nonnull name                               OBJC2_UNAVAILABLE;  //!< 類名
    long version                                             OBJC2_UNAVAILABLE;  //!< 類的版本信息研儒,默認(rèn)為0
    long info                                                OBJC2_UNAVAILABLE;  //!< 類信息豫缨,供運(yùn)行期使用的一些位標(biāo)識(shí)
    long instance_size                                       OBJC2_UNAVAILABLE;  //!< 該類的實(shí)例變量大小
    struct objc_ivar_list * _Nullable ivars                  OBJC2_UNAVAILABLE;  //!< 該類的成員變量鏈表
    struct objc_method_list * _Nullable * _Nullable methodLists                    OBJC2_UNAVAILABLE;  //!< 方法定義的鏈表
    struct objc_cache * _Nonnull cache                       OBJC2_UNAVAILABLE;  //!< 方法緩存表
    struct objc_protocol_list * _Nullable protocols          OBJC2_UNAVAILABLE;  //!< 協(xié)議鏈表
#endif

} OBJC2_UNAVAILABLE;
/* Use `Class` instead of `struct objc_class *` */

此結(jié)構(gòu)體存放的是類的“元數(shù)據(jù)”(metadata)独令,例如類的實(shí)例實(shí)現(xiàn)了幾個(gè)方法端朵,父類是誰(shuí),具備多少實(shí)例變量等信息燃箭。
這里的isa指針指向的是另外一個(gè)類叫做元類(metaClass)冲呢。那什么是元類呢?元類是類對(duì)象的類招狸。也可以換一種容易理解的說法:

  1. 當(dāng)你給對(duì)象發(fā)送消息時(shí)敬拓,runtime處理時(shí)是在這個(gè)對(duì)象的類的方法列表中尋找
  2. 當(dāng)你給類發(fā)消息時(shí),runtime處理時(shí)是在這個(gè)類的元類的方法列表中尋找

我們來看一個(gè)很經(jīng)典的圖來加深理解:


可以總結(jié)如下:

  1. 每一個(gè)Class都有一個(gè)isa指針指向一個(gè)唯一的Meta Class(元類)
  2. 每一個(gè)Meta Classisa指針都指向最上層的Meta Class裙戏,這個(gè)Meta ClassNSObjectMeta Class乘凸。(包括NSObject的Meta Classisa指針也是指向的NSObjectMeta Class)
  3. 每一個(gè)Meta Classsuper class指針指向它原本ClassSuper ClassMeta Class (這里最上層的NSObjectMeta Classsuper class指針還是指向自己)
  4. 最上層的NSObject Classsuper class指向 nil

最后,特別致謝《Effective Objective-C 2.0》第二章

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者

  • 序言:七十年代末累榜,一起剝皮案震驚了整個(gè)濱河市营勤,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌壹罚,老刑警劉巖葛作,帶你破解...
    沈念sama閱讀 206,378評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異猖凛,居然都是意外死亡赂蠢,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,356評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門辨泳,熙熙樓的掌柜王于貴愁眉苦臉地迎上來虱岂,“玉大人,你說我怎么就攤上這事菠红×抗希” “怎么了?”我有些...
    開封第一講書人閱讀 152,702評(píng)論 0 342
  • 文/不壞的土叔 我叫張陵途乃,是天一觀的道長(zhǎng)绍傲。 經(jīng)常有香客問我,道長(zhǎng),這世上最難降的妖魔是什么烫饼? 我笑而不...
    開封第一講書人閱讀 55,259評(píng)論 1 279
  • 正文 為了忘掉前任猎塞,我火速辦了婚禮,結(jié)果婚禮上杠纵,老公的妹妹穿的比我還像新娘荠耽。我一直安慰自己,他們只是感情好比藻,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,263評(píng)論 5 371
  • 文/花漫 我一把揭開白布铝量。 她就那樣靜靜地躺著,像睡著了一般银亲。 火紅的嫁衣襯著肌膚如雪慢叨。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,036評(píng)論 1 285
  • 那天务蝠,我揣著相機(jī)與錄音拍谐,去河邊找鬼。 笑死馏段,一個(gè)胖子當(dāng)著我的面吹牛轩拨,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播院喜,決...
    沈念sama閱讀 38,349評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼亡蓉,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了喷舀?” 一聲冷哼從身側(cè)響起砍濒,我...
    開封第一講書人閱讀 36,979評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎元咙,沒想到半個(gè)月后梯影,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,469評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡庶香,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,938評(píng)論 2 323
  • 正文 我和宋清朗相戀三年甲棍,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片赶掖。...
    茶點(diǎn)故事閱讀 38,059評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡感猛,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出奢赂,到底是詐尸還是另有隱情陪白,我是刑警寧澤,帶...
    沈念sama閱讀 33,703評(píng)論 4 323
  • 正文 年R本政府宣布膳灶,位于F島的核電站咱士,受9級(jí)特大地震影響立由,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜序厉,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,257評(píng)論 3 307
  • 文/蒙蒙 一锐膜、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧弛房,春花似錦道盏、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,262評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至粹排,卻和暖如春种远,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背恨搓。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評(píng)論 1 262
  • 我被黑心中介騙來泰國(guó)打工院促, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留筏养,地道東北人斧抱。 一個(gè)月前我還...
    沈念sama閱讀 45,501評(píng)論 2 354
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像渐溶,于是被迫代替她去往敵國(guó)和親辉浦。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,792評(píng)論 2 345

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