2020-02-08-howto如何创建使用iOS的swift语言Framework及边改target边调App及使用Aggregate一次性编译真机和模拟器通用Framework包

环境

macOS Catalina Version 10.15 (19A583) Xcode Version 11.x

framework文件

创建framework工程,新建NManager.swift文件,内容:

1
2
3
4
5
6
7
8
9
10
11
12
import UIKit
@objc class NManager: NSObject {
@objc public func sayHello() {
print("hello")
}
public func sayWorld() {
print("world")
}
@objc func saySwift() {
print("swift")
}
}

问题:编译出现 symbol dyld_stub_binder not found (normally in libSystem.dylib).
解决:工程中的 Link Binary With Libraries -> 加上libSystem.tbd

新建swift语言工程调用

add framework文件
主要代码

1
2
3
4
import someFramework
let manager = NManager()
manager.sayHello()
manager.sayWorld()

新建oc语言工程调用

add framework文件添加到工程中,注意选择项选中Copy items if needed
主要代码

1
2
3
#import <someFramework/someFramework-Swift.h>
NManager *manager = [NManager new];
[manager sayHello];

问题:编译出现 image not found
解决:在工程Target中的 General -> Frameworks,Libraries,and Embedded Content中将 Embed 改为 Embed Without Signing 或 Embed & Sign 皆可以

target方式引入Framework

新建项目引入(看上面)
(下面是)target方式引入
我们把原先的添加了的framework先丢掉,左上角 File -> save as workspace
关闭这个.xcodeproj 文件重新打开这个 .xcworkspace
将我们的 Framework 项目 拖到 workspace 中,与 App的target 并行
问题:会遇到拖进来的项目没有被正确的识别出来,也没有多出来一个target
解决:关掉xcode其它已打开的所有工程项目,保持只留下当前一个项目运行,然后再重新拖项目或重启xcode只打开这一个项目,试试,以后运行,也只保持这一个项目运行,具体原因可能和xcode有关
这个时候就会发现,我们就有两个项目了可以分别 build 了
到 App 中 工程文件 -> General ->embedded binaries 中将 Framework 下的 .framework 加进来
试一试,scheme 选择 App, build 一下是success,
我们再来到我们的 XPManager.h ,添加几行代码(当测试)
当然还是先build一下我们的XPKit,否则Demo里的framework不更新呐
可以直接在项目里用。那么边写边测,还能设断点,完美~跟平时写app没两样
当你调用并且运行,发现又报错了
且这次我们已经加到 Embedded Binaries 中了,原因是,如果我们在OC项目中引用swift framework,还需要到 build setting 中设置如下: Always Embed Swift Standard Libraries 确认为 Yes
到此为止才是真正完美 ~
另外: Framework因为真机和模拟器的原因,需要两次编译并lipo -create的方式合并,麻烦
用Shell脚本创建通用包
创建通用包用到的次数不多,上面的方法够用了,但是如果你还是觉得不方便、很繁琐。那你可以跟我这样做
选择XPKit 工程点击左下角 +
创建一个 Aggregate 。去个名字,类似 CommonBuilder
image.png
选中 CommonBuilder -> Build Phases -> 添加New Run Script Phase
在编辑器内输入我们的脚本代码,请全部复制,黏贴,记得修改第二步引号内的内容为你的framework name

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
27
28
29
30
31
32
33
34
35
36
37
38
39
# Merge Script
# 1
# Set bash script to exit immediately if any commands fail.
set -e
# 2
# Setup some constants for use later on.
FRAMEWORK_NAME="Your framework name"
# 3
# If remnants from a previous build exist, delete them.
if [ -d "${SRCROOT}/build" ]; then
rm -rf "${SRCROOT}/build"
fi
# 4
# Build the framework for device and for simulator (using
# all needed architectures).
xcodebuild -target "${FRAMEWORK_NAME}" -configuration Release -arch arm64 -arch armv7 -arch armv7s only_active_arch=no defines_module=yes -sdk "iphoneos"
xcodebuild -target "${FRAMEWORK_NAME}" -configuration Release -arch x86_64 -arch i386 only_active_arch=no defines_module=yes -sdk "iphonesimulator"
# 5
# Remove .framework file if exists on Desktop from previous run.
if [ -d "${HOME}/Desktop/${FRAMEWORK_NAME}.framework" ]; then
rm -rf "${HOME}/Desktop/${FRAMEWORK_NAME}.framework"
fi
# 6
# Copy the device version of framework to Desktop.
cp -r "${SRCROOT}/build/Release-iphoneos/${FRAMEWORK_NAME}.framework" "${HOME}/Desktop/${FRAMEWORK_NAME}.framework"
# 7
# Replace the framework executable within the framework with
# a new version created by merging the device and simulator
# frameworks' executables with lipo.
lipo -create -output "${HOME}/Desktop/${FRAMEWORK_NAME}.framework/${FRAMEWORK_NAME}" "${SRCROOT}/build/Release-iphoneos/${FRAMEWORK_NAME}.framework/${FRAMEWORK_NAME}" "${SRCROOT}/build/Release-iphonesimulator/${FRAMEWORK_NAME}.framework/${FRAMEWORK_NAME}"
# 8
# Copy the Swift module mappings for the simulator into the
# framework. The device mappings already exist from step 6.
cp -r "${SRCROOT}/build/Release-iphonesimulator/${FRAMEWORK_NAME}.framework/Modules/${FRAMEWORK_NAME}.swiftmodule/" "${HOME}/Desktop/${FRAMEWORK_NAME}.framework/Modules/${FRAMEWORK_NAME}.swiftmodule"
# 9
# Delete the most recent build.
if [ -d "${SRCROOT}/build" ]; then
rm -rf "${SRCROOT}/build"
fi

