【IOS學習】基礎知識積累
一.iOS開發之loadView、viewDidLoad及viewDidUnload的關係
1.第一次訪問UIViewController的view時,view為nil,然後就會呼叫loadView方法建立view
2.view建立完畢後會呼叫viewDidLoad方法進行介面元素的初始化
3.當記憶體警告時,系統可能會釋放UIViewController的view,將view賦值為nil,並且呼叫viewDidUnload方法
4.當再次訪問UIViewController的view時,view已經在3中被賦值為nil,所以又會呼叫loadView方法重新建立view
5.view被重新建立完畢後,還是會呼叫viewDidLoad方法進行介面元素的初始化
二.load和initialize的區別
- @implementation Person
- // 只要程式啟動就會將所有類的程式碼載入到記憶體中, 放到程式碼區(無論該類有沒有被使用到都會被呼叫)
- // load方法會在當前類被載入到記憶體的時候呼叫, 有且僅會呼叫一次
- // 如果存在繼承關係, 會先呼叫父類的load方法, 再呼叫子類的load方法
- + (void)load
- {
- NSLog(@"Person類被載入到記憶體了");
- }
- // 噹噹前類第一次被使用的時候就會呼叫(建立類物件的時候)
- // initialize方法在整個程式的執行過程中只會被呼叫一次, 無論你使用多少次這個類都只會呼叫一次
- // initialize用於對某一個類進行一次性的初始化
- // initialize和load一樣, 如果存在繼承關係, 會先呼叫父類的initialize再呼叫子類的initialize
- + (void)initialize
- {
- NSLog(@"Person initialize");
- }
- @end
1、+load方法當類或分類新增到object-c runtime時被呼叫,子類的+load方法會在它所有父類的+load方法之後執行,而分類的+load方法會在它的主類的+load方法之後執行。但不同的類之間的+load方法的呼叫順序是不確定的,所以不要在此方法中用另一個類。
2、+load方法不像普通方法一樣,它不遵循那套繼承規則。如果某個類本身沒有實現+load方法,那麼不管其它各級超類是否實現此方法,系統都不會呼叫。+load方法呼叫順序是:SuperClass -->SubClass --> CategaryClass。
3、+initialize是在類或者它的子類接受第一條訊息前被呼叫,但是在它的超類接收到initialize之後。也就是說+initialize是以懶載入的方式被呼叫的,如果程式一直沒有給某個類或它的子類傳送訊息,那麼這個類的+initialize方法是不會被呼叫的。
4、+initialize方法和+load方法還有個區別,就是執行期系統完整度上來講,此時可以安全使用並呼叫任意類中的任意方法。而且,執行期系統也能確保+initialize方法一定會在“執行緒安全的環境”中執行,這就是說,只有執行+initialize的那個執行緒可以操作類或類例項,其他執行緒都要阻塞等著+initialize執行完。
5、+initialize方法和其他類一樣,如果某個類未實現它,而其超類實現了,那麼就會執行超類的實現程式碼。如果本身和超類都沒有實現,超類的分類實現了,就會去呼叫分類的initialize方法。如果本身沒有實現,超類和父類的分類實現了就會去調分類的initialize方法。不管是在超類中還是分類中實現initialize方法都會被調多次,呼叫順序是SuperClass -->SubClass。
三. UIWindow使用
1) 同一層級的 最後一個顯示出來,上一個被覆蓋
2)UIWindow在顯示的時候是不管KeyWindow是誰,都是Level優先的,即Level最高的始終顯示在最前面。
3)誰最後設定的 makeKeyAndVisible 誰就是keyWindow 其他的也會顯示出來 所有的window都可以監聽鍵盤 和點選的事件
UIView的功能
負責渲染區域的內容,並且響應該區域內發生的觸控事件
UIWindow
在iOS App中,UIWindow是最頂層的介面內容,我們使用UIWindow和UIView來呈現介面。UIWindow並不包含任何預設的內容,但是它被當作UIView的容器,用於放置應用中所有的UIView。
從繼承關係來看,UIWindow繼承自UIView,所以UIWindow除了具有UIView的所有功能之外,還增加了一些特有的屬性和方法,而我們最常用的方法,就是在App剛啟動時,呼叫UIWindow的rootViewController(必須指定根控制器) 和 makeKeyAndVisible方法
狀態列和鍵盤都是特殊的UIWindow。
UIWindow的主要作用有:
1.作為UIView的最頂層容器,包含應用顯示所有的UIView;
2.傳遞觸控訊息和鍵盤事件給UIView;
UIWindow的層級:
UIWindow的層級由一個UIWindowLevel型別屬性windowLevel,該屬性指示了UIWindow的層級,windowLevel有三種可取值。
並且層級是可以做加減的self.window.windowLevel = UIWindowLevelAlert+1;
Normal ,StatusBar,Alert.輸出他們三個層級的值,我們發現從左到右依次是0,1000,2000,也就是說Normal級別是最低的,StatusBar處於中級,Alert級別最高。而通常我們的程式的介面都是處於Normal這個級別的,系統頂部的狀態列應該是處於StatusBar級別,提醒使用者等操作位於Alert級別。根據window顯示級別優先原則,級別高的會顯示在最上層,級別低的在下面,我們程式正常顯示的view在最底層;
四.NS_ENUM & NS_OPTIONS
從列舉定義來看,NS_ENUM和NS_OPTIONS本質是一樣的,僅僅從字面上來區分其用途。NS_ENUM是通用情況,NS_OPTIONS一般用來定義具有位移操作或特點的情況(bitmask)。1.當前viewController隱藏本頁面的鍵盤
很容易。直接呼叫 [textfield resignFirstResponder]即可。
2.目前前遇到一個需求
ControllerA出來時候,隱藏當前top 任意view的鍵盤。那麼可以使用這個
- [[[UIApplication sharedApplication] keyWindow] endEditing:YES];
- @interface UIView (UITextField)
- - (BOOL)endEditing:(BOOL)force; // use to make the view or any subview that is the first responder resign (optionally force)
- @end
3.如果不方便獲取當前view
可以使用該方法
- [[UIApplication sharedApplication] sendAction:@selector(resignFirstResponser) to:nil from:nil forEvent:nil];
六.isKindOfClass和isMemberOfClass的區別
isKindOfClass和isMemberOfClass 都是NSObject的比較Class的方法
但兩個有很大區別:
isKindOfClass來確定一個物件是否是一個類的成員,或者是派生自該類的成員
isMemberOfClass只能確定一個物件是否是當前類的成員
例如:我們已經成NSObject派生了自己的類,isMemberOfClass不能檢測任何的類都是基於NSObject類這一事實,而isKindOfClass可以。
[[NSMutableData data] isKindOfClass:[NSData class]]; // YES [[NSMutableData data] isMemberOfClass:[NSData class]]; // NO
七.NSString屬性什麼時候用copy,什麼時候用strong
由於NSMutableString是NSString的子類,所以一個NSString指標可以指向NSMutableString物件,讓我們的strongString指標指向一個可變字串是OK的。
而上面的例子可以看出,當源字串是NSString時,由於字串是不可變的,所以,不管是strong還是copy屬性的物件,都是指向源物件,copy操作只是做了次淺拷貝。
當源字串是NSMutableString時,strong屬性只是增加了源字串的引用計數,而copy屬性則是對源字串做了次深拷貝,產生一個新的物件,且copy屬性物件指向這個新的物件。另外需要注意的是,這個copy屬性物件的型別始終是NSString,而不是NSMutableString,因此其是不可變的。
這裡還有一個性能問題,即在源字串是NSMutableString,strong是單純的增加物件的引用計數,而copy操作是執行了一次深拷貝,所以效能上會有所差異。而如果源字串是NSString時,則沒有這個問題。
所以,在宣告NSString屬性時,到底是選擇strong還是copy,可以根據實際情況來定。不過,一般我們將物件宣告為NSString時,都不希望它改變,所以大多數情況下,我們建議用copy,以免因可變字串的修改導致的一些非預期問題。
八.NSArray 各種遍歷方式
程式設計中經常需要遍歷collection元素,做法有標準的for迴圈,OC1.0的NSEnumerator和OC2.0的fast NSEnumerator。語言引入“塊”這一特性後,又多出來幾種新的遍歷方式。
for迴圈大家很熟悉,很簡單。但是遍歷字典和set的時候就稍麻煩,for迴圈有一個優勢,可以反向遍歷。在刪除陣列中一個元素的時候採用反向遍歷
OC 1.0的NSEnumerator是個抽象基類,定義了兩個方法,供具體的子類實現
- -(NSArray*) allObjects;
- -(id) nextObject;
舉例,遍歷數字
- NSArray *anArray=/*....*/;
- NSEnumerator *enumerator = [anArray objectEnumerator];
- id object;
- while((object = [enumerator nextObject]) != nil){
- //do something
- }
快速遍歷,就是for-in語句
直接上程式碼,這種辦法比上面兩種都高效,安全,簡單,強烈推薦!在刪除陣列元素的時候,同樣可以反序執行。
- NSArray *anArray=/*...*/;
- for (id object in anArray){
- //do something
- }
- //dictionary
- NSDictionary* dic = /*...*/;
- for(id key in dic){
- id value = dic[key];
- //do something
- }
- //set
- NSSet *aSet = /*...*/;
- for (id object in aSet){
- //do something
- }
- //反向遍歷
- NSArray *anArray=/*...*/;
- for (id object in [anArray reverseObjectEnumerator]){
- //do something
- }
基於塊的變數方式
當前OC中,最新引入的一種做法句ishi基於塊來遍歷。這種做法比前三種效率都高,但是程式碼量比for-in多。
- NSArray *anArray=/*...*/;
- [anArray enumerateObjectUsingBlock]:
- ^(id object,NSUInter idx,BOOLBOOL *stop){
- // do something
- if(shouldStop){
- *stop = yes;
- }
- }];
%@ 物件
%d, %i 整數
%u 無符整形
%f 浮點/雙字
%x, %X 二進位制整數
%o 八進位制整數
%zu size_t
%p 指標
%e 浮點/雙字 (科學計算)
%g 浮點/雙字
%s C 字串
%.*s Pascal字串
%c 字元
%C unichar
%lld 64位長整數(long long)
%llu 無符64位長整數
%Lf 64位雙字
%e 是實數,用科學計數法計的
十.iOS 中幾種常用的鎖總結
多執行緒程式設計中,應該儘量避免資源線上程之間共享,以減少執行緒間的相互作用。 但是總是有多個執行緒相互干擾的情況(如多個執行緒訪問一個資源)。線上程必須互動的情況下,就需要一些同步工具,來確保當它們互動的時候是安全的。
鎖是執行緒程式設計同步工具的基礎。iOS開發中常用的鎖有如下幾種:
- @synchronized
- NSLock 物件鎖
- NSRecursiveLock 遞迴鎖
- NSConditionLock 條件鎖
- pthread_mutex 互斥鎖(C語言)
- dispatch_semaphore 訊號量實現加鎖(GCD)
- OSSpinLock (暫不建議使用,原因參見這裡)
十一.記憶體洩露
ARC已經出來很久了,自動釋放記憶體的確很方便,但是並非絕對安全絕對不會產生記憶體洩露。導致iOS物件無法按預期釋放的一個無形殺手是——迴圈引用。迴圈引用可以簡單理解為A引用了B,而B又引用了A,雙方都同時保持對方的一個引用,導致任何時候引用計數都不為0,始終無法釋放。若當前物件是一個ViewController,則在dismiss或者pop之後其dealloc無法被呼叫,在頻繁的push或者present之後記憶體暴增,然後APP就duang地掛了。下面列舉我們變成中比較容易碰到的三種迴圈引用的情形。
(1)計時器NSTimer
一方面,NSTimer經常會被作為某個類的成員變數,而NSTimer初始化時要指定self為target,容易造成迴圈引用。 另一方面,若timer一直處於validate的狀態,則其引用計數將始終大於0。先看一段NSTimer使用的例子(ARC模式):
1 #import <Foundation/Foundation.h> 2 @interface Friend : NSObject 3 - (void)cleanTimer; 4 @end
1 #import "Friend.h" 2 @interface Friend () 3 { 4 NSTimer *_timer; 5 } 6 @end 7 8 @implementation Friend 9 - (id)init 10 { 11 if (self = [super init]) { 12 _timer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(handleTimer:) 13 userInfo:nil repeats:YES]; 14 } 15 return self; 16 } 17 18 - (void)handleTimer:(id)sender 19 { 20 NSLog(@"%@ say: Hi!", [self class]); 21 } 22 - (void)cleanTimer 23 { 24 [_timer invalidate]; 25 _timer = nil; 26 } 27 - (void)dealloc 28 { 29 [self cleanTimer]; 30 NSLog(@"[Friend class] is dealloced"); 31 }
在類外部初始化一個Friend物件,並延遲5秒後將friend釋放(外部執行在非arc環境下)
1 Friend *f = [[Friend alloc] init]; 2 dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 5*NSEC_PER_SEC), dispatch_get_main_queue(), ^{ 4 [f release]; 5 });
我們所期待的結果是,初始化5秒後,f物件被release,f的dealloc方法被呼叫,在dealloc裡面timer失效,物件被析構。但結果卻是如此: