1.怎样打印一个结构体
CGRect rect = CGRectMake(0, 0, 320, 568);
NSString *str = NSStringFromCGRect(rect); //结构体转化为字符串
NSLog(@"%@",str);
2.保持后台长时间运行
-(void)logAction
{
NSLog(@"保持后台");
}
- (void)applicationDidEnterBackground:(UIApplication *)application {
_timer = [NSTimer scheduledTimerWithTimeInterval:1.0f target:self selector:@selector(logAction) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:_timer forMode:NSDefaultRunLoopMode];
UIApplication* app = [UIApplication sharedApplication];
__block UIBackgroundTaskIdentifier bgTask;
bgTask = [app beginBackgroundTaskWithExpirationHandler:^{
dispatch_async(dispatch_get_main_queue(), ^{
if (bgTask != UIBackgroundTaskInvalid){
bgTask = UIBackgroundTaskInvalid;
}
});
}];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
dispatch_async(dispatch_get_main_queue(), ^{
if (bgTask != UIBackgroundTaskInvalid){
bgTask = UIBackgroundTaskInvalid;
}
});
});
}
备注:beginBackgroundTaskWithExpirationHandler:是在告诉系统需要更多时间完成某件事,完成之后再通知它。
bgTask != UIBackgroundTaskInvalid是指系统响应留给我们时间
3.关于error:Cannot assign to ‘self’ outside of a method in the init family
有时候我们重写父类的init方法时不注意将init后面的第一个字母写成了小写,在这个方法里面又调用父类的初始化方法(self = [super init];)时会报错,错误信息如下:error:Cannot assign to 'self' outside of a method in the init family
原因:只能在init方法中给self赋值,Xcode判断是否为init方法规则:方法返回id,并且名字以init+大写字母开头+其他 为准则。例如:- (id) initWithXXX;
4.自定义侧滑控制器和tabarController
ReSideMenu:https://github.com/romaonthego/RESideMenu
tabarController:https://github.com/ChenYilong/CYLTabBarController
5.smartsvn客户端(version客户端类似),一些.a文件无法识别,也就无法提交到svn!
解决办法如下:
在smartsvn客户端下面view->Ignored Files 勾选上就ok(version客户端类似操作)
svn使用地址:http://blog.sina.com.cn/s/blog_7b9d64af0102vb68.html
6.实例变量在block中的使用
@interface CharacteristicViewController : UIViewController<UITableViewDataSource,UITableViewDelegate>{
@public
BabyBluetooth *baby;
NSMutableArray *sect;
__block NSMutableArray *readValueArray;
}
使用时:
[self->readValueArray addObject:[NSString stringWithFormat:@"%@",characteristics.value]];
或者
__weak typeof(self) weakSelf = self;
self.blkA = ^{
__strong typeof(weakSelf) strongSelf = weakSelf;//加一下强引用,避免weakSelf被释放掉
NSLog(@"%@", strongSelf->_xxView); //不会导致循环引用.
};
在委托问题上出现循环引用问题已经是老生常谈了,本文也不再细讲,规避该问题的杀手锏也是简单到哭,一字诀:声明delegate时请用assign(MRC)或者weak(ARC),千万别手贱玩一下retain或者strong,毕竟这基本逃不掉循环引用了!
7.一个简化appDelegate的插件
https://github.com/xareelee/XAspect
8.判断UIViewController是否正在显示
if (self.isViewLoaded && self.view.window) {
}
8.改变tableView的头部高度
CGRect newFrame = _groupHeaderView.frame;
if (newFrame.size.height ==195) return;
newFrame.size.height =195;
_groupHeaderView.frame = newFrame;
[self.tableView setTableHeaderView:_groupHeaderView];
9.在线绘制流程图
https://www.processon.com
10.static 和 extern 的使用
static: 在一个A类.m外面定义一个static NSString * staticStr = @”static”;
要在另一个B类中使用到这个变量,则需要导入A类的头文件,然后可以直接使用staticStr。
extern:在一个A类.m外面定义一个NSString * externStr = @”extern”;
要在另一个B类中使用到这个变量,则需要导入A类的头文件,然后在使用的地方加个 extern NSString *externStr 后就 可以直接使用externStr。
11. 使用CocoaPods之后,头文件无法自动补齐问题
使用CocoaPods来管理三方库,还是比较方便的,但是突然发现一个美中不足的小问题,在使用import引入文件时,不能自动补齐,需要手工copy文件名,纠结了半天:
解决办法:
Target -> Build Settings ,User Header Search Paths条目中,添加
SRCROOT或者
(PODS_ROOT),并且选择Recursive,递归搜索,然后就可以自动补齐了。
12.对OC中类的描述(转)
13.category和extension的使用
category的使用情况:
1.在不创建继承类的情况下实现对已有类的扩展。
2.简化类的开发工作(当一个类需要多个程序员协同开发的时候,Category可以将同一个类根据用途分别放在不同的源文件中,从而便于程序员独立开发相应的方法集合)。
3.将常用的相关的方法分组。
4.在没有源代码的情况下可以用来修复BUG。
5.统计埋点应用
category的特点:
1.不能通过property()添加成员变量,如果需要添加成员变量,则需要进行时重写set和get方法,否则使用时会闪退。
2.并没有什么界限分明的判定标准来作为何时用Category何时用添加子类的方法的指导。但是有以下几个指导性的建议:
如果需要添加一个新的变量,则需添加子类。
如果只是添加一个新的方法,用Category是比较好的选择。
在分类中增加的方法,会被子类所继承,而且在运行时跟其他方 法没有区别。
一般不要在分类中覆盖现有类中的方法。
extension的使用:
1.类的延展就如同时“匿名”的分类,延展中声明的方法在类本身的@implementation和它对应的@end之间实现。
2.类又是需要方法只有自己所见,我们可以通过延展的方式定义类的私有方法
两者区别:
Class Extension 和 Category 在语言机制上有着很大差别:Class Extension 在编译期就会将定义的 Ivar、属性、方法等直接合入主类,而 Category 在程序启动 Runtime Loading 时才会将属性(没 Ivar)和方法合入主类。
14.#ifdef DEBUG的使用
15.静态库和动态库
静态库:静态库在Objective-C里面以.a或者.framework作为后缀,目前开发者自己创建的库文件(Framework)其实都是以静态库的形式链接到执行文件的。链接时完整的拷贝到了可执行文件中,被多次使用就会有多份拷贝(eg:iOS8+的Extention中使用)。静态库文件一般都会比较大,因为所有要使用的数据都会被编译进去,而且如果库文件的某个函数改变了,那么就又需要重新编译新的库文件了,优点就是编译后的执行程序不需要外部的函数库支持,因为所有的函数都已经被编译进去了。(注:静态库多的话会引起包增大)
动态库:动态库在Objective-C里面以dylib或者.framework最为后缀,系统为我们提供的framework就是动态库,目前开发者是不允许使用动态库的,因为我们自己创建的库文件虽然buildSetting中的Mach-O Type设置为Dynamic Library,但是使用时直接链接到程序里面的,而不是放在服务器上进行更新,开发者如果使用动态库放在服务器上,然后动态的加载dlopen是不会通过审核的,不然Apple的审核就没有意义了。动态库在链接时不复制,程序运行时由系统动态加载到内存,系统只加载一次,多个程序间共用,节省内存,而且升级方便。
16.状态栏颜色设置
有两种方法:
一、在info.plist中,将View controller-based status bar appearance设为NO。
//白色
[UIApplication sharedApplication].statusBarStyle = UIStatusBarStyleLightContent;
二、在info.plist中,将View controller-based status bar appearance设为YES,或者没有设置。
View controller-based status bar appearance的默认值就是YES。
如果View controller-based status bar appearance为YES。
则[UIApplication sharedApplication].statusBarStyle 无效。
用下面的方法:
1、在vc中重写vc的preferredStatusBarStyle方法。
-(UIStatusBarStyle)preferredStatusBarStyle{return UIStatusBarStyleDefault;
}
2、在viewDidload中调用:[self setNeedsStatusBarAppearanceUpdate];
但是,当vc在nav中时,上面方法没用,vc中的preferredStatusBarStyle方法根本不用被调用。
原因是,[self setNeedsStatusBarAppearanceUpdate]发出后,
只会调用navigation controller中的preferredStatusBarStyle方法,
vc中的preferredStatusBarStyley方法跟本不会被调用。
解决办法有两个:
方法一:
设置navbar的barStyle 属性会影响status bar 的字体和背景色。如下。
//status bar的字体为白色
//导航栏的背景色是黑色。
self.navigationController.navigationBar.barStyle = UIBarStyleBlack;
//status bar的字体为黑色
//导航栏的背景色是白色,状态栏的背景色也是白色。
//self.navigationController.navigationBar.barStyle = UIBarStyleDefault;
方法二:
自定义一个nav bar的子类,在这个子类中重写preferredStatusBarStyle方法:
MyNav* nav = [[MyNav alloc] initWithRootViewController:vc];
self.window.rootViewController = nav;
@implementation MyNav
- (UIStatusBarStyle)preferredStatusBarStyle
{
UIViewController* topVC = self.topViewController;
return [topVC preferredStatusBarStyle];
}
17.电池信息获取
//电池的状态是可监听的
[UIDevice currentDevice].batteryMonitoringEnabled = YES;
//huoqu电池的状态
switch ([UIDevice currentDevice].batteryState) {
case UIDeviceBatteryStateUnknown:
NSLog(@"电池的状态未知");
break;
case UIDeviceBatteryStateCharging:
NSLog(@"电池正在充电");
break;
case UIDeviceBatteryStateUnplugged:
NSLog(@"电池未充电");
break;
case UIDeviceBatteryStateFull:
NSLog(@"电池电量充满");
break;
default:
break;
}
//获取当前电池的电量
NSLog(@"当前电池的电量百分比是%3.0f%%",([[UIDevice currentDevice] batteryLevel]) *100);
18.绘制圆角tableView
- (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath
{
if ([cell respondsToSelector:@selector(tintColor)]) {
if (tableView == self.tableView) {
// 圆角弧度半径
CGFloat cornerRadius = 5.f;
// 设置cell的背景色为透明,如果不设置这个的话,则原来的背景色不会被覆盖
cell.backgroundColor = UIColor.clearColor;
// 创建一个shapeLayer
CAShapeLayer *layer = [[CAShapeLayer alloc] init];
// 创建一个可变的图像Path句柄,该路径用于保存绘图信息
CGMutablePathRef pathRef = CGPathCreateMutable();
// 获取cell的size
CGRect bounds = CGRectInset(cell.bounds, 0, 0);
// CGRectGetMinY:返回对象顶点坐标
// CGRectGetMaxY:返回对象底点坐标
// CGRectGetMinX:返回对象左边缘坐标
// CGRectGetMaxX:返回对象右边缘坐标
// 这里要判断分组列表中的第一行,每组section的第一行,每组section的中间行
BOOL addLine = NO;
// CGPathAddRoundedRect(pathRef, nil, bounds, cornerRadius, cornerRadius);
if (indexPath.row == 0) {
// 初始起点为cell的左下角坐标
CGPathMoveToPoint(pathRef, nil, CGRectGetMinX(bounds), CGRectGetMaxY(bounds));
// 起始坐标为左下角,设为p1,(CGRectGetMinX(bounds), CGRectGetMinY(bounds))为左上角的点,设为p1(x1,y1),(CGRectGetMidX(bounds), CGRectGetMinY(bounds))为顶部中点的点,设为p2(x2,y2)。然后连接p1和p2为一条直线l1,连接初始点p到p1成一条直线l,则在两条直线相交处绘制弧度为r的圆角。
CGPathAddArcToPoint(pathRef, nil, CGRectGetMinX(bounds), CGRectGetMinY(bounds), CGRectGetMidX(bounds), CGRectGetMinY(bounds), cornerRadius);
CGPathAddArcToPoint(pathRef, nil, CGRectGetMaxX(bounds), CGRectGetMinY(bounds), CGRectGetMaxX(bounds), CGRectGetMidY(bounds), cornerRadius);
// 终点坐标为右下角坐标点,把绘图信息都放到路径中去,根据这些路径就构成了一块区域了
CGPathAddLineToPoint(pathRef, nil, CGRectGetMaxX(bounds), CGRectGetMaxY(bounds));
addLine = YES;
} else if (indexPath.row == [tableView numberOfRowsInSection:indexPath.section]-1) {
// 初始起点为cell的左上角坐标
CGPathMoveToPoint(pathRef, nil, CGRectGetMinX(bounds), CGRectGetMinY(bounds));
CGPathAddArcToPoint(pathRef, nil, CGRectGetMinX(bounds), CGRectGetMaxY(bounds), CGRectGetMidX(bounds), CGRectGetMaxY(bounds), cornerRadius);
CGPathAddArcToPoint(pathRef, nil, CGRectGetMaxX(bounds), CGRectGetMaxY(bounds), CGRectGetMaxX(bounds), CGRectGetMidY(bounds), cornerRadius);
// 添加一条直线,终点坐标为右下角坐标点并放到路径中去
CGPathAddLineToPoint(pathRef, nil, CGRectGetMaxX(bounds), CGRectGetMinY(bounds));
} else {
// 添加cell的rectangle信息到path中(不包括圆角)
CGPathAddRect(pathRef, nil, bounds);
addLine = YES;
}
// 把已经绘制好的可变图像路径赋值给图层,然后图层根据这图像path进行图像渲染render
layer.path = pathRef;
// 注意:但凡通过Quartz2D中带有creat/copy/retain方法创建出来的值都必须要释放
CFRelease(pathRef);
// 按照shape layer的path填充颜色,类似于渲染render
// layer.fillColor = [UIColor colorWithWhite:1.f alpha:0.8f].CGColor;
layer.fillColor = [UIColor whiteColor].CGColor;
// 添加分隔线图层
if (addLine == YES) {
CALayer *lineLayer = [[CALayer alloc] init];
CGFloat lineHeight = (1.f / [UIScreen mainScreen].scale);
lineLayer.frame = CGRectMake(CGRectGetMinX(bounds), bounds.size.height-lineHeight, bounds.size.width, lineHeight);
// 分隔线颜色取自于原来tableview的分隔线颜色
lineLayer.backgroundColor = tableView.separatorColor.CGColor;
[layer addSublayer:lineLayer];
}
// view大小与cell一致
UIView *roundView = [[UIView alloc] initWithFrame:bounds];
// 添加自定义圆角后的图层到roundView中
[roundView.layer insertSublayer:layer atIndex:0];
roundView.backgroundColor = UIColor.clearColor;
//cell的背景view
//cell.selectedBackgroundView = roundView;
cell.backgroundView = roundView;
}
}
}
19.常量定义方式
在项目中经常需要定义一些常量和一些类型,通常我们是在需要的那个类上直接定义,这样虽然快速,但是项目大的话就使得代码比较散乱,所以我们最好是定义在一个统一的头文件中,如下:
#ifndef INTU_LOCATION_REQUEST_DEFINES_H
#define INTU_LOCATION_REQUEST_DEFINES_H
#import <Foundation/Foundation.h>
#import <CoreLocation/CoreLocation.h>
#if __has_feature(nullability)
# define __INTU_ASSUME_NONNULL_BEGIN NS_ASSUME_NONNULL_BEGIN
# define __INTU_ASSUME_NONNULL_END NS_ASSUME_NONNULL_END
# define __INTU_NULLABLE nullable
#else
# define __INTU_ASSUME_NONNULL_BEGIN
# define __INTU_ASSUME_NONNULL_END
# define __INTU_NULLABLE
#endif
#if __has_feature(objc_generics)
# define __INTU_GENERICS(type, ...) type<__VA_ARGS__>
#else
# define __INTU_GENERICS(type, ...) type
#endif
#ifdef NS_DESIGNATED_INITIALIZER
# define __INTU_DESIGNATED_INITIALIZER NS_DESIGNATED_INITIALIZER
#else
# define __INTU_DESIGNATED_INITIALIZER
#endif
static const CLLocationAccuracy kINTUHorizontalAccuracyThresholdCity = 5000.0; // in meters
static const CLLocationAccuracy kINTUHorizontalAccuracyThresholdNeighborhood = 1000.0; // in meters
static const CLLocationAccuracy kINTUHorizontalAccuracyThresholdBlock = 100.0; // in meters
static const CLLocationAccuracy kINTUHorizontalAccuracyThresholdHouse = 15.0; // in meters
static const CLLocationAccuracy kINTUHorizontalAccuracyThresholdRoom = 5.0; // in meters
static const NSTimeInterval kINTUUpdateTimeStaleThresholdCity = 600.0; // in seconds
static const NSTimeInterval kINTUUpdateTimeStaleThresholdNeighborhood = 300.0; // in seconds
static const NSTimeInterval kINTUUpdateTimeStaleThresholdBlock = 60.0; // in seconds
static const NSTimeInterval kINTUUpdateTimeStaleThresholdHouse = 15.0; // in seconds
static const NSTimeInterval kINTUUpdateTimeStaleThresholdRoom = 5.0; // in seconds
/** The possible states that location services can be in. */
typedef NS_ENUM(NSInteger, INTULocationServicesState) {
/** User has already granted this app permissions to access location services, and they are enabled and ready for use by this app.
Note: this state will be returned for both the "When In Use" and "Always" permission levels. */
INTULocationServicesStateAvailable,
/** User has not yet responded to the dialog that grants this app permission to access location services. */
INTULocationServicesStateNotDetermined,
/** User has explicitly denied this app permission to access location services. (The user can enable permissions again for this app from the system Settings app.) */
INTULocationServicesStateDenied,
/** User does not have ability to enable location services (e.g. parental controls, corporate policy, etc). */
INTULocationServicesStateRestricted,
/** User has turned off location services device-wide (for all apps) from the system Settings app. */
INTULocationServicesStateDisabled
};
/** The possible states that heading services can be in. */
typedef NS_ENUM(NSInteger, INTUHeadingServicesState) {
/** Heading services are available on the device */
INTUHeadingServicesStateAvailable,
/** Heading services are available on the device */
INTUHeadingServicesStateUnavailable,
};
/** A unique ID that corresponds to one location request. */
typedef NSInteger INTULocationRequestID;
/** A unique ID that corresponds to one heading request. */
typedef NSInteger INTUHeadingRequestID;
/** An abstraction of both the horizontal accuracy and recency of location data.
Room is the highest level of accuracy/recency; City is the lowest level. */
typedef NS_ENUM(NSInteger, INTULocationAccuracy) {
// 'None' is not valid as a desired accuracy.
/** Inaccurate (>5000 meters, and/or received >10 minutes ago). */
INTULocationAccuracyNone = 0,
// The below options are valid desired accuracies.
/** 5000 meters or better, and received within the last 10 minutes. Lowest accuracy. */
INTULocationAccuracyCity,
/** 1000 meters or better, and received within the last 5 minutes. */
INTULocationAccuracyNeighborhood,
/** 100 meters or better, and received within the last 1 minute. */
INTULocationAccuracyBlock,
/** 15 meters or better, and received within the last 15 seconds. */
INTULocationAccuracyHouse,
/** 5 meters or better, and received within the last 5 seconds. Highest accuracy. */
INTULocationAccuracyRoom,
};
/** An alias of the heading filter accuracy in degrees.
Specifies the minimum amount of change in degrees needed for a heading service update. Observers will not be notified of updates less than the stated filter value. */
typedef CLLocationDegrees INTUHeadingFilterAccuracy;
/** A status that will be passed in to the completion block of a location request. */
typedef NS_ENUM(NSInteger, INTULocationStatus) {
// These statuses will accompany a valid location.
/** Got a location and desired accuracy level was achieved successfully. */
INTULocationStatusSuccess = 0,
/** Got a location, but the desired accuracy level was not reached before timeout. (Not applicable to subscriptions.) */
INTULocationStatusTimedOut,
// These statuses indicate some sort of error, and will accompany a nil location.
/** User has not yet responded to the dialog that grants this app permission to access location services. */
INTULocationStatusServicesNotDetermined,
/** User has explicitly denied this app permission to access location services. */
INTULocationStatusServicesDenied,
/** User does not have ability to enable location services (e.g. parental controls, corporate policy, etc). */
INTULocationStatusServicesRestricted,
/** User has turned off location services device-wide (for all apps) from the system Settings app. */
INTULocationStatusServicesDisabled,
/** An error occurred while using the system location services. */
INTULocationStatusError
};
/** A status that will be passed in to the completion block of a heading request. */
typedef NS_ENUM(NSInteger, INTUHeadingStatus) {
// These statuses will accompany a valid heading.
/** Got a heading successfully. */
INTUHeadingStatusSuccess = 0,
// These statuses indicate some sort of error, and will accompany a nil heading.
/** Heading was invalid. */
INTUHeadingStatusInvalid,
/** Heading services are not available on the device */
INTUHeadingStatusUnavailable
};
/**
A block type for a location request, which is executed when the request succeeds, fails, or times out.
@param currentLocation The most recent & accurate current location available when the block executes, or nil if no valid location is available.
@param achievedAccuracy The accuracy level that was actually achieved (may be better than, equal to, or worse than the desired accuracy).
@param status The status of the location request - whether it succeeded, timed out, or failed due to some sort of error. This can be used to
understand what the outcome of the request was, decide if/how to use the associated currentLocation, and determine whether other
actions are required (such as displaying an error message to the user, retrying with another request, quietly proceeding, etc).
*/
typedef void(^INTULocationRequestBlock)(CLLocation *currentLocation, INTULocationAccuracy achievedAccuracy, INTULocationStatus status);
/**
A block type for a heading request, which is executed when the request succeeds.
@param currentHeading The most recent current heading available when the block executes.
@param status The status of the request - whether it succeeded or failed due to some sort of error. This can be used to understand if any further action is needed.
*/
typedef void(^INTUHeadingRequestBlock)(CLHeading *currentHeading, INTUHeadingStatus status);
#endif /* INTU_LOCATION_REQUEST_DEFINES_H */
20.线程安全之@synchronized的用法
转自http://www.cnblogs.com/jukaiit/p/5570056.html
@synchronized 的作用是创建一个互斥锁,保证此时没有其它线程对self对象进行修改。这个是objective-c的一个锁定令牌,防止self对象在同一时间内被其它线程访问,起到线程的保护作用。
例如:一个电影院,有3个售票员。一场电影的总数量固定。3个售票员售票时,要判断是非还有余票。
#import "ViewController.h"
@interface ViewController ()
/** 售票员01 */
@property (nonatomic, strong) NSThread *thread01;
/** 售票员02 */
@property (nonatomic, strong) NSThread *thread02;
/** 售票员03 */
@property (nonatomic, strong) NSThread *thread03;
/** 票的总数 */
@property (nonatomic, assign) NSInteger ticketCount;
/** 锁对象 */
//@property (nonatomic, strong) NSObject *locker;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// self.locker = [[NSObject alloc] init];
self.ticketCount = 100;
self.thread01 = [[NSThread alloc] initWithTarget:self selector:@selector(saleTicket) object:nil];
self.thread01.name = @"售票员01";
self.thread02 = [[NSThread alloc] initWithTarget:self selector:@selector(saleTicket) object:nil];
self.thread02.name = @"售票员02";
self.thread03 = [[NSThread alloc] initWithTarget:self selector:@selector(saleTicket) object:nil];
self.thread03.name = @"售票员03";
}
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
[self.thread01 start];
[self.thread02 start];
[self.thread03 start];
}
- (void)saleTicket
{
while (1) {
@synchronized(self) {
// 先取出总数
NSInteger count = self.ticketCount;
if (count > 0) {
self.ticketCount = count - 1;
NSLog(@"%@卖了一张票,还剩下%zd张", [NSThread currentThread].name, self.ticketCount);
} else {
NSLog(@"票已经卖完了");
break;
}
}
}
}
@end
21.获取顶层控制器
- (UIViewController *)topViewController {
UIViewController *resultVC;
resultVC = [self _topViewController:[[UIApplication sharedApplication].keyWindow rootViewController]];
while (resultVC.presentedViewController) {
resultVC = [self _topViewController:resultVC.presentedViewController];
}
return resultVC;
}
- (UIViewController *)_topViewController:(UIViewController *)vc {
if ([vc isKindOfClass:[UINavigationController class]]) {
return [self _topViewController:[(UINavigationController *)vc topViewController]];
} else if ([vc isKindOfClass:[UITabBarController class]]) {
return [self _topViewController:[(UITabBarController *)vc selectedViewController]];
} else {
return vc;
}
return nil;
}
//使用方法
UIViewController *topmostVC = [self topViewController];