如图:
scheme 选择 CommonBuilder,任意模拟器,编译,报错了看看报了什么错
Command /bin/sh failed with exit code 65
你们以后看到这些不用慌,网上看,信息都在上面
=== BUILD TARGET XPKit OF PROJECT XPKit WITH CONFIGURATION Release ===
Check dependencies
No architectures to compile for (ARCHS=x86_64 i386, VALID_ARCHS=arm64 armv7 armv7s).
BUILD FAILED
分析一下,这里都是我们提到过的指令集。 VALID_ARCHS=arm64 armv7 armv7s 这就是我们开始在 Build Setting ->Valid Architectures 中设置的内容,很明显,意思是脚本里,要制作包含 x86_64 和 i386的包,但是我们的Valid Architectures 中没有。
那么解决问题就方便了,分别添加x86_64 和 i386
出错: iOS 13 does not support 32-bit programs
解决:把编译选项里的 Build Active Architecture Only 设为 No, 同时将 Valid Architectures 只设为 arm64,arm64e,x86_64,将arm7,arm7s删掉,同时将上面的编译脚本中的arm7,arm7s也要删掉
编译成功~
来到桌面我们发现XPKit.framework,已经静悄悄的在桌面上了,这就是我们的通用包

参考:

https://www.jianshu.com/p/1ad5bede88bd

2020-01-03-howto地理文件gpx文件修改

howto地理文件gpx文件修改

基于KissXML对gpx文件进行修改

参考:

https://www.jianshu.com/p/4f7390cf710f
https://github.com/robbiehanson/KissXML
GPX(GPS eXchange Format,GPS交换格式)是一个XML格式,为应用软件设计的通用GPS数据格式,专门用来存储地理信息。
一个GPX文件当中可能包含一些路点(waypoint)及一些轨迹点(trackpoint)。 以全球定位系统(GPS设备)所产生的GPX档为例, 路点可能是各自独立互不相干的重要标记点, 例如照相的地点或用户手动标记的休息站或路口等等;
至于GPS设备自动定时记录的则是轨迹点。 有顺序的一串轨迹点构成一个轨迹(track)或者路程(route)。轨迹是一个人曾经走过的记录,可能包含走错的路等等;路程则经常是建议未来用路人可以走的路径。
所以,一般来讲,轨迹里的点,包含时间信息,路程里的点,则没有时间信息。

代码:

