久久国产成人av_抖音国产毛片_a片网站免费观看_A片无码播放手机在线观看,色五月在线观看,亚洲精品m在线观看,女人自慰的免费网址,悠悠在线观看精品视频,一级日本片免费的,亚洲精品久,国产精品成人久久久久久久

分享

解決iOS項(xiàng)目的版本兼容問(wèn)題

 ccccshq 2015-12-23

更新

2015-11-16

感謝微博好友@zyyy_000的評(píng)論,,補(bǔ)充了為什么要在+ (void)load方法里面做Method Swizzling

前言

最近,,在做項(xiàng)目時(shí),,因?yàn)槟撤N原因,突然要“適配”iOS6(也是醉了,。,。。),,保證極少數(shù)的iOS6用戶可以“用上”新的版本,。哪怕界面上有瑕疵,只要功能正常就行,。于是就只好花幾天時(shí)間對(duì)iOS6進(jìn)行緊急適配(心中一萬(wàn)頭駝羊奔跑而過(guò),。。,。)

本文總結(jié)了一些常規(guī)的,,和“非常規(guī)”的iOS項(xiàng)目向老版本兼容的辦法,,結(jié)合了宏定義CategoryRuntime,,大家看著消遣一下就好哈~

重點(diǎn)概念

首先強(qiáng)調(diào)一些概念,。

Deployment Target 和 Base SDK

Deployment Target
指的是你的APP能支持的最低系統(tǒng)版本,如要支持iOS6以上,,就設(shè)置成iOS6即可,。

Base SDK
指的是用來(lái)編譯APP的SDK(Software Development Kit)的版本,一般保持當(dāng)前XCode支持的最新的就好,,如iOS8.4,。SDK其實(shí)就是包含了所有的你要用到的頭文件、鏈接庫(kù)的集合,,你的APP里面用的各種類,、函數(shù),能編譯,、鏈接成最后的安裝包,,就要靠它,蘋(píng)果每次升級(jí)系統(tǒng),,新推出的各種API,,也是在SDK里面。所以一般Base SDK肯定是大于等于Deployment Target的版本,。

區(qū)分
既然Base SDK的版本大于等于Deployment Target的版本,那么就要小心了,,因?yàn)?strong>“只要用到的類,、方法,在當(dāng)前的Base SDK版本里面存在,,就可以編譯通過(guò),!但是一旦運(yùn)行APP的手機(jī)的系統(tǒng)版本低于這些類、方法的最低版本要求,,APP就會(huì)Crash,!”

所以并不是說(shuō),能編譯通過(guò)的,,就一定能運(yùn)行成功,!還要在運(yùn)行時(shí)檢查!簡(jiǎn)單來(lái)說(shuō),,就是如下圖:

兼容示意圖

宏只在編譯時(shí)生效,!

宏定義只是純粹的文本替換,只在編譯時(shí)起作用,。如下代碼:

1
2
3
#if __IPHONE_OS_VERSION_MIN_REQUIRED >= 70000
NSLog(@"Tutuge");
#endif

被宏定義包起來(lái)的代碼是否會(huì)執(zhí)行,,在編譯時(shí)就決定好了,,無(wú)論你是用什么系統(tǒng)運(yùn)行,宏定義再也沒(méi)有什么卵用=,。=

編譯時(shí)檢查SDK版本,,運(yùn)行時(shí)檢查系統(tǒng)版本

這個(gè)是最基本的適配手段。

用到的宏如下:

  1. __IPHONE_OS_VERSION_MAX_ALLOWED: 值等于Base SDK,,即用于檢查SDK版本的,。
  2. __IPHONE_OS_VERSION_MIN_REQUIRED: 值等于Deployment Target,檢查支持的最小系統(tǒng)版本,。

運(yùn)行時(shí)檢查系統(tǒng)版本:

1
2
3
if ([UIDevice currentDevice].systemVersion.floatValue > 8.0f) {
// ...
}

