iOSiOS面试

Effective Objective-C 笔记

2017-04-14  本文已影响33人  改变自己_now

1、在类的头文件中尽量少引用其他头文件

把引入头文件的时机尽量延后,减少编译时间。

2、尽量使用字面量语法字符、数组、字典、数值

 NSString *str = nil;
NSArray *arr = [NSArray arrayWithObjects:@"1",@"2",str, nil];
NSArray *arr1 = @[@"1",@"d",str];

上面例子中arr1会创建的时候碰到nil的时候崩溃,方便查找错误。arr的创建则不会崩溃。

3、多用类型常量、少用#define预处理指令

例如:我们可能用#define定义一个动画时间常量

#define AnimationDuration 0.3

预处理会替换AnimationDuration为0.3,如果所有引入这个头文件的AnimationDuration都会被替换。
替代方案:

static const NSTimeInterval KAnimationDuration = 0.3;

类型常量的的命名规则:1、如果只在.m中用则在前面加K
2、如果是其他文件引用则通常以类名做为前缀。
利用extern来定义全局常量,如定义一个test通知的名字在.m中

NSString *const VieControllerTestNotification = @"VieControllerTestNotification";

在.h文件中

extern NSString *const VieControllerTestNotification;

3、理解“对象的等同性”

a.==操作符比较的是两个指针本身,而不是所指的对象。如下例子:

    NSString *first = @"first 1";
NSString *second = [NSString stringWithFormat:@"first %d",1];

BOOL equalA = (first == second);
BOOL equalB = [first isEqual:second];
BOOL equalC = [first isEqualToString:second];
equalResult.png

打断点可以看结果,==的比较结果为NO
b.NSObject协议中有两个用于判断等同性的关键方法:

- (BOOL)isEqual:(id)object;
@property (readonly) NSUInteger hash;
QQ20170413-0.png

c.例如我们自定义一个Person类的判断等同性的方法

@interface Person : NSObject
/** firstName */
  @property (nonatomic,strong) NSString *firstName;

/** lastName */
@property (nonatomic,strong) NSString *lastName;
@end

等同性方法实现

- (BOOL)isEqualToPerson:(Person*)otherPerson {

if(self == otherPerson) return YES;

if(![_firstName isEqualToString:otherPerson.firstName]) return NO;

if(![_lastName isEqualToString:otherPerson.lastName]) return NO;

return YES;
}


- (BOOL)isEqual:(id)object {


if ([self class]  == [object class]) {
    
    return [self isEqualToPerson:(Person*)object];
}
else{

    return [super isEqual:object];
}

}

- (NSUInteger)hash {

NSUInteger firstHash = [_firstName hash];

NSUInteger lastHash = [_lastName hash];

return firstHash ^ lastHash;
}

4、以“类簇模式”隐藏细节

系统的UIButton就是一个很好的例子,调用基类方法可以创建不同类型的按钮,实际上返回的是对应不同的子类实例。

+ (instancetype)buttonWithType:(UIButtonType)buttonType;

例如我们需要设计一个公司雇员的类,公司的雇员又分很多种其中有工程师和设计师两种。

typedef NS_ENUM(NSUInteger,YJEmployeeType){

YJEmployeeTypeDeveloper,
YJEmployeeTypeDesigener
};
@interface YJEmployee : NSObject

+ (YJEmployee*)employeeWithType:(YJEmployeeType)type;

- (void)doWork;

通过类方法创建不同雇员实例,doWork则交给不能的子类实现。
开发者的doWork实现

@implementation YJDeveloper
- (void)doWork {

NSLog(@"我要写代码");
}
@end

设计师的doWork实现

@implementation YJDesiger
- (void)doWork {

NSLog(@"我要搞设计");
}
@end

5、理解消息转发机制

当调用某个对象或者类未实现的方法时会崩溃,

Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[YJMessageForword test]: unrecognized selector sent to instance 0x600000004e70'

下面是消息进行转发的流程图


messageForword.png

如果我们想让调用给未实现的方法不崩溃我们一般在前两步中进行操作。
1、在+ (BOOL)resolveInstanceMethod:(SEL)sel;中为其动态添加一个方法

+ (BOOL)resolveInstanceMethod:(SEL)sel {


return class_addMethod([self class], sel, (IMP)dynamciMethodIMP, "v@:");
}

动态方法的实现如下:

void dynamciMethodIMP (id self,SEL _cmd){

NSLog(@"我是被动态添加的测试方法");
}

2、或者在- (id)forwardingTargetForSelector:(SEL)aSelector中动态添加一个对象并动态添加一个方法

- (id)forwardingTargetForSelector:(SEL)aSelector {


Class testClass = objc_allocateClassPair([NSObject class], "YJTest", 0);

objc_registerClassPair(testClass);

id testObject = [[testClass alloc] init];


class_addMethod(testClass, aSelector, (IMP)dynamciMethodIMP, "v@:");



return testObject;
}

