0%

Swift 5.2新特性

2020年3月25日,随着Xcode 11.4的开放下载,Swift 5.2也正式发布了。

该版本专注于改善开发人员体验:

  • 提升编译器诊断(错误和警告)和代码补全效率
  • 提高Debug的可靠性
  • 改进了Swift Package Manager中依赖的处理
  • LSP和SwiftSyntax的工具改进

语言更新

Swift 5.2实现了下列提案:

以及其他语言特性:

  • 下标支持默认参数
  • Lazy filter顺序调整

改进的编译器诊断

Swift 5.2的新编译器诊断引擎解决了Swift 5.1代码错误提示中潜在的混乱消息,现在可以更好地指出需要修复的确切代码段。

例如:

1
2
3
4
5
6
7
enum E { case one, two }

func check(e: E) {
if e != .three {
print("okay")
}
}

在Swift 5.2之前,我们会看到如下错误提示:

1
2
3
error: binary operator '!=' cannot be applied to operands of type 'E' and '_'
if e != .three {
~ ^ ~~~~~~

在Swift 5.2中:

1
2
3
error: type 'E' has no member 'three'
if e != .three {
~^~~~~

改进的代码补全

通过消除不必要的类型检查来更快地完成。对于大型文件,与Xcode 11.3.1相比,它可以将代码完成速度提高1.2倍至1.6倍,具体取决于完成位置。

为不完整的字典文字和不完整的三元表达式提供隐式成员的名称:

类型出现在结果中时更易于阅读。some View尽可能使用不透明的结果类型(例如),并保留类型别名,例如在Swift 5.1.3(Xcode 11.3.1)中:

在Swift 5.2(Xcode 11.4)中,现在显示为:

##编译速度提升

Swift 5.2编译器利用新的集中式逻辑来更有效地处理编译(尤其是增量编译)中的解析声明和相互引用,避免不必要的开销,从而提高性能和编译速度。

SPM、LLDB改进以及SourceKit-LSP协议更新

Swift 5.2中的SPM使用一种新策略来解决依赖性以提高错误消息的质量和复杂依赖的性能。

Swift 5.3呢?

质量和性能增强,并增加对Windows、Linux平台的支持

Xcode 11.4 新特性

Universal Purchase

Xcode 11.4允许开发者使用同一个Bundle ID分发macOS版本应用,以实现“通用购买”,即用户只需购买一次即可在iOS,iPadOS,macOS,watchOS和tvOS上使用应用或内购服务。

模拟器支持iPadOS的指针捕获

UIButton、手势添加代表“指针按钮”的Point属性

模拟器支持拖拽APNs文件模拟远程推送通知

也可以使用命令行:
xcrun simctl push <device> com.example.my-app ExamplePush.apns

参考:

swift.org - Swift 5.2 Released!

Apple Developer - Xcode 11.4 Release Notes

Apple Developer - Offering Universal Purchase

GitHub - What’s new in Swift 5.2?

swift by sundell - Exploring Swift 5.2’s new functional features

作者: Bobo
- Cocoapods 1.9.0
- Carthage 0.34.0
- Xcode 11.3.1

选择Carthage的理由

  1. 去中心化,预编译特性
  2. 更新兼容了Swift 5编译
  3. 尝试同步开发的另一种选择

如何使用Carthage开发组件

  1. 安装指引 https://github.com/Carthage/Carthage#installing-carthage
  2. framework 链接问题,因为有的target的General里没有Link选项,所以统一使用BuildPhase的Link Binary with Libraries
  3. 私有库包含其他库的是否需要copy-frameworks script,不需要,因为最终链接的地方是主项目,私有库copy会导致嵌套引入
  4. 同步开发,Carthage的file模式并不能很好的支持本地同步开发;https://www.mokacoding.com/blog/carthage-no-build/ 此文章提出的“弱使用”加上submodule的引入方式根本上是使用Xcode workspace集成开发,而这种方式有不少缺点,比如名字空间冲突,build setting重复设置等等

Carthage的问题

  1. 慢,可用Terminal proxy解决
  2. 版本冲突,库没有版本依赖的声明,最终在主项目使用一个版本的库,会出现代码不兼容的情况;和pod引入的同一库也存在不同版本并存的问题
  3. 动态link,有的库有动态依赖的问题,编译时不能发现错误,但实际缺少库运行app会报“dyld __abort_with_payload”问题

Cocoapods常用命令

  1. 创建组件,pod lib create
  2. 验证,pod spec lint --sources='xxx' --allow-warnings
  3. 上传,pod repo push OKSpecs SPECS_NAME.podspec --sources='xxx' --allow-warnings

SwiftPM

优点

  1. 原生Xcode支持,没有类似Podfile,Package.resolved文件则对应lockfile
  2. Package.swift没有version字段,所以私有库更新并不需要管理version/tag,也没有spec仓库管理
  3. 测试方便

缺点

  1. 不支持oc(貌似已经支持),不支持resource
  2. 慢,xcode fetch/install比pod install慢很多,包括更新,也无法单独更新,无法手动控制安装
  3. 源文件相比Pods/文件夹,存在derived data,一旦删掉,需要重新fetch

结论

不适合复杂混编项目,适合小型项目,适合编写纯逻辑的swift库
PS: https://forums.swift.org/t/how-to-add-local-swift-package-as-dependency/26457/7

组件的版本(tag)管理

  1. 主项目使用branch模式,拉取最新代码,也可以用于feature分支开发
  2. 什么时候需要更新tag,组件有互相依赖时,比如OKWorkbench依赖OKCore,这时候OKCore需要更新的话就需要升级tag
  3. 组件的开发都应该优先在主项目切换为path模式做本地开发
  4. 什么时候应该上传tag的更新,一个功能相关的组件全部开发完毕时,这样能保证不会过于频繁的升级组件版本

组件设计

  1. 现阶段组件都是为主项目服务
  2. 组件应尽量具有单一性,可单独测试性,应尽量避免为了使用组件的一个小部分引入整个组件的情况出现
  3. 应避免组件互相之间嵌套依赖,比如OKNetwork依赖OKCore,OKWorkbench依赖OKNetwork和OKCore,此时只需要保持OKWorkbench对OKNetwork依赖即可,避免多版本冲突,第三方库同理
  4. 组件内的命名应保留名字空间,单独可阅读,避免使用过长的名字空间取名,比如OKCore.A.B.C…

作者: Hackice

国际化与本地化

国际化是指在设计软件,将软件与特定语言及地区脱钩的过程。当软件被移植到不同的语言及地区时,软件本身不用做内部工程上的改变或修正。本地化则是指当移植软件时,加上与特定区域设置有关的信息和翻译文件的过程。
——《维基百科》

国际化意味着产品有适用于任何地方的“潜力”;本地化则是为了更适合于“特定”地方的使用。

项目中国际化方式

  1. 静态字符串(.strings),通过调用Bundle的localizedString(forKey: String, value: String, table: String)方法实现国际化
  2. 资源:xib/asset/info.plist
  3. 语言包:xcloc

New

  • iOS 13中用户可以针对每一个应用单独的设置语言选项
  • 获取当前语言设置
1
2
3
4
5
6
// 系统设置中的语言偏好列表
Locale.preferredLanguages
// 应用支持语言的偏好选项
Bundle.main.preferredLocalizations.first
// 指定范围语言中的偏好
Bundle.preferredLocalizations(from: [String]).first
  • Test Plan导出包含上下文的语言包信息

问题

  1. 当前国际化依赖手动维护各个语种的字符串文件(.strings),从调用难易度、容错、可维护性,有没有更好的方案
  2. Interface Builder中国际化的处理,比如文本和图片
  3. app语言切换

参考:

Creating Great Localized Experiences with Xcode 11 WWDC19

Clean iOS Localizable Files medium

How to keep your iOS localizable files clean medium

String Localization objc.io

前言

本文不讲细节,只讲功能,因为细节上,很多前人的库都已经有完美的实现了。我们只是做了整合并实现我们需要的功能
AndroidX中的生命周期组件,Livedata,是个很好用的功能。但是在Rxjava中,很多生命周期组件,都只能解绑,不能进行延迟触发与缓存,以及重订阅

LiveData的功能

延迟触发

什么是延迟触发?在Livedata中,有两个方法:

1
2
3
4
5
6
protected void onActive() {

}
protected void onInactive() {

}

这两个方法,一个是在生命周期onStart之后执行,一个是在生命周期onStop执行,所谓延迟触发是,我在onCreate的时候就已经对LiveData进行订阅,但实际触发请求,是在onStart之后调用onActive之后才进行的数据请求。则为延迟触发。这里的好处是,写在Fragment的onCreate数据订阅,在onStart之后才开始请求数据,这样保证数据到达界面时,界面可用,满足在onCreate时进行数据请求能保证数据到达界面时界面可用

缓存

什么是缓存,Livedata的经典功能,不管订阅数据在后台如何快速发送,当界面可用的时候,都像界面层推送最后一个缓存数据。

重订阅

什么是重订阅?比如,代码中,需要在onStart之后开始获取数据,但是当onInactive的时候关掉数据获取,然后当我界面在可用进入onStart时,再次像数据源订阅,解决界面在压栈之后还频繁更新数据。

主线程管理

在Livedata订阅的数据,接收者都能在主线程直接更新UI

RxAndroidLive

根据目前需要的需求,要实现Livedata的这些功能遍历在Rxjava上的实现,为什么要这么实现,因为Livedata没有很好的错误处理,所以我们需要在RxJava上实现这些界面需要的,我们原本使用非白白的Live组件,但是这个组件只实线了缓存功能,以及仅仅实现了Observable组件,这对我们整个系统的逻辑不完善,所以我们改进并实现了重订阅以及延迟触发机制还有线程管理。解决Android整个生命周期问题。

引入

1
2
3
4
5
6
7
repositories {
jcenter()
}

dependencies {
implementation 'cn.xiaoman.library.android:rxandroidlive:1.0.0'
}

代码使用

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
Flowable.interval(1, TimeUnit.SECONDS)
.compose(RxAndroidLive.<Long>bindLifecycle(this, true, AndroidSchedulers.mainThread()))
.subscribe(new Subscriber<Long>() {
@Override
public void onSubscribe(Subscription s) {
addContent("Flowable onSubscribe");
final int requestNum = 10;
s.request(requestNum);
addContent("Flowable request " + requestNum);
}

@Override
public void onNext(Long aLong) {
addContent("Flowable onNext value " + aLong);
}

@Override
public void onError(Throwable t) {
addContent("Flowable onError");
}

@Override
public void onComplete() {
addContent("Flowable onComplete");
}
});

参数解析

  1. 默认实现onActive感知,只有当onStart状态之后,才会向上订阅数据
  2. 当autoRestart成立时,生命周期经历stop之后取消上层订阅,对界面层订阅不中断,重新进行livedata的onstart时,重新订阅数据
  3. 当界面不可用时,默认缓存最后一次数据,当界面可用时抛出最后一次数据
  4. autoRestart 参数仅仅在可持续订阅数据库可用(Observable,Flowable)
  5. Scheduler 参数指定rxjava后面数据流的所在线程,一般自己指定为主线程(若不指定,线程可能会因为生命周期问题,进行线程飘逸,因为生命周期属于主线程触发,后台数据上抛可能处于子线程)

源码

项目已开源,源码在这

感谢

AndroidX LiveData

Android 生命周期架构组件与 RxJava 完美协作

行文环境
- Xcode 10.1
- Carthage 0.31.2

Carthage

简介

Carthage 是 swift 开发的代码依赖工具,与Cocoapods直接引入第三方源代码并修改 Xcode workspace 不同,Carthage 是将第三方库预先 build 好的 framework 引入工程。这样更适合大型项目,预先编译好的 framework 不需要重新编译。

快速使用

  1. 安装 brew install carthage
  2. 在工程目录下创建 Cartfile
  3. Cartfile 里添加依赖库,比如 github "Alamofire/Alamofire" ~> 4.7.2
  4. 运行 carthage update
  5. 会生成 Cartfile.resolved 和 Carthage 文件夹 包括 Build 和 Checkout
  6. 添加 framework 到工程,在 Build Phases 里新建 New Run Script Phase,输入:
    /usr/local/bin/carthage copy-frameworks

    Input Files 项里填入

    $(SRCROOT)/Carthage/Build/iOS/Alamofire.framework

    Output Files 项里填入

    $(BUILT_PRODUCTS_DIR)/$(FRAMEWORKS_FOLDER_PATH)/Alamofire.framework

常用命令

carthage update 更新并重新编译 framework

carthage bootstrap 类似 update,一般用于新 checkout 的仓库第一次运行 carthage

--platform iOS build 平台参数

--cache-builds build 时使用缓存

carthage build --no-skip-current build private framework

Cartfile

引用github库 github "Alamofire/Alamofire" ~> 4.7.2

指定具体tag github "malcommac/SwiftDate" == 4.5.1

指定分支 github "XiaomaniOS/RichEditorView" "xiaoman"

私有仓库 git "ssh://git@git.xiaoman.co:7999/im/xmprotobuf.git"

本地仓库 git "file:///Users/neilpa/code/project" "branch"

缺点

  • 第一次 build 时间很长(喝杯咖啡先)
  • CI 运行 carthage update 费时,使用 --cache-builds 加快效率
  • 没有 Cocoapods 的各种其他功能

创建私有 framework

新建项目

新建 Cocoa Touch Framework 的项目

将 framework 的 scheme 设置成 shared,这里有个坑,新创建的项目 Xcode 不会生成 .xcscheme 文件,carthage 需要找这个文件来选择哪些 scheme 需要 build,所以需要修改一下 scheme 让 git 里生成.xcscheme 文件

设置 General -> Deployment Target

添加代码

将源代码加入工程,确保需要暴露的类等是 public 的,因为默认是 internal,外部无法识别

Objective-C代码需要设置header为Public

私有 framework 如果有依赖库可以使用 Carthage 添加,主工程使用私有 framework 时也会 build 出依赖的 framework

编译

carthage build --no-skip-current

如果有错参考 Resolve build failures

如果提示找不到 scheme,检查上面提到的 .xcscheme 文件有没有提交到 git

成功后打 git tag,Carthage 用 tag 来定位代码

使用

用上面提到的方法加入到主工程即可,注意依赖库也需要添加

Read More

Carthage

其他

前言

移动端要做离线版的邮件缓存,我们需要同步客户的大量邮件数据到本地,因为数据量大,不在使用http已经json进行邮件协议传输,改用proto并配合snappy来做数据传输。

Snappy

snappy是什么?
好吧,我也不知道,公司要用到了,我就引用一下wikipedia的内容给各位看官看下?

Snappy(以前称Zippy)是Google基于LZ77的思路用C++语言编写的快速数据压缩与解压程序库,并在2011年开源。它的目标并非最大压缩率或与其他压缩程序库的兼容性,而是非常高的速度和合理的压缩率。使用一个运行在64位模式下的酷睿i7处理器的单个核心,压缩速度250 MB/s,解压速度500 MB/s。压缩率比gzip低20-100%。

Snappy广泛应用在Google的项目,例如BigTable、MapReduce和Google内部RPC系统的压缩数据。它可在开源项目中使用,例如Cassandra、Hadoop、LevelDB、MongoDB、RocksDB和Lucene。[4]解压缩时会检测压缩流中是否存在错误。Snappy不使用内联汇编并且可移植。

嗯,看了下介绍,知道为啥要用它了吧?为了非常高的压缩速度和合理的压缩率。

相关项目

嗯,我们移动端做Android项目,肯定优先找java相关的snappy实现。
我们找到了这样的一个项目。

https://github.com/xerial/snappy-java

这个项目也是使用java代码,然后来调用so库里面的c方法来进行代码压缩和解压。但是,这个打包方式,并不适合Android项目来调用,为了解决这个问题,所以有了这文章的主角

Snappy-Android

嗯,我们修改了 snappy-java的源码路径,并改造成 android 项目的编译方式,用以支持 android 使用 snappy 来做压缩解压。

因使用的是相同的 so 动态链接库方式,使用了最新的 NDK 来编译的相应平台的动态链接库。

支持平台

目前使用的 NDK 编译出来的动态链接库包含 armeabi-v7a,arm64-v8a,x86,x86_64 四大平台,
其余平台 armeabi, mips, mips64 因为新版 NDK 编译取消支持,所以编译成品中不在支持,如果有需要,可以自己下载源码,使用旧版 ndk 进行编译打包。

使用方式

这里不在重新写一遍啦,详见开源项目主页
https://github.com/xiaoman-team/snappy-android

感谢

再次感谢 snappy snappy-java 给我们做出这么好的产品用于商业使用,我们只是做了一下 android 的打包适配。

前言

团队博客的驱型基本做好了,接下来是要写一篇交大家使用的文章,这样才能让大家安心写些文档不是?
目前使用的博客系统,是基于nodejs的hexo静态博客生成组件。
好处是什么呢?是我们直接用github来托管就好了,不用自己管理空间哈。
接下来我们说说怎么使用我们的博客系统

平台支持初始化

公司用的比较多的是mac,下面就基于苹果环境来做教程啦,你们相应的做修改哈。
先安装nodejs环境和博客平台环境。

安装nodejs

这个嘛,到官网下载就好了。https://nodejs.org/,作者目前使用的是11.3.0哈,安装好之后,就会有npm指令了。

安装hexo

install -g hexo-cli```
1
2
3
4
5
6
7

这样,博客支持程序就安装好了。

# 配置网站本地环境

## 下载网站源码
```git clone git@github.com:xiaoman-team/xiaoman-team.github.io.git

获取网站源码分支

为啥要获取,因为静态网站使用的是master分支,这个分支是试用hexo发布的,平时我们管理的文章,在source分支,为啥用这个分支?嗯,其实你也可以用其他分支不是?自己换就好了啦。

1
2
cd xiaoman-team.github.io
git checkout -b source origin/source

初始化hexo程序

install hexo --save```
1
2
3
4
5
6
7
8
9

到目前为止,,你就搞定了本地环境配置拉

# 文章编写
接下来,是不是改写文章了呢?好吧,我们用hexo生成一片新文章好不好?

## 新文章编写
比如这篇文章的指令是
```hexo new start-use-hexo

当然,start-use-hexo就是文章的链接名啦,文件会生成在 _post 目录下,是 markdown 格式的。

进入文章编写

怎么写??去_post目录下去写你的文章啦,,markdown怎么用?这不在本文讨论范围内哦,你自己去试试?

预览文章

使用一下命令生成静态文件来预览文章啦

g```
1
2
3

使用一下命令来打开预览服务器
```hexo s

他会提示你本地打开路径,你自己打开就能预览你写好的文章啦。
如果有时候没生效,试试
hexo clean

hexo g

发布文章

也是很简单

1
2
3
hexo clean
hexo g
hexo d

请你在预览好之后,在执行 d 的命令哈。
该命令执行完成之后,会推送到master分支上,然后我们的网站
https://xiaoman-team.github.io.git 就会更新了。

保存文章源码

最后,最重要的,当然是把你的文章源码保存下来了啦。

1
2
3
git add source/_posts/start-use-hexo.md
git commit -m 'add start use hexo post'
git push

结语

好了,目前源码也已经保存了,文章也发布了,教程就是这么简单啦。有啥不懂的,可以提出来我们在探讨一下啦。

行文环境
- Xcode 10.1
- Swfit 4.1
- iOS 12
- 阿里云

证书设置

iOS推送证书设置

证书配置分为开发环境和生产环境,需要与业务服务器的开发环境(如dev/test)和正式环境区分开。例如给测试分发的Ad Hoc包使用生产环境的证书,但业务是test环境。

Ad Hoc包为什么收不到通知

因为Ad Hoc使用生产环境证书,设备的deviceToken在不同证书环境是不同的,但测试包的业务是test环境,后台会在test环境使用开发的apns设置,导致deviceToken无法匹配到对应的设备。

payload变化

老版本payload,只有body

1
2
3
4
5
6
7
8
9
10
11
{
"aps": {
"alert": {
"your notification body",
},
"badge": 1,
"sound": "default",
},
"key1":"value1",
"key2":"value2"
}

新版payload,新增title、subtitle、body、category等字段
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
{
"aps": {
"alert": {
"title": "title",
"subtitle": "subtitle",
"body": "body"
},
"badge": 1,
"sound": "default",
"category": "test_category",
"mutable-content": 1
},
"key1":"value1",
"key2":"value2"
}

业务逻辑字段方式没有变化,直接加在json里

通知实现

初始化SDK

使用配置文件AliyunEmasServices-Info.plist直接调用autoInit

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
func setup(_ application: UIApplication, launchOptions: [UIApplicationLaunchOptionsKey: Any]?) {
CloudPushSDK.autoInit { result in
guard let result = result else {
log.error("Push SDK init failed, error: result is nil!")
return
}
if result.success {
log.debug("Push SDK init success, deviceId: \(String(describing: CloudPushSDK.getDeviceId()))")
} else {
log.error("Push SDK init failed, error: \(String(describing: result.error))")
}
}

//...
// 点击通知将App从关闭状态启动时,将通知打开回执上报
CloudPushSDK.sendNotificationAck(launchOptions)
}

请求通知权限并注册远程通知

第一次安装会弹出请求通知的alert

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
func registerAPNS(application: UIApplication) {
let center = UNUserNotificationCenter.current()
center.delegate = self
var options: UNAuthorizationOptions = [.alert, .sound, .badge]
if #available(iOS 12.0, *) {
options = [.alert, .sound, .badge, .providesAppNotificationSettings]
}
center.requestAuthorization(options: options) { (granted, error) in
if granted {
log.debug("User authored notification.")
DispatchQueue.main.async {
application.registerForRemoteNotifications()
}
} else {
log.debug("User denied notification.")
}
if let error = error {
log.error(error)
}
}
}

注册设备并上报deviceToken

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
func registerDevice(_ deviceToken: Data) {
CloudPushSDK.registerDevice(deviceToken) { result in
guard let result = result else {
log.error("Register deviceToken failed, error:: result is nil!")
return
}
if result.success {
log.debug("Register deviceToken success, deviceToken: \(String(describing: CloudPushSDK.getApnsDeviceToken()))")
} else {
log.debug("Register deviceToken failed, error: \(String(describing: result.error))")
}
}
}

public func application(_ application: UIApplication,
didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
registerDevice(deviceToken)
}

// 注册失败
public func application(_ application: UIApplication,
didFailToRegisterForRemoteNotificationsWithError error: Error) {
log.error("did Fail To Register For Remote Notifications With Error : \(error)")
}

注册通知类别

通知类别(category)用于给通知分类,可添加按钮或自定义UI

1
2
3
4
5
6
7
func createCustomNotificationCategory() {
let action = UNNotificationAction(identifier: "actionID", title: "buttonTitle", options: [])
let category = UNNotificationCategory(identifier: "CategoryID", actions: [action],
intentIdentifiers: [],
options: .customDismissAction)
UNUserNotificationCenter.current().setNotificationCategories([category])
}

UNUserNotificationCenterDelegate

UNUserNotificationCenterDelegate代替了UIAppDelegate的旧通知接收方法didReceiveRemoteNotification将接收通知的情况分为app开启时(foreground)和app不在前台时(background)

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
// app打开时调用
public func userNotificationCenter(_ center: UNUserNotificationCenter,
willPresent notification: UNNotification,
withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) {
// 业务逻辑
handleNotification(notification)

// 通知不弹出
//completionHandler([])

// 在app内弹通知
completionHandler([.badge, .alert, .sound])
}

// app未打开时调用
public func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void) {
let userAction = response.actionIdentifier
if userAction == UNNotificationDefaultActionIdentifier {
// 点击通知栏本身
log.debug("User opened the notification.")

handleNotification(response.notification)

} else if userAction == UNNotificationDismissActionIdentifier {
// 通知dismiss,category创建时传入UNNotificationCategoryOptionCustomDismissAction才可以触发
log.debug("User dismissed the notification.")
} else if userAction == "actionID" {
// 自定义按钮逻辑
}

completionHandler()
}

