Objective-C基礎(chǔ)-OC語法

1皆怕、Objective-C的本質(zhì)

Objective-C代碼捅伤,底層實(shí)現(xiàn)其實(shí)都是C\C++代碼对竣。
Objective-C的對(duì)象、類主要是基于C\C++的結(jié)構(gòu)體實(shí)現(xiàn)的捏卓。


iOS編譯.jpg

將Objective-C代碼轉(zhuǎn)換為C\C++代碼

xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc OC源文件 -o 輸出的CPP文件
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc ViewController.m  -o ViewController.cpp

如果需要鏈接其他框架极祸,使用-framework參數(shù)。比如-framework UIKit

2怠晴、OC對(duì)象的本質(zhì)

OC對(duì)象占用字節(jié)數(shù)

 NSObject *obj = [[NSObject alloc] init];
 
 // 獲得NSObject實(shí)例對(duì)象的成員變量所占用的大小 >> 8個(gè)字節(jié)
 NSLog(@"%zd", class_getInstanceSize([NSObject class]));

 // 獲得obj指針?biāo)赶騼?nèi)存的大小 >> 16個(gè)字節(jié)
 NSLog(@"%zd", malloc_size((__bridge const void *)obj));

NSObject底層實(shí)現(xiàn)

NSObject其實(shí)是用結(jié)構(gòu)體實(shí)現(xiàn)遥金,有個(gè)isa屬性。

@interface NSObject {
    Class isa;
}

轉(zhuǎn)化為結(jié)構(gòu)體

struct NSObject_IMPL {
    Class isa; // 8個(gè)字節(jié)
};
//typedef struct objc_class *Class;//指針

獲取分配字節(jié)的函數(shù)

  • 創(chuàng)建一個(gè)實(shí)例對(duì)象蒜田,至少需要多少內(nèi)存
#import <objc/runtime.h>
class_getInstanceSize([NSObject class]);

  • 創(chuàng)建一個(gè)實(shí)例對(duì)象稿械,實(shí)際上分配了多少內(nèi)存
#import <malloc/malloc.h>
malloc_size((__bridge const void *)obj);

繼承的類占用字節(jié)數(shù)

@interface Student : NSObject
{
    Class isa;//8
    int _no;//4
    int _age;//4
    int _age1;//4
    int _age2;//4
    
}
@end

NSLog(@"%zd", class_getInstanceSize([Student class]));//24 成員變量占用24個(gè)字節(jié)
NSLog(@"%zd", malloc_size((__bridge const void *)stu));//32 系統(tǒng)分配了32個(gè)字節(jié)

2、OC對(duì)象的分類

  • instance對(duì)象(實(shí)例對(duì)象)
  • class對(duì)象(對(duì)象)
  • meta-class對(duì)象(元類對(duì)象)

2.1 instance

instance對(duì)象就是通過類alloc出來的對(duì)象冲粤,每次調(diào)用alloc都會(huì)產(chǎn)生新的instance對(duì)象美莫。

NSObject *object1 = [[NSObject alloc] init];
NSObject *object2 = [[NSObject alloc] init];

  • object1、object2是NSObject的instance對(duì)象(實(shí)例對(duì)象)
  • 它們是不同的兩個(gè)對(duì)象梯捕,分別占據(jù)著兩塊不同的內(nèi)存
  • instance對(duì)象在內(nèi)存中存儲(chǔ)的信息包括isa指針厢呵、其他成員變量。

@interface Person : NSObject {
    @public
    int _age;
}
@end

@implementation Person
@end
Person *p1 = [[Person alloc] init];
person1->_age = 3;

Person *p2 = [[Person alloc] init];
person2->_age = 4;
instance內(nèi)存.jpg

2.2 class

NSObject *object1 = [[NSObject alloc] init];
NSObject *object2 = [[NSObject alloc] init];
Class objectClass1 = [object1 class];
Class objectClass2 = [object2 class];
Class objectClass3 = object_getClass(object1);
Class objectClass4 = object_getClass(object2);
Class objectClass5 = [NSObject class];
Class objectClass6 = [[NSObject class] class];
  • objectClass1 ~ objectClass6都是NSObject的class對(duì)象(類對(duì)象)
  • 它們是同一個(gè)對(duì)象傀顾。每個(gè)類在內(nèi)存中有且只有一個(gè)class對(duì)象
  • class對(duì)象在內(nèi)存中存儲(chǔ)的信息主要包括
    • isa指針
    • superclass指針
    • 類的屬性信息(@property)襟铭、類的對(duì)象方法信息(instance method)
    • 類的協(xié)議信息(protocol)、類的成員變量信息(ivar)
class內(nèi)存.jpg

2.3 meta-class

Class objectMetaClass = object_getClass([NSObject class]);
  • objectMetaClass是NSObject的meta-class對(duì)象(元類對(duì)象)
  • 每個(gè)類在內(nèi)存中有且只有一個(gè)meta-class對(duì)象
  • meta-class對(duì)象和class對(duì)象的內(nèi)存結(jié)構(gòu)是一樣的,但是用途不一樣蝌矛,在內(nèi)存中存儲(chǔ)的信息主要包括
    • isa指針
    • superclass指針
    • 類的類方法信息
meta-class內(nèi)存.jpg
  • 以下代碼獲取的objectClass是class對(duì)象道批,并不是meta-class對(duì)象
Class objectClass6 = [[NSObject class] class];
  • 查看Class是否為meta-class
#import <objc/runtime.h>
BOOL result = class_isMetaClass([NSObject class]);

2.4 isa指針

isa指針.jpg
  1. instanceisa指向class
    當(dāng)調(diào)用對(duì)象方法時(shí),通過instanceisa找到class入撒,最后找到對(duì)象方法的實(shí)現(xiàn)進(jìn)行調(diào)用
  2. classisa指向meta-class
    當(dāng)調(diào)用類方法時(shí)隆豹,通過classisa找到meta-class,最后找到類方法的實(shí)現(xiàn)進(jìn)行調(diào)用

2.5 class對(duì)象的superclass指針

class對(duì)象的superclass指針.jpg
  1. 當(dāng)Student的instance對(duì)象要調(diào)用Person的對(duì)象方法時(shí)茅逮,會(huì)先通過isa找到Student的class璃赡,然后通過superclass找到Person的class,最后找到對(duì)象方法的實(shí)現(xiàn)進(jìn)行調(diào)用

2.6 meta-class對(duì)象的superclass指針

meta-class對(duì)象的superclass指針.jpg
  1. 當(dāng)Student的class要調(diào)用Person的類方法時(shí)献雅,會(huì)先通過isa找到Student的meta-class碉考,然后通過superclass找到Person的meta-class,最后找到類方法的實(shí)現(xiàn)進(jìn)行調(diào)用

2.7 isa挺身、superclass總結(jié)

isa-superclass.png
  1. instance的isa指向class
  2. class的isa指向meta-class
  3. meta-class的isa指向基類的meta-class
  4. class的superclass指向父類的class, 如果沒有父類侯谁,superclass指針為nil
  5. meta-class的superclass指向父類的meta-class, 基類的meta-class的superclass指向基類的class
  6. instance調(diào)用對(duì)象方法的軌跡: isa找到class,方法不存在章钾,就通過superclass找父類
  7. class調(diào)用類方法的軌跡:isa找meta-class墙贱,方法不存在,就通過superclass找父類

注意第5點(diǎn)

@interface NSObject (Test)
+ (void)test;
@end

@implementation NSObject (Test)
- (void)test {
    NSLog(@"-[NSObject test] - %p", self);
}
@end

@interface Person : NSObject
+ (void)test;
@end

@implementation Person
@end
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        NSLog(@"[Person class] - %p", [Person class]);
        NSLog(@"[NSObject class] - %p", [NSObject class]);
        [Person test];
        // objc_msgSend([Person class], @selector(test))
        // isa -> superclass -> suerpclass -> superclass -> .... superclass == nil
        [NSObject test];
        //objc_msgSend([NSObject class], @selector(test))
    }
    return 0;
}
meta-class輸出.jpg

3贱傀、ISA_MASK

ISA_MASK.jpg
  • 從64bit開始惨撇,isa需要進(jìn)行一次位運(yùn)算府寒,才能計(jì)算出真實(shí)地址
# if __arm64__
#   define ISA_MASK        0x0000000ffffffff8ULL
# elif __x86_64__
#   define ISA_MASK        0x00007ffffffffff8ULL
# endif

4、objc_class結(jié)構(gòu)

objc4源碼: https://opensource.apple.com/tarballs/objc4/

struct objc_object {
private:
    isa_t isa;
};

