Jan 27

【Lrdcq的iOS入门 DAY0】OC基础

Lrdcq , 2016/01/27 21:09 , 教程 , 閱讀(3178) , Via 本站原創
Attention:本教程,不,其实是我学习review笔记,主要目的是我自己将我近一个月以来学习ios的姿势与方式用循序渐进的方式重新整理与归纳,并总结为阶梯性的知识点。教程意义不大,多半是给自己看的,所以看到我胡扯乱弹琴或者老是忘事儿请不要太在意。
本教程所有第0章节都是废话,有基础者可以跳过的,请务必注意。


认识oc - 写一个oc类 - 基础的oc内部类 - 协议与代理 - 简单的内存管理



step0.认识oc

oc是一门与我们熟识的js,java,python,c完全不同的语言。不对!首先我们知道的oc以c为基础的一门消息类动态语言,所以:

1.它是无缝兼容c的,也就是说我们可以面向对象的编写一切,同时在需要的时候,也能退回到面向过程的写法(oc核心库当然均为c函数),这为我们编写代码带来了极大的方面。

2.它的方法调用均为消息的形式发出,方法体实际可以理解为消息的订阅者。那么到底有没有订阅者收到消息很多情况下程序并不是那么关心。这种机制可以避免程序崩溃,简化很多错误判断逻辑。

3.它是一门真正的动态语言,不像java的面向对象需要非常严谨的继承,公有私有使用,在oc中我们甚至可以做到替换掉ios的sdk方法,这为我们编写代码带来了极大的方面。

4.还有一点值得一提的是,拆开了看,oc就是一个c语言的语法糖,oc编译的实质是把oc转换为c语言再用c编译器进行编译。那么,我们就很容易了解到一个看起来高大上的oc特性在c语言层面到底是怎样实现的,深刻理解oc的运作原理,并且可以针对c实现进行代码优化和改进。

那么,这些特点一句话就是,oc非常灵活,方便。好!

step1.写一个oc类

和传统的c/cpp系列语言一样,我们写一个类习惯在一个.h文件中写好接口(@interface)并且在.m中完成实现代码(@implementation)。当然,灵活性超级强的oc其实可以把这两样都塞到一个m文件中,就当它是内部类吧(其实还是有方法访问的)。

一个很普通的接口看起来是这样的:
@interface MyClass : NSObject

@property(strong, nonatomic,readonly) UILabel *oc_object;
@property(strong, nonatomic) NSString *oc_string;
@property(assign, nonatomic) int c_number;
@property(assign, nonatomic) NSInteger number;

- (void) method;
- (NSString*) methodWithArg:(NSInteger) number;
+ (NSString*) methodWithArg:(NSInteger) number1 andArg:(NSInteger) number2;

@end

好吧,事实上一点也不普通,我们来看看都有些啥:

首先,我们定义了myclass,继承自NSObject,oc中所有的类都应该继承自这个东西。这一行后面还可以用<>包起来这个类需要实现的协议(@protocol)(协议这个概念类似于java中的interface,下文中讲解)。接下来是4个实例属性和3个方法。

