iOS 內(nèi)存管理

? 前言:隨著手機(jī)市場日新月異的更新袖瞻,目前無論安卓手機(jī)還是iPhone手機(jī)的內(nèi)存都越來越大忠寻,但是手機(jī)系統(tǒng)和App也越來越大,越來越復(fù)雜瑟蜈,這時內(nèi)存管理仍然是必要的,不然再大的內(nèi)存也會消耗殆盡习勤,從而手機(jī)卡死踪栋,App閃退崩潰。

一图毕、基本概念

1夷都、內(nèi)存管理概念

? 移動設(shè)備的內(nèi)存有限,每個app所能占用的內(nèi)存也是有限制的,當(dāng)app所占用的內(nèi)存較多時囤官,這時得回收一些不需要再使用的內(nèi)存空間冬阳。iOS使用引用計數(shù)來管理OC對象的內(nèi)存,有兩種內(nèi)存管理方案:

? 1)ARC:Automatic Reference Counting党饮,自動引用計數(shù)肝陪,系統(tǒng)自動幫你管理引用計數(shù),從iOS 5開始以后使用刑顺,主要是對OC的對象類型有用(因為對象類型存儲在堆區(qū)氯窍,需要自己手動管理),對于基本數(shù)據(jù)類型比如int蹲堂、float等無效(因為基本數(shù)據(jù)類型在棧區(qū)狼讨,由系統(tǒng)自動管理),ARC目前是主流柒竞;說白了政供,就是編譯器自動幫你在合適的位置插入retain/release方法

? 2)MRC:Manual Reference Counting朽基,手動引用計數(shù)布隔,需要手動管理引用計數(shù),iOS 5之前的方案稼虎,目前基本不使用了衅檀,但是在CoreGraphics、CoreFoundation框架里渡蜻,還是要經(jīng)常手動釋放對象的术吝。

// 畫一條直線
CGMutablePathRef path = CGPathCreateMutable(); //創(chuàng)建path
CGPathMoveToPoint(path, nil, 100, 100);
CGPathAddLineToPoint(path, nil, 150, 100);
CGPathRelease(path); //需要手動Release對象
// CFRelease(path); // 等效
2、引用計數(shù)

? 一個新創(chuàng)建的OC對象引用計數(shù)默認(rèn)是1茸苇,當(dāng)引用計數(shù)減為0排苍,OC對象就會銷毀,釋放其占用的內(nèi)存空間学密。調(diào)用retain或strong會讓OC對象的引用計數(shù)+1淘衙,調(diào)用release會讓OC對象的引用計數(shù)-1。

? 簡單總結(jié):當(dāng)調(diào)用alloc腻暮、new彤守、copy、mutableCopy方法返回了一個對象哭靖,在不需要這個對象時具垫,要調(diào)用release或者autorelease(引用計數(shù)不會立刻減一)來釋放它;想擁有某個對象试幽,就讓它的引用計數(shù)+1筝蚕,不想再擁有某個對象,就讓它的引用計數(shù)-1。

3起宽、AutoreleasePool

? 官方文檔-NSAutoreleasePool洲胖,Autorelease延遲了對象的銷毀的時間。

// 在MRC
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
// Code here
[pool release];

// 在ARC
@autoreleasepool {
    // Code here
}

1)AutoreleasePool概念:自動釋放池坯沪,OC中的一種內(nèi)存自動回收機(jī)制绿映,它可以延遲加入AutoreleasePool中的變量release的時機(jī)矛双。

2)AutoreleasePool原理:ARC下殿如,我們使用@autoreleasepool{}來使用一個AutoreleasePool,隨后編譯器將其改寫成下面的樣子:

// atautoreleasepoolobj:哨兵對象
void *atautoreleasepoolobj = objc_autoreleasePoolPush();
// {}中的代碼
objc_autoreleasePoolPop(atautoreleasepoolobj);

而這兩個函數(shù)都是對AutoreleasePoolPage的簡單封裝登下,所以自動釋放機(jī)制的核心就在于這個類藻糖。

