正文
####开篇
关于社会化分享,一般用友盟比较多,但是也有其他的实现方式,这里介绍一下
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 source Application:(NSString *)source Application 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);
}];
} for ControlEvents: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:) for ControlEvents: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:) for ControlEvents: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:) for ControlEvents:UIControlEventTouchUpInside];
//微博分享的实现方法
-(void)weiboViewHandler:(UIButton*)btn{
OSMessage *message=[[OSMessage alloc]init];
message.title=@"hello openshare (message.title)" ;
if (btn.tag>=1002) {
message.image=test Image;
}
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);
}];
} for ControlEvents:UIControlEventTouchUpInside];
方法的添加是由下图中的UIControl+Blocks文件中实现的
在.h中创建了- (void)addEventHandler:(ActionBlock)handler forControlEvents:(UIControlEvents)controlEvents;
以及ActionBlock。我们点进.m,可以看到主要由两个方法组成代码如下:
- (void)addEventHandler:(ActionBlock)handler for ControlEvents:(UIControlEvents)controlEvents
{
objc_setAssociatedObject(self, &UIButtonHandlerKey, handler, OBJC_ASSOCIATION_COPY_NONATOMIC);
[self addTarget:self action:@selector(callActionHandler:) for ControlEvents: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;
}
主要逻辑为:取出剪贴板上内容 -->根据遍历循环取出-->根据之前设置的字典格式区分内容-->设置回调信息
一次分享的整体流程
+(void)set :(NSString*)platform Keys:(NSDictionary *)key
+(NSDictionary *)keyFor:(NSString*)platform{
return [keys valueForKey:platform]?keys[platform]:nil;
}
OSMessage *message=[[OSMessage alloc]init];
04:调去分享微博的方法
05:分享方法的调起内部实现
+(void)shareToWeibo:(OSMessage*)msg Success:(shareSuccess)success Fail:(shareFail)fail
06:区分分享方式,设置分享的字典格式,内容以items的形式复制至剪贴板
[UIPasteboard generalPasteboard].items=messageData;
[self openURL:[NSString stringWithFormat:@"weibosdk://request?id=%@&sdkversion=003013000" ,uuid]];
08 走OpenShare中回调的hook方法确定是否有回调
+(BOOL)handleOpenURL:(NSURL*)openUrl{
}
实现原理
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 set Selector:sel];
[invocation set