Objective-C和Swift混編的一些經驗

阿里云iOS客戶端2.1.0版本中開始嘗試使用Swift來寫新的業務,磕磕絆絆總算是發布了新版,總結一下開發過程中得到的經驗和踩過的坑吧。

CocoaPods

使用Swift作為主要的開發語言,很難避免引入Swift編寫的庫。2.1.0版本引入了SwiftyJSONCharts這兩個Swift寫的庫,分別用于處理JSON數據和畫監控圖。

蘋果要求使用Swift寫的庫,必須通過動態鏈接庫引入,其實這一點我也是不太理解的,因為靜態庫也是可以依賴動態庫的符號的,不存在導入多個Swift動態庫的問題。允許App使用自帶的動態庫從iOS8才開始支持,因此必須將App支持的iOS版本升到iOS8。阿里云iOS客戶端iOS7的用戶不到4%,所以放棄了對iOS7的支持。

Cocoapods支持將依賴的組件編譯成動態庫,只需要在Podfile頂部加上"use_frameworks!"。開啟這個選項之后,所有以源碼引入的pod都會編譯成動態鏈接庫,而以fake framework引入的pod仍然會編譯到主App里面。動態庫都放在App里面的Frameworks目錄,可以看到Swift相關的動態庫也都拷貝進來了,所以支持Swift會導致包變大。我沒有記下2.0.0版本的大小,導致沒法對比2.1.0放大了多少,這是一個失誤。

[~/Library/Developer/Xcode/Archives/2015-12-17/CloudConsoleApp 15-12-17 下午9.24.xcarchive/Products/Applications/CloudConsoleApp.app/Frameworks]$ tree
.
├── Charts.framework
│   ├── Charts
│   ├── Info.plist
│   └── _CodeSignature
│       └── CodeResources
├── SwiftyJSON.framework
│   ├── Info.plist
│   ├── SwiftyJSON
│   └── _CodeSignature
│       └── CodeResources
├── libswiftContacts.dylib
├── libswiftCore.dylib
├── libswiftCoreData.dylib
├── libswiftCoreGraphics.dylib
├── libswiftCoreImage.dylib
├── libswiftDarwin.dylib
├── libswiftDispatch.dylib
├── libswiftFoundation.dylib
├── libswiftObjectiveC.dylib
└── libswiftUIKit.dylib

27 directories, 49 files

$ file Charts 
Charts: Mach-O universal binary with 2 architectures
Charts (for architecture armv7):    Mach-O dynamically linked shared library arm
Charts (for architecture arm64):    Mach-O 64-bit dynamically linked shared library

因為fake framework和源代碼pod分別會編譯成靜態庫和動態庫,這樣會導致一個問題,就是如果源碼pod又依賴fake framework,那就沒辦法了。CocoaPods發現這種情況會提示下面這個錯誤。

target has transitive dependencies that include static binaries: (xxx.framework, xxx.framework)

pod install時會把靜態庫編譯到App里面,源碼編譯成的動態庫沒法依賴它。最終的解決方案只能是CocoaPods對fake framework和源碼pod一視同仁,都編譯成動態庫,這樣彼此才能依賴。不知道CocoaPods什么時候會支持這樣。

設置use_frameworks導致不能引入源碼pod調試的問題,經過我們的實踐發現可以將源碼pod項目直接拖入項目中,這樣可以調試代碼查問題,非常之實用。

混編

  • Swift使用Objective-C

這種情況占絕大多數。只需要在CloudConsoleApp-Bridging-Header.h這個頭文件中包含相關的頭文件就行。pod組件另外一種引入的方式是通過@import引入。比如SDWebImage可以通過下面兩種方式引入。

//在Bridging頭文件包含下面這個頭文件
#import <SDWebImage/UIImageView+WebCache.h>

//另外一種辦法,在Swift文件中引入。
import SDWebImage

Objective-C寫的類和方法都會被改成Swift的使用方式,下面是兩個很典型的例子。使用的時候需要嘗試一下才能找到翻譯的Swift方法。

//Objective-C
titleLabel.lineBreakMode = NSLineBreakByWordWrapping;
titleLabel.numberOfLines = 0;

//Swift
cell.nameLabel?.lineBreakMode = .ByWordWrapping //全寫是 NSLineBreakMode.ByWordWrapping
cell.nameLabel?.numberOfLines = 0

