问题排查:app发热问题

其他参考文章:

CPU或GPU负荷过高是导致手机过度发热的主要原因之一,因此优化应用程序的CPU或GPU负荷是解决这个问题的关键。以下是一些常见的优化方法:

减少图形处理:如果您的应用程序涉及大量的图形处理,例如3D动画、特效等,可以尝试降低图形处理的质量或数量,以减轻GPU的负担(并发对视频进行压缩的时候出现过内存溢出导致崩溃,通过调整为并发数量时候减少)。

优化算法和数据结构:通过优化算法和数据结构,可以减少CPU和GPU的计算量,从而降低负荷。例如,使用更高效的排序算法、数据缓存和内存管理等技术,可以提高应用程序的性能并减少计算量。

使用线程管理:使用线程管理技术可以将应用程序的不同部分分配到不同的线程中,从而减少CPU和GPU的负荷。例如,在主线程中处理用户界面,而在后台线程中处理数据下载和计算等任务。

延迟加载:延迟加载技术可以将应用程序的资源和数据分批加载,而不是一次性全部加载。这样可以减少CPU和GPU的负荷,同时也可以提高应用程序的响应速度和用户体验。

缓存数据:缓存数据可以减少应用程序对网络和磁盘的访问,从而降低CPU和GPU的负荷。例如,将数据缓存在内存中,而不是每次都从网络或磁盘中读取。

使用硬件加速:一些高端手机和平板电脑提供了硬件加速功能,例如GPU加速、硬件解码等。如果您的应用程序需要进行大量的图形或视频处理,可以考虑使用硬件加速来降低CPU和GPU的负荷。

离开web游戏页面停止计时器、停止渲染

调查:

直播的时候也热,正常性能消耗高
网络耗流量

原生交互?指引擎初始化?

CPU
渲染 GPU 图片批量处理,而不是单单
图片处理 图片大小。

接口:http - sockt
定时器个数、
循环精度、

iOS、Android webView的缓存大小

问题排查:app内存问题

一、如何排查内存问题

参考文章:

2内存-③内存泄漏定位.md

二、优化前后数据对比

时刻 目前情况 取消图片缓存扩容设置 去掉TTF文件初始化
打开APP时 img img img
滑动瀑布流 img img img
优化效果说明 优化后,在滑动瀑布流的时候,内存从原来的稳定1.24G,降低到稳定的896M 优化后,打开APP时,内存明显再降低了80M左右

问1:滑动过程中的突刺是什么操作产生?

问2:滑动过程内存增加用户操作是?增加后,内存增加到峰值就没增加的原因?如果估算峰值/峰值是该数值有计算方法?

问3:去掉ttf后,内存降低的数值怎么计算?是1:1的方式?

三、下载的内存分析和优化

以下载以下文件为例,

网络资源:https://media.xihuanwu.com/applet/wishhouse/reward/svga/rocket.svga (13MB)

序号 步骤 Dio 5.2.1
(Flutter)
HTTP 0.13.6
(Flutter)
OkHttp3 4.10.0
(Android)
0 未下载时 同左 同左
1.1 rocket.svga–13MB
下载过程峰值
1.2 rocket.svga–13MB
下载结束后置放
1.3 rocket.svga–13MB
下载结束后切换后台
1.4 rocket.svga–13MB
下载结束后切换后台回前台
2.1 rocket.svga–13MB
下载过程峰值
2.2 rocket.svga–13MB
下载结束后
3.1 rocket.svga–13MB
下载过程峰值
3.2 rocket.svga–13MB
下载结束后
4.1 rocket.svga–13MB
下载过程峰值
4.2 rocket.svga–13MB
下载结束后
5.1 rocket.svga–13MB
下载过程峰值
5.2 rocket.svga–13MB
下载结束后
884M
小结 在5次下载后,内存占用达到884MB,尝试将应用切换至后台、置放、手动GC,内存均无变化。①每次下载内存增加?②下载的内存并没有办法得到释放? 原来的触发下载改为此项后,内存无影响变化

结论:

