- 使用什么语言开发IOS应用?
- 使用什么IDE(集成开发环境)?
- Git支持?
- Objc是什么?怎么学?
- 包与命名空间?
- objc有哪些数据类型?
- 消息
- self & super
- 变量与方法的私有性
- 类与选择器
- 函数指针
- 新建对象
包含一些自我的想法,与熟悉的java语言的比较,项目的进化旅程等.
使用什么语言开发IOS应用?
Objective-C
或Swift
,考虑到Objc比较成熟稳定,Swift则相对比较新,
因此在语言上打算先学Objc,等熟悉了再尝试Swift(这也是网上大部分人的推荐).
但对于比较新的,更简洁优雅的,从Objc的经验上催生的Swift,的确应该抱着谦虚的态度去学习.
使用什么IDE(集成开发环境)?
苹果官方出的XCode
,这个没什么好说的.
Git支持?
目前XCode新建项目时自带了初始化Git的选项,并且XCode已经集成了Git功能,比如修改的文件会显示M
,新增的文件会显示A
.
这说明Git是可行并且是官方推荐的.
对于常用简单的git操作,XCode自带的已经可以了,当然复杂的git操作可以用命令行或其它git工具进行.
只是.gitignore
文件仍然需要自己建立,至于内容,则直接参考gitignore上的Objective-C.gitignore
.
Objc是什么?怎么学?
首先看字面上的意思 – 面向对象的C,即它是C
语言的扩展,在其上添加了面向对象
(OOP)特性.
事实上,Objc是C语言的严格超集.
至于添加的面向对象特性,可以简单的概括为3点:
- 封装: 就是把
数据
与方法
打包在一起,对外隐藏具体实现. - 继承: 利用现有类的功能,无需重新编写相同的代码,对现有功能进行扩展.
- 多态: 主要包括
覆盖
与重载
,覆盖就是子类重新定义父类的方法,重载就是允许存在多个名字相同的函数,但参数不同.
但要注意的是,在Objc里并不存在重载,因为它只认函数名,不认参数类型.
包与命名空间?
不像java有包
,c++有命名空间
,Objc里没有这种机制…(听说这个点是争论比较大的)
但替代的,Objc里通常以前缀
来区分.比如官方核心的NS
前缀,像NSObject
,NSArray
等.
前缀冲突怎么办?这个没什么办法,官方建议两个字母
作为前缀的类名是为官方的库与框架准备的;第三方库应该使用三个或更多字母
来作为前缀
除了类名
,方法名
也应该加前缀(这个甚至更严重?!),否则…一旦冲突,自己cry吧.
objc有哪些数据类型?
objc是C语言的超集,那么先说说C语言有哪些数据类型:
- 基本数据类型
- 整数
- char
- short
- int
- long
- … * 浮点数
- float
- double
- … * bool
- 枚举
- void: 没有值,通常用于以下情形:
- 函数返回为空
- 函数参数为空
- 指针指向void(类型为void *)
- 派生类型
- 指针pointer
- 数组
- 结构struct
- 共用体union
- 函数function (指函数返回值类型)
数组与结构类型统称为聚合
类型.
然后objc附加了以下的数据类型:
- 派生类型
- NSValue: 这是个可以和各种基本数据类型互转的类
- NSNumber: 继承自NSValue,数字的包装类
- NSString: 这是一个类,基本上代替了char与string的使用
- NSArray: 数组
- …
- id类型: 表示任意类型(只表示对象类型,不表示基本数据类型),类型于js语言的var
objc类型封装?
objc在c的基础上,对类型作了一些封装(其实就是别名),如int
与NSInteger
,float
与CGFloat
等.
那么使用原始的类型还是封装的类型更好呢?
推荐使用封装的类型,这样可以屏蔽底层实现细节.
NSInteger
与NSNumber
的区别?
NSInteger是数据类型,而NSNumber是一个类.
比如NSArray里只能放类,因此3
这样的基础数据类型是无法放入的,而包装成@3
类后即可放入.
ps: 数据类型这块还是比较乱,后面再整理
消息
Objc使用消息(Message)来调用方法,消息格式为: [对象或者类名字 方法名字:参数序列];
,其中:
整个消息格式
称为消息表达式(Message expression)对象或类名字
称作接收器(Receiver)方法名字:参数序列
称作消息(Message)方法名字
称作选择器(Selector)或关键字(Keyword)
编译器会检查方法是否有效,如果无效会产生一个警告,但并不会阻止程序启动, 因为objc的动态性,它只有在运行期才会去查找并触发消息,这种机制带来了很大的灵活性.
self & super
self类似java里的this,super类似java里的super.
self与super都是id的指针,这两个比较特殊,可用在实例方法或类方法内, 相应的代表实例或类.
假设方法参数的名字与类实例变量名字重复,那么可以使用self->变量名
来获取到类实例变量.
变量与方法的私有性
在objc里,变量有修饰符protected
(默认),public
,private
:
- protected表示在本类与子类内可见
- public时在其它类内也可见
- private时只在本类内可见
但是,在objc里,不推荐变量使用public修饰符,即不推荐直接访问其它类的变量(那怎么访问?用方法呗).
但是objc里方法没有这种修饰符,一般我们利用objc的语言特性来实现私有化:
- 将方法从头文件中删除,只在实现文件中出现
- 使用Category
类与选择器
类似java里的反射吧,动态地获得指定的类,与动态地调用类的方法.
动态获取指定的类通过Class类型
来实现,可以用以下代码来获取Class:
//方式一
Class class = [类或对象 class];
//方式二
Class class = [类或对象 superclass];
//方式三
Class class = NSClassFromString(@"className");
获取到Class后可以当作普通的class来用,如:
Class class = NSClassFromString(@"className");
id obj = [[class alloc]init];
动态调用类的方法通过选择器
来实现,可以用以下代码来获取选择器:
//方式一
SEL sel = @selector(方法名);
//方式二
SEL sel = NSSelectorFromString(@"方法名");
可以看到,选择器只与方法的名字与参数序列有关,跟类无关,只要方法名与参数相同,那么就是相同的选择器.
当然,也可以获取选择器的方法名,如以下代码:
NSString *name = NSStringFromSelector(sel);
执行选择器:
//没有参数
[obj performSelector:sel];
//一个参数
[obj performSelector:sel withObject:param1];
//两个参数
[obj performSelector:sel withObject:param1 withObject:param2];
//更多参数
没有默认方法了,要靠其它方式实现,方式比较多也比较复杂,后面再详细研究.
以下代码可以先判断一下指定对象是否有指定的选择器,有的话再执行:
//obj是对象实例,sel是选择器
if ([obj respondsToSelector:sel]) {
[obj performSelector:sel];
}
函数指针
首先看一下C语言的函数指针:
//方式一
void (*methodName)(int); //常用
或
void (*methodName)(int x);
//方式二
void methodName(int);
或
void methodName(int x); //常用
然后是使用:
//声明func1
void func1(int x) {
}
//声明func2
void (*func2)(int);
//赋值(注意!下面两种方式是等效的)
// 方式一
func2 = &func1; //此时func1与func2的关系类似int *与int的关系
// 方式二
func2 = func1; //此时func1与func2的关系类似int与int的关系
//函数调用
// 一般方式
func2(10);
// 指针方式
(*func2)(10);
其实,上面代码中的func1是一个函数指针常量
,而func2是一个函数指针变量
.
函数指针变量既然是变量,那么也能当作参数使用,如下:
//定义函数指针类型
typedef void(*FuncType)(int);
void func1(FuncType ft, int x) {
ft(x);
}
void func2(int x) {
}
int main() {
func1(func2, 10);
return 0;
}
再来看objc里函数指针的使用:
void (*func)(id, SEL)
可以看到两个参数,id就代表调用方法的对象,SEL就是方法选择器.
再来个复杂点的,方法返回NSInteger,并且有两个NSInteger类型的参数:
NSInteger (*func)(id, SEL, NSInteger, NSInteger)
然后是一个实际的例子:
//定义一个方法
- (NSInteger) addWithNum:(NSInteger)num1 num2:(NSInteger)num2 {
return num1 + num2;
}
- (void) test {
SEL sel = @selector(addWithNum:num2:);
IMP imp = [self methodForSelector:sel];
//赋值(等号右边是一个强制类型转换)
NSInteger (*func)(id, SEL, NSInteger, NSInteger) = (NSInteger (*)(id, SEL, NSInteger, NSInteger)) imp;
//调用
func(self, sel, 2, 3);
}
上面代码中的IMP
是什么东西呢?来看看定义:
/// A pointer to the function of a method implementation.
#if !OBJC_OLD_DISPATCH_PROTOTYPES
typedef void (*IMP)(void /* id, SEL, ... */ );
#else
typedef id _Nullable (*IMP)(id _Nonnull, SEL _Nonnull, ...);
#endif
可以看到,它就是函数指针类型,并且在新的版本里,默认采用typedef void (*IMP)(void /* id, SEL, ... */ );
这个定义.
作用就是让你在使用时必须进行强制类型转换(来安全地使用),否则编译器就会报错.
如果不这么做的话,假设函数调用参数不匹配,那只能在运行期被发现.
新建对象
首先要分配内存
,使用下列格式:
id 对象名=[类名 alloc];
然后是初始化
,如果初始化时不需要参数,那么可以使用下列格式:
[对象名字 init];
如果需要参数,那么可以调相当的方法,按照objc的约定,通常这些方法以initWith
开头
此外,为了代码的简便,可以使用new来代替alloc与无参数的init,即id 对象名=[[类名 alloc]init];
与id 对象名=[类名 new];
是等同的.