假如我們現(xiàn)在想用iOS8新的UIAlertController來(lái)顯示提示框,,應(yīng)該如下判斷:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// 編譯時(shí)判斷:檢查SDK版本
#if __IPHONE_OS_VERSION_MAX_ALLOWED > 80000
// 運(yùn)行時(shí)判斷:檢查當(dāng)前系統(tǒng)版本
if ([UIDevice currentDevice].systemVersion.floatValue > 8.0f) {
UIAlertController *alertController =
[UIAlertController alertControllerWithTitle:@"Tutuge"
message:@"Compatibility"
preferredStyle:UIAlertControllerStyleAlert];
[alertController addAction:[UIAlertAction actionWithTitle:@"Cancel"
style:UIAlertActionStyleCancel
handler:^(UIAlertAction *action) {
NSLog(@"Cancel");
}]];
[self presentViewController:alertController animated:YES completion:nil];
} else {
// 用舊的代替
UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:@"Tutuge"
message:@"Compatibility"
delegate:nil
cancelButtonTitle:@"Cancel"
otherButtonTitles:nil];
[alertView show];
}
#else
// ...
#endif

總的來(lái)說(shuō)就是編譯時(shí)、運(yùn)行時(shí)的判斷均不能少,。

Weakly Linked - 運(yùn)行時(shí)檢查類,、方法是否可用

除了用宏、系統(tǒng)版本檢測(cè),,還可以用Weakly Linked特性做運(yùn)行時(shí)的檢查,。

對(duì)于iOS4.2以上的,有NS_CLASS_AVAILABLE標(biāo)示的類,,可以如下判斷是否可用:

1
2
3
4
5
6
7
8
#if __IPHONE_OS_VERSION_MAX_ALLOWED > 80000
// Weakly Linked判斷
if ([UIAlertController class]) {
// 使用UIAlertController...
} else {
// 使用舊的方案...
}
#endif

也可以如下判斷:

1
2
3
4
5
6
Class class = NSClassFromString (@"UIAlertController");
if (class) {
// 使用UIAlertController...
} else {
// 使用舊的方案...
}

對(duì)于方法,,如下判斷:

1
2
3
4
5
if ([UITableViewCell instancesRespondToSelector:@selector (setSeparatorInset:)]) {
// ...
} else {
// ...
}

至于用哪種方法,統(tǒng)一一下即可,。

用Method Swizzling做兼容

有關(guān)Runtime,、Method Swizzling的資料很多,各位自行閱讀哈~

+ (void)load方法里面做替換

這里提一下為什么要在+ (void)load方法里面做Method Swizzling,。

在Objective-C中,,運(yùn)行時(shí)會(huì)自動(dòng)調(diào)用每個(gè)類的兩個(gè)方法。+ (void)load會(huì)在類,、Category初始加載時(shí)調(diào)用,,+ (void)initialize會(huì)在第一次調(diào)用類的類方法或?qū)嵗椒ㄖ氨徽{(diào)用。

但是需要注意的是,,+ (void)initialize是可以被Category覆蓋重寫(xiě)的,,并且有多個(gè)Category都重寫(xiě)了+ (void)initialize方法時(shí),只會(huì)運(yùn)行其中一個(gè),,所以在+ (void)initialize里面做Method Swizzling顯然是不行的,。

+ (void)load方法只要實(shí)現(xiàn)了,就一定會(huì)調(diào)用,。具體為什么大家可以自行閱讀Runtime的源碼,,或者查閱相關(guān)文章。

用dispatch_once保證只運(yùn)行一次

因?yàn)镸ethod Swizzling的影響是全局的,,而且一旦多次調(diào)用,,會(huì)出錯(cuò),,所以這個(gè)時(shí)候用dispatch_once就再合適不過(guò)了~

實(shí)例

下面就是利用Method Swizzling做兼容的一個(gè)例子。
有時(shí)候,,不同版本之間,,同一個(gè)類、View控件的默認(rèn)屬性可能都會(huì)變化,,如UILabel的背景色在iOS6上,,默認(rèn)是白色,而iOS6以后是透明的,!如果在每個(gè)用到UILabel的地方,,都手動(dòng)設(shè)置一次背景色,代價(jià)太大,。這個(gè)時(shí)候就需要Runtime的“黑魔法”上場(chǎng),。