​ 通过以上数据可以看出,Flutter实现在每次下载时,内存都发生了显著的上升,而原生实现表现平稳,在执行以上操作后,Flutter实现的内存占用达到了 884MB,而原生为 284MB,相比Flutter实现 少了600MB的内存占用。

建议将现有下载转为原生实现,从而降低资源下载时的内存使用。

ARC中方法创建的内存释放

1
2
3
4
5
6
7
8
- (void)exampleMethod {
// 创建一个局部对象,并且有一个强引用
NSObject *object = [[NSObject alloc] init];

// 做一些操作...

// 当对象离开作用域,强引用计数减1,如果为0,则对象被释放
}

@autoreleasepool中的内存释放

1
2
3
4
5
6
7
8
9
10
11
12
13
14
- (void)exampleWithAutoreleasePool {
// 进入一个自动释放池块
@autoreleasepool {
// 创建一个autorelease对象,它会被放入自动释放池中
NSObject *autoreleaseObject = [[NSObject alloc] init];

// 做一些操作...

// 当这个@autoreleasepool块结束时,autoreleaseObject会被释放
}

// 在自动释放池块外,尝试访问autoreleaseObject会导致崩溃,因为它已经被释放了
// NSObject *accessAfterRelease = autoreleaseObject; // 这将导致崩溃
}

埋点方案

埋点方案-页面和视图的进入和离开

一、整个页面进入、离开的监控方案

视图创建基类 LifeCyclePage,使用 route 方法,并提供以下方法

1
2
3
4
5
// 当前页面显示了,底层必须先执行 super.viewDidAppear
void viewDidAppear(AppearBecause appearBecause)

// 当前页面消失了,底层必须先执行 super.viewDidDisappear
void viewDidDisappear(DisAppearBecause disAppearBecause);

二、页面内部分视图进入、离开的监控方案

场景:运行需要知道页面下的tab、页面下的tab的《子tab/子视图》的浏览时间。

方案1:点击–view_show/hide–不推荐

使用点击 view_show/hide 方案,实现步骤如下:

点击tab1:记tab1 view_show 事件,并记下当前显示的tab,备后续切换的时候为本tab添加 view_hide 事件。

点击tab2:记tab1 view_hide 事件,tab2 view_show 事件,并记下当前显示的tab,备后续切换的时候为本tab添加 view_hide 事件。

缺点:tab点击时候,记下当前显示的tab,备后续切换的时候为本tab添加 view_hide 事件。

方案2:视图是否显示监控–visibility–推荐方案

视图创建基类 LifeCycleView,使用 visibility 方案,并提供以下方法

1
2
3
4
5
// 当前页面显示了,底层必须先执行 super.viewDidAppear
void viewDidAppear(AppearBecause appearBecause)

// 当前页面消失了,底层必须先执行 super.viewDidDisappear
void viewDidDisappear(DisAppearBecause disAppearBecause);

2.1、原理:创建 VisibilityDetector 视图类,并设置 onVisibilityChanged 回调方法;

1
2
3
4
5
6
7
const VisibilityDetector({
required Key key,
required Widget child,
required this.onVisibilityChanged,
}) : assert(key != null),
assert(child != null),
super(key: key, child: child);

优点:不用每个视图(如tab页)去处理。

总结

页面和视图的进入和离开方案,总结如下:

序号 类型 方案 优点与不足
1 页面 LifeCyclePage + route 能比view增加具体显示和离开的原因
2 页面内的视图 LifeCycleView + visibility 只能知道显示、还是离开

附一、Flutter 曝光埋点

1、使用 VisibilityDetector 监听子组件可见性的变化(什么时候触发变化)

2、

附二、iOS tableview的曝光埋点

其他参考文档:iOS-曝光埋点

  1. 使用UIViewwindow属性:可以通过检查视图的window属性是否为空来判断视图是否在屏幕上可见。如果window属性为非空值,表示视图正在显示。
  2. 使用CGRectCGRectIntersectsRect函数:可以通过将视图的frame转换到屏幕坐标系中,然后与屏幕的bounds进行比较,来确定视图是否在屏幕上可见以及可见的比例。
  3. **使用CADisplayLink**:可以通过CADisplayLink来定期检查视图的可见性。在每次屏幕刷新时,都可以检查视图的window属性和frame,从而实现对视图可见性的实时监控。
  4. 使用isDisplayedInScreen类别方法:可以通过为UIView添加一个类别,并在该类别中实现isDisplayedInScreen方法,来判断视图是否在屏幕上可见。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// UIView+isDisplayedInScreen.h