struct objc_class : objc_object {
    // Class ISA;
    Class superclass;
    cache_t cache;             // 方法緩存
    class_data_bits_t bits;    // 用于獲取類的信息
    
    class_rw_t *data() { 
        return bits.data();
    }
};

struct class_rw_t {
    // Be warned that Symbolication knows the layout of this structure.
    uint32_t flags;
    uint32_t version;

    const class_ro_t *ro;

    method_array_t methods;         //方法列表
    property_array_t properties;    //屬性列表
    protocol_array_t protocols;     //協(xié)議列表

    Class firstSubclass;
    Class nextSiblingClass;

    char *demangledName;
};

struct class_ro_t {
    uint32_t flags;
    uint32_t instanceStart;
    uint32_t instanceSize;  //instance對(duì)象占用的內(nèi)存空間
#ifdef __LP64__
    uint32_t reserved;
#endif

    const uint8_t * ivarLayout;
    
    const char * name;      //類名
    method_list_t * baseMethodList;
    protocol_list_t * baseProtocols;
    const ivar_list_t * ivars;//成員變量列表

    const uint8_t * weakIvarLayout;
    property_list_t *baseProperties;
};


5剖淀、KVO

KVO的全稱是Key-Value Observing祷蝌,俗稱“鍵值監(jiān)聽”帆卓,可以用于監(jiān)聽某個(gè)對(duì)象屬性值的改變

KVO監(jiān)聽.jpg

例如下類

@interface Person : NSObject
@property (assign, nonatomic) int age;
@end

@implementation Person

@end

5.1 未使用KVO監(jiān)聽的對(duì)象

KVO正常類.jpg

5.2 使用KVO監(jiān)聽的對(duì)象

KVO增加類.jpg
  • _NSSet*ValueAndNotify的內(nèi)部實(shí)現(xiàn)
- (void)setAge:(int)age {
    [self willChangeValueForKey:@"age"];
    [super setAge:age];
    [self didChangeValueForKey:@"age"];
}

調(diào)用步驟

  1. 調(diào)用willChangeValueForKey:
  2. 調(diào)用原來的setter實(shí)現(xiàn)
  3. 調(diào)用didChangeValueForKey:
  4. didChangeValueForKey:內(nèi)部會(huì)調(diào)用observer的observeValueForKeyPath:ofObject:change:context

5.3 Observer存在哪里

可通過GNUstep-Foundation源碼查看源碼觀察實(shí)現(xiàn)原理

5.4 KVO代碼例子

#import "ViewController.h"
#import <objc/runtime.h>

@interface ViewController ()
@property (strong, nonatomic) Person *person1;
@property (strong, nonatomic) Person *person2;
@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    self.person1 = [[Person alloc] init];
    self.person1.age = 1;
    
    self.person2 = [[Person alloc] init];
    self.person2.age = 2;
    
    // 給person1對(duì)象添加KVO監(jiān)聽
    NSKeyValueObservingOptions options = NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld;
    [self.person1 addObserver:self forKeyPath:@"age" options:options context:@"123"];
    
    NSLog(@"person1.class == %@", object_getClass(self.person1)) ;
    NSLog(@"person2.class == %@", object_getClass(self.person2)) ;

    [self printMethodNamesOfClass:object_getClass(self.person1)];
    [self printMethodNamesOfClass:object_getClass(self.person2)];
}

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    self.person1.age = 20;
    self.person2.age = 20;
}

- (void)dealloc {
    [self.person1 removeObserver:self forKeyPath:@"age"];
    [self.person1 removeObserver:self forKeyPath:@"height"];
}

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {
    NSLog(@"監(jiān)聽到%@的%@屬性值改變了 - %@ - %@", object, keyPath, change, context);
}

- (void)printMethodNamesOfClass:(Class)cls {
    unsigned int count;
    Method *methodList = class_copyMethodList(cls, &count);
    NSMutableString *methodNames = [NSMutableString string];
    for (int i = 0; i < count; i++) {
        Method method = methodList[i];
        NSString *methodName = NSStringFromSelector(method_getName(method));
        [methodNames appendString:methodName];
        [methodNames appendString:@", "];
    }
    free(methodList);
    NSLog(@"%@ %@", cls, methodNames);
}
@end
KVO輸出.jpg

6、KVC

KVC的全稱是Key-Value Coding米丘,俗稱“鍵值編碼”剑令,可以通過一個(gè)key來訪問某個(gè)屬性

  • 常見api
