笔记-iOS中级教程多线程

资料来源:腾讯课堂=>《[iOS]iOS中级教程多线程》

09 __bridge

pthread_t pthread;

//char *name = "zs";
//int result = pthread_create(&pthread, NULL, demo, name);

//------使用OC语言
NSString *name = @"zs";
//__bridge 桥接
//MRC中内存管理原则:谁申请,水释放
//ARC中自动给OC对象,添加retain release autorelease
//把OC中的对象传给c语言的函数,要桥接;同样的,把c语言的参数传给OC也要桥接
int result = pthread_create(&pthread, NULL, demo, (__bridge void *)(name));
//demo函数
void * (*demo)(void * param){
NSString *name = (__bridge NSString *)param;
//NSLog(@"hello %s, %@", param, [NSThread currentThread]);
NSLog(@"hello %@, %@", name, [NSThread currentThread]);
}

__bridge告诉函数pthread_create,在ARC中传入的参数name需要函数来负责release

10 NSThread

3种创建方式:

//方法一:需要调用start方法开启线程
NSThread *thread = [[NSThread alloc] initWithTarget: self selector: @selector(demo) object: nil];
[thread start];
//方法二:类方法
[NSThread detachNewThreadSelector: @selector(demo) toTarget: self withObject: nil];
//方法三:严格来说不算是NSThread方法
[self performSelectorInBackground: @selector(demo) withObject: nil];

11 线程状态

//当线程结束之后,不能再次使用

//新建状态
NSThread *thread = [[NSThread alloc] initWithTarget: self selector: @selector(demo) object: nil];
//就绪状态
[thread start];

-(void)demo{
  for (int i = 0; i < 20; i ++){
    NSLog(@"%d", i);
    if (i == 5){
      //阻塞状态
      [NSThread sleepForTimeInterval: 3];
    }
    if (i == 10){
      //线程退出 死亡状态
      [NSThread exit];
    }
  }
}

12 线程属性

NSThread *thread = [[NSThread alloc] initWithTarget: self selector: @selector(demo) object: nil];
//线程名称
thread.name = @"t1";
//线程优先级,0-1.0,default:0.5
//内核调度算法在决定该运行哪个线程时,会把线程的优先级作为考量因素,较高优先级的线程会比较低优先级的线程具有更多的运行机会。较高优先级不保证你的线程具体执行的时间,只是相比较低优先级的线程它更有可能被调度器选择执行而已。
//即无法保证thread执行完再执行其他线程
thread.threadPriority = 1.0;
[thread start];

15 互斥锁

//任意一个对象内部都有一把锁,锁默认是打开的
//加锁会影响程序的性能

//互斥锁
//线程同步
/*
NSObject *obj = [NSObject new];
@synchronized(obj){
  //此时,使用obj局部变量的话,thread1进来默认objc的锁是打开的,程序可以继续进行;然后thread2进来,又重新初始化了一个objc,锁默认也是打开的,因此程序也可继续进行,无法达到加锁的效果;
  //改进办法:将objc设置成全局变量或者属性
}
*/
//用的是self的锁
@synchronized(self){
  if (self.ticketsCount > 0)
    self.ticketsCount --;
}else{
  NSLog(@"来晚啦,票没了");
}

互斥锁使用:@synchronize(锁对象){//需要锁定的代码}

互斥锁:能有效防止因多线程抢夺资源造成的数据安全问题

线程同步的意思是:多条线程按顺序地执行任务。互斥锁就是使用了线程同步技术


16 原子属性

属性中的修饰符:

  • nonatomic 非原子属性
  • atomic 原子属性(线程安全),针对多线程设计的,默认值。保证同一时间只有一个线程能够写入,但是同一时间多个线程都可以取值。atomic本身就有一把锁(自旋锁),单写多读:单个线程写入,多个线程可以读取。

17 互斥锁和自旋锁的区别

互斥锁:如果发现其他线程正在执行锁定代码,线程会进入休眠(就绪状态),等其他线程时间片到打开锁后,线程会被唤醒(执行)

自旋锁:如果发现有其他线程正在锁定代码,线程会用死循环的方式,一直等待锁定的代码执行完成,自旋锁更适合执行不耗时的代码。一般就用在属性中。

线程安全:线程同时操作时不安全的,多个线程同时操作一个全局变量。线程安全,即是在多个贤臣进行读写操作时,仍然能够保证数据的正确。

主线程(UI线程):

  • 几乎所有UIKit提供的类都是线程不安全的,所有更新UI的操作都在主线程上执行。例如,更新label的值,如果多个线程都要操作label则无法保证线程安全,因此把UI操作都放在主线程,即执行完一样再执行另一样,保证安全。
  • 所有包含Mutable的类都是线程不安全的。

18 异步下载网络图片

-(void)loadView
{
    //初始化scrollView,并赋值给当前vc的view
    self.scrollView = [[UIScrollView alloc] initWithFrame: [UIScreen mainScreen].bounds];
    self.scrollView.backgroundColor = [UIColor whiteColor];
    self.view = self.scrollView;
    //初始化imageView
    self.imageView = [[UIImageView alloc] init];
    [self.scrollView addSubview: self.imageView];
}

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view.
    
    NSThread *thread = [[NSThread alloc] initWithTarget: self selector: @selector(downloadImage) object: nil];
    [thread start];
}

