SWIFT Closure syntax - convert from Objective C - objective-c

I have the following function written in Objective C using blocks and I am trying to convert it to swift, but I am banging my head against the wall and can't get it sorted.
Here is the code in Objective C
typedef void (^ResponseBlock) (BOOL succeeded, NSArray* data);
- (void)findAllMediaFromDate:(NSDate*)createdAtDate block:(ResponseBlock)block
{
NSMutableArray *results = [NSMutableArray array];
PFQuery *query = [PFQuery queryWithClassName:PARSE_MODEL_ACTIVITIES];
[query orderByDescending:#"createdAt"];
[query findObjectsInBackgroundWithBlock:^(NSArray *objects, NSError *error) {
if (!error) {
for (ActivityObject *object in objects) {
if ([object.media.type isEqualToString: #"video"] || [object.media.type isEqualToString: #"photo"]) {
[results addObject:object];
}
}
block(YES, results);
}
else {
}
}];
}
Now here is what I have in SWIFT. It's a different function body, but the syntax I am trying is the same...
func userQuery (){ //This needs to return an array as ABOVE
var results = [UserModel]()
println("NM: userQuery")
var query = UserModel.query()
query.whereKey("objectId", equalTo:"7N0IWUffOZ")
query.findObjectsInBackgroundWithBlock { (objects:[AnyObject]!, error:NSError!) -> Void in
if (objects != nil) {
NSLog("yea")
for object in objects{
results.append(object as UserModel)
//Need to return the array to the userQuery function
}
} else {
NSLog("%#", error)
}
}
}
```

You can add the closure parameter like so:
func userQuery(completionHandler: (succeeded: Bool, array: [UserModel]?) -> ()) {
// do your other stuff here
if objects != nil {
// build results
completionHandler(succeeded: true, array: results)
} else {
// return failure
completionHandler(succeeded: false, array: nil)
}
}
Clearly, change your array parameter to be whatever you want (rename it, change the type, whatever), but the basic idea is to have an optional array return type.
And you can call it using the trailing closure syntax, like so:
userQuery() {
success, results in
if success {
// use results here
} else {
// handle error here
}
}
By the way, if you like the Objective-C typedef pattern, the equivalent in Swift is typealias:
typealias ResponseClosure = (succeeded: Bool, array: [UserModel]?) -> ()
func userQuery(completionHandler: ResponseClosure) {
// the same as above
}

Related

How to extract performance metrics measured by measureBlock in XCTest

I have a simple test function which will tap a button an measure the performance. I'm using XCTest. After measureBlock returns I can see a bunch of perf-metrics on the console. I would like to get this within the test-program such that I can populate the data somewhere else programmatically. Watching the test data on test-console is proving to be slow because I have a lot of test-cases.
- (void)testUseMeasureBlock {
XCUIElement *launchTest1Button = [[XCUIApplication alloc] init].buttons[#"Launch Test 1"];
void (^blockToMeasure)(void) = ^void(void) {
[launchTest1Button tap];
};
// Run once to warm up any potential caching properties
#autoreleasepool {
blockToMeasure();
}
// Now measure the block
[self measureBlock:blockToMeasure];
/// Collect the measured metrics and send somewhere.
When we run a test it prints:
measured [Time, seconds] average: 0.594, relative standard deviation: 0.517%, values: [0.602709, 0.593631, 0.593004, 0.592350, 0.596199, 0.593807, 0.591444, 0.593460, 0.592648, 0.592769],
If I could get the average time, that'd be sufficient for now.
Since there is no API to get this data you can pipe stderr stream and parse tests logs to get needed info e.g. average time. For instance you can use next approach:
#interface MeasureParser : NSObject
#property (nonatomic) NSPipe* pipe;
#property (nonatomic) NSRegularExpression* regex;
#property (nonatomic) NSMutableDictionary* results;
#end
#implementation MeasureParser
- (instancetype)init {
self = [super self];
if (self) {
self.pipe = NSPipe.pipe;
self.results = [NSMutableDictionary new];
let pattern = [NSString stringWithFormat:#"[^']+'\\S+\\s([^\\]]+)\\]'\\smeasured\\s\\[Time,\\sseconds\\]\\saverage:\\s([^,]+)"];
NSError* error = nil;
self.regex = [NSRegularExpression regularExpressionWithPattern:pattern options:NSRegularExpressionCaseInsensitive error:&error];
if (error) {
return nil;
}
}
return self;
}
- (void)capture:(void (^)(void))block {
// Save original output
int original = dup(STDERR_FILENO);
setvbuf(stderr, nil, _IONBF, 0);
dup2(self.pipe.fileHandleForWriting.fileDescriptor, STDERR_FILENO);
__weak let wself = self;
self.pipe.fileHandleForReading.readabilityHandler = ^(NSFileHandle *handle) {
var *str = [[NSString alloc] initWithData:handle.availableData encoding:NSUTF8StringEncoding];
let firstMatch = [wself.regex firstMatchInString:str options:NSMatchingReportCompletion range:NSMakeRange(0, str.length)];
if (firstMatch) {
let name = [str substringWithRange:[firstMatch rangeAtIndex:1]];
let average = [str substringWithRange:[firstMatch rangeAtIndex:2]];
wself.results[name] = average;
}
// Print to stdout because stderr is piped
printf("%s", [str cStringUsingEncoding:NSUTF8StringEncoding]);
};
block();
// Revert
fflush(stderr);
dup2(original, STDERR_FILENO);
close(original);
}
#end
How to use:
- (void)testPerformanceExample {
let measureParser = [MeasureParser new];
[measureParser capture:^{
[self measureBlock:^{
// Put the code you want to measure the time of here.
sleep(1);
}];
}];
NSLog(#"%#", measureParser.results);
}
// Outputs
{
testPerformanceExample = "1.001";
}
Swift 5 version
final class MeasureParser {
let pipe: Pipe = Pipe()
let regex: NSRegularExpression?
let results: NSMutableDictionary = NSMutableDictionary()
init() {
self.regex = try? NSRegularExpression(
pattern: "\\[(Clock Monotonic Time|CPU Time|Memory Peak Physical|Memory Physical|CPU Instructions Retired|Disk Logical Writes|CPU Cycles), (s|kB|kI|kC)\\] average: ([0-9\\.]*),",
options: .caseInsensitive)
}
func capture(completion: #escaping () -> Void) {
let original = dup(STDERR_FILENO)
setvbuf(stderr, nil, _IONBF, 0)
dup2(self.pipe.fileHandleForWriting.fileDescriptor, STDERR_FILENO)
self.pipe.fileHandleForReading.readabilityHandler = { [weak self] handle in
guard self != nil else { return }
let data = handle.availableData
let str = String(data: data, encoding: .utf8) ?? "<Non-ascii data of size\(data.count)>\n"
self!.fetchAndSaveMetrics(str)
// Print to stdout because stderr is piped
if let copy = (str as NSString?)?.cString(using: String.Encoding.utf8.rawValue) {
print("\(copy)")
}
}
completion()
fflush(stderr)
dup2(original, STDERR_FILENO)
close(original)
}
private func fetchAndSaveMetrics(_ str: String) {
guard let mRegex = self.regex else { return }
let matches = mRegex.matches(in: str, options: .reportCompletion, range: NSRange(location: 0, length: str.count))
matches.forEach {
let nameIndex = Range($0.range(at: 1), in: str)
let averageIndex = Range($0.range(at: 3), in: str)
if nameIndex != nil && averageIndex != nil {
let name = str[nameIndex!]
let average = str[averageIndex!]
self.results[name] = average
}
}
}
}
How to use it:
import XCTest
final class MyUiTests: XCTestCase {
var app: XCUIApplication!
let measureParser = MeasureParser()
// MARK: - XCTestCase
override func setUp() {
super.setUp()
continueAfterFailure = false
app = XCUIApplication()
app.launch()
}
override func tearDown() {
//FIXME: Just for debugging
print(self.measureParser.results)
print(self.measureParser.results["CPU Cycles"])
print(self.measureParser.results["CPU Instructions Retired"])
print(self.measureParser.results["CPU Time"])
print(self.measureParser.results["Clock Monotonic Time"])
print(self.measureParser.results["Disk Logical Writes"])
print(self.measureParser.results["Memory Peak Physical"])
print(self.measureParser.results["Memory Physical"])
}
// MARK: - Tests
func testListing() {
self.measureParser.capture { [weak self] in
guard let self = self else { return }
self.measureListingScroll()
}
}
// MARK: XCTest measures
private func measureListingScroll() {
measure(metrics: [XCTCPUMetric(), XCTClockMetric(), XCTMemoryMetric(), XCTStorageMetric()]) {
self.app.swipeUp()
self.app.swipeUp()
self.app.swipeUp()
}
}
}
There's a private instance variable __perfMetricsForID of XCTestCase store the result.
And you can access it by call
NSDictionary* perfMetrics = [testCase valueForKey:#"__perfMetricsForID"];
the result is just like this:

Comparing boolean pointer with an integer Warning

I've got a method that populated a prototype cell and two warnings are being throw and I'm not sure what to do.
First I am 'converting incompatible integer to pointer initializing BOOL...' With my declaration :
BOOL *isAVideo = [[feedItem objectForKey:#"isAVideo"]boolValue];
Second, when I use that variable in a comparison to check the object's value:
if (isAVideo == 1)
The warning states 'Comparison between pointer and integer'.
Here is the method for context:
- (void)setFeedItem:(PFObject *)feedItem
{
_feedItem = feedItem;
PFUser *user = [feedItem objectForKey:#"user"];
BOOL *isAVideo = [[feedItem objectForKey:#"isAVideo"]boolValue];
[_usernameButton setTitle:user.username
forState:UIControlStateNormal];
_captionView.text = [feedItem objectForKey:#"desc"];
_timestampLabel.text = [NSDate cks_stringForTimeSinceDate:feedItem.createdAt];
PFQuery *query = [PFQuery queryWithClassName:#"tfeed"];
[query getFirstObjectInBackgroundWithBlock:^(PFObject *object, NSError *error) {
if (object) {
// Object Found
NSLog(#"object found");
BOOL isVideo = [[object objectForKey:#"isAVideo"]boolValue];
NSLog(#"is a video: %d", isVideo);
if (isAVideo == 1) {
NSLog(#"object is a video");
}
BOOL is a primitive type, so remove the asterisk.
BOOL isAVideo = [[feedItem objectForKey:#"isAVideo"]boolValue];
To check this in an if statement use any the following:
if (isAVideo != NO) {
//true
}
if (isAVideo == YES) {
//true
}
if (isAVideo) {
//true
}
if (isAVideo == NO) {
//false
}
if (!isAVideo) {
//false
}

Translation help needed from SWIFT to Objective C "healthStore.requestAuthorizationToShareTypes"

Unfortunately Apple do not translate their new examples to Objective C. I have a working SWIFT code fragment, but my translation to objective C is not working - The authorisation request does not appear in the objective c code on the iPhone -
SWIFT:
class InterfaceController: WKInterfaceController {
let healthStore = HKHealthStore()
override func willActivate() {
super.willActivate()
guard let quantityType = HKQuantityType.quantityTypeForIdentifier(HKQuantityTypeIdentifierHeartRate) else {
return
}
let dataTypes = Set(arrayLiteral: quantityType)
healthStore.requestAuthorizationToShareTypes(nil, readTypes: dataTypes) { (success, error) -> Void in
if success == false {
}
}
}
}
Objective-C:
#interface InterfaceController()
#property HKHealthStore * healthScore;
#end
#implementation InterfaceController
- (void)willActivate {
[super willActivate];
NSString * quantity = HKQuantityTypeIdentifierHeartRate;
HKQuantityType * quantityType = [HKQuantityType quantityTypeForIdentifier:quantity];
NSSet <HKQuantityType *> * dataTypes = [NSSet setWithArray:#[quantityType]];
[self.healthScore requestAuthorizationToShareTypes:nil readTypes:dataTypes completion:^(BOOL success, NSError * _Nullable error) {
if (!success) { } }];
}
#end
You are missing creating healthScore prior to using it.
let healthStore = HKHealthStore() creates an instance.
// Missing initialization
self.healthScore = [HKHealthStore new];
...
[self.healthScore requestAuthorizationToShareTypes:nil readTypes:dataTypes completion:^(BOOL success, NSError * _Nullable error) {

Uploading multiple files to a single row parse.com

So I'm using the following code to upload multiple files to a single row of a class.
for (NSString* currentString in directoryContents){
NSLog(#"%#",currentString);
NSString *temp2 = [temp stringByAppendingPathComponent:currentString];
PFFile *file = [PFFile fileWithName:currentString contentsAtPath:temp2];
[file saveInBackground];
PFObject *obj = [PFObject objectWithClassName:#"DreamBits"];
if ([currentString isEqualToString:#"index.html"]) {
[obj setObject:file forKey:#"index"];
}
else {
count += 1;
NSString *filen = [NSString stringWithFormat:#"file%i",count];
NSLog(#"%#",filen);
[obj setObject:file forKey:filen];
}
[obj saveInBackground];
}
The issue is I'm getting the files in different rows for some reason. Any idea how I can correct this?
I am modified your code little bit. I am not run this code but hope it helps you.
PFObject *obj = [PFObject objectWithClassName:#"DreamBits"];
for (NSString* currentString in directoryContents){
NSLog(#"%#",currentString);
NSString *temp2 = [temp stringByAppendingPathComponent:currentString];
PFFile *file = [PFFile fileWithName:currentString contentsAtPath:temp2];
if ([currentString isEqualToString:#"index.html"]) {
[obj setObject:file forKey:#"index"];
}
else {
count += 1;
NSString *filen = [NSString stringWithFormat:#"file%i",count];
NSLog(#"%#",filen);
[obj setObject:file forKey:filen];
}
}
[obj saveInBackground];
Create the PFObject outside the loop.Set all the PFFile object to the PFObject inside the loop.
After loop, save the PFObject. It is better to use the method :
[obj saveInBackgroundWithBlock:^(BOOL succeeded, NSError *error)
{
//Check whether the upload is succeeded or not
}];
I have used this method for upload profile and thumb image in a row in the Parse Server table. Using swift 4 and Parse SDK 1.17.1. Hope this technic will help.
func uploadImage(profileImage: UIImage, profileImageName: String, thumbImage: UIImage, thumbImageName: String) {
if let profileImagefile = PFFile.init(name: profileImageName, data: profileImage.jpegData(compressionQuality: 1)!) {
let fileObject = PFObject(className:"ProfileImage")
fileObject.setValue(profileImagefile, forKey: "profile_image")
fileObject.saveInBackground { (success, error) in
if error == nil {
print("thumb image path: \(profileImagefile.url ?? "")")
if let thumbImage = PFFile.init(name: thumbImageName, data: thumbImage.jpegData(compressionQuality: 0.5)!) {
fileObject.setValue(thumbImage, forKey: "thumb_image")
fileObject.saveInBackground(block: { (result, fileError) in
if fileError == nil {
print("thumb image path: \(thumbImage.url ?? "")")
}else {
print("error on thumb upload: \(result)")
}
})
}
}else {
print("error on file upload: \(error.debugDescription)")
}
}
}
}

Converting Objective-C Block into Closure the Swift way

I have successfully converted the following method from Obj-C to Swift:
After learning how blocks are replaced by closures in Swift.
Obj-C:
- (RACSignal *)fetchCurrentConditionsForLocation:(CLLocationCoordinate2D)coordinate {
NSString *urlString = [NSString stringWithFormat:#"http://api.openweathermap.org/data/2.5/weather?lat=%f&lon=%f&units=imperial", coordinate.latitude, coordinate.longitude];
NSURL *url = [NSURL URLWithString:urlString];
return [[self fetchJSONFromURL:url] map:^(NSDictionary *json) {
return [MTLJSONAdapter modelOfClass:[WXCondition class] fromJSONDictionary:json error:nil];
}];
}
Swift:
func fetchCurrentConditionsForLocation(coordinate: CLLocationCoordinate2D) -> RACSignal {
let urlString = NSString(format: "http://api.openweathermap.org/data/2.5/weather?lat=%f&lon=%f&units=metric", coordinate.latitude, coordinate.longitude)
let url = NSURL.URLWithString(urlString)
return fetchJSONFromURL(url).map { json in
return MTLJSONAdapter.modelOfClass(WXCondition.self, fromJSONDictionary: json as NSDictionary, error: nil)
}
}
However, I'm having trouble converting the following return map block to Swift:
func fetchHourlyForecastForLocation(coordinate: CLLocationCoordinate2D) -> RACSignal {
var urlString = NSString(format: "http://api.openweathermap.org/data/2.5/forecast?lat=%f&lon=%f&units=metric&cnt=12", coordinate.latitude, coordinate.longitude)
let url = NSURL.URLWithString(urlString)
/* Original Obj-C:
return [[self fetchJSONFromURL:url] map:^(NSDictionary *json) {
RACSequence *list = [json[#"list"] rac_sequence];
return [[list map:^(NSDictionary *item) {
return [MTLJSONAdapter modelOfClass:[WXCondition class] fromJSONDictionary:item error:nil];
}] array];
}];
}
*/
// My attempt at conversion to Swift
// (I can't resolve `rac_sequence` properly). Kind of confused
// as to how to cast it properly and return
// it as an "rac_sequence" array.
return fetchJSONFromURL(url).map { json in
let list = RACSequence()
list = [json["list"] rac_sequence]
return (list).map { item in {
return MTLJSONAdapter.modelOfClass(WXCondition.self, fromJSONDictionary: item as NSDictionary, error: nil)
} as NSArray
}
}
If it helps, this is what rac_sequence is:
- (RACSequence *)rac_sequence {
return [RACArraySequence sequenceWithArray:self offset:0];
}
RACArraySequence():
+ (instancetype)sequenceWithArray:(NSArray *)array offset:(NSUInteger)offset;
EDIT: The fetch method returns a RACSignal not an NSArray:
func fetchJSONFromURL(url: NSURL) -> RACSignal {
}
It looks like you simply forgot the return type in the function declaration. The declaration should look something like this:
func fetchHourlyForecastForLocation(coordinate: CLLocationCoordinate2D) -> RACSignal { //...rest of function
Because the return type is now named you can then remove the as NSArray cast in your return statement at the end of the function. Hope this helps!
Try this :
func fetchHourlyForecastForLocation(coordinate: CLLocationCoordinate2D,completion:(AnyObject) -> Void) {
//after getting the NSArray (maping)
completion(_array)
}