- (void)setValue:(id)value forKeyPath:(NSString *)keyPath;
- (void)setValue:(id)value forKey:(NSString *)key;
- (id)valueForKeyPath:(NSString *)keyPath;
- (id)valueForKey:(NSString *)key; 

  • setValue:forKey:的原理
setValueforKey原理.jpg

accessInstanceVariablesDirectly方法的默認(rèn)返回值是YES

  • valueForKey:的原理


    ValueforKey原理.jpg

7、Category

7.1 Category底層結(jié)構(gòu)

定義在objc-runtime-new.h中

struct category_t {
    const char *name;
    classref_t cls;
    struct method_list_t *instanceMethods;
    struct method_list_t *classMethods;
    struct protocol_list_t *protocols;
    struct property_list_t *instanceProperties;
    // Fields below this point are not always present on disk.
    struct property_list_t *_classProperties;

    method_list_t *methodsForMeta(bool isMeta) {
        if (isMeta) return classMethods;
        else return instanceMethods;
    }

    property_list_t *propertiesForMeta(bool isMeta, struct header_info *hi);
};

7.1 Category的加載處理過程

  1. 通過Runtime加載某個(gè)類的所有Category數(shù)據(jù)
  2. 把所有Category的方法拄查、屬性吁津、協(xié)議數(shù)據(jù),合并到一個(gè)大數(shù)組中, 后面參與編譯的Category數(shù)據(jù)曙博,會(huì)在數(shù)組的前面
  3. 將合并后的分類數(shù)據(jù)(方法簸搞、屬性、協(xié)議)鸣奔,插入到類原來數(shù)據(jù)的前面

源碼解讀順序

//objc-os.mm
_objc_init
map_images
map_images_nolock

//objc-runtime-new.mm
_read_images
remethodizeClass
attachCategories
attachLists
realloc典尾、memmove役拴、memcpy

7.2 +load方法

  • +load方法會(huì)在runtime加載分類時(shí)調(diào)用
  • 每個(gè)钾埂、分類的+load河闰,在程序運(yùn)行過程中只調(diào)用一次.
  1. 先調(diào)用類的+load
    • 按照編譯先后順序調(diào)用(先編譯,先調(diào)用)
    • 調(diào)用子類的+load之前會(huì)先調(diào)用父類的+load
  2. 再調(diào)用分類的+load
  • 按照編譯先后順序調(diào)用(先編譯褥紫,先調(diào)用)

objc4源碼解讀過程

//objc-os.mm
_objc_init

load_images

prepare_load_methods
schedule_class_load
add_class_to_loadable_list
add_category_to_loadable_list

call_load_methods
call_class_loads
call_category_loads
(*load_method)(cls, SEL_load)

+load方法是根據(jù)方法地址直接調(diào)用姜性,并不是經(jīng)過objc_msgSend函數(shù)調(diào)用

7.3 +initialize方法

  • +initialize方法會(huì)在第一次接收到消息時(shí)調(diào)用

  • 先調(diào)用父類的+initialize,再調(diào)用子類的+initialize
    (先初始化父類印机,再初始化子類,每個(gè)類只會(huì)初始化1次)

  • +initialize是通過objc_msgSend進(jìn)行調(diào)用的楣责,所以有以下特點(diǎn)

  1. 如果子類沒有實(shí)現(xiàn)+initialize,會(huì)調(diào)用父類的+initialize(所以父類的+initialize可能會(huì)被調(diào)用多次)
  2. 如果分類實(shí)現(xiàn)了+initialize沮趣,就覆蓋類本身的+initialize調(diào)用

objc4源碼解讀過程

//objc-msg-arm64.s
objc_msgSend

//objc-runtime-new.mm
class_getInstanceMethod
lookUpImpOrNil
lookUpImpOrForward
_class_initialize
callInitialize
objc_msgSend(cls, SEL_initialize)