// 使用CADisplayLink定期检查
- (void)startMonitoringViewVisibility:(UIView *)view {
CADisplayLink *displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(checkViewVisibility:)];
[displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
}

- (void)checkViewVisibility:(CADisplayLink *)displayLink {
// 在这里检查视图的可见性
}

// 检查视图是否在屏幕上可见
- (BOOL)isViewVisible:(UIView *)view {
CGRect viewRect = [view convertRect:view.bounds toView:nil];
CGRect screenRect = [UIScreen mainScreen].bounds;
CGRect intersectionRect = CGRectIntersection(viewRect, screenRect);
CGFloat visibleArea = CGRectGetArea(intersectionRect);
CGFloat viewArea = CGRectGetArea(view.bounds);
return (visibleArea / viewArea) > 0.5;
}

1、Flutter visibility_detector 原理解析

使用方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@override
Widget build(BuildContext context) {
return VisibilityDetector(
key: Key('exposure_$hashCode'),
onVisibilityChanged: (visibilityInfo) {
var visiblePercentage = visibilityInfo.visibleFraction * 100;
var inScreen = visiblePercentage >= widget.exposeFactor;
if (inScreen != show) {
if (inScreen) {
_onExpose();
} else {
_onHide();
}
}
},
child: widget.child,
);
}

底层核心逻辑代码如下:

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
// 1、VisibilityDetector 继承自 SingleChildRenderObjectWidget 类
class VisibilityDetector extends SingleChildRenderObjectWidget

// 2、 重写其 createRenderObject(BuildContext context) 方法 返回RenderVisibilityDetector
@override
RenderVisibilityDetector createRenderObject(BuildContext context) {
return RenderVisibilityDetector(
key: key,
onVisibilityChanged: onVisibilityChanged,
);
}
}


class RenderVisibilityDetector extends RenderProxyBox {
RenderVisibilityDetector({
RenderBox child,
@required this.key,
@required VisibilityChangedCallback onVisibilityChanged,
}) : assert(key != null),
_onVisibilityChanged = onVisibilityChanged,
super(child);

// 省略一些代码...

// 3、在 RenderVisibilityDetector 中,重写绘制方法 paint(),push 一个用于监听可见状态的 Layer(VisibilityDetector)
@override
void paint(PaintingContext context, Offset offset) {
if (onVisibilityChanged == null) {
VisibilityDetectorLayer.forget(key);
super.paint(context, offset);
return;
}

final layer = VisibilityDetectorLayer(
key: key,
widgetSize: semanticBounds.size,
paintOffset: offset,
onVisibilityChanged: onVisibilityChanged);
context.pushLayer(layer, super.paint, offset);
}
}

在 RenderVisibilityDetector 中,在绘制方法 paint() 里 push 了一个 Layer,这个Layer就是 VisibilityDetector 用于监听可见状态的关键。

End

埋点规范

参考文档:

埋点作用

曝光率(曝光率= 点击量 / 曝光量)来分析用户(比如用户的喜好推荐数据的统计)

一、埋点入参

1、【固定的】公共入参

1.1、【固定的】的【基本】公共入参7个

建议和《基础规范-请求规范》 中的request公共入参保持一致

属性名 说明 备注
app_id App的唯一标识 com.ciyouzen.beyond
app_name App的应用名称 XXX测试版
app_version App的应用版本 1.16.30
app_buildNumber App的应用编译号 16301610
channel_name 渠道名称 《请求规范》 中的request公共入参的渠道】
platform/lib SDK 类型 Android/iOS
account_id 用户唯一标识 用户没有登录时,客户端自动生成唯一标识

1.2、【固定的】的【设备及系统信息】公共入参