//Objective-C
UIImage *image = [UIImage imageNamed:@"abc"];

//Swift
let image = UIImage(named: "abc")
  • Objective-C使用Swift

Xcode會生成一個虛擬的頭文件CloudConsoleApp-Swift.h,在工程里面是找不到這個頭文件的,但是可以包含,并且跳轉進去。這個文件里面包含了所有從Swift導出來的符號,比如下面這個view controller就是用Swift寫的。可以看出來Objectivew-C看到的名稱跟Swift源碼里面的名稱是一樣的,但是Swift會對類做demangling,變成了_TtC15CloudConsoleApp31YWSResourceDetailViewController這樣的,跟C++有點類似。

SWIFT_CLASS("_TtC15CloudConsoleApp31YWSResourceDetailViewController")
@interface YWSResourceDetailViewController : UIPageViewController
@property (nonatomic, strong) NSArray * __nonnull vcs;
@property (nonatomic, copy) NSString * __null_unspecified pluginId;
@property (nonatomic, strong) YWSSegmentedControl * __nonnull segmentedControl;
@property (nonatomic, strong) YWSInstanceListViewController * __nonnull instanceListViewController;
@property (nonatomic, strong) YWSMetricConcernedViewController * __nonnull metricConcernedViewController;
@property (nonatomic, weak) IBOutlet UIBarButtonItem * __null_unspecified addMetricBarButton;
- (void)viewDidLoad;
- (void)initSegmentedControl;
- (void)indexChanged:(id __nonnull)sender;
- (void)onNavigationBack:(id __nonnull)sender;
- (void)prepareForSegue:(UIStoryboardSegue * __nonnull)segue sender:(id __nullable)sender;
- (nonnull instancetype)initWithTransitionStyle:(UIPageViewControllerTransitionStyle)style navigationOrientation:(UIPageViewControllerNavigationOrientation)navigationOrientation options:(NSDictionary<NSString *, id> * __nullable)options OBJC_DESIGNATED_INITIALIZER;
- (nullable instancetype)initWithCoder:(NSCoder * __nonnull)coder OBJC_DESIGNATED_INITIALIZER;
@end

Swift的優缺點

這個項目剛起步,用Swift的經驗尚淺,所以都是一些比較淺薄的理解,后面有更深刻的理解再補上。

優點
  • 代碼簡潔。類的聲明和實現在一個文件中。
  • 統一對屬性和方法的調用,都用.
  • 如果不加額外的訪問控制,所有的符號都是整個項目可見,無需考慮頭文件的問題。
  • 基礎類型如Int、Bool都是結構體,存取到NSUserDefaults、Array、Dictionary不需要裝箱和拆箱,使用更加方便。
  • 字符串處理太方便了。
//字符串比較和拼接實在是太方便了
let foo = "abc"
let bar = "abc"

if foo == bar {
    //blablabla
}

print("====\(foo)+\(bar)")
  • 語言上支持延遲加載。
lazy var imageView : UIImageView = {
    var imageView = UIImageView(image: UIImage(named: "empty_hint"))
    imageView.contentMode = .ScaleAspectFit

    return imageView
}()

lazy var infoLabel : UILabel = {
    var infoLabel = UILabel()
    infoLabel.lineBreakMode = .ByWordWrapping //支持換行
    infoLabel.numberOfLines = 0
    
    return infoLabel
}()

lazy var button : UIButton = {
    var button = UIButton()
    button.titleLabel?.font = UIFont.systemFontOfSize(15)
    button.setTitleColor(UIColor.darkGrayColor(), forState: .Normal)
    button.setBackgroundImage(UIImage(named: "buy_instance_hint_button"), forState: .Normal)
    button.hidden = true
    
    return button
}()
  • 多返回值。比如下面這個函數,如果使用Objective-C寫還是比較麻煩的。
//將 "創建中&#FA8C35" 翻譯成對應的 "(字符串對象, 顏色對象)"
func YWSTranslateRichText (str : String) -> (text : String, color : UIColor) {
    let statusArray = str.componentsSeparatedByString("&")
    
    if statusArray.count == 0 {
        return ("", UIColor.lightGrayColor())
    }
    
    if statusArray.count == 1 {
        return (statusArray[0], UIColor.lightGrayColor())
    }
    
    return (statusArray[0], UIColor.fromHexString(statusArray[1]))
}

//使用方式如下
let (text, color) = YWSTranslateRichText(instanceStatusConf)
  • 支持字符串作為枚舉值。
