iOS Runtime 小結

程序運行以後的內存管理。 OC具備動態特性,即運行時才檢查對象類型和方法實現。html

1 源碼下載

opensource.apple.com數組

2 SEL -> IMP,方法查找

hit / miss / add。緩存

緩存方法列表 -> 對象方法列表 -> 父類的方法列表 -> 消息轉發機制 -> 拋異常bash

2.1 SEL只認方法名,OC不支持函數重載

3 Class對象

3.1 Class對象和metaClass對象

/*
傳入 instance 對象,返回 class 對象
傳入 class 對象,返回 meta-class 對象
傳入 meta-class 對象,返回 NSObject 基類的 meta-class 對象
*/
Class object_getClass(id obj)
//只能返回類對象,不管調用幾回
- (Class)class、 + (Class)class
//未測試,拿不到meta類的類名,NSStringFromClass又要調用class方法,又回到普通
Class objc_getClass(const char *aClassName)
複製代碼

3.2 類方法中的self

  1. +方法中的self,表示Class對象。
  2. Class對象可調用的methodlist,保存在metaClass對象中。 類方法中,用self調用函數,只在Class對象可調用的methodlist中查找。

3.3 super

內部實現,見Runtime源碼數據結構

3.3.1 self調用[super method],父類method方法中的[self xxx]

  1. [self method]和[super method]的調用對象都是self,super的意思是從父類的方法列表中找method(找不到,就去父類的父類中找)。
  2. 案例:被調用的[super method]方法中,調用了[self amethod] 這裏self的調用先從obj本身的方法列表中找amethod。

3.3.2 [super class]爲何返回值和[self class]同樣

由於都是調用的NSObect的-(Class)class方法,實現以下:app

return objc_getClass(self);
複製代碼

3.4 UIView的動畫中使用self會不會有循環引用?

不會,由於,不管self是誰,都不會去引用UIView。 下面的代碼中,dom

  1. UIView.animate類方法中的self,是drawFollowing:實例方法中的隱藏參數。
  2. UIView類方法的block,正常捕獲self。
- (void)drawFollowing:(CGPoint)previousPoint
{
    CGRect frame = CGRectZero;
   UIImageView *aview = [self createImageView:frame];
    [UIView animateWithDuration:2
                          delay:0
         usingSpringWithDamping:1
          initialSpringVelocity:0
                        options:UIViewAnimationOptionCurveEaseInOut
                     animations:^{
                         //這裏是2秒後的狀態,因此rect.size.width=0沒問題
                             aimageView.alpha = 0.7;
                             CGRect rect = imageView.frame;
                             rect.size.width = 0;
                             rect.size.height = 0;
//self
                             imageView.transform=CGAffineTransformMakeRotation(M_PI/[self getRandomNumber:1 to:6]);
                             imageView.frame = rect;
                         }
                     }
     ];
}
複製代碼

4. Swizzling, class_addMethod

  1. +load方法中交換
  2. dispatch_once
  3. 類方法swizz,會用到object_getClass

4.1 可能出現的死循環

  1. 若是繼承關係中的兩個類 都 進行了同名方法的swizzling,會出現死循環。
  2. 解決方法:只在某個結點進行swizzling(如UIViewController),或只在葉子結點交換(很差)。
  3. 死循環緣由:就是3.3節super的問題致使

4.2 有就交換,沒有就添加

4.3 類簇和Swizzling

對類簇的理解,只停留在諸如NSArray這種集合類上。ide

  1. 類簇能夠看出是抽象工廠模式,根據輸入、構造方法,返回不一樣的子類。
  2. 類簇主要是要注意Swizzling 對類簇進行swizz,須要知道內部結構

5. property

5.1 property & dynamic & synthesize

@dynamic,不生成實例變量、getter/setter。 @synthesize,自動合成getter/setter,實例變量。iOS6以後,編譯器有了,屬性自動合成,無需此句了。函數

5.2 property & 實例變量

\ 父類屬性 dynamic category聲明的屬性 @implemetaion A{NSString * aInstance}
Ivar* 不含 不含 不含 包含
objc_property_t * 不含 包含 包含 不含

5.3 property & category & 關聯對象 & KVO

