OC学習記録エッセイ

すべてエッセイとメモです。不規則な。

動特性VS静特性

  • OC: 動的タイプ (id)、多態性バインディング ([obj msgSend])、多態性読み込み (画像 2x3x 置換、メソッドと変数の動的な追加)< /span>

OC 割り当て初期化新規

  • allocメモリを割り当ててポインタをバインドする
  • initreturn(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
sdkiphonearm64

オブジェクトクラスのメモリサイズを取得する

 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 進数 F1111 と表現されます)。
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 はしませんlibmallocnano_callocsegregated_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 (特殊
  • classsuperClass は親クラス のクラスを指します。
  • 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 クラスを生成します。は中間クラスを指し、中間クラスの クラスを指します。 isasuperClassperson

プロセス:

  • 使用runtimeして中間 kvo クラスを生成し、独自のインスタンス オブジェクトisaがこの中間クラス オブジェクトを指すようにします
  • 中間クラスは、監視オブジェクトのsetメソッドをオーバーライドします。
  • 監視対象オブジェクトのプロパティを変更する場合は、set メソッド内の基盤ライブラリの関数を呼び出します。_NSSetXXXValueAndNotify
  • 调用willChangeValueForKey: (可重写验证)
  • 元の親クラスsetメソッド
  • 调用didChangeValueForKey:(可重写验证)
  • オブザーバー(Observer)のリスニングメソッド(observeValueForKeyPath:ofObject:change:context)が内部的にトリガーされます。

つまり、生成されていない set メソッドの呼び出しの前後に、 を手動で呼び出す限り、トリガーできます。 /span>self->age = 5 システム監視機能。 willChangeValueForKeydidChangeValueForKey

メソッド名でランタイム メソッドのアドレス名を検索する

    [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 xxxFoundation や他のフレームワークのメソッド名リストを逆アセンブルできますが、前提条件は iOS バイナリ フレームワークです

KVC

setValue:forKey:(設定)の原理

ここに画像の説明を挿入します

valueForKey の原則: (get)

  • 移行valueForKey
  • メソッドを次の順序で検索しますgetKey key isKey _key -> 見つかったら直接呼び出します
  • が見つからない場合は、accessInstanceVariablesDirectly の戻り値が YES であるかどうかを確認します。NO の場合は、最終エラーをスローします。
  • YES -> メンバー変数を_key _isKey key isKey -> の順序で検索し、値を見つけたら直接取得します
  • が見つかりません -> エラーをスローします -> valueForUnderfinedKey: を呼び出し、例外 をスローしますNSUnknownKeyException
    ここに画像の説明を挿入します

KVC の変更により KVO モニタリングがトリガーされますか?

がトリガーされます。メソッドが生成されない場合set、内部で手動で呼び出す必要がありますwillChangeValueForKeydidChangeValueForKey はシステムのリスニング機能をトリガーできます。

カテゴリー

は、実行時にコンパイルされた構造体を元のクラスの属性の前に抽出します。特定の順序は逆順でコンパイルされます。

生成された 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
Categoryload
+load()objc_msgSend()isaload
appload

さまざまな呼び出しシーケンスをロードする

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 はリトル エンディアン モードです。つまり、ストレージ アドレスが逆方向、つまりリトル エンディアンです。

集まる

NSStringNSArrayNSDictionary などのクラス名は元の名前と異なります。
として Class c = NSStringFromString(@"__NSDictonaryM")

スタックが隠されています

ヒント: 完成したスタック情報を表示します。
メソッド スタックには 35 メソッドが表示されますが、 5-31 メソッドが非表示の場合は、 bt 完了スタックを表示するための指示。
ここに画像の説明を挿入します
以下に示すように:
ここに画像の説明を挿入します

ビューとレイヤー

  • UIView は表示されたコンテンツの管理に重点を置いています
  • CALayer は描画コンテンツに重点を置いています。
  • ビューにはレイヤーコンテンツの描画が表示されます
  • ビューはレイヤーのプロキシであり、レイヤーの表示と呼ばれます (ビューが表示されるとき、UIView はレイヤーの CALayerDelegate として機能し、ビューの表示内容は内部 CALayer のディスプレイによって決定されます)。

その他の収集された学習教材:

-iOS インターフェースをスムーズに保つためのヒント
-RunLoop についての深い理解

おすすめ

転載: blog.csdn.net/goldWave01/article/details/121163407