3)AutoreleasePoolPage:AutoreleasePoolPage是一個C++實現(xiàn)的類卸奉,結(jié)構(gòu)代碼為:

class AutoreleasePoolPage {
    ...
    id *next; //指向當(dāng)前可插入對象的地址
    pthread_t const thread; //當(dāng)前線程 
    AutoreleasePoolPage * const parent; //前驅(qū)指針
    AutoreleasePoolPage *child; //后繼指針

    static void * operator new(size_t size) { //創(chuàng)建PoolPage,SIZE為4096
        return malloc_zone_memalign(malloc_default_zone(), SIZE, SIZE);
    }
    
    /**
    begin()和end()方法標(biāo)記了被自動管理對象的范圍
    */
    id * begin() { //最低地址
        return (id *) ((uint8_t *)this+sizeof(*this));
    }
    id * end() { //最高地址
        return (id *) ((uint8_t *)this+SIZE);
    } 
    ...
}
  • AutoreleasePool并沒有單獨的結(jié)構(gòu)颖御,而是由若干個AutoreleasePoolPage以雙向鏈表的形式組合而成(分別對應(yīng)結(jié)構(gòu)中的parent指針和child指針,即前驅(qū)和后繼指針)凝颇。
  • AutoreleasePoolPage每個對象會開辟4096字節(jié)內(nèi)存(也就是虛擬內(nèi)存一頁的大信斯啊),一部分存自己的實例變量拧略,大部分存autorelease對象的地址芦岂。
  • 結(jié)構(gòu)中的thread指針指向當(dāng)前線程。
  • 一個AutoreleasePoolPage的空間被占滿時垫蛆,會新建一個AutoreleasePoolPage對象禽最,連接鏈表,后來的autorelease對象在新的page加入袱饭。

向一個對象發(fā)送- autorelease消息川无,就是將這個對象加入到當(dāng)前AutoreleasePoolPage的棧頂next指針指向的位置

4)Autorelease對象什么時候釋放:每當(dāng)進(jìn)行一次objc_autoreleasePoolPush調(diào)用時虑乖,runtime向當(dāng)前的AutoreleasePoolPage中add進(jìn)一個哨兵對象懦趋,objc_autoreleasePoolPush的返回值正是這個哨兵對象的地址,被objc_autoreleasePoolPop()作為入?yún)⒄钗叮敲矗?/p>

1仅叫、根據(jù)傳入的哨兵對象地址找到哨兵對象所處的page;

2糙捺、在當(dāng)前page中诫咱,將晚于哨兵對象插入的所有autorelease對象都發(fā)送一次- release消息,并移動next指針到正確位置洪灯;

3坎缭、從最新加入的對象一直向前清理,可以向前跨越若干個page,直到哨兵所在的page幻锁。

總結(jié):在objc_autoreleasePoolPop的時候?qū)utorelease對象進(jìn)行釋放凯亮。

5)嵌套的AutoreleasePool:知道了上面的原理,嵌套的AutoreleasePool就非常簡單了哄尔,pop的時候總會釋放到上次push的位置為止假消,多層的pool就是多個哨兵對象而已,就像剝洋蔥一樣岭接,每次一層富拗,互不影響。

6)AutoreleasePool本身什么時候釋放:每個線程(包括主線程)擁有NSAutoreleasePool的棧鸣戴。如果新的pool被創(chuàng)建(調(diào)用objc_autoreleasePoolPush)啃沪,它就會被添加到棧頂;當(dāng)pool被銷毀(調(diào)用objc_autoreleasePoolPop)窄锅,它就從棧頂被移除创千;最新發(fā)送autorelease消息的對象,會被添加到最近的自動釋放池(即棧頂?shù)尼尫懦兀┤胪担划?dāng)線程終止時追驴,會把棧內(nèi)所有的釋放池移除。

