Objective-C 初体验 -- 语法篇
拿到MBP也有3个礼拜了
上个礼拜总算有时间好好看了一下 Objective-C
Objective-C 可以说是一个静态语言,但又有很多动态特性.这点上感觉和ActionScript很象,不过ActionScript是纯脚本语言了
在这里也记录一下,用其他语言来类比它的一些特性和实现,会以Java为主结合C/C++,ActionScript等,方便兄弟们学习更快掌握 和 同乐
语法
Objective-C是C的超集,即在Objective-C(.m文件)里面是能完全写C的代码的,而且也能和Objective-C相互调用.
对于刚接触Objective-C的兄弟们,一般都会对其语法颇为不爽,这里列一下不爽的几点
- 参数前面还跟了个玩意儿
- self是咋回事
- 方法调用都是用[],眼花缭乱有木有
- 好多 : 冒号有木有5. 编译不报错有木有
这里就来慢慢解释
方法定义
Objective-C的方法定义是很有意思的
举个例子:
- (id)initWithTitle:(NSString *)title message:(NSString *)message;
-(减号) 代表这个方法是个实例方法
如果是+(加号) 代表这个是类方法 相当于Java的static方法(但还是有些不同的下面会讲到)
而一开始很多人会以为方法名是initWithTitle
,这个时候大家的认识就开始错误了,实际上整个方法名应该是initWithTitle:message:
这在后面使用@selector
的时候就很明确了
做个对比就是
- (id)initWithTitle:(NSString *)title error:(NSString *)message;
这个方法和上面的方法实际上是两个不同名字的方法,这个方法全名应该是initWithTitle:error:
虽然他们有相同的initWithTitle
和 相同的参数类型,但是由于message和error也是方法名的一部分所以就导致这是两个方法.
而方法里面 : (冒号)后面跟着的就是参数了 ,比如里面的title和message就在起方法实现里面作为传参被使用
Objective-C 为什么这么定义方法就是为了提高可读性,能够在方法定义的时候对参数就进行描述..
所以类似上面方法的正确看法就是
- (id)initWithTitle:(NSString *)title message:(NSString *)message;
|方法的作用 |需要title这个参数 |需要message这个参数 |
大家以后写方法的时候需要注意这种写法.
还有Objective还有Objective-C虽然实现了面向对象,但毕竟不是Objective-C++(.mm),所以Objective-C是不支持方法重载的,
比如
- (id)initWithTitle:(NSString *)title message:(NSStringessage;
- (id)initWithTitle:(NSString *)title message:(NSNumber *)message;
这两个方法同时定义会直接编译失败
方法调用
Objective-C的方法调用是用[]来表示的
比如 [self addSubview:loadingView]
;
这个就是表示调用self (self就是相当于Java中的this关键字) 的addSubview:
方法 ,并传入loadingView
这个参数
就其内部实现就是就其内部实现就是调用objc_msgSend
这个方法 来实现调用
我们来看看objc_msgSend的实现
http://www.mulle-kybernetik.com/artikel/Optimization/opti-9.html
id c_objc_msgSend( struct objc_class /* ahem */ *self, SEL _cmd, ...)
{
struct objc_class *cls;
struct objc_cache *cache;
unsigned int hash;
struct objc_method *method;
unsigned int index;
if( self)
{
cls = self->isa;
cache = cls->cache;
hash = cache->mask;
index = (unsigned int) _cmd & hash;
do
{
method = cache->buckets[ index];
if( ! method)
goto recache;
index = (index + 1) & cache->mask;
}
while( method->method_name != _cmd);
return( (*method->method_imp)( (id) self, _cmd));
}
return( (id) self);
recache:
/* ... */
return( 0);
}
可以看到方法调用并不是msg base的(当时我就被objc_msgSend
这个方法名给骗了),而是vtable是实现,并且是lazy load的
那么Objective-C的调用有那些特性呢
特性一在于Object特性一在于Objective-C的方法绑定都是在运行时确定的而不是编译时
特性二在于发消息是可以被动态解析,甚至转发的.而调用则做不到这点
即你可以向一个 对一个对象 调用其没有实现的方法.而如果你设置了其动态解析货转发,那么这个方法还是会被执行的
具体可以看
和
所以整个消息被执行过程所以整个消息被执行过程就是
- 先去找这个对象有没有被实现的这个消息
- 没有实现就去找有没有被动态解析
- 没有再看有没有被设置可以消息转发
- 最后都没有则报错
可以看到利用这些特性可以很容易的实现 动态代理
特性三因为是发消息那么这个消息就有可能不是被同步执行的,还可以被异步执行
即[self addSubview:loadingView];
还可以写成
[self performSelectorInBackground:@selector(addSubview:l) withObject:loadingView]
此时这个方法会被另外一个现场异步执行
那么我们看看NSObject(类似Java中的基类Object)的send Messages的相关方法
Sending Messages
-performSelector:withObject:afterDelay:
-performSelector:withObject:afterDelay:inModes:
-performSelectorOnMainThread:withObject:waitUntilDone:
-performSelectorOnMainThread:withObject:waitUntilDone:modes:
-performSelector:onThread:withObject:waitUntilDone:
-performSelector:onThread:withObject:waitUntilDone:modes:
-performSelectorInBackground:withObject:
可以看到performSelector可以选择不同的线程来执行,甚至还能延迟执行
不过具体会在后面的线程章节来细说
那我们再来看看类方法
比如+(id)initWithTitle:(NSString *)title message:(NSString *)message;
就是个类方法 调用的时候 类似[NSObject initWithTitle:title message:message]
这样调用
看到这个就明白类方法就是绑定在类上面的消息实现
那么这个时候在这个实现里面还是可以使用self的,只不过这个self不是指向类实例,而是类本身
那我们再说说self:
在其他语言比如Java里面this,表示的是当前实例,于实例绑定,所以在static方法里面是不能出现this的,因为没有实例.
而在Objective-C里面self不是和实例绑定,而是和消息绑定,实际上self本身也是个传递参数,只不过是隐藏的而已,所以这点上和python很想,self传递的就是这个消息绑定的对象(类本身也是对象),实际上和self一起传递的隐藏参数还有一个 就是_cmd , _cmd代表的就是消息本身即SEL
在做消息动态解析的时候就是很明显的使用这两个参数了 因为在IMP(这个就是消息的真正实现里面),最开始的两个参数就是 self和_cmd,比如
void dynamicMethodIMP(id self, SEL _cmd) {
// implementation ....
}
属性和内部变量
Objective-C的属性定义 使用@property
来实现
Objective-C的内部变量可在紧跟@interface
或@implementation
的{}中来定义
如:
@interface RemoteContext : NSObject {
@private
NSMutableDictionary *_extra;
NSMutableDictionary *_eventListener;
}
@property(retain, nonatomic) NSString *host;
@property(copy, nonatomic) NSString *api;
@property(copy, nonatomic) NSString *version;
@property(retain, nonatomic) NSObject *parameter;
@property(readonly, nonatomic) NSMutableDictionary *extra;
@property(retain, nonatomic) ClientInfo *clientInfo;
@property id <RemoteHandlerProtocol> handler;
@end
其中如
NSMutableDictionary *_extra;
和
@property(readonly, nonatomic) NSMutableDictionary *extra;
是同一份数据
_extra是对对象内部的访问
extra是对象外部来访问
实际上对于@property 的属性 就算你未为他声明内部变量
编译器也会直接帮你生成一个内部变量以_开头加上你的属性名
比如
@property(retain, nonatomic) NSString *host;
会自动帮你生成
NSString *_host;
这样的内部变量 当你能在对象内部直接访问
然后就是@synthesize关键字
如
@implementation RemoteContext {
}
@synthesize api = _api;
就是相当于帮你对api这个property生成getter和setter方法
getter方法是
-(NSString*) api;
setter方法是
-(void)setApi:(NSString *)api;
而getter和setter方法名的自动生成也是可以被自定义的在
@property(getter=getApi) NSString *api;
就会生成-(NSString*) getApi;
这样的getter方法
关于@property
里面的 其他关键字 比如 retain,assign
涉及到Objective-C的内存管理,会在后面的内存管理篇来细讲
相对于@synthesize
还有一个动态生成getter/setter的关键字@dynamic
这里@synthesize
是在编译时生成getter/setter方法而@dynamic
则是在运行时通过上面讲到的方法动态解析来生成getter/setter方法
Protocol
Protocol就是协议, Protocol可以单纯的认为就是Java中的interface
即可以预先定义好有哪些方法,需要可能被实现,而需要被遵守的类则可能需要实现这些预先定义好的方法
如:
@protocol RemoteHandlerProtocol <NSObject>
@optional
- (void)preProcess:(RemoteContext *)context;
- (void)process:(RemoteContext *)context;
- (void)processResult:(RemoteContext *)context;
- (void)response:(RemoteContext *)context;
@required
- (void)request:(RemoteContext *)remoteContext;
@end
协议RemoteHandlerProtocol
继承NSObject
这个协议
@optional
下面的方法表示可以不被实现
@required
下面的方法则必须被实现
那些可以不被实现的方法 ,需要被调用的时候怎么办的
那需要通过[self respondsToSelector:@selector(printTest)]
这个调用来判断是否存在这个方法的实现 然后再调用
而很多在定义 形参或属性或变量是 用id<Protocol>
来表示形参或属性或变量都必须是实现Protocol的才行
Category
Category的作用就是在已存在的类上为其再新加新的方法 实际上就是对已存在的类作mixin
而要用到Category中定义的方法 直接#import这个Category的头文件就可以了
Block
Block是在iOS5中才实现的语言特性,整个来说就是一种闭包的实现,我挺喜欢的.
由于iOS都是使用Protocol + delegate模式来实现事件的响应 , 导致事件的绑定和事件的响应是分开的,导致代码上下文是不连贯的
而Block的出现则可以缓解这个问题
id
id 是一个typedef
typedef struct objc_object {
Class isa;
} *id;
可以看到id是一个指针,它指向一个拥有Class isa;
的结构体
很多资料和书上都把id和NSObject * 作等价处理
但实际上还是有很多不一样的 毕竟不是typedef NSObject * id;
typedef struct objc_object {
Class isa;
} *id;
这样的定义表示,任何拥有Class
的对象结构都可以被认为是id
, 当然原始类型是不行了
id
相比NSObject
还有更神奇的就是 对id 发任何消息 都不会出现 编译错误 甚至这个消息是完全没有被实现的
即对id
作方法调用在编译时是完全不做校验的,全在运行时来动态绑定实现,当然 如果使用了id<Protocol>
还是会在编译时检验消失是否在Protocol中被定义