您的位置:首页 > 移动开发 > IOS开发

【IOS学习】基础知识积累

2018-03-06 11:25 302 查看

一.iOS开发之loadView、viewDidLoad及viewDidUnload的关系 

1.第一次访问UIViewController的view时,view为nil,然后就会调用loadView方法创建view2.view创建完毕后会调用viewDidLoad方法进行界面元素的初始化3.当内存警告时,系统可能会释放UIViewController的view,将view赋值为nil,并且调用viewDidUnload方法4.当再次访问UIViewController的view时,view已经在3中被赋值为nil,所以又会调用loadView方法重新创建view5.view被重新创建完毕后,还是会调用viewDidLoad方法进行界面元素的初始化

二.load和initialize的区别

@implementation Person  
// 只要程序启动就会将所有类的代码加载到内存中, 放到代码区(无论该类有没有被使用到都会被调用)  
// load方法会在当前类被加载到内存的时候调用, 有且仅会调用一次  
// 如果存在继承关系, 会先调用父类的load方法, 再调用子类的load方法  
+ (void)load  
{  
    NSLog(@"Person类被加载到内存了");  
}  
  
// 当当前类第一次被使用的时候就会调用(创建类对象的时候)  
// initialize方法在整个程序的运行过程中只会被调用一次, 无论你使用多少次这个类都只会调用一次  
// initialize用于对某一个类进行一次性的初始化  
// initialize和load一样, 如果存在继承关系, 会先调用父类的initialize再调用子类的initialize  
+ (void)initialize  
{  
    NSLog(@"Person initialize");  
}  
  
@end  
1、+load方法当类或分类添加到object-c runtime时被调用,子类的+load方法会在它所有父类的+load方法之后执行,而分类的+load方法会在它的主类的+load方法之后执行。但不同的类之间的+load方法的调用顺序是不确定的,所以不要在此方法中用另一个类。2、+load方法不像普通方法一样,它不遵循那套继承规则。如果某个类本身没有实现+load方法,那么不管其它各级超类是否实现此方法,系统都不会调用。+load方法调用顺序是:SuperClass -->SubClass --> CategaryClass。3、+initialize是在类或者它的子类接受第一条消息前被调用,但是在它的超类接收到initialize之后。也就是说+initialize是以懒加载的方式被调用的,如果程序一直没有给某个类或它的子类发送消息,那么这个类的+initialize方法是不会被调用的。4、+initialize方法和+load方法还有个区别,就是运行期系统完整度上来讲,此时可以安全使用并调用任意类中的任意方法。而且,运行期系统也能确保+initialize方法一定会在“线程安全的环境”中执行,这就是说,只有执行+initialize的那个线程可以操作类或类实例,其他线程都要阻塞等着+initialize执行完。5、+initialize方法和其他类一样,如果某个类未实现它,而其超类实现了,那么就会运行超类的实现代码。如果本身和超类都没有实现,超类的分类实现了,就会去调用分类的initialize方法。如果本身没有实现,超类和父类的分类实现了就会去调分类的initialize方法。不管是在超类中还是分类中实现initialize方法都会被调多次,调用顺序是SuperClass -->SubClass。            

三. UIWindow使用

1) 同一层级的 最后一个显示出来,上一个被覆盖2)UIWindow在显示的时候是不管KeyWindow是谁,都是Level优先的,即Level最高的始终显示在最前面。3)谁最后设置的 makeKeyAndVisible 谁就是keyWindow 其他的也会显示出来 所有的window都可以监听键盘 和点击的事件  UIView的功能 负责渲染区域的内容,并且响应该区域内发生的触摸事件

UIWindow