属性前面用property包裹了一些特定的设定,包括内存引用设定(strong:强应用/weak:弱引用,assign:直接赋值/copy:释放旧的持有新的对象/retain:释放旧对象并且用新对象代替内外的两份持有),原子性(nonatomic/atomic:是否保持读写是线程安全),只读(readonly/readwrite),具体gettersetter设置等。后面就是普通的数据类型+属性名称了(拓展参看:https://www.pupboss.com/property-and-synthesize/)。可以注意到的是oc对象都是*开头的,没错,oc对象就是一个指针,具体是怎么回事儿日后详细说,记住oc对象是指针就可以了。欸,为何NSInteger不是呢?点开NSInteger看看,fuck,原来NSInteger就是一个int或者long啊,那就可以理解了。

然后看看方法的语法。方法是以减号或者加号开头的,其中减号是实例方法,而加号是静态方法。然后后面紧跟着的是方法名,如果携带参数的话,一般以msg1:data1 msg2:data2的形式进行传递。这就是消息类语言的特点嘛。

欸等等,为何属性并没有分是非是静态属性呢?嗯,oc里并没有静态属性,如果要静态存储东西,使用c里面的static直接写在外面就可以辣。

那么这只是接口,我们来看看类的实现:
@implementation MyClass{
    NSString *_inner_str;
    NSInteger _inner_number;
}
- (void) inner_method{
    _inner_number = 1;
    self->_inner_number = 1;
}
- (void)setOc_string:(NSString *)oc_string{
    self.oc_string = oc_string;
    self->_oc_string = oc_string;
    _oc_string = oc_string;
}
- (void) method{
    
}
- (NSString*) methodWithArg:(NSInteger) number{
    return @"";
}
+ (NSString*) methodWithArg:(NSInteger) number1 andArg:(NSInteger) number2{
    return @"";
}
- (instancetype) initWithNumber:(NSInteger) number{
    if (self = [super init]){
        _inner_number = number;
    }
    return self;
}
@end

首先,@implementation加接口名我们就开始实现一个类了。紧接着,我们用大括号包着的一个代码块我们可以声明一些内部的属性。由于不用被外部设置,当然我们也不用去设定到底内存怎么管理之类的了。一般我们习惯于内部属性用_开头命名以保障与外部属性对内的映射名统一(外部属性如oc_object在实现中调用时即为_oc_object)。

然后开始实现方法,首先暴露给外面那3个方法肯定需要我们确确实实的去实现,这没话说。然后我们实现了一个内部方法inner_method,其中展示了调用内部属性的两种方法(并没有什么区别)。然后重写了一个叫setOc_string的方法,没错,这是一个重写的方法。在接口中声明的属性都会自动生成对应的getter或者setter方法,命名是get/set加上大写开头的属性名,xcode语法提示一下就很明显了。这个方法里,我描写了3个调用外部属性的方法,其中用.和->的区别在于,前者可能的话会通过gettersetter进行操作,而后者是直接操作(所谓c写法)。

在末尾,我还加上了一个- (instancetype) initWithNumber的方法,没错,这是一个自定义的构造器,其中的写法是一般的构造器的标准写法,即调用父级的构造器尝试完成构造,如果成功获得self,并完成接下来自定义的工作,并把自己返回回去。在非构造器中调用super的构造器xcode会抛出错误,而xcode判断它是不是构造器的方式是判断名字是不是init加大写字母开头的...也是醉了。

好,那么我们使用这个类就很简单了:
MyClass *obj = [[MyClass alloc] initWithNumber:12];
obj.oc_string = @"hello";
[obj method];
[obj methodWithArg:12];

一般一个oc类通过类似于[[MyClass alloc] initXX]的方法调用出来,按逻辑分别是分配内存并且执行类基础的构造方法。如果构造方法只是一个init的话,也可以替换为[MyClass new],不过这个写法太特立独行了一般建议避免使用。

调用一个属性当然就用点语法就可以了,一般情况下我们并不推荐使用->语法。调用方法的语法则与其他语言完全不同:用中括号包着并调用消息名:参数。当然如果认同oc中方法的定义方式的话,这种调用方式也没什么不能理解的了。

详细参看:http://www.cnblogs.com/mjios/archive/2013/04/06/3002814.html

setp2.基础的oc内部类

oc之所以是oc而不是c的一个拓展库,除了面向对象的语法糖之外,很重要的一点就是丰富的数据类型支持与封装。在c中处理字符串麻烦吧,不怕,我们有NSString了,处理链表麻烦吧,不怕我们有NSArray了,处理hash表麻烦吧,不怕我们有NSDictionary了。

oc中常用切最重要的数据封装一共有四种,分别是NSNumber,NSString,NSArray,NSDictionary,当然,还有NSData之类的封装。前4者在oc中也有对应的语法糖可以快速建立和使用:

- NSNumber可以通过@1,@2.3这种方式建立,它本身可以装入所有c中的基本类型和常见拓展类型(入布尔)。使用其中的数据需要通过不同类型的不同对应方法来取出来。常见的使用与处理数据的方式如下:
NSNumber *num1 = @1.2;
int c_num = 2;
NSNumber *num2 = [[NSNumber alloc] initWithInt:c_num];
float out_float = [num1 floatValue];
int out_num = [num2 intValue];
if ([num1 isEqualToNumber:num2]){
    NSLog(@"equal!");
}

- NSString可以通过@"this is a string"来建立,这样的一个字符串对象就拥有和其他语言非常相似的方法了。要注意的是oc中的字符串是个确确实实的普通对象,没办法使用加号等语法糖操作,要拼接字符串需要使用格式化字符串方法等。大概常用使用情况下:
char *c_str = "123456";
NSString *str1 = @"str1";
NSString *str2 = [[NSString alloc] initWithString:str1];
NSString *str3 = [[NSString alloc] initWithCString:c_str encoding:NSASCIIStringEncoding];
NSString *str4 = [[NSString alloc] initWithFormat:@"str:%@ nsnum:%@  num:%d",str3,@12,12];

其中格式化输出中,用%@来表示一个NSObject对象,其他的格式和咋们的printf等标准格式输出方法一样咯。

- NSArray可以通过@[a,b,c,d,e]这种方式来建立,其中的数据必须是NSObject对象,这也是使用NSNumber的主要原因,c类型数据没法直接丢进去,需要封一层。使用数据的话,和其他语言一样array[x]就可以把数据取出来了。大概常用方法如下:
NSArray *arr1 = @[@1,@2,@3,@4];
NSArray *arr2 = [[NSArray alloc] initWithObjects:@1,@2,@3,@4,nil];
NSInteger len = [arr2 count];
NSNumber *num = arr2[len-1];

另外还有sorted排序相关的方法,filtered筛选相关的方法也很常用,查文档一看即可知道。

- NSDictionary可以通过@{@"key":a,key,b}来使用。显然,字典中的数据是key:value形式的,一般来说key是一个字符串,当然我们明知这是一个hash表,所以key可以是任意NSObject,当然,value也可以是任意的NSObject咯。使用其中的数据,当然,hash[key]这样取出来就可以了

另外,NSString,NSArray,NSDictionary是三个类型的静态/线程安全的版本,是初始化之后不能被修改的,如果要使用可修改的,他们有子类NSMutableString,NSMutableArray,NSMutableDictionary可以使用。

有了这四个类型,我们使用oc就和别的语言的基本功能几乎没区别了,稍有语言基础的同志已经可以随手拿着oc编写业务逻辑了。

step3.协议与代理

在java中,类与类之间的通信常用接口(interface)与实现来完成。一摸一样的东西,搬到oc中,他们是协议(protocol)与代理(delegate)。编写一个协议是那么容易,比如这是一个协议:
@protocol MyProtocol
- (void)actionA;
@optional
- (void)actionB:(NSInteger) number;
- (void)actionC;
@end

我们定义协议MyProtocol,它没有继承自任何其他协议。其中actionA方法是必须要实现的,另外两个方法则是可选的。于是,让我们刚才的MyClass实现这个协议,MyClass声明改为:
@interface MyClass : NSObject <MyProtocol>
//

同时在@implementation中至少实现actionA方法。同java一样,一个类是可以实现多个协议并且方法互补的。那怎么与别的类通信呢。比如MyClass要和YourClass通信,在这种情形下,YourClass提供了这个MyProtocol并且MyClass实现了这个协议。此时我们看到YourClass的公有属性中,有这么一个东西:
@property (nonatomic, weak, nullable) id <MyProtocol> delegate;
//

一个叫delegate的东西,其中要注意的是,它是弱引用,当然可以为空,同时,它的类型是id也就是任意NSObject类型,标示是帮助xcode识别的,没有代码意义。那么,我们可以从中获得信息:delegate是可为空的,同理于protocol可以有不实现的,得益于oc的消息机制,在YourClass调用[delegate actionC]无论是delegate为空或者actionC压根没有实现,都不会发生什么不好的事情。同时,由于实现协议的有可能是任意对象,所以delegate实际就是一个id类型,也就是一个任意指针就可以了,当然,还是要保障它不是一个野指针否则还是会gg的。同时之所以要弱引用,一方面要保障各自的生命周期正常运行不受通信干扰,另外也大大的避免了循环引用。

那么最后,一般在MyClass单方面持有了YourClass的情况下,只需要设置_yourClass.delegate = self就可以实现需要的单向通信了。更加复杂的通信可以由多重协议,或者delegate传递之类的方法去实现。当然实际项目中还有更好的方式,日后再提。

参看:http://rongchengfei.blog.51cto.com/6269699/1089601

step4.简单的内存管理

一般情况下我们并不用关心内存管理,这得益于苹果在ios的新版引入的内存自动回收池的功能(@autoreleasepool),在这个范围内alloc出来的对象,在它的引用计数变为0的时候自动free掉这个对象。

因此,一般情况下,在引用计数的内存管理下,我们只需要注意循环引用问题就可以避免内存泄漏问题了。而避免循环引用,我们就要在适当的时候使用弱引用(在类的公共属性上加上内存控制属性标示),再者就是block了。

简单的说一下block,在其他动态语言中,它就是一个匿名函数,匿名回调函数,lambda表达式,java8中的Function,c中的函数指针(参看http://www.jianshu.com/p/abb1eafeb068)。无论怎么理解,它就是把一个函数当一个变量储存起来,再在适当的时候调用。可以理解,如果要把一个函数延迟调用,函数中调用到的上下文环境中的变量指针肯定会被储存起来。这样,如果block中使用了上下文中的变量,就会产生循环引用的问题。即环境持有了block,而block持有了环境中的部分变量。如何解决呢?使用__weak和__strong把环境中的变量设置弱引用代理,并且在block内部再把副本strong起来使用,就可以无缝进行弱引用调用了。如果引入了ReactiveCocoa库,可以使用宏@weakify();和@strongify();进行非常方便的弱引用化操作,类似于:
@weakify(self);
[a dosomething:^(id x) {
    @strongify(self);
  [self dosomething];
}];

内存管理的背后到底做了什么,参看资料http://www.cnblogs.com/kenshincui/p/3870325.html

掌握了以上4个关键基本信息,对于一位有过编程经历的同志来说,oc基本使用已被拿下,用oc刷题编写业务逻辑代码已经完全没问题了,那下一页,我们讲正式进入ios的开发中。
logo