在这里对 class_addMethod只不过最后一个参数说明一下:第一个v-代表返回值为void ,@代表id,:代表SEL。
下图为各种符号代表的类型:

QQ20170413-1.png QQ20170413-2.png

6、方法调配

  Method originMethod = class_getInstanceMethod([NSString class], @selector(uppercaseString));

Method changeMethod = class_getInstanceMethod([NSString class], @selector(lowercaseString));

method_exchangeImplementations(originMethod, changeMethod);

7、重写description

重写description返回有意义的字符串

8、block

1、block的定义:
block用^来表示,后面加{},在括号里面就是实现代码。下面是最简单的块。

 void (^Block)() = ^ {

    NSLog(@"我是一个最简单的块");
};

block的语法类似于函数指针

returen_type (^block_name)(paramters)

下面定义返回值为int,并接受两个int参数

 int (^addBlock)(int a ,int b ) = ^(int a ,int b){

    return a + b;
};

block本身也是对象
2、全局block、栈block、堆block
定义block 的时候,其所占的内存区域是分配在栈中,也就是说block值在定义它的范围内有效。例如下面的代码是不安全的

 void(^testBlock)();
BOOL condition;
if (condition) {
    
    testBlock = ^ {
    
        NSLog(@"Block test is good");
    };
}
else{

    testBlock = ^{
    
      
        NSLog(@"un safe Block");
    };
}

testBlock();

定义在if和else中两个block分配在栈中,离开作用域之后,编译器有可能吧分配给block的内存覆写掉。所以上面的代码运行有时候正确还,有时错误。为解决此问题,可以给block对象发送copy消息,这样就是能把block从栈复制到堆内存中了。

8、线程安全少用同步锁

在OC中,如果多个线程要执行同一份代码,那么有时候会出问题。这种情况,通常使用锁来实现某种同步机制。如下面的代码,是不安全的。

   NSMutableArray *arr = [NSMutableArray array];

for (int i = 0; i<1000; i++) {
    
    dispatch_async(dispatch_get_global_queue(0, 0), ^{

            
        [arr addObject:[NSString stringWithFormat:@"%d",i]];

    
    });
    
}

解决办法:
1、 @synchronized

 @synchronized (self) {
            
           [arr addObject:[NSString stringWithFormat:@"%d",i]];
        }

这种写法会根据给定的对象,自动创建一个锁,等等待块中代码执行完毕,锁就释放。但如果滥用@synchronized (self)则会降低代码效率,因为共用同一个锁的那些同步块必须都按顺序执行。
2、NSLock加锁

   NSMutableArray *arr = [NSMutableArray array];


NSLock *lock = [[NSLock alloc] init];
for (int i = 0; i<1000; i++) {
    
    dispatch_async(dispatch_get_global_queue(0, 0), ^{

        [lock lock];
           [arr addObject:[NSString stringWithFormat:@"%d",i]];
        
        [lock unlock];
    });
    
}

3、dispatch_semaphore_t

NSMutableArray *arr = [NSMutableArray array];

dispatch_semaphore_t semaphore = dispatch_semaphore_create(1);
for (int i = 0; i<1000; i++) {
    
    dispatch_async(dispatch_get_global_queue(0, 0), ^{

    
        dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
        
        [arr addObject:[NSString stringWithFormat:@"%d",i]];
        
        dispatch_semaphore_signal(semaphore);

       

    });
    
}

4、有种简单而高效的办法就是使用“串行同步队列”来代替同步块或锁对象。(这里就不上代码了)
5、并发队列配合栅栏

@interface YJSyncObjct ()

@property dispatch_queue_t syncQueue;

@end

@implementation YJSyncObjct

@synthesize someString = _someString;

- (instancetype)init
{
self = [super init];
if (self) {
    _syncQueue = dispatch_get_global_queue(0, 0);
}
return self;
}


- (NSString*)someString {

__block  NSString *localSomeString;


dispatch_sync(_syncQueue, ^{
   
    localSomeString = _someString;
});

return localSomeString;
}

- (void)setSomeString:(NSString *)someString {

dispatch_barrier_async(_syncQueue, ^{
   
    _someString = someString;
});
}

下图为操作时序图

操作时序.png

9、利用block解决NSTimer循环引用问题

@implementation NSTimer (YJBlocksSuppot)

+ (NSTimer*)yj_scheduledTimerWithTimeInterval:(NSTimeInterval)interval block:(void(^)())block repeates:(BOOL)repeates {


return [self scheduledTimerWithTimeInterval:interval target:self selector:@selector(yj_blockInvoke:) userInfo:[block copy] repeats:repeates];
}


+ (void)yj_blockInvoke:(NSTimer*)timer {

void(^block)() = timer.userInfo;

if (block) {
    
    block();
}
}
@end

这个是iOS10以前需要写分类,iOS10以后系统已经存在该方法了

QQ20170414-0.png
上一篇 下一篇

猜你喜欢

热点阅读