前言、打包规范
详见 app打包规范
一、安装包-编译&打包
详情查看:
[iOS 安装包1-编译&打包](./iOS 安装包1-编译&打包.md)
二、安装包-上传
详情查看:
[iOS 安装包2-上传](./iOS 安装包2-上传.md)
三、安装包-二维码扫码安装
详情查看:
[iOS 安装包1-编译&打包](./iOS 安装包1-编译&打包.md)
详见 app打包规范
详情查看:
[iOS 安装包1-编译&打包](./iOS 安装包1-编译&打包.md)
详情查看:
[iOS 安装包2-上传](./iOS 安装包2-上传.md)
详情查看:
[iOS 安装包1-编译&打包](./iOS 安装包1-编译&打包.md)

原因:本地钥匙串中缺少iOS发布证书,即缺少Apple Distribution: xxx Co.,Ltd. (yyy)。

小技巧,为了减少命令编译的错误次数,我们可以使用XCode的界面操作先自己编译导出一次。
如下,在导出的时候遇到如下错误。

进入 ~/Library/MobileDevice/Provisioning Profiles查看证书文件,
参考文章:自动打包时候,自动更新添加进去的设备
Provisioning profile实际上包含了Device ID,因此更新Provisioning profile就可以更新Device ID。根据xcode编译提示在archive阶段加入了-allowProvisioningUpdates这个参数的,应该会自动更新。
实时新增用户反馈<font color="warning">132例,请相关同事注意。
类型:<font color="comment">用户反馈
普通用户反馈:<font color="comment">117例
VIP用户反馈:<font color="comment">15例


以前:使用Application loader上传安装包。
现在:升级xcode11后,你会发现找不到Application loader这个工具了。(附:实际上Application loader只是对altool的封装。)
去查看xcdoe11的**更新说明**,有这样一段话
Xcode supports uploading apps from the Organizer window or from the command line with xcodebuild or xcrun altool. Application Loader is no longer included with Xcode. (29008875)
ApplicationLoader不能使用了,那我们使用什么。苹果在2019年10月份上线了Transporter。
在 App Store Connect 中创建 App 记录后,您便可以通过 Xcode、macOS 版 Transporter 或 altool 上传构建版本。其界面如下:
如果您使用 App Store Connect API,则建议您通过命令行工具 Transporter 和 JSON 网络令牌(JWT)验证来上传二进制文件。用于 API 的 JWT 也可以用于上传二进制文件。
Xcode11前:旧版本的altool只支持ipa、pkg的上传。
xcode11后,新版本的altool在旧版本的基础上,额外支持了各种文件压缩包的上传,应该就是mac、ipad、iphone应用和其他相关打包产物的集合包。
altool 位于:/Applications/Xcode.app/Contents/Developer/usr/bin/altool。

终端执行xcrun altool命令,可见其命令用法如下:
1 | MacBook-Pro:~ $ xcrun altool |
我们主要看两个命令:
1 | Usage: altool --validate-app -f <file> -t <platform> -u <username> {[-p <password>] | --apiKey <api_key> --apiIssuer <issuer_id>} |
将我们查看到的两个命令:
1 | Usage: altool --validate-app -f <file> -t <platform> -u <username> {[-p <password>] | --apiKey <api_key> --apiIssuer <issuer_id>} |
使用 username+password 方式 改为最终的形如:
1 | xcrun altool --validate-app -f ../output/Debug-iphoneos/Runner/Runner.ipa -t ios --verbose -u 账号 -p 密码 |
Please sign in with an app-specific password使用非专用密码时候的错误截图如下: 
提示信息:Please sign in with an app-specific password. You can create one at appleid.apple.com 。这是因为自己账号开启了二重验证,所以这里的密码不能使用原始的明文密码,而应该使用专用密码。
解决办法:在Apple官网https://www.apple.com.cn/登陆你的账号,并进入”管理你的Apple ID“。
在管理密码的地方有一个App专用密码,点击Generate an App-Specific Password,生成专用密码。
点击”查看记录“,可查看管理过往专用密码。

