1.修改storyboard启动的主页为代码实现主页启动。
2.实现SDK对外接口。Development Pods下的TestSDK是我们SDK代码存放的地方。
3.写SDK的测试代码。Example for TestSDK文件夹下是我们的测试工程的测试页面和代码存放的家。
4.测试Demo对测试代码文件的复用问题。
因为公司需要持续性向别人提供一套蓝牙锁SDK,按照传统的方式打包静态库真是太痛苦,SDK依赖的一些私有库会有频繁的更新,依赖的第三方库也是错综复杂。我迫切需要找到一种更方便的打包静态库的方式,既能随时更新私有库,也能解决开源库的冲突问题(比如你的SDK包含了AFNetworking,别人项目中本身也含有AFNetworking,就会产生冲),那就是使用cocoapods。这个相对iOS使用动态库简单了很多,不用直接直接拷贝第三到工程,真正做到SDK和使用SDK的工程各用各的工程。
由cocoapods-packager插件通过对引用的三方库进行重命名很好的解决了类库命名冲突的问题。
1.修改storyboard启动的主页为代码实现主页启动。
我很不喜欢storyboard,文件大笨,打开要很久,难以多个人开发一个页面后合并代码。才用storyboard或nid做的页面的app要比纯用代码写的页面的app要大一倍。所以我去掉了主页面的storyboard,改用代码实现页面。
修改方法:
1.删除Main.storyboard文件。
2.在TestSDK-Info.plist文件删除
3.在TSAppDelegate.m的application函数增加如下代码:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { // Override point for customization after application launch. [self loadHomePage]; return YES; } - (void)loadHomePage { TSViewController *homePage = [[TSViewController alloc] init]; self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]]; UINavigationController *nav = [[UINavigationController alloc]initWithRootViewController:homePage]; self.window.rootViewController = nav; [self.window makeKeyAndVisible]; }
TSViewController为我们的测试主页面。
对制作SDK的静态库工程改造完毕,下面从两个方面来增加代码,制作咱们的SDK工程:SDK对外接口及实现和测试SDK的功能。
2.实现SDK对外接口。Development Pods下的TestSDK是我们SDK代码存放的地方。
其实对外开放只有4个文件:
TSBaseEntity.h(m)定义基本返回的Block定义。
TestSDKSingleObject.h(m) 对外接口单例。我们不可能给用户一大堆类让用使用,那样可能泄漏我们公司内部的逻辑及影响信息安全。
当然我们不可能把所有的逻辑都写在TestSDKSingleObject.m文件里。那不是这个文件要有几万甚至十几万行代码吗?怎么维护?我们采用mvvm架构进行解藕,让一个接口对应几个文件的类处理,并过滤机密信息后才返回到对外接口单例接口block中。
看看我改进的代码结构吧!
注意:
1)Development Pods下的TestSDK才是我们SDK代码存放的地方,这个才是我们的菜。
2)Example for TestSDK文件夹下是我们的测试工程的测试页面和代码存放的家。
下面是对应的电脑上的物理路径:
TestSDK下的Classes文件夹对应的是工程中Development Pods下的TestSDK夹,是我们SDK代码存放的地方,这个才是我们的菜。
下面来些实际的代码:
基础实体类:
TSBaseEntity.h
#import <Foundation/Foundation.h> NS_ASSUME_NONNULL_BEGIN //判断数组是否为空 #define isTSEmptyArray(array) (array == nil || [array isKindOfClass:[NSNull class]] || ![array isKindOfClass:[NSArray class]] || (array.count == 0)) typedef void(^TSSuccessBlock)(id result); typedef void(^TSFailureBlock)(NSError * _Nullable error); @interface TSBaseEntity : NSObject @property (nonatomic, assign) BOOL showHUD; @end NS_ASSUME_NONNULL_END
TSBaseEntity.m这个基础实体类的.m没有什么逻辑
#import "TSBaseEntity.h" @implementation TSBaseEntity @end
核心对外接口文件:
TestSDKSingleObject.h
#import <Foundation/Foundation.h> #import "TSBaseEntity.h" NS_ASSUME_NONNULL_BEGIN @interface TestSDKSingleObject : NSObject + (TestSDKSingleObject *)sharedInstance; -(void)getLockListWithSuccessBlock:(TSSuccessBlock)successBlock failureBlock:(TSFailureBlock)failureBlock; -(void)getLockListWithUserId:(NSString *)userId token:(NSString *)token; @end NS_ASSUME_NONNULL_END
核心接口文件的实现,这个文件可不对外开放,是会编译到到SDK的二机制包中:
#import "TestSDKSingleObject.h" #import "TSLockListModel.h" #import "ReactiveObjC.h" #import "TSLoginModel.h" #import "TSAccountTool.h" @interface TestSDKSingleObject () @property (nonatomic, strong) TSLockListModel *lockListModel; @property (nonatomic, strong) TSLoginModel *loginModel; @property (nonatomic, strong) NSMutableArray *lockList; @end @implementation TestSDKSingleObject + (TestSDKSingleObject *)sharedInstance { static TestSDKSingleObject *sharedInstace = nil; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ sharedInstace = [[self alloc] init]; }); return sharedInstace; } - (instancetype)init { if (self = [super init]) { //设置网络初始配置 [self configRequest]; [self addBlock]; } return self; } -(void)addBlock { } - (void)configRequest { } -(TSLockListModel *)lockListModel{ if (!_lockListModel) { _lockListModel = [[TSLockListModel alloc]init]; } return _lockListModel; } -(TSLoginModel *)loginModel{ if (!_loginModel) { _loginModel = [[TSLoginModel alloc]init]; } return _loginModel; } -(NSMutableArray *)lockList{ if (!_lockList) { _lockList = [NSMutableArray array]; } return _lockList; } -(void)getLockListWithUserId:(NSString *)userId token:(NSString *)token { [self.lockListModel getLockListWithUserId:userId token:token]; } -(void)getLockListWithSuccessBlock:(TSSuccessBlock)successBlock failureBlock:(TSFailureBlock)failureBlock { [self.lockListModel getLockListWithUserId:[TSAccountTool sharedAccountTool].account.userId showHUD:NO successBlock:^(id result) { NSLog(@"TestSDKSingleObject result:%@", result); NSMutableArray *data = result; if(!isTSEmptyArray(data)) { self.lockList = data; } else { self.lockList = [NSMutableArray array]; } if(successBlock) { successBlock(self.lockList); } } failureBlock:^(NSError * _Nullable error) { NSLog(@"TestSDKSingleObject error:%@", error.domain); if(failureBlock) { failureBlock(error); } }]; } @end
注意:要在把lockListModel定义为强引用,不能定义为函数内局部变量,不然通过网络请求组件发送了getLockListWithSuccessBlock的请求后会产生内存对象被释放而崩溃的问题。
至于TSLockListEntity.h(m),TSLockListModel.h(m),TSLockListViewModel.h(m)等我就不详细贴代码介绍了,大家下载代码一看就懂。TSAccountModel.h(m),TSAccountTool.h(m)是用户信息序列化和反序列化处理类及实体类。大家看下对你可能有用。
3.写SDK的测试代码。Example for TestSDK文件夹下是我们的测试工程的测试页面和代码存放的家。
Example下的TestSDK文件夹对应工程中Example for TestSDK文件夹,是我们的测试工程的测试页面和代码存放的地方。
测试SDK的功能代码也很简单:
基本测试单元格,TSCollectionViewCell.h文件
#import <UIKit/UIKit.h> @interface TSCollectionViewCell : UICollectionViewCell @property (nonatomic, strong) UILabel *nameLabel; @property (nonatomic, strong) NSString *nameStr; @end
基本测试单元格,TSCollectionViewCell.m文件
#import "TSCollectionViewCell.h" @implementation TSCollectionViewCell -(instancetype)initWithFrame:(CGRect)frame{ self = [super initWithFrame:frame]; if (self) { [self setupView]; } return self; } -(void)setupView{ self.nameLabel = [[UILabel alloc]initWithFrame:CGRectMake(0, 0,CGRectGetWidth(self.frame), CGRectGetWidth(self.frame))]; self.nameLabel.backgroundColor = [UIColor clearColor]; self.nameLabel.textColor = [UIColor redColor]; self.nameLabel.textAlignment = NSTextAlignmentCenter; self.nameLabel.font = [UIFont systemFontOfSize:15]; [self addSubview:self.nameLabel]; } -(void)setNameStr:(NSString *)nameStr{ self.nameLabel.text = nameStr; } @end
核心测试文件也是主页TSViewController.m,测试按钮采用流式布局
#import "TSViewController.h" #import "ReactiveObjC.h" #import "TSCollectionViewCell.h" #import "TestSDKSingleObject.h" // 屏幕宽度 #define TSFullWidth [[UIScreen mainScreen] bounds].size.width // 屏幕高度 #define TSFullHeight [[UIScreen mainScreen] bounds].size.height #define TSIPhoneEar (TSFullWidth == 375 && TSFullHeight == 812 ? true : false) #define TSMainTopHeight (TSIPhoneEar ? 88 : 64) @interface TSViewController ()<UICollectionViewDataSource,UICollectionViewDelegate> @property (nonatomic, strong) UICollectionView *collectionView; @property (nonatomic, strong) NSMutableArray *testUnitList; @property (nonatomic, strong) NSArray *testUnitSelectorList; @end @implementation TSViewController - (void)viewDidLoad { [super viewDidLoad]; // Do any additional setup after loading the view, typically from a nib. self.view.backgroundColor = [UIColor whiteColor]; self.testUnitList = [NSMutableArray arrayWithObjects:@"测试主工程使用第三方库",@"测试调用SDK的函数",@"测试调用SDK的请求接口", nil]; self.testUnitSelectorList = @[@"testRACSignal", @"testGetLockListWithUserId", @"testSDKRACSignal"]; [self configSubViews]; } - (void)configSubViews { //创建布局 UICollectionViewFlowLayout *flowLayout = [[UICollectionViewFlowLayout alloc]init]; //创建CollectionView _collectionView = [[UICollectionView alloc]initWithFrame:CGRectMake(0, TSMainTopHeight-64 , [[UIScreen mainScreen] bounds].size.width, [[UIScreen mainScreen] bounds].size.height - TSMainTopHeight+64) collectionViewLayout:flowLayout]; _collectionView.dataSource = self; _collectionView.delegate = self; _collectionView.backgroundColor = [UIColor whiteColor]; [self.view addSubview:_collectionView]; [_collectionView registerClass:[TSCollectionViewCell class] forCellWithReuseIdentifier:@"TSCollectionViewCell"]; //定义每个UICollectionView 的大小 flowLayout.itemSize = CGSizeMake(([[UIScreen mainScreen] bounds].size.width - 20 ) / 2 , ([[UIScreen mainScreen] bounds].size.width - 20) / 2); //定义每个UICollectionView 横向的间距 flowLayout.minimumLineSpacing = 5; //定义每个UICollectionView 纵向的间距 flowLayout.minimumInteritemSpacing = 5; //定义每个UICollectionView 的边距距 flowLayout.sectionInset = UIEdgeInsetsMake(5, 5, 5, 5);//上左下右 } - (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath{ NSString *title = @""; if (indexPath.item < self.testUnitList.count) { title = [self.testUnitList objectAtIndex:indexPath.item]; } TSCollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:@"TSCollectionViewCell" forIndexPath:indexPath]; cell.nameStr = title; cell.backgroundColor = [UIColor colorWithRed:241/255.0 green:241/255.0 blue:241/255.0 alpha:1]; return cell; } - (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section{ return self.testUnitList.count; } -(void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath{ NSUInteger idx = indexPath.row; NSString *selectorStr = self.testUnitSelectorList[idx]; SEL testAction = NSSelectorFromString(selectorStr); if ([self respondsToSelector:testAction]) { [self performSelector:testAction withObject:nil afterDelay:0]; } } -(void)testRACSignal { NSLog(@"testRACSignal"); [[RACCommand alloc] initWithSignalBlock:^RACSignal *(id input) { return [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) { return nil; }]; }]; } -(void)testGetLockListWithUserId { [[TestSDKSingleObject sharedInstance] getLockListWithUserId:@"18238124046" token:nil]; } -(void)testSDKRACSignal { [[TestSDKSingleObject sharedInstance] getLockListWithSuccessBlock:^(id result) { NSLog(@"result:%@", result); } failureBlock:^(NSError * _Nullable error) { NSLog(@"error:%@", error); }]; } @end
4.测试Demo对测试代码文件的复用问题。
由于测试代码和SDK的代码在一个工程中,一边开发可以一边测试。当然把这个开发测试工程直接开源出去,那么不泄漏你的SDK源代码了吗?由于该工程直接集成了测试用例,那么在打包SDK后,重新创建测试工程,把测试部分代码拷贝过去,直接使用SDK进行测试就可以。这样也方便,开发速度快。
开发模式研究:
app四种开发模式的优缺点。移动五端合一说了如何让移动五端合一。
如何访问组件的bundle资源。
创建使用SDK静态库具体方案:
创建制作SDK的静态库工程说了如何创建工程,制作SDK静态库已经说了SDK如何写,打包SDK静态库说了如何打包iOS使用SDK静态库。
.framework类型的静态库和.a类型的静态库的优缺点及.framework类型的静态库zip压缩后解压后头文件丢失问题。
Mac电脑如何使用WinRAR。
下面是Demo:
iOS使用SDK静态库Demo.
SDK的Demo。