7.4 +load方法、+initialize方法區(qū)別

  • 調(diào)用方式
  1. load是根據(jù)函數(shù)地址直接調(diào)用
  2. initialize是通過objc_msgSend調(diào)用
  • 調(diào)用時(shí)刻
  1. load是runtime加載類、分類的時(shí)候調(diào)用(只會(huì)調(diào)用1次)
  2. initialize是類第一次接收到消息的時(shí)候調(diào)用凌蔬,每一個(gè)類只會(huì)initialize一次(父類的initialize方法可能會(huì)被調(diào)用多次
  • load調(diào)用順序
  1. 先調(diào)用類的load, 先編譯的類, 優(yōu)先調(diào)用load,調(diào)用子類的load之前, 會(huì)先調(diào)用父類的load
  2. 再調(diào)用分類的load, 先編譯的分類懈词,優(yōu)先調(diào)用load
  • initialize調(diào)用順序
  1. 先初始化父類
  2. 再初始化子類(可能最終調(diào)用的是父類的initialize方法)

7.5 給分類"添加成員變量"

默認(rèn)情況下,因?yàn)榉诸惖讓咏Y(jié)構(gòu)的限制,不能添加成員變量到分類中褐桌。但可以通過關(guān)聯(lián)對(duì)象來間接實(shí)現(xiàn)

7.5.1 關(guān)聯(lián)對(duì)象API

//添加關(guān)聯(lián)對(duì)象
void objc_setAssociatedObject(id object, const void * key,
                                id value, objc_AssociationPolicy policy)

//獲得關(guān)聯(lián)對(duì)象
id objc_getAssociatedObject(id object, const void * key)

//移除所有的關(guān)聯(lián)對(duì)象
void objc_removeAssociatedObjects(id object)

7.5.2 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))

7.5.3 objc_AssociationPolicy

objc_AssociationPolicy 對(duì)應(yīng)的修飾符
OBJC_ASSOCIATION_ASSIGN assign
OBJC_ASSOCIATION_RETAIN_NONATOMIC strong, nonatomic
OBJC_ASSOCIATION_COPY_NONATOMIC copy, nonatomic
OBJC_ASSOCIATION_RETAIN strong, atomic
OBJC_ASSOCIATION_COPY copy, atomic

7.5.4 關(guān)聯(lián)對(duì)象的原理

實(shí)現(xiàn)關(guān)聯(lián)對(duì)象技術(shù)的核心對(duì)象有

  • AssociationsManager
  • AssociationsHashMap
  • ObjectAssociationMap
  • ObjcAssociation

objc4源碼解讀:objc-references.mm

class AssociationsManager {
    static AssociationsHashMap *_map;
};

class AssociationsHashMap : public unordered_map<disguised_ptr_t, ObjectAssociationMap *, DisguisedPointerHash, DisguisedPointerEqual, AssociationsHashMapAllocator>

class ObjectAssociationMap : public std::map<void *, ObjcAssociation, ObjectPointerLess, ObjectAssociationMapAllocator>

class ObjcAssociation {
    uintptr_t _policy;
    id _value;
};
關(guān)聯(lián)對(duì)象的原理.jpg
  • 關(guān)聯(lián)對(duì)象并不是存儲(chǔ)在被關(guān)聯(lián)對(duì)象本身內(nèi)存中
  • 關(guān)聯(lián)對(duì)象存儲(chǔ)在全局的統(tǒng)一的一個(gè)AssociationsManager
  • 設(shè)置關(guān)聯(lián)對(duì)象為nil啦撮,就相當(dāng)于是移除關(guān)聯(lián)對(duì)象

8、Block

8.1 block的本質(zhì)

  • block本質(zhì)上也是一個(gè)OC對(duì)象织中,它內(nèi)部也有個(gè)isa指針
  • block是封裝了函數(shù)調(diào)用以及函數(shù)調(diào)用環(huán)境的OC對(duì)象
  • block的底層結(jié)構(gòu)


    Block本質(zhì).jpg

8.2 block的變量捕獲

為了保證block內(nèi)部能夠正常訪問外部的變量,block有個(gè)變量捕獲機(jī)制


block的變量捕獲.jpg
int global_var = 10;//全局變量
static int static_global_var = 10;//靜態(tài)全局變量

void (^block)(void);
void test() {
    auto int local_var = 10;//局部變量
    static int static_local_var = 10;//靜態(tài)局部變量
    block = ^{
        NSLog(@"%d, %d, %d, %d", global_var, static_global_var, local_var, static_local_var);
    };
}

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        test();
    }
    return 0;
}

轉(zhuǎn)換為C++代碼

struct __block_impl {
  void *isa;
  int Flags;
  int Reserved;
  void *FuncPtr;
};

static struct __test_block_desc_0 {
  size_t reserved;
  size_t Block_size;
}

int global_var = 10;                    //全局變量
static int static_global_var = 10;  //靜態(tài)全局變量

void (*block)(void);

