├── QFMoudle ├── Assets.xcassets │ ├── Contents.json │ ├── me.imageset │ │ ├── me@2x.png │ │ ├── me@3x.png │ │ └── Contents.json │ ├── home.imageset │ │ ├── home@2x.png │ │ ├── home@3x.png │ │ └── Contents.json │ └── AppIcon.appiconset │ │ └── Contents.json ├── ViewController.h ├── QFHome │ ├── Module │ │ ├── MoudleHome.h │ │ └── MoudleHome.m │ └── Controller │ │ ├── QFDetailViewController.h │ │ ├── QFHomeViewController.h │ │ ├── QFDetailViewController.m │ │ └── QFHomeViewController.m ├── QFMe │ ├── Moudle │ │ ├── MoudleMe.h │ │ └── MoudleMe.m │ └── Controller │ │ ├── QFMeViewController.h │ │ └── QFMeViewController.m ├── AppDelegate.h ├── main.m ├── ViewController.m ├── QFRouter │ ├── QFRouter.h │ └── QFRouter.m ├── QFProtocol │ └── QFMoudleProtocol.h ├── Info.plist ├── Base.lproj │ ├── Main.storyboard │ └── LaunchScreen.storyboard └── AppDelegate.m ├── QFMoudle.xcodeproj ├── xcuserdata │ └── liyiping.xcuserdatad │ │ ├── xcdebugger │ │ └── Breakpoints_v2.xcbkptlist │ │ └── xcschemes │ │ └── xcschememanagement.plist ├── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist └── project.pbxproj ├── QFMoudleTests ├── Info.plist └── QFMoudleTests.m ├── QFMoudleUITests ├── Info.plist └── QFMoudleUITests.m └── README.md /QFMoudle/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /QFMoudle/Assets.xcassets/me.imageset/me@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qingfengiOS/QFMoudle/HEAD/QFMoudle/Assets.xcassets/me.imageset/me@2x.png -------------------------------------------------------------------------------- /QFMoudle/Assets.xcassets/me.imageset/me@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qingfengiOS/QFMoudle/HEAD/QFMoudle/Assets.xcassets/me.imageset/me@3x.png -------------------------------------------------------------------------------- /QFMoudle/Assets.xcassets/home.imageset/home@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qingfengiOS/QFMoudle/HEAD/QFMoudle/Assets.xcassets/home.imageset/home@2x.png -------------------------------------------------------------------------------- /QFMoudle/Assets.xcassets/home.imageset/home@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qingfengiOS/QFMoudle/HEAD/QFMoudle/Assets.xcassets/home.imageset/home@3x.png -------------------------------------------------------------------------------- /QFMoudle.xcodeproj/xcuserdata/liyiping.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | -------------------------------------------------------------------------------- /QFMoudle.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /QFMoudle/ViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.h 3 | // QFMoudle 4 | // 5 | // Created by 情风 on 2018/11/5. 6 | // Copyright © 2018年 qingfengiOS. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface ViewController : UIViewController 12 | 13 | 14 | @end 15 | 16 | -------------------------------------------------------------------------------- /QFMoudle.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /QFMoudle/QFHome/Module/MoudleHome.h: -------------------------------------------------------------------------------- 1 | // 2 | // MoudleHome.h 3 | // QFMoudle 4 | // 5 | // Created by 情风 on 2018/11/5. 6 | // Copyright © 2018年 qingfengiOS. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "QFMoudleProtocol.h" 11 | 12 | @interface MoudleHome : NSObject 13 | 14 | @end 15 | -------------------------------------------------------------------------------- /QFMoudle/QFMe/Moudle/MoudleMe.h: -------------------------------------------------------------------------------- 1 | // 2 | // MoudleMe.h 3 | // QFMoudle 4 | // 5 | // Created by 情风 on 2018/11/5. 6 | // Copyright © 2018年 qingfengiOS. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "QFMoudleProtocol.h" 11 | 12 | @interface MoudleMe : NSObject 13 | 14 | 15 | 16 | @end 17 | -------------------------------------------------------------------------------- /QFMoudle/AppDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.h 3 | // QFMoudle 4 | // 5 | // Created by 情风 on 2018/11/5. 6 | // Copyright © 2018年 qingfengiOS. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface AppDelegate : UIResponder 12 | 13 | @property (strong, nonatomic) UIWindow *window; 14 | 15 | 16 | @end 17 | 18 | -------------------------------------------------------------------------------- /QFMoudle/QFMe/Controller/QFMeViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // QFMeViewController.h 3 | // QFMoudle 4 | // 5 | // Created by 情风 on 2018/11/5. 6 | // Copyright © 2018年 qingfengiOS. All rights reserved. 7 | // 8 | 9 | #import 10 | @class MoudleMe; 11 | 12 | @interface QFMeViewController : UIViewController 13 | 14 | /// 外部接口 15 | @property (nonatomic, strong) MoudleMe *interface; 16 | 17 | @end 18 | -------------------------------------------------------------------------------- /QFMoudle/main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // QFMoudle 4 | // 5 | // Created by 情风 on 2018/11/5. 6 | // Copyright © 2018年 qingfengiOS. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "AppDelegate.h" 11 | 12 | int main(int argc, char * argv[]) { 13 | @autoreleasepool { 14 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /QFMoudle/QFHome/Controller/QFDetailViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // QFDetailViewController.h 3 | // QFMoudle 4 | // 5 | // Created by 情风 on 2018/11/5. 6 | // Copyright © 2018年 qingfengiOS. All rights reserved. 7 | // 8 | 9 | #import 10 | @class MoudleHome; 11 | 12 | @interface QFDetailViewController : UIViewController 13 | /// 外部接口 14 | @property(nonatomic, strong) MoudleHome *interface; 15 | 16 | @end 17 | -------------------------------------------------------------------------------- /QFMoudle/QFHome/Controller/QFHomeViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // QFHomeViewController.h 3 | // QFMoudle 4 | // 5 | // Created by 情风 on 2018/11/5. 6 | // Copyright © 2018年 qingfengiOS. All rights reserved. 7 | // 8 | 9 | #import 10 | @class MoudleHome; 11 | 12 | @interface QFHomeViewController : UIViewController 13 | 14 | /// 外部接口 15 | @property(nonatomic, strong) MoudleHome *interface; 16 | 17 | @end 18 | -------------------------------------------------------------------------------- /QFMoudle.xcodeproj/xcuserdata/liyiping.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | QFMoudle.xcscheme 8 | 9 | orderHint 10 | 0 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /QFMoudle/Assets.xcassets/me.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "me@2x.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "filename" : "me@3x.png", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /QFMoudle/Assets.xcassets/home.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "home@2x.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "filename" : "home@3x.png", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /QFMoudle/ViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.m 3 | // QFMoudle 4 | // 5 | // Created by 情风 on 2018/11/5. 6 | // Copyright © 2018年 qingfengiOS. All rights reserved. 7 | // 8 | 9 | #import "ViewController.h" 10 | 11 | @interface ViewController () 12 | 13 | @end 14 | 15 | @implementation ViewController 16 | 17 | - (void)viewDidLoad { 18 | [super viewDidLoad]; 19 | // Do any additional setup after loading the view, typically from a nib. 20 | } 21 | 22 | 23 | - (void)didReceiveMemoryWarning { 24 | [super didReceiveMemoryWarning]; 25 | // Dispose of any resources that can be recreated. 26 | } 27 | 28 | 29 | @end 30 | -------------------------------------------------------------------------------- /QFMoudle/QFMe/Moudle/MoudleMe.m: -------------------------------------------------------------------------------- 1 | // 2 | // MoudleMe.m 3 | // QFMoudle 4 | // 5 | // Created by 情风 on 2018/11/5. 6 | // Copyright © 2018年 qingfengiOS. All rights reserved. 7 | // 8 | 9 | #import "MoudleMe.h" 10 | #import "QFMeViewController.h" 11 | 12 | @implementation MoudleMe 13 | 14 | @synthesize callbackBlock; 15 | 16 | @synthesize interfaceViewController; 17 | 18 | @synthesize paramterForMe; 19 | 20 | 21 | - (UIViewController *)interfaceViewController { 22 | 23 | QFMeViewController *interfaceViewController = [[QFMeViewController alloc]init]; 24 | interfaceViewController.interface = self; 25 | 26 | return (UIViewController *)interfaceViewController; 27 | } 28 | 29 | @end 30 | -------------------------------------------------------------------------------- /QFMoudleTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /QFMoudleUITests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /QFMoudle/QFRouter/QFRouter.h: -------------------------------------------------------------------------------- 1 | // 2 | // QFRouter.h 3 | // QFMoudle 4 | // 5 | // Created by 情风 on 2018/11/5. 6 | // Copyright © 2018年 qingfengiOS. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface QFRouter : NSObject 12 | 13 | /** 14 | 单例路由 15 | 16 | @return 路由实例 17 | */ 18 | + (instancetype)router; 19 | 20 | 21 | /** 22 | 通过协议获取对应的Module 23 | 24 | @param protocol 协议 25 | @return 对应的组件实例(比如ModuleA,ModuleB...) 26 | */ 27 | - (id)interfaceForProtocol:(Protocol *)protocol; 28 | 29 | 30 | /** 31 | 通过url获取对应的Module 32 | 33 | @param url 目标url 34 | [NSURL URLWithString:@"ModuleA://?paramterA=testA"] 35 | 其中: 36 | ModuleA表示组件名 37 | paramterA=testA为对应参数 38 | 39 | @return 对应的组件实例(比如ModuleA,ModuleB...) 40 | */ 41 | - (id)interfaceForURL:(NSURL *)url; 42 | @end 43 | -------------------------------------------------------------------------------- /QFMoudleTests/QFMoudleTests.m: -------------------------------------------------------------------------------- 1 | // 2 | // QFMoudleTests.m 3 | // QFMoudleTests 4 | // 5 | // Created by 情风 on 2018/11/5. 6 | // Copyright © 2018年 qingfengiOS. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface QFMoudleTests : XCTestCase 12 | 13 | @end 14 | 15 | @implementation QFMoudleTests 16 | 17 | - (void)setUp { 18 | [super setUp]; 19 | // Put setup code here. This method is called before the invocation of each test method in the class. 20 | } 21 | 22 | - (void)tearDown { 23 | // Put teardown code here. This method is called after the invocation of each test method in the class. 24 | [super tearDown]; 25 | } 26 | 27 | - (void)testExample { 28 | // This is an example of a functional test case. 29 | // Use XCTAssert and related functions to verify your tests produce the correct results. 30 | } 31 | 32 | - (void)testPerformanceExample { 33 | // This is an example of a performance test case. 34 | [self measureBlock:^{ 35 | // Put the code you want to measure the time of here. 36 | }]; 37 | } 38 | 39 | @end 40 | -------------------------------------------------------------------------------- /QFMoudle/QFHome/Module/MoudleHome.m: -------------------------------------------------------------------------------- 1 | // 2 | // MoudleHome.m 3 | // QFMoudle 4 | // 5 | // Created by 情风 on 2018/11/5. 6 | // Copyright © 2018年 qingfengiOS. All rights reserved. 7 | // 8 | 9 | #import "MoudleHome.h" 10 | #import "QFHomeViewController.h" 11 | #import "QFDetailViewController.h" 12 | @implementation MoudleHome 13 | 14 | @synthesize callbackBlock; 15 | 16 | @synthesize detailViewController; 17 | 18 | @synthesize interfaceViewController; 19 | 20 | @synthesize paramterForHome; 21 | 22 | @synthesize titleString; 23 | 24 | @synthesize descString; 25 | 26 | - (UIViewController *)interfaceViewController { 27 | QFHomeViewController *homeViewController = [[QFHomeViewController alloc]init]; 28 | homeViewController.interface = self; 29 | interfaceViewController = (UIViewController *)homeViewController; 30 | 31 | return interfaceViewController; 32 | } 33 | 34 | - (QFDetailViewController *)detailViewController { 35 | QFDetailViewController *detailViewController = [[QFDetailViewController alloc]init]; 36 | detailViewController.interface = self; 37 | return detailViewController; 38 | } 39 | 40 | 41 | @end 42 | -------------------------------------------------------------------------------- /QFMoudle/QFProtocol/QFMoudleProtocol.h: -------------------------------------------------------------------------------- 1 | // 2 | // QFMoudleProtocol.h 3 | // QFMoudle 4 | // 5 | // Created by 情风 on 2018/11/5. 6 | // Copyright © 2018年 qingfengiOS. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | typedef void(^QFCallBackBlock)(id parameter); 12 | 13 | #pragma mark - 基础协议 14 | @protocol QFMoudleProtocol 15 | 16 | /// 暴露给组件外部的控制器,一般为该组件的主控制器 17 | @property (nonatomic, weak) UIViewController *interfaceViewController; 18 | 19 | /// 回调 20 | @property (nonatomic, copy) QFCallBackBlock callbackBlock; 21 | 22 | @end 23 | 24 | #pragma mark - ”首页“组件 25 | @protocol MoudleHome 26 | 27 | /// 组件“Home”首页所需要的参数 28 | @property (nonatomic, copy) NSString *paramterForHome; 29 | 30 | /// 组件“Home”中详情页面所需要的参数 31 | @property (nonatomic, copy) NSString *titleString; 32 | 33 | /// 组件“Home”中详情页面所需要的参数 34 | @property (nonatomic, copy) NSString *descString; 35 | 36 | /// 组件“Home”所需要暴露的特殊接口,比如其他组件也要跳转到该页面 37 | @property (nonatomic, weak) UIViewController *detailViewController; 38 | 39 | @end 40 | 41 | #pragma mark - “我的”组件 42 | @protocol MoudleMe 43 | 44 | /// 组件“Me”所需要的参数 45 | @property (nonatomic, copy) NSString *paramterForMe; 46 | 47 | @end 48 | -------------------------------------------------------------------------------- /QFMoudleUITests/QFMoudleUITests.m: -------------------------------------------------------------------------------- 1 | // 2 | // QFMoudleUITests.m 3 | // QFMoudleUITests 4 | // 5 | // Created by 情风 on 2018/11/5. 6 | // Copyright © 2018年 qingfengiOS. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface QFMoudleUITests : XCTestCase 12 | 13 | @end 14 | 15 | @implementation QFMoudleUITests 16 | 17 | - (void)setUp { 18 | [super setUp]; 19 | 20 | // Put setup code here. This method is called before the invocation of each test method in the class. 21 | 22 | // In UI tests it is usually best to stop immediately when a failure occurs. 23 | self.continueAfterFailure = NO; 24 | // UI tests must launch the application that they test. Doing this in setup will make sure it happens for each test method. 25 | [[[XCUIApplication alloc] init] launch]; 26 | 27 | // In UI tests it’s important to set the initial state - such as interface orientation - required for your tests before they run. The setUp method is a good place to do this. 28 | } 29 | 30 | - (void)tearDown { 31 | // Put teardown code here. This method is called after the invocation of each test method in the class. 32 | [super tearDown]; 33 | } 34 | 35 | - (void)testExample { 36 | // Use recording to get started writing UI tests. 37 | // Use XCTAssert and related functions to verify your tests produce the correct results. 38 | } 39 | 40 | @end 41 | -------------------------------------------------------------------------------- /QFMoudle/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | LSRequiresIPhoneOS 22 | 23 | UILaunchStoryboardName 24 | LaunchScreen 25 | UIMainStoryboardFile 26 | Main 27 | UIRequiredDeviceCapabilities 28 | 29 | armv7 30 | 31 | UISupportedInterfaceOrientations 32 | 33 | UIInterfaceOrientationPortrait 34 | UIInterfaceOrientationLandscapeLeft 35 | UIInterfaceOrientationLandscapeRight 36 | 37 | UISupportedInterfaceOrientations~ipad 38 | 39 | UIInterfaceOrientationPortrait 40 | UIInterfaceOrientationPortraitUpsideDown 41 | UIInterfaceOrientationLandscapeLeft 42 | UIInterfaceOrientationLandscapeRight 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /QFMoudle/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /QFMoudle/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /QFMoudle/QFHome/Controller/QFDetailViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // QFDetailViewController.m 3 | // QFMoudle 4 | // 5 | // Created by 情风 on 2018/11/5. 6 | // Copyright © 2018年 qingfengiOS. All rights reserved. 7 | // 8 | 9 | #import "QFDetailViewController.h" 10 | #import "MoudleHome.h" 11 | 12 | @interface QFDetailViewController () 13 | /// 描述Label 14 | @property (nonatomic, strong) UILabel *descLabel; 15 | @end 16 | 17 | @implementation QFDetailViewController 18 | 19 | #pragma mark - Life Cycle 20 | - (void)viewDidLoad { 21 | [super viewDidLoad]; 22 | [self initAppreaence]; 23 | } 24 | 25 | - (void)dealloc { 26 | NSLog(@"%@ dealloc resumed",NSStringFromClass([self class])); 27 | } 28 | 29 | #pragma mark - InitAppreaence 30 | - (void)initAppreaence { 31 | self.navigationItem.title = self.interface.titleString; 32 | self.view.backgroundColor = [UIColor whiteColor]; 33 | 34 | self.navigationItem.leftBarButtonItem = [[UIBarButtonItem alloc]initWithTitle:@"back" style:UIBarButtonItemStylePlain target:self action:@selector(backAction)]; 35 | 36 | [self.view addSubview:self.descLabel]; 37 | self.descLabel.text = self.interface.descString; 38 | } 39 | 40 | #pragma mark - Event Response 41 | - (void)backAction { 42 | if (self.interface.callbackBlock) { 43 | self.interface.callbackBlock(@"callBack paramter"); 44 | } 45 | [self.navigationController popViewControllerAnimated:YES]; 46 | } 47 | 48 | #pragma mark - Getters 49 | - (UILabel *)descLabel { 50 | if (!_descLabel) { 51 | _descLabel = [[UILabel alloc]init]; 52 | _descLabel.bounds = CGRectMake(0, 0, 200, 100); 53 | _descLabel.center = self.view.center; 54 | _descLabel.numberOfLines = 0; 55 | _descLabel.textAlignment = NSTextAlignmentCenter; 56 | } 57 | return _descLabel; 58 | } 59 | 60 | @end 61 | -------------------------------------------------------------------------------- /QFMoudle/QFMe/Controller/QFMeViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // QFMeViewController.m 3 | // QFMoudle 4 | // 5 | // Created by 情风 on 2018/11/5. 6 | // Copyright © 2018年 qingfengiOS. All rights reserved. 7 | // 8 | 9 | #import "QFMeViewController.h" 10 | #import "QFRouter.h" 11 | #import "QFMoudleProtocol.h" 12 | 13 | @interface QFMeViewController () 14 | @property (nonatomic, strong) UIButton *jumpButton; 15 | @end 16 | 17 | @implementation QFMeViewController 18 | 19 | - (void)viewDidLoad { 20 | [super viewDidLoad]; 21 | [self initAppreaence]; 22 | } 23 | 24 | #pragma mark - InitAppreaence 25 | - (void)initAppreaence { 26 | self.navigationItem.title = @"个人"; 27 | self.view.backgroundColor = [UIColor orangeColor]; 28 | [self.view addSubview:self.jumpButton]; 29 | } 30 | 31 | #pragma mark - Event Response 32 | - (void)buttonAction { 33 | id homeMoudle = [[QFRouter router]interfaceForProtocol:@protocol(MoudleHome)]; 34 | homeMoudle.titleString = @"ModleMe"; 35 | homeMoudle.descString = @"pushed form MeMoudle"; 36 | UIViewController *viewController = homeMoudle.detailViewController; 37 | homeMoudle.callbackBlock = ^(id parameter) { 38 | NSLog(@"return paramter = %@",parameter); 39 | }; 40 | [self.navigationController pushViewController:viewController animated:YES]; 41 | 42 | } 43 | 44 | #pragma mark - Getters 45 | - (UIButton *)jumpButton { 46 | if (!_jumpButton) { 47 | _jumpButton = [UIButton buttonWithType:UIButtonTypeCustom]; 48 | [_jumpButton setTitle:@"jump detail" forState:UIControlStateNormal]; 49 | _jumpButton.bounds = CGRectMake(0, 0, 100, 50); 50 | _jumpButton.center = self.view.center; 51 | _jumpButton.backgroundColor = [UIColor grayColor]; 52 | [_jumpButton addTarget:self action:@selector(buttonAction) forControlEvents:UIControlEventTouchUpInside]; 53 | } 54 | return _jumpButton; 55 | } 56 | @end 57 | 58 | 59 | -------------------------------------------------------------------------------- /QFMoudle/QFHome/Controller/QFHomeViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // QFHomeViewController.m 3 | // QFMoudle 4 | // 5 | // Created by 情风 on 2018/11/5. 6 | // Copyright © 2018年 qingfengiOS. All rights reserved. 7 | // 8 | 9 | #import "QFHomeViewController.h" 10 | 11 | #import "QFRouter.h" 12 | #import "QFMoudleProtocol.h" 13 | 14 | @interface QFHomeViewController () 15 | @property (nonatomic, strong) UIButton *jumpButton; 16 | @end 17 | 18 | @implementation QFHomeViewController 19 | 20 | - (void)viewDidLoad { 21 | [super viewDidLoad]; 22 | [self initAppreaence]; 23 | } 24 | 25 | #pragma mark - InitAppreaence 26 | - (void)initAppreaence { 27 | self.navigationItem.title = @"首页"; 28 | self.view.backgroundColor = [UIColor greenColor]; 29 | [self.view addSubview:self.jumpButton]; 30 | } 31 | 32 | #pragma mark - Event Response 33 | - (void)buttonAction { 34 | id homeMoudle = [[QFRouter router]interfaceForProtocol:@protocol(MoudleHome)]; 35 | homeMoudle.titleString = @"ModleHome"; 36 | homeMoudle.descString = @"pushed form HomeMoudle"; 37 | UIViewController *viewController = homeMoudle.detailViewController; 38 | homeMoudle.callbackBlock = ^(id parameter) { 39 | NSLog(@"return paramter = %@",parameter); 40 | }; 41 | [self.navigationController pushViewController:viewController animated:YES]; 42 | } 43 | 44 | #pragma mark - Getters 45 | - (UIButton *)jumpButton { 46 | if (!_jumpButton) { 47 | _jumpButton = [UIButton buttonWithType:UIButtonTypeCustom]; 48 | [_jumpButton setTitle:@"jump detail" forState:UIControlStateNormal]; 49 | _jumpButton.bounds = CGRectMake(0, 0, 100, 50); 50 | _jumpButton.center = self.view.center; 51 | _jumpButton.backgroundColor = [UIColor redColor]; 52 | [_jumpButton addTarget:self action:@selector(buttonAction) forControlEvents:UIControlEventTouchUpInside]; 53 | } 54 | return _jumpButton; 55 | } 56 | 57 | @end 58 | -------------------------------------------------------------------------------- /QFMoudle/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "20x20", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "20x20", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "29x29", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "29x29", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "40x40", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "40x40", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "size" : "60x60", 36 | "scale" : "2x" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "size" : "60x60", 41 | "scale" : "3x" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "size" : "20x20", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "size" : "20x20", 51 | "scale" : "2x" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "size" : "29x29", 56 | "scale" : "1x" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "size" : "29x29", 61 | "scale" : "2x" 62 | }, 63 | { 64 | "idiom" : "ipad", 65 | "size" : "40x40", 66 | "scale" : "1x" 67 | }, 68 | { 69 | "idiom" : "ipad", 70 | "size" : "40x40", 71 | "scale" : "2x" 72 | }, 73 | { 74 | "idiom" : "ipad", 75 | "size" : "76x76", 76 | "scale" : "1x" 77 | }, 78 | { 79 | "idiom" : "ipad", 80 | "size" : "76x76", 81 | "scale" : "2x" 82 | }, 83 | { 84 | "idiom" : "ipad", 85 | "size" : "83.5x83.5", 86 | "scale" : "2x" 87 | }, 88 | { 89 | "idiom" : "ios-marketing", 90 | "size" : "1024x1024", 91 | "scale" : "1x" 92 | } 93 | ], 94 | "info" : { 95 | "version" : 1, 96 | "author" : "xcode" 97 | } 98 | } -------------------------------------------------------------------------------- /QFMoudle/QFRouter/QFRouter.m: -------------------------------------------------------------------------------- 1 | // 2 | // QFRouter.m 3 | // QFMoudle 4 | // 5 | // Created by 情风 on 2018/11/5. 6 | // Copyright © 2018年 qingfengiOS. All rights reserved. 7 | // 8 | 9 | #import "QFRouter.h" 10 | #import 11 | 12 | @implementation QFRouter 13 | 14 | static QFRouter *_router; 15 | 16 | #pragma mark - 一个严谨的单例 17 | + (instancetype)router { 18 | 19 | static dispatch_once_t onceToken; 20 | dispatch_once(&onceToken, ^{ 21 | _router = [[self alloc]init]; 22 | }); 23 | return _router; 24 | } 25 | 26 | + (instancetype)allocWithZone:(struct _NSZone *)zone { 27 | static dispatch_once_t onceToken; 28 | dispatch_once(&onceToken, ^{ 29 | if (_router == nil) { 30 | _router = [super allocWithZone:zone]; 31 | } 32 | }); 33 | return _router; 34 | } 35 | 36 | - (id)copyWithZone:(NSZone *)zone { 37 | return _router; 38 | } 39 | 40 | - (id)mutableCopyWithZone:(NSZone *)zone { 41 | return _router; 42 | } 43 | 44 | #pragma mark - Public 45 | - (id)interfaceForProtocol:(Protocol *)protocol { 46 | Class class = [self classForProtocol:protocol]; 47 | return [[class alloc]init]; 48 | } 49 | 50 | - (id)interfaceForURL:(NSURL *)url { 51 | id result = [self interfaceForProtocol:objc_getProtocol(url.scheme.UTF8String)]; 52 | NSURLComponents *cp = [NSURLComponents componentsWithURL:url resolvingAgainstBaseURL:NO]; 53 | [cp.queryItems enumerateObjectsUsingBlock:^(NSURLQueryItem * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { 54 | [result setValue:obj.value forKey:obj.name];//KVC设置 55 | }]; 56 | return result; 57 | } 58 | 59 | #pragma mark - Private 60 | - (Class)classForProtocol:(Protocol *)protocol { 61 | NSString *classString = NSStringFromProtocol(protocol); 62 | return NSClassFromString(classString); 63 | } 64 | 65 | 66 | - (void)assertForMoudleWithProtocol:(Protocol *)p { 67 | if (![self classForProtocol:p]) { 68 | NSString *protocolName = NSStringFromProtocol(p); 69 | NSString *clsName = protocolName; 70 | NSString *reason = [NSString stringWithFormat: @"找不到协议 %@ 对应的接口类 %@ 的实现", protocolName, clsName]; 71 | [self throwException: reason]; 72 | } 73 | } 74 | 75 | - (void)assertForMoudleWithURL:(NSURL *)url { 76 | NSString *protocolName = url.scheme; 77 | if (![self classForProtocol: objc_getProtocol(protocolName.UTF8String)]) { 78 | NSString *clsName = protocolName; 79 | NSString *reason = [NSString stringWithFormat: @"找不到协议 %@ 对应的接口类 %@ 的实现", protocolName, clsName]; 80 | [self throwException: reason]; 81 | } 82 | } 83 | 84 | - (void)throwException:(NSString *) reason { 85 | @throw [NSException exceptionWithName: NSGenericException reason: reason userInfo: nil]; 86 | } 87 | 88 | @end 89 | -------------------------------------------------------------------------------- /QFMoudle/AppDelegate.m: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.m 3 | // QFMoudle 4 | // 5 | // Created by 情风 on 2018/11/5. 6 | // Copyright © 2018年 qingfengiOS. All rights reserved. 7 | // 8 | 9 | #import "AppDelegate.h" 10 | #import "QFRouter.h" 11 | #import "QFMoudleProtocol.h" 12 | 13 | @interface AppDelegate () 14 | 15 | @end 16 | 17 | @implementation AppDelegate 18 | 19 | 20 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { 21 | 22 | /* 23 | 测试一个严谨的单例,需要重写alloc copy mutableCopy方法 24 | */ 25 | QFRouter *r1 = [QFRouter router]; 26 | QFRouter *r2 = [[QFRouter alloc]init]; 27 | QFRouter *r3 = [r1 copy]; 28 | QFRouter *r4 = [r1 mutableCopy]; 29 | NSLog(@"\n%p\n%@\n%@\n%@",r1,r2,r3,r4); 30 | 31 | // Home组件 32 | id homeMoudle = [[QFRouter router]interfaceForProtocol:@protocol(MoudleHome)]; 33 | homeMoudle.paramterForHome = @"MoudleHome"; 34 | UIViewController *homeViewController = homeMoudle.interfaceViewController; 35 | UINavigationController *homeNavi = [[UINavigationController alloc]initWithRootViewController:homeViewController]; 36 | homeNavi.tabBarItem.title = @"首页"; 37 | homeNavi.tabBarItem.image = [UIImage imageNamed:@"home"]; 38 | 39 | // Me组件 40 | id meMoudle = [[QFRouter router]interfaceForURL:[NSURL URLWithString:@"MoudleMe://?paramterForMe=ModuleMe"]]; 41 | UIViewController *meViewConterller = meMoudle.interfaceViewController; 42 | UINavigationController *meNavi = [[UINavigationController alloc]initWithRootViewController:meViewConterller]; 43 | meNavi.tabBarItem.title = @"个人"; 44 | meNavi.tabBarItem.image = [UIImage imageNamed:@"me"]; 45 | 46 | // tabbr和window 47 | UITabBarController *tabbarController = [[UITabBarController alloc]init]; 48 | tabbarController.viewControllers = @[homeNavi,meNavi]; 49 | _window.rootViewController = tabbarController; 50 | _window.backgroundColor = [UIColor whiteColor]; 51 | [_window makeKeyAndVisible]; 52 | 53 | return YES; 54 | } 55 | 56 | 57 | - (void)applicationWillResignActive:(UIApplication *)application { 58 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. 59 | // Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game. 60 | } 61 | 62 | 63 | - (void)applicationDidEnterBackground:(UIApplication *)application { 64 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. 65 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 66 | } 67 | 68 | 69 | - (void)applicationWillEnterForeground:(UIApplication *)application { 70 | // Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background. 71 | } 72 | 73 | 74 | - (void)applicationDidBecomeActive:(UIApplication *)application { 75 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. 76 | } 77 | 78 | 79 | - (void)applicationWillTerminate:(UIApplication *)application { 80 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 81 | } 82 | 83 | 84 | @end 85 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # QFMoudle 2 | 一个极其轻量级的组件化实践 3 | 4 | ## 前言 5 | 说起组件化大家应该都不陌生,不过也再提一下,由于业务的复杂度扩展,各个模块之间的耦合度越来越高,不但造成了“牵一发动全身”的尴尬境地,还增加了测试的重复工程,此时,组件化就值得考虑了。组件化就是将APP拆分成各个组件(或者说模块),同时解除这些组件之间的耦合,然后通过路由中间件将项目所需要的组件结合起来。这样做的好处有: 6 | 7 | - 解耦合,增强可移植性,不用再自身业务模块中大量引入其他业务的头文件。 8 | - 提高复用性,如果其他项目中有类似的功能,直接将模块引入稍作修改就能使用了。 9 | - 减少测试成本,当修改或者迭代某个小组件的过程中就不用进行大规模的回归测试。 10 | 11 | 网上关于组件化的方案不少,流传最广的是[蘑菇街组件化的技术方案](https://limboy.me/tech/2016/03/10/mgj-components.html)和[iOS应用架构谈 组件化方案](https://casatwy.com/iOS-Modulization.html)这里不对大佬们的方案妄加评价,感兴趣的同学可以自己看看。这里我们聊聊另外的一种方式[Protocol-Moudle](https://github.com/qingfengiOS/QFMoudle) 12 | 13 | ## 思路 14 | 在iOS中,[协议(Protocol)](https://juejin.im/post/5bda59da5188257f8f1e1a02)**定义了一个纲领性的接口,所有类都可以选择实现。它主要是用来定义一套对象之间的通信规则。protocol也是我们设计时常用的一个东西,相对于直接继承的方式,protocol则偏向于组合模式。他使得两个毫不相关的类能够相互通信,从而实现特定的目标。** 15 | 16 | 在之前的一篇文章[ResponderChain+Strategy+MVVM实现一个优雅的TableView](https://juejin.im/post/5bd6c734e51d45410c10eb54)中我们用到了protocol来为View提供公共的方法: 17 | 18 | ```- (void)configCellDateByModel:(id)model;``` 19 | 20 | 为Model提供公共的方法: 21 | 22 | ``` 23 | - (NSString *)identifier; 24 | - (CGFloat)height; 25 | ``` 26 | 那么我们也可以以此来构建一个轻量级的路由中间件,定义一套各个组件的通信规则,各自管理和维护各自的模块,对外提供必要的接口。 27 | 28 | ## 实践 29 | 首先看一下这个Demo的结构图和运行效果 30 | ![结构](https://user-gold-cdn.xitu.io/2018/11/8/166f372c2073a148?w=540&h=1270&f=png&s=211387) 31 | 32 | ![效果](https://user-gold-cdn.xitu.io/2018/11/8/166f374df5170fcc?w=227&h=420&f=gif&s=4773661) 33 | 34 | ### 路由 35 | 好了我们看看路由的一些细节,它只需要提供两个关键的东西: 36 | 37 | 1. 提供路由器单例 38 | 2. 获取对应的Moudle 39 | - 通过Protocol获取 40 | - 通过URL获取 41 | 42 | 首先提供单例: 43 | 44 | ``` 45 | + (instancetype)router { 46 | 47 | static dispatch_once_t onceToken; 48 | dispatch_once(&onceToken, ^{ 49 | _router = [[self alloc]init]; 50 | }); 51 | return _router; 52 | } 53 | ``` 54 | 这样的单例可能没有人不会写,but,这其实这仅仅是个“假的”单例,不信你可以使用```[QFRouter router]``` 和```[[QFRouter alloc]init]```以及```[router copy]```试试他们会不会生成三个内存地址不同的实例。可能你会说,谁会无聊这么干?但是如果设计的时候能更严谨的规避这种坑不会更好么。 那么怎么才能才能做一个严谨的单例呢?可以重写他的```alloc```和```copy```方法避免创建多个实例,你可以在[Demo工程](https://github.com/qingfengiOS/QFMoudle)中看到细节,这里不做展开。 55 | 56 | 回归正题,我们看看如何获取Module 57 | 58 | 通过Runtime的反射机制,我们可以通过NSString获取一个class进而创建对应的对象,而Protocol又可以得到一个NSString,那么是否可以由此入手呢?答案是可以的: 59 | 60 | ``` 61 | - (Class)classForProtocol:(Protocol *)protocol { 62 | NSString *classString = NSStringFromProtocol(protocol); 63 | return NSClassFromString(classString); 64 | } 65 | ``` 66 | 这里传入一个protocol即可获取对应的Module的class,再通过class即可以得到对应的Module的object。 67 | 68 | 通过Protocol或者URL获取对应的Module: 69 | 70 | ``` 71 | #pragma mark - Public 72 | - (id)interfaceForProtocol:(Protocol *)protocol { 73 | Class class = [self classForProtocol:protocol]; 74 | return [[class alloc]init]; 75 | } 76 | 77 | - (id)interfaceForURL:(NSURL *)url { 78 | id result = [self interfaceForProtocol:objc_getProtocol(url.scheme.UTF8String)]; 79 | NSURLComponents *cp = [NSURLComponents componentsWithURL:url resolvingAgainstBaseURL:NO]; 80 | [cp.queryItems enumerateObjectsUsingBlock:^(NSURLQueryItem * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { 81 | [result setValue:obj.value forKey:obj.name];//KVC设置 82 | }]; 83 | return result; 84 | } 85 | 86 | ``` 87 | 这里有个瑕疵就是调用方(外部组件)需要知道这个目标组件对外暴露的协议名称,不过既然是协议,对外公开应该不算大问题吧,调用实例如下: 88 | wayOne: 89 | 90 | ``` 91 | id homeMoudle = [[QFRouter router]interfaceForProtocol:@protocol(MoudleHome)]; 92 | ``` 93 | 这样就拿到了对应的目标组件实例,通过对外暴露的属性可以对其进行传值,通过其回调block则可以拿到回调参数。 94 | 95 | wayTwo: 96 | 97 | ``` 98 | id meMoudle = [[QFRouter router]interfaceForURL:[NSURL URLWithString:@"MoudleMe://?paramterForMe=ModuleMe"]]; 99 | ``` 100 | 这里通过url传入,通过KVC设置其属性值。同样地,通过其回调block则可以拿到回调参数。 101 | 102 | ### 公共协议 103 | 通过上面我们了解到:通过Protocol可以获取对应的组件实例,那么这个协议放在哪儿?如何管理呢? 104 | 在日常开发过程中,跨组件的交互场景最多的应该就是:从组件A附带参数跳转到组件B的某个页面,组件B的这个页面中做一些操作,再回到组件A(可能有回调参数,也可能不回调参数),那么我们的协议应该能处理这两个最常见和基础的操作,所以给protocol定义了两个属性: 105 | 106 | ``` 107 | typedef void(^QFCallBackBlock)(id parameter); 108 | 109 | #pragma mark - 基础协议 110 | @protocol QFMoudleProtocol 111 | 112 | /// 暴露给组件外部的控制器,一般为该组件的主控制器 113 | @property (nonatomic, weak) UIViewController *interfaceViewController; 114 | /// 回调参数 115 | @property (nonatomic, copy) QFCallBackBlock callbackBlock; 116 | 117 | @end 118 | ``` 119 | 120 | >这里的interfaceViewController为何声明成了weak属性?这个问题先留一下,后面会聊到这一点。 121 | 122 | 有了这里的两个属性我们即可完成,对应的跳转和参数回调,但是如何正向传值呢? 123 | 124 | 应该还需要对应的属性来做入参,但是组件何其多,入参何其多,如果都把正向的属性写入这里面,那么随着时间和业务的增长,这个协议可能会十分杂乱和臃肿。 125 | 126 | 所以这里把这个协议定为基础协议,对应的组件都继承自它,然后定义各自的需要的入参属性: 127 | 128 | 首页组件: 129 | 130 | ``` 131 | #pragma mark - ”首页“组件 132 | @protocol MoudleHome 133 | 134 | /// 组件“Home”首页所需要的参数 135 | @property (nonatomic, copy) NSString *paramterForHome; 136 | 137 | /// 组件“Home”中详情页面所需要的参数 138 | @property (nonatomic, copy) NSString *titleString; 139 | 140 | /// 组件“Home”中详情页面所需要的参数 141 | @property (nonatomic, copy) NSString *descString; 142 | 143 | /// 组件“Home”所需要暴露的特殊接口,比如其他组件也要跳转到该页面 144 | @property (nonatomic, weak) UIViewController *detailViewController; 145 | 146 | @end 147 | 148 | ``` 149 | 可以看到,由于首页组件需要对外暴露一个主页面 ```QFHomeViewController``` 和详细页面 ``QFDetailViewController``所以参数会多一点。 150 | 151 | 我的组件: 152 | 153 | ``` 154 | #pragma mark - “我的”组件 155 | @protocol MoudleMe 156 | 157 | /// 组件“Me”所需要的参数 158 | @property (nonatomic, copy) NSString *paramterForMe; 159 | 160 | @end 161 | ``` 162 | 而“我的”组件,只对外提供一个```QFMeViewController```页面,参数比较简单。 163 | 164 | 这样,基本算是达成了对协议的处理,但是无可避免的问题就是: 这个公共协议中定义了各个组件的协议,所以需要对多个开发团队可见,感觉这也是组件化过程中的一个普遍问题,目前没找到好的解决方式。 165 | 166 | 167 | ### Module 168 | 上面我们说到了公开protocol中定义了一些属性,比如```interfaceViewController``` 那么这些属性由谁提供呢?没错,就是Module,通过上面的步骤我们可以获取到对应的Module实例,但是我们跳转需要的是Controller,所以,在此时就需要Module的帮助了, Module通过公共协议定义的属性为外部提供Controller接口: 169 | 170 | ``` 171 | - (UIViewController *)interfaceViewController { 172 | QFHomeViewController *homeViewController = [[QFHomeViewController alloc]init]; 173 | homeViewController.interface = self; 174 | interfaceViewController = (UIViewController *)homeViewController; 175 | return interfaceViewController; 176 | } 177 | ``` 178 | 因为Module是在对应的组件中,所以可以随意引用自己组件内部的头文件完成初始化, 179 | 而对应的控制器中,需要组件外部的参数,所以这里把Module实例也暴露给对应的控制器实例,也就是```homeViewController.interface = self;```所做的事情。 180 | 181 | >在上面说协议的时候我们提到为什么要使用weak,至此,应该比较明朗了 ———— 打破循环强引用。 182 | 183 | 通过公共协议解耦获取到Module,Module完成为组件内和组件外的搭桥铺路工作,由此,使得跨组件传值、调用、参数回调得以实现。更多细节请看[QFMoudle](https://github.com/qingfengiOS/QFMoudle.git)。 184 | 185 | 由于时间关系,如何制作私有库就不再赘述了,有需要欢迎留言,我们一起手把手创建一个属于你自己的pod私有库。 186 | 187 | ## 后记 188 | 在这种组件化的实践中,一般会把对应的组件、路由以及公共协议做成pod私有库,而需要多个团队知道的也就只有这个公共协议库,把所有的协议放入这个公开协议库中,每次升级也只需要更新这个库就好了。 189 | 190 | 关于对组件化的态度:上面说了那么多好处,下面说说弊端(亲身体会) 191 | 如果团队规模较小业务复杂度较低的话,不太建议做私有库,原因: 192 | 193 | 1. 它的 修改 -> 升级 -> 集成 -> 测试是一个比较耗时的过程,可能一点小小的改动(比如改个颜色,换个背景)需要耗费成倍的时间。 194 | 2. 增加项目的调用复杂度,增加新成员的熟悉成本。 195 | 196 | 任何事情都是双面的,有利有弊,望各位看官自行取舍。最后由于笔者文笔有限,若给您造成了困扰,实在抱歉,有任何疑问or意见or建议,欢迎交流讨论,当然,如果对您有用,希望顺手给个Star,点个关注。赠人玫瑰,手留余香,您的支持是我最大的动力! 197 | 198 | -------------------------------------------------------------------------------- /QFMoudle.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 50; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | FD9E1D9B218FD41D009E05A9 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = FD9E1D9A218FD41D009E05A9 /* AppDelegate.m */; }; 11 | FD9E1D9E218FD41D009E05A9 /* ViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = FD9E1D9D218FD41D009E05A9 /* ViewController.m */; }; 12 | FD9E1DA1218FD41D009E05A9 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = FD9E1D9F218FD41D009E05A9 /* Main.storyboard */; }; 13 | FD9E1DA3218FD41E009E05A9 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = FD9E1DA2218FD41E009E05A9 /* Assets.xcassets */; }; 14 | FD9E1DA6218FD41E009E05A9 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = FD9E1DA4218FD41E009E05A9 /* LaunchScreen.storyboard */; }; 15 | FD9E1DA9218FD41E009E05A9 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = FD9E1DA8218FD41E009E05A9 /* main.m */; }; 16 | FD9E1DB3218FD41E009E05A9 /* QFMoudleTests.m in Sources */ = {isa = PBXBuildFile; fileRef = FD9E1DB2218FD41E009E05A9 /* QFMoudleTests.m */; }; 17 | FD9E1DBE218FD41E009E05A9 /* QFMoudleUITests.m in Sources */ = {isa = PBXBuildFile; fileRef = FD9E1DBD218FD41E009E05A9 /* QFMoudleUITests.m */; }; 18 | FD9E1DD1218FD7BA009E05A9 /* QFRouter.m in Sources */ = {isa = PBXBuildFile; fileRef = FD9E1DD0218FD7BA009E05A9 /* QFRouter.m */; }; 19 | FD9E1DE1218FDBFA009E05A9 /* MoudleHome.m in Sources */ = {isa = PBXBuildFile; fileRef = FD9E1DDD218FDBFA009E05A9 /* MoudleHome.m */; }; 20 | FD9E1DE2218FDBFA009E05A9 /* QFHomeViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = FD9E1DE0218FDBFA009E05A9 /* QFHomeViewController.m */; }; 21 | FD9E1DE5218FDC0C009E05A9 /* QFDetailViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = FD9E1DE4218FDC0C009E05A9 /* QFDetailViewController.m */; }; 22 | FD9E1DEA218FDC46009E05A9 /* QFMeViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = FD9E1DE9218FDC46009E05A9 /* QFMeViewController.m */; }; 23 | FD9E1DEE218FDC79009E05A9 /* MoudleMe.m in Sources */ = {isa = PBXBuildFile; fileRef = FD9E1DED218FDC79009E05A9 /* MoudleMe.m */; }; 24 | /* End PBXBuildFile section */ 25 | 26 | /* Begin PBXContainerItemProxy section */ 27 | FD9E1DAF218FD41E009E05A9 /* PBXContainerItemProxy */ = { 28 | isa = PBXContainerItemProxy; 29 | containerPortal = FD9E1D8E218FD41C009E05A9 /* Project object */; 30 | proxyType = 1; 31 | remoteGlobalIDString = FD9E1D95218FD41C009E05A9; 32 | remoteInfo = QFMoudle; 33 | }; 34 | FD9E1DBA218FD41E009E05A9 /* PBXContainerItemProxy */ = { 35 | isa = PBXContainerItemProxy; 36 | containerPortal = FD9E1D8E218FD41C009E05A9 /* Project object */; 37 | proxyType = 1; 38 | remoteGlobalIDString = FD9E1D95218FD41C009E05A9; 39 | remoteInfo = QFMoudle; 40 | }; 41 | /* End PBXContainerItemProxy section */ 42 | 43 | /* Begin PBXFileReference section */ 44 | FD9E1D96218FD41D009E05A9 /* QFMoudle.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = QFMoudle.app; sourceTree = BUILT_PRODUCTS_DIR; }; 45 | FD9E1D99218FD41D009E05A9 /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; 46 | FD9E1D9A218FD41D009E05A9 /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; 47 | FD9E1D9C218FD41D009E05A9 /* ViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ViewController.h; sourceTree = ""; }; 48 | FD9E1D9D218FD41D009E05A9 /* ViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ViewController.m; sourceTree = ""; }; 49 | FD9E1DA0218FD41D009E05A9 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 50 | FD9E1DA2218FD41E009E05A9 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 51 | FD9E1DA5218FD41E009E05A9 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 52 | FD9E1DA7218FD41E009E05A9 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 53 | FD9E1DA8218FD41E009E05A9 /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; 54 | FD9E1DAE218FD41E009E05A9 /* QFMoudleTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = QFMoudleTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 55 | FD9E1DB2218FD41E009E05A9 /* QFMoudleTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = QFMoudleTests.m; sourceTree = ""; }; 56 | FD9E1DB4218FD41E009E05A9 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 57 | FD9E1DB9218FD41E009E05A9 /* QFMoudleUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = QFMoudleUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 58 | FD9E1DBD218FD41E009E05A9 /* QFMoudleUITests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = QFMoudleUITests.m; sourceTree = ""; }; 59 | FD9E1DBF218FD41E009E05A9 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 60 | FD9E1DCF218FD7BA009E05A9 /* QFRouter.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = QFRouter.h; sourceTree = ""; }; 61 | FD9E1DD0218FD7BA009E05A9 /* QFRouter.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = QFRouter.m; sourceTree = ""; }; 62 | FD9E1DD3218FD9D0009E05A9 /* QFMoudleProtocol.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = QFMoudleProtocol.h; sourceTree = ""; }; 63 | FD9E1DDC218FDBFA009E05A9 /* MoudleHome.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MoudleHome.h; sourceTree = ""; }; 64 | FD9E1DDD218FDBFA009E05A9 /* MoudleHome.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MoudleHome.m; sourceTree = ""; }; 65 | FD9E1DDF218FDBFA009E05A9 /* QFHomeViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = QFHomeViewController.h; sourceTree = ""; }; 66 | FD9E1DE0218FDBFA009E05A9 /* QFHomeViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = QFHomeViewController.m; sourceTree = ""; }; 67 | FD9E1DE3218FDC0C009E05A9 /* QFDetailViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = QFDetailViewController.h; sourceTree = ""; }; 68 | FD9E1DE4218FDC0C009E05A9 /* QFDetailViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = QFDetailViewController.m; sourceTree = ""; }; 69 | FD9E1DE8218FDC46009E05A9 /* QFMeViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = QFMeViewController.h; sourceTree = ""; }; 70 | FD9E1DE9218FDC46009E05A9 /* QFMeViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = QFMeViewController.m; sourceTree = ""; }; 71 | FD9E1DEC218FDC79009E05A9 /* MoudleMe.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MoudleMe.h; sourceTree = ""; }; 72 | FD9E1DED218FDC79009E05A9 /* MoudleMe.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MoudleMe.m; sourceTree = ""; }; 73 | /* End PBXFileReference section */ 74 | 75 | /* Begin PBXFrameworksBuildPhase section */ 76 | FD9E1D93218FD41C009E05A9 /* Frameworks */ = { 77 | isa = PBXFrameworksBuildPhase; 78 | buildActionMask = 2147483647; 79 | files = ( 80 | ); 81 | runOnlyForDeploymentPostprocessing = 0; 82 | }; 83 | FD9E1DAB218FD41E009E05A9 /* Frameworks */ = { 84 | isa = PBXFrameworksBuildPhase; 85 | buildActionMask = 2147483647; 86 | files = ( 87 | ); 88 | runOnlyForDeploymentPostprocessing = 0; 89 | }; 90 | FD9E1DB6218FD41E009E05A9 /* Frameworks */ = { 91 | isa = PBXFrameworksBuildPhase; 92 | buildActionMask = 2147483647; 93 | files = ( 94 | ); 95 | runOnlyForDeploymentPostprocessing = 0; 96 | }; 97 | /* End PBXFrameworksBuildPhase section */ 98 | 99 | /* Begin PBXGroup section */ 100 | FD9E1D8D218FD41C009E05A9 = { 101 | isa = PBXGroup; 102 | children = ( 103 | FD9E1D98218FD41D009E05A9 /* QFMoudle */, 104 | FD9E1DB1218FD41E009E05A9 /* QFMoudleTests */, 105 | FD9E1DBC218FD41E009E05A9 /* QFMoudleUITests */, 106 | FD9E1D97218FD41D009E05A9 /* Products */, 107 | ); 108 | sourceTree = ""; 109 | }; 110 | FD9E1D97218FD41D009E05A9 /* Products */ = { 111 | isa = PBXGroup; 112 | children = ( 113 | FD9E1D96218FD41D009E05A9 /* QFMoudle.app */, 114 | FD9E1DAE218FD41E009E05A9 /* QFMoudleTests.xctest */, 115 | FD9E1DB9218FD41E009E05A9 /* QFMoudleUITests.xctest */, 116 | ); 117 | name = Products; 118 | sourceTree = ""; 119 | }; 120 | FD9E1D98218FD41D009E05A9 /* QFMoudle */ = { 121 | isa = PBXGroup; 122 | children = ( 123 | FD9E1DDA218FDBFA009E05A9 /* QFHome */, 124 | FD9E1DCE218FD7A2009E05A9 /* QFMe */, 125 | FD9E1DCC218FD7A2009E05A9 /* QFProtocol */, 126 | FD9E1DCD218FD7A2009E05A9 /* QFRouter */, 127 | FD9E1D99218FD41D009E05A9 /* AppDelegate.h */, 128 | FD9E1D9A218FD41D009E05A9 /* AppDelegate.m */, 129 | FD9E1D9C218FD41D009E05A9 /* ViewController.h */, 130 | FD9E1D9D218FD41D009E05A9 /* ViewController.m */, 131 | FD9E1D9F218FD41D009E05A9 /* Main.storyboard */, 132 | FD9E1DA2218FD41E009E05A9 /* Assets.xcassets */, 133 | FD9E1DA4218FD41E009E05A9 /* LaunchScreen.storyboard */, 134 | FD9E1DA7218FD41E009E05A9 /* Info.plist */, 135 | FD9E1DA8218FD41E009E05A9 /* main.m */, 136 | ); 137 | path = QFMoudle; 138 | sourceTree = ""; 139 | }; 140 | FD9E1DB1218FD41E009E05A9 /* QFMoudleTests */ = { 141 | isa = PBXGroup; 142 | children = ( 143 | FD9E1DB2218FD41E009E05A9 /* QFMoudleTests.m */, 144 | FD9E1DB4218FD41E009E05A9 /* Info.plist */, 145 | ); 146 | path = QFMoudleTests; 147 | sourceTree = ""; 148 | }; 149 | FD9E1DBC218FD41E009E05A9 /* QFMoudleUITests */ = { 150 | isa = PBXGroup; 151 | children = ( 152 | FD9E1DBD218FD41E009E05A9 /* QFMoudleUITests.m */, 153 | FD9E1DBF218FD41E009E05A9 /* Info.plist */, 154 | ); 155 | path = QFMoudleUITests; 156 | sourceTree = ""; 157 | }; 158 | FD9E1DCC218FD7A2009E05A9 /* QFProtocol */ = { 159 | isa = PBXGroup; 160 | children = ( 161 | FD9E1DD3218FD9D0009E05A9 /* QFMoudleProtocol.h */, 162 | ); 163 | path = QFProtocol; 164 | sourceTree = ""; 165 | }; 166 | FD9E1DCD218FD7A2009E05A9 /* QFRouter */ = { 167 | isa = PBXGroup; 168 | children = ( 169 | FD9E1DCF218FD7BA009E05A9 /* QFRouter.h */, 170 | FD9E1DD0218FD7BA009E05A9 /* QFRouter.m */, 171 | ); 172 | path = QFRouter; 173 | sourceTree = ""; 174 | }; 175 | FD9E1DCE218FD7A2009E05A9 /* QFMe */ = { 176 | isa = PBXGroup; 177 | children = ( 178 | FD9E1DE6218FDC3A009E05A9 /* Moudle */, 179 | FD9E1DE7218FDC3A009E05A9 /* Controller */, 180 | ); 181 | path = QFMe; 182 | sourceTree = ""; 183 | }; 184 | FD9E1DDA218FDBFA009E05A9 /* QFHome */ = { 185 | isa = PBXGroup; 186 | children = ( 187 | FD9E1DDB218FDBFA009E05A9 /* Module */, 188 | FD9E1DDE218FDBFA009E05A9 /* Controller */, 189 | ); 190 | path = QFHome; 191 | sourceTree = ""; 192 | }; 193 | FD9E1DDB218FDBFA009E05A9 /* Module */ = { 194 | isa = PBXGroup; 195 | children = ( 196 | FD9E1DDC218FDBFA009E05A9 /* MoudleHome.h */, 197 | FD9E1DDD218FDBFA009E05A9 /* MoudleHome.m */, 198 | ); 199 | path = Module; 200 | sourceTree = ""; 201 | }; 202 | FD9E1DDE218FDBFA009E05A9 /* Controller */ = { 203 | isa = PBXGroup; 204 | children = ( 205 | FD9E1DDF218FDBFA009E05A9 /* QFHomeViewController.h */, 206 | FD9E1DE0218FDBFA009E05A9 /* QFHomeViewController.m */, 207 | FD9E1DE3218FDC0C009E05A9 /* QFDetailViewController.h */, 208 | FD9E1DE4218FDC0C009E05A9 /* QFDetailViewController.m */, 209 | ); 210 | path = Controller; 211 | sourceTree = ""; 212 | }; 213 | FD9E1DE6218FDC3A009E05A9 /* Moudle */ = { 214 | isa = PBXGroup; 215 | children = ( 216 | FD9E1DEC218FDC79009E05A9 /* MoudleMe.h */, 217 | FD9E1DED218FDC79009E05A9 /* MoudleMe.m */, 218 | ); 219 | path = Moudle; 220 | sourceTree = ""; 221 | }; 222 | FD9E1DE7218FDC3A009E05A9 /* Controller */ = { 223 | isa = PBXGroup; 224 | children = ( 225 | FD9E1DE8218FDC46009E05A9 /* QFMeViewController.h */, 226 | FD9E1DE9218FDC46009E05A9 /* QFMeViewController.m */, 227 | ); 228 | path = Controller; 229 | sourceTree = ""; 230 | }; 231 | /* End PBXGroup section */ 232 | 233 | /* Begin PBXNativeTarget section */ 234 | FD9E1D95218FD41C009E05A9 /* QFMoudle */ = { 235 | isa = PBXNativeTarget; 236 | buildConfigurationList = FD9E1DC2218FD41E009E05A9 /* Build configuration list for PBXNativeTarget "QFMoudle" */; 237 | buildPhases = ( 238 | FD9E1D92218FD41C009E05A9 /* Sources */, 239 | FD9E1D93218FD41C009E05A9 /* Frameworks */, 240 | FD9E1D94218FD41C009E05A9 /* Resources */, 241 | ); 242 | buildRules = ( 243 | ); 244 | dependencies = ( 245 | ); 246 | name = QFMoudle; 247 | productName = QFMoudle; 248 | productReference = FD9E1D96218FD41D009E05A9 /* QFMoudle.app */; 249 | productType = "com.apple.product-type.application"; 250 | }; 251 | FD9E1DAD218FD41E009E05A9 /* QFMoudleTests */ = { 252 | isa = PBXNativeTarget; 253 | buildConfigurationList = FD9E1DC5218FD41E009E05A9 /* Build configuration list for PBXNativeTarget "QFMoudleTests" */; 254 | buildPhases = ( 255 | FD9E1DAA218FD41E009E05A9 /* Sources */, 256 | FD9E1DAB218FD41E009E05A9 /* Frameworks */, 257 | FD9E1DAC218FD41E009E05A9 /* Resources */, 258 | ); 259 | buildRules = ( 260 | ); 261 | dependencies = ( 262 | FD9E1DB0218FD41E009E05A9 /* PBXTargetDependency */, 263 | ); 264 | name = QFMoudleTests; 265 | productName = QFMoudleTests; 266 | productReference = FD9E1DAE218FD41E009E05A9 /* QFMoudleTests.xctest */; 267 | productType = "com.apple.product-type.bundle.unit-test"; 268 | }; 269 | FD9E1DB8218FD41E009E05A9 /* QFMoudleUITests */ = { 270 | isa = PBXNativeTarget; 271 | buildConfigurationList = FD9E1DC8218FD41E009E05A9 /* Build configuration list for PBXNativeTarget "QFMoudleUITests" */; 272 | buildPhases = ( 273 | FD9E1DB5218FD41E009E05A9 /* Sources */, 274 | FD9E1DB6218FD41E009E05A9 /* Frameworks */, 275 | FD9E1DB7218FD41E009E05A9 /* Resources */, 276 | ); 277 | buildRules = ( 278 | ); 279 | dependencies = ( 280 | FD9E1DBB218FD41E009E05A9 /* PBXTargetDependency */, 281 | ); 282 | name = QFMoudleUITests; 283 | productName = QFMoudleUITests; 284 | productReference = FD9E1DB9218FD41E009E05A9 /* QFMoudleUITests.xctest */; 285 | productType = "com.apple.product-type.bundle.ui-testing"; 286 | }; 287 | /* End PBXNativeTarget section */ 288 | 289 | /* Begin PBXProject section */ 290 | FD9E1D8E218FD41C009E05A9 /* Project object */ = { 291 | isa = PBXProject; 292 | attributes = { 293 | CLASSPREFIX = QF; 294 | LastUpgradeCheck = 0940; 295 | ORGANIZATIONNAME = qingfengiOS; 296 | TargetAttributes = { 297 | FD9E1D95218FD41C009E05A9 = { 298 | CreatedOnToolsVersion = 9.4.1; 299 | }; 300 | FD9E1DAD218FD41E009E05A9 = { 301 | CreatedOnToolsVersion = 9.4.1; 302 | TestTargetID = FD9E1D95218FD41C009E05A9; 303 | }; 304 | FD9E1DB8218FD41E009E05A9 = { 305 | CreatedOnToolsVersion = 9.4.1; 306 | TestTargetID = FD9E1D95218FD41C009E05A9; 307 | }; 308 | }; 309 | }; 310 | buildConfigurationList = FD9E1D91218FD41C009E05A9 /* Build configuration list for PBXProject "QFMoudle" */; 311 | compatibilityVersion = "Xcode 9.3"; 312 | developmentRegion = en; 313 | hasScannedForEncodings = 0; 314 | knownRegions = ( 315 | en, 316 | Base, 317 | ); 318 | mainGroup = FD9E1D8D218FD41C009E05A9; 319 | productRefGroup = FD9E1D97218FD41D009E05A9 /* Products */; 320 | projectDirPath = ""; 321 | projectRoot = ""; 322 | targets = ( 323 | FD9E1D95218FD41C009E05A9 /* QFMoudle */, 324 | FD9E1DAD218FD41E009E05A9 /* QFMoudleTests */, 325 | FD9E1DB8218FD41E009E05A9 /* QFMoudleUITests */, 326 | ); 327 | }; 328 | /* End PBXProject section */ 329 | 330 | /* Begin PBXResourcesBuildPhase section */ 331 | FD9E1D94218FD41C009E05A9 /* Resources */ = { 332 | isa = PBXResourcesBuildPhase; 333 | buildActionMask = 2147483647; 334 | files = ( 335 | FD9E1DA6218FD41E009E05A9 /* LaunchScreen.storyboard in Resources */, 336 | FD9E1DA3218FD41E009E05A9 /* Assets.xcassets in Resources */, 337 | FD9E1DA1218FD41D009E05A9 /* Main.storyboard in Resources */, 338 | ); 339 | runOnlyForDeploymentPostprocessing = 0; 340 | }; 341 | FD9E1DAC218FD41E009E05A9 /* Resources */ = { 342 | isa = PBXResourcesBuildPhase; 343 | buildActionMask = 2147483647; 344 | files = ( 345 | ); 346 | runOnlyForDeploymentPostprocessing = 0; 347 | }; 348 | FD9E1DB7218FD41E009E05A9 /* Resources */ = { 349 | isa = PBXResourcesBuildPhase; 350 | buildActionMask = 2147483647; 351 | files = ( 352 | ); 353 | runOnlyForDeploymentPostprocessing = 0; 354 | }; 355 | /* End PBXResourcesBuildPhase section */ 356 | 357 | /* Begin PBXSourcesBuildPhase section */ 358 | FD9E1D92218FD41C009E05A9 /* Sources */ = { 359 | isa = PBXSourcesBuildPhase; 360 | buildActionMask = 2147483647; 361 | files = ( 362 | FD9E1DE5218FDC0C009E05A9 /* QFDetailViewController.m in Sources */, 363 | FD9E1DE2218FDBFA009E05A9 /* QFHomeViewController.m in Sources */, 364 | FD9E1D9E218FD41D009E05A9 /* ViewController.m in Sources */, 365 | FD9E1DD1218FD7BA009E05A9 /* QFRouter.m in Sources */, 366 | FD9E1DA9218FD41E009E05A9 /* main.m in Sources */, 367 | FD9E1D9B218FD41D009E05A9 /* AppDelegate.m in Sources */, 368 | FD9E1DE1218FDBFA009E05A9 /* MoudleHome.m in Sources */, 369 | FD9E1DEA218FDC46009E05A9 /* QFMeViewController.m in Sources */, 370 | FD9E1DEE218FDC79009E05A9 /* MoudleMe.m in Sources */, 371 | ); 372 | runOnlyForDeploymentPostprocessing = 0; 373 | }; 374 | FD9E1DAA218FD41E009E05A9 /* Sources */ = { 375 | isa = PBXSourcesBuildPhase; 376 | buildActionMask = 2147483647; 377 | files = ( 378 | FD9E1DB3218FD41E009E05A9 /* QFMoudleTests.m in Sources */, 379 | ); 380 | runOnlyForDeploymentPostprocessing = 0; 381 | }; 382 | FD9E1DB5218FD41E009E05A9 /* Sources */ = { 383 | isa = PBXSourcesBuildPhase; 384 | buildActionMask = 2147483647; 385 | files = ( 386 | FD9E1DBE218FD41E009E05A9 /* QFMoudleUITests.m in Sources */, 387 | ); 388 | runOnlyForDeploymentPostprocessing = 0; 389 | }; 390 | /* End PBXSourcesBuildPhase section */ 391 | 392 | /* Begin PBXTargetDependency section */ 393 | FD9E1DB0218FD41E009E05A9 /* PBXTargetDependency */ = { 394 | isa = PBXTargetDependency; 395 | target = FD9E1D95218FD41C009E05A9 /* QFMoudle */; 396 | targetProxy = FD9E1DAF218FD41E009E05A9 /* PBXContainerItemProxy */; 397 | }; 398 | FD9E1DBB218FD41E009E05A9 /* PBXTargetDependency */ = { 399 | isa = PBXTargetDependency; 400 | target = FD9E1D95218FD41C009E05A9 /* QFMoudle */; 401 | targetProxy = FD9E1DBA218FD41E009E05A9 /* PBXContainerItemProxy */; 402 | }; 403 | /* End PBXTargetDependency section */ 404 | 405 | /* Begin PBXVariantGroup section */ 406 | FD9E1D9F218FD41D009E05A9 /* Main.storyboard */ = { 407 | isa = PBXVariantGroup; 408 | children = ( 409 | FD9E1DA0218FD41D009E05A9 /* Base */, 410 | ); 411 | name = Main.storyboard; 412 | sourceTree = ""; 413 | }; 414 | FD9E1DA4218FD41E009E05A9 /* LaunchScreen.storyboard */ = { 415 | isa = PBXVariantGroup; 416 | children = ( 417 | FD9E1DA5218FD41E009E05A9 /* Base */, 418 | ); 419 | name = LaunchScreen.storyboard; 420 | sourceTree = ""; 421 | }; 422 | /* End PBXVariantGroup section */ 423 | 424 | /* Begin XCBuildConfiguration section */ 425 | FD9E1DC0218FD41E009E05A9 /* Debug */ = { 426 | isa = XCBuildConfiguration; 427 | buildSettings = { 428 | ALWAYS_SEARCH_USER_PATHS = NO; 429 | CLANG_ANALYZER_NONNULL = YES; 430 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 431 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 432 | CLANG_CXX_LIBRARY = "libc++"; 433 | CLANG_ENABLE_MODULES = YES; 434 | CLANG_ENABLE_OBJC_ARC = YES; 435 | CLANG_ENABLE_OBJC_WEAK = YES; 436 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 437 | CLANG_WARN_BOOL_CONVERSION = YES; 438 | CLANG_WARN_COMMA = YES; 439 | CLANG_WARN_CONSTANT_CONVERSION = YES; 440 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 441 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 442 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 443 | CLANG_WARN_EMPTY_BODY = YES; 444 | CLANG_WARN_ENUM_CONVERSION = YES; 445 | CLANG_WARN_INFINITE_RECURSION = YES; 446 | CLANG_WARN_INT_CONVERSION = YES; 447 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 448 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 449 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 450 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 451 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 452 | CLANG_WARN_STRICT_PROTOTYPES = YES; 453 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 454 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 455 | CLANG_WARN_UNREACHABLE_CODE = YES; 456 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 457 | CODE_SIGN_IDENTITY = "iPhone Developer"; 458 | COPY_PHASE_STRIP = NO; 459 | DEBUG_INFORMATION_FORMAT = dwarf; 460 | ENABLE_STRICT_OBJC_MSGSEND = YES; 461 | ENABLE_TESTABILITY = YES; 462 | GCC_C_LANGUAGE_STANDARD = gnu11; 463 | GCC_DYNAMIC_NO_PIC = NO; 464 | GCC_NO_COMMON_BLOCKS = YES; 465 | GCC_OPTIMIZATION_LEVEL = 0; 466 | GCC_PREPROCESSOR_DEFINITIONS = ( 467 | "DEBUG=1", 468 | "$(inherited)", 469 | ); 470 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 471 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 472 | GCC_WARN_UNDECLARED_SELECTOR = YES; 473 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 474 | GCC_WARN_UNUSED_FUNCTION = YES; 475 | GCC_WARN_UNUSED_VARIABLE = YES; 476 | IPHONEOS_DEPLOYMENT_TARGET = 11.4; 477 | MTL_ENABLE_DEBUG_INFO = YES; 478 | ONLY_ACTIVE_ARCH = YES; 479 | SDKROOT = iphoneos; 480 | }; 481 | name = Debug; 482 | }; 483 | FD9E1DC1218FD41E009E05A9 /* Release */ = { 484 | isa = XCBuildConfiguration; 485 | buildSettings = { 486 | ALWAYS_SEARCH_USER_PATHS = NO; 487 | CLANG_ANALYZER_NONNULL = YES; 488 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 489 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 490 | CLANG_CXX_LIBRARY = "libc++"; 491 | CLANG_ENABLE_MODULES = YES; 492 | CLANG_ENABLE_OBJC_ARC = YES; 493 | CLANG_ENABLE_OBJC_WEAK = YES; 494 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 495 | CLANG_WARN_BOOL_CONVERSION = YES; 496 | CLANG_WARN_COMMA = YES; 497 | CLANG_WARN_CONSTANT_CONVERSION = YES; 498 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 499 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 500 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 501 | CLANG_WARN_EMPTY_BODY = YES; 502 | CLANG_WARN_ENUM_CONVERSION = YES; 503 | CLANG_WARN_INFINITE_RECURSION = YES; 504 | CLANG_WARN_INT_CONVERSION = YES; 505 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 506 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 507 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 508 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 509 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 510 | CLANG_WARN_STRICT_PROTOTYPES = YES; 511 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 512 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 513 | CLANG_WARN_UNREACHABLE_CODE = YES; 514 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 515 | CODE_SIGN_IDENTITY = "iPhone Developer"; 516 | COPY_PHASE_STRIP = NO; 517 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 518 | ENABLE_NS_ASSERTIONS = NO; 519 | ENABLE_STRICT_OBJC_MSGSEND = YES; 520 | GCC_C_LANGUAGE_STANDARD = gnu11; 521 | GCC_NO_COMMON_BLOCKS = YES; 522 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 523 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 524 | GCC_WARN_UNDECLARED_SELECTOR = YES; 525 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 526 | GCC_WARN_UNUSED_FUNCTION = YES; 527 | GCC_WARN_UNUSED_VARIABLE = YES; 528 | IPHONEOS_DEPLOYMENT_TARGET = 11.4; 529 | MTL_ENABLE_DEBUG_INFO = NO; 530 | SDKROOT = iphoneos; 531 | VALIDATE_PRODUCT = YES; 532 | }; 533 | name = Release; 534 | }; 535 | FD9E1DC3218FD41E009E05A9 /* Debug */ = { 536 | isa = XCBuildConfiguration; 537 | buildSettings = { 538 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 539 | CODE_SIGN_STYLE = Automatic; 540 | DEVELOPMENT_TEAM = DZK6T3AW3W; 541 | INFOPLIST_FILE = QFMoudle/Info.plist; 542 | LD_RUNPATH_SEARCH_PATHS = ( 543 | "$(inherited)", 544 | "@executable_path/Frameworks", 545 | ); 546 | PRODUCT_BUNDLE_IDENTIFIER = com.qingfengiOS.www.QFMoudle; 547 | PRODUCT_NAME = "$(TARGET_NAME)"; 548 | TARGETED_DEVICE_FAMILY = "1,2"; 549 | }; 550 | name = Debug; 551 | }; 552 | FD9E1DC4218FD41E009E05A9 /* Release */ = { 553 | isa = XCBuildConfiguration; 554 | buildSettings = { 555 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 556 | CODE_SIGN_STYLE = Automatic; 557 | DEVELOPMENT_TEAM = DZK6T3AW3W; 558 | INFOPLIST_FILE = QFMoudle/Info.plist; 559 | LD_RUNPATH_SEARCH_PATHS = ( 560 | "$(inherited)", 561 | "@executable_path/Frameworks", 562 | ); 563 | PRODUCT_BUNDLE_IDENTIFIER = com.qingfengiOS.www.QFMoudle; 564 | PRODUCT_NAME = "$(TARGET_NAME)"; 565 | TARGETED_DEVICE_FAMILY = "1,2"; 566 | }; 567 | name = Release; 568 | }; 569 | FD9E1DC6218FD41E009E05A9 /* Debug */ = { 570 | isa = XCBuildConfiguration; 571 | buildSettings = { 572 | BUNDLE_LOADER = "$(TEST_HOST)"; 573 | CODE_SIGN_STYLE = Automatic; 574 | DEVELOPMENT_TEAM = DZK6T3AW3W; 575 | INFOPLIST_FILE = QFMoudleTests/Info.plist; 576 | LD_RUNPATH_SEARCH_PATHS = ( 577 | "$(inherited)", 578 | "@executable_path/Frameworks", 579 | "@loader_path/Frameworks", 580 | ); 581 | PRODUCT_BUNDLE_IDENTIFIER = com.shanglv51.www.QFMoudleTests; 582 | PRODUCT_NAME = "$(TARGET_NAME)"; 583 | TARGETED_DEVICE_FAMILY = "1,2"; 584 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/QFMoudle.app/QFMoudle"; 585 | }; 586 | name = Debug; 587 | }; 588 | FD9E1DC7218FD41E009E05A9 /* Release */ = { 589 | isa = XCBuildConfiguration; 590 | buildSettings = { 591 | BUNDLE_LOADER = "$(TEST_HOST)"; 592 | CODE_SIGN_STYLE = Automatic; 593 | DEVELOPMENT_TEAM = DZK6T3AW3W; 594 | INFOPLIST_FILE = QFMoudleTests/Info.plist; 595 | LD_RUNPATH_SEARCH_PATHS = ( 596 | "$(inherited)", 597 | "@executable_path/Frameworks", 598 | "@loader_path/Frameworks", 599 | ); 600 | PRODUCT_BUNDLE_IDENTIFIER = com.shanglv51.www.QFMoudleTests; 601 | PRODUCT_NAME = "$(TARGET_NAME)"; 602 | TARGETED_DEVICE_FAMILY = "1,2"; 603 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/QFMoudle.app/QFMoudle"; 604 | }; 605 | name = Release; 606 | }; 607 | FD9E1DC9218FD41E009E05A9 /* Debug */ = { 608 | isa = XCBuildConfiguration; 609 | buildSettings = { 610 | CODE_SIGN_STYLE = Automatic; 611 | DEVELOPMENT_TEAM = DZK6T3AW3W; 612 | INFOPLIST_FILE = QFMoudleUITests/Info.plist; 613 | LD_RUNPATH_SEARCH_PATHS = ( 614 | "$(inherited)", 615 | "@executable_path/Frameworks", 616 | "@loader_path/Frameworks", 617 | ); 618 | PRODUCT_BUNDLE_IDENTIFIER = com.shanglv51.www.QFMoudleUITests; 619 | PRODUCT_NAME = "$(TARGET_NAME)"; 620 | TARGETED_DEVICE_FAMILY = "1,2"; 621 | TEST_TARGET_NAME = QFMoudle; 622 | }; 623 | name = Debug; 624 | }; 625 | FD9E1DCA218FD41E009E05A9 /* Release */ = { 626 | isa = XCBuildConfiguration; 627 | buildSettings = { 628 | CODE_SIGN_STYLE = Automatic; 629 | DEVELOPMENT_TEAM = DZK6T3AW3W; 630 | INFOPLIST_FILE = QFMoudleUITests/Info.plist; 631 | LD_RUNPATH_SEARCH_PATHS = ( 632 | "$(inherited)", 633 | "@executable_path/Frameworks", 634 | "@loader_path/Frameworks", 635 | ); 636 | PRODUCT_BUNDLE_IDENTIFIER = com.shanglv51.www.QFMoudleUITests; 637 | PRODUCT_NAME = "$(TARGET_NAME)"; 638 | TARGETED_DEVICE_FAMILY = "1,2"; 639 | TEST_TARGET_NAME = QFMoudle; 640 | }; 641 | name = Release; 642 | }; 643 | /* End XCBuildConfiguration section */ 644 | 645 | /* Begin XCConfigurationList section */ 646 | FD9E1D91218FD41C009E05A9 /* Build configuration list for PBXProject "QFMoudle" */ = { 647 | isa = XCConfigurationList; 648 | buildConfigurations = ( 649 | FD9E1DC0218FD41E009E05A9 /* Debug */, 650 | FD9E1DC1218FD41E009E05A9 /* Release */, 651 | ); 652 | defaultConfigurationIsVisible = 0; 653 | defaultConfigurationName = Release; 654 | }; 655 | FD9E1DC2218FD41E009E05A9 /* Build configuration list for PBXNativeTarget "QFMoudle" */ = { 656 | isa = XCConfigurationList; 657 | buildConfigurations = ( 658 | FD9E1DC3218FD41E009E05A9 /* Debug */, 659 | FD9E1DC4218FD41E009E05A9 /* Release */, 660 | ); 661 | defaultConfigurationIsVisible = 0; 662 | defaultConfigurationName = Release; 663 | }; 664 | FD9E1DC5218FD41E009E05A9 /* Build configuration list for PBXNativeTarget "QFMoudleTests" */ = { 665 | isa = XCConfigurationList; 666 | buildConfigurations = ( 667 | FD9E1DC6218FD41E009E05A9 /* Debug */, 668 | FD9E1DC7218FD41E009E05A9 /* Release */, 669 | ); 670 | defaultConfigurationIsVisible = 0; 671 | defaultConfigurationName = Release; 672 | }; 673 | FD9E1DC8218FD41E009E05A9 /* Build configuration list for PBXNativeTarget "QFMoudleUITests" */ = { 674 | isa = XCConfigurationList; 675 | buildConfigurations = ( 676 | FD9E1DC9218FD41E009E05A9 /* Debug */, 677 | FD9E1DCA218FD41E009E05A9 /* Release */, 678 | ); 679 | defaultConfigurationIsVisible = 0; 680 | defaultConfigurationName = Release; 681 | }; 682 | /* End XCConfigurationList section */ 683 | }; 684 | rootObject = FD9E1D8E218FD41C009E05A9 /* Project object */; 685 | } 686 | --------------------------------------------------------------------------------