资料来源:腾讯课堂=>《[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提供的类都是线程不安全的,<font color=red>所有更新UI的操作都在主线程上执行</font>。例如,更新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<UITouch *> *)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__
,环境MRCint number = 3; void (^demo)() = ^{ NSLog(@"aaa %d", number); }; NSLog(@"%@", demo);
第三种block 堆Block
__NSMallocBlock__
,环境MRCint 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对象被销毁,但是栈上的指针指向地址没有变化,但是对应地址的对象已经销毁,才报野指针错误。