属性名 说明 备注
systemVersion/lib_version 操作系统版本 16.1.1
manufacturer 设备制造商
brand 设备品牌 iPhone
model 设备型号 iPhone
os 操作系统 iPhone13,3
os_version 操作系统版本
screen_width 屏幕宽度 390.0
screen_height 屏幕高度 844.0
screen_devicePixelRatio 设备像素比率 3.0
carrier SIM 卡的运营商名称
device_id 设备ID Android 端主要取 Android ID ,
iOS 端先尝试获取 IDFA,如果获取不到,则取 IDFV
geo_coordinate_system 坐标系:iOS 端默认为 WGS84, Android 端需要手动传入 bd09ll

1.3、【固定的】的【其他】公共入参

属性名 说明 备注
lib_method 埋点方式 固定值为code

2、【可变的】的Body入参

2.1、单记录Body:【可变的】的【基本】入参

属性名 说明 备注 是否可全局按需时时获取
network_type 网络类型
mobile / wifi / bluetooth / vpn / other
mobile
timezone App 或系统的时区
Duration timezoneOffset = DateTime.now().timeZoneOffset;
Asia/Shanghai
timezone_offset 时区偏移量(以分为单位)
int timezoneOffsetMinutes = timezoneOffset.inMinutes;
480
latitude GPS信息-纬度
longitude GPS信息-经度
request_time 单条记录生成时间,精确到毫秒 批量时,多条应是同一个时间

2.2、单记录Body:【可变的】的【其他】入参

属性名 说明 备注
param 服务端下发 如算法标识等
event_name 事件名称 如“AppInstall”
event_attr 事件属性对象集合 event_name=”AppInstall”,event_attr={“accountId”:”1234”,”city”:”厦门”}

1、event_attr 希望的其他参数

属性名 说明 备注 是否可全局按需时时获取
cur_page 当前页面,用于标明 position/scene “goods_detail_page” 可全局记录旧页面
cur_view 当前视图,用于标明 position/scene “sku_choose_window” 必须请求单独设置
referrer_title 前一个页面标题

二、埋点事件

1、App事件

事件描述 事件名
冷启动 appStart
关闭应用 appEnd
返回前台/热启动 appResumed
进入后台 appPaused

1.1、打开/冷启动 appStart

参数 说明 示例
—- —-

1.2、关闭应用 appEnd

参数 说明 示例
—- —-

1.3、返回前台/热启动 appResumed

参数 说明 示例
—- —-

1.4、进入后台 appPaused

参数 说明 示例
duration 页面停留的时长(毫秒)

2、页面停留事件

事件描述 事件名
显示页面 viewDidAppear
离开页面 viewDidDisappear
1
2
3
4
5
enum AppearBecause {
newCreate, // 新显示
pop, // 从其他界面pop回来的
resume, // 从后台进入前台
}
1
2
3
4
5
enum DisAppearBecause {
goNew, // 去新的页面
pop, // 退出当前界面
pause, // 从前台进入后台
}

2.1、原生页面停留

2.1.1、进入页面 viewDidAppear
参数 示例 其他
page 页面类名 Goods_detail_page
cause 进入的方式 newCreate / pop
2.1.2、离开页面 viewDidDisappear
参数 说明 示例
page 页面类名 Goods_detail_page
cause 离开的方式 goNew / pop
duration 页面停留的时长(毫秒)
2.1.3、页面/视图进入、离开的监控方案

页面/视图进入、离开的监控方案:ShowMonitorMethod,详见《埋点方案-页面和视图的进入和离开

2.2、游戏页面停留

2.2.1、进入页面 viewDidAppear
参数 示例 其他
page 页面类名 WebViewPage
cause 进入的方式 newCreate / pop
url 网页地址
2.2.2、离开页面 viewDidDisappear
参数 说明 示例
page 页面类名 WebViewPage
cause 离开的方式 newCreate / pop
duration 页面停留的时长
url 网页地址

3、页面加载事件

事件描述 事件名
页面加载开始 firstStart
页面首帧绘制结束 firstAppFrameEnd
页面首屏绘制结束
(取到数据即算,不论是后台还是网络)
firstUserScreenEnd
页面首屏绘制结束
(以相关请求的最后一个为结束为结束)
firstNetworkScreenEnd

3.1、页面加载开始 firstStart

参数 示例 其他
page 页面类名 GoodsDetailPage

3.2、页面首屏绘制结束 firstNetworkScreenEnd

参数 说明 示例
page 页面类名 GoodsDetailPage
requestCount 请求的网络个数 3
duration 页面加载的时长(毫秒)

4、点击与曝光事件(非页面)

描述 点击事件名 曝光事件名 属性示例
商品 click_goods exposure_goods {“id”: id, “sku_id”: sku_id}
用户 click_user exposure_user {
“userId”: userId,
“userType”: avatar/name/other
}
订单 click_order exposure_order {“id”: id}
卡片 click_card exposure_card {
“cardId”: cardId,
“cardType”: user/goods/banner,
“cardOwnerId”: cardOwnerId
}
tab(一个页面多tab) click_tab
(使用该tab的视图曝光)
{“tab_name”: tab_name}
照片切换 click_photo_change 无(不需要) {“count”: 3, “old_index”: 0, “cur_index”: 2}
照片浏览 click_photo_browse 无(不需要) {count”: 3, “index”: 0}

4.1、卡片参数

参数 说明 示例
cardPosition/cardScene 卡片位置/场景
cardType 卡片类型(banner、user、goods)
cardId 卡片id
cardOwnerId 卡片持有者id(可空)

三、埋点数据不准的修复

1、启动app的人数

序号 方案 结果
1 通过appStart计算启动app的人数 不准确❌
2 通过登录后必进入的接口请求 准确✅
3

方案1:通过appStart计算启动app的人数,结果不准确

原因说明:

appStart 用户为冷启动(杀掉再进)
当用户未登录:有appStart,但没userId。
进行登录后,有首页的访问记录,但是appStart已上报过。

所以,首页的访问记录 >= appStart(99.99999%是大于)

其他:
userId登录时候,未登录无;deviceId:登录和未登录都有

2、页面的停留时长

页面的停留时长不应该只以系统didAppear和disAppear为判断依据。

还要扣除页面进入后台的时间。

解决:

进入后台的时候,触发消失的埋点;进入前台的时候触发显示的埋点。

相当于,如果用户 进入页面 – 退到后台 – 返回前台 – 离开页面

会等价于:显示 – 消失 – 显示 – 消失 。两次记录。而不是只有 进入页面– 离开页面的一次记录。

3、未显示的tab页面,也有使用时长

4、后台筛选出的数据不准确

可能原因:

4.1、未区分版本

可能旧版本有问题,新版本上修复后,筛选的数据要以新版本为主。

举例:

4.2、会区分时间段

比如:计算页面的在某一天的时候时长。

实际举例:用户从昨天22:59开始进入app或者一直在app内的某个页面,直到第二天的23:59才退出。

则app获取到的数据是,用户使用了25个小时的应用。

如果计算当天,实际上,只用了23小时59分钟。

为方便后台计算该次记录的初始显示时间,客户端在消失的埋点里添加显示的时间。

四、埋点的数据上报

将监听到的数据添加到 final List<Map<String, dynamic>> _buriedPointModels = []; 中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
	_init() {
// print('BuriedPointManager _init');
/// 每5秒上传一次,避免请求太多
_uploadTimer = Timer.periodic(const Duration(milliseconds: 5000), (timer) {
_uploadBuriedPoints();
});
}

addEvent(String eventName, Map<String, dynamic> eventAttr) async {
Map<String, dynamic> eventMap = {};

eventMap.addAll({
"event_name": eventName,
"event_attr": eventAttr,
"request_time": DateTime.now().millisecondsSinceEpoch, // 单条记录生成时间,精确到毫秒
});

_buriedPointModels.add(eventMap);
}

五、曝光

曝光(曝光结束才计算曝光。曝光过程不计算,那初始曝光的怎么办)

当然我们对滑动曝光有一些额外的要求:

  • 需要滑出一定比例(只出现一点点不算)、一定时间(太快划走不算)的时候才出发曝光
  • 滑出视野的模块,再次滑入视野时需要再次上报
  • 模块在视野中上下反复移动只触发一次曝光(还未实现)

