轻量级社会化分享openShare源码解析
####開篇 關(guān)于社會化分享,一般用友盟比較多,但是也有其他的實現(xiàn)方式,這里介紹一下 openShare ,可以不利用官方SDK,直接進(jìn)行分享。和友盟相比包小了太多,不過貌似沒法統(tǒng)計,各有特色吧。 ####正文
如上圖openShare的整體結(jié)構(gòu)主要分為兩大部分,openShare 和各大平臺的分類。每個平臺都去擴(kuò)展OpenShare的類方法,來很好的保證平臺的增加和整體功能的完善等。 我們通過新浪微博和QQ的登錄和分享來介紹openShare的使用以及對源碼的實現(xiàn)方法的理解。 ####新浪微博分享 #####AppDelegate配置 首先導(dǎo)入頭文件并注冊相關(guān)的key //第一步:注冊key[OpenShare connectQQWithAppId:@"1103194207"];[OpenShare connectWeiboWithAppKey:@"402180334"];[OpenShare connectWeixinWithAppId:@"wxd930ea5d5a258f4f"];[OpenShare connectRenrenWithAppId:@"228525" AndAppKey:@"1dd8cba4215d4d4ab96a49d3058c1d7f"];[OpenShare connectAlipay];//支付寶參數(shù)都是服務(wù)器端生成的,這里不需要key.復(fù)制代碼然后設(shè)置分享的回調(diào)方法代碼如下,這里等下分析OpenShare+Weibo文件的時候詳細(xì)再說。
-(BOOL)application:(UIApplication *)application openURL:(NSURL *)url sourceApplication:(NSString *)sourceApplication annotation:(id)annotation{//第二步:添加回調(diào)if ([OpenShare handleOpenURL:url]) {return YES;}//這里可以寫上其他OpenShare不支持的客戶端的回調(diào),比如支付寶等。return YES; }復(fù)制代碼#####ViewController中的調(diào)用 在示例代碼中登錄和分享的調(diào)用方法如下:
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];//微博分享的實現(xiàn)方法 -(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);}]; }復(fù)制代碼這里首先先說一下登錄的事件添加方式
[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];復(fù)制代碼方法的添加是由下圖中的UIControl+Blocks文件中實現(xiàn)的
在.h中創(chuàng)建了- (void)addEventHandler:(ActionBlock)handler forControlEvents:(UIControlEvents)controlEvents; 以及ActionBlock。我們點進(jìn).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);} }復(fù)制代碼首先
objc_setAssociatedObject(self, &UIButtonHandlerKey, handler, OBJC_ASSOCIATION_COPY_NONATOMIC); 復(fù)制代碼為runtime的動態(tài)屬性的添加,各個參數(shù)對應(yīng)的解釋如下
* @param self 需要添加關(guān)聯(lián)的對象* @param UIButtonHandlerKey 添加的唯一標(biāo)識符* @param handler 關(guān)聯(lián)的對象* @param OBJC_ASSOCIATION_COPY_NONATOMIC 關(guān)聯(lián)的策略,是個枚舉復(fù)制代碼這句話可以理解為,以O(shè)BJC_ASSOCIATION_COPY_NONATOMIC的關(guān)聯(lián)策略為自己添加一個標(biāo)識符為UIButtonHandlerKey的handler對象。 點進(jìn)這句話我們可以看到系統(tǒng)對應(yīng)的關(guān)聯(lián)策略的枚舉,有以下幾種形式
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. }; 復(fù)制代碼OK,接下來在看對應(yīng)的添加的callActionHandler事件中的代碼
ActionBlock handler = (ActionBlock)objc_getAssociatedObject(self, &UIButtonHandlerKey);if (handler) {//判斷block是否為null 防止crashhandler(sender);}復(fù)制代碼ActionBlock通過標(biāo)示符UIButtonHandlerKey,得到的之前的管理屬性,然后判斷如果block不為空,進(jìn)行下一步的操作。
#####OpenShare+Weibo 我們從調(diào)用處著手開始分析一下源碼中的一些方法。登錄和分享,最先調(diào)起的兩個方法分別為: 登錄
+(void)WeiboAuth:(NSString*)scope redirectURI:(NSString*)redirectURI Success:(authSuccess)success Fail:(authFail)fail;復(fù)制代碼分享調(diào)起方法
+(void)shareToWeibo:(OSMessage*)msg Success:(shareSuccess)success Fail:(shareFail)fail;復(fù)制代碼點擊這兩個方法我們首先進(jìn)入到的是OpenShare+Weibo,一個專門用來處理微博的分類中,我們可以看到一共有以下4個方法:
+(void)connectWeiboWithAppKey:(NSString *)appKey; +(BOOL)isWeiboInstalled; /*** 分享到微博,微博只支持三種類型:文本/圖片/鏈接。根據(jù)OSMessage自動判定想分享的類型。** @param msg 要分享的msg* @param success 分享成功回調(diào)* @param fail 分享失敗回調(diào)*/ +(void)shareToWeibo:(OSMessage*)msg Success:(shareSuccess)success Fail:(shareFail)fail;/*** 微博登錄OAuth** @param scope scope,如果不填寫,默認(rèn)是all* @param redirectURI 必須填寫,可以通過http://open.weibo.com/apps/402180334/info/advanced編輯(后臺不驗證,但是必須填寫一致)* @param success 登錄成功回調(diào)* @param fail 登錄失敗回調(diào)*/ +(void)WeiboAuth:(NSString*)scope redirectURI:(NSString*)redirectURI Success:(authSuccess)success Fail:(authFail)fail; 復(fù)制代碼分別用來注冊微博相關(guān)的key,判斷是否安裝以及主要的分享登錄。 平臺和key的設(shè)置
每個分類中設(shè)置不同的schema,區(qū)分平臺 +(void)connectWeiboWithAppKey:(NSString *)appKey{[self set:schema Keys:@{@"appKey":appKey}]; } 調(diào)用openShare的設(shè)置方法 +(void)set:(NSString*)platform Keys:(NSDictionary *)key{if (!keys) {keys=[[NSMutableDictionary alloc] init];}keys[platform]=key; } 復(fù)制代碼- 分享的方法 下面通過分享方法,介紹一下核心的分享的實現(xiàn)方式
通過代碼我們可以看到主要步驟為:判斷參數(shù)是否存在-->設(shè)置分享方式 -->設(shè)置messageData并歸檔信息-->把數(shù)據(jù)放到粘貼板上 -->打開網(wǎng)址( [[UIApplication sharedApplication] openURL:[NSURL URLWithString:url]]; 的方式) 下面我們分析是如何設(shè)置分享方式的,代碼如下:
if ([msg isEmpty:@[@"link" ,@"image"] AndNotEmpty:@[@"title"] ]) {//text類型分享message= @{@"__class" : @"WBMessageObject",@"text" :msg.title};} 復(fù)制代碼msg代表的OSMessage對象包含內(nèi)容如下:
@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復(fù)制代碼OSMessage是用來保存分享的數(shù)據(jù)信息的對象。可以通過其中包含的內(nèi)容來進(jìn)行區(qū)分分享的內(nèi)容類型。而其中:-(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對應(yīng)的內(nèi)容 如果不存在返回NOreturn NO;}}}return YES;}@catch (NSException *exception) {NSLog(@"isEmpty error:\n %@",exception);return NO;} }復(fù)制代碼方法實現(xiàn)的目的是 判斷emptyValueForKeys的value都是空的,notEmptyValueForKeys的value都不是空的。valueForKeyPath判斷給定的數(shù)組中的內(nèi)容是否有OSMessage中相同key的元素,如果有的話返回NO,走下一種分享模式。valueForKey判斷給定的內(nèi)容是否也存在于OSMessage的對象中,如果存在返回YES。 對下面代碼中的翻譯為:
if ([msg isEmpty:@[@"link" ,@"image"] AndNotEmpty:@[@"title"] ]) {//text類型分享message= @{@"__class" : @"WBMessageObject",@"text" :msg.title};} 復(fù)制代碼如果OSMessage對象中不包含,link,image,只包含title,則設(shè)置的分享格式是純文本分享,同時設(shè)置分享的字典格式。
[UIPasteboard generalPasteboard].items=messageData; 復(fù)制代碼在新浪微博的分享中以上述方式,把要分享的內(nèi)容放至剪貼板上。
- 回調(diào)方法
接下來看回調(diào)函數(shù)
+(BOOL)Weibo_handleOpenURL{ } 復(fù)制代碼主要代碼如下:
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"]) {//通過反歸檔 取出的類名 登錄//authif ([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"]) {//分享//分享回調(diào)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;} 復(fù)制代碼主要邏輯為:取出剪貼板上內(nèi)容 -->根據(jù)遍歷循環(huán)取出-->根據(jù)之前設(shè)置的字典格式區(qū)分內(nèi)容-->設(shè)置回調(diào)信息
一次分享的整體流程
- 01:對應(yīng)平臺設(shè)置key 和相關(guān)平臺設(shè)置
- 02:對應(yīng)平臺取值方法
- 03:創(chuàng)建創(chuàng)建要分享的OSMessage對象
-
04:調(diào)去分享微博的方法
-
05:分享方法的調(diào)起內(nèi)部實現(xiàn)
- 06:區(qū)分分享方式,設(shè)置分享的字典格式,內(nèi)容以items的形式復(fù)制至剪貼板
- 07:拼接uuid 打開軟件
- 08 走OpenShare中回調(diào)的hook方法確定是否有回調(diào)
- 09分享結(jié)束回調(diào),取出剪貼板的數(shù)據(jù),做回調(diào)處理
實現(xiàn)原理
- A程序通過Uri跳轉(zhuǎn)到對應(yīng)的分享程序B里。
- 在B里面,他讀取從粘貼板里的數(shù)據(jù)和根據(jù)uri做對應(yīng)的分享處理。
- 分享完,B把分享的狀態(tài)結(jié)果也放到粘貼板里。然后,根據(jù)A之前設(shè)好的uri,又跳回到A應(yīng)用中。
- 然后A把粘貼板的數(shù)據(jù)讀出來,就知道分享是成功還是失敗了。
#####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; }復(fù)制代碼我們主要看一下下面這個方法中的內(nèi)容
+(BOOL)handleOpenURL:(NSURL*)openUrl{ } 復(fù)制代碼創(chuàng)建了一個SEL對象,來獲取之前分類中不同的handleOpenURL方法,通過NSSelectorFromString 來判斷 未實現(xiàn)的方法是不是我們想要動態(tài)添加的方法如:
+(BOOL)Weibo_handleOpenURL{} 復(fù)制代碼如果實現(xiàn)了,利用NSInvocation來直接調(diào)用這個消息,在設(shè)置萬簽名,和設(shè)置對象之后,執(zhí)行[invocation invoke],然后根據(jù)Weibo_handleOpenURL方法中的返回內(nèi)容通過getReturnValue來確定有無回調(diào)方法。
- NSInvocation介紹 NSInvocation對象只能使用其類方法來初始化,不可使用alloc/init方法。它執(zhí)行調(diào)用之前,需要設(shè)置兩個方法:setSelector: 和setArgument:atIndex,簡單的NSInvocation使用方法如下:
有返回值無參數(shù)類型的函數(shù),此處的打印數(shù)值應(yīng)該是0,因為Test2的返回值是NO。
//有返回值,有參數(shù)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; }復(fù)制代碼如果是 [method3Invocation getReturnValue:&arg1] 他的作用是獲取函數(shù)的返回結(jié)果,打印結(jié)果為
2017-06-22 16:14:40.850 01234567899[2884:103358] ----122334 2017-06-22 16:14:40.850 01234567899[2884:103358] .....122334復(fù)制代碼如果是 [method3Invocation setReturnValue:&arg1] 他的作用是設(shè)置函數(shù)的返回結(jié)果 函數(shù)表達(dá)式變?yōu)槿缦?#xff1a;
-(NSString *)Test3 :(NSString*)a{NSLog(@"----%@",a);return a; } 復(fù)制代碼打印結(jié)果為
2017-06-22 16:17:28.261 01234567899[2924:105348] ----testArg1 2017-06-22 16:17:28.261 01234567899[2924:105348] .....testArg1 復(fù)制代碼[invocation invoke]; 這個方法中,只要調(diào)用invocation的invoke方法,就代表需要執(zhí)行NSInvocation對象中制定對象的指定方法,并且傳遞指定的參數(shù)。 至此我們基本說完了新浪微博分享的整體流程那么我們在大致說一下QQ分享的時候,與新浪微博的區(qū)別。 ####QQ分享的區(qū)別 調(diào)用分享的主要函數(shù)舉例如下:
+(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]];} } 復(fù)制代碼主要的區(qū)別在genShareUrl中
+(NSString*)genShareUrl:(OSMessage*)msg to:(int)shareTo{ } 復(fù)制代碼數(shù)據(jù)的歸檔并放置到剪貼板的方法為[[UIPasteboard generalPasteboard] setData:data forPasteboardType:key];不同于新浪微博的 [UIPasteboard generalPasteboard].items=messageData;具體實現(xiàn)如下:
/**數(shù)據(jù)的歸檔 并放置剪貼板*/ +(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類型的數(shù)據(jù)放在粘貼板中,pasteboardType 類型 字符串*/}} }/**從剪貼板取出數(shù)據(jù) 并解檔*/ +(NSDictionary*)generalPasteboardData:(NSString*)key encoding:(OSPboardEncoding)encoding{NSData *data=[[UIPasteboard generalPasteboard] dataForPasteboardType:key];//從剪貼板取出數(shù)據(jù)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; }復(fù)制代碼并在打開軟件的過程中使用了base64編碼等,其余的原理大致相同。 到這里,對openShare實現(xiàn)的過程和源碼有了大致的說明,可以開心的使用啦。
####后記 這篇介紹,主要是自己對openShare,實現(xiàn)的一種認(rèn)識,以及對源碼的理解,可能存在一定的錯誤和理解偏差,希望積極指出。
總結(jié)
以上是生活随笔為你收集整理的轻量级社会化分享openShare源码解析的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 使用ASIFormDataRequest
- 下一篇: DeprecationWarning