7)main函數(shù)的autoreleasepool作用:從技術(shù)角度看疏之,不是非要有個自動釋放池殿雪。因為塊的末尾恰好是應(yīng)用程序的終止處,而此時操作系統(tǒng)會將引用程序所占的全部內(nèi)存都釋放掉锋爪。雖說如此丙曙,但是如果不寫這個塊的話,那么由UIApplicationMain函數(shù)所自動釋放的那些對象其骄,就沒有自動釋放池可用亏镰,于是系統(tǒng)發(fā)出了警告,所以說年栓,這個池子可以理解成最外圍捕捉自動釋放對象用的拆挥。

8)Autoreleasepool 與 Runloop 的關(guān)系:主線程默認(rèn)為我們開啟 Runloop,Runloop 會自動幫我們創(chuàng)建Autoreleasepool某抓,并進(jìn)行Push纸兔、Pop 等操作來進(jìn)行內(nèi)存管理。準(zhǔn)確的說否副,在kCFRunLoopEntry即將進(jìn)入的時候進(jìn)行push汉矿,創(chuàng)建pool;在kCFRunLoopBeforeWaiting即將休眠的時候备禀,進(jìn)行pop和push洲拇,即釋放舊的池并創(chuàng)建新池奈揍,在kCFRunLoopExit即將退出RunLoop的時候進(jìn)行pop,釋放舊的池赋续。更多詳情-iOS RunLoop男翰。

9)ARC 下什么樣的對象由 Autoreleasepool 管理:所有的對象都?xì)wAutoreleasePool管理嗎?非也纽乱,在ARC環(huán)境下蛾绎,對于普通的對象(通過alloc、new鸦列、copy租冠、mutableCopy創(chuàng)建)是由編譯器在合適的地方為我們 Realease,只有收到Autorelease消息的對象才歸AutoreleasePool管理薯嗤。

那到底什么情況才歸AutoreleasePool管理呢顽爹?

  • 系統(tǒng)自帶的方法中,如果不包含alloc new copy mutableCopy骆姐,則這些方法返回的對象都是autorelease的镜粤。比如[NSDate date],[NSString stringWithFormat:@"%ld", i]等玻褪。
  • 開發(fā)者自己通過方法創(chuàng)建并返回一個對象繁仁,比如自己創(chuàng)建的類方法返回的對象就是autorelease的,因為需要延遲調(diào)用归园。

10)子線程默認(rèn)不會開啟 Runloop,那出現(xiàn) Autorelease 對象如何處理稚矿?不手動處理會內(nèi)存泄漏嗎庸诱?:如果在子線程你創(chuàng)建了 Pool 的話,產(chǎn)生的 Autorelease 對象就會交給 pool 去管理晤揣;如果你沒有創(chuàng)建 Pool 桥爽,但是產(chǎn)生了 Autorelease 對象,就會調(diào)用 autoreleaseNoPage 方法昧识,在這個方法中钠四,會自動幫你創(chuàng)建一個 hotpage(hotPage 可以理解為當(dāng)前正在使用的 AutoreleasePoolPage),并把Autorelease對象放進(jìn)該Pool中跪楞。也就是說你不進(jìn)行手動的內(nèi)存管理缀去,也不會內(nèi)存泄漏啦。參考-各個線程 Autorelease 對象的內(nèi)存管理甸祭。

4缕碎、內(nèi)存分區(qū)

? 內(nèi)存分區(qū)大體分為5個區(qū):

1、代碼區(qū):存放App代碼池户,App程序會拷貝到這里咏雌。

2凡怎、常量區(qū):常量字符串就是放在這里的,還有const常量赊抖。

3统倒、全局區(qū)/靜態(tài)區(qū)(static):全局變量和靜態(tài)變量的存儲是放在一塊的,初始化的全局變量和靜態(tài)變量在一塊區(qū)域氛雪, 未初始化的全局變量和未初始化的靜態(tài)變量在相鄰的另一塊區(qū)域房匆,程序結(jié)束后由系統(tǒng)釋放。

4注暗、堆區(qū)(heap):需要我們自己管理內(nèi)存坛缕,alloc申請內(nèi)存release釋放內(nèi)存。創(chuàng)建的對象也都放在這里捆昏, 地址是從低到高分配赚楚。堆是所有程序共享的內(nèi)存,當(dāng)N個這樣的內(nèi)存得不到釋放骗卜,堆區(qū)會被擠爆宠页,程序立馬癱瘓。