六、全埋点

参考文章:

1、点击自动埋点 autoTrackClick

参数 示例 其他
path 按钮路径
content 按钮上的文本,如果存在
screentName 当前页面名,同于page,却值不同

七、埋点页面名

1、一级页

页面描述 页面类名 页面埋点名
主页
作品首页 WishHomePage WishHomePage
商城首页 MallHomePage MallHomePage
发布页面
消息首页
我的首页

1.1、一级页中的标签页

页面描述 页面类名 页面埋点名
作品–推荐
作品–同城 WishDiscoverNearby WishDiscoverNearby
作品–热单 WishDiscoverHot WishDiscoverHot
作品–关注 WishDiscoverFriend WishDiscoverFriend

2、二级页

2.1、作品相关

页面描述 页面类名 页面埋点名
作品–推荐–推荐用户
作品–同城–
作品–热单–获豆榜 RankBeanWidget RankBeanWidget
作品–热单–愿望榜 RankWishWidget RankWishWidget
作品–关注–关注/粉丝/互关
作品详情页

2.2、商品相关

页面描述 页面类名 页面埋点名
商品详情页 GoodsTaDetailPage GoodsTaDetailPage

2.3、发布相关

页面描述 页面类名 页面埋点名
添加商品页

2.4、消息相关

页面描述 页面类名 页面埋点名
消息–收到的打赏 MsgCostPage MsgCostPage
消息–实现的愿望 MsgWishPage MsgWishPage
消息–新增的关注 MsgFocusPage MsgFocusPage
消息–互动消息–收到的评论 MsgCommentPage MsgCommentPage
消息–互动消息–发表的评论 InteractiveMessageListPage InteractiveMessageListPage
消息–互动消息–赞过的评论 InteractiveMessageListPage InteractiveMessageListPage
消息–系统消息
消息–聊天页面

2.5、我的相关

页面描述 页面类名 页面埋点名
我的–关注 MyFriendsPage MyFriendsPage
我的–粉丝 MyRelationPage MyRelationPage
我的–愿望豆 StarHistoryPage StarHistoryPage
我的–我的农场
我的–我的订单 MyOrderMainPage MyOrderMainPage
我的–我的足迹 RecordPage RecordPage
我的–我的收藏 CollectPage CollectPage

3、其他

页面描述 页面类名 页面埋点名
意见反馈 FeedbackPage FeedbackPage
平台客服 APPIMChat APPIMChat

End

瘦身-③app安装包

一、查看包大小

序号 类型 描述 其他
1 下载大小 此值为通过无线下载的压缩 App 大小。
2 安装大小 此值为此 App 将在用户设备上占用的磁盘空间大小。

1、查看iOS包大小

1、进入官网 https://appstoreconnect.apple.com/apps

2、选择指定app –> TestFilght –> 指定版本 –> 指定构建号

3、查看内容如下

app_package_size_lookup_ios

二、如何瘦身

iOS APP瘦身优化

Flutter 如何缩减接近 50% 的 Flutter 包体积

三、项目示例

assets_generator

Python3安装xlsxwriter

网络ApiMock

网络Api mock

一、背景

为了避免后端因为其他任务,来不及开发接口,特意建立mock环境,以此来保证后续出现类似情况,app也能够正常的开发,并能够在后端开发结束时候,快速完成整体的联调,从而在某个环节断档的情况下,也能够保证整体的进度。

二、功能介绍

  • 远程模拟
  • 本地模拟

三、远程模拟

1、远程界面样式

目前该环境,已对现有所有app接口实现mock。

image-20220119193932769

2、使用简介

2.1、修改地址

如果你已在api mock后台创建了模拟的接口,则只需要在项目中进行如下修改即可,其会自动将所请求的地址进行模拟。

1
2
3
4
static String getVirtualList = "/account/wallet/virtualAsset/page"

//模拟时候,在字符串尾部加上`.toSimulateApi()`即可
static String getVirtualList = "/account/wallet/virtualAsset/page".toSimulateApi();

2.2、修改mock数据