5.3.1 加載時機

  1. category添加的屬性和方法,編譯階段已經鏈接好了,運行時,加載Class對象的時候,所有加入屬性列表方法列表中,不影響方法查找流程。oop

  2. category添加的同名方法會覆蓋原類中的方法,不管是否import category。

  3. 多個category,+load方法不會分類覆蓋,而是按照project.pbxproj文件中PBXSourcesBuildPhase的順序依次執行。

  4. 多個category,+initialize方法,只會有一個生效,project.pbxproj文件中PBXSourcesBuildPhase中最後的category。

  5. 多個category,實例方法,會覆蓋,只會有一個生效,是誰由編譯器決定。

5.3.2 屬性,只添加了getter/setter,沒有添加成員變量

@dynamic pages;
- (void)setPages:(NSArray *)pages {
    objc_setAssociatedObject(self, _cmd, pages, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
- (NSArray *)pages {
    return objc_getAssociatedObject(self, @selector(pages));
}
複製代碼

5.3.3 添加基礎類型的屬性, 裝箱拆箱

- (void)setBb:(NSInteger)bb {
    objc_setAssociatedObject(self, @selector(bb), @(bb), OBJC_ASSOCIATION_RETAIN);
}
- (long)bb {
    NSLog(@"Current method: %@ %@",[self class],NSStringFromSelector(_cmd));
    return [objc_getAssociatedObject(self, _cmd) integerValue];
}
複製代碼

5.3.4 使關聯對象添加的屬性能夠被KVO

- (void)setPageView:(MyPageView *) pageView{
    [self willChangeValueForKey:@"pageView"];
    objc_setAssociatedObject(self, @selector(pageView),
                             pageView,
                             OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    [self didChangeValueForKey:@"pageView"];
}
複製代碼

6. 反射

#import "objc/runtime.h"

複製代碼
unsigned int count = 0;

Ivar *ivars = class_copyIvarList([self class], &count);

    for (int i=0; i < count; i++) {

        Ivar const ivar = ivars[i];

        //獲取屬性名

        NSString *key = [NSString stringWithUTF8String:ivar_getName(ivar)];

        //獲取屬性值,小心crash

        id value = [self valueForKey:key];

    }

if(ivars){

    free(ivars);
}

複製代碼

對方法的反射,是針對.m的。 注意,方法反射,只能拿到-方法(靜態方法拿不到),拿不到父類的方法,能夠拿到分類方法。

7 autoreleasepool

每一個線程都會自動建立本身最外層的autorealsepool。 autoreleasepool能夠嵌套。

沒有手動建立autoreleasepool的狀況下,等到線程執行 下一次事件循環(如for循環總體結束後、線程執行完等)時,纔去清空再也不使用的對象。 對於頻繁的文件操做、for循環中的圖片等操做,使用單獨的autoreleasepool可能會下降memory peak。(eoc-34)

注意:這裏提到了事件循環,不必定是RunLoop,只是一個生命週期的概念?由於子線程不會自動建立RunLoop。

8 消息轉發

消息轉發

緩存方法列表 -> 對象的方法列表 -> 父類的方法列表 -> 動態方法解析 -> 消息轉發。 應用場景,除了捕獲Crash,暫時沒找到,並且捕獲Crash有更好的方式。

消息轉發有三個階段

  1. 本身處理
  2. 轉發給另外一個類的某方法
  3. 轉發給能夠不止一個類的某些方法
//本身處理
+ (BOOL)resolveInstanceMethod:(SEL)sel {} (實例方法)
+ (BOOL)resolveClassMethod:(SEL)sel {} (類方法)

//轉發給其餘類的其餘方法
- (id)forwardingTargetForSelector:(SEL)aSelector {}

//
//第一個要求返回一個方法簽名,第二個方法轉發具體的實現。兩者相互依賴,只有返回了正確的方法簽名,纔會執行第二個方法。
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {}
- (void)forwardInvocation:(NSInvocation *)anInvocation {}

// ViewController.m 中
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
    //下面幾行是告訴編譯器,不要對特定類型(未聲明方法)報警
     #pragma clang diagnostic push
     #pragma clang diagnostic ignored "-Wundeclared-selector"
     if (aSelector == @selector(myTestPrint:)) {
         #pragma clang diagnostic pop
         return [NSMethodSignature signatureWithObjCTypes:"v@:@"];
    }
     return [super methodSignatureForSelector:aSelector];
}

- (void)forwardInvocation:(NSInvocation *)anInvocation {
     Person *person = [Person new];
     Animal *animal = [Animal new];
     if ([person respondsToSelector:anInvocation.selector]) {
          [anInvocation invokeWithTarget:person];
     }
     if ([animal respondsToSelector:anInvocation.selector]) {
          [anInvocation invokeWithTarget:animal];
     }
}
複製代碼

8.1 ObjcType

//在NSMethodSignature.h中
+ (nullable NSMethodSignature *)signatureWithObjCTypes:(const char *)types; //1

//在NSObject.h中
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector OBJC_SWIFT_UNAVAILABLE(""); //2
+ (NSMethodSignature *)instanceMethodSignatureForSelector:(SEL)aSelector OBJC_SWIFT_UNAVAILABLE(""); //3
複製代碼

NSMethodSignature是方法簽名,是用來記錄返回值類型和參數類型的一個對象。 2和3兩個方法是根據SEL來構造NSMethodSignature 1是根據ObjCTypes來構造NSMethodSignature ObjCTypes是一個是字符串數組,該數組包含了方法的類型編碼

- (void)saySth:(Something *)sth;
複製代碼

其ObjcTypes就是 "v@:@",有兩種方式獲取該字符串

  • 直接查表。在Type Encodings裏面列出了對應關係。
  • 使用 @encode()計算。( NSLog(@"%s",@encode(BOOL))的結果爲B )

舉例,消息轉發中

[sbody saySth:sth];
//-> void objc_msgSend(sbody, @selector(saySth:), sth);
複製代碼

v@:@ 各符號含義以下,

  • v 指代void,(連起來,是 返回值void)
  • @ 指代對象,(連起來,是 消息的接受者,即sbody)
  • : 指代SEL
  • @ 指代對象,(連起來,是 消息的參數,即sth)

9 引用計數、weak、isa、SideTable

  1. 引用計數 isa管理對象的引用計數

    1. 增長時,若是引用數超過2^8-1,就會減半,把一半的值保存在SideTable中。
    2. 減小時,若是引用數<0,若是SideTable中有,就從SideTable中取一半出來,若是發現引用數爲0,就會作dealloc處理,釋放內存、weak表
  2. SideTables,全局變量,容量爲64的數組,數組元素是SideTable 一個obj對應一個SideTable。 一個SideTable對應多個Obj。

這裏有一個二次hash,緣由:SideTables最大是64。能夠理解爲大小表,下降索引量,另外聯繫到hash衝突是用index+1的方式,而不是拉鍊法,也多是爲了下降hash衝突的可能性。 第一次,是obj->SideTables 第二次,是obj->計數表、弱引用表

  1. SideTable,聚合了計數表和弱引用表 實際上,若是不看SideTables,而後把計數表、弱引用表移到SideTables中。結構就很是清晰了。

數據結構, 見Runtime源碼

10 內存分佈

  1. 內存中的五大區域.

    1. 棧: 存儲局部變量.
    2. BSS段: 存儲未初始化的全局變量、靜態變量.
    3. 數據段(常量區): 存儲已經初始化的全局變量、靜態變量、常量數據.
    4. 堆: 其餘對象.
    5. 代碼段:存儲程序的代碼.
  2. 類加載.

    1. 當建立對象的時候,訪問這個類
    2. 若是隻是聲明類指針的時候,也會訪問這個類,以肯定這個類型是否存在。
    3. 當類第一次被import的時候,會將類存儲到代碼段之中。
      1. 將類的代碼以字符串的形式存儲在代碼段中。
      2. 只有類第1次被訪問的時候,纔會有類加載。
      3. 一旦類被加載到代碼區,直到程序結束的時候纔會被回收。
  3. 內存對齊

    • 64位地址空間中,oc對象的指針爲8字節。
    • 對於指針對內存空間的可能浪費,蘋果採用了Tagged Pointer技術。 即,若是指針所指對象的內容能夠用8字節(實際上是小於8字節,由於還有標誌位)表示,該指針自己就會存儲內容。該方案應用在了NSData,NSString,NSNumber中。
    • 也所以,isa並不必定是一個真正的對象,可是object_getClass能夠獲取到對象的類型。
相關文章
相關標籤/搜索