美文网首页
iOS开发 (解惑-02)

iOS开发 (解惑-02)

作者: Xcode8 | 来源:发表于2018-11-03 22:36 被阅读31次
    一、block

    1)iOS开发的内存分配;
    2)block的变量捕获机制:为了保证在block的内部可以正常访问外部的变量;
    3)block的类型(__NSGlobalBlock__(存放数据段)、__NSStackBlock__(存放栈内存,出了作用域销毁)、__NSMallocBlock__(存放堆内存,自己控制));
    4)block在ARC下会自动copy的四种情况;
    5)对象auto修饰注意事项;
    6)__block修饰变量、对象;
    7)block的本质、block在使用过程中的注意事项、block的修饰词copy,block修改NSMutableArray需不需要添加__block修饰;

    iOS开发内存分布.png

    1-1、iOS内存分配:
    栈区:存放函数局部变量,栈区地址从高到低分配;
    堆区:堆区的地址是从低到高分配,存放alloc,malloc对象;
    全局区/静态区(未初始化):初始化的全局变量static修饰的变量,其实就是存放数据段;
    全局区/静态区(初始化):未初始化的全局变量static修饰的变量,其实就是存放数据段;
    常量:常量字符串就是放在这里,const修饰的常量;
    代码区:存放编译过的代码;

    1-2、block的变量捕捉分析:

    #import <Foundation/Foundation.h>
    int globalAge = 30;
    int main(int argc, const char * argv[]) {
        @autoreleasepool {
            auto int autoAge = 10;
            static int staicAge = 20;
    //1、定义方法
            void(^block)(void) = ^{
                NSLog(@"autoAge=%d,staicAge=%d,globalAge=%d",autoAge,staicAge,globalAge);
    //输出结果:autoAge=10(值补捉),staicAge=21(地址补捉),globalAge=31(全局变量)
            };
            autoAge = 11;
            staicAge = 21;
            globalAge = 31;
    //2、调用方法
            block();
        }
        return 0;
    }
    

    转化成C++代码分析:

    //1-1,调用过程
        auto int autoAge = 10;
        static int staicAge = 20;
        //定义方法
        void(*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, autoAge, &staicAge));
        //定义方法简化
        void(*block)(void)= &__main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA, autoAge, &staicAge);
        autoAge = 11;
        staicAge = 21;
        globalAge = 31;
        //调用方法
        ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
        //调用方法简化
        block->FuncPtr)(block);
    
    //1-2、调用过程
        int globalAge = 30;
        struct __main_block_impl_0 {
            struct __block_impl impl;
            struct __main_block_desc_0* Desc;
            int autoAge;
            int *staicAge;
            //方法的初始化(设置类的属性,方法初始值)
            __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _autoAge, int *_staicAge, int flags=0) : autoAge(_autoAge), staicAge(_staicAge) {
                impl.isa = &_NSConcreteStackBlock;
                impl.Flags = flags;
                impl.FuncPtr = fp;
                Desc = desc;
            }
        };
    
    //1-3、调用过程基本信息
        struct __block_impl {
            void *isa;
            int Flags;
            int Reserved;
            void *FuncPtr;
        };
        
        static struct __main_block_desc_0 {
            size_t reserved;
            size_t Block_size;
        } __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};
    
    //1-4、调用过程
        //block的方法内部实现
        static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
            int autoAge = __cself->autoAge; // bound by copy
            int *staicAge = __cself->staicAge; // bound by copy
            NSLog((NSString *)&__NSConstantStringImpl__var_folders_k4_j2sqg6y12zvgxxrm1h1wtpnh0000gn_T_main_101291_mi_0,autoAge,(*staicAge),globalAge);
        }
    

    1-2、总结(block变量捕捉)
    1)block的变量捕获本质(为了block的内部可以正常的访问block外部的变量auto(自动变量为局部变量,出了作用域内存就销毁掉了,尤其出现在跨方法调用问题上)、static(static修饰的代码只会运行一次,存放静态区域,存放内存中不会销毁)、全局变量(存放全局区,运行过程不会销毁));
    2)通过c++源码可以看出,static和auto修饰局部变量,会出现变量捕捉到block内部(auto是值捕捉方式,static是地址捕捉方式);全局变量不会出现变量捕捉;

    1-3、block的类型:(__NSStackBlock__进行copy操作会存储到__NSMallocBlock__上

    int height = 172;
    void (^blockWholeTest)(void);
    //1-3-1、block的类型(__NSGlobalBlock__) 没有访问auto变量(访问了static、全局变量(注意全局的宏不是全局变量只是替换值))的为此类型,放在堆上,需要自己管理
        static int age = 10;
        void (^blockTest)(void) = ^{
            NSLog(@"height=%d",height);
            NSLog(@"age=%d",age);
        };
        blockTest();
        NSLog(@"blockTest type is %@",[blockTest class]);
    
    //1-3-2、block的类型(__NSStackBlock__)访问了auto变量 (需要关闭 Automatic Reference Counting),存放在栈上,出了作用域内存回收
        auto int length = 18;
        void (^blockTest2)(void) = ^{
            NSLog(@"length=%d",length);
        };
        blockTest2();
        NSLog(@"blockTest2 type is %@",[blockTest2 class]);
    
    //1-3-3、分析作用域存在的问题:MRC环境下(ARC环境下:系统内部会自动进行copy操作)
    void test()
    {
        //__NSStackBlock__ 存放在栈上,除了作用域,系统回收内存
        int age = 27;
        blockWholeTest = [^{
            //打印出来的值因为内存回收了,所以是个未知值,想要正常访问的话,需要copy操作将block的stack到malloc
            NSLog(@"test---block---age=%d",age);
        } copy];
        [blockWholeTest release];
    }
    
    //作用域的问题
    test();
    blockWholeTest();
    

    1-4:总结:block的存放在栈区、有强指针指向这个block、使用了Cocoa API中的方法含有UsingBlock的参数、在GCD中block作为参数的时候;

    typedef void(^CWBlock)(void);
    CWBlock myblock()
    {
    //问题1.MRC环境下---这个block存在栈区
        int age = 20;
        CWBlock block = ^{
            NSLog(@"this return block %d",age);
        };
        return block;
    }
    int main(int argc, const char * argv[]) {
        @autoreleasepool {
            //问题1.这样调用比较危险,因为返回的block是存放在栈区,出了作用域销毁;
            CWBlock block = myblock();
            block();
            NSLog(@"%@",[block class]);
        
            //问题2:将block赋值给__strong指针时,ARC环境下编译器会自动copy操作(局部变量在arc环境下默认是__strong),什么时候赋值给__strong(我自己的理解:只是将^{ NSLog(@"age=%d",age);};赋值给block2了,全局的CWBlock强指针这个block块);
            int age = 10;
            CWBlock block2 = ^{
                NSLog(@"age=%d",age);
            };
            block2();
        
            //3、使用了Cocoa API中方法名含有UsingBlock的方法参数
            NSMutableArray *array = [NSMutableArray arrayWithObjects:@"3",@"6",@"4",nil];
            [array enumerateObjectsUsingBlock:^(id  _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
            
            }];
        
            //4、block作为GCD的方法参数的时候,会自动copy
            static dispatch_once_t onceToken;
            dispatch_once(&onceToken, ^{
                NSLog(@"once method");
            });
        }
        return 0;
    }
    

    1-5.总结:
    1)MRC情况下:Person在执行完[p2 release]就释放,所以再调用block()就有问题,想要正常调用block()需要将block从栈区copy到堆区
    2)ARC情况下:CWBlock强引用block,所以系统会自动将block copy到堆区;

    MRC:

        CWBlock block;
        {
            //auto修饰的对象,值传递
            Person *p2 = [[Person alloc] init];
            p2.age = 20;
    
            //ARC下需要自动管理内存,优化成下面代码
            //block = ^{
        //      NSLog(@"age=%zd",p2.age);
        //  } ;
    
            block = [^{
                NSLog(@"age=%zd",p2.age);
            } copy];
            [p2 release];
        }
        block();
        NSLog(@"-------华丽分割线--------");
    

    ARC:

    //block的修饰:__weak、__strong类型(ARC的环境下面,会自动copy操作,_Block_object_assign根据修饰词__weak、__strong,作出相应的弱引用,强引用;_Block_object_dispose根据修饰词进行销毁操作,相当于release);
    CWBlock block;
        {
            //auto修饰的对象,值传递
            Person *p2 = [[Person alloc] init];
            p2.age = 20;
    //          __weak Person *weakPerson = p2;
            block = ^{
                //使用weakPerson时候,出了Person作用域就销毁(因为没有strong引用);使用p2出了CWBlock作用域才会销毁;
                NSLog(@"age=%zd",p2.age);
    //              NSLog(@"age=%zd",weakPerson.age);
            };
        }
        NSLog(@"block type %@",[block class]);
        NSLog(@"-------华丽分割线--------");
    

    ARC分析:

    //对象的内存释放,出了作用域并没有进行释放,因为block持用person对象(因为auto修饰,捕捉了person对象),放block除了他的作用域的时候,block释放了,所以person也才释放
         struct __main_block_impl_0 {
         struct __block_impl impl;
         struct __main_block_desc_0* Desc;
         //捕捉了person对象
         Person *p2;
         __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, Person *_p2, int flags=0) : p2(_p2) {
             impl.isa = &_NSConcreteStackBlock;
             impl.Flags = flags;
             impl.FuncPtr = fp;
             Desc = desc;
    //此句会是block强引用p2对象
             p2 = _p2;
             }
         };
    

    1-6、__block修饰变量、对象注意事项:会生成__Block_byref_age_0、__Block_byref_per_1的结构体;

        __block int age = 10;
        void(^blockTest)(void) = ^{
            age = 20;
            NSLog(@"age=%d",age);
        };
        blockTest();
    

    __block修饰的变量:

    //1、定义方法
    void(*blockTest)(void) = ((void (*)())&__main_block_impl_1((void *)__main_block_func_1, &__main_block_desc_1_DATA, (__Block_byref_age_0 *)&age, 570425344))
    //2、生成的__Block_byref_age_0结构体
    struct __Block_byref_age_0 {
       void *__isa;
       __Block_byref_age_0 *__forwarding;
       int __flags;
       int __size;
       int age;
    };
    //3、修改age的值
    _static void __main_block_func_1(struct __main_block_impl_1 *__cself) {
      __Block_byref_age_0 *age = __cself->age; // bound by ref
        //这一步修改值
        (age->__forwarding->age) = 20;
        NSLog((NSString *)&__NSConstantStringImpl__var_folders_k4_j2sqg6y12zvgxxrm1h1wtpnh0000gn_T_main_96f4fb_mi_3,(age->__forwarding->age));
     }
    

    __block修饰的对象生成代码:

    //源代码
    __block Person *per = [[Person alloc] init];
    //生成的代码
    __main_block_impl_0包含__Block_byref_per_1 *per;
    
    struct __Block_byref_per_1 {
      void *__isa;
    __Block_byref_per_1 *__forwarding;
     int __flags;
     int __size;
     void (*__Block_byref_id_object_copy)(void*, void*);
     void (*__Block_byref_id_object_dispose)(void*);
     Person *per;
    };
    

    1-7、总结:
    1)block的本质(是一个OC对象、封装了代码块函数的定义初始化及函数的调用);
    2)block在使用过程中的注意事项(想要改变外部的变量:采用__block修饰变量;防止出现循环引用的问题:ARC环境下面可以采用__weak、__unsafe_unretained、__block方式解决循环引用问题,MRC:采用__unsafe_unretained、__block解决循环引用问题);
    3)block的修饰词copy(因为原始block的是放在栈区或者全局区,使用copy之后才能放到堆内存中,程序员控制block的生命周期);
    4)block中修改NSMutableArray不需要添加__block修饰();

    代码分析3问题:

    //ARC内存问题解决方案
       //__block Person *per = [[Person alloc] init];
       Person *per = [[Person alloc] init];
        per.age = 28;
    //方案1:最常用的方案
        //__weak typeof(Person) *weakPerson = per;
    //方案2:与方案1的,在release时刻会将指针置为nil,而此方案不会置为nil;
        //  __unsafe_unretained typeof(Person) *unsafePerson = per;
    //方案3:使用__block修饰对象,在block内部per.block = nil方案解决循环引用;
        per.block = ^{
    //          NSLog(@"%ld",weakPerson.age);
    //          NSLog(@"%ld",unsafePerson.age);
                NSLog(@"%ld",per.age);
            per.block = nil;
        };
        per.block();
    
    //MRC内存解决方案:
    采用__unsafe_unretained、__block(__main_block_impl_0 强引用__Block_byref_per_0、__Block_byref_per_0 中的per 弱引用Person对象),所以可以正常释放;
    
    __block Person *per = [[Person alloc] init];
    per.age = 28;
    per.block = ^{
        NSLog(@"%ld",per.age);
    };
    per.block();
    [per release];
    

    源码方案3分析:__main_block_impl_0 强引用__Block_byref_per_0、__Block_byref_per_0 中的per 强引用Person对象、Person对象又强引用block,所以形成三角引用关系

    struct __main_block_impl_0 {
      struct __block_impl impl;
      struct __main_block_desc_0* Desc;
      //1、__main_block_impl_0 强引用__Block_byref_per_0
      __Block_byref_per_0 *per; // by ref
      __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_per_0 *_per, int flags=0) : per(_per->__forwarding) {
        impl.isa = &_NSConcreteStackBlock;
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
      }
    };
    
    struct __Block_byref_per_0 {
      void *__isa;
    __Block_byref_per_0 *__forwarding;
     int __flags;
     int __size;
     void (*__Block_byref_id_object_copy)(void*, void*);
     void (*__Block_byref_id_object_dispose)(void*);
    //2、__Block_byref_per_0 中的per 强引用Person对象
     Person *per;
    };
    

    代码分析4问题:

    NSMutableArray *array = [NSMutableArray array];
    void(^blockTest)(void) = ^{
    //没有修改指针的值,只是使用值
        [array addObject:@"1"];
        [array addObject:@"2"];
    };
    blockTest();
    
    二、Runtime

    Runtime知识点:
    1)Runtime的方法的调用三大过程:消息发送、动态解析、消息转发(objc_msgSend的执行流程:消息发送、动态方法解析、消息转发(都找不到报错:找不到方法);
    2)super [self class]、[self superclass]、[super class]、[super superclass];
    3)isKindOfClass、isMemberOfClass方法;
    4)Runtime的应用:利用关联对象给分类添加属性、获取实例对象的属性和成员变量 、方法交换实现、NSArray,NSDictionary的nil处理;

    1-1、class的结构:

    class的结构.png

    1-2、cache_t方法缓存结构:缓存通过方法名&mask生成一个key值,用于存取方法的key(哈希表)

    方法缓存.png

    1-3、方法的调用流程总结:1、当对象调用一个方法,首先会在通过isa找到类对象,在类对象里面的cache_t的方法里查找是否有调用过这个方法,有则直接返回方法的地址调用;2、没有则在class_rw_t的结构体里面method_list_t里面查找,有则调用,缓存到cache_t里面;3、没有的话通过superClass找到父类,在父类的cache_t里面看是否有缓存过这个方法(有的话则缓存到当前调用对象的cache_t),没有的话,则在class_rw_t的结构体里面method_list_t里面查找,有则调用,缓存到调用类cache_t里面;没有则继续在父类中查找,父类还是没有找到则进入动态解析过程,动态解析没找到方法实现则进入消息转发,没有报错doesNotRecognizeSelector:

    1-3-1、消息发送:

    objc_msgSend执行流程(消息发送).png
    //转化成runtime的消息发送  消息接收者:per;消息名称:test
    //objc_msgSend(per,selector(test));
    Person *per = [[Person alloc] init];
    [per test];
    

    1-3-2、消息动态解析:

    objc_msgSend执行流程(动态解析).png
    + (BOOL)resolveInstanceMethod:(SEL)sel
    {
        if (sel == @selector(test)) {
            // 获取其他方法
            Method method = class_getInstanceMethod(self, @selector(otherTest));
           // 动态添加test方法的实现
            class_addMethod(self, sel,
                          method_getImplementation(method),
                          method_getTypeEncoding(method));
            // 返回YES代表有动态添加方法
            return YES;
        }
        return [super resolveInstanceMethod:sel];
    }
    

    1-3-3、消息转发:

    objc_msgSend执行流程(消息转发).png
    //消息转发阶段--返回消息的接受者(方法为空,走签名的方法)
    - (id)forwardingTargetForSelector:(SEL)aSelector{
        if (aSelector == @selector(test)) {
            return [[Student alloc] init];
        }
        return [super forwardingTargetForSelector:aSelector];
    }
    
    //方法签名:返回值类型、参数类型(当返回了方法签名,就会走forwardInvocation方法)
    - (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
    {
        if (aSelector == @selector(test)) {
            return [NSMethodSignature signatureWithObjCTypes:"v16@0:8"];
        }
        return [super methodSignatureForSelector:aSelector];
    }
    
    //NSInvocation:封装了方法调用者、方法名、方法参数
    //anInvocation.target 方法调用者
    //anInvocation.selector 方法名
    //[anInvocation getArgument:null atIndex:0] 获取参数
    - (void)forwardInvocation:(NSInvocation *)anInvocation
    {
        //改下target,执行
        anInvocation.target = [[Student alloc] init];
        [anInvocation invoke];
    //  [anInvocation invokeWithTarget:[[Student alloc] init]];
    }
    

    2-1、代码验证super内部调用逻辑:
    [super msg]源码实现:objc_msgSendSuper({self, //消息接收者
    class_getSuperclass(objc_getClass("CWStudent"))},//消息接受父类,方法从父类查找
    sel_registerName("superclass"))//执行方法
    }

    //结构体
    struct objc_super {
        /// Specifies an instance of a class.
        __unsafe_unretained _Nonnull id receiver;//消息接收者
        /// Specifies the particular superclass of the instance to message.
        __unsafe_unretained _Nonnull Class super_class;//方法调用开始从父类查找
        /* super_class is the first class to search */
    };
    
    /**< NSObject的class、superclass内部实现代码 */
    - (Class)class
    {
        //self消息接受者,即当前实例对象
        return object_getClass(self);
    }
    - (Class)superclass
    {
        //self为消息接受者,实例对象
        return class_getSuperclass(object_getClass(self));
    }
    
    - (instancetype)init
    {
        if (self = [super init]) {
            //转化成的消息
            NSLog(@"self - class = %@",[self class]);//CWStudent
            ((Class (*)(id, SEL))(void *)objc_msgSend)((id)self,
                            sel_registerName("class"));
            NSLog(@"self - superclass = %@",[self superclass]);//CWPerson
            ((Class (*)(id, SEL))(void *)objc_msgSend)((id)self,
                            sel_registerName("superclass"));
        
        //__rw_objc_super为结构体
        NSLog(@"super - superclass = %@",[super class]);//CWStudent
        ((Class (*)(__rw_objc_super *, SEL))(void *)objc_msgSendSuper)((__rw_objc_super){
                (id)self,
                (id)class_getSuperclass(objc_getClass("CWStudent"))},
                sel_registerName("class"));
    
         NSLog(@"super - superclass = %@",[super superclass]);//CWPerson
        ((Class (*)(__rw_objc_super *, SEL))(void *)objc_msgSendSuper)((__rw_objc_super){
                (id)self,
                (id)class_getSuperclass(objc_getClass("CWStudent"))},
                sel_registerName("superclass"));
        
        return self;
    }
    

    3-1、源码:

    + (BOOL)isMemberOfClass:(Class)cls {
        //判断:当前实例对象的`类对象`是否等于传进来的对象
        return object_getClass((id)self) == cls;
    }
    
    - (BOOL)isMemberOfClass:(Class)cls {
        //判断:当前类对象的`元类对象`是否等于传进来的对象
        return [self class] == cls;
    }
    
    + (BOOL)isKindOfClass:(Class)cls {
        //判断:当前、或者父类--的类对象的`元类对象`是否等于传进来的对象
        for (Class tcls = object_getClass((id)self); tcls; tcls = tcls->superclass) {
            if (tcls == cls) return YES;
        }
        return NO;
    }
    
    - (BOOL)isKindOfClass:(Class)cls {
        //判断:当前、或者父类--的实例对象的`类对象`是否等于传进来的对象
        for (Class tcls = [self class]; tcls; tcls = tcls->superclass)     {
            if (tcls == cls) return YES;
        }
        return NO;
    }
    

    3-2、代码实践:

    CWStudent *stu = [[CWStudent alloc] init];
    CWPerson *per = [[CWPerson alloc] init];
    
    NSLog(@"----------------");
    NSLog(@"%d",[stu isKindOfClass:[CWStudent class]]);//1
    NSLog(@"%d",[stu isMemberOfClass:[CWPerson class]]);//0
    NSLog(@"%d",[per isMemberOfClass:[CWStudent class]]);//0
    NSLog(@"%d",[per isMemberOfClass:[CWPerson class]]);//1
    
    
    NSLog(@"----------------");
    NSLog(@"%d",[CWStudent isKindOfClass:[CWPerson class]]);//0
    NSLog(@"%d",[CWStudent isKindOfClass:[CWStudent class]]);//0
    NSLog(@"%d",[CWPerson isMemberOfClass:[CWPerson class]]);//0
    NSLog(@"%d",[CWPerson isMemberOfClass:[CWStudent class]]);//0
    
    
    NSLog(@"----------------");
    NSLog(@"%d",[CWStudent isKindOfClass:object_getClass([CWPerson class])]);//1
    NSLog(@"%d",[CWStudent isKindOfClass:object_getClass([CWStudent class])]);//1
    NSLog(@"%d",[CWPerson isMemberOfClass:object_getClass([CWPerson class])]);//1
    NSLog(@"%d",[CWPerson isMemberOfClass:object_getClass([CWStudent class])]);//0
    
    
    NSLog(@"%d",[CWPerson isMemberOfClass:[NSObject class]]);//0
    //特殊分析:因为CWPerson的元类一直到NSObject的元类(NSObject的元类就是NSObject)
    NSLog(@"%d",[CWPerson isKindOfClass:[NSObject class]]);//1
    

    4-1、获取对象的成员变量和属性(查看私有成员变量、重置对象值、字典转模型):

        CWPerson *person = [[CWPerson alloc] init];
        //获取person类的属性和成员变量
        unsigned int count;
        Ivar *ivars = class_copyIvarList([person class], &count);
        for (int i = 0; i < count; i ++) {
            Ivar ivar = ivars[i];
            NSLog(@"%s %s",ivar_getName(ivar),ivar_getTypeEncoding(ivar));
        }
    

    4-2:方法交换实现(统计按钮的点击事件、NSMutableArray,NSMutableDictionary为nil处理):

    Method  methodTest1 = class_getInstanceMethod([person class], @selector(test1));
    Method  methodTest2 = class_getInstanceMethod([person class], @selector(test2));
    method_exchangeImplementations(methodTest1, methodTest2);
    //拦截所有的button点击事件,统一做处理
    + (void)load
    {
        Method sy_btnMethod = class_getInstanceMethod(self, @selector(sendAction:to:forEvent:));
        Method cw_btnMethod = class_getInstanceMethod(self, @selector(cw_sendAction:to:forEvent:));
        method_exchangeImplementations(sy_btnMethod, cw_btnMethod);
    }
    
    - (void)cw_sendAction:(SEL)action to:(id)target forEvent:(UIEvent *)event
    {
        NSLog(@"%@-%@-%@",self,NSStringFromSelector(action),target);
    
        [self cw_sendAction:action to:target forEvent:event];
    }
    
    //类簇--数组添加nil对象崩溃
     NSMutableArray *array = [NSMutableArray array];
     [array addObject:nil];
    //添加的对象为nil处理
    + (void)load
    {
        Class cls = NSClassFromString(@"__NSArrayM");
        Method sy_arrayMethod = class_getInstanceMethod(cls, @selector(insertObject:atIndex:));
        Method cw_arrayMethod = class_getInstanceMethod(cls, @selector(cw_insertObject:atIndex:));
        method_exchangeImplementations(sy_arrayMethod, cw_arrayMethod);
    }
    - (void)cw_insertObject:(id)anObject atIndex:(NSUInteger)index
    {
        if (anObject == nil) return ;
        [self cw_insertObject:anObject atIndex:index];
    }
    
    
    //类簇--字典key为nil崩溃
     NSMutableDictionary *dic = [NSMutableDictionary dictionary];
     dic[nil] = @"cjw";
    //key为nil处理
    + (void)load
    {
        Class cls = NSClassFromString(@"__NSDictionaryM");
        Method sy_dicMethod = class_getInstanceMethod(cls, @selector(setObject:forKeyedSubscript:));
        Method cw_dicMethod = class_getInstanceMethod(cls, @selector(cw_setObject:forKeyedSubscript:));
        method_exchangeImplementations(sy_dicMethod, cw_dicMethod);
    }
    - (void)cw_setObject:(id)obj forKeyedSubscript:(id<NSCopying>)key
    {
        if (key == nil) return ;
        [self cw_setObject:obj forKeyedSubscript:key];
    }
    
    三、Runloop

    app项目:

    @autoreleasepool {
            return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
        }
    

    命令行项目:

    @autoreleasepool {
            // insert code here...
            NSLog(@"Hello, World!");
        }
     return 0;
    

    二个项目的区别:
    1、app项目执行UIApplicationMain函数创建一个Runloop,一直在检测app的是否有点击、触摸事件、定时器等,有事件处理事件、没有则处于休眠状态(保持程序的运行状态);
    2、命令行项目:相当于执行完NSLog函数就退出;

    相关文章

      网友评论

          本文标题:iOS开发 (解惑-02)

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