oc 协议 回调 静态成员_OC底层原理探究:Category、关联对象和block本质
1.分類Category的使用
// 給MJPerson類添加分類
@interface MJPerson : NSObject
- (void)run;
@end@implementation MJPerson
- (void)abc{}
- (void)run{NSLog(@"MJPerson - run");}
+ (void)run2{}
@end// MJPerson的分類,給MJPerson添加test方法
@interface MJPerson (Test)
- (void)test;
@end @implementation MJPerson (Test)
- (void)run{ NSLog(@"MJPerson (Test) - run");}
- (void)test{ NSLog(@"test");} // 會存放在類對象中
+ (void)test2 {} // 會存放在元類對象中
@end// 調用拓展出來的方法
MJPerson *person = [[MJPerson alloc] init];
[person run];
[person test];2.Category的本質
- 編譯時會把Category數據(實例|類方法、屬性、協議)放到一個category_t的結構體內。
- 在程序運行的時候,通過Runtime將Category的數據合并到類信息(類對象、元類對象)
- 細節:運行時候,通過Runtime加載某個類的所有Category數據,把所有Category數據,合并到一個數組中(后面參與編譯的Category數據,會放在數組的前面)。然后將合并后的分類數據,插入到類原來類數據的前面(所以當category和類的方法重名的時候,會調用Category方法)。
Category與extension本質的區別
- Extension在編譯的時候,它的數據包含在類信息中。
- Category是在運行的時候,才會將數據合并到類信息中。
+load方法(+load方法是根據方法地址直接調用,并不是經過objc_msgSend函數調用)
+load方法會在runtime加載類、分類時調用。每個類、分類的+load,在程序運行過程中只調用一次。其調用順序如下:
- <1>.先調用類的+load,按照編譯先后順序調用(先編譯,先調用,調用子類的+load之前會先調用父類的+load)
- <2>.再調用分類的+load,按照編譯先后順序調用(先編譯,先調用)
注意:主動調用load方法,會和消息發送機制一樣,會調到分類里面的load方法。
+Initialize方法
- +initialize方法會在類第一次接收到消息時調用,調用順序:先調用父類的+initialize,再調用子類的+initialize (先初始化父類,再初始化子類,每個類只會初始化1次)
- +initialize和+load的很大區別是,+initialize是通過objc_msgSend進行調用的,是在類第一次接收到消息的時候調用。而load是根據函數地址直接調用,實在runtime加載類、分類的時候調用,且只調用一次。
- 如果子類沒有實現+initialize,會調用父類的+initialize(所以父類的+initialize可能會被調用多次;調用子類,必先調用父類+initialize(第一次),然后調用這個子類的+initialize,但是這個子類沒有實現,那么調用父類+initialize(第二次)方法。通過isa、supperclass指針找方法來調用)
- 如果分類實現了+initialize,就覆蓋類本身的+initialize調用。
2.分類添加成員變量(關聯對象)
在分類中添加一個屬性,只會生成聲明,不會生成成員變量和set/get的實現。
@interface MJPerson (Test)
// 各類中添加屬性
@property (assign, nonatomic) int weight;
// 只會生成方法的聲明
//- (void)setWeight:(int)weight;
//- (int)weight;
@end// 實現get set方法 來使得分類中的方法可以賦值和訪問
#define MJKey [NSString stringWithFormat:@"%p", self]
@implementation MJPerson (Test)NSMutableDictionary *names_;
NSMutableDictionary *weights_;
+ (void)load
{weights_ = [NSMutableDictionary dictionary];names_ = [NSMutableDictionary dictionary];
}- (void)setName:(NSString *)name { names_[MJKey] = name; }
- (NSString *)name { return names_[MJKey]; }- (void)setWeight:(int)weight { weights_[MJKey] = @(weight); }- (int)weight{ return [weights_[MJKey] intValue]; }關聯對象
#import "MJPerson.h"
@interface MJPerson (Test)
@property (copy, nonatomic) NSString *name;
@property (assign, nonatomic) int weight;
@end#import "MJPerson+Test.h"
#import <objc/runtime.h>@implementation MJPerson (Test)
- (void)setName:(NSString *)name
{objc_setAssociatedObject(self, @selector(name), name, OBJC_ASSOCIATION_COPY_NONATOMIC);
}- (NSString *)name
{// 隱式參數// _cmd == @selector(name) 只有get方法才可以這么寫return objc_getAssociatedObject(self, _cmd);
}- (void)setWeight:(int)weight
{objc_setAssociatedObject(self, @selector(weight), @(weight), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}- (int)weight
{// _cmd == @selector(weight)return [objc_getAssociatedObject(self, _cmd) intValue];
}// 這樣就可以像調用類屬性一樣來調用分類里面的屬性
person.name = "Lewis"關聯對象本質
實現關聯對象技術的核心如下:
- AssociationsManager
- AssociationsHashMap
- ObjectAssociationMap
- ObjcAssociation
3.block
block的定義、簡單使用
// 格式
返回值 (^block)(參數) = ^{ block的內容 }// 例子
void (^block)(void) = ^{ NSLog(@"this is a block") }
// 調用
block()block本質
- block本質上也是一個OC對象(最終繼承于NSBlock->NSObject),它內部也有個isa指針,而且有函數調用地址,和捕獲的變量。
- block是封裝了函數調用以及函數調用環境的OC對象。
- block的底層結構如下圖所示。
block捕獲變量
為了保證block內部能夠正常訪問外部的變量,block有個變量捕獲機制。
<1>. 捕獲局部auto類型變量(自動變量:離開作用域就會自動銷毀)
<2>. 也可以捕獲局部static類型變量
// 默認定義的局部變量,默認就是auto
// auto int age = 10
int age = 10 // 傳遞是age的值
static int height = 10 // 傳遞的是height的地址值
// 在創建這個block對象的時候,age和height的值已經存儲到了block的內存中
void (^block)(void) = ^{// age和height的值已經被捕獲進來了NSLog(@"age is %d, height is %d", age, height);
};
age = 20;
height = 20;
// 通過*height取出height的值,只是通過指針取出指向的外部的值
block(); // 輸出:age is 10, height is 20注:1>.self(局部變量)也會被捕獲。2>.如果block里面使用的成員變量,那么其實捕獲的是self對象。
block的類型
block有3種類型,可以通過調用class方法或者isa指針查看具體類型,最終都是繼承自NSBlock類型。
- __NSGlobalBlock__ ( _NSConcreteGlobalBlock )
- __NSStackBlock__ ( _NSConcreteStackBlock )
- __NSMallocBlock__ ( _NSConcreteMallocBlock )
存放區域如下:
- .text區域:主要放程序的代碼
- .data區域:數據段一般放一些全局變量
- 堆:一般放那些alloc出來的數據,動態分配內存的。特點是需要開發者寫代碼申請的。需要開發者管理內存。
- 棧:放一些局部變量。特點是系統自動分配內存。
int a = 10;// 堆:動態分配內存,需要程序員申請申請,也需要程序員自己管理內存
void (^block1)(void) = ^{NSLog(@"Hello");
};
int age = 10;
void (^block2)(void) = ^{NSLog(@"Hello - %d", age);
};NSLog(@"%@ %@ %@", [block1 class], [block2 class], [^{NSLog(@"%d", age);
} class]);// 輸出:__NSGlobalBlock__ __NSMallocBlock__ __NSStackBlock__void (^block)(void);
void test2()
{// NSStackBlockint age = 10;block = [^{NSLog(@"block---------%d", age);} copy];// copy操作之后 block就會在堆上,就會變成NSMallocBlock// 如果沒有copy操作,那么這個block捕獲的是auto變量,所以是一個NSStackBlock,所以在作用域之外調用,打印出的age值不會是10,因為這個age已被釋放[block release];
}void test()
{// Global:沒有訪問auto變量void (^block1)(void) = ^{NSLog(@"block1---------");};// Stack:訪問了auto變量int age = 10;void (^block2)(void) = ^{NSLog(@"block2---------%d", age);};NSLog(@"%p", [block2 copy]);// NSLog(@"%@ %@", [block1 class], [block2 class]);
}int age = 10;int main(int argc, const char * argv[]) {@autoreleasepool {int a = 10;NSLog(@"數據段:age %p", &age);NSLog(@"棧:a %p", &a);NSLog(@"堆:obj %p", [[NSObject alloc] init]);NSLog(@"數據段:class %p", [MJPerson class]);}return 0;
}block的自動copy操作
當在ARC機制下有下列情況的時候,會自動對block進行copy操作
- block作為函數返回值時
- 將block賦值給__strong指針時
- block作為Cocoa API中方法名含有usingBlock的方法參數時
- block作為GCD API的方法參數時
ARC下block屬性的建議寫法
- @property (strong, nonatomic) void (^block)(void);
- @property (copy, nonatomic) void (^block)(void);
對象類型的auto變量、__block變量、__block修飾的對象
- 當block內部訪問了對象類型的auto變量時:如果block是在棧上,將不會對auto變量、__block變量和__block修飾的對象產生強引用。
- 如果block被拷貝到堆上,會調用block內部的copy函數,copy函數內部會調用_Block_object_assign函數,_Block_object_assign函數會根據auto變量的修飾符(__strong、__weak、__unsafe_unretained)做出相應的操作,形成強引用(retain)或者弱引用。(而__block修飾的auto變量(如int類型),則形成強引用,無弱引用)
- 如果block從堆上移除,會調用block內部的dispose函數,dispose函數內部會調用_Block_object_dispose函數,_Block_object_dispose函數會自動釋放引用的auto變量(release)
而__block修飾的對象會根據所指向對象的修飾符(__strong、__weak、__unsafe_unretained)做出相應的操作,形成強引用(retain)或者弱引用(注意:這里僅限于ARC時會retain,MRC時不會retain)
__block修飾符
- __block可以用于解決block內部無法修改auto變量值的問題
- __block不能修飾全局變量、靜態變量(static)
- 編譯器會將__block變量包裝成一個對象
循環引用的解決
總結
以上是生活随笔為你收集整理的oc 协议 回调 静态成员_OC底层原理探究:Category、关联对象和block本质的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: linux 启动nacos报错_naco
- 下一篇: 传统的6d位姿估计fangfa1_基于改