5寇仓、棧區(qū)(stack):由系統(tǒng)去管理內(nèi)存举户,地址從高到低分配,F(xiàn)irst In Last Out先進(jìn)后出原則遍烦。會存一些局部變量俭嘁,函數(shù)跳轉(zhuǎn)時現(xiàn)場保護(hù)(寄存器值保存于恢復(fù)),這些系統(tǒng)都會幫我們自動實現(xiàn)服猪,無需我們干預(yù)供填。所以大量的局部變量,深遞歸罢猪,函數(shù)循環(huán)調(diào)用都可能耗盡棧內(nèi)存而造成程序崩潰 近她。

來一張高清大圖表示各個分區(qū)的地址高低:

iOS內(nèi)存地址的分配
5、淺拷貝和深拷貝

? 1)淺拷貝:指針拷貝膳帕,并沒有創(chuàng)建新的對象粘捎,比如NSString、NSArray危彩、NSDictionary調(diào)用copy方法攒磨;

? 2)深拷貝:創(chuàng)建一個新的對象,比如NSMutableString汤徽、NSMutableArray咧纠、NSMutableDictionary調(diào)用copy方法,生成一個不可變的新對象泻骤,或者NSString漆羔、NSArray梧奢、NSDictionary調(diào)用mutableCopy生成一個新的可變的對象;

- (void)viewDidLoad {
    [super viewDidLoad];
    
    // 淺拷貝:NSString演痒、NSArray亲轨、NSDictionary調(diào)用copy方法
    NSString *str1 = @"Hello";
    NSString *str2 = [str1 copy]; //淺拷貝
    
    // 深拷貝:NSMutableString、NSMutableArray鸟顺、NSMutableDictionary調(diào)用copy方法
    NSMutableString *str3 = [[NSMutableString alloc] initWithString:@"World"];
    NSString *str4 = [str3 copy]; //深拷貝
    // 深拷貝:NSString惦蚊、NSArray、NSDictionary調(diào)用mutableCopy方法
    NSMutableString *str5 = [str1 mutableCopy]; //深拷貝
    
    NSLog(@"str1 = %p", str1);
    NSLog(@"str2 = %p", str2);
    NSLog(@"str3 = %p", str3);
    NSLog(@"str4 = %p", str4);
    NSLog(@"str5 = %p", str5);
}

運行結(jié)果:

str1 = 0x10e0bd440
str2 = 0x10e0bd440
str3 = 0x600002d7f240
str4 = 0xe641bba2b7d9af9f
str5 = 0x600002d7c750

二讯嫂、循環(huán)引用

? 既然現(xiàn)在都是ARC的時代了蹦锋,系統(tǒng)幫你管理引用計數(shù),幫你管理內(nèi)存分配欧芽,你就可以安枕無憂嗎莉掂?非也,如果代碼使用不當(dāng)千扔,可能造成對象之間的循環(huán)引用憎妙,導(dǎo)致引用計數(shù)大于0,系統(tǒng)沒法回收內(nèi)存曲楚,導(dǎo)致內(nèi)存泄漏厘唾;如果反復(fù)出現(xiàn)內(nèi)存泄漏,當(dāng)使用的內(nèi)存超過系統(tǒng)限制時龙誊,App被系統(tǒng)kill抚垃,App程序閃退。

? 下面介紹三種常見的循環(huán)引用模式:

1趟大、三種循環(huán)引用模式

? 1)自循環(huán)引用:對象的強(qiáng)持有變量指向自身讯柔,比如ViewController強(qiáng)持有一個block,在block里又捕獲持有ViewController护昧,造成自循環(huán)引用;

? 2)相互循環(huán)引用:比如定義一個A類和B類粗截,A類有一個B類的屬性惋耙,B類有一個A類的屬性,修飾詞都是strong類型熊昌,在A類里面訪問B類屬性和B類里邊訪問A類屬性绽榛,那么就會出現(xiàn)相互持有,不會走dealloc方法婿屹;

