Objective-C 新老静态SDK共存方案

背景 ​ 本文将介绍一种在有源码的情况下,新老版本静态SDK共存于同一应用的方法实现。 ​ 静态SDK升级并上线后,可能会发现新版本SDK有问题而老版本SDK没有问题的现象,这时对于已经升级的应用将无法直接回退到老版本SDK,除非一开始就新老版本SDK同时存在于这个应用中。 ​ 对于Objiective-C这种没有命名空间的语言,当一份代码构建出两个静态库,且两个静态库同时链接于同一个应用时(同一个应用同时链接这个两个静态库),那么将会发生符号冲突问题—-符号重定义(duplicate symbol);而解决符号重定义的办法就是将符号重命名,当然删除符号也可以—-不过这样可能引起符号为定义错误。 ​ 就Objiective-C而言,符号重命名需要修改源码。对于静态SDK工程来说,第一步是需要先将类名、函数名、全局变量名这些符号列举出来,然后逐一添加前缀或者后缀, 对于影响业务逻辑的地方还需要斟酌修改,特别是Objiective-C的运行时反射更要注意。例如像NSClassFromString,相关代码添加前缀或者后缀要仔细。 模块化拆分 ​ 这步骤不是必须,但是强烈推荐的。 ​ 就双静态SDK共存这事来看,模块化拆分的唯一目的是减少需要修改的符号数量:例如,通常情况下像AFNetworking这种工具库是没有必要添加前缀或者后缀的。 ​ 所以,拆分的唯一原则:将长期不修改的代码拆分出来,这部分可以作为双SDK的公共库。 导出符号列表 ​ 常见的方法是使用 nm 工具,可以导出SDK里的符号列表。也可采用分析 linkmap 文件的方式,导出SDK里的符号列表。 ​ 需要导出的符号有: 类名 函数名—非类所属 全局变量名 导出的全局变量名如果是静态变量名,一般不需要添加前缀或者后缀。不一般的情况,非静态全局变量引用了静态变量,这时就要处理下。 添加前缀或者后缀 ​ 这里推荐使用预编译宏技术,定义一个宏并使用 ## 连接符 #define SUFX (x) x##_我是后缀 // 类 @interface SUFX(MyClass) ---> @interface MyClass_我是后缀 [SUFX(MyClass) method] ---> [MyClass_我是后缀 method] // 函数 void SUFX(MyFunc) ---> void MyFunc_我是后缀 //变量 int SUFX(MyVar) ---> int MyVar_我是后缀 给Objective-C添加命名空间? 最后,介绍下llvm编译插件,增加命名空间语法支持,从根本上解决问题。不过,尚未完成。。。 ...

June 17, 2022 · 1 min · holdsky

JavaScript兼容性检查(JavaScript compatibility check)

查阅资料,找到以下 使用https://caniuse.com/ 数据, 对应的在线工具为 https://seedmanc.github.io/jscc/ 。不过,这个在线工具还有待完善,检查结果漏了很多。 另外一个是,基于eslint的兼容性检查插件, https://github.com/amilajack/eslint-plugin-compat ,推荐使用这个,可以配置各种浏览器、语法版本、很灵活

May 26, 2022 · 1 min · holdsky

iOS关于音频焦点的资料整理

内容来自互联网,使用了google翻译。 简介 ​ 在iOS中,音频焦点是操作系统为了管理音频硬件而引入的概念,是指App对音频硬件在时间纬度上的使用控制(不是使用权限,使用权限需要App单独申请);App可以独占使用,也可以共享使用—-独占/共享音频焦点。 ​ 独占音频焦点,则只能有一个App使用音频硬件;共享音频焦点,则多个App可以同时使用音频硬件,通常共享音频焦点会导致多个App播放的音频被混音。 ​ iOS中还有个音频会话的概念,在一个App中只有一个音频会话。这里需要注意下,iOS系统只有一个音频焦点,所以App要想使用音频硬件,需要使用音频会话来抢占音频焦点。 ​ 抢占音频焦点示例: // 一个App内所有场景/任务都共享同一个音频会话 AVAudioSession *session = [AVAudioSession sharedInstance]; //获取共享单例 //设置音频硬件使用模式 NSError *error = nil; if ([session setCategory:AVAudioSessionCategoryAmbient error:&error] == NO) { NSLog(@"%@",error); //发生错误 return; } //抢占音频焦点 error = nil; if ([session setActive:YES error:&error] == NO) { NSLog(@"%@",error); //发生错误 return; } ​ 上述代码中,先设置 AVAudioSessionCategoryAmbient 模式,表示应用会随着静音键和屏幕关闭而静音,并且不会中止其它应用播放声音(可以和其它自带应用如iPod,safari等同时播放声音),然后再使用 setActive 抢占音频焦点。 释放音频焦点 ​ 我们的App抢占音频焦点后,可能会导致其他App失去音频焦点,所以当我们使用完音频硬件时,应将音频焦点释放出来。 // 一个App内所有场景/任务都共享同一个音频会话 AVAudioSession *session = [AVAudioSession sharedInstance]; //获取共享单例 //释放音频焦点 NSError *error = nil; if ([session setActive:NO error:&error] == NO) { NSLog(@"%@",error); //发生错误 return; } 释放音频焦点还可以这样: ...