在iOS App中,UIWindow是最顶层的界面内容,我们使用UIWindow和UIView来呈现界面。UIWindow并不包含任何默认的内容,但是它被当作UIView的容器,用于放置应用中所有的UIView。从继承关系来看,UIWindow继承自UIView,所以UIWindow除了具有UIView的所有功能之外,还增加了一些特有的属性和方法,而我们最常用的方法,就是在App刚启动时,调用UIWindow的rootViewController(必须指定根控制器) 和 makeKeyAndVisible方法状态栏和键盘都是特殊的UIWindow。 UIWindow的主要作用有:1.作为UIView的最顶层容器,包含应用显示所有的UIView;2.传递触摸消息和键盘事件给UIView; UIWindow的层级:UIWindow的层级由一个UIWindowLevel类型属性windowLevel,该属性指示了UIWindow的层级,windowLevel有三种可取值。并且层级是可以做加减的
self.window.windowLevel = UIWindowLevelAlert+1;
Normal ,StatusBar,Alert.输出他们三个层级的值,我们发现从左到右依次是0,1000,2000,也就是说Normal级别是最低的,StatusBar处于中级,Alert级别最高。而通常我们的程序的界面都是处于Normal这个级别的,系统顶部的状态栏应该是处于StatusBar级别,提醒用户等操作位于Alert级别。根据window显示级别优先原则,级别高的会显示在最上层,级别低的在下面,我们程序正常显示的view在最底层;

四.NS_ENUM & NS_OPTIONS

从枚举定义来看,NS_ENUM和NS_OPTIONS本质是一样的,仅仅从字面上来区分其用途。NS_ENUM是通用情况,NS_OPTIONS一般用来定义具有位移操作或特点的情况(bitmask)。

五. 在任意页面,隐藏其他页面的键盘

    1.当前viewController隐藏本页面的键盘

    很容易。直接调用 [textfield resignFirstResponder]即可。

    2.目前前遇到一个需求

     ControllerA出来时候,隐藏当前top 任意view的键盘。那么可以使用这个[objc] view plain copy[[[UIApplication sharedApplication] keyWindow] endEditing:YES];  
这是有根据的,OC sdk中有一个UIView的类别,就是endEditing。可以参考UITextField.h的头文件[objc] view plain copy@interface UIView (UITextField)  
- (BOOL)endEditing:(BOOL)force;    // use to make the view or any subview that is the first responder resign (optionally force)  
@end  

3.如果不方便获取当前view

    可以使用该方法[objc] view plain copy[[UIApplication sharedApplication] sendAction:@selector(resignFirstResponser) to:nil from:nil forEvent:nil];  

六.isKindOfClass和isMemberOfClass的区别

isKindOfClass和isMemberOfClass 都是NSObject的比较Class的方法  但两个有很大区别:
  isKindOfClass来确定一个对象是否是一个类的成员,或者是派生自该类的成员
  isMemberOfClass只能确定一个对象是否是当前类的成员
  例如:我们已经成NSObject派生了自己的类,isMemberOfClass不能检测任何的类都是基于NSObject类这一事实,而isKindOfClass可以。
  [[NSMutableData data] isKindOfClass:[NSData class]]; // YES
[[NSMutableData data] isMemberOfClass:[NSData class]]; // NO

七.NSString属性什么时候用copy,什么时候用strong

由于NSMutableString是NSString的子类,所以一个NSString指针可以指向NSMutableString对象,让我们的strongString指针指向一个可变字符串是OK的。而上面的例子可以看出,当源字符串是NSString时,由于字符串是不可变的,所以,不管是strong还是copy属性的对象,都是指向源对象,copy操作只是做了次浅拷贝。当源字符串是NSMutableString时,strong属性只是增加了源字符串的引用计数,而copy属性则是对源字符串做了次深拷贝,产生一个新的对象,且copy属性对象指向这个新的对象。另外需要注意的是,这个copy属性对象的类型始终是NSString,而不是NSMutableString,因此其是不可变的。这里还有一个性能问题,即在源字符串是NSMutableString,strong是单纯的增加对象的引用计数,而copy操作是执行了一次深拷贝,所以性能上会有所差异。而如果源字符串是NSString时,则没有这个问题。所以,在声明NSString属性时,到底是选择strong还是copy,可以根据实际情况来定。不过,一般我们将对象声明为NSString时,都不希望它改变,所以大多数情况下,我们建议用copy,以免因可变字符串的修改导致的一些非预期问题。

八.NSArray 各种遍历方式

 编程中经常需要遍历collection元素,做法有标准的for循环,OC1.0的NSEnumerator和OC2.0的fast NSEnumerator。语言引入“块”这一特性后,又多出来几种新的遍历方式。    for循环大家很熟悉,很简单。但是遍历字典和set的时候就稍麻烦,for循环有一个优势,可以反向遍历。在删除数组中一个元素的时候采用反向遍历    OC 1.0的NSEnumerator是个抽象基类,定义了两个方法,供具体的子类实现