struct __test_block_impl_0 {
  struct __block_impl impl;
  struct __test_block_desc_0* Desc;
  int local_var;                    //捕獲的局部變量
  int *static_local_var;        //捕獲的靜態(tài)局部變量
  __test_block_impl_0(void *fp, struct __test_block_desc_0 *desc, int _local_var, int *_static_local_var, int flags=0) : local_var(_local_var), static_local_var(_static_local_var) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

8.3 block的類型

block有3種類型,可以通過調(diào)用class方法或者isa指針查看具體類型疲吸,最終都是繼承自NSBlock類型

  • __NSGlobalBlock__ ( _NSConcreteGlobalBlock )
  • __NSStackBlock__ ( _NSConcreteStackBlock )
  • __NSMallocBlock__ ( _NSConcreteMallocBlock )
block的內(nèi)存區(qū)域.png
Block類型.jpg
  • 每一種類型的block調(diào)用copy后的結(jié)果如下所示
BlockCopy.png

ARC環(huán)境下,編譯器會(huì)根據(jù)情況自動(dòng)將棧上的block復(fù)制到堆上,比如以下情況

  • block作為函數(shù)返回值時(shí)
  • 將block賦值給__strong指針時(shí)
  • block作為Cocoa API中方法名含有usingBlock的方法參數(shù)時(shí)
  • block作為GCD API的方法參數(shù)時(shí)

8.4 對(duì)象類型的auto變量

  • 當(dāng)block內(nèi)部訪問了對(duì)象類型的auto變量時(shí), 如果block是在棧上,將不會(huì)對(duì)auto變量產(chǎn)生強(qiáng)引用

  • 如果block被拷貝到堆上,會(huì)調(diào)用block內(nèi)部的copy函數(shù), copy函數(shù)內(nèi)部會(huì)調(diào)用_Block_object_assign函數(shù),_Block_object_assign函數(shù)會(huì)根據(jù)auto變量的修飾符(__strong窃祝、__weak__unsafe_unretained)做出相應(yīng)的操作探膊,形成強(qiáng)引用(retain)或者弱引用。

  • 如果block從堆上移除, 會(huì)調(diào)用block內(nèi)部的dispose函數(shù), dispose函數(shù)內(nèi)部會(huì)調(diào)用_Block_object_dispose函數(shù)
    _Block_object_dispose函數(shù)會(huì)自動(dòng)釋放引用的auto變量(release)腌闯。

Block-copy-dispose函數(shù).png

例子

//MRC環(huán)境下測試
@interface Person : NSObject
@property (assign, nonatomic) int age;
@end

@implementation Person
- (void)dealloc {
    NSLog(@"Person - dealloc");
    [super dealloc];
}
@end

1、如果block是在棧上分瘦,將不會(huì)對(duì)auto變量產(chǎn)生強(qiáng)引用

//MRC環(huán)境下測試
typedef void (^Block)(void);
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        Block block;
        {
            Person *person = [[Person alloc] init];
            person.age = 10;
            block = ^{
                NSLog(@"---------%d", person.age);
            };//棧block
            [person release];
        }
        NSLog(@"---------%@", block);
        NSLog(@"---end---");
    }
    return 0;
}

轉(zhuǎn)換為C++代碼如下

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  Person *person;
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, Person *_person, int flags=0) : person(_person) {
    impl.isa = &_NSConcreteStackBlock;//棧Block
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

棧Block輸出.jpg

2 如果block是在堆上, 根據(jù)auto變量的__strong翁潘、__weak渗勘、__unsafe_unretained形成強(qiáng)引用或者弱引用

strong

//ARC環(huán)境下測試
typedef void (^Block)(void);
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        Block block;
        {
            Person *person = [[Person alloc] init];
            person.age = 10;
            block = ^{
                NSLog(@"---------%d", person.age);
            };
            block();
        }
        NSLog(@"---------%@", block);
        NSLog(@"---end---");
    }
    return 0;
}

轉(zhuǎn)化為C++代碼

xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc -fobjc-arc -fobjc-runtime=ios-8.0.0 main.m
struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  Person *__strong person;
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, Person *__strong _person, int flags=0) : person(_person) {
    impl.isa = &_NSConcreteStackBlock;//堆block
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
堆Block輸出Strong.jpg

__weak

//ARC環(huán)境下測試
typedef void (^Block)(void);
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        Block block;
        {
            Person *person = [[Person alloc] init];
            person.age = 10;
            __weak Person *weakPerson = person;
            block = ^{
                NSLog(@"---------%d", weakPerson.age);
            };
            block();
        }
        NSLog(@"---------%@", block);
        NSLog(@"---end---");
    }
    return 0;
}