就以設(shè)置UILabel的默認(rèn)背景色透明為例,就是在UILabel初始化時(shí),,如initWithFrame之前,,先設(shè)置好透明背景色,簡(jiǎn)單的示例如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// 創(chuàng)建Category
@implementation UILabel (TTGCompatibility)

+ (void)load {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
// 先判斷系統(tǒng)版本,,盡量減少Runtime的作用范圍
if ([UIDevice currentDevice].systemVersion.floatValue < 7.0f) {
// Method Swizzling
// initWithFrame
Method oriMethod = class_getInstanceMethod(self, @selector(initWithFrame:));
Method newMethod = class_getInstanceMethod(self, @selector(compatible_initWithFrame:));
method_exchangeImplementations(oriMethod, newMethod);

// initWithCoder...
}
});
}

// initWithFrame
- (id)compatible_initWithFrame:(CGRect)frame {
id newSelf = [self compatible_initWithFrame:frame];
// 設(shè)置透明背景色
((UILabel *)newSelf).backgroundColor = [UIColor clearColor];
return newSelf;
}

// initWithCoder...

運(yùn)行時(shí)添加“Dummy”方法,,減少代碼改動(dòng)

Dummy,意思是“假的,、假動(dòng)作,、假人”,在這里指的是為舊版本不存在的方法提供一個(gè)“假的”替代方法,,防止因新API找不到而導(dǎo)致的Crash,。

以UITableViewCell的“setSeparatorInset:”方法為例,在iOS6中,,壓根就不存在separatorInset,但是現(xiàn)有的代碼里面大量的調(diào)用了這個(gè)方法,,怎么辦,?難道一個(gè)一個(gè)的去加上判斷條件?代價(jià)太大,。

這個(gè)時(shí)候就可以用Runtime的手段,,在運(yùn)行時(shí)添加一個(gè)Dummy方法,去“代替接收”setSeparatorInset消息,,防止在iOS6上的Crash,。

代碼如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@implementation UITableViewCell (TTGCompatibility)

+ (void)load {
// 編譯時(shí)判斷SDK
#if __IPHONE_OS_VERSION_MAX_ALLOWED > __IPHONE_7_0
// 運(yùn)行時(shí)判斷系統(tǒng)版本
if ([UIDevice currentDevice].systemVersion.floatValue < 7.0f) {
Method newMethod = class_getInstanceMethod(self, @selector(compatible_setSeparatorInset:));
// 增加Dummy方法
class_addMethod(
self,
@selector(setSeparatorInset:),
method_getImplementation(newMethod),
method_getTypeEncoding(newMethod));
}
#endif
}

// setSeparatorInset: 的Dummy方法
- (void)compatible_setSeparatorInset:(UIEdgeInsets) inset {
// 空方法都可以,只是為了接收setSeparatorInset:消息,。
}

總結(jié)

在適配舊版本時(shí),,除了基本的宏定義,、[UIDevice currentDevice].systemVersion判斷,適當(dāng)?shù)挠肦untime,,可以大大減少對(duì)現(xiàn)有代碼的“干涉”,,多種方法相結(jié)合才是最好的。

嗯,,還在用iOS6的用戶,,升個(gè)級(jí)唄=。=

參考

    本站是提供個(gè)人知識(shí)管理的網(wǎng)絡(luò)存儲(chǔ)空間,,所有內(nèi)容均由用戶發(fā)布,,不代表本站觀點(diǎn)。請(qǐng)注意甄別內(nèi)容中的聯(lián)系方式,、誘導(dǎo)購(gòu)買(mǎi)等信息,,謹(jǐn)防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,,請(qǐng)點(diǎn)擊一鍵舉報(bào),。
    轉(zhuǎn)藏 分享 獻(xiàn)花(0

    0條評(píng)論

    發(fā)表

    請(qǐng)遵守用戶 評(píng)論公約

    類似文章 更多