usage
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
打包 KissXML.framework 并加入工程
#import <KissXML/KissXML.h>
#import "YTRoutePoint.h"
主要代码
route_app_id是gpx文件的文件名
NSArray *a1rr = [self readGPXFileToPoints:[NSString stringWithFormat:@"%@/IDmoniLocation.gpx", NSHomeDirectory()] route_app_id:@"IDmoniLocation"];
NSMutableArray *a2rr = [NSMutableArray new];
NSLog(@"%@", a1rr);
for (YTRoutePoint *po1nt in a1rr) {
YTRoutePoint *po2nt = [YTRoutePoint new];
po2nt.lon = 1 + po1nt.lon;
po2nt.lat = 1 + po1nt.lat;
[a2rr addObject:po2nt];
}
[self writeXmlDocumentCallbackPath:@"IDmoniLocation" with:a2rr];
read write API函数
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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
- (NSArray *)readGPXFileToPoints:(NSString *)filePath route_app_id:(NSString *)route_app_id
{
//转成XML字符串
NSString *xmlString = [NSString stringWithContentsOfFile:filePath encoding:NSUTF8StringEncoding error:nil];
DDXMLDocument *xmlDoc = [[DDXMLDocument alloc]initWithXMLString:xmlString options:0 error:nil];
//开始解析
NSArray *children = [xmlDoc nodesForXPath:@"gpx/wpt" error:nil];
NSMutableArray *points = [NSMutableArray arrayWithCapacity:children.count];
//遍历每个元素
YTRoutePoint *prevPoint = nil;
for (DDXMLElement *child in children) {
@autoreleasepool {
NSString *lat =[[child attributeForName:@"lat"] stringValue];
NSString *lon =[[child attributeForName:@"lon"] stringValue];
DDXMLElement *speedEle = [child elementForName:@"speed"];
DDXMLElement *timeEle = [child elementForName:@"time"];
DDXMLElement *distacneEle = [child elementForName:@"distacne"];
DDXMLElement *levelEle = [child elementForName:@"level"];
DDXMLElement *slopeEle = [child elementForName:@"slope"];
YTRoutePoint *point = [[YTRoutePoint alloc]init];
point.route_app_id = route_app_id;
point.lon = [lon doubleValue];
point.lat = [lat doubleValue];
point.speed = [[speedEle stringValue] doubleValue];
point.altude = [[levelEle stringValue] doubleValue];
point.distance = [[distacneEle stringValue] doubleValue];
point.slope = [[slopeEle stringValue] doubleValue];
point.create_time = [[timeEle stringValue] integerValue];
//去重
if(!prevPoint) {
[points addObject:point];
prevPoint = point;
}
else {
if(prevPoint.lat == point.lat && prevPoint.lon == point.lon) {
continue;
}
[points addObject:point];
prevPoint = point;
}
}
}
return [NSArray arrayWithArray:points];
}
- (NSString *)writeXmlDocumentCallbackPath:(NSString *)route_app_id with:(NSArray *)trackRoute
{
//gpx(root根)
DDXMLElement *gpxElement = [[DDXMLElement alloc]initWithName:@"gpx"];
// DDXMLNode *gpxUserID = [DDXMLNode attributeWithName:@"userid" stringValue:[[DataManagerObject shareDataManagerObject] getWildtoID]];
// [gpxElement addAttribute:gpxUserID];
// DDXMLNode *gpxUserName = [DDXMLNode attributeWithName:@"nickname" stringValue:[[DataManagerObject shareDataManagerObject] getUserNickName]];
// [gpxElement addAttribute:gpxUserName];
// DDXMLNode *gpxDevice = [DDXMLNode attributeWithName:@"device" stringValue:[UIDevice machineModelName]];
// [gpxElement addAttribute:gpxDevice];
// DDXMLNode *gpxVersion = [DDXMLNode attributeWithName:@"appversion" stringValue:YTAPPVersion];
// [gpxElement addAttribute:gpxVersion];
// DDXMLNode *gpxChannel = [DDXMLNode attributeWithName:@"channel" stringValue:@"wildto"];
// [gpxElement addAttribute:gpxChannel];
// DDXMLNode *gpxClient = [DDXMLNode attributeWithName:@"client" stringValue:YT_IOS_DEVICE];
// [gpxElement addAttribute:gpxClient];
NSArray *points = trackRoute;
for (NSInteger i = 0; i < points.count; i ++) {
@autoreleasepool {
YTRoutePoint *point = points[i];
//wpt元素(包含经纬度两个属性)
DDXMLElement *wptElement = [[DDXMLElement alloc]initWithName:@"wpt"];
DDXMLNode *wptLonAtt = [DDXMLNode attributeWithName:@"lon" stringValue:[NSString stringWithFormat:@"%f",point.lon]];
[wptElement addAttribute:wptLonAtt];
DDXMLNode *wptLatAtt = [DDXMLNode attributeWithName:@"lat" stringValue:[NSString stringWithFormat:@"%f",point.lat]];
[wptElement addAttribute:wptLatAtt];
//速度
DDXMLElement *speedElement = [[DDXMLElement alloc]initWithName:@"speed" stringValue:[NSString stringWithFormat:@"%f",point.speed]];
[wptElement addChild:speedElement];
//海拔
DDXMLElement *altudeElement = [[DDXMLElement alloc]initWithName:@"level" stringValue:[NSString stringWithFormat:@"%f",point.altude]];
[wptElement addChild:altudeElement];
//时间点(时间戳)
DDXMLElement *creatTimeElement = [[DDXMLElement alloc]initWithName:@"time" stringValue:[NSString stringWithFormat:@"%ld",(long)point.create_time]];
[wptElement addChild:creatTimeElement];
//坡度
DDXMLElement *slopeElement = [[DDXMLElement alloc]initWithName:@"slope" stringValue:[NSString stringWithFormat:@"%f",point.slope]];
[wptElement addChild:slopeElement];
//当前点经过的总距离
DDXMLElement *distacneElement = [[DDXMLElement alloc]initWithName:@"distacne" stringValue:[NSString stringWithFormat:@"%.1f",point.distance]];
[wptElement addChild:distacneElement];
[gpxElement addChild:wptElement];
}
}
NSString *xmlString = [@"<?xml version=\"1.0\"?>" stringByAppendingString:[gpxElement XMLString]];
NSLog(@"xmlString %@",xmlString);
DDXMLDocument *xmlDocument = [[DDXMLDocument alloc]initWithXMLString:xmlString options:0 error:nil];
NSString *path = [NSString stringWithFormat:@"%@/%@.gpx", NSHomeDirectory(), route_app_id];
NSLog(@"路劲 == %@",path);
NSString *result = [NSString stringWithFormat:@"%@",xmlDocument];
BOOL writed = [result writeToFile:path atomically:YES encoding:NSUTF8StringEncoding error:nil];
if (writed) {
return path;
}
return nil;
}
YTRoutePoint.h
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#import <Foundation/Foundation.h>
#import <CoreLocation/CLLocation.h>
#import <CoreLocation/CLPlacemark.h>
NS_ASSUME_NONNULL_BEGIN
@interface YTRoutePoint : NSObject
@property (nonatomic, strong) NSString *route_app_id;
@property (nonatomic, assign) double lon;
@property (nonatomic, assign) double lat;
@property (nonatomic, assign) double speed;
@property (nonatomic, assign) double altude;
@property (nonatomic, assign) double distance;
@property (nonatomic, assign) double slope;
@property (nonatomic, assign) NSInteger create_time;
@end
NS_ASSUME_NONNULL_END
YTRoutePoint.m
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#import "YTRoutePoint.h"
@implementation YTRoutePoint
- (instancetype)initWithDictionary:(NSDictionary *)dict {
if (self = [super init]) {
_route_app_id = [dict objectForKey:@"route_app_id"];
_lon = [[dict objectForKey:@"lon"] doubleValue];
_lat = [[dict objectForKey:@"lat"] doubleValue];
_speed = [[dict objectForKey:@"speed"] doubleValue];
_altude = [[dict objectForKey:@"altude"] doubleValue];
_distance = [[dict objectForKey:@"distance"] doubleValue];
_slope = [[dict objectForKey:@"slope"] doubleValue];
_create_time = [[dict objectForKey:@"create_time"] integerValue];
return self;
} else
return nil;
}
@end