因为从Swagger同步过来的接口,其返回值不是完整的response结构。

完整的response={“code”:0, “msg”: “成功”, data:xxx};

同步的response=data

所以为避免每个接口都得重复的去添加response外层来调整结构,我们通过在本地网络上进行兼容,从而实现了即使你没对同步的接口进行操作,也能够直接调用请求,得到完整的数据结构。

四、本地模拟

1、使用简介

以模拟/account/wallet/wishStar/page接口为例

1.1、添加本地json

在项目中asset下的的data文件夹里添加该以请求路径命名的json文件。

image-20220322134145737

1.2、修改地址

将请求地址的尾部加上.toLocalApi(),即可自动将所请求的地址进行本地数据模拟。

1
2
3
4
static String wishStar = "/account/wallet/wishStar/page"; //愿望星流水明细

//模拟时候,在字符串尾部加上`.toLocalApi()`即可
static String wishStar = "/account/wallet/wishStar/page".toLocalApi(); //愿望星流水明细

_random.nextDouble() 获取的是0-1之间不为负数的小数

四舍五入

1
2
var foo = 6.28;
print(foo.round()); // 6

End

网络扩展

一、背景

TPFVPN

NetworkExtension2-Client开发

NetworkExtension3-Tunnel开发

NetworkExtension4-Server开发

NetworkExtension5-App和Extension通信

https://github.com/cgcym1234/YYVPN

移动/PC客户端流量拦截与转发

深信服移动终端EasyApp SDK介绍

其他github搜索结果:https://github.com/search?q=vpn+language%3AObjective-C+&type=repositories&s=stars&o=desc

其他文章:App extension实战 - NetworkExtension 讲解连接并捕获packet

End

Swift扩展(Extensions)

入门二

  • 一、Swift扩展(Extensions)

一、Swift扩展(Extensions)

Swift基础部门:http://www.swift51.com/swift4.0/chapter2/01_The_Basics.html

详见:http://www.swift51.com/swift4.0/chapter2/21_Extensions.html中的2.21扩展

在swift中**extension(扩展)与Objective-C的category(分类/扩展/类别)**有点类似,但是extension比起category来说更加强大和灵活,它不仅可以扩展某种类型或结构体的方法,同时它还可以与protocol等结合使用,编写出更加灵活和强大的代码。

二、

Swift3.0语法变化

swift 枚举的取值

Swift中协议的可选方法的实现判断

二、第三方库

OC第三方库需要在桥街文件中引入,swift第三方库需要import model方式引入!桥街文件不能引入swift第三方库

  • Network:

Alamofire:著名的AFNetworking网络基础库Swift版 - Alamofire/Alamofire · GitHub

SwiftyJSON:最为开发者认可的JSON解析类 - SwiftyJSON/SwiftyJSON · GitHub

  • Storage:

SQLite.swift:简单、轻量,使用上最SQL的SQLite封装库 - stephencelis/SQLite.swift · GitHub

SugarRecord:基于CoreData与REALM的好用封装 - SugarRecord/SugarRecord · GitHub

  • UI:

SweetAlert:带动画效果弹窗封装类 - codestergit/SweetAlert-iOS · GitHub

RAMAnimatedTabBarController:灵动的动画标签栏类库 - Ramotion/animated-tab-bar · GitHub

PNChart-Swift:带动画效果的图表控件库 - kevinzhow/PNChart-Swift · GitHub

LTMorphingLabel:各种文字动画效果 - lexrus/LTMorphingLabel · GitHub

Cartography:用代码解决麻烦的AutoLayout - robb/Cartography · GitHub

other:

pod ‘SVProgressHUD’

pod ‘MJRefresh’

pod ‘SnapKit’

Swift常用第三方库

2017最受欢迎的30个Swift 库,你关注到了吗?

Swift常用第三方库

iOS:开发随笔

开发随笔

iOS macOS Xcode报错: The file couldn’t be opened

不管是打开 .xcworkspace 还是 .xcodeproj 项目里的文件目录都没法显示。

出现这个的主要原因一般是不同电脑里面安装的系统和Xcode版本不一致导致的。提示“The file couldn’t be opened”。

