写在开头
工作这么些年,被面试和面试他人都有经历不少了,个人感觉过程中除了技术考察外态度也很重要,诚恳、谦虚、自信、上进这些应该是一个优秀面试者该有的特质!回答问题要抓住重点,不知为不知即可,尽量别紧张,交流也是一种拓宽知识面、收获新知识的方式,往往面试官对你的提点会有所帮助。
刷刷面试题
面试之前还是要做足准备的,除了对项目要加深熟悉外,刷一刷热门面试题也是有用的。以下为我的收录(皆可搜到,自刷方可熟记):
1.什么情况使用weak关键字,相比assgin有什么不同
weak使用场景有delegate代理属性、Xib/Storboard的IBOutlet视图属性,weak属性是指在setter设置方法里无保留新值、释放旧值操作,当指向的对象被销毁时,会自动置空(=nil),而assgin只是对纯量类型(scalar type,例如CGFloat、NSInteger)的简单赋值操作。
2.这个写法会出什么问题:@property (copy) NSMutableArray *array;
可变数组copy后会变成不可变数组,再对其添加、删除、修改元素时,会因为找不到对应方法而崩溃;
使用atomic会在setter方法中加入锁机制严重影响性能。
3.如何让自己的类用copy修饰符?如何重写带copy关键字的 setter?
自定义类要支持copy方法,需要声明该类遵从NSCopying协议,并实现其协议方法- (id)copyWithZone:(NSZone *)zone
,举例如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
| @implementation MKUser<NSCopying> @property (nonatomic, copy) NSString *username; @property (nonatomic, assgin) NSUInteger age; @property (nonatomic, strong) NSMutableSet *friends; - (instancetype)initWithUsername:(NSString *)username age:(NSUInteger)age { if(self = [super init]) { _username = [username copy]; _age = age; _friends = [[NSMutableSet alloc] init]; } return self; } - (id)copyWithZone:(NSZone *)zone { MKUser *copy = [[[self class] allocWithZone:zone] initWithUsername:_username age:_age]; copy->_friends = [_friends mutableCopy]; return copy; } - (id)deepCopy { MKUser *copy = [[[self class] alloc] initWithUsername:_username age:_age]; copy->_friends = [[NSMutableSet alloc] initWithSet:_friends copyItems:YES]; return copy; } ... @end
|
重写copy语义的setter方法如下:
1 2 3 4 5
| - (void)setUsername:(NSString *)username { if (_username != username) { _username = [username copy]; } }
|
4. @property 的本质是什么?ivar、getter、setter 是如何生成并添加到这个类中的
@property本质是结构体(struct),runtime对@property的定义如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| typedef struct objc_property *objc_property_t; typedef struct { const char *name; const char *value; } objc_property_attribute_t; typedef struct property_t *objc_property_t; struct property_t { const char *name; const char *attributes; };
|
1 2 3 4 5 6 7 8
| @property (nonatomic, copy) NSString *string; unsigned int count = 0; objc_property_t *props = class_copyPropertyList([TestClass class], &count); NSLog(@"count %u, %s", count, property_getAttributes(*props)); # "count 1, T@"NSString",C,N,V_string"
|
实测结果中T表示类型、C表示copy、N表示nonatomic、V表示实例变量(_ivar)。
ivar、getter、setter 是如何生成并添加到这个类中的?
属性定义好后,编译器会在编译期自动合成(autosynthesis)存取方法(setter和getter),并且添加对应名称的实例变量(_ivar),其中可通过@synthesize指定不同的实例变量名称。
实现流程:每增加一个属性,系统都会在成员变量列表(ivar_list)中添加一个成员变量的描述,在方法列表(method_list)添加setter和getter方法的描述,在属性列表(prop_list)添加一个属性的描述,然后计算该属性在对象中的偏移量,然后给出setter与getter方法对应的实现,在setter方法中从偏移量的位置开始赋值,在getter方法中从偏移量开始取值,为了能够读取正确字节数,系统对象偏移量的指针类型进行了类型强转。
5.@protocol 和 category 中如何使用 @property
协议(protocol)中使用@property是希望遵从此协议的对象实现setter和getter方法;
分类(category)中使用@property需要用到runtime的关联对象(associated object)。举个例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| @interface NSObject (AssociatedObject) @property (nonatomic, strong) id associatedObject; @end #import <objc/runtime.h> @implementation NSObject (AssociatedObject) @dynamic associatedObject; - (void)setAssociatedObject:(id)object { objc_setAssociatedObject(self, @selector(associatedObject), object, OBJC_ASSOCIATION_RETAIN_NONATOMIC); } - (id)associatedObject { return objc_getAssociatedObject(self, @selector(associatedObject)); } @end
|
6.runtime如何实现weak属性
系统将以weak指针指向对象的内存地址为key,所有weak指针为value的键值对存入一个全局weak引用表中,当这个对象被销毁时,会从weak表中搜出以key为键的所有weak指针,并遍历将weak指针指向nil对象。runtime代码摘录如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67
| id objc_initWeak(id *location, id newObj) { ... } template <bool HaveOld, bool HaveNew, bool CrashIfDeallocating> static id storeWeak(id *location, objc_object *newObj) { ... newObj = (objc_object *)weak_register_no_lock(&newTable->weak_table, (id)newObj, location, CrashIfDeallocating); ... } struct SideTable { ... weak_table_t weak_table; ... }; * The global weak references table. Stores object ids as keys, * and weak_entry_t structs as their values. */ struct weak_table_t { weak_entry_t *weak_entries; size_t num_entries; uintptr_t mask; uintptr_t max_hash_displacement; }; * Registers a new (object, weak pointer) pair. Creates a new weak * object entry if it does not exist. * * @param weak_table The global weak table. * @param referent The object pointed to by the weak reference. * @param referrer The weak pointer address. */ id weak_register_no_lock(weak_table_t *weak_table, id referent_id, id *referrer_id, bool crashIfDeallocating) { ... } * Called by dealloc; nils out all weak pointers that point to the * provided object so that they can no longer be used. * * @param weak_table * @param referent The object being deallocated. */ void weak_clear_no_lock(weak_table_t *weak_table, id referent_id) { ... if (*referrer == referent) { *referrer = nil; } ... }
|
7.@property中有哪些属性关键字?/ @property 后面可以有哪些修饰符?
原子性:atomic(默认)、nonatomic
使用atomic,会在setter方法中加锁,其方案是自旋锁(spinlocks),而不是@synchronized(self)
,其原因是自旋锁更快,对于属性设置这种相对快的操作更适合。在runtime中的实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| void objc_setProperty(id self, SEL _cmd, ptrdiff_t offset, id newValue, BOOL atomic, signed char shouldCopy) { ... reallySetProperty(self, _cmd, newValue, offset, atomic, copy, mutableCopy); } static inline void reallySetProperty(id self, SEL _cmd, id newValue, ptrdiff_t offset, bool atomic, bool copy, bool mutableCopy) { ... spinlock_t& slotlock = PropertyLocks[slot]; slotlock.lock(); oldValue = *slot; *slot = newValue; slotlock.unlock(); ... }
|
读/写权限:readwrite(默认)、readonly
内存管理语义:assgin、strong、weak、unsafe_unretained、copy
unsafe_unretained属性对象被销毁时,其指针不会被指向nil对象,因此如果再调用这个销毁对象的方法,是会报错崩溃的。
方法名:getter=<name>
、setter=<name>
getter=<name>
样式举例:
1
| @property (nonatomic, copy, getter=theNewOne) NSString *newOne;
|
不常用:nonnull、nullable、null_resettable
nonnull表示不可为nil
nullable表示可为nil
null_resettable表示setter nullable而getter nonnull
8.@synthesize和@dynamic分别有什么作用?
@synthesize表示如果没有手动实现setter和getter,编译器会自动帮你合成,并添加相应类型的实例变量;
@dynamic表示setter和getter自行实现,声明@dynamic使编译器不会警告,但运行时如果setter和getter方法不存在并被调用的话会报错崩溃。
9.用@property声明的NSString(或NSArray,NSDictionary)经常使用copy关键字,为什么?如果改用strong关键字,可能造成什么问题?
使用copy会拷贝出一个新的不可变对象,这样保证了本对象不会有被改变的风险,如果使用strong声明,当这个指针指向的是一个可变对象时,这个对象假如在外部改变了,内部也会受影响。举例说明:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| @property (nonatomic, strong) NSString *string; NSMutableString *mutableString = [[NSMutableString alloc] init]; self.string = mutableString; [mutableString appendString:@" hello monki!"]; NSLog(@"\nmutableString(%p)=%@\n_string(%p)=%@", mutableString, mutableString, _string, _string); self.string = [mutableString copy]; [mutableString appendString:@" hello MengQi Yang!"]; NSLog(@"\nmutableString(%p)=%@\n_string(%p)=%@", mutableString, mutableString, _string, _string); # mutableString(0x608000262f00)= hello monki! # _string(0x608000262f00)= hello monki! # mutableString(0x608000262f00)= hello monki! hello MengQi Yang! # _string(0x600000234780)= hello monki!
|
延伸理解下深拷贝和浅拷贝:
对非集合类、不可变(immutable)对象操作,copy是指针拷贝,mutableCopy是内容拷贝,而对非集合类、可变(mutable)对象操作,都是内容拷贝。代码示例:
1 2 3 4
| [immutableObject copy] [immutableObject mutableCopy] [mutableObject copy] [mutableObject mutableCopy]
|
对集合类、不可变对象操作,copy是指针拷贝,mutableCopy是内容拷贝,而对集合类、可变对象操作,都是内容拷贝。注意:集合对象内容拷贝仅限于对象本身,对象元素仍然是指针拷贝。代码示例:
1 2 3 4
| [immutableSet copy] [immutableSet mutableCopy] [mutableSet copy] [mutableSet mutableCopy]
|
10. @synthesize合成实例变量的规则是什么?假如property名为foo,存在一个名为_foo的实例变量,那么还会自动合成新变量么?
规则如下:
1.如果指定了实例变量名称,会生成指定名称的实例变量@synthesize foo = _foo
2.如果这个变量已经存在了就不再生成了
1 2 3 4
| @interface TestClass : NSObject { NSObject *_foo; } @end
|
3.如果没指定实例变量名称,会生成属性同名的实例变量@synthesize foo
假如 property 名为 foo,存在一个名为 _foo 的实例变量,那么还会自动合成新变量么?
不会,示例如下:
1 2 3 4 5 6 7 8
| @interface TestClass () { NSString *_string; } @property (nonatomic, copy) NSString *string; @property (nonatomic, copy) NSString *_string; @end # Warning:Auto property synthesis will not synthesize property '_string' because it cannot share an ivar with another synthesized property
|
11.在有了自动合成属性实例变量之后,@synthesize还有哪些使用场景?
场景包括:
1.重写setter和getter时
2.重写只读属性的getter时
3.protocol中定义的属性
4.父类中重载的属性
示例如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41
| @interface BaseClass : NSObject @property (nonatomic, copy) NSString *string4; @end #import "BaseClass.h" @interface TestClass: BaseClass @end @protocol TestProtocol <NSObject> @property (nonatomic, copy) NSString *string3; @end @interface TestClass ()<TestProtocol> @property (nonatomic, copy) NSString *string1; @property (nonatomic, copy, readonly) NSString *string2; @property (nonatomic, copy) NSString *string4; @end @implementation TestClass @synthesize string1 = _string1; @synthesize string2 = _string2; @synthesize string3 = _string3; @synthesize string4 = _string4; - (void)setString1:(NSString *)string1 { ... } - (NSString *)string1 { ... } - (NSString *)string2 { ... } ... @end
|
12.objc中向一个nil对象发送消息将会发生什么?
在Objective-C中,发送消息直到运行时才会与方法执行绑定,编译器会将消息表达式转换为对objc_msgSend
的调用,例如[receiver message]
对应 objc_msgSend(receiver, selector)
,如果消息带参数则为objc_msgSend(receiver, selector, arg1, arg2, ...)
。发送消息的关键在于每个类或实例中都包含两个重要的元素即指向元类或所属类的指针和类的方法列表(存放着方法的名称与指针之间的映射),当消息被发送给对象时,会根据对象的isa指针找到其所属类,在该类的方法列表中去查找方法名称(selector),如果没找到就会去父类的方法列表中查找,直到NSObject,其间只要定位到selector,就会调用表中对应的方法执行。为了加快这个过程,系统会在这些方法被使用时缓存其名称和指针,而且每个类都有独立的缓存,它包含了继承的和自身的方法。在搜索方法列表前,程序会首先在接收对象的类的方法缓存中检查,如果在其中,那么发送消息也慢不了直接调用函数多少。
按上面的理解,如果receiver为nil的话,一开始便无法按isa找到所属类,并不会报什么错,objc_msgSend
的返回值由消息返回类型决定。
13.objc中向一个对象发送消息[obj foo]和objc_msgSend()函数之间有什么关系?
按12题的理解,发送消息[obj foo]会被转换为调用objc_msgSend(obj, @selector(foo))
。使用命令clang -rewrite-objc obj.m
将Objective-C编译为C++,以下为结果:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| #import <Foundation/Foundation.h> @interface Obj : NSObject @end @implementation Obj - (void)foo { } @end int main(int argc, char * argv[]) { @autoreleasepool { Obj *obj = [[Obj alloc] init]; [obj foo]; return 0; } }
|
1 2 3 4 5 6 7 8 9 10 11 12
| ... int main(int argc, char * argv[]) { { __AtAutoreleasePool __autoreleasepool; Obj *obj = ((Obj *(*)(id, SEL))(void *)objc_msgSend)((id)((Obj *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("Obj"), sel_registerName("alloc")), sel_registerName("init")); ((void (*)(id, SEL))(void *)objc_msgSend)((id)obj, sel_registerName("foo")); return 0; } } ...
|
14.什么时候会报unrecognized selector的异常?
当调用某对象的某个方法,而该对象并没有实现这个方法时,会报”unrecognized selector”异常。接12题的理解,当在最顶层父类依然找不到对应的方法时,程序在运行时会崩溃并抛出异常,在此之前,runtime提供了三次拯救程序的机会:
Dynamic Method Resolution
首先runtime会调用+resolveInstanceMethod:
或者+resolveClassMethod:
,只要在其中提供一个方法实现,并返回YES,runtime会重启一次消息发送的过程。
1 2 3 4 5 6 7 8 9 10 11
| void anotherFoo(id obj, SEL _cmd) { NSLog(@"Doing another foo"); } + (BOOL)resolveInstanceMethod:(SEL)selector { if(selector == @selector(foo:)) { class_addMethod([self class], selector, (IMP)anotherFoo, "v@:"); return YES; } return [super resolveInstanceMethod:selector]; }
|
Message Fast Forwarding
如果resolve
返回NO,runtime会调用-forwardingTargetForSelector:
,通过它可将消息转发给另一个对象。
1 2 3 4 5 6
| - (id)forwardingTargetForSelector:(SEL)selector { if (selector == @selector(foo:)) { return alternateObject; } return [super forwardingTargetForSelector:selector]; }
|
只要返回的不是nil和self,整个消息发送过程就会重启,当然发送对象会变成你返回的那个对象,否则会继续Normal Fowarding。之所以叫Fast,是为了区分下一步的转发,因为这一步不会创建任何新的对象,而下一步会创建一个NSInvocation对象,所以相对更快点。
Message Normal Forwarding
如果Fast Forwarding没有新的对象返回,runtime会先发送methodSignatureForSelector:
消息。如果其返回nil,runtime会发出-doesNotRecognizeSelector:
消息,程序崩溃。如果返回一个方法签名,runtime会创建一个NSInvocation对象并发送-forwardInvocation:
消息。
1 2 3 4 5 6 7
| - (NSMethodSignature *)methodSignatureForSelector:(SEL)selector { NSMethodSignature *signature = [super methodSignatureForSelector:selector]; if (!signature) { signature = [alternateObject methodSignatureForSelector:selector]; } return signature; }
|
NSInvocation对象是用来存储和转发消息的,可配置selector、target以及参数(self、_cmd分别占用索引0、1)等信息。所以可在-forwardInvocation:
中修改NSInvocation对象,然后发送-invokeWithTarget:
消息。
1 2 3 4 5 6 7
| - (void)forwardInvocation:(NSInvocation *)anInvocation { if ([alternatedObject respondsToSelector:anInvocation.selector]) { [anInvocation invokeWithTarget:alternatedObject]; } else { return [super forwardInvocation:anInvocation]; } }
|
15.一个objc对象如何进行内存布局?(考虑有父类的情况)
在Objective-C中,每一个objc对象都是一个类的实例,每个对象都有一个名为isa的指针,指向该对象的所属类,这个类记录了实例变量列表、实例方法列表等等,其中的superclass指针所要描述的是继承关系,继承链中最终指向的根父类是NSObject,而NSObject的superclass指向nil。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72
| ... #include <objc/objc.h> ... @interface NSObject <NSObject> { Class isa OBJC_ISA_AVAILABILITY; } ... ... - (id)init { return _objc_rootInit(self); } ... id _objc_rootInit(id obj) { return obj; } ... ... typedef struct objc_class *Class; struct objc_object { Class isa OBJC_ISA_AVAILABILITY; }; typedef struct objc_object *id; ... ... struct objc_class : objc_object { Class superclass; ... struct old_ivar_list *ivars; struct old_method_list **methodLists; ... } ... ... struct objc_object { private: isa_t isa; public: Class ISA(); Class getIsa(); ... } ...
|
由上面摘录的runtime代码,也可以看出类其实也是一个对象,其必然也含有一个isa指针,指向的是元类(metaclass),元类保存有类方法列表,当一个类方法被调用时,会在其元类的类方法列表中查找selector,如果没有,则该元类会通过superclass去其父类查找,直到NSObject。元类也是一个对象,那么元类的isa指针又指向哪里呢?为了设计上的完整,所有元类的isa指针都会指向根元类(root metaclass即NSObject的元类),而根元类的isa指针指向自己,这样就形成一个闭环。下面这张图比较直观的描述了isa和继承的关系:
16.一个objc对象的isa的指针指向什么?有什么作用?
按15题的理解,指向它的类对象,从方法列表中查找selector。
17.下面的代码输出什么?
1 2 3 4 5 6 7 8 9 10 11
| @implementation Son : Father - (id)init { self = [super init]; if (self) { NSLog(@"%@", NSStringFromClass([self class])); NSLog(@"%@", NSStringFromClass([super class])); } return self; } @end
|
都输出Son,此题考察的是对self和super的理解,我们都知道self是指向当前调用方法的这个类的实例的指针,由此很多人会想当然的认为super应该是指向父类的实例的指针,而其实不然,在Objective-C中super实质是一个编译器标示符,和self一样指向同一个消息接收者,不同点在于super会告诉编译器,调用方法时,要去父类中查找,而不是本类。通过命令clang -rewrite-objc test.m
编译为C++代码关键处如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
| #import <Foundation/Foundation.h> @interface Father : NSObject @end @implementation Father @end @interface Son : Father @end @implementation Son - (id)init { self = [super init]; if (self) { NSLog(@"%@", NSStringFromClass([self class])); NSLog(@"%@", NSStringFromClass([super class])); } return self; } @end ... static id _I_Son_init(Son * self, SEL _cmd) { self = ((Son *(*)(__rw_objc_super *, SEL))(void *)objc_msgSendSuper)((__rw_objc_super){(id)self, (id)class_getSuperclass(objc_getClass("Son"))}, sel_registerName("init")); if (self) { NSLog((NSString *)&__NSConstantStringImpl__var_folders_5p_yqf_zqt55f9_qf32zwnjnrkc0000gn_T_test_08005e_mi_0, NSStringFromClass(((Class (*)(id, SEL))(void *)objc_msgSend)((id)self, sel_registerName("class")))); NSLog((NSString *)&__NSConstantStringImpl__var_folders_5p_yqf_zqt55f9_qf32zwnjnrkc0000gn_T_test_08005e_mi_1, NSStringFromClass(((Class (*)(__rw_objc_super *, SEL))(void *)objc_msgSendSuper)((__rw_objc_super){(id)self, (id)class_getSuperclass(objc_getClass("Son"))}, sel_registerName("class")))); } return self; } ...
|
从上面代码,我们发现[self class]被转换为objc_msgSend,runtime中定义为
1 2 3 4 5 6
| ... OBJC_EXPORT id objc_msgSend(id self, SEL op, ...) OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0); ...
|
self作为第一参数传进去,[super class]被转换为objc_msgSendSuper,runtime中定义为
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| ... struct objc_super { __unsafe_unretained id receiver; #if !defined(__cplusplus) && !__OBJC2__ __unsafe_unretained Class class; #else __unsafe_unretained Class super_class; #endif }; ... OBJC_EXPORT id objc_msgSendSuper(struct objc_super *super, SEL op, ...) OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0); ... ... - (Class)class { return object_getClass(self); } ...
|
objc_super作为第一参数传入objc_msgSendSuper,self作为结构体objc_super的第一个成员,第二个记录的是当前类的父类。
所以,当调用[self class]时,实际是调用obj_msgSend,第一个参数是Son类型的实例,然后在Son类对象的方法列表中去找class方法,没有则去父类Father中找,也没有,最终会在NSObject类中找到,而class的实现就是返回self的类,故输出Son。而当调用[super class]时,实际是调用objc_msgSendSuper,会先构造objc_super结构体,self是其第一个成员,第二个是(id)class_getSuperclass(objc_getClass("Son"))
返回Father类,接着从Father类对象的方法列表中查找class方法,没有,最终也会在NSObject类中找到,class返回的是objc_super->receiver即self的类,故也输出Son。
18.runtime如何通过selector找到对应的IMP地址?(分别考虑类方法和实例方法)
结合15题理解,实例方法是从对象所属类的方法列表中查找selector并获得对应的IMP指针的,类方法则是从所属类的元类的方法列表中查找selector并获得对应的IMP指针的。
19.使用runtime Associate方法关联的对象,需要在主对象dealloc的时候释放么?
不需要,根据WWDC 2011, Session 322, 36:22中发布的内存销毁时间表:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| 1.调用release,引用计数为0 * 对象正在被销毁,生命周期即将结束 * 新的__weak弱引用是不允许的,并且旧的将指向nil * 调用[self dealloc] 2.子类调用dealloc * 最底层子类调用dealloc * 非ARC代码手动释放实例变量 * 继承链上的父类调用dealloc 3.NSObject调用dealloc * 仅仅调用Objc runtime的object_dispose()函数 4.object_dispose() * 调用C++实例变量的销毁函数 * 调用ARC实例变量的release * 清除关联对象的引用 * 清除__weak对象的引用 * 调用free()
|
可以看到关联对象会在被NSObject的dealloc方法调用的object_dispose()函数中释放。
20.objc中的类方法和实例方法有什么本质区别和联系?
类方法:
1.类方法属于元类对象
2.类方法只能被类对象调用
3.类方法中的self是类对象
4.类方法可以调用其他的类方法
5.类方法中不能访问实例变量
6.类方法中不能直接调用实例方法
实例方法:
1.实例方法属于类对象
2.实例方法只能被实例对象调用
3.实例方法中的self是实例对象
4.实例方法中可以访问实例变量
5.实例方法中可以调用类方法
此处可跳转第二篇iOS面试题收录(二)
参考资料
《招聘一个靠谱的iOS》面试题参考答案(上)
objc-runtime源码
Objective-C对象模型及应用 by 唐巧
Objective-C中的元类(meta class)是什么?
Objective-C Runtime by Glow 技术团队博客