2019-07-19-如何使用fastlane实现iOS的(CI)持续集成(CD)持续交付(CD)持续部署

把开发工作流程分为几个阶段:编码 -> 构建 -> 集成 -> 测试 -> 交付 -> 部署

持续集成(Continuous Integration)」、「持续交付(Continuous Delivery)」和「持续部署(Continuous Deployment)」

  • 持续集成(Continuous Integration)
    持续集成是指软件个人研发的部分向软件整体部分交付,频繁进行集成以便更快地发现其中的错误。
    CI 需要具备这些:全面的自动化测试。
    “快速失败”,在对产品没有风险的情况下进行测试,并快速响应;最大限度地减少风险,降低修复错误代码的成本;将重复性的手工流程自动化,让工程师更加专注于代码;保持频繁部署,快速生成可部署的软件;提高项目的能见度,方便团队成员了解项目的进度和成熟度;增强开发人员对软件产品的信心,帮助建立更好的工程师文化。
  • 持续交付(Continuous Delivery)
    持续交付在持续集成的基础上,将集成后的代码部署到更贴近真实运行环境的「类生产环境」(production-like environments)中。持续交付优先于整个产品生命周期的软件部署,建立在高水平自动化持续集成之上。 快速发布。能够应对业务需求,并更快地实现软件价值。编码->测试->上线->交付的频繁迭代周期缩短,同时获得迅速反馈;高质量的软件发布标准。整个交付过程标准化、可重复、可靠,整个交付过程进度可视化,方便团队人员了解项目成熟度;更先进的团队协作方式。从需求分析、产品的用户体验到交互 设计、开发、测试、运维等角色密切协作,相比于传统的瀑布式软件团队,更少浪费。
    持续交付和持续集成的优点非常相似:
  • 持续部署(Continuous Deployment)
    持续部署是指当交付的代码通过评审之后,自动部署到生产环境中。持续部署是持续交付的最高阶段。这意味着,所有通过了一系列的自动化测试的改动都将自动部署到生产环境。它也可以被称为“Continuous Release”。
    持续部署主要好处是,可以相对独立地部署新的功能,并能快速地收集真实用户的反馈。“You build it, you run it”,这是 Amazon 一年可以完成 5000 万次部署,平均每个工程师每天部署超过 50 次的核心秘籍。
  • 综上:「持续集成(Continuous Integration)」、「持续交付(Continuous Delivery)」和「持续部署(Continuous Deployment)」提供了一个优秀的 DevOps 环境,对于整个团队来说,好处与挑战并行。无论如何,频繁部署、快速交付以及开发测试流程自动化都将成为未来软件工程的重要组成部分。