enum YWSECSInstanceStatus : String {
    case Starting = "Starting"
    case Running = "Running"
    case Stopping = "Stopping"
    case Stopped = "Stopped"
}

//使用方法
cell.ECSInstanceStatus = YWSECSInstanceStatus(rawValue: instanceStatus!)

//轉換成字符串
textDetailLabel.text = YWSECSInstanceStatus.Starting.rawValue
  • Selector 類型實現了 StringLiteralConvertible,使用起來更加方便。這種用法很方便,但是沒有任何保證,指定一個錯誤的方法名,編譯的時候也不會報錯,所以Swift 2.2引入了 #selector,跟OC就差不多了。
//2.2版本之前
self.button.addTarget(self, action: "introduceResources:", forControlEvents: .TouchUpInside)

//2.2版本之后
self.button.addTarget(self, action: #selector(XXXViewController.introduceResources(_:)), forControlEvents: .TouchUpInside)
  • 不再需要引入libextobjc這個Pod,因為Swift支持更方便的用法。在block開始的時候,在數組里面weak所有要用到的對象。
inputViewController.finishBlock = { [weak inputViewController, weak cell, weak self] () -> Void in
}
  • 函數支持默認參數。比如下面這個函數,有五個參數,其中三個有默認參數,用戶需要設置的參數只有兩個。
convenience init(text: String, textColor: UIColor = UIColor.whiteColor(), bgColor: UIColor, font: UIFont = UIFont.systemFontOfSize(10), inset: UIEdgeInsets = UIEdgeInsetsMake(0, 3, 0, 3)) {
    //blabla
}

lazy var vipLabel : YWSInsetsTextLabel! = YWSInsetsTextLabel(text: "vip", bgColor: UIColor.orangeColor())
  • 通過減少動態性,使用vtable替換原有的objc_msgSend,獲取更高的性能。新增了final、private等關鍵字,編譯器可以對代碼做更多優化,提升性能并減少內存的使用。比如final方法不用放入虛表中,節省內存;跳轉時不用查表,性能更佳;private的類如果發現有方法只在本文件中使用,可以直接內聯,提高性能。
  • 在Playground工程里面練習Swift編程非常之方便,尤其是測試VFL語句的時候。
Snip20151231_1
  • 支持運算符重載,對于解決某些問題真的是太方便了,比如對比兩個HomeBannerVo數組是否相等,重載==運算符之后,直接比較即可。
final class HomeBannerVo : Mappable, Equatable {
    // 圖片URL
    var url : String?

    // banner名稱
    var name : String?

    // 點擊后跳轉URL
    var target : String?

    required init?(_ map: Map) {
    }

    func mapping(map: Map) {
        url <- map["cover"]
        name <- map["name"]
        target <- map["target"]
    }
}

func ==(lhs: HomeBannerVo, rhs: HomeBannerVo)->Bool {
    if lhs.url != rhs.url || lhs.name != rhs.name || lhs.target != rhs.target {
        return false
    }

    return true
}
缺點
  • Optional讓人頭疼,大量的?!,沒處理好很容易導致崩潰。
  • 強類型和Optional,給JSON解釋帶來了災難。
  • 目前Xcode不支持對Swift寫的代碼做重構。
  • Build Settings里面設置Treat Warnings as Errors對Swift代碼無效。
  • Swift不支持宏,OC里面比較常用的宏,比如下面這個UIColorFromRGB就沒法用了。
#define UIColorFromRGB(rgbValue) [UIColor colorWithRed:((float)((rgbValue & 0xFF0000) >> 16))/255.0 green:((float)((rgbValue & 0xFF00) >> 8))/255.0 blue:((float)(rgbValue & 0xFF))/255.0 alpha:1.0]
  • 不支持與C++混編,必須通過OC包一下C++的接口,Swift才能使用。使用一些跨端的C++庫(OpenGL、全文搜索、網絡底層等)比較麻煩。
  • Swift基于vtable的調用方式,讓iOS目前通行的各種hotpatch方法都失效了,比如Wax和JSPatch。想了解關于Swift runtime的更多信息,請參看:Swift Runtime分析:還像OC Runtime一樣嗎?。除非用dynamic修飾所有Swift方法,否則沒法替換方法。而NSInvocation的機制就更加無從談起了。
  • 用private修飾的類,如果使用KVC來給屬性設置值,編譯不會報錯,運行時也不會報錯,但就是設置不上。去掉private就好了。
  • Swift和OC混著寫的時候,有時候會出現OC的類在CloudConsoleApp-Bridging-Header.h里面提供給Swift使用,但是這個類又需要引入CloudConsoleApp-Swift.h使用Swift的一些功能,這樣就循環包含了,沒法玩下去了。

crash分析

手解crash可以看到具體崩潰代碼的行號。

$ symbolicatecrash ~/Downloads/034dc058c5d4ff1f717ec7a05d4d55b8 CloudConsoleApp.app.dSYM

Exception Type:  SIGTRAP
Exception Codes: #0 at 0x1001c09b4
Crashed Thread:  0

Thread 0 Crashed:
0   CloudConsoleApp                     0x00000001001c09b4 YWSInstanceListViewController.goToBuyPage() -> () (YWSInstanceListViewController.swift:701)
1   CloudConsoleApp                     0x00000001001c92cc specialized YWSInstanceListViewController.introduceResources(AnyObject) -> () (YWSInstanceListViewController.swift:689)
2   CloudConsoleApp                     0x00000001001c029c @objc YWSInstanceListViewController.introduceResources(AnyObject) -> () (YWSInstanceListViewController.swift:0)
3   UIKit                               0x000000018601be50 0x185fd0000 + 310864
4   UIKit                               0x000000018601bdcc 0x185fd0000 + 310732
5   UIKit                               0x0000000186003a88 0x185fd0000 + 211592
6   UIKit                               0x000000018601b6e4 0x185fd0000 + 308964
7   UIKit                               0x000000018601b314 0x185fd0000 + 307988
8   UIKit                               0x0000000186013e30 0x185fd0000 + 278064
9   UIKit                               0x0000000185fe44cc 0x185fd0000 + 83148
10  UIKit                               0x0000000185fe2794 0x185fd0000 + 75668
11  CoreFoundation                      0x00000001812a8efc 0x1811cc000 + 904956
12  CoreFoundation                      0x00000001812a8990 0x1811cc000 + 903568
13  CoreFoundation                      0x00000001812a6690 0x1811cc000 + 894608
14  CoreFoundation                      0x00000001811d5680 0x1811cc000 + 38528
15  GraphicsServices                    0x00000001826e4088 0x1826d8000 + 49288
16  UIKit                               0x000000018604cd90 0x185fd0000 + 511376
17  CloudConsoleApp                     0x000000010014b4e0 main (main.m:16)
18  libdyld.dylib                       0x0000000180d768b8 0x180d74000 + 10424

//不過我對著這行代碼分析了好久,實在想不出來崩潰的原因。沒有任何crash提示信息。
//這個版本Swift代碼只有這樣一個crash
//后面再看看新crash會不會也是這樣
self.resourceType = YWSXXX.shareInstance().getXXXByXXX(self.XXX.pluginId)

實際證明Swift的crash信息非常不準確,能知道崩潰的文件和函數,行號不準確,也不會輸出Application Specific Information。比如下面這個crash。

Incident Identifier: 54087A46-D37D-454B-9305-22ED5420B58B
CrashReporter Key:   TODO
Hardware Model:      iPhone6,2
Process:             CloudConsoleApp [696]
Path:                /var/mobile/Containers/Bundle/Application/E8E24C8B-A47B-425E-863F-A871F273FCA2/CloudConsoleApp.app/CloudConsoleApp
Identifier:          com.aliyun.wstudio.amc.AliyunMobileApp
Version:             2.2.0 (2169)
Code Type:           ARM-64
Parent Process:      ??? [1]

Date/Time:           2016-01-25 10:44:56 +0000
OS Version:          iPhone OS 9.2.1 (13D15)
Report Version:      104

Exception Type:  SIGTRAP
Exception Codes: #0 at 0x100139e80
Triggered by Thread:  0

Thread 0 Crashed:
0   CloudConsoleApp                 0x0000000100139e80 __TFC15CloudConsoleApp24YWSTouchIDViewController14viewWillAppearfS0_FSbT_ (in CloudConsoleApp) + 1304
1   CloudConsoleApp                 0x0000000100139eb0 __TToFC15CloudConsoleApp24YWSTouchIDViewController14viewWillAppearfS0_FSbT_ (in CloudConsoleApp) + 44
2   UIKit                           0x000000018722c74c 0x0000000187200000 + 182092
3   UIKit                           0x000000018722c4c0 0x0000000187200000 + 181440
4   UIKit                           0x00000001872d3130 0x0000000187200000 + 864560
5   UIKit                           0x00000001872d2a6c 0x0000000187200000 + 862828
6   UIKit                           0x00000001872d2694 0x0000000187200000 + 861844
7   UIKit                           0x00000001872d25fc 0x0000000187200000 + 861692
8   UIKit                           0x000000018720f778 0x0000000187200000 + 63352
9   QuartzCore                      0x0000000184c1eb2c 0x0000000184c10000 + 60204
10  QuartzCore                      0x0000000184c19738 0x0000000184c10000 + 38712
11  QuartzCore                      0x0000000184c195f8 0x0000000184c10000 + 38392
12  QuartzCore                      0x0000000184c18c94 0x0000000184c10000 + 35988
13  QuartzCore                      0x0000000184c189dc 0x0000000184c10000 + 35292
14  QuartzCore                      0x0000000184c120cc 0x0000000184c10000 + 8396
15  CoreFoundation                  0x00000001824d8588 0x00000001823fc000 + 902536
16  CoreFoundation                  0x00000001824d632c 0x00000001823fc000 + 893740
17  CoreFoundation                  0x00000001824d675c 0x00000001823fc000 + 894812
18  CoreFoundation                  0x0000000182405680 0x00000001823fc000 + 38528
19  GraphicsServices                0x0000000183914088 0x0000000183908000 + 49288
20  UIKit                           0x000000018727cd90 0x0000000187200000 + 511376
21  CloudConsoleApp                 0x000000010007f988 main (in CloudConsoleApp) (main.m:16)
22  libdyld.dylib                   0x0000000181fa68b8 0x0000000181fa4000 + 10424

//確實崩潰在viewWillAppear函數中,但是是97行的 as! 導致的,在crash信息里面這兩個重要的信息沒有暴露出來。
override func viewWillAppear(animated: Bool) {
    super.viewWillAppear(animated)

    //let urlString = YWSXXX.sharedInstance().get("userIcon") as! String

    //因為2.1.0重構過登錄模塊,新的登錄模塊才會保存 userIcon 這個值
    //所以2.2.0覆蓋2.1.0之前的版本用 as! 會崩潰,這里改成 as? 了
    if let urlString = YWSXXX.sharedInstance().get("userIcon") as? String {
    }
}

總結

Swift 2.1版本已經非常穩定,蘋果將其開源,也表明對Swift的質量和可靠性有足夠的信心。開源社區開始涌現一批優秀的Swift庫,比如Charts,這個畫圖的組件很不錯。StackOverflow的答案中很多人會同時提供Objective-C和Swift兩個版本。目前來看唯一美中不足的問題就是解出來的crash沒有Objective-C那么直觀了,很多時候都得靠猜。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,119評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,382評論 3 415
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,038評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,853評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,616評論 6 408
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,112評論 1 323
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,192評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,355評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,869評論 1 334
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,727評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,928評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,467評論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,165評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,570評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,813評論 1 282
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,585評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,892評論 2 372

推薦閱讀更多精彩內容

  • 發現 關注 消息 iOS 第三方庫、插件、知名博客總結 作者大灰狼的小綿羊哥哥關注 2017.06.26 09:4...
    肇東周閱讀 12,151評論 4 61
  • 首先給大家傳授一個懶人減肥大法,不管多懶的人都可以用。 那就是不管走路坐著還是站立的時候,肚子始終是繃著勁兒的,始...
    lily小莉啊閱讀 623評論 0 0
  • 兒子昨晚不知道游戲打到幾點鐘,又發生激烈的情緒對戰,既然我說這么多一點作用也沒有,反而招致他的厭煩。 今晚上也只管...
    吳若閱讀 145評論 0 0
  • 《黃昏》節選 風帶著夕陽的宣言走了。 像忽然熔化了似的, 海的無數跳躍著的金眼睛 攤平為暗綠的大面孔。 遠處有悲壯...
    fionahappy閱讀 3,094評論 0 1
  • 不久前熱播的《煎餅俠》中,古惑仔們重聚銀幕,四兄弟帥氣出場的時候,感覺整個影院都沸騰起來了啊! 現在的小男孩們,心...
    云上de耳朵哥閱讀 1,437評論 1 2