[objc] view plain copy-(NSArray*) allObjects;  
-(id) nextObject;  
    举例,遍历数字[objc] view plain copyNSArray *anArray=/*....*/;  
NSEnumerator *enumerator = [anArray objectEnumerator];  
id object;  
while((object = [enumerator nextObject]) != nil){  
    //do something   
}  
快速遍历,就是for-in语句直接上代码,这种办法比上面两种都高效,安全,简单,强烈推荐!在删除数组元素的时候,同样可以反序执行。[objc] view plain copyNSArray *anArray=/*...*/;  
for (id object in anArray){  
    //do something   
}  
//dictionary  
NSDictionary* dic = /*...*/;  
for(id key in dic){  
    id value = dic[key];  
    //do something   
}  
//set  
NSSet *aSet = /*...*/;  
for (id object in aSet){  
    //do something   
}  
//反向遍历  
NSArray *anArray=/*...*/;  
for (id object in [anArray reverseObjectEnumerator]){  
    //do something   
}  

基于块的变量方式    当前OC中,最新引入的一种做法句ishi基于块来遍历。这种做法比前三种效率都高,但是代码量比for-in多。[objc] view plain copyNSArray *anArray=/*...*/;  
[anArray enumerateObjectUsingBlock]:  
    ^(id object,NSUInter idx,BOOLBOOL *stop){  
        // do something  
        if(shouldStop){  
            *stop = yes;  
        }  
    }];  
其他collection类似

九.NSLog 格式化输出数据

%@     对象%d, %i 整数%u     无符整形%f     浮点/双字%x, %X 二进制整数%o     八进制整数%zu    size_t%p     指针%e     浮点/双字 (科学计算)%g     浮点/双字%s     C 字符串%.*s   Pascal字符串%c     字符%C     unichar%lld   64位长整数(long long)%llu   无符64位长整数%Lf    64位双字%e 是实数,用科学计数法计的


十.iOS 中几种常用的锁总结

多线程编程中,应该尽量避免资源在线程之间共享,以减少线程间的相互作用。 但是总是有多个线程相互干扰的情况(如多个线程访问一个资源)。在线程必须交互的情况下,就需要一些同步工具,来确保当它们交互的时候是安全的。
锁是线程编程同步工具的基础。iOS开发中常用的锁有如下几种:
@synchronized
NSLock 对象锁
NSRecursiveLock 递归锁
NSConditionLock 条件锁
pthread_mutex 互斥锁(C语言)
dispatch_semaphore 信号量实现加锁(GCD)
OSSpinLock (暂不建议使用,原因参见这里
链接:https://www.jianshu.com/p/1e59f0970bf5

十一.内存泄露
ARC已经出来很久了,自动释放内存的确很方便,但是并非绝对安全绝对不会产生内存泄露。导致iOS对象无法按预期释放的一个无形杀手是——循环引用。循环引用可以简单理解为A引用了B,而B又引用了A,双方都同时保持对方的一个引用,导致任何时候引用计数都不为0,始终无法释放。若当前对象是一个ViewController,则在dismiss或者pop之后其dealloc无法被调用,在频繁的push或者present之后内存暴增,然后APP就duang地挂了。下面列举我们变成中比较容易碰到的三种循环引用的情形。(1)计时器NSTimer一方面,NSTimer经常会被作为某个类的成员变量,而NSTimer初始化时要指定self为target,容易造成循环引用。 另一方面,若timer一直处于validate的状态,则其引用计数将始终大于0。先看一段NSTimer使用的例子(ARC模式):
1 #import <Foundation/Foundation.h>
2 @interface Friend : NSObject
3 - (void)cleanTimer;
4 @end

1 #import "Friend.h"
2 @interface Friend ()
3 {
4     NSTimer *_timer;
5 }
6 @end
7
8 @implementation Friend
9 - (id)init
10 {
11     if (self = [super init]) {
12         _timer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(handleTimer:)
13                                                 userInfo:nil repeats:YES];
14     }
15     return  self;
16 }
17
18 - (void)handleTimer:(id)sender
19 {
20     NSLog(@"%@ say: Hi!", [self class]);
21 }
22 - (void)cleanTimer
23 {
24     [_timer invalidate];
25     _timer = nil;
26 }
27 - (void)dealloc
28 {
29     [self cleanTimer];
30     NSLog(@"[Friend class] is dealloced");
31 }