-(void)downloadImage
{
    NSData *data = [NSData dataWithContentsOfURL: [NSURL URLWithString: @"https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1583403375529&di=765d6a01b4b7a5183862ad886f5a2f5d&imgtype=0&src=http%3A%2F%2Fpic1.win4000.com%2Fwallpaper%2F2017-11-29%2F5a1e130e127ef.jpg"]];
    UIImage *image = [UIImage imageWithData: data];
    
    //在主线程上更新UI控件  线程间通信
    //waitUntilDone 值是YES 会等待方法执行完毕,才会执行后续代码
    [self performSelectorOnMainThread: @selector(updateUI:) withObject: image waitUntilDone: YES];
    
}

-(void)updateUI: (UIImage *)image
{
    self.imageView.image = image;
    //    self.imageView.frame = CGRectMake(0, 0, image.size.width, image.size.height);
    //让imageView的大小和图片一致
    [self.imageView sizeToFit];
    //设置scrollView的滚动范围
    self.scrollView.contentSize = image.size;
}

19 strong和weak

什么时候用strong和weak

  • OC对象用strong
  • UI控件用连线的时候用weak,因为拖拽的时候相当于[self.view addSubview: **],即又一次强引用了;使用strong也可以,只是self对控件又加了一次强引用,不会导致循环引用,但在释放时要释放两次。

示例:

创建ZYPerson类,整体工程是ARC模式的,设置ZYPerson为MRC

那么,秉持谁申请谁释放的原则,将person放入自动释放池,延迟释放。

+(instancetype)personWithName:(NSString *)name
{
    ZYPerson *person = [[ZYPerson new] autorelease];
    person.name = name;
    return person;
}

在vc中绑定属性,特地设置为weak

@property (nonatomic, weak) ZYPerson *p1;
@property (nonatomic, weak) ZYPerson *p2;

viewDidLoad中初始化p1和p2属性

- (void)viewDidLoad {
    [super viewDidLoad];
    
    self.p1 = [[ZYPerson alloc] init];
    self.p1.name = @"zs";
    NSLog(@"p1: %@", self.p1.name);
    
    self.p2 = [ZYPerson personWithName: @"ls"];
    NSLog(@"p2: %@", self.p2.name);
}

结果为:


原因:p2采用的初始化方法中,自动释放池对p2有强引用,因此可以打印出ls。在viewDidLoad()结束后自动释放池销毁时,p2也就release了。因此如果在其他方法中再次调用self.p2.name也会得到null的结果。

-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
    NSLog(@"p1: %@", self.p1.name);
    NSLog(@"p2: %@", self.p2.name);
}

20 自动释放池

iOS开放中的内存管理:

  • iOS开发中,没有JAVA或C#中的垃圾回收机制
  • 在MRC中对象谁申请,谁释放
  • 使用ARC开发,只是在编译时,编译器会根据代码结构自动添加retain, release和autorelease

自动释放池:

  • 标记为autorelease的对象,会被添加到最近一次创建的自动释放池中
  • 当自动释放池被销毁或耗尽时,会向池中所有对象发送release消息

每一次主线程的消息循环开始的时候,系统会先创建自动释放池,消息循环结束前,会释放自动释放池。消息循环(the event loop)是用来处理事件的。

自动释放池和线程的关系,其实是因为消息循环和线程有关。

示例中viewDidLoad()方法中的是在application:didFinishLaunchingWithOptions事件里执行的,因此自动释放池也是在此事件中创建和释放的。