? 3)多循環(huán)引用:比如類似于三角戀關(guān)系灭美,A持有B,B持有C昂利,C又持有A届腐,造成循環(huán)引用铁坎;

2、如何解決循環(huán)引用

? 1)_ _weak:弱引用犁苏,項目中使用最多的方式硬萍,無論是使用weak修飾self,還是修飾delegate等围详,使用廣泛朴乖。weak指針指向的對象在被廢棄之后會被自動置為nil:

? 當(dāng)weak指針指向的對象被廢棄之后,dealloc的內(nèi)部實現(xiàn)當(dāng)中會調(diào)用清除弱引用的一個方法助赞。然后在清除弱引用的方法當(dāng)中买羞,會通過哈希算法來查找被廢棄對象在弱引用表當(dāng)中的位置,來提取所對應(yīng)的弱引用指針的列表數(shù)組雹食,然后進(jìn)行for循環(huán)遍歷畜普,把每一個weak指針都置為nil。

? 2)_ _unsafe_unretained:不安全引用婉徘,修飾對象不會增加引用計數(shù)漠嵌,當(dāng)指針指向的對象被廢棄后,指針不會置為nil盖呼,成為懸垂指針儒鹿;

? 3)_ _block:在MRC模式下,_ _block修飾對象不會增加引用計數(shù)几晤,避免了循環(huán)引用约炎;在ARC模式下, _ _block一般用來在block內(nèi)部修改外部的局部變量蟹瘾。如果對于block有興趣圾浅,移步-iOS Block

@property(nonatomic, copy) void (^testBlock)(void);

- (void)viewDidLoad {
    [super viewDidLoad];
    
    __weak typeof(self) wself = self;
//    __unsafe_unretained typeof(self) wself = self;
    self.testBlock = ^{
            //當(dāng)然憾朴,如果不在block內(nèi)使用self狸捕,就不會捕獲self,自然就沒有循環(huán)引用了
        NSLog(@"testBlock = %@", wself); 
    };
    self.testBlock();
}

- (void)dealloc {
    NSLog(@"ViewController dealloc");
}

三众雷、解決NSTimer灸拍、CADisplayLink內(nèi)存泄漏問題

? 由于NSTimer在項目中經(jīng)常使用,并且就算使用weak修飾砾省,還是會存在內(nèi)存泄漏問題鸡岗,所以單獨拿出來用代碼解釋說明,并提供優(yōu)雅的解決方案编兄。CADisplayLink也是定時器轩性,想詳細(xì)了解-iOS RunLoop

@interface BViewController ()
@property(nonatomic, assign) NSInteger num;
@property(nonatomic, weak) NSTimer *timer; //使用weak修飾也不能解決內(nèi)存泄漏
@end

@implementation BViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    self.num = 0;
    // 默認(rèn)自動添加到RunLoop狠鸳,也就是RunLoop持有timer揣苏,直到調(diào)用invalidate悯嗓,才移除timer
    self.timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(timerFire:) userInfo:nil repeats:YES];
}

- (void)dealloc {
    NSLog(@"BViewController dealloc");
    // invalidate:停止定時器,并把timer從RunLoop中移除舒岸,并把timer的target強(qiáng)引用去除
    [self.timer invalidate];
    self.timer = nil;
}

- (void)timerFire:(NSTimer *)timer {
    self.num ++;
    NSLog(@"num = %ld", self.num);
}

@end

先從一個AViewController跳轉(zhuǎn)至BViewController绅作,點擊返回按鈕,發(fā)現(xiàn)并沒有調(diào)用dealloc析構(gòu)函數(shù)蛾派,為什么俄认?首先BViewController弱持有timer,timer強(qiáng)持有BViewController洪乍,當(dāng)點擊返回按鈕時眯杏,NavigationController不再持有BViewController,當(dāng)前的RunLoop仍然強(qiáng)持有timer壳澳,而timer強(qiáng)持有BViewController岂贩,所以BViewController引用計數(shù)不為0,自然得不到釋放巷波。

