Object-C语法
面向對象
我們平時編寫的Objective-C代碼,底層實現其實都是C\C++代碼
所以Objective-C的面向對象都是基于C\C++的數據結構實現的
Objective-C的對象、類主要是基于C\C++的什么數據結構實現的?
結構體
將Objective-C代碼轉換為C\C++代碼
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc OC源文件 -o 輸出的CPP文件 如果需要鏈接其他框架,使用-framework參數。比如-framework UIKit一個OC對象在內存中是如何布局的
// 指針 isa typedef struct objc_class *Class;// NSObject Implementation struct NSObject_IMPL {Class isa; // 8個字節 };指針在64位系統中占8個字節、 32位系統中 占4個字節
OC對象的本質
下面類占幾個字節內存
@interface Student : NSObject {@publicint _no;int _age; } @end@implementation Student@endint main(int argc, const char * argv[]) {@autoreleasepool {Student *stu = [[Student alloc] init];stu->_no = 4;stu->_age = 5;NSLog(@"%zd", class_getInstanceSize([Student class]));NSLog(@"%zd", malloc_size((__bridge const void *)stu));struct Student_IMPL *stuImpl = (__bridge struct Student_IMPL *)stu;NSLog(@"no is %d, age is %d", stuImpl->_no, stuImpl->_age);}return 0; }如果繼承的話: 子類的結構中會有一個父類的結構
父類 struct OC_IMPL {Class isa; }; 子類 struct OC_11_IMPL:OC_IMPL {OC_IMPL oc_impl;int _no;int _age; };Tips:
內存對齊: 結構體的最終大小必須是最大成員大小的倍數
屬性和方法
屬性會自動生成對應的成員變量和set get 方法
方法是存儲在類對象中的(只有一分就夠了,不需要創建那么多),所以不會影響對象的內存占用
FACE
一個NSObject對象占用多少內存
- 系統分配了16個字節給NSObject對象(通過malloc_size函數獲得)(如果小于16則等于16)
- 但NSObject對象內部只使用了8個字節的空間(64bit環境下,可以通過class_getInstanceSize函數獲得)
結構體內存對齊;用來計算結構體的大小
操作系統在分配內存的時候也有一個分配對齊(最小分配大小: 有很多分好的塊 16 32 64等 都是16的倍數,靠近哪一個分給你哪一個)
三個獲取大小的方法
sizeof : 獲取這個類型的大小,他不是方法
class_getInstanceSize() 獲取類實際占用的空間大小
malloc_size() 獲取某個實例實際分配的內存大小
LLDB
打印對象或者值
讀取內存
memory read/數量格式字節數 內存地址
簡稱: x
x/數量格式字節數 內存地址
格式
x是16進制,f是浮點,d是10進制
字節大小
b:byte 1字節,h:half word 2字節
w:word 4字節,g:giant word 8字節
修改內存中的值
memory write 內存地址 數值
memory write 0x0000010 10
OC對象的分類
Objective-C中的對象,簡稱OC對象,主要可以分為3種
instance對象(實例對象)
instance對象就是通過類alloc出來的對象,每次調用alloc都會產生新的instance對象
instance對象在內存中存儲的信息包括
isa指針
其他成員變量
class對象(類對象)
每個類在內存中有且只有一個class對象
獲取類對象的三種方法
類對象在內存中存儲的信息包括
isa指針
superclass指針
類的屬性信息(@property)、類的對象方法信息(instance method)
類的協議信息(protocol)、類的成員變量信息(ivar)
meta-class對象(元類對象)
每個類在內存中有且只有一個meta-class對象
獲取
meta-class對象和class對象的內存結構是一樣的,但是用途不一樣,在內存中存儲的信息主要包括
三個函數
1.Class objc_getClass(const char *aClassName)1> 傳入字符串類名2> 返回對應的類對象2.Class object_getClass(id obj)1> 傳入的obj可能是instance對象、class對象、meta-class對象2> 返回值a) 如果是instance對象,返回class對象b) 如果是class對象,返回meta-class對象c) 如果是meta-class對象,返回NSObject(基類)的meta-class對象3.- (Class)class、+ (Class)class1> 返回的就是類對象- (Class) {return self->isa;}+ (Class) {return self;}元類對象在內存中存儲的信息包括
isa指針
superclass指針
類方法
判斷對象是不是元類對象
class_isMetaClass(objectMetaClass)isa
instance的isa指向class
當調用對象方法時,通過instance的isa找到class,最后找到對象方法的實現進行調用
class的isa指向meta-class
當調用類方法時,通過class的isa找到meta-class,最后找到類方法的實現進行調用
superclass
類對象的superclass
繼承中 調用父類的方法就需要先獲取到自己的類對象 然后在獲取到類對象的父對象 然后發消息
exp:
當Student的instance對象要調用Person的對象方法時,會先通過isa找到Student的class,然后通過superclass找到Person的class,最后找到對象方法的實現進行調用
元類對象的superclass
當Student的class要調用Person的類方法時,會先通過isa找到Student的meta-class,然后通過superclass找到Person的meta-class,最后找到類方法的實現進行調用
總結
instance的isa指向class
class的isa指向meta-class
meta-class的isa指向基類的meta-class
class的superclass指向父類的class
如果沒有父類,superclass指針為nil
meta-class的superclass指向父類的meta-class
基類的meta-class的superclass指向基類的class
instance調用對象方法的軌跡
isa找到class,方法不存在,就通過superclass找父類
class調用類方法的軌跡
isa找meta-class,方法不存在,就通過superclass找父類
face
對象的isa指針指向哪里?
instance對象的isa指向class對象
class對象的isa指向meta-class對象
meta-class對象的isa指向基類的meta-class對象
OC的類信息存放在哪里?
對象方法、屬性、成員變量、協議信息,存放在class對象中
類方法,存放在meta-class對象中
成員變量的具體值,存放在instance對象
class的結構
按理說 實例對象的isa 里存儲著 類對象的地址, 類對象的isa中存儲著元類對象的地址
實際情況是 :
從64bit開始,isa需要進行一次位運算,才能計算出真實地址
https://opensource.apple.com/tarballs/objc4/
class、meta-class對象的本質結構都是struct objc_class
KVO
使用
KVO的全稱是Key-Value Observing,俗稱“鍵值監聽”,可以用于監聽某個對象屬性值的改變
// 給person1對象添加KVO監聽NSKeyValueObservingOptions options = NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld;[self.person addObserver:self forKeyPath:@"age" options:options context:@"123"];[self.person addObserver:self forKeyPath:@"height" options:options context:@"456"];// 當監聽對象的屬性值發生改變時,就會調用 - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {NSLog(@"監聽到%@的%@屬性值改變了 - %@ - %@", object, keyPath, change, context); } // 在控制器銷毀之前需要移除監聽 - (void)dealloc {[self.person removeObserver:self forKeyPath:@"age"];[self.person removeObserver:self forKeyPath:@"height"]; }本質
如果你的實例對象添加KVO后, isa 指向的是另一個類對象(可以 po 打印 當前實例的isa)
NSKVONotifying_XXPerson是使用Runtime動態創建的一個類,是XXPerson的子類
在改變屬性值的時候會先找到NSKVONotifying_XXPerson的set方法;
偽代碼實現
@implementation NSKVONotifying_XXPerson- (void)setAge:(int)age {_NSSetIntValueAndNotify(); }// 偽代碼 void _NSSetIntValueAndNotify() {[self willChangeValueForKey:@"age"];// 調用了父類的set方法(原來XXPerson里面)[super setAge:age];[self didChangeValueForKey:@"age"]; }- (void)didChangeValueForKey:(NSString *)key {// 通知監聽器,某某屬性值發生了改變[oberser observeValueForKeyPath:key ofObject:self change:nil context:nil]; }@endlldb 打印方法的實現
p (IMP)方法地址
face
iOS用什么方式實現對一個對象的KVO?(KVO的本質是什么?)
利用RuntimeAPI動態生成一個子類,并且讓instance對象的isa指向這個全新的子類
當修改instance對象的屬性時,會調用Foundation的_NSSetXXXValueAndNotify函數
過程
willChangeValueForKey:
父類原來的setter
didChangeValueForKey:
內部會觸發監聽器(Oberser)的監聽方法( observeValueForKeyPath:ofObject:change:context:)
如何手動觸發KVO?
手動調用willChangeValueForKey:和didChangeValueForKey:
直接修改成員變量會觸發KVO么?
不會觸發KVO
KVC
KVC的全稱是Key-Value Coding,俗稱“鍵值編碼”,可以通過一個key來訪問某個屬性
常見的API有
- (void)setValue:(id)value forKeyPath:(NSString *)keyPath; - (void)setValue:(id)value forKey:(NSString *)key; - (id)valueForKeyPath:(NSString *)keyPath; - (id)valueForKey:(NSString *)key;setValue:(id)value forKey
accessInstanceVariablesDirectly方法的默認返回值是YES
valueForKey:的原理
face
通過KVC修改屬性會觸發KVO么?
會觸發KVO
KVC的賦值和取值過程是怎樣的?原理是什么?
Category
Category的底層結構
定義在objc-runtime-new.h中
Category的加載處理過程
通過Runtime加載某個類的所有Category數據
把所有Category的方法、屬性、協議數據,合并到一個大數組中
后面參與編譯的Category數據,會在數組的前面
將合并后的分類數據(方法、屬性、協議),插入到類原來數據的前面
合并是在運行中而不是在編譯的時候;在編譯的時候會先將分類存儲在一個叫Category_t的結構體里面,每一個分類都對應一個結構體實例
在運行中用到的時候合并到所屬類的類對象和元類對象
+load方法
+load方法會在runtime加載類、分類時調用
每個類、分類的+load,在程序運行過程中只調用一次
調用順序
先調用類的+load
按照編譯先后順序調用(先編譯,先調用)
調用子類的+load之前會先調用父類的+load
再調用分類的+load
按照編譯先后順序調用(先編譯,先調用)
+load方法是根據方法地址直接調用,并不是經過objc_msgSend函數調用
+initialize方法
+initialize方法會在類第一次接收到消息時調用
調用順序
先調用父類的+initialize,再調用子類的+initialize
(先初始化父類,再初始化子類,每個類只會初始化1次)
+initialize和+load的很大區別是,+initialize是通過objc_msgSend進行調用的,所以有以下特點
如果子類沒有實現+initialize,會調用父類的+initialize(所以父類的+initialize可能會被調用 多次)
如果分類實現了+initialize,就覆蓋類本身的+initialize調用
Cateogry-memmove、memcpy區別
memmove : 內存移動 可以實現向前向后移動內存(會判斷)保證以前的數據完整的保存在新的位置上
memcpy : 內存拷貝 直接挪動 會直接覆蓋
face
Category的實現原理
Category編譯之后的底層結構是struct category_t,里面存儲著分類的對象方法、類方法、屬性、協議信息
在程序運行的時候,runtime會將Category的數據,合并到類信息中(類對象、元類對象中)
Category和Class Extension的區別是什么?
Class Extension在編譯的時候,它的數據就已經包含在類信息中
Category是在運行時,才會將數據合并到類信息中
Category中有load方法嗎?load方法是什么時候調用的?load 方法能繼承嗎?
有load方法
load方法在runtime加載類、分類的時候調用
load方法可以繼承,但是一般情況下不會主動去調用load方法,都是讓系統自動調用
load、initialize方法的區別什么?
1.調用方式
1> load是根據函數地址直接調用
2> initialize是通過objc_msgSend調用
2.調用時刻
1> load是runtime加載類、分類的時候調用(只會調用1次)
2> initialize是類第一次接收到消息的時候調用,每一個類只會initialize一次(父類的initialize方法可能會被調用多次)
load、initialize的調用順序?
1.load
1> 先調用類的load
a) 先編譯的類,優先調用load
b) 調用子類的load之前,會先調用父類的load
2> 再調用分類的load
a) 先編譯的分類,優先調用load
2.initialize
1> 先初始化父類
2> 再初始化子類(可能最終調用的是父類的initialize方法)
關聯對象
如何實現給分類“添加成員變量”?
默認情況下,因為分類底層結構的限制,不能添加成員變量到分類中。但可以通過關聯對象來間接實現
關聯對象提供了以下API
添加關聯對象 void objc_setAssociatedObject(id object, const void * key,id value, objc_AssociationPolicy policy)獲得關聯對象 id objc_getAssociatedObject(id object, const void * key)移除所有的關聯對象 void objc_removeAssociatedObjects(id object)key的常見用法
static void *MyKey = &MyKey; objc_setAssociatedObject(obj, MyKey, value, OBJC_ASSOCIATION_RETAIN_NONATOMIC) objc_getAssociatedObject(obj, MyKey)static char MyKey; objc_setAssociatedObject(obj, &MyKey, value, OBJC_ASSOCIATION_RETAIN_NONATOMIC) objc_getAssociatedObject(obj, &MyKey)使用屬性名作為key objc_setAssociatedObject(obj, @"property", value, OBJC_ASSOCIATION_RETAIN_NONATOMIC); objc_getAssociatedObject(obj, @"property");使用get方法的@selecor作為key objc_setAssociatedObject(obj, @selector(getter), value, OBJC_ASSOCIATION_RETAIN_NONATOMIC) objc_getAssociatedObject(obj, @selector(getter))objc_AssociationPolicy
關聯對象的原理
實現關聯對象技術的核心對象有
AssociationsManager
AssociationsHashMap
ObjectAssociationMap
ObjcAssociation
原理
runtime 在底層實現了一個全局的字典來保存
Category能否添加成員變量?如果可以,如何給Category添加成員變量?
不能直接給Category添加成員變量,但是可以間接實現Category有成員變量的效果
block
block本質上也是一個OC對象,它內部也有個isa指針
block是封裝了函數調用以及函數調用環境的OC對象
變量
自動變量 也是只存在于局部變量 默認就是auto: 意思就是離開作用于自動銷毀;block捕獲的時候是值傳遞
static變量 block捕獲的時候是地址傳遞
全局變量 block 不會進行捕獲,需要的時候直接使用就好
類型
block有3種類型,可以通過調用class方法或者isa指針查看具體類型,最終都是繼承自NSBlock類型
NSGlobalBlock ( _NSConcreteGlobalBlock )
NSStackBlock ( _NSConcreteStackBlock )
NSMallocBlock ( _NSConcreteMallocBlock )
每一種類型的block調用copy后的結果如下所示
block的copy
在ARC環境下,編譯器會根據情況自動將棧上的block復制到堆上,比如以下情況
block作為函數返回值時
將block賦值給__strong指針時
block作為Cocoa API中方法名含有usingBlock的方法參數時
block作為GCD API的方法參數時
對象類型的auto變量
當block內部訪問了對象類型的auto變量時
如果block是在棧上,將不會對auto變量產生強引用
如果block被拷貝到堆上
會調用block內部的copy函數
copy函數內部會調用_Block_object_assign函數
_Block_object_assign函數會根據auto變量的修飾符(__strong、__weak、__unsafe_unretained)做出相應的操作,形成強引用(retain)或者弱引用
如果block從堆上移除
會調用block內部的dispose函數
dispose函數內部會調用_Block_object_dispose函數
_Block_object_dispose函數會自動釋放引用的auto變量(release)
__Block
__block可以用于解決block內部無法修改auto變量值的問題
__block不能修飾全局變量、靜態變量(static)
編譯器會將__block變量包裝成一個對象
face
block的原理是怎樣的?本質是什么?
封裝了函數調用以及調用環境的OC對象
__block的作用是什么?有什么使用注意點?
block的屬性修飾詞為什么是copy?使用block有哪些使用注意?
block一旦沒有進行copy操作,就不會在堆上
使用注意:循環引用問題
block在修改NSMutableArray,需不需要添加__block?
總結
以上是生活随笔為你收集整理的Object-C语法的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 华为虚拟机eNSP命令大全(所有命令)
- 下一篇: word多级标题的使用