什么时候使用自动释放池:

  • 如果你写了一个循环,其内部创建了很多临时对象,你需要在循环内部创建自动释放池用来销毁这些对象。
  • 如果你生成一个子线程,你必须创建你自己的自动释放池,并且是在线程开始的时候尽快创建autoreleasepool,否则会造成内存泄露(该释放没释放;野指针:不该释放的释放了

21 自动释放池面试题

for (int i = 0; i < 100000000;i ++){
  @autoreleasepool{
      NSString *str = [[NSString alloc] initWithFormat: @"%d", i];
  }
}

加上自动释放池后,内存几乎不涨。

22 属性的修饰符

属性修饰符:

retain: MRC中使用

strong: ARC中使用

weak: 只有ARC下才能用

assign: ARC和MRC都可以使用

copy:ARC和MRC都可以使用

  • 字符串为什么要用copy:

    举例:

    NSMutableString *string = [NSMutableString string];
    [string appendString: @"hello"];
    

    如果使用strong

    @property (nonatomic, strong) NSString *name;
    //赋值
    self.name = string;
    

    再修改string的值

    [string appendString: @" zs"];
    

    结果得到name也是"hello zs"。因为*表示地址,对name赋值时相当于指向string的地址,因此string更改,name也跟着改变。但这不是我们想要的,name作为字符串被赋值后,我们希望它保持这个值,下面来看看copy

    @property (nonatomic, copy) NSString *name;
    //赋值
    self.name = string;
    

    修改string的值后打印name依然是"hello"

    原理:使用copy时编译器会帮我们做一件事,赋值时对string进行copy。相当于

    @property (nonatomic, strong) NSString *name;
    //赋值
    self.name = [string copy];
    
  • block作为属性的时候,为什么要用copy

    第一种block 全局Block __NSGlobalBlock__

    void (^demo)() = ^{
      NSLog(@"aaa");
    };
    NSLog(@"%@", demo);
    

    第二种block 栈Block __NSStackBlock__,环境MRC

    int number = 3;
    void (^demo)() = ^{
      NSLog(@"aaa %d", number);
    };
    NSLog(@"%@", demo);
    

    第三种block 堆Block __NSMallocBlock__,环境MRC

    int number = 3;
    void (^demo)() = ^{
      NSLog(@"aaa %d", number);
    };
    NSLog(@"%@", [demo copy]);
    

    一般我们使用的最多的是第二种block,在MRC中定义block属性如下

    @property (nonatomic, assign) void(^myBlock)();
    

    给myBlock赋值

    -(void)test{
      int n = 5;
      [self setMyBlock: ^{
        NSLog(@"%d", n);
      }];
      NSLog(@"%@", self.myBlock);
    }
    

    使用myBlock

    [self test];
    self.myBlock();
    self.myBlock();//第二次调用的时候报错,因为myBlock是在栈空间,test作用域外即被释放。
    

    因此使用block作为属性时使用copy,copy过的block被储存在堆上,如第三种block。

  • delegate为什么要用weak修饰

    self.person = [Person new];
    self.person.delegate = self;
    

    如果delegate的修饰符是strong,导致循环引用

    vc-->person-->delegate-->self(vc)

  • weak和assign的区别

    举例,分别用weak和assign修饰oc对象

    @property (nonatomic, weak) Person *weakPerson;
    @property (nonatomic, assign) Person *assignPerson
    

    初始化并复制

    self.weakPerson = [Person new];//1
    self.weakPerson.name = @"zs";//2
    NSLog(@"%@", self.weakPerson.name);
    
    self.assignPerson = [Person new];//3
    self.assignPerson.name = @"ls";//4
    NSLog(@"%@", self.assignPerson.name);
    

    结果,程序在self.assignPerson.name = @"ls";处崩溃,报野指针错误。

    原因:weakPerson初始化时(1)在堆上开辟内存空间存放weakPerson对象,栈上开辟内存空间存放堆上的内存地址;执行到2时由于没有强引用,堆上的对象被销毁,栈上的指针指向内存地址为0的nil对象。因此2是向空对象发送对象,得到null。assignPerson执行3时和1一样,执行4时因为没有强引用堆上的assignPerson对象被销毁,但是栈上的指针指向地址没有变化,但是对应地址的对象已经销毁,才报野指针错误。

你可能感兴趣的:(笔记-iOS中级教程多线程)