March 28, 2022 · 2 min · holdsky

【CEF3学习】– 在同一窗口打开链接

本次着手实现将 target="_blank“的行为修改为 target="_self"。 CEF3提供了UI窗口生命周期接口 CefLifeSpanHandler,借助这个接口来监听新窗口创建事件,并在创建前取消窗口创建,使用已有窗口来加载新链接。 class CefLifeSpanHandler : public virtual CefBaseRefCounted { public: ///由UI线程调用,调用时机是新浏览窗口创建前。 // frame 弹出新窗口链接的源frame(即从哪个frame里的链接弹出新窗口) // target_url 目的链接 // target_frame_name 目的frame名字 // 返回true,则表示要取消窗口创建 virtual bool OnBeforePopup(CefRefPtr browser, CefRefPtr frame, const CefString& target_url, const CefString& target_frame_name, WindowOpenDisposition target_disposition, bool user_gesture, const CefPopupFeatures& popupFeatures, CefWindowInfo& windowInfo, CefRefPtr& client, CefBrowserSettings& settings, CefRefPtr& extra_info, bool* no_javascript_access) { frame->LoadURL(target_url); //在当前frame中加载链接 return true; //返回true,取消窗口创建 }

March 23, 2022 · 1 min · holdsky

【CEF3学习】-- 文件下载管理

CEF3关于文件下载的开放接口是 CefDownloadHandler,这个类的公开函数也挺简单的,就两个:一个是下载前 OnBeforeDownload,一个是下载状态更新 OnDownloadUpdated class CefDownloadHandler : public virtual CefBaseRefCounted { public: /// 文件下载前调用 // suggested_name (建议的)文件保存名 // callback 回调,用来通知下载动作继续进行。默认情况下,如果不主动调用这个回调,则会取消下载动作。 virtual void OnBeforeDownload( CefRefPtr browser, CefRefPtr download_item, const CefString& suggested_name, CefRefPtr callback) = 0; /// 用来通知下载状态信息,包括下载进度、总大小、已完成大小、是否完成等;这个方法可能会被调用多次,以用来持续更新下载状态 // download_item 下载项,下载的状态信息保存在这里。 // callback 回调,用来取消/暂停/恢复/下载。默认情况下,如果不主动调用这个回调,则会继续下载。 virtual void OnDownloadUpdated(CefRefPtr browser, CefRefPtr download_item, CefRefPtr callback) {} }; 需要注意的是,上面两个方法都有一个 download_item 参数,这个参数是不能在上面两个方法的作用域外使用的。

March 23, 2022 · 1 min · holdsky

【CEF3学习】-- 修改User-Agent

本文要实现的是在 右键菜单 中添加一个修改User-Agent功能 定义一个变量,用来控制是使用默认UA还是Android UA static bool s_UerAgentAndroid = false; 添加 右键菜单 // 添加菜单枚举 enum client_menu_ids { CLIENT_ID_SHOW_DEVTOOLS = MENU_ID_USER_FIRST, ... .... CLIENT_ID_UserAnget, }; // 修改菜单项和菜单事件处理 void ClientHandler::OnBeforeContextMenu(CefRefPtr browser, CefRefPtr frame, CefRefPtr params, CefRefPtr model) { ...... ...... //添加菜单项 model->AddItem(CLIENT_ID_UserAnget, s_UerAgentAndroid ? "默认UA":"Android UA"); if (delegate_) delegate_->OnBeforeContextMenu(model); } bool ClientHandler::OnContextMenuCommand(CefRefPtr browser, CefRefPtr frame, CefRefPtr params, int command_id, EventFlags event_flags) { CEF_REQUIRE_UI_THREAD(); switch (command_id) { ...... ...... //菜单事件处理 case CLIENT_ID_UserAnget: s_UerAgentAndroid = !s_UerAgentAndroid; browser->Reload(); // 修改UA后重新加载网页 return true; default: // Allow default handling, if any. return ExecuteTestMenu(command_id); } } 下面就是对每一个请求修改HTTP报文的User-Agent。CEF3会将网页所有资源请求事件通知给 CefRequestHandler 或其子类处理,其 OnBeforeResourceLoad 是在http请求发出前的处理点,修改相应内容,代码如下 ...

March 9, 2022 · 1 min · holdsky

【CEF3学习】-- 添加右键菜单

cefclient这个项目启动后,默认情况下会有下图所示的右键菜单 我们要做的就是仿照这个菜单,添加新的菜单项。 根据关键字搜索,可以找到client_handler.cpp文件,有两函数 OnBeforeContextMenu、 OnContextMenuCommand,用来处理菜单项。修改这两个函数,增加新的菜单项,例如重新加载当前页面。 /// enum client_menu_ids { CLIENT_ID_SHOW_DEVTOOLS = MENU_ID_USER_FIRST, .... CLIENT_ID_TESTMENU_RADIOITEM3, CLIENT_ID_RELOAD_PAGE /// 添加一个菜单项枚举 }; void ClientHandler::OnBeforeContextMenu(CefRefPtr browser, CefRefPtr frame, CefRefPtr params, CefRefPtr model) { ///在菜单显示前,处理菜单项 .... model->AddItem(CLIENT_ID_RELOAD_PAGE, "重新加载页面"); .... } bool ClientHandler::OnContextMenuCommand(CefRefPtr browser, CefRefPtr frame, CefRefPtr params, int command_id, EventFlags event_flags){ ///对应菜单项的响应处理 switch (command_id) { case CLIENT_ID_RELOAD_PAGE: browser->Reload(); return true; case CLIENT_ID_SHOW_DEVTOOLS: ..... ... } } ...

March 7, 2022 · 1 min · holdsky

【CEF3学习】-- 生成IDE的工程文件

下载下来的CEF压缩包解压后,里面包含CEF接口源码、示例、静态库等资源,并提供cmake脚本文件以生成IDE工程文件;使用CMake UI来操作这些脚本文件。 首先,填写好CEF解压后的路径(包含CMakeLists.txt的目录),IDE工程文件的输出路径(可以写和CMakeLists.txt相同的目录) 然后,点击 Configure 按钮,配置IDE参数 正常情况下,下图到参数是自动生成的 点击 Generate 按钮,生成IDE工程文件 打开IDE工程文件,直接以Debug模式运行 cefclient 这个项目,可以看到一个十分简单的浏览器页面出现了

March 7, 2022 · 1 min · holdsky

【CEF3学习】-- 下载构建好的版本

CEF全称Chromium Embedded Framework,是一个基于Google Chromium 的开源项目。相关资料在https://bitbucket.org/chromiumembedded/cef/wiki/Home 中基本都能找到。 我不选择手动构建,一是因为没有合适的硬件资源,二是构建好的二进制产品可以很方便的从这里下载。 https://cef-builds.spotifycdn.com/index.html 上面这个网站提供包括32/64位以及RAM架构的CPU下的Linux/MacOS/Windows平台的构建产品; 提供的版本有两种,一种是稳定版本(Current Stable Build (Preferred)),一种是Beta版本(Current Beta Build (For Testing))。 本文选择的是MacOS-64-Standard Distribution版本, https://cef-builds.spotifycdn.com/cef_binary_97.1.7%2Bg45ce543%2Bchromium-97.0.4692.99_macosx64.tar.bz2

March 7, 2022 · 1 min · holdsky

Framework not found Pods_xxxxx

把老的xcode工程升级为cocoapod工程,先创建一个空的podfile use_frameworks!:linkage => :static inhibit_all_warnings! # 忽略引入库的所有警告 workspace 'all.xcworkspace' target 'testXcode8' do platform :ios, '8.0' project 'testXcode8/testXcode8' end pod install 后,执行构建在链接阶段报错 Framework not found Pods_testXcode8 尝试的解决过程就不赘述了,最后修改了一个八杆子打不着的BuidSetting解决的,将 Build Active Architecture Only 由非默认值NO修改为默认值 未修改前 修改后

February 10, 2022 · 1 min · holdsky