NSTimer強(qiáng)引用ViewController

其實不管BViewController用strong還是用weak修飾timer萎津,最終timer都會強(qiáng)持有BViewController,造成內(nèi)存泄漏∧鳎現(xiàn)在列出三種解決方案:

1锉屈、使用block回調(diào)

? 使用block回調(diào)處理事件,而不是使用target垮耳,這樣就不會強(qiáng)持有target颈渊,但是是iOS 10之后才有的方法。

@interface BViewController ()
@property(nonatomic, assign) NSInteger num;
@property(nonatomic, strong) NSTimer *timer;
@end

@implementation BViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    self.num = 0;
    __weak typeof(self) wself = self;
    // 注意:這個方法是iOS 10之后的
    self.timer = [NSTimer scheduledTimerWithTimeInterval:1.0 repeats:YES block:^(NSTimer * _Nonnull timer) {
        [wself timerFire:timer];
    }];
}

- (void)dealloc {
    NSLog(@"BViewController dealloc");
    // invalidate:停止定時器终佛,并把timer從RunLoop中移除俊嗽,并把timer的target強(qiáng)引用去除
    [self.timer invalidate];
    self.timer = nil;
}

- (void)timerFire:(NSTimer *)timer {
    self.num ++;
    NSLog(@"num = %ld", self.num);
}

@end
2、使用動態(tài)消息解析

? RunLoop會對timer強(qiáng)引用铃彰,timer對BViewController強(qiáng)引用绍豁,那么我們可以創(chuàng)建一個中間類來弱引用BViewController,讓timer對中間類強(qiáng)引用牙捉,方法2和下面的方法3都是基于這個思想做的竹揍。當(dāng)然這里還牽扯到OC的消息轉(zhuǎn)發(fā)機(jī)制,如果有興趣請看-iOS Runtime鹃共。

給NSTimer添加中間件

創(chuàng)建一個中間類TimerMiddleware:

// .h文件
@interface TimerMiddleware : NSObject
+ (instancetype)middlewareWithTarget:(id)target;
@end

// .m文件
@interface TimerMiddleware ()
/**
 這里使用weak,弱引用驶拱,才能對BViewController弱引用霜浴,才能解決內(nèi)存泄漏
 如果使用strong,那么還是會內(nèi)存泄漏
 */
@property(nonatomic, weak) id target;
@end

@implementation TimerMiddleware
+ (instancetype)middlewareWithTarget:(id)target {
    TimerMiddleware *middleware = [TimerMiddleware new];
    middleware.target = target;
    return middleware;
}

- (id)forwardingTargetForSelector:(SEL)aSelector {
    return self.target;
}

@end

在BViewController中:

// 其他代碼和上面一樣
- (void)viewDidLoad {
    [super viewDidLoad];
    
    self.num = 0;
    TimerMiddleware *middleware = [TimerMiddleware middlewareWithTarget:self];
    self.timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:middleware selector:@selector(timerFire:) userInfo:nil repeats:YES];
}

運行結(jié)果:BViewController會走dealloc方法蓝纲,說明解決了內(nèi)存泄漏問題阴孟。

3晌纫、使用NSProxy轉(zhuǎn)發(fā)消息

1、NSProxy是跟NSObject一個級別的基類永丝,用來設(shè)計做消息轉(zhuǎn)發(fā)的锹漱;

2、NSProxy是抽象類慕嚷,使用時候我們需要使用其子類哥牍;

3、NSProxy和NSObject都遵循NSObject協(xié)議喝检;

4嗅辣、NSProxy不會跟NSObject類一樣去父類搜索方法實現(xiàn),會直接進(jìn)入消息轉(zhuǎn)發(fā)流程挠说,所以效率更高澡谭。

先創(chuàng)建一個TimerProxy,繼承自NSProxy:

// .h文件
@interface TimerProxy : NSProxy
+ (instancetype)proxyWithTarget:(id)target;
@end

// .m文件
@interface TimerProxy ()
@property(nonatomic, weak) id target;
@end

@implementation TimerProxy

+ (instancetype)proxyWithTarget:(id)target {
    TimerProxy *proxy = [TimerProxy alloc]; //注意:沒有init方法
    proxy.target = target;
    return proxy;
}

// NSProxy接收到消息會自動進(jìn)入到調(diào)用這個方法 進(jìn)入消息轉(zhuǎn)發(fā)流程
- (nullable NSMethodSignature *)methodSignatureForSelector:(SEL)sel {
    return [self.target methodSignatureForSelector:sel];
}

- (void)forwardInvocation:(NSInvocation *)invocation {
    [invocation invokeWithTarget:self.target];
}
@end

在BViewController中:

// 其他代碼和上面一樣
- (void)viewDidLoad {
    [super viewDidLoad];
    
    self.num = 0;
    TimerProxy *proxy = [TimerProxy proxyWithTarget:self];
    self.timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:proxy selector:@selector(timerFire:) userInfo:nil repeats:YES];
}

總結(jié):如果不管iOS系統(tǒng)版本损俭,那么使用block方式簡單蛙奖;如果需要兼容系統(tǒng)版本,那么使用NSProxy更加高效杆兵,一次封裝雁仲,終生受用??。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末拧咳,一起剝皮案震驚了整個濱河市伯顶,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌骆膝,老刑警劉巖祭衩,帶你破解...
    沈念sama閱讀 216,692評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異阅签,居然都是意外死亡掐暮,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,482評論 3 392
  • 文/潘曉璐 我一進(jìn)店門政钟,熙熙樓的掌柜王于貴愁眉苦臉地迎上來路克,“玉大人,你說我怎么就攤上這事养交【悖” “怎么了?”我有些...
    開封第一講書人閱讀 162,995評論 0 353
  • 文/不壞的土叔 我叫張陵碎连,是天一觀的道長灰羽。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么廉嚼? 我笑而不...
    開封第一講書人閱讀 58,223評論 1 292
  • 正文 為了忘掉前任玫镐,我火速辦了婚禮,結(jié)果婚禮上怠噪,老公的妹妹穿的比我還像新娘恐似。我一直安慰自己,他們只是感情好傍念,可當(dāng)我...
    茶點故事閱讀 67,245評論 6 388
  • 文/花漫 我一把揭開白布矫夷。 她就那樣靜靜地躺著,像睡著了一般捂寿。 火紅的嫁衣襯著肌膚如雪口四。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,208評論 1 299
  • 那天秦陋,我揣著相機(jī)與錄音蔓彩,去河邊找鬼。 笑死驳概,一個胖子當(dāng)著我的面吹牛赤嚼,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播顺又,決...
    沈念sama閱讀 40,091評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼更卒,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了稚照?” 一聲冷哼從身側(cè)響起蹂空,我...
    開封第一講書人閱讀 38,929評論 0 274
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎果录,沒想到半個月后上枕,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,346評論 1 311
  • 正文 獨居荒郊野嶺守林人離奇死亡弱恒,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,570評論 2 333
  • 正文 我和宋清朗相戀三年辨萍,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片返弹。...
    茶點故事閱讀 39,739評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡锈玉,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出义起,到底是詐尸還是另有隱情拉背,我是刑警寧澤,帶...
    沈念sama閱讀 35,437評論 5 344
  • 正文 年R本政府宣布默终,位于F島的核電站椅棺,受9級特大地震影響抡诞,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜土陪,卻給世界環(huán)境...
    茶點故事閱讀 41,037評論 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望肴熏。 院中可真熱鬧鬼雀,春花似錦、人聲如沸蛙吏。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,677評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽鸦做。三九已至励烦,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間泼诱,已是汗流浹背坛掠。 一陣腳步聲響...
    開封第一講書人閱讀 32,833評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留治筒,地道東北人屉栓。 一個月前我還...
    沈念sama閱讀 47,760評論 2 369
  • 正文 我出身青樓,卻偏偏與公主長得像耸袜,于是被迫代替她去往敵國和親友多。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,647評論 2 354