讀後感先放在前邊 如今詳細來看這本書應該也不晚吧,iOS 開發之類的書籍其實網上的總結仍是蠻多的 有不少文章寫得都是挺不錯的, 可是終歸是別人的的讀後感總結,看着別人的總結終歸不能徹底吸取爲本身的,因此近期抽空把 iOS 相關書籍看一遍 來對本身的知識體系作一個校驗 書中舉得不少例子都是挺好的 此文章也總結了此書的大綱,只有一些本人比較生疏的知識點纔會展開詳細描述,書中有不少細節並非咱們平常開發中能注意到的可是很重要的一些知識點, 此篇文章寫得耗費時間仍是挺久的程序員
OC 語言使用了"消息結構" 而非是"函數調用" 消息結構與函數調用區別關鍵在於: 一 使用消息結構的語言,其運行時所應執行的代碼有運行環境來決定 二 使用函數調用的語言,則有編譯器決定的 OC 語言使用動態綁定的消息結構,也就是說在在運行時纔會檢查對象類型,接受一條消息以後,究竟應執行何種代碼, 有運行期環境而非編譯器來決定算法
下圖來看一下 OC 對象的內存分配 數據庫
此圖佈局演示了一個分配在對堆中的 NSString 實例, 有兩個分配在棧上的指針指向改實例 OC 系統框架中也有不少使用結構體的, 好比 CGRect, 由於若是改用 OC 對象來作的話, 性能就會受影響@Class Person
來代表 Person 是一個類)來說起別的類, 並在實現文件中引入那些類的 頭文件, 這樣作儘可能下降類之間 的耦合推薦使用字面量語法:編程
NSString * someString = @"奧卡姆剃鬚刀";
NSNumber *number = @18;
NSArray *arr = @[@"123",@"456]; NSDictionary *dict = @{ @"key":@"value" }; 複製代碼
對應的非字面量語法數組
NSString *str = [NSString stringWithString:@"奧卡姆"];
NSNumber *number = [NSNumber numberWithInt:18];
NSArray *arr = [NSArray arrayWithObject:@"123",@"456"];
複製代碼
不要使用預處理指令定義常量, 這樣定義出來的常量不含類型,編譯器只會在編譯前據此執行查找與替換操做, 即便有人從新定義了常量值, 編譯器也不會產生警告信息, 這將致使應用程序中的常量值不一致緩存
在實現文件中使用 static const 來定義"只在編譯單元內可見的常量",因爲此類常量不在全局符號表中, 因此無須爲其名稱加前綴安全
舉例說明 不合適的寫法bash
//動畫時間
#define ANIMATION_DUATION 0.3
複製代碼
正確的寫法網絡
視圖修改 const修飾的變量則會報錯
static const NSTimeInterval KAnimationDuration = 0.3
複製代碼
// EOCAnimatedView.h
extern const NSTiemInterval EOCAnimatedViewANmationDuration
// EOCAnimatedView.m
const NSTiemInterval EOCAnimatedViewANmationDuration = 0.3
複製代碼
這樣定義的常量要優於# Define 預處理指令, 由於編譯器會確保常量不變, 並且外部也可使用數據結構
按位或操做符枚舉
typedef NS_OPTIONS(NSUInteger, UIViewAutoresizing) {
UIViewAutoresizingNone = 0,
UIViewAutoresizingFlexibleLeftMargin = 1 << 0,
UIViewAutoresizingFlexibleWidth = 1 << 1,
UIViewAutoresizingFlexibleRightMargin = 1 << 2,
UIViewAutoresizingFlexibleTopMargin = 1 << 3,
UIViewAutoresizingFlexibleHeight = 1 << 4,
UIViewAutoresizingFlexibleBottomMargin = 1 << 5
};
複製代碼
isEqual
與 hash
方法此小節比較抽象,用文中的規則來總結一下 大概以下
這種方法我在分類中常用,並且屢試不爽 如下是本人在項目中的用法
static void *callBackKey = "callBackKey";
@implementation UIView (category)
- (void)addTapWithBlock:(callBack)callback{
objc_setAssociatedObject(self, callBackKey, callback, OBJC_ASSOCIATION_COPY);
self.userInteractionEnabled = YES;
UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc]initWithTarget:self action:@selector(tapClick)];
[self addGestureRecognizer:tap];
}
- (void)tapClick{
callBack block = objc_getAssociatedObject(self, callBackKey);
if (block) {
block();
}
}
複製代碼
objc_msgSend 函數會依據接受者與選擇子的類型來調用適當的方法,爲了完成此操做, 該方法須要在接受者所屬的類中搜尋其"方法列表" ,若是能找到與選擇名稱相符的方法,就跳至其實現代碼, 如果找不到 就沿着繼承體系繼續向上查找, 等找到合適的方法在挑戰, 若是仍是找不到相符的方法,那就執行"消息轉發"操做 這個會在12條來說
消息轉發分爲兩大階段,第一階段先徵詢接受者,所屬的類, 看其是否能動態添加方法,以處理當前這個"未知的的選擇子" 這叫作"動態方法解析",第二階段涉及完整的消息轉發機制. 若是運行期系統已經把第一階段執行完了, 那麼接受者本身就沒法再以動態新增方法的手段來響應包含蓋選擇子的消息了, 此時,運行期系統會請求接受者以其餘手段來處理與消息相關的方法調用, 這又細分兩小步. 首先請接受者看看有沒有其餘對象能處理這條消息,如有 則運行期系統會吧消息轉給那個對象,因而消息轉發過程結束,一切如常, 若沒有背援的接受者,則啓動完整的消息轉發機制,運行期系統會吧消息有關的所有細節都封裝在 NSInvocation 對象中, 在給接受者最後一次機會, 令其設法解決當前還沒處理的這條消息
動態方法解析 + (Bool) resolveInstanceMethod:(SEL)selector
該方法的參數就是那個未知的選擇子,其返回值爲 Boolean 類型,表示這個類是否能新增一個實例方法用以處理此選擇子.在繼續往下執行轉發機制以前, 本類有機會新增一個處理此選擇子的方法,假如還沒有實現的方法不是實例方法而是類方法, 那麼運行期系統就會調用另一個方法 和當前方法相似 resolveClassMethod
備援接受者 當前接受者還有第二次機會能處理未知的選擇子,在這一步,運行期系統會問它: 能不能把這條消息轉給其餘接受者來處理. 與該步驟對應的處理方法以下: - (id)forwardingTargetForSelestor:(SEL)selector
方法參數表明未知的選擇子, 若當前接受者能找到備援對象,則將其返回,若找不到,就返回 nil
完整的消息轉發 若是轉發算法已經到這一步的話,俺那麼惟一能作的就是啓用完整的消息轉發機制了.首先建立 NSInvocation 對象, 把與還沒有處理的那條消息有關的所有細節, 都封裝於其中,此對象包含選擇子、目標,及參數, 在觸發 NSInvocation 對象時, "消息派發系統"將親自出馬,把消息指派給目標對象 此步驟會調用下列方法來轉發消息 - (void)forwardInvocation:(NSInvocation * )invocation
再觸發消息前, 先以某種方式改變消息內容,好比追加另一個參數,或是改換選擇子等等 實現此方法是,若發現某調用不該有本類處理,擇婿調用超類的同名方法, 這樣的話,繼承體系中的每一個類都有機會處理此調用請求,直至 NSObject, 若是最後調用了 NSOBject 方法,那麼該方法還會繼而調用doesNotRecognizeSelector
以拋出異常,此異常代表選擇子最終未能獲得處理
消息轉發全流程
通俗講 其實就是利用 runtime 實現方法交換 這個就再也不詳細解說了
每一個 Objective-C 對象實例都是指向某塊內存數據的指針,描述 Objective-C對象所用的數據結構定義在運行期程序庫的頭文件裏, id 類型自己也是定義在這裏
typedef struct objc_object {
Class isa;
} * id
複製代碼
因而可知,每一個對象結構體的首個成員是 Class 類的變量. 該變量定義了對象所屬的類,一般稱爲 isa 指針 Class 對象也定義在運行期程序庫的頭文件中中:
typedef struct objc_class *Class;
struct objc_class{
Class isa;
Class super_class;
const char *name;
long version;
long info;
long instance_size;
struct objc_ivar_list *ivars;
struct objc_method_list **methodLists;
struct objc_cache *cache;
struct objc_protocol_list *protocols;
}
複製代碼
此結構體存放類的元數據,此結構體的首個變量也是 isa 指針, 這說明, Class 自己也是 Objective-C 對象,結構體中的 super_class 它定義了本類的超類, 類對象所屬的類型(也就是 isa 指針所指向的類型)是另一個類, 叫作元類,用來表述類對象自己所具有的元數據.每一個類僅有一個類對象,而每一個類對象僅有一個與之相關的元類 假設有一個 someClass 的子類從 NSObject 中繼承而來,則它的繼承體系可由下圖表示
在類繼承體系中查詢類型信息 能夠用類型信息查詢方法來檢視類繼承體系,isMemberOfClass
可以判斷出對象是不是特定類的實例 而isKindOfClass
則可以判斷出對象是否爲某類或某派生派類的實例
UITableViewCell 初始化該類對象的時候,須要指明其樣式及標識符, 標識符可以區分不一樣類型的單元格, 因爲這種對象的建立成本比較高, 因此繪製表格時 可依照標識符來服用,提高程序執行效率,這種能夠爲對象提供必要信息以便其能完成工做的初始化方法叫作"全能初始化方法"
這一點寫開源框架的時候十分的受用
這個就很少說了 實際開發中常常用
class-continuation分類
中將其有 readonly 屬相擴展爲 readwrite 屬性LLPerson.h
@interface LLPerson : NSObject
@property (nonatomic, copy, readonly) NSString *name;
@property (nonatomic, assign, readonly) NSInteger age;
- (instancetype)initWithName:(NSString *)name age:(NSInteger)age;
@end
LLPerson.m
@interface LLPerson()
@property (nonatomic, copy, readwrite) NSString *name;
@property (nonatomic, assign, readwrite) NSInteger age;
@end
@implementation LLPerson
- (instancetype)initWithName:(NSString *)name age:(NSInteger)age{
if (self = [super init]) {
self.name = name;
self.age = age;
}
return self;
}
複製代碼
方法命名的幾條規則
// 好比 有一個抽象基類, 他的正確用法是先從中繼承一個類,而後使用這個子類, 在這種狀況下,若是有人直接使用了一個抽象基類,那麼久拋出異常
- (void)mustOverrideMethod{
NSString *reason = [NSString stringWithFormat:@"%@m must be overridden",
NSStringFromSelector(_cmd)];
@throw [NSException
exceptionWithName:NSInternalInconsistencyException
reason:reason
userInfo:nil];
}
複製代碼
這個就是常規咱們使用的代理了 可是書中講了一個新的知識點 我卻是從前從沒有見過的 能夠一塊兒來看一下
// 定義一個結構體
@interface LLNetWorkFetcher(){
struct {
unsigned int didReceiveData : 1;
unsigned int didDailWIthError : 1;
unsigned int didUpdateProgressTo : 1;
} _delegateFlags;
// 在外界設置代理的時候 重寫 delegate 的 set 方法 對此結構體進行賦值
- (void)setDelegate:(id<LLNetworkFetcherDelegate>)delegate{
_delegate = delegate;
_delegateFlags.didReceiveData = [delegate respondsToSelector:@selector(networkFetcher:didReceiveData:)];
_delegateFlags.didDailWIthError = [delegate respondsToSelector:@selector(networkFetcher:didDailWIthError:)];
_delegateFlags.didUpdateProgressTo = [delegate respondsToSelector:@selector(networkFetcher:didUpdateProgressTo:)];
}
// 這樣在調用的時候只需判斷 結構體裏邊的標誌就能夠了 不須要一直調用 respondsToSelector 這個方法
if (_delegateFlags.didUpdateProgressTo) {
[_delegate networkFetcher:self didUpdateProgressTo:currentProgress];
}
}
複製代碼
分類的方法加入到類中這一操做是在運行期系統加載分類是完成的.運行期系統會把分類中所實現的每一個方法都加入到類的方法列表中,若是類中原本就有此方法,而分類又實現了一次, 那麼分類中的方法會覆蓋原來那一份實現代碼, 實際上可能會發生屢次覆蓋, 屢次覆蓋的結果一最後一個分類爲準
這個老生常談了
class - continuation分類 通俗點來說其實就是咱們平時所說的延展
這一點也很少說了 不過有一個概念確實是以前沒想過的 UIApplication 對象是 跟對象
-fobjc-arc-exceptions
標誌NSObject *object;
@try {
object = [NSObject new];
[object doSomeThingThatMayThrow];
}
@catch(...){
}
@finally{
}
複製代碼
@autoreleasepool
應用程序在執行循環的時候內存峯值就會下降NSArray *dataArr = [NSArray array];
NSMutableArray *personArrM = [NSMutableArray array];
for (NSDictionary *recode in dataArr) {
@autoreleasepool{
LLPerson *person = [[LLPerson alloc]initWithRecode:recode];
[personArrM addObject:person];
}
}
複製代碼
###第六章 塊與大中樞派發
塊自己也是對象,在存放塊對象內存區域中, 首個變量是指向 Class 對象的指針,該指針叫作 isa, 其他內存裏含有塊對象正常運轉所需的各類信息, 在內存佈局中,最重要的就是 invoke 變量,這就是函數指針,指向塊的實現代碼, 函數原型只要要接受一個 void* 型的參數, 此參數表明塊.剛纔說過,塊其實就是一種代替函數指針的語法結構, 原來使用函數指針是,須要用不透明的 void 指針來傳遞狀態 而改用塊以後, 則能夠把原來用標準 C 語言特性所編寫的代碼封裝成簡明且易用的接口.
descriptor 變量是指向結構體的指針, 每一個塊裏都包含此結構體,其中聲明瞭塊對象的整體大小,還聲明瞭 copy 和 dispose 這兩個輔助函數所對象的函數指針, 輔助函數在拷貝及丟棄塊對象時運行, 其中會執行一些操做, 比方說 前者要保留捕獲的對象, 然後者則將之釋放
塊還會把它所捕獲的全部變量都拷貝一份, 這些拷貝放在 descriptor 變量後邊,捕獲了多少變量,就要佔據多少內存空間, 請注意, 拷貝的並非對象變量,而是指向這些對象的指針變量, invoke 函數爲什麼須要把塊對象做爲參數傳進來呢? 緣由就在於,執行塊的時候 要從內存中把這些捕獲到的變量讀出來
這一點就詳細說說吧
在 OC 中多線程要執行同一份代碼,那麼有時可能會出問題, 這種狀況下,一般要使用鎖來實現某種同步機制.
在 GCD 出現以前, 有兩種方法:
- (void)synchronizedMethod{
@synchronized(self){
// safe
}
}
複製代碼
_lock = [[NSLock alloc]init];
- (void)synchronizedMethod{
[_lock lock];
// safe
[_lock unlock];
}
複製代碼
這兩種方法都很好不過也都有缺陷 好比說,在極端狀況下,同步塊會致使死鎖, 另外 其效率也不見得高, 而若是直接使用鎖對象的話,一旦遇到死鎖, 就會很是麻煩
GCD 的到來它能以更簡單更高效的形式爲代碼加鎖
咱們都知道屬性就是開發者常常須要同步的地方,這種屬性須要作成"原子的", 用 atomic 便可實現這一點, 但若是咱們本身實現的話就能夠用 GCD 來實現
_syncQueue = dispatch_queue_create("aokamu.syncQueue", NULL);
- (NSString *)someString{
__block NSString *localSomeString;
dispatch_sync(_syncQueue, ^{
localSomeString = _someString;
});
return localSomeString;
}
- (void)setSomeString:(NSString *)someString{
dispatch_sync(_syncQueue, ^{
_someString = someString;
})
}
複製代碼
上述代碼: 把設置操做與獲取操做都安排在序列化的隊列裏執行了, 這樣的話, 全部針對屬性的訪問操做就都同步了, 所有加鎖任務都在 GCD 中處理, 而 GCD 是至關深的底層來實現的,因而可以作許多優化
- (void)setSomeString:(NSString *)someString{
dispatch_async(_syncQueue, ^{
_someString = someString;
})
}
複製代碼
這個吧同步派發改爲異步派發,能夠提高設置方法的執行速度, 而讀取操做與寫入操做依然會按順序執行, 不過這樣寫昂寫仍是有一個弊端. :若是你測一下程序性能,那麼可能會發現這種寫法比原來慢, 由於執行異步派發時須要拷貝塊.
_syncQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
- (NSString *)someString{
__block NSString *localSomeString;
dispatch_sync(_syncQueue, ^{
localSomeString = _someString;
});
return localSomeString;
}
- (void)setSomeString:(NSString *)someString{
dispatch_barrier_async(_syncQueue, ^{
_someString = someString;
})
}
複製代碼
在隊列中 柵欄塊必須單獨執行, 不能與其餘塊並行, 這隻對併發隊列有意義, 由於串行隊列中的塊老是按順序逐個來執行的, 併發隊列若是發現接下來要處理的塊是個柵欄塊,那麼久一直要等當前全部併發塊都執行完畢,纔會單獨執行這個柵欄塊 待柵欄塊執行事後 再按正常方式向下處理 以下圖
@synchronized
塊或者NSLock
對象更簡單這個如今已經沒有人去用performSelector 系列方法了
在來簡單總結一下操做隊列(NSOPeration)的幾種使用方法 ① 取消某個操做 運行任務前能夠調用 cancel 方法 ,該方法會設置對象內的標誌位,用以代表此任務不須要執行, 不過已經啓動的任務沒法取消了, ②指定操做間的依賴關係 一個操做能夠依賴其餘多個操做 ③ 經過鍵值觀測機制監控 NSOperation 對象的屬性. NSOPeration 對象有許多屬性都適合經過鍵值觀測機制來監聽 ④指定操做的優先級 NSOperation 對象也有"線程優先級",這決定了運行此操做的線程處在何種優先級上
這個也簡單記錄一下把 Dispatch Group 俗稱 GCD 任務組,咱們 用僞代碼來看一下 Dispatch Group的用法
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_group_t dispatchGroup = dispatch_group_create();
for (id object in collectin) {
dispatch_group_async(dispatchGroup,
queue,
^{
[object performTask];
})
}
dispatch_group_notify(dispatchGroup,
dispatch_get_main_queue(),
^{
[self updateUI];
})
複製代碼
notify回調的隊列徹底能夠本身來定 能夠用自定義的串行隊列或全局併發隊列
這裏還有 GCD 的另外一個函數平時比較少用的 那就是dispatch_apply
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_apply(array.count,
queue,
^(size_t i) {
id object = array[i];
[object performTask];
})
複製代碼
dispatch_apply所使用的隊列可使併發隊列, 也能夠是串行隊列, 加入把塊派給了當前隊列(或體系中高於當前隊列的某個串行隊列),這將會致使死鎖,
這個就是老生常談的單例了 也就很少說了
這個函數已經廢棄了 此處就很少說了
咱們開發者常常碰到的就是 Foundation 框架 像NSobject,NSArray,NSDictionary 等類 都在其中,
還有一個與Foundation相伴的框架是 CoreFoundation,CoreFoundation 不是 OC 框架,可是肯定編寫 OC 應用程序時所應熟悉的重要框架,Foundation框架中的許多功能均可以在此框架中找到對應的 C 語言 API 除了 Foundation和CoreFoundation還有如下系統庫:
CFNetwork 此框架提供了 C 語言級別的網絡通訊, 它將"BSD 套接字"抽象成易於使用的網絡接口
CoreAudio 該框架所提供的 C語言 API 可用來操做設備上的音頻硬件, 這個框架屬於比較難用的那種, 由於音頻處理自己就很複雜,所幸由這套 API 能夠抽象出另一個 OC 的 API, 用後者來處理音頻問題會簡單些
AVFoundation 此框架所提供的 OC 對象可用來回放並錄製音頻及視頻,好比 可以在 UI 視圖類播放視頻
CoreData 此框架中所提供的 OC 接口可將對象放入到數據庫中,便於持久保存
CoreText 此框架提供的 C語言接口能夠高效執行文字排版及渲染操做
請記住 用純 C 語言寫的框架與用 OC 寫成的同樣重要, 若想成爲優秀的 OC 開發者, 應該掌握 C 語言的核心概念
NSArray<LLPerson *> *dataArr = [NSArray array];
[dataArr enumerateObjectsUsingBlock:^(LLPerson * _Nonnull obj,
NSUInteger idx,
BOOL * _Nonnull stop) {
}];
複製代碼
NSArray *anNSArray = @[@1,@2,@3,@4,@5];
CFArrayRef aCFArray = (__bridge CFArrayRef)(anNSArray);
NSLog(@"count = %li",CFArrayGetCount(aCFArray));
// Output: count = 5 ;
複製代碼
typedef void(^LLNetWorkFetcherCompleteHandler)(NSData *data);
@interface LLNetWorkFetcher : NSObject
- (instancetype)initWithURL:(NSURL *)url;
- (void)startWithCompletionHandler:(LLNetWorkFetcherCompleteHandler)handler;
@end
#import "LLClass.h"
#import "LLNetWorkFetcher.h"
@implementation LLClass{
NSCache *_cache;
}
- (instancetype)init{
if (self = [super init]) {
_cache = [NSCache new];
_cache.countLimit = 100;
_cache.totalCostLimit = 5 * 1024 * 1024;
}
return self;
}
- (void)downLoadDataForURL:(NSURL *)url{
NSData *cacheData = [_cache objectForKey:url];
if (cacheData) {
[self useData:cacheData];
}else{
LLNetWorkFetcher *fetcher = [[LLNetWorkFetcher alloc]initWithURL:url];
[fetcher startWithCompletionHandler:^(NSData *data) {
[_cache setObject:data forKey:url cost:data.length];
[self useData:cacheData];
}];
}
}
複製代碼
+ (void) load 對於加入運行期系統的每一個類及分類來講,一定會調用此方法並且僅調用一次,當包含類或者分類的程序庫載入系統時, 就會執行此方法 若是分類和其所屬的類都定義了 load 方法, 則先調用類裏邊的 在調用分類裏邊的 load 方法的問題在於執行該方法時,運行期系統是"脆弱狀態",在執行子類的 load 方法以前,一定會先執行全部超類的 load 方法, 若是代碼還依賴了其餘程序庫,那麼程序庫裏相關類的 load 方法也一定會先執行, 根據某個給定的程序庫,卻沒法判斷出其中各個類的載入順序, 所以 在 load 方法中使用其餘類是不安全的. load 方法不像其餘普通方法同樣, 他不聽從那套繼承規則, 若是某個類自己沒有實現 load 方法,那麼無論其各級超類是否實現此方法, 系統都不會調用.
+ (void)initialize 對於每一個類來講 該方法會在程序首次使用該類以前調用, 且只調用一次,他是有運行期系統來調用的,毫不應該經過代碼直接調用 他與 load 方法有必定的區別的 首先 他是惰性調用的, 也就是說只有當程序用到了相關的類是,纔會調用 若是某個類一直都沒有使用, 那麼其 initialize 方法就一直不會運行 其次, 運行期系統在執行該方法時,是處於正常狀態的, 所以 從運行期系統完整度來說, 此時能夠安全使用並調用任意類中的任意方法 並且運行期系統也能確保initialize 方法必定會在"線程安全的環境"中執行,也就是說 只有執行initialize的那個線程 能夠操做類與類實例, 最後, initialize 方法與其餘消息同樣,若是某個類未實現它, 而其超類實現了,俺那麼就會運行超類的實現代碼
在加載階段 若是類實現了 load 方法,那麼系統就會調用它.分類裏也能夠定義此方法,類的 load 方法要比分類中先調用,其餘方法不一樣, load 方法不參與複寫機制
首次使用某個類以前,系統會向其發送initialize 消息,因爲此方法聽從普通的複寫規則,因此一般應該在裏邊判斷當前要初始化的是哪個類
load 和initialize 方法都應該實現的精簡一些, 這有助於保持應用程序的相應能力 也能減小引入"依賴環"的概率
沒法在編譯期設定的全局變量,能夠放在initialize 方法裏初始化
計時器是一種很方便也頗有用的對象,可是 因爲計時器會保留其目標對象, 因此反覆執行任務一般會致使應用程序出問題,也就是很容易引入"保留環" 來看下列代碼
@interface LLClass : NSObject
- (void)startPolling;
- (void)stopPolling;
@end
@implementation LLClass{
NSTimer *_pollTimer;
}
- (void)startPolling{
_pollTimer = [NSTimer scheduledTimerWithTimeInterval:5.0
target:self
selector:@selector(p_doPoll)
userInfo:nil
repeats:YES];
}
- (void)stopPolling{
[_pollTimer invalidate];
_pollTimer = nil;
}
- (void)p_doPoll{
}
- (void)dealloc{
[_pollTimer invalidate];
}
複製代碼
計時器的目標對象是 self, 而後計時器使用實例變量來存放的, 因此實例變量也保存李計時器, 因而就產生了保留環
本書中提供了一個用"塊"來解決的方案 雖然計時器當前並不直接支持塊,可是能夠用下面這段代碼添加功能
@implementation NSTimer (LLBlocksSupport)
+ (NSTimer *)ll_schedeledTimerWithTimeInterval:(NSTimeInterval)interval
block:(void(^)())block
repeats:(BOOL)repeats{
return [self scheduledTimerWithTimeInterval:interval target:self selector:@selector(ll_blockInvoke:) userInfo:[block copy] repeats:repeats];
}
+ (void)ll_blockInvoke:(NSTimer *)timer{
void (^block)() = timer.userInfo;
if (block) {
block();
}
}
複製代碼
上邊的代碼是在 NSTimer 分類中添加的代碼 來看一下具體的使用
- (void)startPolling{
__weak LLClass *weakSelf = self;
_pollTimer = [NSTimer ll_schedeledTimerWithTimeInterval:5.0
block:^{
LLClass *strongSelf = weakSelf;
[strongSelf p_doPoll];
}
repeats:YES];
複製代碼
先定義弱引用,而後用block捕獲這個引用,可是在用以前在馬上生成 strong 引用.保證明例在執行期間持續存活