iOS完全自学手册——[三]Objective-C语言速成,利用Objective-C创建自己的对象
1.前言
上一篇已经介绍了App Delegate、View Controller的基本概念,除此之外,分别利用storyboard和纯代码创建了第一个Xcode的工程,并对不同方式搭建项目进行了比较。这一篇文章,我准备为大家介绍一下奇葩的Objective-C语言的语法和使用。这篇文章主要讲Objective-C的理论概念。
2.学习目标
2.1 Objective-C语言面向对象的特性
与C++类比学习,声明定义一个MyObject类,并创建这个类的实例。
2.2 Objective-C语言常见语法概念概览
介绍类,方法,分类,扩展,协议,消息机制,动态特性selector,block,ARC等概念
本文关于Objective-C整理总结的内容比较多,不需要完全掌握,混个眼熟就成。
3.奇葩的Objective-C语言
1)对于已经有开发经验的开发者而言,初次接触Objective-C的时候觉得根本不能接受,甚至有种畏惧,oc奇葩的语法简直颠覆了自己的世界观,因为出现了莫名其妙的符号,如, + - [ ]。但是,不要怕,我们慢慢来了解这到底是毛线。从名字即可看出,Objective-C是一个面向对象的编程语言,也就是它具有继承、封装、多态的语言特性。
2)那对于没有任何编程语言的童鞋,如何理解面向对象的编程呢?
对于没有任何编程经验的童鞋,我想这样解释,要弄清楚两个问题:i)编程到底在做什么?ii)什么是面向对象的编程?
i)通俗的语言讲,编程就是在和计算机说话,用计算机明白的语言跟他交流,让他帮你做事。(就好像你跟外国人交流一样,你想跟人家交朋友,总得用人家懂得语言吧~)
ii)“面向对象编程”,首先只是一个名词,实质是一种抽象的概念,主要是将一类待处理的数据及数据对应的操作集合化,写(封装)在一起。比如,一个人的属性有,身高、体重、年龄、性别,人可以做的事情有吃饭、睡觉、打豆豆~所有具有这些特征的都可以称之为人这一类。这便是面向对象的编程。(如果看到这里还不明白,那还是要多写程序,慢慢接受了新的概念有了固化的思维模式,就能理解并应用了)
学习的时候可以多种语言比较学习。我选择用C++和Objective-C进行类比,来声明定义一个MyObject类,比较异同点,了解各自面向对象编程的方法。(为何选择C++?因为,1.C++有指针的概念;2.C++是面向对象的语言。另,指针就是内存的地址。)
3.1 先看一下C++中声明定义类是如何编写的
3.1.1 C++程序源码
- #include <iostream>
- using namespace std;
- class MyObject {
- public:
- MyObject();
- ~MyObject();
- void display();
- void setValue(int x);
- int getIndex();
- int getKey();
- static void classMethod();
- private:
- int index;
- int key;
- };
- MyObject::MyObject() {
- cout<<"construct"<<endl;
- }
- MyObject::~MyObject() {
- cout<<"destruct"<<endl;
- }
- void MyObject::setValue(int x) {
- this->index = x;
- this->key = <<(x+);
- }
- void MyObject::display() {
- cout<<"index == "<<this->index<<endl;
- cout<<"key == "<<this->key<<endl;
- }
- int MyObject::getKey() {
- return this->key;
- }
- int MyObject::getIndex() {
- return this->index;
- }
- void MyObject::classMethod() {
- cout<<"this is static (or class) method"<<endl;
- }
- int main(int argc, const char * argv[]) {
- // insert code here...
- std::cout << "Hello, World!\n";
- MyObject *object = new MyObject();
- MyObject::classMethod();
- object->setValue();
- object->display();
- int c = object->getIndex() + object->getKey();
- object->MyObject::~MyObject();
- printf("c = %d\n", c);
- MyObject object2;
- object2.setValue();
- object2.display();
- return ;
- }
3.1.2 控制台打印信息
Hello, World!
construct
this is static (or class) method
index == 3
key == 32
destruct
c = 35
construct
index == 2
key == 16
destruct
Program ended with exit code: 0
3.1.3 代码解释:
以上代码利用C++声明定义了MyObject类
1)定义了公有方法方法:
- 构造函数MyObject()
- 析构函数~MyObject()
- 打印index和key的方法display()
- 设置私有变量方法setValue(int x)
- 获得index值方法getIndex()
- 获得key方法getKey()
- 类方法classMethod()
2)定义了私有成员变量:
- index和key
3.1.4 特别注意
- 构造函数是创建实例时调用的,在这个方法中可以为实例进行初始化操作;
- 析构函数是销毁实例对象是被系统调用的,这里可以将对象中的指针对象都delete掉;
- 不管是否使用new关键字创建对象,系统都会自动调用构造函数和析构函数,构造函数实质是为对象在内存分配一个空间,存储这个对象的数据,而析构函数则是将这个对象占用的内存释放掉。
- 使用new关键字创建类的实例时,系统在堆内存为该实例动态分配了一块内存区域(实际上等同于调用的C语言的malloc方法为变量动态分配内存),调用对象的方法(或成员变量)使用“->”符号;
- 不使用new关键字创建类的实例时,系统在栈内存为该实例分配一块内存区域,调用对象的方法和变量须使用“.”符号。
- 类方法是静态方法,是在类装载的时候装载的。(但是要特别注意,类的静态变量是该类的对象所共有的,即是所有对象共享变量。所以建议尽量少用静态变量。尽量在静态方法中使用内部变量。)
3.2 再看一下Objective-C是如何定义一个MyObject类的
3.2.1 创建新项目
选择OS X中的Command Line Tool,创建好工程后,按住command+N键创建Cocoa Touch Class,创建一个名为MyObject的NSObject的子类。
这样就创建了OS X的命令行程序,以用于简单演示如何声明定义Objective-C的类
3.2.2 程序源码
1)main.m文件:
- #import <Foundation/Foundation.h>
- #import "MyObject.h"
- int main(int argc, const char * argv[]) {
- @autoreleasepool {
- // insert code here...
- NSLog(@"Hello, World!");
- MyObject *object = [[MyObject alloc] initWithIndex:];
- [object display];
- // object = nil;
- }
- return ;
- }
2) MyObject.h
- #import <Foundation/Foundation.h>
- @interface MyObject : NSObject
- - (instancetype)initWithIndex:(NSInteger)index;
- - (void)display;
- + (void)classMethod;
- @end
3) .m文件
- #import "MyObject.h"
- @interface MyObject()
- @property (assign, nonatomic) NSInteger index;
- @property (assign, nonatomic) NSInteger key;
- @end
- @implementation MyObject
- @synthesize index = _index;
- @synthesize key = _key;
- - (instancetype)initWithIndex:(NSInteger)index {
- self = [super init];
- if (self) {
- NSLog(@"init");
- self.index = index;
- }
- return self;
- }
- - (void)setIndex:(NSInteger)index {
- _index = index;
- self.key = <<(index+);
- }
- - (NSInteger)index {
- return _index;
- }
- - (NSInteger)key {
- return _key;
- }
- - (void)display {
- NSLog(@"index == %ld \n key == %ld", (long)_index, (long)_key);
- }
- + (void)classMethod {
- NSLog(@"this is static (or class) method");
- }
- - (void)dealloc {
- NSLog(@"dealloc");
- }
- @end
3.2.3 控制台打印结果
2016-01-10 17:55:50.401 CompileOC[20792:1052939] Hello, World!
2016-01-10 17:55:50.402 CompileOC[20792:1052939] init
2016-01-10 17:55:50.402 CompileOC[20792:1052939] index == 3
key == 32
2016-01-10 17:55:50.402 CompileOC[20792:1052939] dealloc
Program ended with exit code: 0
3.2.4 代码解释
1) 在main.m文件中创建了MyObject的实例并调用display方法。
2)在头文件中声明了三个公有方法,
- (instancetype)initWithIndex:(NSInteger)index;
- (void)display;
+ (void)classMethod;
3).m文件中
- 创建了一个MyObject类的扩展,在扩展中声明了两个NSInteger的属性,属性的特性为assign和nonatomic
- 重写了父类中的实例(减号)init方法,并为index属性赋值
- 实现了index属性的setter方法
- 实现了index和key属性的getter方法
- 创建了打印index和key的方法
- (void)display
- 创建了类方法
+ (void)classMethod;
- 重写了父类的dealloc方法
4)与C++代码做对比
- 在C++中可以创建没有父类的类,但是在Objective-C中创建的类都继承自NSObject或其子类。不选择父类的话是不能创建Objective-C的类的。
- 在C++中调用方法或变量用“->”或“.”符号,而在Objective-C中调用方法是[],而且顺序不是从左至右,而是从内到外。
- 在C++中只有当new来创建对象时,才会动态分配内存;
- 而在Objective-C中创建的所有类的实例对象都是动态分配的,从创建Objective-C实例的方式
类名 *实例名 = [[类名 alloc] init];
也可以看出,Objective-C创建实例都是动态分配内存,实际上也是等同于调用的C语言的malloc方法为变量动态分配内存。 - 即在Objective-C中所有的对象都是指针。
- 在C++中声明公有方法用public关键字表明, 私有方法用private表明。而在Objective-C中声明公有方法只要在头文件的
@interface
@end
之间就可以了,而私有方法不需要在头文件中声明。 - 补充,在Objective-C中可以使用@private @public @protected @package 编译器指令来设置实例变量的访问限制。
实例变量的声明方式为,@implementation {
@private
int index;
int key;
}
@end
注意实例变量和类的属性并不是一回事,属性和实例变量的本质区别是,属性无法直接访问对象的内部状态,但是可以利用setter和getter方法进行访问,而在setter和getter方法中还可以包含其他逻辑,比如,本文章中
- (void)setIndex:(NSInteger)index
方法,不单为实例变量_index赋值,还用setter的简洁方式self.key = 1<<(index+2);
为实例变量_key赋值。那_index和_key实例变量是谁声明的呢?这是Xcode编译器自己生成的:
@synthesize index = _index;
@synthesize key = _key;
上面的代码使用了 @synthesize 编译指令,让编译器分别为属性index和key自动创建实例变量_index和_key。即,自动为属性创建对应实例变量的方法是
@synthesize 属性名 = 实例变量名;
事实上,当没有实现setter方法时,@synthesize指令不写,编译器也可以为属性自动创建实例变量,编译器自动为属性创建的变量名默认为_属性名,在调试的时候可以设置变量观察。- 注意本程序是在Xcode7中创建运行的,Xcode5之后创建工程时就默认ARC模式了,现在基本项目都是自动引用计数的模式编写的。老项目中的使用手动管理的需要做如下的设置:
在Targets的Build Phases选项下Compile Sources下选择要不使用ARC编译的文件,双击它,输入-fno-objc-arc
即可 - 在项目开发中,我认为属性远比实例变量好用,大可以只声明属性,而不声明实例变量。
4.Objective-C语言常见语法概念
4.1 基本语法概念
4.1.1类元素
1)实例变量
声明方法:
@implementation {
//声明实例变量
}
@end
实例变量是是类封装的数据
2)属性
@property (特性) 类型 属性名;
属性是Objective-C提供的访问对象共享状态的机制,编译器可以自动生成访问实例变量的方法。Xcode自动创建的实例变量默认变量名为“_属性名”
3)类的声明(接口)
@interface MyObject : NSObject
@end
通用的如下:
@interface 类名 : 父类名
@end
说明:
- @interface 和 @end 指令之间的区域只能声明属性或方法,不能有具体的实现过程。
- 如果在头文件中的接口声明的属性或方法,其实就是公有的属性和方法,可以直接访问。
4)类的实现
@implementation {
//实例变量的声明
}
//属性和方法的定义
@end
说明:
- 方法的实现过程必须写在 @implementation 和 @end 之间
5)类的方法:分为类方法和实例方法
- 声明:
[+或-](返回类型)方法名:(参数类型)参数1 参数名:(参数类型)参数2 ... 参数名:(参数类型)参数n;
- 实现:
[+或-](返回类型)方法名:(参数类型)参数1 参数名2:(参数类型)参数2 ... 参数名n:(参数类型)参数n {
//code here
return 返回值;
}
- 调用:
[消息接收者(即类的实例对象) 方法名:1 参数名2:2 ... 参数名n:n];
说明:
- 方法的声明其实就是方法部分实现除了{}以外的代码Objective-C的方法定义方式很奇葩,刚开始不好接受,这其实是Objective-C消息机制一种体现。
- 在其他语言中,方法的书写方式是函数式的,类似于数学中的函数f(x,y,z)。
- 在Objective-C中调用方法时向消息的接收者(类的实例对象)发送一条消息,书写形式为[消息接收者 消息方法名:参数1 参数名2:参数2],所有调用方法都是动态的,根据发出消息的方法名来动态查找对应的指针。感兴趣的话可以参考罗朝辉大神的博客 http://blog.csdn.net/kesalin/article/details/6689226
“不同的类可以拥有相同的 selector,这个没有问题,因为不同类的实例对象performSelector相同的 selector 时,会在各自的消息选标(selector)/实现地址(address) 方法链表中根据 selector 去查找具体的方法实现IMP, 然后用这个方法实现去执行具体的实现代码。这是一个动态绑定的过程,在编译的时候,我们不知道最终会执行哪一些代码,只有在执行的时候,通过selector去查询,我们才能确定具体的执行代码。”
多参数的声明、实现和调用。例如,我在MyObject中定义一个多参数的请求方法:
- - (void)requestDataWithIndex:(NSInteger)index forKey:(NSInteger)key completion:(void(^)(NSError *error, id response))completion { //code here
- NSError *responseError = nil;//assume it is responsed from server
- NSDictionary *responseData = @{@"message": @"OK"};
- if (completion != nil) {
- if (responseError != nil) {
- completion(responseError, nil);
- } else {
- completion(nil, responseData);
- }
- }
- }
- - (void)requestDataWithIndex:(NSInteger)index forKey:(NSInteger)key completion:(void(^)(NSError *error, id response))completion { //code here
说明:
- - (void)requestDataWithIndex:(NSInteger)index forKey:(NSInteger)key completion:(void(^)(NSError *error, id response))completion直接可以复制到@interface 和 @end 之间作为方法的声明
- 注意方法中第三个参数是block变量,这个后面会稍作介绍,先了解概念混个眼熟,以后慢慢细琢磨。
- block是一块代码块,实质是闭包,程序运行时,block{}内的不会立即执行,即非顺序执行的,是异步执行的,所以最适合选为做网络异步请求的回调函数,功能类似于ajax。
6)类的扩展
其实就是写在类.m文件中的接口,只不过与类.h中接口相比,在.m文件中声明的实例变量、属性、方法外部不能访问,即实现了方法、属性的私有化。例如MyObject.m文件中
@interface MyObject()
@property (assign, nonatomic) NSInteger index;
@property (assign, nonatomic) NSInteger key;
@end
这段代码就是扩展,并且声明的index和key属性外部不能访问,只能在类的内部访问。
7)分类
分类的目的主要是为了扩展类的方法,比如MyObject类我添加一个NY的分类,创建方法如下:
- command+N 选择iOS Source中的Objective-C File,Next
- 填写File(分类名),选择category,class 填写MyObject,然后点Next,再点Create创建
- 头文件:
- #import "MyObject.h"
- @interface MyObject (NY)
- @end
- 实现文件
- #import "MyObject+NY.h"
- @implementation MyObject (NY)
- @end
说明:
- 分类中只可以声明和定义方法,不可以声明类接口属性或实例变量。因为已经引用了原来类的头文件会报重复声明类的语法错误。
/Users/.../CompileOC/CompileOC/MyObject+NY.m:10:1: Duplicate interface definition for class 'MyObject'
8)协议
- 协议的声明定义
@protocol NYProtocol <NSObject>
@optional
- (void)nyOptionalProtocalMethod;
@required
- (void)nyRequiredProtocalMethod;
@end
通用:
@protocol 协议名 <NSObject>
@optional
//可选择实现的方法
@required
//必须实现的方法
@end
- 类遵从协议的方法
@interface MyObject() <NYProtocol>
@property (assign, nonatomic) NSInteger index;
@property (assign, nonatomic) NSInteger key;
@end
说明:
- 协议的定义可以写写在类的头文件中也可以写在类的实现文件中,或者也可以创建一个单独的协议头文件,但是必须要写在遵从该协议的类接口之前,否则会报语法错误
- @optional指令标明的方法,遵从该协议的类不须要实现,但@required指令标明的必须实现
- 只有类的接口后面可以跟<协议> @interface MyObject() <NYProtocol>
- 协议方法类似于C++中的虚函数,不需要具体实现,只需声明方法名,在子类中实现具体方法。协议方法实际是为了实现C++多继承的目的,子类可以根据需要实现方法,比如,tableview的delegate协议和datasource协议,根据具体的UI需要来实现对应的协议方法。
4.2 ARC(Automatic Reference Counting)自动引用计数
ARC是Objective-C提供的内存管理方式,系统根据对象被引用的次数计次,当引用计数为零时,自动释放该对象所占有的内存。而非系统自动管理内存的方式,称为MRR(Manual Retain Release),老的项目代码中仍会有手动管理内存的代码。你会看到 retain 、 release、 autorelease 、dealloc这样的消息(方法)代码,以用来管理内存。但是在ARC模式下并不能手动编写这些代码,当引用一个对象时,即这个对象引用计数加一,当这个对象中某个指针,例如,本文中的 object = nil 时,引用计数减一,引用计数为零时,系统自动释放对象所占用的内存。
但在Xcode5之后,创建项目时无法限定非ARC模式,而且「基本现在开发的项目都使用ARC」,所以,对于初学者来说,手动内存管理就暂时不需要更深入的了解了。但如果,希望能实现ARC与MRR的混编译,那么就需要在Targets的Build Phases选项下Compile Sources下选择要不使用ARC编译的文件,双击它,输入-fno-objc-arc
即可实现ARC和MRR混编。
5.小结
本篇文章主要介绍了 Objective-C 的基本语法,希望初学者读完之后能对 Objective-C 编程语言有一点基本的了解,打消一部分 Objective-C 这种陌生的编程语言的畏惧感,从而能继续往下学。
关于学习的方法,我还想说几句。我认为,人接触新鲜事物总会有种恐惧感,适当的恐惧感反而会更有意思,恐惧感太强烈会阻碍接受新的概念。接受新观念肯定是需要一定时间,因为原有的观念已经给思维产生一个定式,新观念可能和旧观念有冲突的地方,但肯定两者之间也存在一定联系。找出两者的共同点,能快速理解新概念。所以,我觉得学习一种新事物,首先要接受,慢慢的就适应了,不急~咱还年轻~
6.博文推荐
http://blog.csdn.net/kesalin/article/details/8155245
7.结语
十分感谢萌萌哒乐乐(https://medium.com/@HiSuneBear)给我提的排版建议及文章评价,让我有更强烈的动力把文章写好(这篇文章写的太长,截至2016-01-11 17:41:55 我写了快三天。所以,最后有点累,不想写了,但是乐乐童鞋让我的小宇宙又重新燃了
iOS完全自学手册——[三]Objective-C语言速成,利用Objective-C创建自己的对象的更多相关文章
- iOS完全自学手册——[一]Ready?No!
1.前言 今天开始我会不定期写一些iOS自学的相关文章.毕竟,自己是自学开始,知道自学有哪些坑,知道自学对于开发欠缺什么,此外,加上现在的实际开发经验,希望能给自学的iOS开发者一些建议. 2.Rea ...
- iOS完全自学手册——[二]Hello World工程
1.前言 写第二篇文章之前,我在想第二篇应该怎么写?后来觉得与其写Objective-C语言的相关语法,不如直接开始写个小项目.语法简单入门即可.因为,即便把语法看的很深到最后还是不一定能做项目,运用 ...
- 孤荷凌寒自学python第三十四天python的文件操作对file类的对象学习
孤荷凌寒自学python第三十四天python的文件操作对file类的对象学习 (完整学习过程屏幕记录视频地址在文末,手写笔记在文末) 一.close() 当一个file对象执行此方法时,将关闭当前 ...
- [XMPP]iOS聊天软件学习笔记[三]
今天做了好友界面,其实xmpp内部已经写好很多扩展模块,所以使用起来还是很方便的 开发时间:五天(工作时间) 开发工具:xcode6 开发平台:iOS8 XMPP框架:XMPPFramework gi ...
- <iOS>other linker flags[转]
包含静态库时候需要在Target的Other linker flags里面加上值:-objC,-all_load,-force_load 对于64位机子和iPhone OS应用 解决方法是使用-all ...
- [caffe]linux下安装caffe(无cuda)以及python接口
昨天在mac上折腾了一天都没有安装成功,晚上在mac上装了一个ParallelDesktop虚拟机,然后装了linux,十分钟就安装好了,我也是醉了=.= 主要过程稍微记录一下: 1.安装BLAS s ...
- iOS10收集IDFA,植入第三方广告[终结]--ADMob
[PS: 前段时间,公司做ASO推广,需要在应用中收集IDFA值,跟广告平台做交互!于是有了这个需求--] 1.首先,考虑了一下情况(自己懒 -_-#),就直接在首页上写了一个Banner,循环加载广 ...
- Java基础 之软引用、弱引用、虚引用 ·[转载]
Java基础 之软引用.弱引用.虚引用 ·[转载] 2011-11-24 14:43:41 Java基础 之软引用.弱引用.虚引用 浏览(509)|评论(1) 交流分类:Java|笔记分类: Ja ...
- CSU 1642 Problem B[难][前缀和]
Description 已知两个正整数a和b,求在a与b之间(包含a和b)的所有整数的十进制表示中1出现的次数. Input 多组数据(不超过100000组),每组数据2个整数a,b.(1≤a,b≤1 ...
随机推荐
- hadoop群集 启动
###注意:严格按照下面的步骤 .5启动zookeeper集群(分别在itcast04.itcast05.itcast06上启动zk) cd /itcast/zookeeper-/bin/ ./zkS ...
- kafka系列 -- 基础概念
kafka是一个分布式的.分区化.可复制提交的发布订阅消息系统 传统的消息传递方法包括两种: 排队:在队列中,一组用户可以从服务器中读取消息,每条消息都发送给其中一个人. 发布-订阅:在这个模型中,消 ...
- Django入门与实践 17-26章总结
Django入门与实践-第17章:保护视图 Django 有一个内置的视图装饰器 来避免它被未登录的用户访问: 现在如果用户没有登录,将被重定向到登录页面: 现在尝试登录,登录成功后,应用程序会跳转到 ...
- [operator]Ubuntu server 18 设置静态IP
root@ubuntu-MesosMaster-Marathon:~# cat /etc/netplan/-cloud-init.yaml # This file is generated from ...
- Internal Server Error - http code 500
Eror Example 1 :
- CentOS7 Docker 安装
CentOS7 已经内置了docker ,可以直接安装 安装Docker 命令: sudo yum install -y docker 启动docker 命令: service docker st ...
- 笔记本U盘安装CentOS 7
1. 下载镜像,制作U盘安装盘,设置BIOS启动等内容网上有大量的文章,本文不再赘述. 2. 开机U盘启动后会看到这样的界面: 3. 笔记本安装CentOS最容易出问题的地方在于USB安装盘的选择,如 ...
- [JS] Ajax请求会话过期处理
对于页面来说,处理session过期比较简单,一般只需在过滤器里面判断session用户是否存在,不存在则跳转页面到登陆页即可. 对于Ajax请求来说,这个办法则无效,只能获取到登录页的html代码. ...
- python的标准库
第三方库放的位置:E:\python\Lib\site-packages 通过命令查询:import sys print (sys.path) 标准库:E:\\python\\lib 第三方库的上一级 ...
- 转(C# 实现生产者消费者队列)
class Program { // 任务队列 static Queue<string> _tasks = new Queue<string>(); // 为保证线程安全,使用 ...