使用 fastlane 实现 iOS 的 CI 和 CD

fastlane 简介

fastlane是一个通过简单命令来完成诸如截图、获取证书、编译、导出安装包、提交iTunesConnect等一系列操作的工具,它同时支持iOS和Android。
你能够通过简单的方式配置流程进行的顺序,并通过非常简单的命令执行其中的一个流程。当然它的简单并不代表功能也简陋,有开源社区的支持,更新迅速且有很多功能能够满足你的需求。

fastlane 环境配置

fastlane使用的是ruby环境且对ruby有版本要求(官网要求是ruby2.0以上),所以如果需要的话更新一波ruby,然后通过gem安装fastlane。
(未确认更新ruby是否需要rvm:更新ruby 使用的是RVM工具,在命令行进行如下操作,安装时可能出现进度不动,多半可能是因为被墙了。)

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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
# (未安装Xcode时执行) Install the latest Xcode command line tools:
# xcode-select --install
# Using RubyGems
sudo gem install fastlane -NV
# (未安装brew时执行) 安装 Homebrew
# /usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"
# Alternatively using Homebrew
brew cask install fastlane
# 如果提示没有detect到 .bash_profile 文件,就手动建立,并根据提示添加
# export PATH="$HOME/.fastlane/bin:$PATH" 然后 source .bash_profile 文件
更新fastlane执行
fastlane update-fastlane
# Navigate your terminal to your project's directory and run
fastlane init
提示有4种选项
1Automate screenshots
2Automate beta distribution to TestFlight
3Automate App Store distribution
4Manual setup - manually setup your project to automate your tasks
然后输入数字2或4 "Manual setup - manually setup your project to automate your tasks"
提示
"An error occurred while installing unf_ext (0.0.7.6), and Bundler cannot continue."
"Make sure that `gem install unf_ext -v '0.0.7.6'` succeeds before bundling"
那就跟着提示,执行
sudo gem install unf_ext -v '0.0.7.6' (需要sudo权限)
再执行了一次
fastlane init
提示fastlane init failed. Unable to locate Xcode. Please make sure to have Xcode installed on your machine
执行
xcode-select --install
已安装
fastlane init
报错可能在Xcode中没有设置“Command Line Tools”:打开Xcode偏好设置,选择"Location"选项卡,选择相应的“Command Line Tools”即可。
再执行了一次
fastlane init
再提示有4种选项
接着提示操作来完成初始化
(详细操作见下面举例分类)
完成后,可以试下
fastlane tests 测试一下根据提示再操作