解决方法:

优先尝试的方法,修复Xcode编译版本号,步骤如下:
选择.xcodeproj的包文件 –> 右键Show Package Contents 后,用记事本打开里面的 project.pbxproj –> 搜索objectVersion修改Xcode编译版本号。

如果上诉方法不行,去除当前Xcode识别不了的字段,步骤如下:

Xcode 版本 引入/支持情况
Xcode 14.2 及之前 不支持 PBXFileSystemSynchronizedRootGroup
Xcode 15+ 开始支持这种新的文件同步机制
Xcode 15.3+ 完整支持并可能自动生成这类配置

iOS研发助手DoraemonKit

iOS研发助手DoraemonKit技术实现(一)

https://www.jianshu.com/p/00763123dbc4

iOS 常用调试方法:LLDB命令

iOS 线程保活

iOS RunLoop(一)

iOS中NSOperation详解

iOS 逆向开发16:HOOK原理上(HOOK 系统C函数)

另类iOS上的C函数hook

iOS被开发者遗忘在角落的NSException-其实它很强大

UITextView对齐

textView中的文字默认离顶部和底部是有间距的

1
2
3
4
UITextView *textView = [[UITextView alloc] init];
NSLog(@"...%@", NSStringFromUIEdgeInsets(textView.textContainerInset)); // 默认值{8, 0, 8, 0}

textView.textContainerInset = UIEdgeInsetsZero;

iOS 侧滑返回详解FDFullscreenPopGesture

https://www.jianshu.com/p/62015b4c9076

哆啦A梦 只能显示nslog,无法显示debugPrint

podfile中 use_frameworks! 和 #use_frameworks!区别

https://www.jianshu.com/p/ac629a1cb8f5

iOS 效果处理(内阴影、外阴影、外发光、内发光、投影)

https://blog.csdn.net/qq_34534179/article/details/109180717

IOS微信分享调起微信后立刻返回到app中无法分享的问题

https://blog.csdn.net/qq_35153373/article/details/112285261

iOS 14 popToRootViewControllerAnimated 底部tabbar消失

https://www.jianshu.com/p/c6b3ccff9e5b

美图秀秀的拼图功能

https://github.com/hxxyyangyong/MeituDemo

iOS 不规则(多边形)图形,贝塞尔曲线绘制自定义图形

https://www.jianshu.com/p/6786cc4d28a7?from=groupmessage

iOS UIButton 渐变色、边框渐变色、字体渐变色

https://blog.csdn.net/liwenjie0912/article/details/87548692

textfield

1、不要delloc

2、不要delegate = self. shouldchange不调用

3、二分法查找

iOS根据网络图片的size大小设置UIImageView的大小

https://www.cnblogs.com/sunfuyou/p/6284986.html

iOS 如何让button上的字体居左居右对齐

https://www.jianshu.com/p/737553cd8eb5

iOS 使用CGAffineTransform 使视图平移|旋转|缩放

https://www.jianshu.com/p/58d810cec41d

UILabel *titleLabel = [UILabel alloc]; 空指针

UILabel *titleLabel = [[UILabel alloc] init];

[container addSubview:titleLabel];

iOS开发 怎么删除UICollectionView的cell

https://jingyan.baidu.com/article/ceb9fb10bd32a08cac2ba053.html

UICollectionView执行performBatchUpdates 奔溃

https://blog.csdn.net/jamy08/article/details/50505859?utm_source=blogxgwz7

ios扩大按钮的点击区域

https://www.jianshu.com/p/9107be4cd84a

iOS 利用UICollectionView横向滚动、余弦函数曲线特性实现居中放大的卡片浏览工具 XLCardSwitch

https://blog.csdn.net/u013282507/article/details/54136812

iOS之事件穿透

https://www.jianshu.com/p/0bece5f27650

iOS 13-beta presentViewController 样式变化

https://www.jianshu.com/p/67901ae2323d

cocoapods 1.8.0版本之后,CDN: trunk 推荐解决方法

https://blog.csdn.net/ZHFDBK/article/details/106949342

Commond + alt + /

Pasted Graphic.png