すべてエッセイとメモです。不規則な。
-
ブロック:https://blog.csdn.net/goldWave01/article/details/121445902
-
ランタイム:https://blog.csdn.net/goldWave01/article/details/121504224
-
RunLoop:https://blog.csdn.net/goldWave01/article/details/121877941
-
多線程:https://blog.csdn.net/goldWave01/article/details/122094507
-
メモリ管理
https://blog.csdn.net/goldWave01/article/details/122262611
動特性VS静特性
- OC: 動的タイプ (
id
)、多態性バインディング ([obj msgSend]
)、多態性読み込み (画像 2x3x 置換、メソッドと変数の動的な追加)< /span>
OC 割り当て初期化新規
alloc
メモリを割り当ててポインタをバインドするinit
return(id)self、ファクトリ構築方法、ファクトリ設計、開発者がデータを初期化するための書き換えエントリを提供
記憶テスト
僵尸对象zombie object
: システムによって再利用されたが空にはされていないメモリ。いつでも他のデバイスに適用され、上書きされる可能性があります。内存
、プロセスは物理メモリにアクセスするためにそれぞれ仮想ページを持っています。アクティブなメモリのみが物理メモリにロードされます。各ページは iOS の場合は 16K、Mac および Linux の場合は 4K虚拟内存
、直接アクセスできず、完全に読み込まれないため、セキュリティの問題とメモリ不足が解決されます。缺页异常,页面置换
、iOS ソフトウェアが強制終了されました。クリックして再起動してください
OC バイナリ再配置
- xcode で linkmap オプションを設定すると、現在のプロジェクトのオーダー ファイルがコンパイル アドレスに追加されます。
- 注文ファイルを変更した後、xcode に現在の注文ファイルのアドレスを設定してインポートします。
- 一般的に使用されるコードまたはスタートアップ コードのすべてのメソッドのバイナリ メモリ ページを比較的早い段階でロードし、メモリ ページの頻繁な適用と上書きを減らすため (メモリ ページのほとんどは頻繁にアクティブになるメソッドのメモリ ページであるため)
.mはcppファイルにコンパイルされます
の代わりに であるため、生成されるコードはより的を絞ったものになります。コードもかなり少なくなります。 であると指定されているためです。指定されたアーキテクチャは が xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m -o main-arm63.cpp
します。これは、使用clang -rewrite-objc main.m -o main.cpp
sdk
iphone
arm64
オブジェクトクラスのメモリサイズを取得する
NSObject *objclass = [[NSObject alloc] init];
// NSObject 类实例对象大小(成员变量所占用的内存大小) //8
NSLog(@"%zd", class_getInstanceSize([objclass class]));
//所指obj指针内存大小 //16
NSLog(@"%zd", malloc_size((__bridge const void*) objclass));
//TODO //8
NSLog(@"%zd", sizeof(objclass));
16進数の合計
0x100749340 -> 89 6C DF 4A F8 FF 1D 01
は 16 進数表現を表します。 1 つの 16 進数は 4 桁を表します (16 進数 F
は 1111
と表現されます)。
1 バイトは 8 ビット、バイナリ 8 ビット: xxxxxxxx
の範囲は 00000000-11111111
で、0 ~ 255 を表します。 16 進数 (2 進数表記では xxxx
) は 15 (つまり 16 進数の F に相当) までしか表現できません。255 を表すには 2 桁目が必要です。したがって、1 バイト = 2 つの 16 進数文字、1 つの 16 進数 = 0.5 バイトになります。
要約:0x100749340 -> 89 6C DF 4A F8 FF 1D 01
は 8 バイトを占有します。
は NSObject のメモリ レイアウトから取得されます。
ビッグエンディアン リトルエンディアン
数値を文字列とみなした場合、たとえば、11223344 は末尾に「\0」が付いた「11223344」とみなされ、数値「11」から「44」が 1 つの記憶単位を占有する場合、その末尾は明らかに 44 になります。先頭の High または Low は、末尾が上位アドレスに配置されるか下位アドレスに配置されるかを示します。以下に示すように、メモリ内での配置方法は非常に直感的です。
- ビッグ エンディアン: ハイ エンディアン: 上位アドレスに格納されている最後の文字 44 を表します。ポジティブシーケンス。
- リトルエンディアン: ローエンディアン: 最初の文字 11 が上位アドレスに格納されることを意味します。 iOSはリトルエンディアンモードです。逆の順序。
NSObject に割り当てられたメモリ サイズ
@interface Student : NSObject {
int _no;
int _age;
int _height;
}.
@end.
Student
isa
の構造には、8 バイトのアライメント後に 24
バイトのメモリが必要です。ただし、 calloc
メソッドを使用してメモリを開く場合、 calloc
は < i=5> ライブラリ 詳細は以下の通り メモリメソッドを開くメソッド。 16 バイトの倍数を割り当てます (以下のコードを参照してください)。最大数は . であるため、最終的なメモリ サイズは になります。 calloc によって開かれたコンテンツは自動的に 0 で埋められます。しかし、malloc はしませんlibmalloc
nano_calloc
segregated_size_to_fit
#define NANO_MAX_SIZE 256
32
static MALLOC_INLINE size_t
segregated_size_to_fit(nanozone_t *nanozone, size_t size, size_t *pKey)
{
size_t k, slot_bytes;
if (0 == size) {
size = NANO_REGIME_QUANTA_SIZE; // Historical behavior
}
k = (size + NANO_REGIME_QUANTA_SIZE - 1) >> SHIFT_NANO_QUANTUM; // round up and shift for number of quanta
slot_bytes = k << SHIFT_NANO_QUANTUM; // multiply by power of two quanta size
*pKey = k - 1; // Zero-based!
return slot_bytes;
}
OC オブジェクトの分類
instance
オブジェクト (インスタンス オブジェクト):alloc
を通じて出てくるオブジェクトは、alloc
が呼び出されるたびに新しいオブジェクトを生成します。instance
オブジェクト。Student *stu = [[Student alloc] init]
class
オブジェクト (クラス オブジェクト)。クラスごとに 1 つのオブジェクトのみが存在します。meta-class
オブジェクト (メタクラス オブジェクト)。各クラスにはメタクラス オブジェクトが 1 つだけあります。
インスタンスオブジェクト
- ストレージ
isa
、メンバー変数具体指
クラスオブジェクト
NSObject *ob = [[NSObject alloc] init];
Class objClass = [ob class];
Class objClass1 = object_getClass([NSObjcet class]);
ストレージ:
isa
ポインタsuperClass
ポインタ- 类的属性信息(
@property
) - クラス (
instance method
) のオブジェクト メソッド情報 - クラスのプロトコル情報 (
protocol
) - クラス(
ivar
)のメンバ変数情報(型名などを入れる) - …
メタクラスオブジェクト
Class objClass = [NSObject class];
Class metaClass = object_getClass(objClass); //元类对象
//class_isMetaClass 可判读是否是元类对象
object_getClass
クラス オブジェクトを渡してメタクラス オブジェクトを取得します。[[NSObject class] class]
は何度でもインスタンス オブジェクトです
ストレージ:- 他の部分が空の値を格納することを除けば、クラス オブジェクトと同じです。
isa
ポインタsuperClass
ポインタ- 类的
类方法(工厂方法)
信息(class method
)
クラスオブジェクトの取得方法
Class _Nullable objc_getClass(const char * _Nonnull name)
クラス オブジェクトを返します (クラス名のみが渡されるため、固定オブジェクト タイプのみを返すことができます)。Class _Nullable object_getClass(id _Nullable obj)
クラスオブジェクトまたはメタクラスオブジェクトを返します。+ (Class)class
クラスオブジェクトを返す
は、スーパークラス
- インスタンスの
isa
方向クラス - クラスの
isa
メタクラスを指向 - metaClass的
isa
指向基类的 metaClass (特殊) - class
superClass
は親クラス のクラスを指します。 - metaClass
superClass
は親クラスのメタクラスを指します - 親クラスがない場合、
superClass
は nil です - 基类metaClass的
superClass
指向 基类的 class (特殊)
ISA_MASK TODO:
# define ISA_MASK 0x0000000ffffffff8ULL
isa
ポインタはmask
実際のポインタを取得する必要がありますsuperClass
マスク値を使用せずに、親クラスのポインタを直接取得します。
完全な略語
class_rw_t;
rw -> readwrite
t -> table
class_ro_t;
ro -> readonly
oc オブジェクトの保存場所:
objc-runtimme-new.h
-> struct objc_class : objc_object
-> class_rw_t *-
> class_rw_ext_t(class_ro_t)
objc_class 構造体の主なメンバーは次のとおりです。
Class ISA;
Class superclass;
cache_t cache; // formerly cache pointer and vtable
class_data_bits_t bits; // class_rw_t * plus custom rr/alloc flags
入手経路は以下の通り
KVO
は、 runtime
に中間クラス NSKVONotifying_Person
クラスと person
クラスを生成します。は中間クラスを指し、中間クラスの は クラスを指します。 isa
superClass
person
プロセス:
- 使用
runtime
して中間 kvo クラスを生成し、独自のインスタンス オブジェクトisa
がこの中間クラス オブジェクトを指すようにします - 中間クラスは、監視オブジェクトの
set
メソッドをオーバーライドします。 - 監視対象オブジェクトのプロパティを変更する場合は、
set
メソッド内の基盤ライブラリの関数を呼び出します。_NSSetXXXValueAndNotify
- 调用
willChangeValueForKey:
(可重写验证) - 元の親クラス
set
メソッド - 调用
didChangeValueForKey:
(可重写验证) - オブザーバー(Observer)のリスニングメソッド(observeValueForKeyPath:ofObject:change:context)が内部的にトリガーされます。
つまり、生成されていない set メソッドの呼び出しの前後に、 と を手動で呼び出す限り、トリガーできます。 /span>self->age = 5
システム監視機能。 willChangeValueForKey
didChangeValueForKey
メソッド名でランタイム メソッドのアドレス名を検索する
[self.person addObserver:self forKeyPath:@"age" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:@"11111"];
NSLog(@"before: %p", [self.person methodForSelector:@selector(setAge:)]);
上記に出力されたメモのアドレスを通じて、lldb の runtime の runtime メソッドをデバッグします。
// p (IMP)0x7fff207a3963
nmツール
nm Foundation | grep xxx
Foundation や他のフレームワークのメソッド名リストを逆アセンブルできますが、前提条件は iOS バイナリ フレームワークです
KVC
setValue:forKey:(設定)の原理
valueForKey の原則: (get)
- 移行
valueForKey
- メソッドを次の順序で検索します
getKey
key
isKey
_key
-> 見つかったら直接呼び出します - が見つからない場合は、
accessInstanceVariablesDirectly
の戻り値が YES であるかどうかを確認します。NO の場合は、最終エラーをスローします。 - YES -> メンバー変数を
_key
_isKey
key
isKey
-> の順序で検索し、値を見つけたら直接取得します - が見つかりません -> エラーをスローします ->
valueForUnderfinedKey:
を呼び出し、例外 をスローしますNSUnknownKeyException
KVC の変更により KVO モニタリングがトリガーされますか?
がトリガーされます。メソッドが生成されない場合set
、内部で手動で呼び出す必要がありますwillChangeValueForKey
。 didChangeValueForKey
はシステムのリスニング機能をトリガーできます。
カテゴリー
は、実行時にコンパイルされた構造体を元のクラスの属性の前に抽出します。特定の順序は逆順でコンパイルされます。
生成された cpp 実装構造体をコンパイルします。
コンパイル中に生成された cpp ファイルを表示でき、_category_t 構造体が作成され、値が割り当てられます。
struct _category_t {
const char *name;
struct _class_t *cls;
const struct _method_list_t *instance_methods;
const struct _method_list_t *class_methods;
const struct _protocol_list_t *protocols;
const struct _prop_list_t *properties; //只存在属性,没有成员变量(ivar)
};
static struct _category_t _OBJC_$_CATEGORY_MJPerson_$_Eat __attribute__ ((used, section ("__DATA,__objc_const"))) =
{
"MJPerson", // name
0, // &OBJC_CLASS_$_MJPerson,
(const struct _method_list_t *)&_OBJC_$_CATEGORY_INSTANCE_METHODS_MJPerson_$_Eat, //实例方法名字
0,
0,
(const struct _prop_list_t *)&_OBJC_$_PROP_LIST_MJPerson_$_Eat, //properties
};
実行時のバインド手順
_objc_init()
すべてのランタイムのメイン エントリ ポイント_dyld_objc_notify_register(&map_images, load_images, unmap_image);
map_images
map_images_nolock
_read_images
load_categories_nolock(hi);
attachCategories()
mlist、proplist、protolist はそれぞれすべてのカテゴリ データを取得し、Eluoji に保存します。void attachLists(List* const * addedLists, uint32_t addedCount)
このメソッドを呼び出し、元のメソッド属性 (malloc
すべてのオブジェクト) の前に挿入します
カテゴリとクラス拡張の違い
- クラス拡張がコンパイルされると、データはすでにクラス情報に含まれています。
- カテゴリは実行時にクラス情報にマージされます。
- クラス拡張は、クラスのプライベート メンバーとしてソース コードの m ファイルに記述する必要があります。
属性の追加 (メンバー変数を間接的に追加)
- カテゴリではメンバー変数を追加できません。属性のみを追加できます。 struct _category_t にはメンバー変数の属性が含まれていないため (
ivar
) - 属性を追加した後、set get メソッドを手動で実装し、
objc_setAsscoiatedObject()
を呼び出してオブジェクトを関連付ける必要があります。 - TOTO 需看
objc_setAsscoiatedObject()
源码
@property (nonatomic, copy) NSString *mname;
//m 里面实现
#define KeyName = "mname"
- (void)setMname:(NSString *)mname {
//第二个参数需要传入 void *的地址 @selector(mname) 方法地址,或者 &KeyName 的地址都可以。随意使用,注意和get方法使用key相同即可。
objc_setAssociatedObject(self, @selector(mname), mname, OBJC_ASSOCIATION_COPY_NONATOMIC);
}
- (NSString *)mname {
// _cmd == 当前方法的地址(每个方法传入的隐藏参数)
return objc_getAssociatedObject(self, @selector(mname));
}
objc_setAsscoiatedObject のソース コード分析
AssociationsManager
typedef DenseMap<const void *, ObjcAssociation> ObjectAssociationMap;
typedef DenseMap<DisguisedPtr<objc_object>, ObjectAssociationMap> AssociationsHashMap;
class ObjcAssociation {
uintptr_t _policy;
id _value;
};
関連する上位レベルの API:
id _object_get_associative_reference(id object, const void *key)
キーの値を取得するvoid _object_set_associative_reference(id object, const void *key, id value, uintptr_t policy)
nil に設定すると、現在のキーが削除されます。void _object_remove_assocations(id object, bool deallocating)
オブジェクトのすべての関連付けを削除します
+ロードメソッド
ソースコードの手順
_objc_init()
すべてのランタイムのメイン エントリ ポイント_dyld_objc_notify_register(&map_images, load_images, unmap_image);
load_images
call_load_methods();
call_class_loads();
call_category_loads();
は、 クラス のload
メソッドとCategory
メソッドを個別に呼び出します。load
(*load_method)(cls, @selector(load));
呼び出す関数ポインタ
暫定的な結論
Soload
メソッドは よりも前に呼び出されます。つまり < /a > メソッドは 1 回だけ呼び出されます のライフ サイクル中、各クラスの 関数は呼び出されません。 ポインターが指すクラス オブジェクトを検索します。そのため、オーバーライドされた ではなく関数ポインタを通じて呼び出されるため、 not go このメソッドはこのメソッドは元のクラスのloadメソッドを上書きしませんmain
Category
load
+load()
objc_msgSend()
isa
load
app
load
さまざまな呼び出しシーケンスをロードする
classref_t const *classlist =
_getObjc2NonlazyClassList(mhdr, &count);
for (i = 0; i < count; i++) {
schedule_class_load(remapClass(classlist[i]));
}
.......
// Recursively schedule +load for cls and any un-+load-ed superclasses.
// cls must already be connected.
static void schedule_class_load(Class cls)
{
if (!cls) return;
ASSERT(cls->isRealized()); // _read_images should realize
if (cls->data()->flags & RW_LOADED) return;
// Ensure superclass-first ordering 优先调用 父类
schedule_class_load(cls->getSuperclass());
add_class_to_loadable_list(cls);
cls->setInfo(RW_LOADED);
}
インスタンスオブジェクト
_getObjc2NonlazyClassList
コンパイル順序のインスタンス オブジェクトを取得しますcls->getSuperclass()
各インスタンス クラスについて、まず親クラスのloadメソッドが再帰的に呼び出され、それが配列にロードされます。- 結論: まず、コンパイル シーケンスを順番に呼び出します。各クラスが呼び出されるときに、親クラスを繰り返し呼び出し、
loadable_classes
を追加してから、順番に呼び出します。
カテゴリクラス
- コンパイル順序に従って順番に
loadable_categories
配列に直接追加し、順番に呼び出します
+initialize 方法
は、类
が初めてメッセージを受信したとき ([Person alloc]
など) に呼び出されます。呼び出す場所がない場合は、自動的には呼び出されません。
+initilaze
メソッドは objc_msgSend()
メソッドを通じて呼び出されるため、分類メソッドまたはクラス メソッドの 1 つだけが呼び出されます。
呼び出しプロセス
はアセンブリを通じて直接呼び出されます。 void callInitialize(Class cls) asm("_CALLING_SOME_+initialize_METHOD");
、コード内にこのメソッドがあるため、コード内で初期化メソッドを直接ブレークポイントすると、スタック内のメソッド名が _CALLING_SOME_+initialize_METHOD
であることがわかります。 。
- 。 。 。まだ見つかりません
Method class_getClassMethod(Class cls, SEL sel)
//class_getClassMethod.指定されたクラスとセレクターのクラス メソッドを返します。Method class_getInstanceMethod(Class cls, SEL sel)
またはstatic IMP _lookUpImpTryCache(id inst, SEL sel, Class cls, int behavior)
(cache_getImp はアセンブリに実装されており、キャッシュされた IMP を直接検索します)IMP lookUpImpOrForward(id inst, SEL sel, Class cls, int behavior)
static Class initializeAndLeaveLocked(Class cls, id obj, mutex_t& lock)
static Class initializeAndMaybeRelock(Class cls, id inst, mutex_t& lock, bool leaveLocked)
void initializeNonMetaClass(Class cls)
まず、最も親クラスのメソッドを再帰的に呼び出します。void callInitialize(Class cls)
((void(*)(Class, SEL))objc_msgSend)(cls, @selector(initialize));
警告: 親クラス (Person) の + initialize
メソッドは複数回呼び出される可能性があります。 (サブクラスが + initialize
メソッドを実装していない場合)
疑似コードは次のとおりです。
* SubPerson 继承于 Person
* SubPerson 没有实现 +initialize()
* Person 实现了 +initialize()
*/
SubPerson *p = [SubPerson alloc]; //第一次 发送消息
//SubPerson 第一次进来自己没有初始化
if (cls->isInitialized()) {
if (supercls && !supercls->isInitialized()) {
//同时父类Person也没有初始化,直接调用父类Person的initialize
((void(*)(Class, SEL))objc_msgSend)(supercls, @selector(initialize));
}
//然后给自己发initialize的消息,但是自己并没有实现这个方法,所以又调用了次次父类Person的initialize
((void(*)(Class, SEL))objc_msgSend)(cls, @selector(initialize));
}
ロードと初期化の違い
load が呼び出されるとき、環境はあまり安定していないため、基本的にはメソッドの交換にのみ使用されます。内部で時間のかかる操作を行わないでください。
initialize は、コンパイル中に初期化できないグローバル変数に使用できます。 (初期化が msgSend メカニズムに基づいている場合、複数のカテゴリがある場合、交換メソッドが配置されているカテゴリの初期化が呼び出されない可能性があるため、ここではメソッドは交換されません)
メソッドのデフォルトパラメータ
- OC のすべてのメソッドには、デフォルトで 2 つのデフォルト パラメータがあります
Person * self
SEL _cmd
。 self
: メソッドの呼び出し元を表し、_cmd
はメソッド名を表します
这几行是对下面的调用函数的提取。
可以看见,objc_msgSend 默认第一个,和第二个参数就是 self 和 _cmd.
(self = objc_msgSendSuper({
self, class_getSuperclass(objc_getClass("Person"))}, ("init")))
objc_msgSend(self, sel_registerName("setName:"),name);
objc_msgSend(self, sel_registerName("testMethod");
static instancetype _I_Person_initWithName_(Person * self, SEL _cmd, NSString *name) {
if (self = ((Person sel_registerName*(*)(__rw_objc_super *, SEL))(void *)objc_msgSendSuper)((__rw_objc_super){
(id)self, (id)class_getSuperclass(objc_getClass("Person"))}, sel_registerName("init"))) {
((void (*)(id, SEL, NSString * _Nonnull))(void *)objc_msgSend)((id)self, sel_registerName("setName:"), (NSString *)name);
((void (*)(id, SEL))(void *)objc_msgSend)((id)self, sel_registerName("testMethod"));
}
return self;
}
ブロック
長さが長すぎるため、この記事に移動しました。
Clang が __weak と __strong を使用する方法
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m
以下のエラーが発生します
/var/folders/9j/jxsl94c13kzbq69hbwnc6ytr0000gn/T/main-a89405.mi:29689:28: error: cannot create __weak reference because the current deployment target does not support weak references
__attribute__((objc_ownership(weak))) Person *weakPerson = per;
したがって、arc環境とランタイムライブラリのバージョンを追加する必要があります
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc -fobjc-arc -fobjc-runtime=ios-9.0.0 main.m
ランタイム
長さが長すぎるため、この記事に移動しました。
スタック空間のアドレス方向
+上位アドレスから下位アドレスに成長します。 iOS はリトル エンディアン モードです。つまり、ストレージ アドレスが逆方向、つまりリトル エンディアンです。
集まる
NSString
、NSArray
、NSDictionary
などのクラス名は元の名前と異なります。
として Class c = NSStringFromString(@"__NSDictonaryM")
スタックが隠されています
ヒント: 完成したスタック情報を表示します。
メソッド スタックには 35
メソッドが表示されますが、 5-31
メソッドが非表示の場合は、 bt
完了スタックを表示するための指示。
以下に示すように:
ビューとレイヤー
- UIView は表示されたコンテンツの管理に重点を置いています
- CALayer は描画コンテンツに重点を置いています。
- ビューにはレイヤーコンテンツの描画が表示されます
- ビューはレイヤーのプロキシであり、レイヤーの表示と呼ばれます (ビューが表示されるとき、UIView はレイヤーの CALayerDelegate として機能し、ビューの表示内容は内部 CALayer のディスプレイによって決定されます)。