iOS開發幾年了,你清楚OC中的這些東西麼

發表於2016-08-21

前言

幾年前筆者是使用Objective-C進行iOS開發, 不過在兩年前Apple釋出swift的時候,就開始了swift的學習, 在swift1.2釋出後就正式並且一直都使用了swift進行iOS的開發了, 之後就是對swift持續不斷的學習, 近來swift3.0的釋出, 更多的人會選擇swift來進行iOS的開發看上去更是成為了一種趨勢, 不過一個合格的iOS開發者對oc以及c語言的掌握是必不可少的技能, 本篇中主要是寫一些大家平時都可能用到但是不一定知道的oc的東西

  1. oc中的物件的建立: 首先會通過 +(id)alloc 動態的分配所有的變數以及父類定義的變數所需要的足夠記憶體, 同時會清除所有的分配的記憶體空間, 全部置為0
  2. 同時接著需要呼叫class的 -(id)init 方法, 這個方法給每個變數設定初始值
  3. 返回的型別為id, id是一個可以指向任意型別的指標(不用 * 號), 這個在一定程度上可以完成多型的效果
  4. 對oc中的class檔案的理解: class, extension, category
    ZJPerson.h檔案
    111271831-87a54cd20ae2140f
    ZJPerson.m檔案

    121271831-7f5232809ed38b81
    ZJPerson.m檔案

    131271831-6a512ddcd8fd2374
  5. [[XXObject alloc] init] 初始化方法不需要引數的時候, 和 [XXObject new] 方法相同
  6. 通過字面量來初始化物件, 例如
  7. oc(c)中多行巨集的定義(這個在swift…中更方便直接一個全域性的函式就搞定了): 在除了最後一行的每一行結尾加一條反斜槓 \
    141271831-12d1f3ca5957ad60
    151271831-7284d8c06f45798b
  8. 比較是否相同: 使用 if(a==b) {}, 如果a,b是物件型別, 那麼比較的是指標是否相同, 而不是比較值是否相同, 如果a, b是基本型別(int, double…), 那麼比較的是值是否相同; 使用if ([a isEqual: b]) { }, 則比較的是a,b的值是否相同
  9. 初始化基本型別的時候儘量設定初始值, 因為編譯器分配的初始值並不確定, 但是物件型別會預設初始化為nil
  10. 條件判斷: 當物件不為nil(有記憶體地址)的時候, 或者基本型別非0, 或者bool型別為true, 這個時候條件都為真, 其他情況條件為假
  11. oc中屬性的getter和setter
    @property (nonatomic) NSString *name;
    • 例如當有這樣一個name屬性的時候, 預設是readWrite的, 編譯器會自動生成一個set (setName:)和get(-(NSString *)name)方法, 這個時候可以通過set或者get方法訪問到name, 如果申明為(readonly), 那麼將只會生成get方法
    • 同時你可以重寫name的get(懶載入…)和setter(攔截set方法)…
      對應name屬性, 編譯器會生成(synthesize)一個 _name 允許我們直接通過指標訪問變數, 而不會呼叫get方法, 所以通過_xx訪問的變數不會呼叫懶載入(get方法), 所以在寫懶載入方法的時候, 不能使用self.xx(造成死迴圈), 而要使用_xx
    • 同時這個synthesize的名字我們是可以自己修改的, 使用如下的語法
      @synthesize name = customName;
    • 那麼這個時候就不能通過 __name訪問到name了, 因為我們已經指定了通過customName才能訪問到了
      NSString *getName = customName;
    • 當然如果, 你是這樣寫的 @synthesize name;, 並沒有指定名字, 這個時候訪問的時候就直接使用變數名而不需要加下劃線( _ )了 name = @"set name"; ?這個時候就比較爽了, 和swift,java這些一樣, 不需要self,this了;
  12. oc的屬性預設是atomic(原子的), 也就是說是執行緒安全的, 這個時候是不允許重寫set和get方法的, 因為內部的setter和getter會做出處理, 保證執行緒安全, 但是我們經常使用的是noatomic, 因為訪問的速度比較快, 並且可以自己重寫getter和setter
  13. oc中的物件是動態管理(記憶體)的, 是分配在heap(堆)上所以需要一個指標來指向它(才能訪問), 所以物件型別需要用 星號 NSString * str;
  14. oc中的物件管理在ARC下是用引用計數來管理的, 當有一個強引用物件A指向這個物件B的時候, B引用計數加一, 當這個物件A銷燬的時候,B的引用計數減一, 直到B的引用計數為0的時候就被自動銷燬, 當然這個時候如果A強引用B, B同時強引用A就造成了迴圈引用, 兩者都不會被銷燬, 就造成了記憶體洩漏, 解決方法是將一方標記為 weak 或者unsafe_unretained(垂懸指標, 和swift中的[unowned self]類似, 所以運用不當會造成野指標的問題)
  15. oc中的屬性預設是strong的, 所以需要顯示的指定為其他的(weak, unsafe_unretained…)
  16. NSObject * __weak someObject = [[NSObject alloc] init];, 這個someObject沒有物件強引用他, 所以這行程式碼之後會立馬被置為nil, NSObject * __weak someObject = self.someObject, 這個someObject在這行程式碼之後不會立刻被置為nil, 而是會在所在的程式碼塊結束後被置為nil
  17. 對於屬性的賦值(深淺拷貝)
  18. 分類(category)定義的函式和屬性在執行時中和原生的class中定義的東西並沒有區別At runtime, there’s no difference between a method added by a category and one that is implemented by the original class
  19. 不過分類中定義的屬性, 編譯器並不會自動生成getter和setter, 以及_XX變數來訪問,需要自己提供getter和setter, 並且需要使用執行時才能繫結這個屬性到這個類中, 實現原生類中定義的屬性的效果
  20. 同時分類也可以用來將一個複雜的類中的程式碼分塊(swift的extension可以有相似的作用), 使得程式碼組織更好, 例如可以將tableView的delegate, 和Datasource在分類中實現,
  21. 但是在使用category來擴充套件Cocoa的原生類的時候, 要注意函式的命名如果是和原生已有的函式名相同,那麼將會發生不可預料的結果(不能確定哪一個方法在執行時會被呼叫), 因此建議在自己的函式名前面加上字首, 就像重寫 +load() 來實現各種黑魔法的時候也是可能會發生不可預料的結果, 因為同一個專案中可能有多個地方重寫了這個類的 +load方法
  22. 初始化NSArray的時候, 如果通過NSArray *arr1 = @[object1, object2];, 不需要以nil結尾, 如果通過構造方法初始化, 則需要傳入nil結尾, 同時, 如果中間的物件有nil, 那麼將在中間nil就結束了, NSArray *arr2 = [NSArray arrayWithObjects:object1, object2, object3, nil, object4, object5, nil] 這個arr2只可能會儲存第一個nil前的物件
  23. 如果在陣列中一定要儲存nil, 那麼只能用NSNull來代替
  24. 如果NSArray中儲存的是NSArray, NSDictionary, NSString, NSData, NSDate , NSNumber這些型別的物件, 那就可以直接寫入disk並且讀取disk的資料做持久化資料操作[array writeToURL:fileURL atomically:YES], 但是如果是有其他的型別, 就需要使用歸檔來實現了
  25. 在for-in快速列舉中, 不能夠修改(增刪)被列舉的物件(陣列, 字典,集合)
  26. 在寫程式碼的時候, 進行條件判斷的時候, 經常會出現這樣的程式碼 if (a = 1){...}, 這樣寫編譯器是會報錯的, 需要寫成 if(a == 1) {...}, 當然你非要使用一個等號也是可以的, 需要額外加一個括號, if ((a = 1)) {...}
  27. 實際上絕大多數情況下都是我們寫條件判斷的時候都是使用==, 而非 =, 也就只有當我們寫構造方法的時候才可能會寫到 =,像這樣 if (self = [super init]) {...}, 其實這並不是使用=來判斷條件相等是正確的, 只是在這裡, 通過[super init]方法會返回一個id物件, 通過, self = [super init], 把這個物件賦值給self, 這個時候的 if就是用來判斷, 被賦值後的self是否為nil, 而不是 self是否等於[super init]返回的物件.
  28. 在oc中block是object型別的, 所以是可以儲存在NSArray…中, 同時在呼叫block的時候, 如果block為nil(未賦值), 那麼程式將crash.
  29. oc中block可以捕獲變數, 什麼意思呢 — 就是block會預設捕獲到變數的值, 在之後不受到原來變數的改變的影響, 例如
  30. 第二種block捕獲變數的方式, 是捕獲變數的指標, 被捕獲的變數值改變, 則block中的變數值也改變了,不過需要對變數進行__block標記, 例如上面的程式碼, 只改變一點, 結果就變了
  31. 伴隨著block能夠捕獲變數的能力的一個問題就是, 迴圈引用, 在ARC中, 只要不是用到純C語言的庫,管理記憶體的工作都不需要我們完成, 但是迴圈引用卻是我們需要解決的, 最常見的就是當block捕獲的變數是一個物件的屬性(方法)的時候, 也就是會捕獲到self, 那麼這個時候就可能會造成迴圈引用(block屬性應該被標記為copy), 解決方法也很簡單, 使用一個對self弱引用的指標即可, 這個寫法就很多了, 筆者習慣的寫法是: __weak typeof(self) weakSelf = self;, 那麼在block中使用weakSelf替代self呼叫相關的屬性或者方法, 就不會造成迴圈引用
  32. 使用weakSelf能夠解決block捕獲self造成的迴圈引用的記憶體洩漏問題, 但是帶來的另一個問題就是, 特別是在多執行緒中,可能在block中程式碼正在執行的時候, self被銷燬了, 因為使用weakSelf捕獲到的是self的弱引用, 那麼後續的程式碼就不能夠繼續執行了, 這個時候為了保證在這個block中self即使被銷燬block裡面的程式碼也能正常執行, 我們需要的另一個操作就是, 將weakSelf強引用一次, 讓他的引用計數加1, 就能處理這個問題, 就是Apple在wwdc中提到的weak-strong-dance, 筆者習慣的書寫方式是: __strong typeof(self) strongSelf = weakSelf;,,, 當然這個必須要明白的是, 這個block裡面的strongSelf能夠保證裡面程式碼執行完畢的前提是程式能夠執行到block, 如果在執行block之前self已經被銷燬了, 那麼這個block肯定是不會被呼叫的(block的引用計數已經為0).

按照慣例, 我的文章裡面都會有demo的, 但是這篇文章沒有demo, 因為這只是寫一點點語法的東西

相關文章