Invalid Provisioning Profile. The provisioning profile included in the bundle com.dvlproad.autopackage [Payload/Runner.app] is invalid.使用专用密码,进行
1 | xcrun altool --validate-app -f ../output/Debug-iphoneos/Runner/Runner.ipa -t ios --verbose -u 12345678@qq.com -p lnaa-xxxx-yyyy-zzzz |
出现了新错误,新错误截图如下: 
原因:
1、你打出来的包不是生产环境,比如你打的是dev环境
2、
xcrun altool有双重认证限制后,不能直接使用账号密码了,只能使用新的-apiKey 、–apiIssuer。
所以,最终的验证、上传的两个命令形如:
1 | xcrun altool --validate-app -f xxx/xxx/xxx.ipa -t ios --apiKey xxxxxxxx --apiIssuer xxxxxx --verbose --output-format xml |
2个命令分别对应 验证IPA 和 上传IPA,都需要使用3个参数ipa路径、apiKey、apiIssuer
①、ipa路径:用xcodebuild打包或用OrganizerExport导出的App store包。
②、apiKey和apiIssuer需要去开发者官网-用户和访问-密钥-去新增。
apiKey和apiIssuer需要去开发者官网-用户和访问-密钥-去新增。如下图流程
新增一个密钥,名称和访问者可以自由填写

生成完成后,IssuerID就是apiIssuer,密钥ID就是apiKey,并且还有一个API密钥可以下载。注意此秘钥文件只能下载一次,请妥善保存

创建生成apiKey和apiIssuer后,按照我们之前在终端执行的xcrun altool命令提示有如下说明:
1 | --apiKey <api_key> apiKey. Required for JWT authentication while using validation, upload, and notarization. |
即:我们需要把刚才下载好的密钥文件放到这里面的其中一个文件夹里,它会按顺序去查找。
这里我们就选择~/private_keys这个位置吧,即在自己用户目录下/Users/xxx/private_keys新建了个private_keys文件夹,并把密钥文件放进去的。
在确定完ipa包验证和上传使用的xcrun altool命令和上传参数中的apiKey和apiIssuer后,我们就可以开始使用xcrun altool验证上传了。
验证结果
1 | 2019-11-13 11:36:56.773 altool[3970:100440] Deallocating <ITunesSoftwareServiceWorkSeriesFactory: 0x7fa5d6d06610> |
等到提示No errors validating archive at xxx时就代表验证成功了,可以上传了。
上传结果
1 | 2019-11-13 11:49:55.382 altool[4093:102055] [2019-11-13 11:49:55 CST] <main> DBG-X: Returning 0 |
等到提示No errors uploading时就代表上传成功了。
可以去开发者中心-活动里面查看一下是否有构建版本
自动打包
浏览器缓存、分段、
拉前端代码?+正常的环境的切换+通知
没网?数据?
h5请求,app拦截,自动更新
h5调用插件
多渠道打包
Cordova、
安全
支付:
架构 MVP
代码层面 hashMap 16大小、本地化存储 mmkv 门面模式
性能优化 线上oom、bugly、图片过大
Viewmodel 数据持久
不同客户不同包
网络框架 重试、日志
权限管理
离线缓存?
日历视图 七八层嵌套-> 1层
通知、插件、路由
插入视图
安卓自启动问题
本地化/国际化-
框架-getX
webView-flutter 白屏
扫码
设计模式?策略模式?单例?通知?几个原则。
搜索逻辑?冒泡排序?
封装
看过什么书,什么三方库?
图片缓存?
性能优化(webView 优化)?
应用在后台长时间执行(service 保活)
怎么区分不同平台的安装包?
同一应用多端,项目上怎么开发(司机端、乘客端)。
路由设计 scheme://path?argument
app中核心的非数据组件()
图片处理、网络库、路由
锁
线程同步
掌握 RxJava + Retrofit + OkHttp 的⽹络底层框架,并结合 MVP、MVC、MVVM 三种架构模式编写出⽹络底 层框架,已应⽤于项⽬中,底层稳定性强
了解LiveData,Viewmodel,DataBinding等新框架,且结合RxJava 使⽤MVVM架构应⽤在项⽬中
理解内存泄露的原理,编写代码过程中能避免常⻅内存泄露。使⽤Memory Profiler 和 Memory Analyzer等 ⼯具检测内存泄露问题
了解App启动速度优化,内存,以及APK 瘦身等优化
理解 Android 适配原理,能够做到适配各类常⽤机型
理解Android触摸事件传递机制,⾃定义View和动画机制 理解 Handle 消息处理机制
理解多个设计模式的使⽤。如单例设计模式,观察者模式,建造者模式,装饰者设计模式等 独⽴编写多个lib库,如图库,列表适配库,⼴告库,分享库(QQ,微信,微博,Facebook等第三⽅),均采⽤ 组件化开发⽅式,可快速移植其他项⽬中
Flutter
状态管理 setState\Provider
网络拦截器、
图片缓存
请求
参考文章:
苹果官网–在 Mac 上将“自动操作”工作流程添加到“脚本编辑器”中的“脚本”菜单