// iOS12新功能,在app系统通知设置里点击按钮跳到app内通知设置的回调
public func userNotificationCenter(_ center: UNUserNotificationCenter, openSettingsFor notification: UNNotification?) {
log.debug("Open notification in-app setting.")
}

扩展阅读

行文环境
- Xcode 9.3
- Swfit 4.1
- iOS 11
- Bugly

崩溃分析平台(Bugly)

崩溃平台大多数问题就是缺失dsyMs文件,只要找到crash log对应的dsyMs文件上传重新符号化(Re-symbolicate)即可

找到dsyMs文件的方法

本地文件

mdfind "com_apple_xcode_dsym_uuids == <UUID>"
在bundle打包的电脑上使用上面的命令查找对应UUID的dsyMs文件

xcarchives

在bundle打包的电脑上也可以在Xcode Orgnizer里找到对应Archive

1

2

3

下载 Bitcode dSYMs

第一种方式是在Xcode Orgnizer

第二种方式是在iTC,已发布的bundle推荐使用这种方式

Crash log

崩溃分析平台无法定位的崩溃可以通过直接分析crash log文件

获取设备的crash log

打开Xcode Device页面,并导出crash log

dysMs文件

打开crash log,第一行就是对应bundle的UUID
Incident Identifier: C2FB3056-8C2E-4141-800F-1E575858998D

使用前述的方式找到dysMs文件

符号化

使用Xcode自带的一个命令行工具来符号化crash log文件

/Applications/Xcode.app/Contents/SharedFrameworks/DVTFoundation.framework/Versions/A/Resources/

在这个路径找到该工具,将crash log、symbolicatecrash和dyMs文件夹zip包拷贝到同一目录下

cd到该目录,运行如下命令
export DEVELOPER_DIR="/Applications/Xcode.app/Contents/Developer"

符号化
./symbolicatecrash myCrash.crash > SymbolicatedM.crash

done

更多

符号化之后的崩溃分析不属于本文范围

扩展阅读