专栏名称: 3033
iOS开发
目录
相关文章推荐
码农翻身  ·  为何 Linus ... ·  昨天  
程序猿  ·  450万虚假Star曝光,最低0.7元/个? ... ·  2 天前  
程序员的那些事  ·  趣图:初五迎财神,初六送穷鬼 ·  4 天前  
程序员小灰  ·  第一期AI特训,我们要发车了! ·  5 天前  
51好读  ›  专栏  ›  3033

轻量级社会化分享openShare源码解析

3033  · 掘金  ·  · 2017-12-14 00:35

正文

####开篇 关于社会化分享,一般用友盟比较多,但是也有其他的实现方式,这里介绍一下 openShare ,可以不利用官方SDK,直接进行分享。和友盟相比包小了太多,不过貌似没法统计,各有特色吧。 ####正文

openShare整体结构.png
如上图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文件中实现的

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 set






请到「今天看啥」查看全文