开篇
关于社会化分享,一般用友盟比较多,但是也有其他的实现方式,这里介绍一下
openShare ,可以不利用官方SDK,直接进行分享。和友盟相比包小了太多,不过貌似没法统计,各有特色吧。
正文
如上图openShare的整体结构主要分为两大部分,openShare 和各大平台的分类。每个平台都去扩展OpenShare的类方法,来很好的保证平台的增加和整体功能的完善等。
我们通过新浪微博和QQ的登录和分享来介绍openShare的使用以及对源码的实现方法的理解。
新浪微博分享
AppDelegate配置
首先导入头文件并注册相关的key
//第一步:注册key
[OpenShare connectQQWithAppId:@"1103194207"];
[OpenShare connectWeiboWithAppKey:@"402180334"];
[OpenShare connectWeixinWithAppId:@"wxd930ea5d5a258f4f"];
[OpenShare connectRenrenWithAppId:@"228525" AndAppKey:@"1dd8cba4215d4d4ab96a49d3058c1d7f"];
[OpenShare connectAlipay];//支付宝参数都是服务器端生成的,这里不需要key.
然后设置分享的回调方法代码如下,这里等下分析OpenShare+Weibo文件的时候详细再说。
-(BOOL)application:(UIApplication *)application openURL:(NSURL *)url sourceApplication:(NSString *)sourceApplication annotation:(id)annotation{
//第二步:添加回调
if ([OpenShare handleOpenURL:url]) {
return YES;
}
//这里可以写上其他OpenShare不支持的客户端的回调,比如支付宝等。
return YES;
}
ViewController中的调用
在示例代码中登录和分享的调用方法如下:
UIView *ret=[[UIView alloc] initWithFrame:frame];
UIButton *auth=[self button:@"登录" WithCenter:CGPointMake(frame.size.width/2, 40)];
[ret addSubview:auth];
[auth addEventHandler:^(id sender) {
[OpenShare WeiboAuth:@"all" redirectURI:@"http://openshare.gfzj.us/" Success:^(NSDictionary *message) {
ULog(@"微博登录成功:\n%@",message);
} Fail:^(NSDictionary *message, NSError *error) {
ULog(@"微博登录失败:\n%@\n%@",message,error);
}];
} forControlEvents:UIControlEventTouchUpInside];
UIButton *textShare=[self button:@"分享纯文本" WithCenter:CGPointMake(auth.center.x, calcYFrom(auth)+40)];
[ret addSubview:textShare];
textShare.tag=1001;
[textShare addTarget:self action:@selector(weiboViewHandler:) forControlEvents:UIControlEventTouchUpInside];
UIButton *imgShare=[self button:@"分享图片" WithCenter:CGPointMake(auth.center.x, calcYFrom(textShare)+40)];
[ret addSubview:imgShare];
imgShare.tag=1002;
[imgShare addTarget:self action:@selector(weiboViewHandler:) forControlEvents:UIControlEventTouchUpInside];
UIButton *newsShare=[self button:@"分享新闻" WithCenter:CGPointMake(auth.center.x, calcYFrom(imgShare)+40)];
[ret addSubview:newsShare];
newsShare.tag=1003;
[newsShare addTarget:self action:@selector(weiboViewHandler:) forControlEvents:UIControlEventTouchUpInside];
//微博分享的实现方法
-(void)weiboViewHandler:(UIButton*)btn{
OSMessage *message=[[OSMessage alloc]init];
message.title=@"hello openshare (message.title)";
if (btn.tag>=1002) {
message.image=testImage;
}
if (btn.tag==1003) {
message.link=@"http://openshare.gfzj.us/";
}
[OpenShare shareToWeibo:message Success:^(OSMessage *message) {
ULog(@"分享到sina微博成功:\%@",message);
} Fail:^(OSMessage *message, NSError *error) {
ULog(@"分享到sina微博失败:\%@\n%@",message,error);
}];
}
这里首先先说一下登录的事件添加方式
[auth addEventHandler:^(id sender) {
[OpenShare WeiboAuth:@"all" redirectURI:@"http://openshare.gfzj.us/" Success:^(NSDictionary *message) {
ULog(@"微博登录成功:\n%@",message);
} Fail:^(NSDictionary *message, NSError *error) {
ULog(@"微博登录失败:\n%@\n%@",message,error);
}];
} forControlEvents:UIControlEventTouchUpInside];
方法的添加是由下图中的UIControl+Blocks文件中实现的
在.h中创建了- (void)addEventHandler:(ActionBlock)handler forControlEvents:(UIControlEvents)controlEvents;
以及ActionBlock。我们点进.m,可以看到主要由两个方法组成代码如下:
- (void)addEventHandler:(ActionBlock)handler forControlEvents:(UIControlEvents)controlEvents
{
objc_setAssociatedObject(self, &UIButtonHandlerKey, handler, OBJC_ASSOCIATION_COPY_NONATOMIC);
[self addTarget:self action:@selector(callActionHandler:) forControlEvents:controlEvents];
};
}
- (void)callActionHandler:(id)sender
{
ActionBlock handler = (ActionBlock)objc_getAssociatedObject(self, &UIButtonHandlerKey);
if (handler) {
handler(sender);
}
}
首先
objc_setAssociatedObject(self, &UIButtonHandlerKey, handler, OBJC_ASSOCIATION_COPY_NONATOMIC);
为runtime的动态属性的添加,各个参数对应的解释如下
* @param self 需要添加关联的对象
* @param UIButtonHandlerKey 添加的唯一标识符
* @param handler 关联的对象
* @param OBJC_ASSOCIATION_COPY_NONATOMIC 关联的策略,是个枚举
这句话可以理解为,以OBJC_ASSOCIATION_COPY_NONATOMIC的关联策略为自己添加一个标识符为UIButtonHandlerKey的handler对象。
点进这句话我们可以看到系统对应的关联策略的枚举,有以下几种形式
typedef OBJC_ENUM(uintptr_t, objc_AssociationPolicy) {
OBJC_ASSOCIATION_ASSIGN = 0, //< Specifies a weak reference to the associated object.
OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1, //< Specifies a strong reference to the associated object.
The association is not made atomically.
OBJC_ASSOCIATION_COPY_NONATOMIC = 3, //< Specifies that the associated object is copied.
The association is not made atomically.
OBJC_ASSOCIATION_RETAIN = 01401, //< Specifies a strong reference to the associated object.
The association is made atomically.
OBJC_ASSOCIATION_COPY = 01403 //< Specifies that the associated object is copied.
The association is made atomically.
};
OK,接下来在看对应的添加的callActionHandler事件中的代码
ActionBlock handler = (ActionBlock)objc_getAssociatedObject(self, &UIButtonHandlerKey);
if (handler) {//判断block是否为null 防止crash
handler(sender);
}
ActionBlock通过标示符UIButtonHandlerKey,得到的之前的管理属性,然后判断如果block不为空,进行下一步的操作。
OpenShare+Weibo
我们从调用处着手开始分析一下源码中的一些方法。登录和分享,最先调起的两个方法分别为:
登录
+(void)WeiboAuth:(NSString*)scope redirectURI:(NSString*)redirectURI Success:(authSuccess)success Fail:(authFail)fail;
分享调起方法
+(void)shareToWeibo:(OSMessage*)msg Success:(shareSuccess)success Fail:(shareFail)fail;
点击这两个方法我们首先进入到的是OpenShare+Weibo,一个专门用来处理微博的分类中,我们可以看到一共有以下4个方法:
+(void)connectWeiboWithAppKey:(NSString *)appKey;
+(BOOL)isWeiboInstalled;
/**
* 分享到微博,微博只支持三种类型:文本/图片/链接。根据OSMessage自动判定想分享的类型。
*
* @param msg 要分享的msg
* @param success 分享成功回调
* @param fail 分享失败回调
*/
+(void)shareToWeibo:(OSMessage*)msg Success:(shareSuccess)success Fail:(shareFail)fail;
/**
* 微博登录OAuth
*
* @param scope scope,如果不填写,默认是all
* @param redirectURI 必须填写,可以通过http://open.weibo.com/apps/402180334/info/advanced编辑(后台不验证,但是必须填写一致)
* @param success 登录成功回调
* @param fail 登录失败回调
*/
+(void)WeiboAuth:(NSString*)scope redirectURI:(NSString*)redirectURI Success:(authSuccess)success Fail:(authFail)fail;
分别用来注册微博相关的key,判断是否安装以及主要的分享登录。
平台和key的设置
每个分类中设置不同的schema,区分平台
+(void)connectWeiboWithAppKey:(NSString *)appKey{
[self set:schema Keys:@{@"appKey":appKey}];
}
调用openShare的设置方法
+(void)set:(NSString*)platform Keys:(NSDictionary *)key{
if (!keys) {
keys=[[NSMutableDictionary alloc] init];
}
keys[platform]=key;
}
- 分享的方法
下面通过分享方法,介绍一下核心的分享的实现方式
+(void)shareToWeibo:(OSMessage*)msg Success:(shareSuccess)success Fail:(shareFail)fail{
if (![self beginShare:schema Message:msg Success:success Fail:fail]) {
return;
}
NSDictionary *message;
//根据不同的分享形式 设置不同的字典格式
if ([msg isEmpty:@[@"link" ,@"image"] AndNotEmpty:@[@"title"] ]) {
//text类型分享
message= @{
@"__class" : @"WBMessageObject",
@"text" :msg.title
};
}else if ([msg isEmpty:@[@"link" ] AndNotEmpty:@[@"title",@"image"] ]) {
//图片类型分享
message=@{
@"__class" : @"WBMessageObject",
@"imageObject":@{
@"imageData":[self dataWithImage:msg.image]
},
@"text" : msg.title
};
}else if ([msg isEmpty:nil AndNotEmpty:@[@"title",@"link" ,@"image"] ]) {
//链接类型分享
message=@{
@"__class" : @"WBMessageObject",
@"mediaObject":@{
@"__class" : @"WBWebpageObject",
@"description": msg.desc?:msg.title,
@"objectID" : @"identifier1",
@"thumbnailData":msg.thumbnail ? [self dataWithImage:msg.thumbnail] : [self dataWithImage:msg.image scale:CGSizeMake(100, 100)], //三目运算
@"title": msg.title,
@"webpageUrl":msg.link
}
};
}
NSString *uuid=[[NSUUID UUID] UUIDString];
NSArray *messageData=@[
@{@"transferObject":[NSKeyedArchiver archivedDataWithRootObject:@{
@"__class" :@"WBSendMessageToWeiboRequest",
@"message":message,
@"requestID" :uuid,
}]},
@{@"userInfo":[NSKeyedArchiver archivedDataWithRootObject:@{}]},
@{@"app":[NSKeyedArchiver archivedDataWithRootObject:@{ @"appKey" : [self keyFor:schema][@"appKey"],@"bundleID" : [self CFBundleIdentifier]}]}
];
[UIPasteboard generalPasteboard].items=messageData;
[self openURL:[NSString stringWithFormat:@"weibosdk://request?id=%@&sdkversion=003013000",uuid]];
}
通过代码我们可以看到主要步骤为:判断参数是否存在-->设置分享方式 -->设置messageData并归档信息-->把数据放到粘贴板上 -->打开网址( [[UIApplication sharedApplication] openURL:[NSURL URLWithString:url]];
的方式)
下面我们分析是如何设置分享方式的,代码如下:
if ([msg isEmpty:@[@"link" ,@"image"] AndNotEmpty:@[@"title"] ]) {
//text类型分享
message= @{
@"__class" : @"WBMessageObject",
@"text" :msg.title
};
}
msg代表的OSMessage对象包含内容如下:
@interface OSMessage : NSObject
@property NSString* title;
@property NSString* desc;
@property NSString* link;
@property UIImage *image;
@property UIImage *thumbnail;
@property OSMultimediaType multimediaType;
//for 微信
@property NSString* extInfo;
@property NSString* mediaDataUrl;
@property NSString* fileExt;
@property (nonatomic, strong) NSData *file; /// 微信分享gif/文件
/**
* 判断emptyValueForKeys的value都是空的,notEmptyValueForKeys的value都不是空的。
*
* @param emptyValueForKeys 空值的key
* @param notEmptyValueForKeys 非空值的key
*
* @return YES/NO
*/
-(BOOL)isEmpty:(NSArray*)emptyValueForKeys AndNotEmpty:(NSArray*)notEmptyValueForKeys;
@end
OSMessage是用来保存分享的数据信息的对象。可以通过其中包含的内容来进行区分分享的内容类型。而其中:-(BOOL)isEmpty:(NSArray)emptyValueForKeys AndNotEmpty:(NSArray)notEmptyValueForKeys;介绍如下:
-(BOOL)isEmpty:(NSArray*)emptyValueForKeys AndNotEmpty:(NSArray*)notEmptyValueForKeys{
@try {
if (emptyValueForKeys) {
for (NSString *key in emptyValueForKeys) {
if ([self valueForKeyPath:key]) {//valueForKeyPath 可以获取OSMessage中相同key的元素
return NO;
}
}
if (notEmptyValueForKeys) {
for (NSString *key in notEmptyValueForKeys) {
if (![self valueForKey:key]) {//取OSMessage对象里 key对应的内容 如果不存在返回NO
return NO;
}
}
}
return YES;
}
@catch (NSException *exception) {
NSLog(@"isEmpty error:\n %@",exception);
return NO;
}
}
方法实现的目的是 判断emptyValueForKeys的value都是空的,notEmptyValueForKeys的value都不是空的。valueForKeyPath判断给定的数组中的内容是否有OSMessage中相同key的元素,如果有的话返回NO,走下一种分享模式。valueForKey判断给定的内容是否也存在于OSMessage的对象中,如果存在返回YES。
对下面代码中的翻译为:
if ([msg isEmpty:@[@"link" ,@"image"] AndNotEmpty:@[@"title"] ]) {
//text类型分享
message= @{
@"__class" : @"WBMessageObject",
@"text" :msg.title
};
}
如果OSMessage对象中不包含,link,image,只包含title,则设置的分享格式是纯文本分享,同时设置分享的字典格式。
[UIPasteboard generalPasteboard].items=messageData;
在新浪微博的分享中以上述方式,把要分享的内容放至剪贴板上。
- 回调方法
接下来看回调函数
+(BOOL)Weibo_handleOpenURL{
}
主要代码如下:
if ([url.scheme hasPrefix:@"wb"]) {
NSArray *items=[UIPasteboard generalPasteboard].items;
NSMutableDictionary *ret=[NSMutableDictionary dictionaryWithCapacity:items.count];
for (NSDictionary *item in items) {
for (NSString *k in item) {
ret[k]=[k isEqualToString:@"transferObject"]?[NSKeyedUnarchiver unarchiveObjectWithData:item[k]]:item[k];
}
}
NSDictionary *transferObject=ret[@"transferObject"];
if ([transferObject[@"__class"] isEqualToString:@"WBAuthorizeResponse"]) {//通过反归档 取出的类名 登录
//auth
if ([transferObject[@"statusCode"] intValue]==0) {
if ([self authSuccessCallback]) {
[self authSuccessCallback](transferObject);
}
}else{
if ([self authFailCallback]) {
NSError *err=[NSError errorWithDomain:@"weibo_auth_response" code:[transferObject[@"statusCode"] intValue] userInfo:transferObject];
[self authFailCallback](transferObject,err);
}
}
}else if ([transferObject[@"__class"] isEqualToString:@"WBSendMessageToWeiboResponse"]) {//分享
//分享回调
if ([transferObject[@"statusCode"] intValue]==0) {
if ([self shareSuccessCallback]) {
[self shareSuccessCallback]([self message]);
}
}else{
if ([self shareFailCallback]) {
NSError *err=[NSError errorWithDomain:@"weibo_share_response" code:[transferObject[@"statusCode"] intValue] userInfo:transferObject];
[self shareFailCallback]([self message],err);
}
}
}
return YES;
}
主要逻辑为:取出剪贴板上内容 -->根据遍历循环取出-->根据之前设置的字典格式区分内容-->设置回调信息
一次分享的整体流程
- 01:对应平台设置key 和相关平台设置
+(void)set:(NSString*)platform Keys:(NSDictionary *)key
- 02:对应平台取值方法
+(NSDictionary *)keyFor:(NSString*)platform{
return [keys valueForKey:platform]?keys[platform]:nil;
}
- 03:创建创建要分享的OSMessage对象
OSMessage *message=[[OSMessage alloc]init];
04:调去分享微博的方法
05:分享方法的调起内部实现
+(void)shareToWeibo:(OSMessage*)msg Success:(shareSuccess)success Fail:(shareFail)fail
- 06:区分分享方式,设置分享的字典格式,内容以items的形式复制至剪贴板
[UIPasteboard generalPasteboard].items=messageData;
- 07:拼接uuid 打开软件
[self openURL:[NSString stringWithFormat:@"weibosdk://request?id=%@&sdkversion=003013000",uuid]];
- 08 走OpenShare中回调的hook方法确定是否有回调
+(BOOL)handleOpenURL:(NSURL*)openUrl{
}
- 09分享结束回调,取出剪贴板的数据,做回调处理
实现原理
- A程序通过Uri跳转到对应的分享程序B里。
- 在B里面,他读取从粘贴板里的数据和根据uri做对应的分享处理。
- 分享完,B把分享的状态结果也放到粘贴板里。然后,根据A之前设好的uri,又跳回到A应用中。
- 然后A把粘贴板的数据读出来,就知道分享是成功还是失败了。
hook方法
+(BOOL)handleOpenURL:(NSURL*)openUrl{
returnedURL=openUrl;
for (NSString *key in keys) {
SEL sel=NSSelectorFromString([key stringByAppendingString:@"_handleOpenURL"]);
if ([self respondsToSelector:sel]) {
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:
[self methodSignatureForSelector:sel]];
[invocation setSelector:sel];
[invocation setTarget:self];
[invocation invoke];
BOOL returnValue;
[invocation getReturnValue:&returnValue];
if (returnValue) {
return YES;
}
}else{
NSLog(@"fatal error: %@ is should have a method: %@",key,[key stringByAppendingString:@"_handleOpenURL"]);
}
}
return NO;
}
我们主要看一下下面这个方法中的内容
+(BOOL)handleOpenURL:(NSURL*)openUrl{ }
创建了一个SEL对象,来获取之前分类中不同的handleOpenURL方法,通过NSSelectorFromString 来判断 未实现的方法是不是我们想要动态添加的方法如:
+(BOOL)Weibo_handleOpenURL{}
如果实现了,利用NSInvocation来直接调用这个消息,在设置万签名,和设置对象之后,执行[invocation invoke],然后根据Weibo_handleOpenURL方法中的返回内容通过getReturnValue来确定有无回调方法。
- NSInvocation介绍
NSInvocation对象只能使用其类方法来初始化,不可使用alloc/init方法。它执行调用之前,需要设置两个方法:setSelector: 和setArgument:atIndex,简单的NSInvocation使用方法如下:
SEL selector = @selector(Test2);
methodSign = [[self class] instanceMethodSignatureForSelector:selector];
NSInvocation *mehtod2Invocation = [NSInvocation invocationWithMethodSignature:methodSign];
[mehtod2Invocation setSelector:selector];
[mehtod2Invocation invokeWithTarget:self];
BOOL returnValue = 1;
[mehtod2Invocation getReturnValue:&returnValue];
NSLog(@"返回值:%ld",returnValue);
-(BOOL)Test2{
return NO;
}
有返回值无参数类型的函数,此处的打印数值应该是0,因为Test2的返回值是NO。
//有返回值,有参数
SEL selector = @selector(Test3:);
NSMethodSignature *method3Sign = [[self class] instanceMethodSignatureForSelector:selector];
NSInvocation *method3Invocation = [NSInvocation invocationWithMethodSignature:method3Sign];
[method3Invocation setTarget: self];
[method3Invocation setSelector:selector];
NSString *arg1 = @"testArg1";
[method3Invocation setArgument:&arg1 atIndex:2];
[method3Invocation invoke];
// [method3Invocation setReturnValue:&arg1];
[method3Invocation getReturnValue:&arg1];
NSLog(@".....%@",arg1);
-(NSString *)Test3 :(NSString*)a{
a= @"122334";
NSLog(@"----%@",a);
return a;
}
如果是 [method3Invocation getReturnValue:&arg1] 他的作用是获取函数的返回结果,打印结果为
2017-06-22 16:14:40.850 01234567899[2884:103358] ----122334
2017-06-22 16:14:40.850 01234567899[2884:103358] .....122334
如果是 [method3Invocation setReturnValue:&arg1] 他的作用是设置函数的返回结果 函数表达式变为如下:
-(NSString *)Test3 :(NSString*)a{
NSLog(@"----%@",a);
return a;
}
打印结果为
2017-06-22 16:17:28.261 01234567899[2924:105348] ----testArg1
2017-06-22 16:17:28.261 01234567899[2924:105348] .....testArg1
[invocation invoke]; 这个方法中,只要调用invocation的invoke方法,就代表需要执行NSInvocation对象中制定对象的指定方法,并且传递指定的参数。
至此我们基本说完了新浪微博分享的整体流程那么我们在大致说一下QQ分享的时候,与新浪微博的区别。
QQ分享的区别
调用分享的主要函数举例如下:
+(void)shareToQQFriends:(OSMessage*)msg Success:(shareSuccess)success Fail:(shareFail)fail{
if ([self beginShare:schema Message:msg Success:success Fail:fail]) {
[self openURL:[self genShareUrl:msg to:0]];
}
}
主要的区别在genShareUrl中
+(NSString*)genShareUrl:(OSMessage*)msg to:(int)shareTo{ }
数据的归档并放置到剪贴板的方法为[[UIPasteboard generalPasteboard] setData:data forPasteboardType:key];不同于新浪微博的 [UIPasteboard generalPasteboard].items=messageData;具体实现如下:
/**
数据的归档 并放置剪贴板
*/
+(void)setGeneralPasteboard:(NSString*)key Value:(NSDictionary*)value encoding:(OSPboardEncoding)encoding{
if (value&&key) {
NSData *data=nil;
NSError *err;
switch (encoding) {
case OSPboardEncodingKeyedArchiver://归档方式存储
data=[NSKeyedArchiver archivedDataWithRootObject:value];
break;
case OSPboardEncodingPropertyListSerialization://序列化
data=[NSPropertyListSerialization dataWithPropertyList:value format:NSPropertyListBinaryFormat_v1_0 options:0 error:&err];
default:
NSLog(@"encoding not implemented");
break;
}
if (err) {
NSLog(@"error when NSPropertyListSerialization: %@",err);
}else if (data){
[[UIPasteboard generalPasteboard] setData:data forPasteboardType:key];///*data类型的数据放在粘贴板中,pasteboardType 类型 字符串*/
}
}
}
/**
从剪贴板取出数据 并解档
*/
+(NSDictionary*)generalPasteboardData:(NSString*)key encoding:(OSPboardEncoding)encoding{
NSData *data=[[UIPasteboard generalPasteboard] dataForPasteboardType:key];//从剪贴板取出数据
NSDictionary *dic=nil;
if (data) {
NSError *err;
switch (encoding) {
case OSPboardEncodingKeyedArchiver:
dic= [NSKeyedUnarchiver unarchiveObjectWithData:data];
break;
case OSPboardEncodingPropertyListSerialization:
dic=[NSPropertyListSerialization propertyListWithData:data options:0 format:0 error:&err];
default:
break;
}
if (err) {
NSLog(@"error when NSPropertyListSerialization: %@",err);
}
}
return dic;
}
并在打开软件的过程中使用了base64编码等,其余的原理大致相同。
到这里,对openShare实现的过程和源码有了大致的说明,可以开心的使用啦。
后记
这篇介绍,主要是自己对openShare,实现的一种认识,以及对源码的理解,可能存在一定的错误和理解偏差,希望积极指出。