美文网首页
Objective-C基础-OC语法

Objective-C基础-OC语法

作者: 学习天亦 | 来源:发表于2019-08-04 23:48 被阅读0次

    1、Objective-C的本质

    Objective-C代码,底层实现其实都是C\C++代码。
    Objective-C的对象、类主要是基于C\C++的结构体实现的。


    iOS编译.jpg

    将Objective-C代码转换为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参数。比如-framework UIKit

    2、OC对象的本质

    OC对象占用字节数

     NSObject *obj = [[NSObject alloc] init];
     
     // 获得NSObject实例对象的成员变量所占用的大小 >> 8个字节
     NSLog(@"%zd", class_getInstanceSize([NSObject class]));
    
     // 获得obj指针所指向内存的大小 >> 16个字节
     NSLog(@"%zd", malloc_size((__bridge const void *)obj));
    
    

    NSObject底层实现

    NSObject其实是用结构体实现,有个isa属性。

    @interface NSObject {
        Class isa;
    }
    

    转化为结构体

    struct NSObject_IMPL {
        Class isa; // 8个字节
    };
    //typedef struct objc_class *Class;//指针
    

    获取分配字节的函数

    • 创建一个实例对象,至少需要多少内存
    #import <objc/runtime.h>
    class_getInstanceSize([NSObject class]);
    
    
    • 创建一个实例对象,实际上分配了多少内存
    #import <malloc/malloc.h>
    malloc_size((__bridge const void *)obj);
    
    

    继承的类占用字节数

    @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个字节
    NSLog(@"%zd", malloc_size((__bridge const void *)stu));//32 系统分配了32个字节
    
    

    2、OC对象的分类

    • instance对象(实例对象)
    • class对象(对象)
    • meta-class对象(元类对象)

    2.1 instance

    instance对象就是通过类alloc出来的对象,每次调用alloc都会产生新的instance对象。

    NSObject *object1 = [[NSObject alloc] init];
    NSObject *object2 = [[NSObject alloc] init];
    
    
    • object1、object2是NSObject的instance对象(实例对象)
    • 它们是不同的两个对象,分别占据着两块不同的内存
    • instance对象在内存中存储的信息包括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内存.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对象(类对象)
    • 它们是同一个对象。每个类在内存中有且只有一个class对象
    • class对象在内存中存储的信息主要包括
      • isa指针
      • superclass指针
      • 类的属性信息(@property)、类的对象方法信息(instance method)
      • 类的协议信息(protocol)、类的成员变量信息(ivar)
    class内存.jpg

    2.3 meta-class

    Class objectMetaClass = object_getClass([NSObject class]);
    
    • objectMetaClass是NSObject的meta-class对象(元类对象)
    • 每个类在内存中有且只有一个meta-class对象
    • meta-class对象和class对象的内存结构是一样的,但是用途不一样,在内存中存储的信息主要包括
      • isa指针
      • superclass指针
      • 类的类方法信息
    meta-class内存.jpg
    • 以下代码获取的objectClass是class对象,并不是meta-class对象
    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
      当调用对象方法时,通过instanceisa找到class,最后找到对象方法的实现进行调用
    2. classisa指向meta-class
      当调用类方法时,通过classisa找到meta-class,最后找到类方法的实现进行调用

    2.5 class对象的superclass指针

    class对象的superclass指针.jpg
    1. 当Student的instance对象要调用Person的对象方法时,会先通过isa找到Student的class,然后通过superclass找到Person的class,最后找到对象方法的实现进行调用

    2.6 meta-class对象的superclass指针

    meta-class对象的superclass指针.jpg
    1. 当Student的class要调用Person的类方法时,会先通过isa找到Student的meta-class,然后通过superclass找到Person的meta-class,最后找到类方法的实现进行调用

    2.7 isa、superclass总结

    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调用对象方法的轨迹: isa找到class,方法不存在,就通过superclass找父类
    7. class调用类方法的轨迹:isa找meta-class,方法不存在,就通过superclass找父类

    注意第5点

    @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需要进行一次位运算,才能计算出真实地址
    # if __arm64__
    #   define ISA_MASK        0x0000000ffffffff8ULL
    # elif __x86_64__
    #   define ISA_MASK        0x00007ffffffffff8ULL
    # endif
    
    

    4、objc_class结构

    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;     //协议列表
    
        Class firstSubclass;
        Class nextSiblingClass;
    
        char *demangledName;
    };
    
    struct class_ro_t {
        uint32_t flags;
        uint32_t instanceStart;
        uint32_t instanceSize;  //instance对象占用的内存空间
    #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,俗称“键值监听”,可以用于监听某个对象属性值的改变

    KVO监听.jpg

    例如下类

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

    5.1 未使用KVO监听的对象

    KVO正常类.jpg

    5.2 使用KVO监听的对象

    KVO增加类.jpg
    • _NSSet*ValueAndNotify的内部实现
    - (void)setAge:(int)age {
        [self willChangeValueForKey:@"age"];
        [super setAge:age];
        [self didChangeValueForKey:@"age"];
    }
    

    调用步骤

    1. 调用willChangeValueForKey:
    2. 调用原来的setter实现
    3. 调用didChangeValueForKey:
    4. didChangeValueForKey:内部会调用observer的observeValueForKeyPath:ofObject:change:context

    5.3 Observer存在哪里

    可通过GNUstep-Foundation源码查看源码观察实现原理

    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对象添加KVO监听
        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(@"监听到%@的%@属性值改变了 - %@ - %@", 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,俗称“键值编码”,可以通过一个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:forKey:的原理
    setValueforKey原理.jpg

    accessInstanceVariablesDirectly方法的默认返回值是YES

    • valueForKey:的原理


      ValueforKey原理.jpg

    7、Category

    7.1 Category底层结构

    定义在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加载某个类的所有Category数据
    2. 把所有Category的方法、属性、协议数据,合并到一个大数组中, 后面参与编译的Category数据,会在数组的前面
    3. 将合并后的分类数据(方法、属性、协议),插入到类原来数据的前面

    源码解读顺序

    //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方法会在runtime加载分类时调用
    • 每个分类的+load,在程序运行过程中只调用一次.
    1. 先调用类的+load
      • 按照编译先后顺序调用(先编译,先调用)
      • 调用子类的+load之前会先调用父类的+load
    2. 再调用分类的+load
    • 按照编译先后顺序调用(先编译,先调用)

    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方法是根据方法地址直接调用,并不是经过objc_msgSend函数调用

    7.3 +initialize方法

    • +initialize方法会在第一次接收到消息时调用

    • 先调用父类的+initialize,再调用子类的+initialize
      (先初始化父类,再初始化子类,每个类只会初始化1次)

    • +initialize是通过objc_msgSend进行调用的,所以有以下特点

    1. 如果子类没有实现+initialize,会调用父类的+initialize(所以父类的+initialize可能会被调用多次)
    2. 如果分类实现了+initialize,就覆盖类本身的+initialize调用

    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方法区别

    • 调用方式
    1. load是根据函数地址直接调用
    2. initialize是通过objc_msgSend调用
    • 调用时刻
    1. load是runtime加载类、分类的时候调用(只会调用1次)
    2. initialize是类第一次接收到消息的时候调用,每一个类只会initialize一次(父类的initialize方法可能会被调用多次
    • load调用顺序
    1. 先调用类的load, 先编译的类, 优先调用load,调用子类的load之前, 会先调用父类的load
    2. 再调用分类的load, 先编译的分类,优先调用load
    • initialize调用顺序
    1. 先初始化父类
    2. 再初始化子类(可能最终调用的是父类的initialize方法)

    7.5 给分类"添加成员变量"

    默认情况下,因为分类底层结构的限制,不能添加成员变量到分类中。但可以通过关联对象来间接实现

    7.5.1 关联对象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)
    
    

    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 对应的修饰符
    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 关联对象的原理

    实现关联对象技术的核心对象有

    • 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;
    };
    
    关联对象的原理.jpg
    • 关联对象并不是存储在被关联对象本身内存中
    • 关联对象存储在全局的统一的一个AssociationsManager
    • 设置关联对象为nil,就相当于是移除关联对象

    8、Block

    8.1 block的本质

    • block本质上也是一个OC对象,它内部也有个isa指针
    • block是封装了函数调用以及函数调用环境的OC对象
    • block的底层结构


      Block本质.jpg

    8.2 block的变量捕获

    为了保证block内部能够正常访问外部的变量,block有个变量捕获机制


    block的变量捕获.jpg
    int global_var = 10;//全局变量
    static int static_global_var = 10;//静态全局变量
    
    void (^block)(void);
    void test() {
        auto int local_var = 10;//局部变量
        static int static_local_var = 10;//静态局部变量
        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;
    }
    

    转换为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;  //静态全局变量
    
    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;        //捕获的静态局部变量
      __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种类型,可以通过调用class方法或者isa指针查看具体类型,最终都是继承自NSBlock类型

    • __NSGlobalBlock__ ( _NSConcreteGlobalBlock )
    • __NSStackBlock__ ( _NSConcreteStackBlock )
    • __NSMallocBlock__ ( _NSConcreteMallocBlock )
    block的内存区域.png Block类型.jpg
    • 每一种类型的block调用copy后的结果如下所示
    BlockCopy.png

    ARC环境下,编译器会根据情况自动将栈上的block复制到堆上,比如以下情况

    • block作为函数返回值时
    • 将block赋值给__strong指针时
    • block作为Cocoa API中方法名含有usingBlock的方法参数时
    • block作为GCD API的方法参数时

    8.4 对象类型的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-copy-dispose函数.png

    例子

    //MRC环境下测试
    @interface Person : NSObject
    @property (assign, nonatomic) int age;
    @end
    
    @implementation Person
    - (void)dealloc {
        NSLog(@"Person - dealloc");
        [super dealloc];
    }
    @end
    

    1、如果block是在栈上,将不会对auto变量产生强引用

    //MRC环境下测试
    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;
    }
    

    转换为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是在堆上, 根据auto变量的__strong__weak__unsafe_unretained形成强引用或者弱引用

    strong

    //ARC环境下测试
    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;
    }
    

    转化为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环境下测试
    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;
    }
    

    转化为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内部无法修改auto变量值的问题
    • __block不能修饰全局变量、静态变量(static)
    • 编译器会将__block变量包装成一个对象
    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的内存管理

    1、当block在栈上时,并不会对__block变量产生强引用。
    2、当block被copy到堆时

    • 会调用block内部的copy函数
    • copy函数内部会调用_Block_object_assign函数
    • _Block_object_assign函数会对__block变量形成强引用(retain
    Block__copy__block变量.jpg

    3、当block从堆中移除时

    • 会调用block内部的dispose函数
    • dispose函数内部会调用_Block_object_dispose函数
    • _Block_object_dispose函数会自动释放引用的__block变量(release
    Block__dispose_block变量.jpg

    4、__block__forwarding指针

    Blockcopy后的__forwarding指针.jpg

    5、对象类型的auto变量、__block变量

    • 当block在栈上时,对它们都不会产生强引用

    • 当block拷贝到堆上时,都会通过copy函数来处理它们

    • __block变量(假设变量名叫做a)
      _Block_object_assign((void*)&dst->a, (void*)src->a, 8/*BLOCK_FIELD_IS_BYREF*/);

    • 对象类型的auto变量(假设变量名叫做p)
      _Block_object_assign((void*)&dst->p, (void*)src->p, 3/*BLOCK_FIELD_IS_OBJECT*/);

    • 当block从堆上移除时,都会通过dispose函数来释放它们

    • __block变量(假设变量名叫做a)
      _Block_object_dispose((void*)src->a, 8/*BLOCK_FIELD_IS_BYREF*/);

    • 对象类型的auto变量(假设变量名叫做p)
      _Block_object_dispose((void*)src->p, 3/*BLOCK_FIELD_IS_OBJECT*/);

    8.6 Block的循环引用问题

    Block循环引用1.jpg
    Block循环引用2.jpg

    ARC下

    使用__weak__unsafe_unretained解决循环引用问题
    __weak__unsafe_unretained区别在于__weak在对象释放时会自动置nil,而__unsafe_unretained不会置nil。

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

    使用__block解决循环引用问题,必须调用Block

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

    MRC下

    __unsafe_unretained解决循环引用问题

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

    __block解决循环引用问题,可以不用调用Block,

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

    相关文章

      网友评论

          本文标题:Objective-C基础-OC语法

          本文链接:https://www.haomeiwen.com/subject/hfiqrctx.html