OpenShare实现(2): how

简介:

我们自己的app中集成的官方SDK需要和官方客户端通信,在iOS中,调起其他app,基本上都是用:

[[UIApplication sharedApplication] openURL:[NSURL URLWithString:@"weixin:"]];//app中调起微信
[[UIApplication sharedApplication] openURL:[NSURL URLWithString:@"http://www.baidu.com"]];//app中调起Safari,并打开百度首页

这就类似于http中的GET方法,我们可以在AppDelegate.m-(BOOL)application:(UIApplication *)application openURL:(NSURL *)url sourceApplication:(NSString *)sourceApplication annotation:(id)annotation中接收这个url然后做相应的处理。比如url如果是weixin://app/wx0fff8fc7685bb2c6/auth/?scope=snsapi_userinfo,那么微信就知道其他app请求的是auth,scope是snsapi_userinfo,urlschema是wx0fff8fc7685bb2c6,这样微信处理以后,就可以通过打开wx0fff8fc7685bb2c6://处理结果来调起我们自己的app,我们自己的app同样处理url,就能得到返回结果了。

这里最重要的是URLScheme,可以在Info.plist中设置,比如:

<key>CFBundleURLTypes</key>
	<array>
		<dict>
			<key>CFBundleURLName</key>
			<string>weixin</string>
			<key>CFBundleURLSchemes</key>
			<array>
				<string>wx0fff8fc7685bb2c6</string>
				<string>wxd930ea5d5a258f4f</string>
			</array>
		</dict>
	</array>

这样如果其他的应用程序(包括Safari中的html页面中的href),都可以通过打开wx0fff8fc7685bb2c6wxd930ea5d5a258f4f掉起我们的app,比如Safari中给的链接地址是:

wx0fff8fc7685bb2c6://view/pid123456

这样我们在

在js中。比如下面的代码AppDelegate.m-(BOOL)application:(UIApplication *)application openURL:(NSURL *)url sourceApplication:(NSString *)sourceApplication annotation:(id)annotation中解析一下,就知道用户想查看pid123456的商品,展现这个就对了。

类似于HTTP中的GET,通过url传递的信息毕竟是有长度上限的,虽然Apple并没有文档指定最大长度,但是对于需要传递的内容比较长的情况(比如分享文件到微信),最好用类似于HTTP中的POST方法,iOS中常用的是系统粘贴板。

粘贴板好像是一个全局(操作系统全局)的map,每个app都可以去set和get。而且目前还没有发现最大限制(除了系统内存限制),所以是一个很好的传递数据的方法。这样我想起很久以前浏览器中两个Tab中的页面通信的黑科技,有人就用粘贴板实现了,一个页面set,另一个页面循环get。

知道了通信原理。下一步要做的就是监控SDK调用这些方法的时候,传递的参数的格式。如果我们也能生成同样的参数,然后发起请求,官方的客户端也不能区分到底用的是OpenShare呢还是官方SDK。狸猫换太子,达到同样的效果。

监控的思路就是hook关键方法。比如在js中,我们想要在alert的时候输出到console再alert,可以:

var oldAlert=alert;
alert=function(msg){
  console.log(msg);
  oldAlert(msg);
}

这样,如果调用alert("Hello World");,就能输出到console再弹窗了。

同样在objc中,我们可以用runtime提供的API进行Method Swizzling

我们首先把各种官方提供的Demo运行一下,然后在appdelegate中对我们确定的几个方法进行Swizzling:

#import <objc/runtime.h>

//对UIApplication的openURL:方法进行hook
-(void)swizzleOpenUrl{
    SEL openUrlSEL=@selector(openURL:);
    BOOL (*openUrlIMP)(id,SEL,id) =(BOOL(*)(id,SEL,id))[UIApplication instanceMethodForSelector:openUrlSEL];
    static int count=0;
    BOOL (^myOpenURL)(id SELF,NSURL * url)=^(id SELF,NSURL *url){
        NSLog(@"\n----------open url: %d----------\n%@\n%@\n",count++,url,@"\n"/*[NSThread callStackSymbols]*/);
        
        return (BOOL)openUrlIMP(SELF,openUrlSEL,url);
    };
    class_replaceMethod([UIApplication class], openUrlSEL, imp_implementationWithBlock(myOpenURL), NULL);
}
//pasteboardWithName:create:方法进行hook,注意这是一个类方法
-(void)swizzlePasteboard{
    SEL pasteboardWithNameSEL=@selector(pasteboardWithName:create:);
    UIPasteboard* (*pasteboardWithNameIMP)(id,SEL,id,BOOL) =(UIPasteboard* (*)(id,SEL,id,BOOL))[UIPasteboard methodForSelector:pasteboardWithNameSEL];
    
    static int count=0;
    UIPasteboard* (^mypasteboardWithName)(id SELF,NSString *name,BOOL create)=^(id SELF,NSString *name,BOOL create){
        NSLog(@"\n----------pasteboardWithName: %d----------\n%@\n%d\n",count++,name,create);
        return (UIPasteboard*)pasteboardWithNameIMP(SELF,pasteboardWithNameSEL,name,create);
    };
    class_replaceMethod(/*类方法hook http://stackoverflow.com/a/3267898/3825920*/object_getClass((id)[UIPasteboard class]), pasteboardWithNameSEL, imp_implementationWithBlock(mypasteboardWithName), NULL);
}

//粘贴板setData:forPasteboardType:
-(void)swizzlePasteboardSetData{
    SEL swizzlePasteboardSetDataSEL=@selector(setData:forPasteboardType:);
    void (*swizzlePasteboardSetDataIMP)(id,SEL,id,id)=(void(*)(id,SEL,id,id))[UIPasteboard instanceMethodForSelector:swizzlePasteboardSetDataSEL];
    
    static int count=0;
    void (^mypasteboardSetData)(id SELF,NSData *data,NSString *type)=^(id SELF,NSData *data,NSString *type){
        
        NSLog(@"\n----------swizzlePasteboardSetData: %d----------\n%@\n%@\n%@\n",count++,[((UIPasteboard *)SELF) name], type,[NSPropertyListSerialization propertyListWithData:data options:0 format:0 error:nil]);
        swizzlePasteboardSetDataIMP(SELF,swizzlePasteboardSetDataSEL,data,type);
    };
    class_replaceMethod([UIPasteboard class], swizzlePasteboardSetDataSEL, imp_implementationWithBlock(mypasteboardSetData), NULL);
}

//粘贴板 dataForPasteboardType:
-(void)swizzlePasteboardGetData{
    SEL swizzlePasteboardGetDataSEL=@selector(dataForPasteboardType:);
    NSData* (*swizzlePasteboardGetDataIMP)(id,SEL,id)=(NSData*(*)(id,SEL,id))[UIPasteboard instanceMethodForSelector:swizzlePasteboardGetDataSEL];
    
    static int count=0;
    NSData* (^mypasteboardGetData)(id SELF,NSString *type)=^(id SELF,NSString *type){//
        NSData *ret=(NSData*)swizzlePasteboardGetDataIMP(SELF,swizzlePasteboardGetDataSEL,type);
        NSLog(@"\n----------pasteboardGetData: %d----------\n%@\n%@\n%@\n%@",count++,[((UIPasteboard *)SELF) name], type,ret,ret);
        return ret;
    };
    class_replaceMethod([UIPasteboard class], swizzlePasteboardGetDataSEL, imp_implementationWithBlock(mypasteboardGetData), NULL);
}

这样可以在- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions中调用一下[self swizzle];就可以监控了。

其中还遇到一些坑,比如研究Sina微博的时候,监控不到粘贴板数据,百思不得其解。于是用lldb添加断点:

breakpoint set -r '\[UIPasteboard .*\]$'
br l

这样就能把所有调用UIPasteboard的方法都打印出来了。原来Sina微博用的是[UIPasteboard generalPasteboard].items方法设置粘贴板。这个方法没有hook当然监控不到啦。

update 20150717 今天又学到一种监控mac/iOS模拟器中runtime message的方法:

#import <objc/runtime.h>
void instrumentObjcMessageSends();

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    instrumentObjcMessageSends(YES);//设置为NO可以关闭。
}

这样在/tmp/下目录就能生成/tmp/msgSends-1234类似的文件了。可以用tail -f查看。非常方便。加上从源代码编译objc runtime中提到的方法,现在都有两种监控消息发送的方法了。另外还听说Dtrace的方法。


我们知道粘贴板传递的数据了,得到的是一个NSData类型,还需要猜测这个二进制NSData是如何生成和解码的。通过把NSData写入到文件,隐约看到bplist的身影,于是用node.js和plutil试一下:

#!/usr/bin/env node

/*
pbpaste |./decodeBplist.js
从粘贴板读取数据。然后转换成xml
*/
var buffer = require('buffer');
var fs = require('fs');

var input = "";

process.stdin.setEncoding('utf8');
process.stdin.on('readable', function() {
	var chunk = process.stdin.read();
	if (chunk !== null) {
		input += chunk;
	}
});
process.stdin.on('end', function() {
	process.stdout.write(input);
	var fn = "/tmp/bp.plist";
	input = input.replace(/ |>|</g, "");
	input = new Buffer(input, "hex");
	fs.writeFile(fn, input);

	var exec = require('child_process').exec,
		child;

	child = exec('/usr/bin/plutil -convert xml1 ' + fn,
		function(error, stdout, stderr) {
			if (error !== null) {
				console.log('exec error: ' + error);
			} else {
				console.log("\n\n" + fs.readFileSync(fn));
			}
		});

});

果然可以解析出来。最后经过尝试,目前只发现两种序列化方式:

NSData *output=[NSKeyedArchiver archivedDataWithRootObject:data];
NSDictionary *dic=[NSKeyedUnarchiver unarchiveObjectWithData:output;

NSData *output=[NSPropertyListSerialization dataWithPropertyList:data format:NSPropertyListBinaryFormat_v1_0 options:0 error:&err];
NSDictionary *dic=[NSPropertyListSerialization propertyListWithData:output];

这样就能解决app和客户端之间通信的问题了

目录
相关文章
IDE Java Linux
441 0
|
4月前
|
安全 关系型数据库 PostgreSQL
别让烂代码拖垮项目!Docker一键部署SonarQube,10分钟搞定代码质量检测
还在为代码里的隐藏bug、安全漏洞和越积越多的技术债头疼?本文教你用Docker快速部署SonarQube这款业界领先的代码质量检测工具,从环境准备到第一次代码扫描全程保姆级教学。支持30多种编程语言,能自动检测bug、安全漏洞、代码异味,还能生成可视化的质量报告,让你的团队告别"屎山",写出更健壮、更易维护的代码。
576 1
别让烂代码拖垮项目!Docker一键部署SonarQube,10分钟搞定代码质量检测
|
2月前
|
SQL 自然语言处理 数据库
为什么多数智能问数项目失败不是因为技术,而是因为业务没想清楚?
本文指出:智能问数项目失败的根因并非技术不足,而是业务定义不清——对象边界模糊、术语口径不一、复合逻辑缺失。本体语义层路线通过结构化表达对象、属性、关系及业务定义,实现“又泛又准”的问数能力;相较预置或NL2SQL路线,其维护成本更可控、扩展性更强。成功前提在于企业愿投入语义治理。
|
4月前
|
人工智能 Cloud Native 算法
玄晶引擎2.9.0实操:IP自动化+爆款复刻,阿里云生态适配落地
玄晶引擎2.9.0深度适配阿里云生态,主打人设IP自动化与短视频爆款复刻两大功能,支持低代码配置、云原生部署及AI私域转化闭环。无缝对接DataWorks、MaxCompute等服务,助力企业咨询、电商、连锁等行业实现“获客—转化—复购—裂变”全流程提效降本。
357 1
|
人工智能 程序员 定位技术
和AI谈恋爱指南:从尬聊到心有灵犀
想让AI理解你的需求?本文用最轻松有趣的方式教你掌握提示词工程,从小白到高手,让ChatGPT成为你最得力的助手。通过生动的类比和实战案例,轻松掌握与AI对话的艺术!
636 8
|
4月前
|
SQL 机器学习/深度学习 自然语言处理
企业做智能问数,最容易被低估的不是模型,而是人工预置工作量
本文将从第三方视角出发,系统拆解当前主流智能问数路径的技术逻辑,对比不同厂商路线在人工预置维度上的差异,并结合实施交付的真实场景,分析企业在选择技术路线时应关注的核心权衡点。
|
7月前
|
数据可视化 安全 BI
PageAdmin CMS 可视化智能表单与业务系统搭建完全指南
“无需代码,拖拽即用!PageAdmin CMS可视化智能表单系统让企业零基础搭建专业级管理系统,26种组件+智能工作流,从数据收集到BI分析一站式实现业务数字化。”
289 5
|
9月前
|
Ubuntu 安全 Linux
CentOS与Ubuntu
CentOS与Ubuntu代表两种开源哲学:前者稳重如山,追求企业级稳定;后者灵动如水,倡导快速创新。源自RHEL与Debian的不同血脉,塑造了它们在更新策略、安全机制与生态定位上的鲜明对比。CentOS深耕传统服务器领域,Ubuntu则领跑云原生与开发者社区。尽管技术趋同日益明显,二者仍以不同方式诠释着自由软件的多样性与包容性,共同推动Linux生态繁荣发展。
|
开发者
移动端UI名词 - AxureMost
该文档介绍了多种UI组件的分类和功能,包括按钮、图标、宫格等基础组件,头像、徽标、轮播图等数据展示类组件,复选框、日期选择器、输入框等数据输入类组件,以及对话框、加载、消息通知等反馈类组件。此外,还涵盖了下拉菜单、导航栏、分页器等导航类组件。每个组件都有具体的应用场景和作用,帮助开发者快速构建界面。
712 28
|
存储 人工智能 算法
一文彻底搞清楚电感元件
电感元件是将电能转化为磁能并储存的电子组件,主要由线圈构成。它在电流变化时产生感应电动势,阻碍电流突变,起到滤波、扼流、谐振和储能的作用。电感单位为亨利(H),常见单位有毫亨(mH)和微亨(μH)。电感分为贴片和插件两类,关键参数包括电感值(L)、额定电流(I)和品质因数(Q)。读取方法有直接标记、色环标记、仪器测量等。应用场景涵盖工业设备、电源系统及传统电子设备。关注我,一起学习更多!
2197 0
一文彻底搞清楚电感元件