try your new fastlane setup ,just enter
#fastlane custom_lane

enable fastlane to handle automatic version incrementing Automating Version and Build Numbers Using agvtool
https://developer.apple.com/library/archive/qa/qa1827/_index.html

fastlane how to setup automatic build increments - fastlane docs
https://docs.fastlane.tools/getting-started/ios/beta-deployment/#best-practices

安装完成后,会提示查如下文档

初始化完成后可以看到
项目会多出来一个 Gemfile 文件,(如果没有此文件,需要手动创建此文件),内有内容:

1
2
source "https://rubygems.org"
gem "fastlane"

项目目录里多出了一个 fastlane 文件夹,内有2-4个比较重要的文件,其中有 Appfile 和 Fastfile 等。

fastlane 使用

Automatic iOS Beta deployment 2Automate beta distribution to TestFlight

执行 fastlane init 后,按2,进入此模式2Automate beta distribution to TestFlight
根据提示输入账号密码及二次验证,等各种必须的资料信息
然后查看 https://docs.fastlane.tools/getting-started/ios/beta-deployment/ 文档中的代码片断
修改Fastfile文件内容

1
2
3
4
5
6
7
8
9
10
default_plantform(:ios)
platform :ios do
desc "Description of what the lane does"
lane :beta do
#sync_code_signing(type: "appstore") #see code signing guide for more information #可能会要从git或svn同步代码,不需要自动同步代码的可以注释掉此行
build_app(scheme: "XXXX.target.name")
upload_to_testflight
#slack(message: "Successfully distributed a new beta build")
end
end

然后命令行执行

1
fastlane ios beta

