研究Openshare的原理

内容来源:

http://www.gfzj.us/series/openshare/
https://github.com/100apps/openshare

起因

因为项目需要同时使用4家的分享。QQ,微信,新浪微博和腾讯微博。按照传统的方法,去各个官方平台的开发者网站,下载SDK,然后集成进去。这样做会导致最后打包的app体积增大不少,而且每个平台API使用方法都不统一,研究每个平台分享、登录功能,也浪费了不少时间。用shareSDK这样类库,也要引入很多的包和库。

终于我发现一个开源的项目openshare,号称100多行代码就可以完成这个任务。

原理

平时一个http的请求有post和get两种。

  1. get就是拼一条url然后发出请求,缺点就是参数都是可见,且数据量有限。
  2. post请求是把参数当成一种请求,提交给服务器。

ps:GET方式提交的数据最多只能是1024字节,理论上POST没有限制,可传较大量的数据,起限制作用的是服务器的处理能力。

  1. 经过研究发现各个厂商的分享是先通过openUrl去请求一个Uri,从而可以跳到他们的应用里面去。(比如微信就是浏览器输入:weixin:// 就可以打开微信。)

  2. 然后用类似于post的请求来做这个事。post的参数在哪里呢?他们放在iOS系统的粘贴板里。

    那我们观察一下是不是。

  3. 针对以下的函数hook一下,然后观察分享时他们的值。

1)、对UIApplication的openURL:

2)、pasteboardWithName:create:

3)、粘贴板setData:forPasteboardType:

4)、粘贴板 dataForPasteboardType:

看例子:testSwizzle,主要的代码这里贴一下

//对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);
}
  1. 通过以上例子,这时我们就可以通过自己模拟一个这样的请求,也把参数放到粘贴板里面,从而达到像SDK一样的分享效果。
  2. 分享后,状态的返回也是利用粘贴板。可以看例子。
经过作者的研究,返回的数据格式为两种序列化方式:
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];

总结:

整个分享的步骤为:
1. A程序通过Uri跳转到对应的分享程序B里。
2. 在B里面,他读取从粘贴板里的数据和根据uri做对应的分享处理。
3. 分享完,B把分享的状态结果也放到粘贴板里。然后,根据A之前设好的uri,又跳回到A应用中。
4. 然后A把粘贴板的数据读出来,就知道分享是成功还是失败了。

缺点就是当本地没有装要分享的程序时,无任何响应。

PS:
一种新的监控mac/iOS模拟器中runtime message的方法,这样在mac电脑的/tmp/下目录就能生成/tmp/msgSends-1234类似的文件了。可以用tail -f查看。

#import 
void instrumentObjcMessageSends();

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

你可能感兴趣的:(iOS)