如果要删除已创建的,则进入 /Users/qian/Library/Services 删除对应文件即可。
各位需要安装app的苹果手机用户们,晚上好。如您在通过如下地址:
https://www.pgyer.com/xxxx
下载app的时候,发现无法安装,则可能是您的设备未进行证书注册(如果您可以安装则证明已注册,请忽略本消息)。
如您对安装有需求,则可通过如下地址
https://www.pgyer.com/tools/udid
将您的苹果手机udid告知我们,我们为您添加后。当我们打新包的时候,就可以安装上app啦。
特别注意:因证书注册设备只能添加100台,故即使添加上去之后,再删除也算是一台。所以如果您非必要安装,请忽略本消息。
最后,提前祝大家节日快乐!
| 序号 | 类型 | 简写 | |
|---|---|---|---|
| 1 | 产品 | 产 | |
| 2 | 测试 | 测 | |
| 3 | 后台 | 后 | |
| 4 | iOS开发 | i | |
| 5 | Android开发 | A |
| 序号 | 类型 | 场景 | |
|---|---|---|---|
| 1 | 开发 | 开发必须 | |
| 2 | 测试 | 测试测试与回归需求 | |
| 3 | 验收 | 产品验收版本功能 | |
| 4 | 体验 | 其他用户提前体验 |
| 序号 | 备注 | 用途 | UUID | 是否必须 | 申请时间 |
|---|---|---|---|---|---|
| 1 | 产-张三-iPhone8 | 功能验收 | |||
设计模式分为三种类型,共23类。
(1)创建型模式:单例模式、抽象工厂模式、建造者模式、工厂模式、原型模式。
(2)结构型模式:适配器模式、桥接模式、装饰模式、组合模式、外观模式、享元模式、代理模式。
(3)行为型模式:模版方法模式、命令模式、迭代器模式、观察者模式、中介者模式、备忘录模式、解释器模式、状态模式、策略模式、职责链模式、访问者模式。
使用设计模式的目的:为了代码可重用性、让代码更容易被他人理解、保证代码可靠性。
开闭原则可以认为是总则,依赖倒置原则的使用是实现开闭的一个手段。在依赖倒置的实现上,需要单一职责和接口隔离来保证接口和实现的处理质量。在实现中,通过合成复用和迪米特法法则来降低耦合度,提高灵活性。在实现过程中,如果使用了继承,那么必须遵循里氏替换原则来保证系统的可扩展性。
1 | // 里氏替换原则:应用程序中任何父类对象出现的地方,我们都可以用其子类的对象来替换,并且可以保证原有程序的逻辑行为和正确性。因为这里父类是抽象类,所以肯定遵守里氏替换原则。 |
1 | ``` |
设计模式六大原则(6):开闭原则
定义:一个软件实体如类、模块和函数应该对扩展开放,对修改关闭。
问题由来:在软件的生命周期内,因为变化、升级和维护等原因需要对软件原有代码进行修改时,可能会给旧代码中引入错误,也可能会使我们不得不对整个功能进行重构,并且需要原有代码经过重新测试。
解决方案:当软件需要变化时,尽量通过扩展软件实体的行为来实现变化,而不是通过修改已有的代码来实现变化。
举例:
当有新需求时,如果增加功能涉及老功能点某些地方,最好不要轻易修改老功能,最好重新新增相应的功能,不然一些忽略的细节可能就会造成隐患。
1 |
|
则
1 | // 错误 |
里氏替换原则主要阐述了有关继承的一些原则,即什么时候应该使用继承,什么时候不应该使用继承,以及其中蕴含的原理。里氏替换原是继承复用的基础,它反映了基类与子类之间的关系,是对开闭原则的补充,是对实现抽象化的具体步骤的规范。
实现里氏替换原则通俗来讲就是:子类可以扩展父类的功能,但不能改变父类原有的功能。也就是说:子类继承父类时,除添加新的方法完成新增功能外,尽量不要重写父类的方法。
里氏替换原则是针对继承而言的。其至少有两种含义:
1、如果继承是为了实现代码重用,也就是为了共享方法,那么共享的父类方法就应该保持不变,不能被子类重新定义。子类只能通过新添加方法来扩展功能,父类和子类都可以实例化,而子类继承的方法和父类是一样的,父类调用方法的地方,子类也可以调用同一个继承得来的,逻辑和父类一致的方法,这时用子类对象将父类对象替换掉时,当然逻辑一致,相安无事。
2、如果继承的目的是为了多态,。。。。
MVC是模型、视图、控制器开发模式,对于iOS SDK,所有的View都是视图层的,它应该独立于模型层,由视图器来控制。所有的用户数据都是模型层,它应该独立于视图。所有的ViewController都是视图器,由它负责控制视图,访问模型数据。
1 | class CacheManager { // 单例模式 |
应用场景:确保程序运行期间,某个类只有一份实例,常用于进行资源共享控制。
实例:[UIApplication sharedApplication]。
注意事项:确保使用者只能通过 getInstance方法才能获得,单例类的唯一实例。
返回的也只是此单例类的唯一静态变量。
应用场景:工厂方式创建类的实例,多与proxy模式配合,创建可替换代理类。
优势:易于替换,面向抽象编程,application只与抽象工厂和易变类的共性抽象类发生调用关系。
敏捷原则:DIP依赖倒置原则
实例:项目部署环境中依赖多个不同类型的数据库时,需要使用工厂配合proxy完成易用性替换
注意事项:项目初期,软件结构和需求都没有稳定下来时,不建议使用此模式,因为其劣势也很明显,
增 加了代码的复杂度,增加了调用层次,增加了内存负担。所以要注意防止模式的滥用。
iOS 设计模式之工厂模式工厂模式我的理解是:他就是为了创建对象的
创建对象的时候,我们一般是alloc一个对象,如果需要创建100个这样的对象,如果是在一个for循环中还好说,直接一句alloc就行了,但是事实并不那么如意,我们可能会在不同的地方去创建这个对象,那么我们可能需要写100句alloc 了,但是如果我们在创建对象的时候,需要在这些对象创建完之后,为它的一个属性添加一个固定的值,比方说都是某某学校的学生,那么可能有需要多些100行重复的代码了,那么,如果写一个-(void)createObj方法,把创建对象和为对象设置初始的属性值(比如学校属性)写在这个方法里边,那么就是会省事很多,也就是说我们可以alloc 创建对象封装到一个方法里边,直接调用这个方法就可以了,这就是简单工厂方法
如果一个工厂里有两个create创建方法,比如PeopleFactory类工厂同时有createTeacher和createStudent两个方法,现在假设原本创建100个老师的写法是如下所示:
1 | PeopleFactory *factory = [PeopleFactory alloc] init]; |
现在突然想让创建出来的对象是student,而不是老师teacher,那么这时候我们就不得不一个个的把createTeacher替换成createStudent方法了。
1 | PeopleFactory *factory = [PeopleFactory alloc] init]; |
但是如果利用工厂模式,我们为每一个要创建的对象所在的类都相应地创建一个工厂,则我们的写法将会变成
1 | //创建老师 |
显然上面这种方法,在我们需要不同对象时,修改起来更方便点。
工厂方法模式是为每一个要创建的对象所在的类都相应地创建一个工厂
应用场景:当一个类的某些功能需要由别的类来实现,但是又不确定具体会是哪个类实现。
实例:tableview的datasource和delegate
应用场景:一般为model层对,controller和view进行的通知方式,不关心谁去接收,只负责发布信息。
实例:
①Notification通知中心,注册通知中心,任何位置可以发送消息,注册观察者的对象可以接收。
②KVO,键值对改变通知的观察者。
| 模式 | iOS 中的应用 | 订阅方式 |
|---|---|---|
| 观察者模式 | KVO(Key-Value Observing)、 Delegate(代理模式也是观察者的一种特殊情况) |
观察者直接注册到被观察者/ 被观察者直接注册给观察者 |
| 发布-订阅模式 | NotificationCenter、Combine、RxSwift | 订阅者向事件中心注册 |
应用场景:定义算法族,封装起来,使他们之间可以相互替换。
优势:使算法的变化独立于使用算法的用户
敏捷原则:接口隔离原则;多用组合,少用继承;针对接口编程,而非实现。
实例:排序算法,NSArray的sortedArrayUsingSelector;经典的鸭子会叫,会飞案例。
注意事项:1,剥离类中易于变化的行为,通过组合的方式嵌入抽象基类
2,变化的行为抽象基类为,所有可变变化的父类
3,用户类的最终实例,通过注入行为实例的方式,设定易变行为
防止了继承行为方式,导致无关行为污染子类。完成了策略封装和可替换性。
OC中常用的数组排序有以下几种方法:
①、sortedArrayUsingSelector:;
②、sortedArrayUsingComparator:;
③、sortedArrayUsingDescriptors:
1 | //简单排序 |
策略模式的其他例子:
![]()
图片来源于 版本检查.graffle
公共参数详见:《基础规范-请求规范》 中的request公共入参保持一致
| 序号 | 标识 | 示例 | 描述 | 必传 | 用途 |
|---|---|---|---|---|---|
| 1 | isManualCheck | true/false | 是否是手动检查更新 | 否 | 避免关闭后无法弹出 |
| 2 | callOwner | app、h5 | 调用者 | 否 | 不同调用者版本提示语可能不一致 |
背景:一些新活动需要触发弹出新版本
| 序号 | js接口 | 描述 | 场景 |
|---|---|---|---|
| 1 | h5CallBridgeAction_checkVersion | 检查更新 | |
| 2 | h5CallBridgeAction_updateVersion | 马上更新 | 更新弹窗h5游戏自定义不同的风格 |
略
进入bulid.gradle文件,修改如下:
1 | flutterVersionName ="1."+new Date().format("MM.dd", TimeZone.getTimeZone("GMT+8:00")) |
进入Jenkins指定iOS项目的配置,修改
1 | VERSION=$(date "+%y.%m.%d") # 19.02.21 小y是19,大Y是2019 |
以 https://www.google.com 为例,即访问google的时候,想要访问到最新的版本内容。
其他参考文章:
有版本更新时候,接口直接返回最终的访问地址。形如:https://www.google.com/v10201000
缺点:
返回给app的地址得一直变化。
app中历史记录得做映射,不能直接保存网页地址。否则会出现点击了一个历史记录,发现之前的地址无法访问的问题。(为此,app就得通过类似id的方式,每次去向接口取最新的值)
退一步将每次获取改为切换前后台的时候,才去获取,也会出现有地址id,却出现通过id取接口失败的一些异常情况出现。
版本举例如下:
| 时间 | 发布的版本号 | 浏览器/客户端访问的【输入地址】 | 浏览器/客户端【访问的实际地址】 |
|---|---|---|---|
| 10.20 10:00 | v10201000 | https://www.google.com | https://www.google.com/v10201000 |
| 10.24 10:00 | v10241000 | 同上 | https://www.google.com/v10241000 |
| 11.01 10:00 | V11011000 | 同上 | https://www.google.com/v10311000 |
| 11.01 12:00 | V11011200 | 同上 | https://www.google.com/v10311200 |
方案过程如下:
1、浏览器/客户端访问 https://www.google.com
2、https://www.google.com 所对应的网页 https://www.google.com/index.html 进行 check_version 接口请求
1 | 网页:https://www.google.com/index.html |
3、https://www.google.com/index.html 根据所得地址重定向
4、最终即看到的是用户在浏览器/客户端访问 https://www.google.com ,但实际上会访问最新的版本 https://www.google.com/v10241000/index.html 。
优点:
问题1:访问google没看到变化的内容
1、无重定向时,即访问的地址是 https://www.google.com/v10201000
可能原因为:有缓存,需要在url后加参数 来刷缓存,形如: https://www.google.com/v10201000?refresh=123
2、有重定向时,即访问的地址一直是 https://www.google.com
可能原因为:https://www.google.com/index.html 没做重定向到最新的版本地址