轉(zhuǎn)化為C++代碼

xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc -fobjc-arc -fobjc-runtime=ios-8.0.0 main.m
struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  Person *__weak weakPerson;    //weak指針
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, Person *__weak _weakPerson, int flags=0) : weakPerson(_weakPerson) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
堆Block輸出weak.jpg

8.5 __block修飾符

8.5.1 __block原理

  • __block可以用于解決block內(nèi)部無法修改auto變量值的問題
  • __block不能修飾全局變量取刃、靜態(tài)變量(static)
  • 編譯器會(huì)將__block變量包裝成一個(gè)對(duì)象
struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  __Block_byref_age_0 *age; // by ref
};

struct __Block_byref_age_0 {
  void *__isa;
__Block_byref_age_0 *__forwarding;
 int __flags;
 int __size;
 int age;
};

Block__forwarding指針.png

8.5.2 __block的內(nèi)存管理

1坯辩、當(dāng)block在棧上時(shí)漆魔,并不會(huì)對(duì)__block變量產(chǎn)生強(qiáng)引用。
2阿纤、當(dāng)block被copy到堆時(shí)

  • 會(huì)調(diào)用block內(nèi)部的copy函數(shù)
  • copy函數(shù)內(nèi)部會(huì)調(diào)用_Block_object_assign函數(shù)
  • _Block_object_assign函數(shù)會(huì)對(duì)__block變量形成強(qiáng)引用(retain
Block__copy__block變量.jpg

3、當(dāng)block從堆中移除時(shí)

  • 會(huì)調(diào)用block內(nèi)部的dispose函數(shù)
  • dispose函數(shù)內(nèi)部會(huì)調(diào)用_Block_object_dispose函數(shù)
  • _Block_object_dispose函數(shù)會(huì)自動(dòng)釋放引用的__block變量(release
Block__dispose_block變量.jpg

4、__block__forwarding指針

Blockcopy后的__forwarding指針.jpg

5、對(duì)象類型的auto變量东揣、__block變量

  • 當(dāng)block在棧上時(shí)嘶卧,對(duì)它們都不會(huì)產(chǎn)生強(qiáng)引用

  • 當(dāng)block拷貝到堆上時(shí),都會(huì)通過copy函數(shù)來處理它們

  • __block變量(假設(shè)變量名叫做a)
    _Block_object_assign((void*)&dst->a, (void*)src->a, 8/*BLOCK_FIELD_IS_BYREF*/);

  • 對(duì)象類型的auto變量(假設(shè)變量名叫做p)
    _Block_object_assign((void*)&dst->p, (void*)src->p, 3/*BLOCK_FIELD_IS_OBJECT*/);

  • 當(dāng)block從堆上移除時(shí),都會(huì)通過dispose函數(shù)來釋放它們

  • __block變量(假設(shè)變量名叫做a)
    _Block_object_dispose((void*)src->a, 8/*BLOCK_FIELD_IS_BYREF*/);

  • 對(duì)象類型的auto變量(假設(shè)變量名叫做p)
    _Block_object_dispose((void*)src->p, 3/*BLOCK_FIELD_IS_OBJECT*/);

8.6 Block的循環(huán)引用問題

Block循環(huán)引用1.jpg

Block循環(huán)引用2.jpg

ARC下

使用__weak__unsafe_unretained解決循環(huán)引用問題
__weak蒙袍、__unsafe_unretained區(qū)別在于__weak在對(duì)象釋放時(shí)會(huì)自動(dòng)置nil,而__unsafe_unretained不會(huì)置nil瘾蛋。

__weak typeof(self) weakSelf = self;
self.block = ^{    
    NSLog(@"%p", weakSelf);
};

使用__block解決循環(huán)引用問題,必須調(diào)用Block

__block id weakSelf = self;
self.block = ^{
    NSLog(@"%p", weakSelf);
    weakSelf = nil;
};
self.block();

MRC下

__unsafe_unretained解決循環(huán)引用問題

__unsafe_unretained id weakSelf = self;
self.block = ^{
    NSLog(@"%p", weakSelf);
};

__block解決循環(huán)引用問題,可以不用調(diào)用Block,

__block id weakSelf = self;
self.block = ^{
    NSLog(@"%p", weakSelf);
};
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末佩抹,一起剝皮案震驚了整個(gè)濱河市无宿,隨后出現(xiàn)的幾起案子孽鸡,更是在濱河造成了極大的恐慌,老刑警劉巖巷疼,帶你破解...
    沈念sama閱讀 219,270評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件瓷患,死亡現(xiàn)場離奇詭異攀细,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)故河,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,489評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門理盆,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人姨俩,你說我怎么就攤上這事∈χ#” “怎么了环葵?”我有些...
    開封第一講書人閱讀 165,630評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長宝冕。 經(jīng)常有香客問我张遭,道長,這世上最難降的妖魔是什么地梨? 我笑而不...
    開封第一講書人閱讀 58,906評(píng)論 1 295
  • 正文 為了忘掉前任洁闰,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘征唬。我一直安慰自己善镰,他們只是感情好树姨,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,928評(píng)論 6 392
  • 文/花漫 我一把揭開白布变汪。 她就那樣靜靜地躺著,像睡著了一般淆党。 火紅的嫁衣襯著肌膚如雪荷憋。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,718評(píng)論 1 305
  • 那天闷尿,我揣著相機(jī)與錄音,去河邊找鬼。 笑死玻募,一個(gè)胖子當(dāng)著我的面吹牛蝗砾,可吹牛的內(nèi)容都是我干的申尤。 我是一名探鬼主播胶逢,決...
    沈念sama閱讀 40,442評(píng)論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼穴亏,長吁一口氣:“原來是場噩夢(mèng)啊……” “哼恬偷!你這毒婦竟也來了协怒?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,345評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎亥鸠,沒想到半個(gè)月后塑荒,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體串绩,經(jīng)...
    沈念sama閱讀 45,802評(píng)論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,984評(píng)論 3 337
  • 正文 我和宋清朗相戀三年芜壁,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了礁凡。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,117評(píng)論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡慧妄,死狀恐怖顷牌,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情塞淹,我是刑警寧澤窟蓝,帶...
    沈念sama閱讀 35,810評(píng)論 5 346
  • 正文 年R本政府宣布,位于F島的核電站饱普,受9級(jí)特大地震影響疗锐,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜费彼,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,462評(píng)論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望口芍。 院中可真熱鬧箍铲,春花似錦、人聲如沸鬓椭。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,011評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽小染。三九已至翘瓮,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間裤翩,已是汗流浹背资盅。 一陣腳步聲響...
    開封第一講書人閱讀 33,139評(píng)論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人呵扛。 一個(gè)月前我還...
    沈念sama閱讀 48,377評(píng)論 3 373
  • 正文 我出身青樓每庆,卻偏偏與公主長得像,于是被迫代替她去往敵國和親今穿。 傳聞我的和親對(duì)象是個(gè)殘疾皇子缤灵,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,060評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容

  • 一、OC本質(zhì) 模擬器(i386)蓝晒、32bit(armv7)腮出、64bit(arm64) 查看C語言實(shí)現(xiàn)的指令不包含_...
    Yaanco閱讀 763評(píng)論 0 0
  • 面試題的答案都是拋磚引玉,具體細(xì)節(jié)還得深入了解iOS底層原理復(fù)制代碼 1芝薇、一個(gè)NSObject對(duì)象占用多少內(nèi)存胚嘲? ...
    大豆豆_小豆豆閱讀 352評(píng)論 0 0
  • 1、一個(gè)NSObject對(duì)象占用多少內(nèi)存剩燥? 系統(tǒng)分配了16個(gè)字節(jié)給NSObject對(duì)象(通過malloc_size...
    ptlCoder閱讀 645評(píng)論 0 5
  • 從未讓你驕傲慢逾,你卻待我如寶。 隨著年歲的增長灭红,離家的日子的也越來越多侣滩,相應(yīng)地,與親人相聚的時(shí)間也愈發(fā)減少变擒。在以前的...
    木白非白閱讀 307評(píng)論 2 7
  • 一直自詡文藝青年的我君珠,怎么可能錯(cuò)過去電影院看《芳華》的機(jī)會(huì)。盡管網(wǎng)絡(luò)上對(duì)馮小剛這部電影褒貶不一娇斑,但是我看到的各位看...
    西瓜皮90閱讀 297評(píng)論 0 1