iOS开发 (解惑-02)

一、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 
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)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))