执行出错,提示
Usually it’s caused by the Skip Install option in Xcode, set it to NO
For more information visit https://developer.apple.com/library/ios/technotes/tn2215/_index.html
那就跟着提示改呗,确认下Xcode的Target中的Build Settings里,注意选择All显示全部设置后,查找Skip Install选项是不是都为NO,如果是NO了,那就手动再选择下NO,重选下NO
然后再执行命令 fastlane ios beta
自动执行过程中可能会要手动输入二次验证码,app-spec(application-specific)密码,等等
会有提示
Password (app-spec(application-specific for xxxx开发者账号)
或者
your account has 2 step verification enabled
please go to https://appleid.apple.com/account/manage
and generate an application specific password for
the iTunes Transporter, which is used to upload builds
to set the application specific password on a CI machine using
an environment variable, you can set the
FASTLANE_APPLE_APPLICATION_SPECIFIC_PASSWORD variable
Please provide your Apple Developer Program account credentials
The login information you enter will be stored in your macOS Keychain
解决
输入已生成的app-spec(application-specific)

登录网站https://appleid.apple.com/account/manage
在页面里有生成app专用密码,生成并将此密码输入进当前命令行中
接着再输入

1
fastlane ios beta

命令后接下来,会提示
可能会提示
ERROR ITEM-90189 Redundant Binary Upload. …… Make sure you increment the build string before you upload your app to App Store Connect. ……
这是提示版本号问题,注意Xcode里版本号,不能一样要逐增加,是否建立了版本在appStoreConnect上,等版本号原因,改即可
可能会提示
This might take a few minutes. Please dont interrupt the script.
那就继续等呗
(可能会发现,默认版本号version没变,默认build变了自动加了个1)
等到成功的信息如下
Successfully finished processing the build 1.x - xxxx for IOS
Successfully distributed build to Internal testers
fastlane.tools just saved you 8 minutes!
这时如果有提示Please update using fastlane update_fastlane, 估计是提示升级的,那就照做呗,为下次操作做好准备

1
fastlane update_fastlane

完成!挺顺利的就完成了!挺好的!

Automatic iOS App Store deployment 3Automate App Store distribution

过程可参考2Automate beta distribution to TestFlight的
或参考在此https://docs.fastlane.tools/getting-started/ios/appstore-deployment
只是其中的fastlane文件脚本,需要各别修改,demo如下

1
2
3
4
5
6
7
lane :release do
capture_screenshots # generate new screenshots for the App Store
sync_code_signing(type: "appstore") # see code signing guide for more information
build_app(scheme: "XXXX.target.name")
upload_to_app_store # upload your app to App Store Connect
#slack(message: "Successfully uploaded a new App Store build")
end

Generate localized iOS screenshots for the App Store 1Automate screenshots

过程可参考2Automate beta distribution to TestFlight的
或参考在此https://docs.fastlane.tools/getting-started/ios/screenshots/
只是其中的fastlane文件脚本,需要各别修改,demo如下

1
2
3
4
lane :screenshots do
capture_screenshots
upload_to_app_store
end

其它方式的 CI/CD

使用 GitHub 实现简单的 CI/CD
https://www.cnblogs.com/selimsong/p/9398738.html
使用 gitlab ci/cd + fastlane 在 iOS 上实施 ci/cd
https://www.jianshu.com/p/96449c6bd7d9

使用的一些感受

2Automate beta distribution to TestFlight
本想在相同时间内通过自动化精简一些手动操作
自测结果发现,fastlane运行过程中不知何原因(Apple服务器边?或本地网络?)引起出错外,一般都会节约时间
考虑精简繁锁操作的话,可以试试fastlane
fastlane出错的话,还是要后续手动操作(概率发生)

参考链:

https://docs.fastlane.tools/getting-started/ios/beta-deployment/
https://docs.fastlane.tools/getting-started/ios/appstore-deployment/#building-your-app
https://docs.fastlane.tools/getting-started/ios/screenshots/
https://www.jianshu.com/p/04b83b335d53
https://www.cnblogs.com/lulushen/p/8268330.html
https://www.cnblogs.com/pegasus923/p/8674196.html
https://www.jianshu.com/p/c6c7db6f7c44
https://juejin.im/post/5a7d51986fb9a063435ece35
https://docs.fastlane.tools/getting-started/ios/setup/
https://brew.sh/index_zh-cn
https://www.jianshu.com/p/96449c6bd7d9
https://www.cnblogs.com/selimsong/p/9398738.html

2019-07-18-如何将Markdown格式文件转换成pdf文件

(Mac系统) Markdown 文本编辑工具工具 VSCode 及其插件 Markdown PDF

经常用VSCode,其它工具就不推荐了,安装就完事了
插件 Markdown PDF 在VSCode的Extensions中安装
其它类似工具Markdown文本编辑工具工具有,atom,等等

(Mac系统) 如何文件格式转换?

  • 查看插件的Readme说明,下面在此再列出来
  • 方法一
  1. Open Markdown file in VSCode
  2. 在VSCode的此文件中,点右键,在右键菜单中,选择即可
    1
    2
    3
    4
    5
    6
    MarkdownPDF:Export(settings.json)
    MarkdownPDF:Export(pdf)
    MarkdownPDF:Export(html)
    MarkdownPDF:Export(png)
    MarkdownPDF:Export(jpeg)
    MarkdownPDF:Export士pdf,html,png,jpeg)
  • 方法二
  1. Open Markdown file in VSCode
  2. #+Shift+P(Mac系统),另一方法,(Mac系统)在菜单栏里选 View -> Command Palette(中文翻译命令面板),其它方法 Press F1 or Ctrl+Shift+P
  3. 在框中输入 Markdown PDF,然后下拉,选择即可
    1
    2
    3
    4
    5
    6
    MarkdownPDF:Export(settings.json)
    MarkdownPDF:Export(pdf)
    MarkdownPDF:Export(html)
    MarkdownPDF:Export(png)
    MarkdownPDF:Export(jpeg)
    MarkdownPDF:Export士pdf,html,png,jpeg)

参考链:

https://www.jianshu.com/p/519efe3f4799
VSCode 的 Extensions 的 Markdown PDF 的 Readme 文档说明

2019-07-18-hexo遇到1个md文件2篇文章的问题及解决

Bug现场

hexo s 调试时, (http://localhost:4000/) ,遇到1个md文件2篇文章的问题

Bug解决

尝试重新hexo s仍然无法解决,后来重装hexo解决了,重装用的是全新安装的命令
命令如下

1
npm install hexo-cli -g

解决后,发现我没有用hexo clean命令,不知道这命令是否会解决问题,下次再遇到了,再试试

hexo的官方站链接

https://hexo.io/zh-cn/