此處說(shuō)的可拆卸凿可,意思就是業(yè)務(wù)App的工程是否導(dǎo)入這個(gè)framework都不影響編譯。如果業(yè)務(wù)導(dǎo)入了這個(gè)framework涉馅,就可以使用其中功能归园,如果沒(méi)導(dǎo)入,也能編譯通過(guò)稚矿。
1. 應(yīng)用場(chǎng)景
在我們開(kāi)發(fā)項(xiàng)目的過(guò)程中庸诱,會(huì)導(dǎo)入很多的三方庫(kù),比如:會(huì)導(dǎo)入公司內(nèi)部業(yè)務(wù)封裝的晤揣、微信桥爽、微博和支付寶等相關(guān)的framework等。在稍微復(fù)雜一點(diǎn)的業(yè)務(wù)中昧识,如下圖:
其中钠四,A.framework和B.framework都是靜態(tài)庫(kù),A.framework使用了B.framework中的方法跪楞,那么缀去,一般情況下在APP中想要使用A.framework中的方法,必須要同時(shí)將A.framework和B.framework導(dǎo)入到APP工程中习霹,否則編譯時(shí)會(huì)報(bào)錯(cuò)朵耕。但是在現(xiàn)實(shí)情況中,可能業(yè)務(wù)不需要A包中涉及到B包的功能淋叶,因此只想導(dǎo)入A.framework且不想導(dǎo)入B.framework阎曹。
我們新建了一個(gè)下面的工程,工程中有兩個(gè)framework煞檩,示例中APP直接使用TestDynamicSdk的方法处嫌,TestDynamicSdk使用TestStaticSdk的方法。
2. 使用相應(yīng)的宏
以下代碼工作在APP的Test類中:
#import "Test.h"
//// __has_include() 宏在導(dǎo)入三方庫(kù) .h 過(guò)程中的使用
#if __has_include(<TestStaticSdk/TestStaticSdk.h>)
#import <TestStaticSdk/TestStaticSdk.h>
#ifndef HAS_IMPORT_DY
#define HAS_IMPORT_DY 1
#endif
#else
#ifndef HAS_IMPORT_DY
#define HAS_IMPORT_DY 0
#endif
#endif
@interface Test ()
@property (nonatomic, strong) id staticSdkObject;
@end
@implementation TestDynamicSdk
//// 上面定義的宏HAS_IMPORT_DY的使用
- (NSString *)getCombineStrWithA:(NSString *)aStr B:(NSString *)bStr {
#if HAS_IMPORT_DY == 1
TestStaticSdk *staticSdk = [[TestStaticSdk alloc] init];
NSString *combinedStr = [staticSdk getCombineStrWithA:@"common_A_String" B:@"common_B_String"];
return combinedStr;
#else
return nil;
#endif
}
@end
注意:使用__has_include宏的方式在使用TestDynamicSdk時(shí)斟湃,來(lái)實(shí)現(xiàn)自由拆卸TestStaticSdk.framework熏迹,是行不通的。宏在編譯時(shí)就已經(jīng)被展開(kāi)替換凝赛,即在A.framwork編譯的時(shí)就已經(jīng)決定了B.framwork是否導(dǎo)入注暗。__has_include這個(gè)宏只能在APP層中坛缕,實(shí)現(xiàn)代碼上的容錯(cuò)。
3. 使用運(yùn)行時(shí)相關(guān)方法
以下代碼工作在TestDynamicSdk中捆昏,我們以TestDynamicSdk作為上述A.framework的角色做說(shuō)明:
#import "TestDynamicSdk.h"
@interface TestDynamicSdk ()
@property (nonatomic, strong) id staticSdkObject;
@end
@implementation TestDynamicSdk
- (id)init {
self = [super init];
if (self) {
Class class = NSClassFromString(@"TestStaticSdk");
if (!class) {
NSLog(@"TestStaticSdk沒(méi)有編譯");
return self;
}
SEL sel = NSSelectorFromString(@"new");
id (*imp)(id, SEL) = (id (*)(id, SEL))[class methodForSelector:sel];
self.staticSdkObject = imp(class, sel);
}
return self;
}
- (NSString *)getCombineStrWithA:(NSString *)aStr B:(NSString *)bStr {
if (!_staticSdkObject) {
NSLog(@"staticSdkObject為空");
return nil;
}
SEL sel = NSSelectorFromString(@"getCombineStrWithA:B:");
if (![_staticSdkObject respondsToSelector:sel]) {
NSLog(@"getCombineStrWithA:B:方法沒(méi)有實(shí)現(xiàn)");
return nil;
}
NSString * (*imp)(id, SEL, NSString *, NSString *) = (NSString * (*)(id, SEL, NSString *, NSString *))[_staticSdkObject methodForSelector:sel];
NSString *combinedStr = imp(_staticSdkObject, sel, aStr, bStr);
return combinedStr;
}
@end
這樣能實(shí)現(xiàn)APP在使用TestDynamicSdk時(shí)赚楚,自由拆卸TestStaticSdk.framework。
注意骗卜,SEL和IMP的區(qū)別宠页,
函數(shù)具體實(shí)現(xiàn)的指針I(yè)MP定義: typedef id (*IMP)(id, SEL, );
可以從對(duì)象 & SEL的方法得到:IMP imp = [self methodForSelector:selector];
// NSObject里面的這兩個(gè)方法:
- (IMP)methodForSelector:(SEL)aSelector;
+ (IMP)instanceMethodForSelector:(SEL)aSelector;
// 無(wú)參方法調(diào)用
Class class = NSClassFromString(@"classA");
SEL selector = NSSelectorFromString(@"方法名");
IMP imp = [class methodForSelector:selector];
void (*func)(id, SEL) = (void *)imp;
func(class, selector);
// 有參有返回值方法調(diào)用
SEL selector = NSSelectorFromString(@"方法名");
IMP imp = [self methodForSelector:selector];
id (*func)(id, SEL,NSString *, NSArray *) = (void *)imp; //id (*)(id, SEL,NSString *, NSArray *) 應(yīng)該強(qiáng)轉(zhuǎn)這個(gè)吧?寇仓?
id ret = func(self, selector,@"1",@[@"1",@"2"]);
// 調(diào)用self自身方法举户,可直接用msgSend
SEL testFunc = NSSelectorFromString(@"testRuntime");
((void(*)(id,SEL, id,id))objc_msgSend)(self, testFunc, nil, nil);
PS:
- 在用宏定義實(shí)現(xiàn)時(shí)編譯可能會(huì)報(bào)錯(cuò),開(kāi)發(fā)TestDynamicSdk時(shí)需要在其工程配置中添加TestStaticSdk.framework遍烦;
- 使用運(yùn)行時(shí)相關(guān)方法實(shí)現(xiàn)時(shí)俭嘁,需要在APP的工程配置中設(shè)置-all_load。