在类外部初始化一个Friend对象,并延迟5秒后将friend释放(外部运行在非arc环境下)
1         Friend *f = [[Friend alloc] init];
2         dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 5*NSEC_PER_SEC), dispatch_get_main_queue(), ^{
4             [f release];
5         });

我们所期待的结果是,初始化5秒后,f对象被release,f的dealloc方法被调用,在dealloc里面timer失效,对象被析构。但结果却是如此:
这是为什么呢?主要是因为从timer的角度,timer认为调用方(Friend对象)被析构时会进入dealloc,在dealloc可以顺便将timer的计时停掉并且释放内存;但是从Friend的角度,他认为timer不停止计时不析构,那我永远没机会进入dealloc。循环引用,互相等待,子子孙孙无穷尽也。问题的症结在于-(void)cleanTimer函数的调用时机不对,显然不能想当然地放在调用者的dealloc中。一个比较好的解决方法是开放这个函数,让Friend的调用者显式地调用来清理现场。如下:
=======================================(2)blockblock在copy时都会对block内部用到的对象进行强引用(ARC)或者retainCount增1(非ARC)。在ARC与非ARC环境下对block使用不当都会引起循环引用问题,一般表现为,某个类将block作为自己的属性变量,然后该类在block的方法体里面又使用了该类本身,简单说就是self.someBlock = ^(Type var){[self dosomething];或者self.otherVar = XXX;或者_otherVar = ...};block的这种循环引用会被编译器捕捉到并及时提醒。举例如下,依旧以Friend类为例子:
#import "Friend.h"

@interface Friend ()
@property (nonatomic) NSArray *arr;
@end

@implementation Friend
- (id)init
{
if (self = [super init]) {
self.arr = @[@111, @222, @333];
self.block = ^(NSString *name){
NSLog(@"arr:%@", self.arr);
};
}
return  self;
}

我们看到,在block的实现内部又使用了Friend类的arr属性,xcode给出了warning, 运行程序之后也证明了Friend对象无法被析构:

网上大部分帖子都表述为"block里面引用了self导致循环引用",但事实真的是如此吗?我表示怀疑,其实这种说法是不严谨的,不一定要显式地出现"self"字眼才会引起循环引用。我们改一下代码,不通过属性self.arr去访问arr变量,而是通过实例变量_arr去访问,如下:

由此我们知道了,即使在你的block代码中没有显式地出现"self",也会出现循环引用!只要你在block里用到了self所拥有的东西!但对于这种情况,我们无法通过加__weak声明或者__block声明去禁止block对self进行强引用或者强制增加引用计数。但我们可以通过其他指针来避免循环引用(多谢xq_120的提醒),具体是这么做的: 
  对于self.arr的情况,我们要分两种环境去解决:1)ARC环境下:ARC环境下可以通过使用_weak声明一个代替self的新变量代替原先的self,我们可以命名为weakSelf。通过这种方式告诉block,不要在block内部对self进行强制strong引用:(如果要兼容ios4.3,则用__unsafe_unretained代替__weak,不过目前基本不需考虑这么low的版本)
1          self.arr = @[@111, @222, @333];
2         __weak typeof(self) weakSelf=self;
3         self.block = ^(NSString *name){
4             NSLog(@"arr:%@", weakSelf.arr);
5         };

2)MRC环境下:解决方式与上述基本一致,只不过将__weak关键字换成__block即可,这样的意思是告诉block:小子,不要在内部对self进行retain了! =========================================================(3)委托delegate在委托问题上出现循环引用问题已经是老生常谈了,本文也不再细讲,规避该问题的杀手锏也是简单到哭,一字诀:声明delegate时请用assign(MRC)或者weak(ARC),千万别手贱玩一下retain或者strong,毕竟这基本逃不掉循环引用了!
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: