本文尝试通过 STAR 原则拆解成Susu这个项目。
- Situation 情景
- Task 任务
- Action 行动
- Result 结果
# 业务介绍
Susu是一个线上的陌生人社交业务。目前全平台日活差不多 xx 万左右,具体在 iOS 上大概是 xx 万左右。
# 个人职责
团队之前是四个人,我的职责
- IM SDK 部分的接入以及界面定制
- 视频 SDK 的接入
- 相关页面的开发
# 项目中用到的技术点
# 推送
使用云信进行推送配置。
# 即时消息(IM)
云信 SDK。
# 视频播放
阿里云视频。
# H5/Native 交互
主要是通过 JSBridge 的方式进行交互
# 项目中遇到的难点
# 动态库延迟加载
问题背景,因为我们之前有个需求就是新装的 App 需要点击进入欢迎页的「进入 Susu」按钮 的时候才弹窗进行网络权限提示,但是通过 cocoapod 引入 facebook 库后,facebook 库实现了 +load() 方法,并且在 +load()
方法里进行了网络请求,这样就等不到用户点击进入 Susu 就弹出了网络权限弹窗,所以现状就是不符合产品的需求。
解决方案就是 将 facebook 从 cocoapod 里面移出来,然后将其作为二进制包嵌入到工程里面。这里面嵌入的 framework 是需要经过签名的,不签名的话会在导出包的时候报错「 code signing FBSDKCoreKit.framework failed 」,所以需要先通过 cocoapod 方式打包成 IPA 方式之后再从里面提取出对应的 framework 文件,可以通过 codesign
命令查看签名信息
> codesign -dvvv /Frameworks/FBSDKCoreKit
Authority=Apple Distribution: xxx
Authority=Apple Worldwide Developer Relations Certification Authority
Authority=Apple Root CA
启动后在合适的时机,比如在用户已经点击过「进入Susu」之后,我们再去通过动态加载 bundle 的方式去加载 facebook 库。
+ (void)loadFBSDKBundle {
static BOOL loaded = NO;
if (loaded) { return; }
NSString *path = [[NSBundle mainBundle].bundlePath stringByAppendingString:@"/Frameworks/FBSDKCoreKit.framework”];
NSBundle *bundle = [NSBundle bundleWithPath:path];
if (!bundle.isLoaded) {
loaded = [bundle load];
}
}
不光要加载 facebook 库还要执行库中对应的方法,因为这种加载方式是没有办法直接引入头文件,正常 Pod 引入的方式是会在 Build Setting
的 Search Paths
下自动对应库的 header 文件的。
我们只能通过调用原始的消息发送的机制去发送消息,通过运行时API,拿到 Facebook 库初始化用到的主类,然后通过 objc_msgSend
方法去进行对应的消息发送。
+ (BOOL)openUrl:(NSURL *)url withOptions:(NSDictionary *)options {
Class FBSDKApplicationDelegateClass = (Class)objc_getClass("FBSDKApplicationDelegate");
FBSDKApplicationDelegate *instance = ((FBSDKApplicationDelegate *(*)(id,SEL))objc_msgSend)(FBSDKApplicationDelegateClass,@selector(sharedInstance));
UIApplication *app = UIApplication.sharedApplication;
BOOL (*objc_msgSendWithSingleType)(id self, SEL _cmd,UIApplication *app,NSURL *url, NSDictionary* launchOption) = (void*)objc_msgSend;
return objc_msgSendWithSingleType(instance, @selector(application:openURL:options:), app, url, options);
}
# 项目中的亮点
# 防 SPAM 抓包
应用中有很多机器人用户来骚扰正常用户,我们判断这些机器人可能是被批量操控的,猜测这些机器人是被放置在某个架子上的,所以这些机器人的陀螺仪不会产生波动的数据,于是业务上设计了,通过客户端定时获取陀螺仪数据,上传到服务端,然后根据服务端对陀螺仪的数据分析,对相关用户执行封禁的操作。
客户端定时获取陀螺仪数据的实现是通过 Runloop
保活线程,然后再 Runloop
上添加定时器的方式来执行对应的任务。
主要代码如下
class TimerPermanentThread: NSObject {
private static var timerPermanentThread:Thread? = nil
@discardableResult
static func buildTimerPermanentThread(_ timeInterval:TimeInterval, _ task:@escaping vv_t) -> Thread {
objc_sync_enter(TimerPermanentThread.self)
defer { objc_sync_exit(TimerPermanentThread.self) }
if timerPermanentThread != nil {
return timerPermanentThread!
}
timerPermanentThread = Thread.init {
Thread.current.name = "com.xx.permanent-thread"
let timer = Timer.init(timeInterval: timeInterval, repeats: true) { _ in
NSLog("TimerPermanentThread")
task()
}
let runloop = RunLoop.current
runloop.add(timer, forMode: RunLoop.Mode.default)
runloop.run()
}
timerPermanentThread?.start()
return timerPermanentThread!
}
}