(持续更新,暂未完成)
最近公司在扩招,在招聘面试过程中,发现很多同学的基础知识掌握的并不牢固,在此写下这篇博客,希望可以帮助到同学们(归纳整理后的,有任何疑问或建议欢迎随时艾特我)
这篇文章,无论你想扎实你的基本功好,面试也好,都是很有用处的,每字每句都很重要。
一、一些常见的关键词
初步了解下:成员变量中包含实例变量,实例变量是成员变量的一种特殊情况,用于类内部的,不会生成setter、getter。属性变量适用于与其他对象交互的变量。(属性不是变量)
1.setter方法和getter方法
事例1(原始写法)
- (void)setAge:(int)age; // setter方法声明
- (int)age; // setter方法声明
- (void)setAge:(int)age // setter方法实现
{
_age = age;
}
- (int)age // getter方法实现(又称之为懒加载、延迟加载,在需要的时候才加载,效率低,占用内存小,区别在于懒加载需要进行判断一下是否存在没有再去实例化,优点是代码可读性高,彼此之间独立性高,松耦合)
{
return _age;
}
事例2(改进写法)
@property int age; // 这句代码等价于声明了一个实例属性和它的setter方法和getter方法
@synthesize age; // 这句代码等价于实现setter和getter
@synthesize age = _age; // 上面哪种方式的getter方法名就是变量名,容易混淆,所以这么写就可以使用_age和使用age是一样的。
⚠️注意
@property属性的setter方法和getter方法是不能同时进行重写的,一旦全部重写,系统就不会帮你生成这个成员变量了,就会报错(不信你试试)。怎么解决这个问题呢,这就需要手动生成成员变量,然后就可以重写了或者用下面的方法(加入@synthesize age = myAge;即可):
@synthesize age = myAge;
- (void)setAge:(NSString *)age
{
myAge = age;
}
- (NSString *)age
{
return myAge;
}
2.@property和@synthesize
OC最初设定@property的作用是定义属性、声明getter、声明setter,@synthesize的作用是实现属性的setter、getter方法。在声明属性的情况下,如果重写setter、getter方法就需要把未识别的变量在@synthesize中定义,把属性的存取方法作用于变量。后来因为@property使用频繁,从Xcode4.4以后,@property独揽了@synthesize的功能,生成了私有的带下划线的成员变量,因此子类不可直接访问,但是可以通过setter、getter方法访问。那么如果想让定义的成员变量让子类直接访问,那么只能在.h文件中定义成员变量了,因为它默认是@protected。还有就是生成了setter和getter方法,如果同时重写了setter和getter方法,那么与@property生命的成员属性就不是一个成员属性了,是另外一个实例变量,而这个实例变量需要手动声明,所以会报错。
1.@property
现在我们声明属性用的最多的方式就是这个样子了。编译器会自动成生一个以下划线开头的实例变量_age,不需要手动再去写实例变量,也不需要在.m文件中写@synthesize age;这种方式还会自动为你生成setter、getter方法。
a.getter=getterName, setter=setterName,设置setter与getter的方法名
b.readwrite,readonly,设置可供访问级别
c.assign,setter方法直接赋值,不进行任何retain操作,为了解决原类型与环循引用问题
d.retain,setter方法对参数进行release旧值再retain新值,所有实现都是这个顺序
e.copy,setter方法进行Copy操作,与retain处理流程一样,先旧值release,再Copy出新的对象,retainCount为1。这是为了减少对上下文的依赖而引入的机制。 copy是在你不希望a和b共享一块内存时会使用到。a和b各自有自己的内存。
f.nonatomic,非原子性访问,不加同步,多线程并发访问会提高性能。注意,如果不加此属性,则默认是两个访问方法都为原子型事务访问。锁被加到所属对象实例级(我是这么理解的...)。
g.atomic和nonatomic用来决定编译器生成的getter和setter是否为原子操作。在多线程环境下,原子操作是必要的,否则有可能引起错 误的结果。
2.@synthesize
@synthesize 声明的属性=变量;语意:将属性的setter、getter方法作用于该变量。
@synthesize还有一个作用,就是可以指定与属性对应的实例变量,例如:@synthesize age = myAge;这样子的话我们调用self.age的时候其实是操作实例变量myAge而不是_age了。
@synthersize window = _window;(含义:属性window的存取方法是作用于_window这个变量的)
@synthersize就是生成setter和getter方法的一个关键词(自动生成setter和getter)
3.@property和@synthesize区别(声明和实现)
@property:相当于声明了setter和getter方法
@ synthesize:先去访问同名变量,如果没有定义那就先定义一个,再实现setter方法和getter方法,如果本身已经实现了,就不再实现了。
备注:@synthesize window = _window生成的是_window的setter、getter方法,而@synthesize window则生成的是window的的setter、getter方法,总之:定义的变量是根据@synthesize name = xx;来定的。
3.属性修饰词
1.retain
setter方法对参数进行release旧值,在retain新值。
retain会使对象的引用计数+1
2. release
release会使对象的引用计数-1
这里要扩充一个知识点,nil是把一个对象的指针置为空,只是切断了指针与内存中的对象的联系。而release才是真正的通知内存释放这个对象,所以nil并没有释放内存,只有release才会真正的释放内存。如果没有release就直接nil,虽然不会报错,却造成了内存泄漏,因为nil之后release就不起作用了。如果release了,没有置为nil,程序可能不会报错,但可能会发生崩溃现象,野指针Crash。所以我们应该先release然后nil。
3. autorelease
4. autoreleasepool
5.strong
指向并持有该对象,引用计数+1,引用计数为0时销毁,可以通过将变量强制赋值nil来销毁
6.weak
指向但不持有该对象,引用计数不会+1,在Runtime中对该属性进行了相关操作,无需处理,自动销毁(Runtime中专门维护了一个用于weak指针变量的Hash表,这个表key是weak指针所指向的内存地址,value是指向这个内存地址的所有weak指针,实际上就是一个数组,释放时根据对象地址获取所有weak指针地址的数组,然后遍历这个数组,把其中的数据置为nil,最后把这个对象从weak表中删除)
7. assign
主要用于修饰基本数据类型,如NSInteger,CGFloat,存储在栈中,内存无需程序员管理,setter方法直接赋值,而不进行retain操作(默认参数)
8.copy
类似Strong,copy多用于修饰有可变类型的不可变对象上,如NSString,NSArray,NSDictionary
9. __unsafe_unretain
类似weak,但是当对象被释放后,指针依然保存着之前的地址,被释放后的地址变为僵尸对象,这也就造成了野指针,容易造成Crash,访问被释放的地址就会出问题,所以说是不安全的,而weak在指向的内存销毁后可以将指针置为nil,更加安全。 setter方法进行Copy操作,与retain一样
10. atomic
为了保证多线程的情况下,编译器会自动生成一些互斥加锁代码,避免该变量的读写不同步的问题(atomic可以保证setter和getter存取的线程安全并不保证整个对象是线程安全的。比如声明一个NSMutableArray的原子属性数组,此时self.array和self.array=otherArray都是线程安全的,但是使用[self.array objectAtIndex:index]就不是线程安全的,需要用锁来保证线程安全),默认atomic
11. nonatomic
如果该对象无需考虑多线程的情况,这个属性会让编译器少生成一些互斥代码,可以提高效率
12. readonly
属性是只读的,默认是读写,只生成getter方法
13. readwrite
属性是读写的,生成setter方法和getter方法
4.属性修饰词 拓展部分
1.Copy和Strong
(1)修饰不可变的字符串
示例代码:
@property (strong, nonatomic) NSString *strStrong;
@property (copy, nonatomic) NSString *strCopy;
NSString *str = @"aaaa";
NSLog(@"str = %@ 内存地址 = %p 指针地址 = %p",str,str,&str);
self.strStrong = str;
NSLog(@"strong = %@ 内存地址 = %p 指针地址 = %p",self.strStrong,self.strStrong,&_strStrong);
self.strCopy = str;
NSLog(@"copy = %@ 内存地址 = %p 指针地址 = %p\n",self.strCopy,self.strCopy,&_strCopy);
------------------------------------------------------------------------------------------------
str = @"bbb";
NSLog(@"str = %@ 内存地址 = %p 指针地址 = %p",str,str,&str);
NSLog(@"strong = %@ 内存地址 = %p 指针地址 = %p",self.strStrong,self.strStrong,&_strStrong);
NSLog(@"copy = %@ 内存地址 = %p 指针地址 = %p\n",self.strCopy,self.strCopy,&_strCopy);
打印结果:
2019-05-05 11:11:08.840288+0800 TTT[3684:27977] str = aaaa 内存地址 = 0x108dfe098 指针地址 = 0x7ffee6e00a98
2019-05-05 11:11:08.840362+0800 TTT[3684:27977] strong = aaaa 内存地址 = 0x108dfe098 指针地址 = 0x7fe957417710
2019-05-05 11:11:08.840426+0800 TTT[3684:27977] copy = aaaa 内存地址 = 0x108dfe098 指针地址 = 0x7fe957417718
------------------------------------------------------------------------------------------------
2019-05-05 11:11:08.840575+0800 TTT[3684:27977] str = bbb 内存地址 = 0x108dfe118 指针地址 = 0x7ffee6e00a98
2019-05-05 11:11:08.840658+0800 TTT[3684:27977] strong = aaaa 内存地址 = 0x108dfe098 指针地址 = 0x7fe957417710
2019-05-05 11:11:08.840731+0800 TTT[3684:27977] copy = aaaa 内存地址 = 0x108dfe098 指针地址 = 0x7fe957417718
结论:源对象为不可变字符串而言,不论使用copy还是strong,所对应的值是不会发生变化的,strong和copy不会开辟新的内存,固不是深拷贝,属于浅拷贝。
补充:此处使用的是self.赋值,如果使用下划线结果是一样的,此处的self.是浅拷贝
(2)修饰可变的字符串
示例代码:
@property (strong, nonatomic) NSString *strStrong;
@property (copy, nonatomic) NSString *strCopy;
NSMutableString *mutableStr = [[NSMutableString alloc] initWithString:@"aaa"];
NSLog(@"mutableStr = %@ 内存地址 = %p 指针地址 = %p",mutableStr,mutableStr,&mutableStr);
self.strStrong = mutableStr;
NSLog(@"strong = %@ 内存地址 = %p 指针地址 = %p",self.strStrong,self.strStrong,&_strStrong);
self.strCopy = mutableStr;
NSLog(@"copy = %@ 内存地址 = %p 指针地址 = %p\n",self.strCopy,self.strCopy,&_strCopy);
[mutableStr appendString:@"bbb"];
NSLog(@"mutableStr = %@ 内存地址 = %p 指针地址 = %p",mutableStr,mutableStr,&mutableStr);
NSLog(@"strong = %@ 内存地址 = %p 指针地址 = %p",self.strStrong,self.strStrong,&_strStrong);
NSLog(@"copy = %@ 内存地址 = %p 指针地址 = %p",self.strCopy,self.strCopy,&_strCopy);
打印结果:
2019-05-05 11:23:18.961313+0800 TTT[3728:32476] mutableStr = aaa 内存地址 = 0x600002944030 指针地址 = 0x7ffee8f8ca98
2019-05-05 11:23:18.961510+0800 TTT[3728:32476] strong = aaa 内存地址 = 0x600002944030 指针地址 = 0x7fb61f612810
2019-05-05 11:23:18.961594+0800 TTT[3728:32476] copy = aaa 内存地址 = 0x8d76ba2dd400d6bd 指针地址 = 0x7fb61f612818
2019-05-05 11:23:18.961682+0800 TTT[3728:32476] mutableStr = aaabbb 内存地址 = 0x600002944030 指针地址 = 0x7ffee8f8ca98
2019-05-05 11:23:18.961835+0800 TTT[3728:32476] strong = aaabbb 内存地址 = 0x600002944030 指针地址 = 0x7fb61f612810
2019-05-05 11:23:18.961946+0800 TTT[3728:32476] copy = aaa 内存地址 = 0x8d76ba2dd400d6bd 指针地址 = 0x7fb61f612818
结论:数据源为可变字符串而言,使用copy申明属性,会开辟一块新的内存空间存放值,源数据无论怎样变化,都不会影响copy属性中的值,属于深拷贝。使用strong申明属性,不会开辟新的内存空间,只会引用到数据源内存地址,因此数据改变,则strong属性也会改变,属于浅拷贝。
补充:此处使用的是self.赋值,如果使用下划线,则还是“aaa”,因为@property声明属性变量时,会自动生成下划线+属性名命名的实例变量,并生成其对应的getter、setter方法(@synthesize copyyStr = _copyyStr),self.赋值时,会调用setter方法,而下划线是直接赋值,并不会调用setter方法
(3)修饰可变数组、可变字典
示例代码:
@property (copy, nonatomic) NSMutableArray *originArray_0;
@property (strong, nonatomic) NSMutableArray *originArray_1;
self.originArray_0 = [NSMutableArray arrayWithCapacity:0];
self.originArray_1 = [NSMutableArray arrayWithCapacity:0];
NSLog(@"self. originArray_0 class name %@",[self.originArray_0 class]);
NSLog(@"self. originArray_1 class name %@",[self.originArray_1 class]);
打印结果:
2019-05-05 11:33:17.006850+0800 TTT[3920:36550] self. originArray_0 class name __NSArray0
2019-05-05 11:33:17.006963+0800 TTT[3920:36550] self. originArray_1 class name __NSArrayM
结论:copy申明可变数据,初始化或赋值后,变成不可变数组,对数组执行增删会出错误,这是因为copy属性修饰后,在初始化或赋值时,会先执行copy操作,然后赋值。所以开发中对可变数组、字典使用strong属性修饰
扩展:
(1)NSArray0:不可变的数组
(2)NSArrayM:NSMutbleArray *arr4 = [NSMutbleArray array];---初始化后的可变数组类名
(3)NSArrayI:NSArray *arr2 = [[NSArray alloc]init];---初始化后的不可变数组类名
(4)NSPlaceHolderArray:NSArray *arr3 = [NSArray alloc];---alloc但未初始化init的
汇总:
浅拷贝:指针的复制,指向同一块内存。
深拷贝:内存的复制,指向不同的内存,互不干涉。
1.当原字符串是NSString时,因为其是不可变字符串,所以无论使用strong还是copy修饰,都指向原来的对象,copy操作只是做了一次浅拷贝。
2.当原字符串是NSMutableString时,strong只是将原字符串的引用计数+1,而copy则是对原字符串做了次深拷贝,从而生成了一个新对象,并且copy的对象指向了这个新对象。
所以如果字符串是NSMutableString,使用strong只会增加引用计数,但是copy会执行一次深拷贝,会造成不必要的内存浪费。而如果原字符串是NSString,strong和copy效果一样不会存在这个问题,但是我们一般声明NSString时,不希望它改变,所以一般情况下建议使用copy,这样可以避免NSMutableString带来的错误。
3.所以,在我们写代码过程中,一般不希望NSString改变,固然声明为copy,我们对NSMutalbleArray时,我们需要它改变,所以声明为Strong,Copy会让可变变为不可变的哦(很重要、牢牢记住)
2.Copy和retain
a.copy其实是建立了一个相同的对象,而retain不是
b.copy是内容拷贝,retain是指针拷贝
c.copy是内容拷贝,对于像NSString,的确是这样,但是如果copy是一个NSArray呢?这时只是copy指向了array中相对应元素的指针,这便是浅拷贝。
3.assign和retain
a.assign:简单赋值,不更改索引计数
b.assign就是直接赋值
c.retain使用了引用计数,retain引用计数+1,release引用计数-1,当引用计数为0时,dealloc函数被调用,内存被回收。
4.weak和strong
a.默认是strong,只有一种情况你需要使用weak,那就是防止循环引用,A类包含B类,B类包含了A类,这时候要使用weak。
b.声明weak的指针,指针指向的地址一旦被释放,这些指针都将被置为nil,可以有效的防止野指针。
5.点语法和下划线(self.和_)
self.:等价于[self setName:xxx],会调用setter方法(retaincount+1)
_:仅仅是对一个指针的赋值,不会调用setter方法,这就是一个简单的指针复制(retaincount不变)
6.Block
1.Block的修饰词为什么用Copy
默认情况下,Block会存档在栈中,所以block会在函数调用结束后被销毁,再调用会报空指针异常,如果用copy修饰的话,可以使其保存在堆区,它的生命周期会随着对象的销毁而结束,只要对象不销毁,我们就可以调用堆中的block。(说白了,栈区就是用完就销毁,而堆区是先用着最后在销毁)
Block的三种类型
a.NSGlobalBlock:全局静态Block,Block内部没有访问外部变量,你的Block类型就是这种类型(你的Block没有调用其他外部变量)
b.NSStackBlock:保存在栈中的Block,没有用copy去修饰并且访问了外部变量,会在函数调用结束被销毁(在MRC)
c.NSMallocBlock:保存在堆中,用copy修饰出来的Block,会随着对象销毁而销毁,只要对象不销毁,我们就可以调用的到在堆中的Block。
2.Block循环引用
7.栈区、堆区
网友评论